* [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry
@ 2025-06-20 21:11 Ada Couprie Diaz
2025-06-20 21:11 ` [PATCH v4 01/13] arm64: debug: clean up single_step_handler logic Ada Couprie Diaz
` (13 more replies)
0 siblings, 14 replies; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:11 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Hi,
This series simplifies the debug exception entry path by removing handler
registration mechanisms for the debug exception handlers, a holdover from
the arm kernel, as well as the break and stepping handlers.
This moves much of the code related to debug exceptions outside of
`mm/fault.c` where it didn't make much sense.
This allows us to split the debug exception entries: going from one common
path per EL for all debug exceptions to a unique one per exception and EL.
The result is a much simpler and fully static exception entry path, which
we tailor to the different exceptions and their constraints.
The series is structured as such :
1 : related clean-up in the signle step handler
2 : related refactor of `aarch32_break_handler()`
3-5 : software breakpoints and single step handlers registration removal
6: preparatory function move that is made internal in patch 13
8: preparatory refactor of `reinstall_suspended_bps()`
7, 9-13 : debug exception splitting and handler registration removal
* Patch 3 copies and extends the `early_brk64` break handling for the
normal path, byassing the dynamic registration.
* Patch 4 does something similar for the single stepping handlers.
* Patches 7, and 9 through 13 split each individual debug exception from
the unified path by creating specific handlers, adapting them to
their constraints and calling into them, bypassing the dynamically
registered handlers used before.
* Patch 8 refactors `reinstall_suspended_bps()` in preparation for the
single-step exception entry split, as it is moved out of the handler.
This could be squashed in patch 9 (the single-step exception split), but
I opted to separate it for clarity (and because the commit message
for patch 9 is already 45 lines long !).
* Patches 5 and 13 are clean-ups removing the code that has been replaced
and made redundant by the preceding patches.
PREEMPT_RT
===
Of note, this allows us to make the single exception handling mostly
preemptible coming from EL0 in patch 9, fixing an issue with PREEMPT_RT[0].
The commit message details the reasoning on why this should be safe.
It is *definitely* not preemptible at EL1 in the current state, given
that the EL1 software step exception is handled by KGDB.
We can do the same for the software break exception coming from EL0,
which works in a very similar way.
However, the hardware breakpoint and watchpoint exceptions also have
a sleeping while non-preemptible issue with PREEMPT_RT, but this is
much trickier to fix, so this won't be fixed in this series.
A bit more details in [1].
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
(I don't really know what is the best thing to do if a tag is added
on the cover, so I added it here as well as for the whole series.)
As the EL0 and EL1 code paths are now split for the BRK64 and single-step
handlers, I added a comment to the EL0 paths to highlight that they
are preemptible with interrupts unmasked, and to be wary when changing them.
Happy for it to be dropped if it is not considered relevant or useful !
Testing
===
Testing EL1 debug exceptions can only be properly done with perf.
A simple test of hardware breakpoints, watchpoints, and software stepping
can be achieved by using `perf stat sleep 0.01` and adding hardware events
for `jiffies` and `hrtimer_nanosleep`, which guarantees a
hardware watchpoint and breakpoint.
Inserting a `WARN()` at a convenient place will allow testing both early
and late software breakpoints at EL1, or using KGDB to test without
changing code.
For EL0 debug exceptions, the easiest is to setup a basic program and use
GDB with a list of pre-programmed commands setting hardware and software
breakpoints as well as watchpoints. Setpping will occur naturally when
encountering breakpoints.
All tests maintained behaviour after the patches.
I also tested with KASAN on, with no apparent impact.
See below for some testing examples.
Regarding [0], I tested a PREEMPT_RT kernel with the patches, running
`ssdd` on loop as well as some spammy GDB stepping, and some
hardware watchpoints on a tight loop without any issues.
Note: `ssdd`[2], the test that highlighted the original bug, forks pairs
of tracer/tracee children. The tracer has a timeout waiting
for confirmation that its tracee process has been single stepped.
Given that the single step is now mostly preemptible, the step can be
delayed : possibly much more than what was usual previously.
Under heavy system load and by requesting the test to fork much more
children than its default, the test will start to fail unpredictably.
On a 4-core AMD Seattle board, I was able to see some inconsistent
failures with 32 (default 10) forks, becoming much more consistent
when the system load was above 16 (one or two failures per run).
Raising the timeout made the failures completely disappear, even with
a system load above 40, confirming that it is indeed a timeout issue.
Given that running `ssdd` with its default values does not seem to fail,
even with a system load of 21, so I do not feel like something
needs to be done to address this.
Based on v6.16-rc2.
Thanks,
Ada
v4 :
- Split `do_{softstep,brk}()` by EL : `do_el0_XX()` and `do_el1_XX()` (Mark)
- Remove now unused arch_init_uprobes (Anshuman)
- Reword patch 3 summary (Anshuman)
- Rename `XX_singlestep_handler()` handlers to `XX_single_step_handler()`
(Anshuman, Mark)
- Drop `NOKRPOBE_SYMBOL()` for EL0 paths where relevant (Mark)
- Call `die()` directly in `do_el1_brk64()` instead of `pr_warn()`
and `arm64_notify_die()`. (Mark)
- Add comments to `do_el0_{softstep,brk}()` handlers highlighting
that they are called with unmasked interrupts and preemption enabled,
in cases changes need to be made in the future.
- Added tags from v3.
- Rebased on v6.16-rc2
v3 : https://lore.kernel.org/linux-arm-kernel/20250609173413.132168-1-ada.coupriediaz@arm.com
- Refactor `aarch32_break_handler()` to be consistent with other
`el0_undef()` tentative handlers and the refactored
`reinstall_suspended_bps()` later in the series.
- Drop the intermediate handlers for hardware breakpoint and watchpoint
exceptions, as they both only return 0 and never end up calling
`arm64_notify_die()`. Make the real handlers return void, rename them
`do_{break,watch}point()` so that they can be called directly from
`entry-common.c`. This is similar to what was done for the single-step
handler.
- Add static inline stand-ins for disabled stepping handlers. (Will)
- Don't duplicate `debug_exception_enter()` and `debug_exception_exit()`
in patch 06 (Will)
- Move them to `kernel/entry-common.c` and make them externally visible
while `do_debug_exception()` in `mm/fault.c` needs them
- Make them static again when cleaning up in patch 11.
- Refactor `reinstall_suspended_bps()` to make the logic cleaner and easier
to understand (Will, Mark).
- Be more explicit in the commit messages about the goal and safety of
moving `arm64_apply_bp_hardening()` to `kernel/entry-common.c` and
calling it before `enter_from_user_mode` in patches 07 and 09 (Will)
- Enable preemption and unmask interrupts early for the brk64 handler,
fixing a PREEMPT_RT sleeping while non-preemptible issue (Luis)
- Mention in patch 13 commit message that `bug_brk_handler()` is
not called as a fall-through anymore in early boot, but that it doesn't
change the actual behaviour (Will)
- Scope FAR_EL1 comment (Will)
- Fix typos (Will)
- Rebase on v6.16-rc1
- Update the UBSAN BRK ESR check to `esr_is_ubsan_brk(esr)`.
v2 : https://lore.kernel.org/linux-arm-kernel/20250512174326.133905-1-ada.coupriediaz@arm.com
- Move the BP hardening call outside of the handlers to `entry-common`,
as they are not needed coming from EL1.
- Make the EL0 software stepping exception mostly preemptible
- Move `reinstall_hw_bps()` call to `entry-common`
- Don't disable preemption in `el0_softstp()`
- Unmask DAIF before `do_softstep()` in `el0_softstp()`
- Update comments to make sense with the changes
- Simplify the single step handler, as it always returns 0 and could not
trigger the `arm64_notify_die()` call.
- Fix some commit messages
v1 : https://lore.kernel.org/linux-arm-kernel/20250425153647.438508-1-ada.coupriediaz@arm.com
[0]: https://lore.kernel.org/linux-arm-kernel/Z6YW_Kx4S2tmj2BP@uudg.org/
[1]: https://lore.kernel.org/linux-arm-kernel/e86c5c3a-6666-46a7-b7ec-e803212a81a1@arm.com/
[2]: https://man.archlinux.org/man/ssdd.8.en
Testing examples
===
Perf (for EL1):
~~~
Assuming that `perf` is on your $PATH and building with `kallsyms`
#!/bin/bash
watch_addr=$(sudo cat /proc/kallsyms | grep "D jiffies$" | cut -f1 -d\ )
break_addr=$(sudo cat /proc/kallsyms | grep "clock_nanosleep$" | cut -f1 -d\ )
cmd="sleep 0.01"
sudo perf stat -a -e mem:0x${watch_addr}/8:w -e mem:0x${break_addr}:x ${cmd}
NB: This does /not/ test EL1 BRKs.
GDB commands (for EL0):
~~~
The following C example, compiled with `-g -O0`
int main() {
int add = 0xAA;
int target = 0;
target += add;
#ifdef COMPAT
__asm__("BKPT");
#else
__asm__("BRK 1");
#endif
return target;
}
Combined with the following GDB command-list
start
hbreak 3
watch target
commands 2
continue
end
commands 3
continue
end
continue
jump 11
continue
quit
Executed as such : `gdb -x ${COMMAND_LIST_FILE} ./a.out`
should go through the whole program, return 0252/170/0xAA, and
exercise all EL0 debug exception entries.
By using a cross-compiler and passing and additional `-DCOMPAT` argument
during compilation, the `BKPT32` path can also be tested.
NOTE: `BKPT` *will* make GDB loop infinitely, that is expected. Sending
SIGINT to GDB will break the loop and the execution should complete.
Ada Couprie Diaz (13):
arm64: debug: clean up single_step_handler logic
arm64: refactor aarch32_break_handler()
arm64: debug: call software breakpoint handlers statically
arm64: debug: call step handlers statically
arm64: debug: remove break/step handler registration infrastructure
arm64: entry: Add entry and exit functions for debug exceptions
arm64: debug: split hardware breakpoint exception entry
arm64: debug: refactor reinstall_suspended_bps()
arm64: debug: split single stepping exception entry
arm64: debug: split hardware watchpoint exception entry
arm64: debug: split brk64 exception entry
arm64: debug: split bkpt32 exception entry
arm64: debug: remove debug exception registration infrastructure
arch/arm64/include/asm/debug-monitors.h | 34 +--
arch/arm64/include/asm/exception.h | 8 +-
arch/arm64/include/asm/kgdb.h | 12 +
arch/arm64/include/asm/kprobes.h | 8 +
arch/arm64/include/asm/system_misc.h | 4 -
arch/arm64/include/asm/traps.h | 6 +
arch/arm64/include/asm/uprobes.h | 11 +
arch/arm64/kernel/debug-monitors.c | 255 +++++++-----------
arch/arm64/kernel/entry-common.c | 146 +++++++++-
arch/arm64/kernel/hw_breakpoint.c | 60 ++---
arch/arm64/kernel/kgdb.c | 39 +--
arch/arm64/kernel/probes/kprobes.c | 31 +--
arch/arm64/kernel/probes/kprobes_trampoline.S | 2 +-
arch/arm64/kernel/probes/uprobes.c | 24 +-
arch/arm64/kernel/traps.c | 79 +-----
arch/arm64/mm/fault.c | 75 ------
16 files changed, 327 insertions(+), 467 deletions(-)
base-commit: e04c78d86a9699d136910cfc0bdcf01087e3267e
--
2.43.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v4 01/13] arm64: debug: clean up single_step_handler logic
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
@ 2025-06-20 21:11 ` Ada Couprie Diaz
2025-06-27 15:44 ` Will Deacon
2025-06-20 21:11 ` [PATCH v4 02/13] arm64: refactor aarch32_break_handler() Ada Couprie Diaz
` (12 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:11 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Remove the unnecessary boolean which always checks if the handler was found
and return early instead.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
Acked-by: Mark Rutland <mark.rutland@arm.com>
---
arch/arm64/kernel/debug-monitors.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 58f047de3e1c..676fa0231935 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -241,8 +241,6 @@ static void send_user_sigtrap(int si_code)
static int single_step_handler(unsigned long unused, unsigned long esr,
struct pt_regs *regs)
{
- bool handler_found = false;
-
/*
* If we are stepping a pending breakpoint, call the hw_breakpoint
* handler first.
@@ -250,10 +248,10 @@ static int single_step_handler(unsigned long unused, unsigned long esr,
if (!reinstall_suspended_bps(regs))
return 0;
- if (!handler_found && call_step_hook(regs, esr) == DBG_HOOK_HANDLED)
- handler_found = true;
+ if (call_step_hook(regs, esr) == DBG_HOOK_HANDLED)
+ return 0;
- if (!handler_found && user_mode(regs)) {
+ if (user_mode(regs)) {
send_user_sigtrap(TRAP_TRACE);
/*
@@ -263,7 +261,7 @@ static int single_step_handler(unsigned long unused, unsigned long esr,
* to the active-not-pending state).
*/
user_rewind_single_step(current);
- } else if (!handler_found) {
+ } else {
pr_warn("Unexpected kernel single-step exception at EL1\n");
/*
* Re-enable stepping since we know that we will be
base-commit: e04c78d86a9699d136910cfc0bdcf01087e3267e
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 02/13] arm64: refactor aarch32_break_handler()
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
2025-06-20 21:11 ` [PATCH v4 01/13] arm64: debug: clean up single_step_handler logic Ada Couprie Diaz
@ 2025-06-20 21:11 ` Ada Couprie Diaz
2025-06-27 15:45 ` Will Deacon
2025-06-20 21:11 ` [PATCH v4 03/13] arm64: debug: call software breakpoint handlers statically Ada Couprie Diaz
` (11 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:11 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
`aarch32_break_handler()` is called in `do_el0_undef()` when we
are trying to handle an exception whose Exception Syndrome is unknown.
It checks if the instruction hit might be a 32-bit arm break (be it
A32 or T2), and sends a SIGTRAP to userspace if it is so that it can
be handled.
However, this is badly represented in the naming of the function, and
is not consistent with the other functions called with the same logic
in `do_el0_undef()`.
Rename it `try_handle_aarch32_break()` and change the return value to
a boolean to align with the logic of the other tentative handlers in
`do_el0_undef()`, the previous error code being ignored anyway.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
Acked-by: Mark Rutland <mark.rutland@arm.com>
---
arch/arm64/include/asm/debug-monitors.h | 2 +-
arch/arm64/kernel/debug-monitors.c | 10 +++++-----
arch/arm64/kernel/traps.c | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/include/asm/debug-monitors.h b/arch/arm64/include/asm/debug-monitors.h
index 8f6ba31b8658..e1caf6a8380c 100644
--- a/arch/arm64/include/asm/debug-monitors.h
+++ b/arch/arm64/include/asm/debug-monitors.h
@@ -116,7 +116,7 @@ static inline int reinstall_suspended_bps(struct pt_regs *regs)
}
#endif
-int aarch32_break_handler(struct pt_regs *regs);
+bool try_handle_aarch32_break(struct pt_regs *regs);
void debug_traps_init(void);
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 676fa0231935..9e69f2e915e8 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -334,7 +334,7 @@ static int brk_handler(unsigned long unused, unsigned long esr,
}
NOKPROBE_SYMBOL(brk_handler);
-int aarch32_break_handler(struct pt_regs *regs)
+bool try_handle_aarch32_break(struct pt_regs *regs)
{
u32 arm_instr;
u16 thumb_instr;
@@ -342,7 +342,7 @@ int aarch32_break_handler(struct pt_regs *regs)
void __user *pc = (void __user *)instruction_pointer(regs);
if (!compat_user_mode(regs))
- return -EFAULT;
+ return false;
if (compat_thumb_mode(regs)) {
/* get 16-bit Thumb instruction */
@@ -366,12 +366,12 @@ int aarch32_break_handler(struct pt_regs *regs)
}
if (!bp)
- return -EFAULT;
+ return false;
send_user_sigtrap(TRAP_BRKPT);
- return 0;
+ return true;
}
-NOKPROBE_SYMBOL(aarch32_break_handler);
+NOKPROBE_SYMBOL(try_handle_aarch32_break);
void __init debug_traps_init(void)
{
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index 9bfa5c944379..685c11b2c553 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -454,7 +454,7 @@ void do_el0_undef(struct pt_regs *regs, unsigned long esr)
u32 insn;
/* check for AArch32 breakpoint instructions */
- if (!aarch32_break_handler(regs))
+ if (try_handle_aarch32_break(regs))
return;
if (user_insn_read(regs, &insn))
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 03/13] arm64: debug: call software breakpoint handlers statically
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
2025-06-20 21:11 ` [PATCH v4 01/13] arm64: debug: clean up single_step_handler logic Ada Couprie Diaz
2025-06-20 21:11 ` [PATCH v4 02/13] arm64: refactor aarch32_break_handler() Ada Couprie Diaz
@ 2025-06-20 21:11 ` Ada Couprie Diaz
2025-06-27 15:45 ` Will Deacon
2025-06-20 21:11 ` [PATCH v4 04/13] arm64: debug: call step " Ada Couprie Diaz
` (10 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:11 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Software breakpoints pass an immediate value in ESR ("comment") that can
be used to call a specialized handler (KGDB, KASAN...).
We do so in two different ways :
- During early boot, `early_brk64` statically checks against known
immediates and calls the corresponding handler,
- During init, handlers are dynamically registered into a list. When
called, the generic software breakpoint handler will iterate over
the list to find the appropriate handler.
The dynamic registration does not provide any benefit here as it is not
exported and all its uses are within the arm64 tree. It also depends on an
RCU list, whose safe access currently relies on the non-preemptible state
of `do_debug_exception`.
Replace the list iteration logic in `call_break_hooks` to call
the breakpoint handlers statically if they are enabled, like in
`early_brk64`.
Expose the handlers in their respective headers to be reachable from
`arch/arm64/kernel/debug-monitors.c` at link time.
Unify the naming of the software breakpoint handlers to XXX_brk_handler(),
making it clear they are related and to differentiate from the
hardware breakpoints.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
---
arch/arm64/include/asm/kgdb.h | 3 +
arch/arm64/include/asm/kprobes.h | 8 +++
arch/arm64/include/asm/traps.h | 6 ++
arch/arm64/include/asm/uprobes.h | 2 +
arch/arm64/kernel/debug-monitors.c | 54 +++++++++++++----
arch/arm64/kernel/kgdb.c | 22 ++-----
arch/arm64/kernel/probes/kprobes.c | 31 ++--------
arch/arm64/kernel/probes/kprobes_trampoline.S | 2 +-
arch/arm64/kernel/probes/uprobes.c | 9 +--
arch/arm64/kernel/traps.c | 59 ++++---------------
10 files changed, 83 insertions(+), 113 deletions(-)
diff --git a/arch/arm64/include/asm/kgdb.h b/arch/arm64/include/asm/kgdb.h
index 21fc85e9d2be..82a76b2102fb 100644
--- a/arch/arm64/include/asm/kgdb.h
+++ b/arch/arm64/include/asm/kgdb.h
@@ -24,6 +24,9 @@ static inline void arch_kgdb_breakpoint(void)
extern void kgdb_handle_bus_error(void);
extern int kgdb_fault_expected;
+int kgdb_brk_handler(struct pt_regs *regs, unsigned long esr);
+int kgdb_compiled_brk_handler(struct pt_regs *regs, unsigned long esr);
+
#endif /* !__ASSEMBLY__ */
/*
diff --git a/arch/arm64/include/asm/kprobes.h b/arch/arm64/include/asm/kprobes.h
index be7a3680dadf..f2782560647b 100644
--- a/arch/arm64/include/asm/kprobes.h
+++ b/arch/arm64/include/asm/kprobes.h
@@ -41,4 +41,12 @@ void __kretprobe_trampoline(void);
void __kprobes *trampoline_probe_handler(struct pt_regs *regs);
#endif /* CONFIG_KPROBES */
+
+int __kprobes kprobe_brk_handler(struct pt_regs *regs,
+ unsigned long esr);
+int __kprobes kprobe_ss_brk_handler(struct pt_regs *regs,
+ unsigned long esr);
+int __kprobes kretprobe_brk_handler(struct pt_regs *regs,
+ unsigned long esr);
+
#endif /* _ARM_KPROBES_H */
diff --git a/arch/arm64/include/asm/traps.h b/arch/arm64/include/asm/traps.h
index 82cf1f879c61..e3e8944a71c3 100644
--- a/arch/arm64/include/asm/traps.h
+++ b/arch/arm64/include/asm/traps.h
@@ -29,6 +29,12 @@ void arm64_force_sig_fault_pkey(unsigned long far, const char *str, int pkey);
void arm64_force_sig_mceerr(int code, unsigned long far, short lsb, const char *str);
void arm64_force_sig_ptrace_errno_trap(int errno, unsigned long far, const char *str);
+int bug_brk_handler(struct pt_regs *regs, unsigned long esr);
+int cfi_brk_handler(struct pt_regs *regs, unsigned long esr);
+int reserved_fault_brk_handler(struct pt_regs *regs, unsigned long esr);
+int kasan_brk_handler(struct pt_regs *regs, unsigned long esr);
+int ubsan_brk_handler(struct pt_regs *regs, unsigned long esr);
+
int early_brk64(unsigned long addr, unsigned long esr, struct pt_regs *regs);
/*
diff --git a/arch/arm64/include/asm/uprobes.h b/arch/arm64/include/asm/uprobes.h
index 014b02897f8e..3659a79a9f32 100644
--- a/arch/arm64/include/asm/uprobes.h
+++ b/arch/arm64/include/asm/uprobes.h
@@ -28,4 +28,6 @@ struct arch_uprobe {
bool simulate;
};
+int uprobe_brk_handler(struct pt_regs *regs, unsigned long esr);
+
#endif
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 9e69f2e915e8..15d7158a7548 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -21,8 +21,11 @@
#include <asm/cputype.h>
#include <asm/daifflags.h>
#include <asm/debug-monitors.h>
+#include <asm/kgdb.h>
+#include <asm/kprobes.h>
#include <asm/system_misc.h>
#include <asm/traps.h>
+#include <asm/uprobes.h>
/* Determine debug architecture. */
u8 debug_monitors_arch(void)
@@ -299,20 +302,47 @@ void unregister_kernel_break_hook(struct break_hook *hook)
static int call_break_hook(struct pt_regs *regs, unsigned long esr)
{
- struct break_hook *hook;
- struct list_head *list;
-
- list = user_mode(regs) ? &user_break_hook : &kernel_break_hook;
-
- /*
- * Since brk exception disables interrupt, this function is
- * entirely not preemptible, and we can use rcu list safely here.
- */
- list_for_each_entry_rcu(hook, list, node) {
- if ((esr_brk_comment(esr) & ~hook->mask) == hook->imm)
- return hook->fn(regs, esr);
+ if (user_mode(regs)) {
+ if (IS_ENABLED(CONFIG_UPROBES) &&
+ esr_brk_comment(esr) == UPROBES_BRK_IMM)
+ return uprobe_brk_handler(regs, esr);
+ return DBG_HOOK_ERROR;
}
+ if (esr_brk_comment(esr) == BUG_BRK_IMM)
+ return bug_brk_handler(regs, esr);
+
+ if (IS_ENABLED(CONFIG_CFI_CLANG) && esr_is_cfi_brk(esr))
+ return cfi_brk_handler(regs, esr);
+
+ if (esr_brk_comment(esr) == FAULT_BRK_IMM)
+ return reserved_fault_brk_handler(regs, esr);
+
+ if (IS_ENABLED(CONFIG_KASAN_SW_TAGS) &&
+ (esr_brk_comment(esr) & ~KASAN_BRK_MASK) == KASAN_BRK_IMM)
+ return kasan_brk_handler(regs, esr);
+
+ if (IS_ENABLED(CONFIG_UBSAN_TRAP) && esr_is_ubsan_brk(esr))
+ return ubsan_brk_handler(regs, esr);
+
+ if (IS_ENABLED(CONFIG_KGDB)) {
+ if (esr_brk_comment(esr) == KGDB_DYN_DBG_BRK_IMM)
+ return kgdb_brk_handler(regs, esr);
+ if (esr_brk_comment(esr) == KGDB_COMPILED_DBG_BRK_IMM)
+ return kgdb_compiled_brk_handler(regs, esr);
+ }
+
+ if (IS_ENABLED(CONFIG_KPROBES)) {
+ if (esr_brk_comment(esr) == KPROBES_BRK_IMM)
+ return kprobe_brk_handler(regs, esr);
+ if (esr_brk_comment(esr) == KPROBES_BRK_SS_IMM)
+ return kprobe_ss_brk_handler(regs, esr);
+ }
+
+ if (IS_ENABLED(CONFIG_KRETPROBES) &&
+ esr_brk_comment(esr) == KRETPROBES_BRK_IMM)
+ return kretprobe_brk_handler(regs, esr);
+
return DBG_HOOK_ERROR;
}
NOKPROBE_SYMBOL(call_break_hook);
diff --git a/arch/arm64/kernel/kgdb.c b/arch/arm64/kernel/kgdb.c
index f3c4d3a8a20f..b5a3b9c85a65 100644
--- a/arch/arm64/kernel/kgdb.c
+++ b/arch/arm64/kernel/kgdb.c
@@ -234,21 +234,21 @@ int kgdb_arch_handle_exception(int exception_vector, int signo,
return err;
}
-static int kgdb_brk_fn(struct pt_regs *regs, unsigned long esr)
+int kgdb_brk_handler(struct pt_regs *regs, unsigned long esr)
{
kgdb_handle_exception(1, SIGTRAP, 0, regs);
return DBG_HOOK_HANDLED;
}
-NOKPROBE_SYMBOL(kgdb_brk_fn)
+NOKPROBE_SYMBOL(kgdb_brk_handler)
-static int kgdb_compiled_brk_fn(struct pt_regs *regs, unsigned long esr)
+int kgdb_compiled_brk_handler(struct pt_regs *regs, unsigned long esr)
{
compiled_break = 1;
kgdb_handle_exception(1, SIGTRAP, 0, regs);
return DBG_HOOK_HANDLED;
}
-NOKPROBE_SYMBOL(kgdb_compiled_brk_fn);
+NOKPROBE_SYMBOL(kgdb_compiled_brk_handler);
static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned long esr)
{
@@ -260,16 +260,6 @@ static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned long esr)
}
NOKPROBE_SYMBOL(kgdb_step_brk_fn);
-static struct break_hook kgdb_brkpt_hook = {
- .fn = kgdb_brk_fn,
- .imm = KGDB_DYN_DBG_BRK_IMM,
-};
-
-static struct break_hook kgdb_compiled_brkpt_hook = {
- .fn = kgdb_compiled_brk_fn,
- .imm = KGDB_COMPILED_DBG_BRK_IMM,
-};
-
static struct step_hook kgdb_step_hook = {
.fn = kgdb_step_brk_fn
};
@@ -316,8 +306,6 @@ int kgdb_arch_init(void)
if (ret != 0)
return ret;
- register_kernel_break_hook(&kgdb_brkpt_hook);
- register_kernel_break_hook(&kgdb_compiled_brkpt_hook);
register_kernel_step_hook(&kgdb_step_hook);
return 0;
}
@@ -329,8 +317,6 @@ int kgdb_arch_init(void)
*/
void kgdb_arch_exit(void)
{
- unregister_kernel_break_hook(&kgdb_brkpt_hook);
- unregister_kernel_break_hook(&kgdb_compiled_brkpt_hook);
unregister_kernel_step_hook(&kgdb_step_hook);
unregister_die_notifier(&kgdb_notifier);
}
diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
index d9e462eafb95..0c5d408afd95 100644
--- a/arch/arm64/kernel/probes/kprobes.c
+++ b/arch/arm64/kernel/probes/kprobes.c
@@ -292,8 +292,8 @@ int __kprobes kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr)
return 0;
}
-static int __kprobes
-kprobe_breakpoint_handler(struct pt_regs *regs, unsigned long esr)
+int __kprobes
+kprobe_brk_handler(struct pt_regs *regs, unsigned long esr)
{
struct kprobe *p, *cur_kprobe;
struct kprobe_ctlblk *kcb;
@@ -336,13 +336,8 @@ kprobe_breakpoint_handler(struct pt_regs *regs, unsigned long esr)
return DBG_HOOK_HANDLED;
}
-static struct break_hook kprobes_break_hook = {
- .imm = KPROBES_BRK_IMM,
- .fn = kprobe_breakpoint_handler,
-};
-
-static int __kprobes
-kprobe_breakpoint_ss_handler(struct pt_regs *regs, unsigned long esr)
+int __kprobes
+kprobe_ss_brk_handler(struct pt_regs *regs, unsigned long esr)
{
struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
unsigned long addr = instruction_pointer(regs);
@@ -360,13 +355,8 @@ kprobe_breakpoint_ss_handler(struct pt_regs *regs, unsigned long esr)
return DBG_HOOK_ERROR;
}
-static struct break_hook kprobes_break_ss_hook = {
- .imm = KPROBES_BRK_SS_IMM,
- .fn = kprobe_breakpoint_ss_handler,
-};
-
-static int __kprobes
-kretprobe_breakpoint_handler(struct pt_regs *regs, unsigned long esr)
+int __kprobes
+kretprobe_brk_handler(struct pt_regs *regs, unsigned long esr)
{
if (regs->pc != (unsigned long)__kretprobe_trampoline)
return DBG_HOOK_ERROR;
@@ -375,11 +365,6 @@ kretprobe_breakpoint_handler(struct pt_regs *regs, unsigned long esr)
return DBG_HOOK_HANDLED;
}
-static struct break_hook kretprobes_break_hook = {
- .imm = KRETPROBES_BRK_IMM,
- .fn = kretprobe_breakpoint_handler,
-};
-
/*
* Provide a blacklist of symbols identifying ranges which cannot be kprobed.
* This blacklist is exposed to userspace via debugfs (kprobes/blacklist).
@@ -422,9 +407,5 @@ int __kprobes arch_trampoline_kprobe(struct kprobe *p)
int __init arch_init_kprobes(void)
{
- register_kernel_break_hook(&kprobes_break_hook);
- register_kernel_break_hook(&kprobes_break_ss_hook);
- register_kernel_break_hook(&kretprobes_break_hook);
-
return 0;
}
diff --git a/arch/arm64/kernel/probes/kprobes_trampoline.S b/arch/arm64/kernel/probes/kprobes_trampoline.S
index a362f3dbb3d1..b60739d3983f 100644
--- a/arch/arm64/kernel/probes/kprobes_trampoline.S
+++ b/arch/arm64/kernel/probes/kprobes_trampoline.S
@@ -12,7 +12,7 @@
SYM_CODE_START(__kretprobe_trampoline)
/*
* Trigger a breakpoint exception. The PC will be adjusted by
- * kretprobe_breakpoint_handler(), and no subsequent instructions will
+ * kretprobe_brk_handler(), and no subsequent instructions will
* be executed from the trampoline.
*/
brk #KRETPROBES_BRK_IMM
diff --git a/arch/arm64/kernel/probes/uprobes.c b/arch/arm64/kernel/probes/uprobes.c
index cb3d05af36e3..ad68b4a5974d 100644
--- a/arch/arm64/kernel/probes/uprobes.c
+++ b/arch/arm64/kernel/probes/uprobes.c
@@ -173,7 +173,7 @@ int arch_uprobe_exception_notify(struct notifier_block *self,
return NOTIFY_DONE;
}
-static int uprobe_breakpoint_handler(struct pt_regs *regs,
+int uprobe_brk_handler(struct pt_regs *regs,
unsigned long esr)
{
if (uprobe_pre_sstep_notifier(regs))
@@ -194,12 +194,6 @@ static int uprobe_single_step_handler(struct pt_regs *regs,
return DBG_HOOK_ERROR;
}
-/* uprobe breakpoint handler hook */
-static struct break_hook uprobes_break_hook = {
- .imm = UPROBES_BRK_IMM,
- .fn = uprobe_breakpoint_handler,
-};
-
/* uprobe single step handler hook */
static struct step_hook uprobes_step_hook = {
.fn = uprobe_single_step_handler,
@@ -207,7 +201,6 @@ static struct step_hook uprobes_step_hook = {
static int __init arch_init_uprobes(void)
{
- register_user_break_hook(&uprobes_break_hook);
register_user_step_hook(&uprobes_step_hook);
return 0;
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index 685c11b2c553..654c8ea46ec6 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -987,7 +987,7 @@ void do_serror(struct pt_regs *regs, unsigned long esr)
int is_valid_bugaddr(unsigned long addr)
{
/*
- * bug_handler() only called for BRK #BUG_BRK_IMM.
+ * bug_brk_handler() only called for BRK #BUG_BRK_IMM.
* So the answer is trivial -- any spurious instances with no
* bug table entry will be rejected by report_bug() and passed
* back to the debug-monitors code and handled as a fatal
@@ -997,7 +997,7 @@ int is_valid_bugaddr(unsigned long addr)
}
#endif
-static int bug_handler(struct pt_regs *regs, unsigned long esr)
+int bug_brk_handler(struct pt_regs *regs, unsigned long esr)
{
switch (report_bug(regs->pc, regs)) {
case BUG_TRAP_TYPE_BUG:
@@ -1017,13 +1017,8 @@ static int bug_handler(struct pt_regs *regs, unsigned long esr)
return DBG_HOOK_HANDLED;
}
-static struct break_hook bug_break_hook = {
- .fn = bug_handler,
- .imm = BUG_BRK_IMM,
-};
-
#ifdef CONFIG_CFI_CLANG
-static int cfi_handler(struct pt_regs *regs, unsigned long esr)
+int cfi_brk_handler(struct pt_regs *regs, unsigned long esr)
{
unsigned long target;
u32 type;
@@ -1046,15 +1041,9 @@ static int cfi_handler(struct pt_regs *regs, unsigned long esr)
arm64_skip_faulting_instruction(regs, AARCH64_INSN_SIZE);
return DBG_HOOK_HANDLED;
}
-
-static struct break_hook cfi_break_hook = {
- .fn = cfi_handler,
- .imm = CFI_BRK_IMM_BASE,
- .mask = CFI_BRK_IMM_MASK,
-};
#endif /* CONFIG_CFI_CLANG */
-static int reserved_fault_handler(struct pt_regs *regs, unsigned long esr)
+int reserved_fault_brk_handler(struct pt_regs *regs, unsigned long esr)
{
pr_err("%s generated an invalid instruction at %pS!\n",
"Kernel text patching",
@@ -1064,11 +1053,6 @@ static int reserved_fault_handler(struct pt_regs *regs, unsigned long esr)
return DBG_HOOK_ERROR;
}
-static struct break_hook fault_break_hook = {
- .fn = reserved_fault_handler,
- .imm = FAULT_BRK_IMM,
-};
-
#ifdef CONFIG_KASAN_SW_TAGS
#define KASAN_ESR_RECOVER 0x20
@@ -1076,7 +1060,7 @@ static struct break_hook fault_break_hook = {
#define KASAN_ESR_SIZE_MASK 0x0f
#define KASAN_ESR_SIZE(esr) (1 << ((esr) & KASAN_ESR_SIZE_MASK))
-static int kasan_handler(struct pt_regs *regs, unsigned long esr)
+int kasan_brk_handler(struct pt_regs *regs, unsigned long esr)
{
bool recover = esr & KASAN_ESR_RECOVER;
bool write = esr & KASAN_ESR_WRITE;
@@ -1107,26 +1091,14 @@ static int kasan_handler(struct pt_regs *regs, unsigned long esr)
arm64_skip_faulting_instruction(regs, AARCH64_INSN_SIZE);
return DBG_HOOK_HANDLED;
}
-
-static struct break_hook kasan_break_hook = {
- .fn = kasan_handler,
- .imm = KASAN_BRK_IMM,
- .mask = KASAN_BRK_MASK,
-};
#endif
#ifdef CONFIG_UBSAN_TRAP
-static int ubsan_handler(struct pt_regs *regs, unsigned long esr)
+int ubsan_brk_handler(struct pt_regs *regs, unsigned long esr)
{
die(report_ubsan_failure(esr & UBSAN_BRK_MASK), regs, esr);
return DBG_HOOK_HANDLED;
}
-
-static struct break_hook ubsan_break_hook = {
- .fn = ubsan_handler,
- .imm = UBSAN_BRK_IMM,
- .mask = UBSAN_BRK_MASK,
-};
#endif
/*
@@ -1138,31 +1110,20 @@ int __init early_brk64(unsigned long addr, unsigned long esr,
{
#ifdef CONFIG_CFI_CLANG
if (esr_is_cfi_brk(esr))
- return cfi_handler(regs, esr) != DBG_HOOK_HANDLED;
+ return cfi_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
#endif
#ifdef CONFIG_KASAN_SW_TAGS
if ((esr_brk_comment(esr) & ~KASAN_BRK_MASK) == KASAN_BRK_IMM)
- return kasan_handler(regs, esr) != DBG_HOOK_HANDLED;
+ return kasan_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
#endif
#ifdef CONFIG_UBSAN_TRAP
if (esr_is_ubsan_brk(esr))
- return ubsan_handler(regs, esr) != DBG_HOOK_HANDLED;
+ return ubsan_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
#endif
- return bug_handler(regs, esr) != DBG_HOOK_HANDLED;
+ return bug_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
}
void __init trap_init(void)
{
- register_kernel_break_hook(&bug_break_hook);
-#ifdef CONFIG_CFI_CLANG
- register_kernel_break_hook(&cfi_break_hook);
-#endif
- register_kernel_break_hook(&fault_break_hook);
-#ifdef CONFIG_KASAN_SW_TAGS
- register_kernel_break_hook(&kasan_break_hook);
-#endif
-#ifdef CONFIG_UBSAN_TRAP
- register_kernel_break_hook(&ubsan_break_hook);
-#endif
debug_traps_init();
}
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 04/13] arm64: debug: call step handlers statically
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (2 preceding siblings ...)
2025-06-20 21:11 ` [PATCH v4 03/13] arm64: debug: call software breakpoint handlers statically Ada Couprie Diaz
@ 2025-06-20 21:11 ` Ada Couprie Diaz
2025-06-27 15:45 ` Will Deacon
2025-06-20 21:11 ` [PATCH v4 05/13] arm64: debug: remove break/step handler registration infrastructure Ada Couprie Diaz
` (9 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:11 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Software stepping checks for the correct handler by iterating over a list
of dynamically registered handlers and calling all of them until one
handles the exception.
This is the only generic way to handle software stepping handlers in arm64
as the exception does not provide an immediate that could be checked,
contrary to software breakpoints.
However, the registration mechanism is not exported and has only
two current users : the KGDB stepping handler, and the uprobe single step
handler.
Given that one comes from user mode and the other from kernel mode, call
the appropriate one by checking the source EL of the exception.
Add a stand-in that returns DBG_HOOK_ERROR when the configuration
options are not enabled.
Remove `arch_init_uprobes()` as it is not useful anymore and is
specific to arm64.
Unify the naming of the handler to XXX_single_step_handler(), making it
clear they are related.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
---
arch/arm64/include/asm/kgdb.h | 9 +++++++++
arch/arm64/include/asm/uprobes.h | 9 +++++++++
arch/arm64/kernel/debug-monitors.c | 25 ++++++-------------------
arch/arm64/kernel/kgdb.c | 17 +++--------------
arch/arm64/kernel/probes/uprobes.c | 15 +--------------
5 files changed, 28 insertions(+), 47 deletions(-)
diff --git a/arch/arm64/include/asm/kgdb.h b/arch/arm64/include/asm/kgdb.h
index 82a76b2102fb..3184f5d1e3ae 100644
--- a/arch/arm64/include/asm/kgdb.h
+++ b/arch/arm64/include/asm/kgdb.h
@@ -26,6 +26,15 @@ extern int kgdb_fault_expected;
int kgdb_brk_handler(struct pt_regs *regs, unsigned long esr);
int kgdb_compiled_brk_handler(struct pt_regs *regs, unsigned long esr);
+#ifdef CONFIG_KGDB
+int kgdb_single_step_handler(struct pt_regs *regs, unsigned long esr);
+#else
+static inline int kgdb_single_step_handler(struct pt_regs *regs,
+ unsigned long esr)
+{
+ return DBG_HOOK_ERROR;
+}
+#endif
#endif /* !__ASSEMBLY__ */
diff --git a/arch/arm64/include/asm/uprobes.h b/arch/arm64/include/asm/uprobes.h
index 3659a79a9f32..89bfb0213a50 100644
--- a/arch/arm64/include/asm/uprobes.h
+++ b/arch/arm64/include/asm/uprobes.h
@@ -29,5 +29,14 @@ struct arch_uprobe {
};
int uprobe_brk_handler(struct pt_regs *regs, unsigned long esr);
+#ifdef CONFIG_UPROBES
+int uprobe_single_step_handler(struct pt_regs *regs, unsigned long esr);
+#else
+static inline int uprobe_single_step_handler(struct pt_regs *regs,
+ unsigned long esr)
+{
+ return DBG_HOOK_ERROR;
+}
+#endif
#endif
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 15d7158a7548..26c71b7edc26 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -200,30 +200,17 @@ void unregister_kernel_step_hook(struct step_hook *hook)
}
/*
- * Call registered single step handlers
+ * Call single step handlers
* There is no Syndrome info to check for determining the handler.
- * So we call all the registered handlers, until the right handler is
- * found which returns zero.
+ * However, there is only one possible handler for user and kernel modes, so
+ * check and call the appropriate one.
*/
static int call_step_hook(struct pt_regs *regs, unsigned long esr)
{
- struct step_hook *hook;
- struct list_head *list;
- int retval = DBG_HOOK_ERROR;
+ if (user_mode(regs))
+ return uprobe_single_step_handler(regs, esr);
- list = user_mode(regs) ? &user_step_hook : &kernel_step_hook;
-
- /*
- * Since single-step exception disables interrupt, this function is
- * entirely not preemptible, and we can use rcu list safely here.
- */
- list_for_each_entry_rcu(hook, list, node) {
- retval = hook->fn(regs, esr);
- if (retval == DBG_HOOK_HANDLED)
- break;
- }
-
- return retval;
+ return kgdb_single_step_handler(regs, esr);
}
NOKPROBE_SYMBOL(call_step_hook);
diff --git a/arch/arm64/kernel/kgdb.c b/arch/arm64/kernel/kgdb.c
index b5a3b9c85a65..968324a79a89 100644
--- a/arch/arm64/kernel/kgdb.c
+++ b/arch/arm64/kernel/kgdb.c
@@ -250,7 +250,7 @@ int kgdb_compiled_brk_handler(struct pt_regs *regs, unsigned long esr)
}
NOKPROBE_SYMBOL(kgdb_compiled_brk_handler);
-static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned long esr)
+int kgdb_single_step_handler(struct pt_regs *regs, unsigned long esr)
{
if (!kgdb_single_step)
return DBG_HOOK_ERROR;
@@ -258,11 +258,7 @@ static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned long esr)
kgdb_handle_exception(0, SIGTRAP, 0, regs);
return DBG_HOOK_HANDLED;
}
-NOKPROBE_SYMBOL(kgdb_step_brk_fn);
-
-static struct step_hook kgdb_step_hook = {
- .fn = kgdb_step_brk_fn
-};
+NOKPROBE_SYMBOL(kgdb_single_step_handler);
static int __kgdb_notify(struct die_args *args, unsigned long cmd)
{
@@ -301,13 +297,7 @@ static struct notifier_block kgdb_notifier = {
*/
int kgdb_arch_init(void)
{
- int ret = register_die_notifier(&kgdb_notifier);
-
- if (ret != 0)
- return ret;
-
- register_kernel_step_hook(&kgdb_step_hook);
- return 0;
+ return register_die_notifier(&kgdb_notifier);
}
/*
@@ -317,7 +307,6 @@ int kgdb_arch_init(void)
*/
void kgdb_arch_exit(void)
{
- unregister_kernel_step_hook(&kgdb_step_hook);
unregister_die_notifier(&kgdb_notifier);
}
diff --git a/arch/arm64/kernel/probes/uprobes.c b/arch/arm64/kernel/probes/uprobes.c
index ad68b4a5974d..1f91fd2a8187 100644
--- a/arch/arm64/kernel/probes/uprobes.c
+++ b/arch/arm64/kernel/probes/uprobes.c
@@ -182,7 +182,7 @@ int uprobe_brk_handler(struct pt_regs *regs,
return DBG_HOOK_ERROR;
}
-static int uprobe_single_step_handler(struct pt_regs *regs,
+int uprobe_single_step_handler(struct pt_regs *regs,
unsigned long esr)
{
struct uprobe_task *utask = current->utask;
@@ -194,16 +194,3 @@ static int uprobe_single_step_handler(struct pt_regs *regs,
return DBG_HOOK_ERROR;
}
-/* uprobe single step handler hook */
-static struct step_hook uprobes_step_hook = {
- .fn = uprobe_single_step_handler,
-};
-
-static int __init arch_init_uprobes(void)
-{
- register_user_step_hook(&uprobes_step_hook);
-
- return 0;
-}
-
-device_initcall(arch_init_uprobes);
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 05/13] arm64: debug: remove break/step handler registration infrastructure
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (3 preceding siblings ...)
2025-06-20 21:11 ` [PATCH v4 04/13] arm64: debug: call step " Ada Couprie Diaz
@ 2025-06-20 21:11 ` Ada Couprie Diaz
2025-06-27 15:45 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 06/13] arm64: entry: Add entry and exit functions for debug exceptions Ada Couprie Diaz
` (8 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:11 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Remove all infrastructure for the dynamic registration previously used by
software breakpoints and stepping handlers.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/debug-monitors.h | 24 ----------
arch/arm64/kernel/debug-monitors.c | 63 -------------------------
2 files changed, 87 deletions(-)
diff --git a/arch/arm64/include/asm/debug-monitors.h b/arch/arm64/include/asm/debug-monitors.h
index e1caf6a8380c..caee1d923f9c 100644
--- a/arch/arm64/include/asm/debug-monitors.h
+++ b/arch/arm64/include/asm/debug-monitors.h
@@ -62,30 +62,6 @@ struct task_struct;
#define DBG_HOOK_HANDLED 0
#define DBG_HOOK_ERROR 1
-struct step_hook {
- struct list_head node;
- int (*fn)(struct pt_regs *regs, unsigned long esr);
-};
-
-void register_user_step_hook(struct step_hook *hook);
-void unregister_user_step_hook(struct step_hook *hook);
-
-void register_kernel_step_hook(struct step_hook *hook);
-void unregister_kernel_step_hook(struct step_hook *hook);
-
-struct break_hook {
- struct list_head node;
- int (*fn)(struct pt_regs *regs, unsigned long esr);
- u16 imm;
- u16 mask; /* These bits are ignored when comparing with imm */
-};
-
-void register_user_break_hook(struct break_hook *hook);
-void unregister_user_break_hook(struct break_hook *hook);
-
-void register_kernel_break_hook(struct break_hook *hook);
-void unregister_kernel_break_hook(struct break_hook *hook);
-
u8 debug_monitors_arch(void);
enum dbg_active_el {
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 26c71b7edc26..46fb9359a554 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -159,46 +159,6 @@ NOKPROBE_SYMBOL(clear_user_regs_spsr_ss);
#define set_regs_spsr_ss(r) set_user_regs_spsr_ss(&(r)->user_regs)
#define clear_regs_spsr_ss(r) clear_user_regs_spsr_ss(&(r)->user_regs)
-static DEFINE_SPINLOCK(debug_hook_lock);
-static LIST_HEAD(user_step_hook);
-static LIST_HEAD(kernel_step_hook);
-
-static void register_debug_hook(struct list_head *node, struct list_head *list)
-{
- spin_lock(&debug_hook_lock);
- list_add_rcu(node, list);
- spin_unlock(&debug_hook_lock);
-
-}
-
-static void unregister_debug_hook(struct list_head *node)
-{
- spin_lock(&debug_hook_lock);
- list_del_rcu(node);
- spin_unlock(&debug_hook_lock);
- synchronize_rcu();
-}
-
-void register_user_step_hook(struct step_hook *hook)
-{
- register_debug_hook(&hook->node, &user_step_hook);
-}
-
-void unregister_user_step_hook(struct step_hook *hook)
-{
- unregister_debug_hook(&hook->node);
-}
-
-void register_kernel_step_hook(struct step_hook *hook)
-{
- register_debug_hook(&hook->node, &kernel_step_hook);
-}
-
-void unregister_kernel_step_hook(struct step_hook *hook)
-{
- unregister_debug_hook(&hook->node);
-}
-
/*
* Call single step handlers
* There is no Syndrome info to check for determining the handler.
@@ -264,29 +224,6 @@ static int single_step_handler(unsigned long unused, unsigned long esr,
}
NOKPROBE_SYMBOL(single_step_handler);
-static LIST_HEAD(user_break_hook);
-static LIST_HEAD(kernel_break_hook);
-
-void register_user_break_hook(struct break_hook *hook)
-{
- register_debug_hook(&hook->node, &user_break_hook);
-}
-
-void unregister_user_break_hook(struct break_hook *hook)
-{
- unregister_debug_hook(&hook->node);
-}
-
-void register_kernel_break_hook(struct break_hook *hook)
-{
- register_debug_hook(&hook->node, &kernel_break_hook);
-}
-
-void unregister_kernel_break_hook(struct break_hook *hook)
-{
- unregister_debug_hook(&hook->node);
-}
-
static int call_break_hook(struct pt_regs *regs, unsigned long esr)
{
if (user_mode(regs)) {
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 06/13] arm64: entry: Add entry and exit functions for debug exceptions
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (4 preceding siblings ...)
2025-06-20 21:11 ` [PATCH v4 05/13] arm64: debug: remove break/step handler registration infrastructure Ada Couprie Diaz
@ 2025-06-20 21:12 ` Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 07/13] arm64: debug: split hardware breakpoint exception entry Ada Couprie Diaz
` (7 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:12 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Move the `debug_exception_enter()` and `debug_exception_exit()`
functions from mm/fault.c, as they are needed to split
the debug exceptions entry paths from the current unified one.
Make them externally visible in include/asm/exception.h until
the caller in mm/fault.c is cleaned up.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/exception.h | 4 ++++
arch/arm64/kernel/entry-common.c | 22 ++++++++++++++++++++++
arch/arm64/mm/fault.c | 22 ----------------------
3 files changed, 26 insertions(+), 22 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index d48fc16584cd..e54b5466fd2c 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -80,4 +80,8 @@ void do_serror(struct pt_regs *regs, unsigned long esr);
void do_signal(struct pt_regs *regs);
void __noreturn panic_bad_stack(struct pt_regs *regs, unsigned long esr, unsigned long far);
+
+void debug_exception_enter(struct pt_regs *regs);
+void debug_exception_exit(struct pt_regs *regs);
+
#endif /* __ASM_EXCEPTION_H */
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index 7c1970b341b8..3bdfa5abaf7a 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -441,6 +441,28 @@ static __always_inline void fpsimd_syscall_exit(void)
__this_cpu_write(fpsimd_last_state.to_save, FP_STATE_CURRENT);
}
+/*
+ * In debug exception context, we explicitly disable preemption despite
+ * having interrupts disabled.
+ * This serves two purposes: it makes it much less likely that we would
+ * accidentally schedule in exception context and it will force a warning
+ * if we somehow manage to schedule by accident.
+ */
+void debug_exception_enter(struct pt_regs *regs)
+{
+ preempt_disable();
+
+ /* This code is a bit fragile. Test it. */
+ RCU_LOCKDEP_WARN(!rcu_is_watching(), "exception_enter didn't work");
+}
+NOKPROBE_SYMBOL(debug_exception_enter);
+
+void debug_exception_exit(struct pt_regs *regs)
+{
+ preempt_enable_no_resched();
+}
+NOKPROBE_SYMBOL(debug_exception_exit);
+
UNHANDLED(el1t, 64, sync)
UNHANDLED(el1t, 64, irq)
UNHANDLED(el1t, 64, fiq)
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index ec0a337891dd..d451d7d834f1 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -966,28 +966,6 @@ void __init hook_debug_fault_code(int nr,
debug_fault_info[nr].name = name;
}
-/*
- * In debug exception context, we explicitly disable preemption despite
- * having interrupts disabled.
- * This serves two purposes: it makes it much less likely that we would
- * accidentally schedule in exception context and it will force a warning
- * if we somehow manage to schedule by accident.
- */
-static void debug_exception_enter(struct pt_regs *regs)
-{
- preempt_disable();
-
- /* This code is a bit fragile. Test it. */
- RCU_LOCKDEP_WARN(!rcu_is_watching(), "exception_enter didn't work");
-}
-NOKPROBE_SYMBOL(debug_exception_enter);
-
-static void debug_exception_exit(struct pt_regs *regs)
-{
- preempt_enable_no_resched();
-}
-NOKPROBE_SYMBOL(debug_exception_exit);
-
void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
struct pt_regs *regs)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 07/13] arm64: debug: split hardware breakpoint exception entry
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (5 preceding siblings ...)
2025-06-20 21:12 ` [PATCH v4 06/13] arm64: entry: Add entry and exit functions for debug exceptions Ada Couprie Diaz
@ 2025-06-20 21:12 ` Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 08/13] arm64: debug: refactor reinstall_suspended_bps() Ada Couprie Diaz
` (6 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:12 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Currently all debug exceptions share common entry code and are routed
to `do_debug_exception()`, which calls dynamically-registered
handlers for each specific debug exception. This is unfortunate as
different debug exceptions have different entry handling requirements,
and it would be better to handle these distinct requirements earlier.
Hardware breakpoints exceptions are generated by the hardware after user
configuration. As such, they can be exploited when training branch
predictors outside of the userspace VA range: they still need to call
`arm64_apply_bp_hardening()` if needed to mitigate against this attack.
However, they do not need to handle the Cortex-A76 erratum #1463225 as
it only applies to single stepping exceptions.
It does not set an address in FAR_EL1 either, only the hardware
watchpoint does.
As the hardware breakpoint handler only returns 0 and never triggers
the call to `arm64_notify_die()`, we can call it directly from
`entry-common.c`.
Split the hardware breakpoint exception entry, adjust
the function signature, and handling of the Cortex-A76 erratum to fit
the behaviour of the exception.
Move the call to `arm64_apply_bp_hardening()` to `entry-common.c` so that
we can do it as early as possible, and only for the exceptions coming
from EL0, where it is needed.
This is safe to do as it is `noinstr`, as are all the functions it
may call. `el0_ia()` and `el0_pc()` already call it this way.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
---
arch/arm64/include/asm/exception.h | 1 +
arch/arm64/kernel/entry-common.c | 28 ++++++++++++++++++++++++++++
arch/arm64/kernel/hw_breakpoint.c | 16 ++++++----------
3 files changed, 35 insertions(+), 10 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index e54b5466fd2c..926bad7b6704 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -61,6 +61,7 @@ void do_el0_gcs(struct pt_regs *regs, unsigned long esr);
void do_el1_gcs(struct pt_regs *regs, unsigned long esr);
void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
struct pt_regs *regs);
+void do_breakpoint(unsigned long esr, struct pt_regs *regs);
void do_fpsimd_acc(unsigned long esr, struct pt_regs *regs);
void do_sve_acc(unsigned long esr, struct pt_regs *regs);
void do_sme_acc(unsigned long esr, struct pt_regs *regs);
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index 3bdfa5abaf7a..be2add6b4ae3 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -526,6 +526,15 @@ static void noinstr el1_mops(struct pt_regs *regs, unsigned long esr)
exit_to_kernel_mode(regs);
}
+static void noinstr el1_breakpt(struct pt_regs *regs, unsigned long esr)
+{
+ arm64_enter_el1_dbg(regs);
+ debug_exception_enter(regs);
+ do_breakpoint(esr, regs);
+ debug_exception_exit(regs);
+ arm64_exit_el1_dbg(regs);
+}
+
static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
{
unsigned long far = read_sysreg(far_el1);
@@ -575,6 +584,8 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
el1_mops(regs, esr);
break;
case ESR_ELx_EC_BREAKPT_CUR:
+ el1_breakpt(regs, esr);
+ break;
case ESR_ELx_EC_SOFTSTP_CUR:
case ESR_ELx_EC_WATCHPT_CUR:
case ESR_ELx_EC_BRK64:
@@ -769,6 +780,19 @@ static void noinstr el0_inv(struct pt_regs *regs, unsigned long esr)
exit_to_user_mode(regs);
}
+static void noinstr el0_breakpt(struct pt_regs *regs, unsigned long esr)
+{
+ if (!is_ttbr0_addr(regs->pc))
+ arm64_apply_bp_hardening();
+
+ enter_from_user_mode(regs);
+ debug_exception_enter(regs);
+ do_breakpoint(esr, regs);
+ debug_exception_exit(regs);
+ local_daif_restore(DAIF_PROCCTX);
+ exit_to_user_mode(regs);
+}
+
static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
{
/* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
@@ -848,6 +872,8 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
el0_gcs(regs, esr);
break;
case ESR_ELx_EC_BREAKPT_LOW:
+ el0_breakpt(regs, esr);
+ break;
case ESR_ELx_EC_SOFTSTP_LOW:
case ESR_ELx_EC_WATCHPT_LOW:
case ESR_ELx_EC_BRK64:
@@ -968,6 +994,8 @@ asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
el0_cp15(regs, esr);
break;
case ESR_ELx_EC_BREAKPT_LOW:
+ el0_breakpt(regs, esr);
+ break;
case ESR_ELx_EC_SOFTSTP_LOW:
case ESR_ELx_EC_WATCHPT_LOW:
case ESR_ELx_EC_BKPT32:
diff --git a/arch/arm64/kernel/hw_breakpoint.c b/arch/arm64/kernel/hw_breakpoint.c
index 722ac45f9f7b..d7eede5d869c 100644
--- a/arch/arm64/kernel/hw_breakpoint.c
+++ b/arch/arm64/kernel/hw_breakpoint.c
@@ -22,6 +22,7 @@
#include <asm/current.h>
#include <asm/debug-monitors.h>
#include <asm/esr.h>
+#include <asm/exception.h>
#include <asm/hw_breakpoint.h>
#include <asm/traps.h>
#include <asm/cputype.h>
@@ -618,8 +619,7 @@ NOKPROBE_SYMBOL(toggle_bp_registers);
/*
* Debug exception handlers.
*/
-static int breakpoint_handler(unsigned long unused, unsigned long esr,
- struct pt_regs *regs)
+void do_breakpoint(unsigned long esr, struct pt_regs *regs)
{
int i, step = 0, *kernel_step;
u32 ctrl_reg;
@@ -662,7 +662,7 @@ static int breakpoint_handler(unsigned long unused, unsigned long esr,
}
if (!step)
- return 0;
+ return;
if (user_mode(regs)) {
debug_info->bps_disabled = 1;
@@ -670,7 +670,7 @@ static int breakpoint_handler(unsigned long unused, unsigned long esr,
/* If we're already stepping a watchpoint, just return. */
if (debug_info->wps_disabled)
- return 0;
+ return;
if (test_thread_flag(TIF_SINGLESTEP))
debug_info->suspended_step = 1;
@@ -681,7 +681,7 @@ static int breakpoint_handler(unsigned long unused, unsigned long esr,
kernel_step = this_cpu_ptr(&stepping_kernel_bp);
if (*kernel_step != ARM_KERNEL_STEP_NONE)
- return 0;
+ return;
if (kernel_active_single_step()) {
*kernel_step = ARM_KERNEL_STEP_SUSPEND;
@@ -690,10 +690,8 @@ static int breakpoint_handler(unsigned long unused, unsigned long esr,
kernel_enable_single_step(regs);
}
}
-
- return 0;
}
-NOKPROBE_SYMBOL(breakpoint_handler);
+NOKPROBE_SYMBOL(do_breakpoint);
/*
* Arm64 hardware does not always report a watchpoint hit address that matches
@@ -988,8 +986,6 @@ static int __init arch_hw_breakpoint_init(void)
core_num_brps, core_num_wrps);
/* Register debug fault handlers. */
- hook_debug_fault_code(DBG_ESR_EVT_HWBP, breakpoint_handler, SIGTRAP,
- TRAP_HWBKPT, "hw-breakpoint handler");
hook_debug_fault_code(DBG_ESR_EVT_HWWP, watchpoint_handler, SIGTRAP,
TRAP_HWBKPT, "hw-watchpoint handler");
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 08/13] arm64: debug: refactor reinstall_suspended_bps()
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (6 preceding siblings ...)
2025-06-20 21:12 ` [PATCH v4 07/13] arm64: debug: split hardware breakpoint exception entry Ada Couprie Diaz
@ 2025-06-20 21:12 ` Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 09/13] arm64: debug: split single stepping exception entry Ada Couprie Diaz
` (5 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:12 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
`reinstall_suspended_bps()` plays a key part in the stepping process
when we have hardware breakpoints and watchpoints enabled.
It checks if we need to step one, will re-enable it if it has
been handled and will return whether or not we need to proceed with
a single-step.
However, the current naming and return values make it harder to understand
the logic and goal of the function.
Rename it `try_step_suspended_breakpoints()` and change the return value
to a boolean, aligning it with similar functions used in
`do_el0_undef()` like `try_emulate_mrs()`, and making its behaviour
more obvious.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/debug-monitors.h | 6 +++---
arch/arm64/kernel/debug-monitors.c | 2 +-
arch/arm64/kernel/hw_breakpoint.c | 25 ++++++++++++-------------
3 files changed, 16 insertions(+), 17 deletions(-)
diff --git a/arch/arm64/include/asm/debug-monitors.h b/arch/arm64/include/asm/debug-monitors.h
index caee1d923f9c..586290e95d87 100644
--- a/arch/arm64/include/asm/debug-monitors.h
+++ b/arch/arm64/include/asm/debug-monitors.h
@@ -84,11 +84,11 @@ void kernel_rewind_single_step(struct pt_regs *regs);
void kernel_fastforward_single_step(struct pt_regs *regs);
#ifdef CONFIG_HAVE_HW_BREAKPOINT
-int reinstall_suspended_bps(struct pt_regs *regs);
+bool try_step_suspended_breakpoints(struct pt_regs *regs);
#else
-static inline int reinstall_suspended_bps(struct pt_regs *regs)
+static inline bool try_step_suspended_breakpoints(struct pt_regs *regs)
{
- return -ENODEV;
+ return false;
}
#endif
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 46fb9359a554..89264d1a9d65 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -195,7 +195,7 @@ static int single_step_handler(unsigned long unused, unsigned long esr,
* If we are stepping a pending breakpoint, call the hw_breakpoint
* handler first.
*/
- if (!reinstall_suspended_bps(regs))
+ if (try_step_suspended_breakpoints(regs))
return 0;
if (call_step_hook(regs, esr) == DBG_HOOK_HANDLED)
diff --git a/arch/arm64/kernel/hw_breakpoint.c b/arch/arm64/kernel/hw_breakpoint.c
index d7eede5d869c..309ae24d4548 100644
--- a/arch/arm64/kernel/hw_breakpoint.c
+++ b/arch/arm64/kernel/hw_breakpoint.c
@@ -847,36 +847,35 @@ NOKPROBE_SYMBOL(watchpoint_handler);
/*
* Handle single-step exception.
*/
-int reinstall_suspended_bps(struct pt_regs *regs)
+bool try_step_suspended_breakpoints(struct pt_regs *regs)
{
struct debug_info *debug_info = ¤t->thread.debug;
- int handled_exception = 0, *kernel_step;
-
- kernel_step = this_cpu_ptr(&stepping_kernel_bp);
+ int *kernel_step = this_cpu_ptr(&stepping_kernel_bp);
+ bool handled_exception = false;
/*
* Called from single-step exception handler.
- * Return 0 if execution can resume, 1 if a SIGTRAP should be
- * reported.
+ * Return true if we stepped a breakpoint and can resume execution,
+ * false if we need to handle a single-step.
*/
if (user_mode(regs)) {
if (debug_info->bps_disabled) {
debug_info->bps_disabled = 0;
toggle_bp_registers(AARCH64_DBG_REG_BCR, DBG_ACTIVE_EL0, 1);
- handled_exception = 1;
+ handled_exception = true;
}
if (debug_info->wps_disabled) {
debug_info->wps_disabled = 0;
toggle_bp_registers(AARCH64_DBG_REG_WCR, DBG_ACTIVE_EL0, 1);
- handled_exception = 1;
+ handled_exception = true;
}
if (handled_exception) {
if (debug_info->suspended_step) {
debug_info->suspended_step = 0;
/* Allow exception handling to fall-through. */
- handled_exception = 0;
+ handled_exception = false;
} else {
user_disable_single_step(current);
}
@@ -890,17 +889,17 @@ int reinstall_suspended_bps(struct pt_regs *regs)
if (*kernel_step != ARM_KERNEL_STEP_SUSPEND) {
kernel_disable_single_step();
- handled_exception = 1;
+ handled_exception = true;
} else {
- handled_exception = 0;
+ handled_exception = false;
}
*kernel_step = ARM_KERNEL_STEP_NONE;
}
- return !handled_exception;
+ return handled_exception;
}
-NOKPROBE_SYMBOL(reinstall_suspended_bps);
+NOKPROBE_SYMBOL(try_step_suspended_breakpoints);
/*
* Context-switcher for restoring suspended breakpoints.
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 09/13] arm64: debug: split single stepping exception entry
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (7 preceding siblings ...)
2025-06-20 21:12 ` [PATCH v4 08/13] arm64: debug: refactor reinstall_suspended_bps() Ada Couprie Diaz
@ 2025-06-20 21:12 ` Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 10/13] arm64: debug: split hardware watchpoint " Ada Couprie Diaz
` (4 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:12 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Currently all debug exceptions share common entry code and are routed
to `do_debug_exception()`, which calls dynamically-registered
handlers for each specific debug exception. This is unfortunate as
different debug exceptions have different entry handling requirements,
and it would be better to handle these distinct requirements earlier.
The single stepping exception has the most constraints : it can be
exploited to train branch predictors and it needs special handling at EL1
for the Cortex-A76 erratum #1463225. We need to conserve all those
mitigations.
However, it does not write an address at FAR_EL1, as only hardware
watchpoints do so.
The single-step handler does its own signaling if it needs to and only
returns 0, so we can call it directly from `entry-common.c`.
Split the single stepping exception entry, adjust the function signature,
keep the security mitigation and erratum handling.
Further, as the EL0 and EL1 code paths are cleanly separated, we can split
`do_softstep()` into `do_el0_softstep()` and `do_el1_softstep()` and
call them directly from the relevant entry paths.
We can also remove `NOKPROBE_SYMBOL` for the EL0 path, as it cannot
lead to a kprobe recursion.
Move the call to `arm64_apply_bp_hardening()` to `entry-common.c` so that
we can do it as early as possible, and only for the exceptions coming
from EL0, where it is needed.
This is safe to do as it is `noinstr`, as are all the functions it
may call. `el0_ia()` and `el0_pc()` already call it this way.
When taking a soft-step exception from EL0, most of the single stepping
handling is safely preemptible : the only possible handler is
`uprobe_single_step_handler()`. It only operates on task-local data and
properly checks its validity, then raises a Thread Information Flag,
processed before returning to userspace in `do_notify_resume()`, which
is already preemptible.
However, the soft-step handler first calls `reinstall_suspended_bps()`
to check if there is any hardware breakpoint or watchpoint pending
or already stepped through.
This cannot be preempted as it manipulates the hardware breakpoint and
watchpoint registers.
Move the call to `try_step_suspended_breakpoints()` to `entry-common.c`
and adjust the relevant comments.
We can now safely unmask interrupts before handling the step itself,
fixing a PREEMPT_RT issue where the handler could call a sleeping function
with preemption disabled.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Closes: https://lore.kernel.org/linux-arm-kernel/Z6YW_Kx4S2tmj2BP@uudg.org/
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
---
arch/arm64/include/asm/exception.h | 2 +
arch/arm64/kernel/debug-monitors.c | 77 +++++++++++-------------------
arch/arm64/kernel/entry-common.c | 43 +++++++++++++++++
arch/arm64/kernel/hw_breakpoint.c | 2 +-
4 files changed, 75 insertions(+), 49 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 926bad7b6704..59294e556a5d 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -62,6 +62,8 @@ void do_el1_gcs(struct pt_regs *regs, unsigned long esr);
void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
struct pt_regs *regs);
void do_breakpoint(unsigned long esr, struct pt_regs *regs);
+void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
+void do_el1_softstep(unsigned long esr, struct pt_regs *regs);
void do_fpsimd_acc(unsigned long esr, struct pt_regs *regs);
void do_sve_acc(unsigned long esr, struct pt_regs *regs);
void do_sme_acc(unsigned long esr, struct pt_regs *regs);
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 89264d1a9d65..cb50d1f59798 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -21,6 +21,7 @@
#include <asm/cputype.h>
#include <asm/daifflags.h>
#include <asm/debug-monitors.h>
+#include <asm/exception.h>
#include <asm/kgdb.h>
#include <asm/kprobes.h>
#include <asm/system_misc.h>
@@ -159,21 +160,6 @@ NOKPROBE_SYMBOL(clear_user_regs_spsr_ss);
#define set_regs_spsr_ss(r) set_user_regs_spsr_ss(&(r)->user_regs)
#define clear_regs_spsr_ss(r) clear_user_regs_spsr_ss(&(r)->user_regs)
-/*
- * Call single step handlers
- * There is no Syndrome info to check for determining the handler.
- * However, there is only one possible handler for user and kernel modes, so
- * check and call the appropriate one.
- */
-static int call_step_hook(struct pt_regs *regs, unsigned long esr)
-{
- if (user_mode(regs))
- return uprobe_single_step_handler(regs, esr);
-
- return kgdb_single_step_handler(regs, esr);
-}
-NOKPROBE_SYMBOL(call_step_hook);
-
static void send_user_sigtrap(int si_code)
{
struct pt_regs *regs = current_pt_regs();
@@ -188,41 +174,38 @@ static void send_user_sigtrap(int si_code)
"User debug trap");
}
-static int single_step_handler(unsigned long unused, unsigned long esr,
- struct pt_regs *regs)
+/*
+ * We have already unmasked interrupts and enabled preemption
+ * when calling do_el0_softstep() from entry-common.c.
+ */
+void do_el0_softstep(unsigned long esr, struct pt_regs *regs)
{
+ if (uprobe_single_step_handler(regs, esr) == DBG_HOOK_HANDLED)
+ return;
+
+ send_user_sigtrap(TRAP_TRACE);
/*
- * If we are stepping a pending breakpoint, call the hw_breakpoint
- * handler first.
+ * ptrace will disable single step unless explicitly
+ * asked to re-enable it. For other clients, it makes
+ * sense to leave it enabled (i.e. rewind the controls
+ * to the active-not-pending state).
*/
- if (try_step_suspended_breakpoints(regs))
- return 0;
-
- if (call_step_hook(regs, esr) == DBG_HOOK_HANDLED)
- return 0;
-
- if (user_mode(regs)) {
- send_user_sigtrap(TRAP_TRACE);
-
- /*
- * ptrace will disable single step unless explicitly
- * asked to re-enable it. For other clients, it makes
- * sense to leave it enabled (i.e. rewind the controls
- * to the active-not-pending state).
- */
- user_rewind_single_step(current);
- } else {
- pr_warn("Unexpected kernel single-step exception at EL1\n");
- /*
- * Re-enable stepping since we know that we will be
- * returning to regs.
- */
- set_regs_spsr_ss(regs);
- }
-
- return 0;
+ user_rewind_single_step(current);
}
-NOKPROBE_SYMBOL(single_step_handler);
+
+void do_el1_softstep(unsigned long esr, struct pt_regs *regs)
+{
+ if (kgdb_single_step_handler(regs, esr) == DBG_HOOK_HANDLED)
+ return;
+
+ pr_warn("Unexpected kernel single-step exception at EL1\n");
+ /*
+ * Re-enable stepping since we know that we will be
+ * returning to regs.
+ */
+ set_regs_spsr_ss(regs);
+}
+NOKPROBE_SYMBOL(do_el1_softstep);
static int call_break_hook(struct pt_regs *regs, unsigned long esr)
{
@@ -329,8 +312,6 @@ NOKPROBE_SYMBOL(try_handle_aarch32_break);
void __init debug_traps_init(void)
{
- hook_debug_fault_code(DBG_ESR_EVT_HWSS, single_step_handler, SIGTRAP,
- TRAP_TRACE, "single-step handler");
hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,
TRAP_BRKPT, "BRK handler");
}
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index be2add6b4ae3..7265bef96672 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -535,6 +535,24 @@ static void noinstr el1_breakpt(struct pt_regs *regs, unsigned long esr)
arm64_exit_el1_dbg(regs);
}
+static void noinstr el1_softstp(struct pt_regs *regs, unsigned long esr)
+{
+ arm64_enter_el1_dbg(regs);
+ if (!cortex_a76_erratum_1463225_debug_handler(regs)) {
+ debug_exception_enter(regs);
+ /*
+ * After handling a breakpoint, we suspend the breakpoint
+ * and use single-step to move to the next instruction.
+ * If we are stepping a suspended breakpoint there's nothing more to do:
+ * the single-step is complete.
+ */
+ if (!try_step_suspended_breakpoints(regs))
+ do_el1_softstep(esr, regs);
+ debug_exception_exit(regs);
+ }
+ arm64_exit_el1_dbg(regs);
+}
+
static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
{
unsigned long far = read_sysreg(far_el1);
@@ -587,6 +605,8 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
el1_breakpt(regs, esr);
break;
case ESR_ELx_EC_SOFTSTP_CUR:
+ el1_softstp(regs, esr);
+ break;
case ESR_ELx_EC_WATCHPT_CUR:
case ESR_ELx_EC_BRK64:
el1_dbg(regs, esr);
@@ -793,6 +813,25 @@ static void noinstr el0_breakpt(struct pt_regs *regs, unsigned long esr)
exit_to_user_mode(regs);
}
+static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
+{
+ if (!is_ttbr0_addr(regs->pc))
+ arm64_apply_bp_hardening();
+
+ enter_from_user_mode(regs);
+ /*
+ * After handling a breakpoint, we suspend the breakpoint
+ * and use single-step to move to the next instruction.
+ * If we are stepping a suspended breakpoint there's nothing more to do:
+ * the single-step is complete.
+ */
+ if (!try_step_suspended_breakpoints(regs)) {
+ local_daif_restore(DAIF_PROCCTX);
+ do_el0_softstep(esr, regs);
+ }
+ exit_to_user_mode(regs);
+}
+
static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
{
/* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
@@ -875,6 +914,8 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
el0_breakpt(regs, esr);
break;
case ESR_ELx_EC_SOFTSTP_LOW:
+ el0_softstp(regs, esr);
+ break;
case ESR_ELx_EC_WATCHPT_LOW:
case ESR_ELx_EC_BRK64:
el0_dbg(regs, esr);
@@ -997,6 +1038,8 @@ asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
el0_breakpt(regs, esr);
break;
case ESR_ELx_EC_SOFTSTP_LOW:
+ el0_softstp(regs, esr);
+ break;
case ESR_ELx_EC_WATCHPT_LOW:
case ESR_ELx_EC_BKPT32:
el0_dbg(regs, esr);
diff --git a/arch/arm64/kernel/hw_breakpoint.c b/arch/arm64/kernel/hw_breakpoint.c
index 309ae24d4548..8a80e13347c8 100644
--- a/arch/arm64/kernel/hw_breakpoint.c
+++ b/arch/arm64/kernel/hw_breakpoint.c
@@ -854,7 +854,7 @@ bool try_step_suspended_breakpoints(struct pt_regs *regs)
bool handled_exception = false;
/*
- * Called from single-step exception handler.
+ * Called from single-step exception entry.
* Return true if we stepped a breakpoint and can resume execution,
* false if we need to handle a single-step.
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 10/13] arm64: debug: split hardware watchpoint exception entry
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (8 preceding siblings ...)
2025-06-20 21:12 ` [PATCH v4 09/13] arm64: debug: split single stepping exception entry Ada Couprie Diaz
@ 2025-06-20 21:12 ` Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 11/13] arm64: debug: split brk64 " Ada Couprie Diaz
` (3 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:12 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Currently all debug exceptions share common entry code and are routed
to `do_debug_exception()`, which calls dynamically-registered
handlers for each specific debug exception. This is unfortunate as
different debug exceptions have different entry handling requirements,
and it would be better to handle these distinct requirements earlier.
Hardware watchpoints are the only debug exceptions that will write
FAR_EL1, so we need to preserve it and pass it down.
However, they cannot be used to maliciously train branch predictors, so
we can omit calling `arm64_bp_hardening()`, nor do they need to handle
the Cortex-A76 erratum #1463225, as it only applies to single stepping
exceptions.
As the hardware watchpoint handler only returns 0 and never triggers
the call to `arm64_notify_die()`, we can call it directly from
`entry-common.c`.
Split the hardware watchpoint exception entry and adjust the behaviour
to match the lack of needed mitigations.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
---
arch/arm64/include/asm/exception.h | 2 ++
arch/arm64/kernel/entry-common.c | 31 +++++++++++++++++++++++++++++-
arch/arm64/kernel/hw_breakpoint.c | 17 +++++-----------
3 files changed, 37 insertions(+), 13 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 59294e556a5d..649f66f15a39 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -64,6 +64,8 @@ void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
void do_breakpoint(unsigned long esr, struct pt_regs *regs);
void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
void do_el1_softstep(unsigned long esr, struct pt_regs *regs);
+void do_watchpoint(unsigned long addr, unsigned long esr,
+ struct pt_regs *regs);
void do_fpsimd_acc(unsigned long esr, struct pt_regs *regs);
void do_sve_acc(unsigned long esr, struct pt_regs *regs);
void do_sme_acc(unsigned long esr, struct pt_regs *regs);
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index 7265bef96672..c2ea1653dbb8 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -553,10 +553,20 @@ static void noinstr el1_softstp(struct pt_regs *regs, unsigned long esr)
arm64_exit_el1_dbg(regs);
}
-static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
+static void noinstr el1_watchpt(struct pt_regs *regs, unsigned long esr)
{
+ /* Watchpoints are the only debug exception to write FAR_EL1 */
unsigned long far = read_sysreg(far_el1);
+ arm64_enter_el1_dbg(regs);
+ debug_exception_enter(regs);
+ do_watchpoint(far, esr, regs);
+ debug_exception_exit(regs);
+ arm64_exit_el1_dbg(regs);
+}
+
+static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
+{
arm64_enter_el1_dbg(regs);
if (!cortex_a76_erratum_1463225_debug_handler(regs))
do_debug_exception(far, esr, regs);
@@ -608,6 +618,8 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
el1_softstp(regs, esr);
break;
case ESR_ELx_EC_WATCHPT_CUR:
+ el1_watchpt(regs, esr);
+ break;
case ESR_ELx_EC_BRK64:
el1_dbg(regs, esr);
break;
@@ -832,6 +844,19 @@ static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
exit_to_user_mode(regs);
}
+static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
+{
+ /* Watchpoints are the only debug exception to write FAR_EL1 */
+ unsigned long far = read_sysreg(far_el1);
+
+ enter_from_user_mode(regs);
+ debug_exception_enter(regs);
+ do_watchpoint(far, esr, regs);
+ debug_exception_exit(regs);
+ local_daif_restore(DAIF_PROCCTX);
+ exit_to_user_mode(regs);
+}
+
static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
{
/* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
@@ -917,6 +942,8 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
el0_softstp(regs, esr);
break;
case ESR_ELx_EC_WATCHPT_LOW:
+ el0_watchpt(regs, esr);
+ break;
case ESR_ELx_EC_BRK64:
el0_dbg(regs, esr);
break;
@@ -1041,6 +1068,8 @@ asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
el0_softstp(regs, esr);
break;
case ESR_ELx_EC_WATCHPT_LOW:
+ el0_watchpt(regs, esr);
+ break;
case ESR_ELx_EC_BKPT32:
el0_dbg(regs, esr);
break;
diff --git a/arch/arm64/kernel/hw_breakpoint.c b/arch/arm64/kernel/hw_breakpoint.c
index 8a80e13347c8..ab76b36dce82 100644
--- a/arch/arm64/kernel/hw_breakpoint.c
+++ b/arch/arm64/kernel/hw_breakpoint.c
@@ -750,8 +750,7 @@ static int watchpoint_report(struct perf_event *wp, unsigned long addr,
return step;
}
-static int watchpoint_handler(unsigned long addr, unsigned long esr,
- struct pt_regs *regs)
+void do_watchpoint(unsigned long addr, unsigned long esr, struct pt_regs *regs)
{
int i, step = 0, *kernel_step, access, closest_match = 0;
u64 min_dist = -1, dist;
@@ -806,7 +805,7 @@ static int watchpoint_handler(unsigned long addr, unsigned long esr,
rcu_read_unlock();
if (!step)
- return 0;
+ return;
/*
* We always disable EL0 watchpoints because the kernel can
@@ -819,7 +818,7 @@ static int watchpoint_handler(unsigned long addr, unsigned long esr,
/* If we're already stepping a breakpoint, just return. */
if (debug_info->bps_disabled)
- return 0;
+ return;
if (test_thread_flag(TIF_SINGLESTEP))
debug_info->suspended_step = 1;
@@ -830,7 +829,7 @@ static int watchpoint_handler(unsigned long addr, unsigned long esr,
kernel_step = this_cpu_ptr(&stepping_kernel_bp);
if (*kernel_step != ARM_KERNEL_STEP_NONE)
- return 0;
+ return;
if (kernel_active_single_step()) {
*kernel_step = ARM_KERNEL_STEP_SUSPEND;
@@ -839,10 +838,8 @@ static int watchpoint_handler(unsigned long addr, unsigned long esr,
kernel_enable_single_step(regs);
}
}
-
- return 0;
}
-NOKPROBE_SYMBOL(watchpoint_handler);
+NOKPROBE_SYMBOL(do_watchpoint);
/*
* Handle single-step exception.
@@ -984,10 +981,6 @@ static int __init arch_hw_breakpoint_init(void)
pr_info("found %d breakpoint and %d watchpoint registers.\n",
core_num_brps, core_num_wrps);
- /* Register debug fault handlers. */
- hook_debug_fault_code(DBG_ESR_EVT_HWWP, watchpoint_handler, SIGTRAP,
- TRAP_HWBKPT, "hw-watchpoint handler");
-
/*
* Reset the breakpoint resources. We assume that a halting
* debugger will leave the world in a nice state for us.
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 11/13] arm64: debug: split brk64 exception entry
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (9 preceding siblings ...)
2025-06-20 21:12 ` [PATCH v4 10/13] arm64: debug: split hardware watchpoint " Ada Couprie Diaz
@ 2025-06-20 21:12 ` Ada Couprie Diaz
2025-06-27 15:47 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 12/13] arm64: debug: split bkpt32 " Ada Couprie Diaz
` (2 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:12 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Currently all debug exceptions share common entry code and are routed
to `do_debug_exception()`, which calls dynamically-registered
handlers for each specific debug exception. This is unfortunate as
different debug exceptions have different entry handling requirements,
and it would be better to handle these distinct requirements earlier.
The BRK64 instruction can only be triggered by a BRK instruction. Thus,
we know that the PC is a legitimate address and isn't being used to train
a branch predictor with a bogus address : we don't need to call
`arm64_apply_bp_hardening()`.
We do not need to handle the Cortex-A76 erratum #1463225 either, as it
only relevant for single stepping at EL1.
BRK64 does not write FAR_EL1 either, as only hardware watchpoints do so.
Split the BRK64 exception entry, adjust the function signature, and its
behaviour to match the lack of needed mitigations.
Further, as the EL0 and EL1 code paths are cleanly separated, we can split
`do_brk64()` into `do_el0_brk64()` and `do_el1_brk64()`, and call them
directly from the relevant entry paths.
Use `die()` directly for the EL1 error path, as in `do_el1_bti()` and
`do_el1_undef()`.
We can also remove `NOKRPOBE_SYMBOL` for the EL0 path, as it cannot
lead to a kprobe recursion.
When taking a BRK64 exception from EL0, the exception handling is safely
preemptible : the only possible handler is `uprobe_brk_handler()`.
It only operates on task-local data and properly checks its validity,
then raises a Thread Information Flag, processed before returning
to userspace in `do_notify_resume()`, which is already preemptible.
Thus we can safely unmask interrupts and enable preemption before
handling the break itself, fixing a PREEMPT_RT issue where the handler
could call a sleeping function with preemption disabled.
Given that the break hook registration is handled statically in
`call_break_hook` since
(arm64: debug: call software break handlers statically)
and that we now bypass the exception handler registration, this change
renders `early_brk64` redundant : its functionality is now handled through
the post-init path.
This also removes the last usage of `el1_dbg()`.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
---
arch/arm64/include/asm/exception.h | 2 ++
arch/arm64/kernel/debug-monitors.c | 48 ++++++++++++++----------------
arch/arm64/kernel/entry-common.c | 19 ++++++++----
3 files changed, 38 insertions(+), 31 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 649f66f15a39..075fb81cacc2 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -66,6 +66,8 @@ void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
void do_el1_softstep(unsigned long esr, struct pt_regs *regs);
void do_watchpoint(unsigned long addr, unsigned long esr,
struct pt_regs *regs);
+void do_el0_brk64(unsigned long esr, struct pt_regs *regs);
+void do_el1_brk64(unsigned long esr, struct pt_regs *regs);
void do_fpsimd_acc(unsigned long esr, struct pt_regs *regs);
void do_sve_acc(unsigned long esr, struct pt_regs *regs);
void do_sme_acc(unsigned long esr, struct pt_regs *regs);
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index cb50d1f59798..7c1c89bb9ad1 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -207,15 +207,8 @@ void do_el1_softstep(unsigned long esr, struct pt_regs *regs)
}
NOKPROBE_SYMBOL(do_el1_softstep);
-static int call_break_hook(struct pt_regs *regs, unsigned long esr)
+static int call_el1_break_hook(struct pt_regs *regs, unsigned long esr)
{
- if (user_mode(regs)) {
- if (IS_ENABLED(CONFIG_UPROBES) &&
- esr_brk_comment(esr) == UPROBES_BRK_IMM)
- return uprobe_brk_handler(regs, esr);
- return DBG_HOOK_ERROR;
- }
-
if (esr_brk_comment(esr) == BUG_BRK_IMM)
return bug_brk_handler(regs, esr);
@@ -252,24 +245,30 @@ static int call_break_hook(struct pt_regs *regs, unsigned long esr)
return DBG_HOOK_ERROR;
}
-NOKPROBE_SYMBOL(call_break_hook);
+NOKPROBE_SYMBOL(call_el1_break_hook);
-static int brk_handler(unsigned long unused, unsigned long esr,
- struct pt_regs *regs)
+/*
+ * We have already unmasked interrupts and enabled preemption
+ * when calling do_el0_brk64() from entry-common.c.
+ */
+void do_el0_brk64(unsigned long esr, struct pt_regs *regs)
{
- if (call_break_hook(regs, esr) == DBG_HOOK_HANDLED)
- return 0;
+ if (IS_ENABLED(CONFIG_UPROBES) &&
+ esr_brk_comment(esr) == UPROBES_BRK_IMM &&
+ uprobe_brk_handler(regs, esr) == DBG_HOOK_HANDLED)
+ return;
- if (user_mode(regs)) {
- send_user_sigtrap(TRAP_BRKPT);
- } else {
- pr_warn("Unexpected kernel BRK exception at EL1\n");
- return -EFAULT;
- }
-
- return 0;
+ send_user_sigtrap(TRAP_BRKPT);
}
-NOKPROBE_SYMBOL(brk_handler);
+
+void do_el1_brk64(unsigned long esr, struct pt_regs *regs)
+{
+ if (call_el1_break_hook(regs, esr) == DBG_HOOK_HANDLED)
+ return;
+
+ die("Oops - BRK", regs, esr);
+}
+NOKPROBE_SYMBOL(do_el1_brk64);
bool try_handle_aarch32_break(struct pt_regs *regs)
{
@@ -311,10 +310,7 @@ bool try_handle_aarch32_break(struct pt_regs *regs)
NOKPROBE_SYMBOL(try_handle_aarch32_break);
void __init debug_traps_init(void)
-{
- hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,
- TRAP_BRKPT, "BRK handler");
-}
+{}
/* Re-enable single step for syscall restarting. */
void user_rewind_single_step(struct task_struct *task)
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index c2ea1653dbb8..1e8306817056 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -565,11 +565,12 @@ static void noinstr el1_watchpt(struct pt_regs *regs, unsigned long esr)
arm64_exit_el1_dbg(regs);
}
-static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
+static void noinstr el1_brk64(struct pt_regs *regs, unsigned long esr)
{
arm64_enter_el1_dbg(regs);
- if (!cortex_a76_erratum_1463225_debug_handler(regs))
- do_debug_exception(far, esr, regs);
+ debug_exception_enter(regs);
+ do_el1_brk64(esr, regs);
+ debug_exception_exit(regs);
arm64_exit_el1_dbg(regs);
}
@@ -621,7 +622,7 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
el1_watchpt(regs, esr);
break;
case ESR_ELx_EC_BRK64:
- el1_dbg(regs, esr);
+ el1_brk64(regs, esr);
break;
case ESR_ELx_EC_FPAC:
el1_fpac(regs, esr);
@@ -857,6 +858,14 @@ static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
exit_to_user_mode(regs);
}
+static void noinstr el0_brk64(struct pt_regs *regs, unsigned long esr)
+{
+ enter_from_user_mode(regs);
+ local_daif_restore(DAIF_PROCCTX);
+ do_el0_brk64(esr, regs);
+ exit_to_user_mode(regs);
+}
+
static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
{
/* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
@@ -945,7 +954,7 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
el0_watchpt(regs, esr);
break;
case ESR_ELx_EC_BRK64:
- el0_dbg(regs, esr);
+ el0_brk64(regs, esr);
break;
case ESR_ELx_EC_FPAC:
el0_fpac(regs, esr);
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 12/13] arm64: debug: split bkpt32 exception entry
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (10 preceding siblings ...)
2025-06-20 21:12 ` [PATCH v4 11/13] arm64: debug: split brk64 " Ada Couprie Diaz
@ 2025-06-20 21:12 ` Ada Couprie Diaz
2025-06-27 15:47 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 13/13] arm64: debug: remove debug exception registration infrastructure Ada Couprie Diaz
2025-06-27 15:48 ` [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Will Deacon
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:12 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Currently all debug exceptions share common entry code and are routed
to `do_debug_exception()`, which calls dynamically-registered
handlers for each specific debug exception. This is unfortunate as
different debug exceptions have different entry handling requirements,
and it would be better to handle these distinct requirements earlier.
The BKPT32 exception can only be triggered by a BKPT instruction. Thus,
we know that the PC is a legitimate address and isn't being used to train
a branch predictor with a bogus address : we don't need to call
`arm64_apply_bp_hardening()`.
The handler for this exception only pends a signal and doesn't depend
on any per-CPU state : we don't need to inhibit preemption, nor do we
need to keep the DAIF exceptions masked, so we can unmask them earlier.
Split the BKPT32 exception entry and adjust function signatures and its
behaviour to match its relaxed constraints compared to other
debug exceptions.
We can also remove `NOKRPOBE_SYMBOL`, as this cannot lead to a kprobe
recursion.
This replaces the last usage of `el0_dbg()`, so remove it.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
---
arch/arm64/include/asm/exception.h | 1 +
arch/arm64/kernel/debug-monitors.c | 7 +++++++
arch/arm64/kernel/entry-common.c | 21 +++++++++------------
3 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 075fb81cacc2..484809ff03d5 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -68,6 +68,7 @@ void do_watchpoint(unsigned long addr, unsigned long esr,
struct pt_regs *regs);
void do_el0_brk64(unsigned long esr, struct pt_regs *regs);
void do_el1_brk64(unsigned long esr, struct pt_regs *regs);
+void do_bkpt32(unsigned long esr, struct pt_regs *regs);
void do_fpsimd_acc(unsigned long esr, struct pt_regs *regs);
void do_sve_acc(unsigned long esr, struct pt_regs *regs);
void do_sme_acc(unsigned long esr, struct pt_regs *regs);
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 7c1c89bb9ad1..43a612eaa7d2 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -270,6 +270,13 @@ void do_el1_brk64(unsigned long esr, struct pt_regs *regs)
}
NOKPROBE_SYMBOL(do_el1_brk64);
+#ifdef CONFIG_COMPAT
+void do_bkpt32(unsigned long esr, struct pt_regs *regs)
+{
+ arm64_notify_die("aarch32 BKPT", regs, SIGTRAP, TRAP_BRKPT, regs->pc, esr);
+}
+#endif /* CONFIG_COMPAT */
+
bool try_handle_aarch32_break(struct pt_regs *regs)
{
u32 arm_instr;
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index 1e8306817056..bbf58fcab142 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -866,17 +866,6 @@ static void noinstr el0_brk64(struct pt_regs *regs, unsigned long esr)
exit_to_user_mode(regs);
}
-static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
-{
- /* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
- unsigned long far = read_sysreg(far_el1);
-
- enter_from_user_mode(regs);
- do_debug_exception(far, esr, regs);
- local_daif_restore(DAIF_PROCCTX);
- exit_to_user_mode(regs);
-}
-
static void noinstr el0_svc(struct pt_regs *regs)
{
enter_from_user_mode(regs);
@@ -1037,6 +1026,14 @@ static void noinstr el0_svc_compat(struct pt_regs *regs)
exit_to_user_mode(regs);
}
+static void noinstr el0_bkpt32(struct pt_regs *regs, unsigned long esr)
+{
+ enter_from_user_mode(regs);
+ local_daif_restore(DAIF_PROCCTX);
+ do_bkpt32(esr, regs);
+ exit_to_user_mode(regs);
+}
+
asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
{
unsigned long esr = read_sysreg(esr_el1);
@@ -1080,7 +1077,7 @@ asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
el0_watchpt(regs, esr);
break;
case ESR_ELx_EC_BKPT32:
- el0_dbg(regs, esr);
+ el0_bkpt32(regs, esr);
break;
default:
el0_inv(regs, esr);
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v4 13/13] arm64: debug: remove debug exception registration infrastructure
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (11 preceding siblings ...)
2025-06-20 21:12 ` [PATCH v4 12/13] arm64: debug: split bkpt32 " Ada Couprie Diaz
@ 2025-06-20 21:12 ` Ada Couprie Diaz
2025-06-27 15:47 ` Will Deacon
2025-06-27 15:48 ` [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Will Deacon
13 siblings, 1 reply; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-20 21:12 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, Will Deacon
Now that debug exceptions are handled individually and without the need
for dynamic registration, remove the unused registration infrastructure.
This removes the external caller for `debug_exception_enter()` and
`debug_exception_exit()`.
Make them static again and remove them from the header.
Remove `early_brk64()` as it has been made redundant by
(arm64: debug: split brk64 exception entry) and is not used anymore.
Note : in `early_brk64()` `bug_brk_handler()` is called unconditionally
as a fall-through, but now `call_break_hook()` only calls it if the
immediate matches.
This does not change the behaviour in early boot, as if
`bug_brk_handler()` was called on a non-BUG immediate it would return
DBG_HOOK_ERROR anyway, which `call_break_hook()` will do if no immediate
matches.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
---
arch/arm64/include/asm/debug-monitors.h | 2 -
arch/arm64/include/asm/exception.h | 6 ---
arch/arm64/include/asm/system_misc.h | 4 --
arch/arm64/kernel/debug-monitors.c | 3 --
arch/arm64/kernel/entry-common.c | 4 +-
arch/arm64/kernel/traps.c | 26 +-----------
arch/arm64/mm/fault.c | 53 -------------------------
7 files changed, 3 insertions(+), 95 deletions(-)
diff --git a/arch/arm64/include/asm/debug-monitors.h b/arch/arm64/include/asm/debug-monitors.h
index 586290e95d87..4dfc37dd1d96 100644
--- a/arch/arm64/include/asm/debug-monitors.h
+++ b/arch/arm64/include/asm/debug-monitors.h
@@ -94,7 +94,5 @@ static inline bool try_step_suspended_breakpoints(struct pt_regs *regs)
bool try_handle_aarch32_break(struct pt_regs *regs);
-void debug_traps_init(void);
-
#endif /* __ASSEMBLY */
#endif /* __ASM_DEBUG_MONITORS_H */
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 484809ff03d5..acaee1c5b3e3 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -59,8 +59,6 @@ void do_el0_bti(struct pt_regs *regs);
void do_el1_bti(struct pt_regs *regs, unsigned long esr);
void do_el0_gcs(struct pt_regs *regs, unsigned long esr);
void do_el1_gcs(struct pt_regs *regs, unsigned long esr);
-void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
- struct pt_regs *regs);
void do_breakpoint(unsigned long esr, struct pt_regs *regs);
void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
void do_el1_softstep(unsigned long esr, struct pt_regs *regs);
@@ -88,8 +86,4 @@ void do_serror(struct pt_regs *regs, unsigned long esr);
void do_signal(struct pt_regs *regs);
void __noreturn panic_bad_stack(struct pt_regs *regs, unsigned long esr, unsigned long far);
-
-void debug_exception_enter(struct pt_regs *regs);
-void debug_exception_exit(struct pt_regs *regs);
-
#endif /* __ASM_EXCEPTION_H */
diff --git a/arch/arm64/include/asm/system_misc.h b/arch/arm64/include/asm/system_misc.h
index c34344256762..344b1c1a4bbb 100644
--- a/arch/arm64/include/asm/system_misc.h
+++ b/arch/arm64/include/asm/system_misc.h
@@ -25,10 +25,6 @@ void arm64_notify_die(const char *str, struct pt_regs *regs,
int signo, int sicode, unsigned long far,
unsigned long err);
-void hook_debug_fault_code(int nr, int (*fn)(unsigned long, unsigned long,
- struct pt_regs *),
- int sig, int code, const char *name);
-
struct mm_struct;
extern void __show_regs(struct pt_regs *);
diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c
index 43a612eaa7d2..43f342a01fdf 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -316,9 +316,6 @@ bool try_handle_aarch32_break(struct pt_regs *regs)
}
NOKPROBE_SYMBOL(try_handle_aarch32_break);
-void __init debug_traps_init(void)
-{}
-
/* Re-enable single step for syscall restarting. */
void user_rewind_single_step(struct task_struct *task)
{
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index bbf58fcab142..4b67312a88ad 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -448,7 +448,7 @@ static __always_inline void fpsimd_syscall_exit(void)
* accidentally schedule in exception context and it will force a warning
* if we somehow manage to schedule by accident.
*/
-void debug_exception_enter(struct pt_regs *regs)
+static void debug_exception_enter(struct pt_regs *regs)
{
preempt_disable();
@@ -457,7 +457,7 @@ void debug_exception_enter(struct pt_regs *regs)
}
NOKPROBE_SYMBOL(debug_exception_enter);
-void debug_exception_exit(struct pt_regs *regs)
+static void debug_exception_exit(struct pt_regs *regs)
{
preempt_enable_no_resched();
}
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index 654c8ea46ec6..d6c5268e6947 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -1101,29 +1101,5 @@ int ubsan_brk_handler(struct pt_regs *regs, unsigned long esr)
}
#endif
-/*
- * Initial handler for AArch64 BRK exceptions
- * This handler only used until debug_traps_init().
- */
-int __init early_brk64(unsigned long addr, unsigned long esr,
- struct pt_regs *regs)
-{
-#ifdef CONFIG_CFI_CLANG
- if (esr_is_cfi_brk(esr))
- return cfi_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
-#endif
-#ifdef CONFIG_KASAN_SW_TAGS
- if ((esr_brk_comment(esr) & ~KASAN_BRK_MASK) == KASAN_BRK_IMM)
- return kasan_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
-#endif
-#ifdef CONFIG_UBSAN_TRAP
- if (esr_is_ubsan_brk(esr))
- return ubsan_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
-#endif
- return bug_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
-}
-
void __init trap_init(void)
-{
- debug_traps_init();
-}
+{}
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index d451d7d834f1..bc7f554dea9f 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -53,18 +53,12 @@ struct fault_info {
};
static const struct fault_info fault_info[];
-static struct fault_info debug_fault_info[];
static inline const struct fault_info *esr_to_fault_info(unsigned long esr)
{
return fault_info + (esr & ESR_ELx_FSC);
}
-static inline const struct fault_info *esr_to_debug_fault_info(unsigned long esr)
-{
- return debug_fault_info + DBG_ESR_EVT(esr);
-}
-
static void data_abort_decode(unsigned long esr)
{
unsigned long iss2 = ESR_ELx_ISS2(esr);
@@ -938,53 +932,6 @@ void do_sp_pc_abort(unsigned long addr, unsigned long esr, struct pt_regs *regs)
}
NOKPROBE_SYMBOL(do_sp_pc_abort);
-/*
- * __refdata because early_brk64 is __init, but the reference to it is
- * clobbered at arch_initcall time.
- * See traps.c and debug-monitors.c:debug_traps_init().
- */
-static struct fault_info __refdata debug_fault_info[] = {
- { do_bad, SIGTRAP, TRAP_HWBKPT, "hardware breakpoint" },
- { do_bad, SIGTRAP, TRAP_HWBKPT, "hardware single-step" },
- { do_bad, SIGTRAP, TRAP_HWBKPT, "hardware watchpoint" },
- { do_bad, SIGKILL, SI_KERNEL, "unknown 3" },
- { do_bad, SIGTRAP, TRAP_BRKPT, "aarch32 BKPT" },
- { do_bad, SIGKILL, SI_KERNEL, "aarch32 vector catch" },
- { early_brk64, SIGTRAP, TRAP_BRKPT, "aarch64 BRK" },
- { do_bad, SIGKILL, SI_KERNEL, "unknown 7" },
-};
-
-void __init hook_debug_fault_code(int nr,
- int (*fn)(unsigned long, unsigned long, struct pt_regs *),
- int sig, int code, const char *name)
-{
- BUG_ON(nr < 0 || nr >= ARRAY_SIZE(debug_fault_info));
-
- debug_fault_info[nr].fn = fn;
- debug_fault_info[nr].sig = sig;
- debug_fault_info[nr].code = code;
- debug_fault_info[nr].name = name;
-}
-
-void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
- struct pt_regs *regs)
-{
- const struct fault_info *inf = esr_to_debug_fault_info(esr);
- unsigned long pc = instruction_pointer(regs);
-
- debug_exception_enter(regs);
-
- if (user_mode(regs) && !is_ttbr0_addr(pc))
- arm64_apply_bp_hardening();
-
- if (inf->fn(addr_if_watchpoint, esr, regs)) {
- arm64_notify_die(inf->name, regs, inf->sig, inf->code, pc, esr);
- }
-
- debug_exception_exit(regs);
-}
-NOKPROBE_SYMBOL(do_debug_exception);
-
/*
* Used during anonymous page fault handling.
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* Re: [PATCH v4 01/13] arm64: debug: clean up single_step_handler logic
2025-06-20 21:11 ` [PATCH v4 01/13] arm64: debug: clean up single_step_handler logic Ada Couprie Diaz
@ 2025-06-27 15:44 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:44 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:11:55PM +0100, Ada Couprie Diaz wrote:
> Remove the unnecessary boolean which always checks if the handler was found
> and return early instead.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
> Acked-by: Mark Rutland <mark.rutland@arm.com>
> ---
> arch/arm64/kernel/debug-monitors.c | 10 ++++------
> 1 file changed, 4 insertions(+), 6 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 02/13] arm64: refactor aarch32_break_handler()
2025-06-20 21:11 ` [PATCH v4 02/13] arm64: refactor aarch32_break_handler() Ada Couprie Diaz
@ 2025-06-27 15:45 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:45 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:11:56PM +0100, Ada Couprie Diaz wrote:
> `aarch32_break_handler()` is called in `do_el0_undef()` when we
> are trying to handle an exception whose Exception Syndrome is unknown.
> It checks if the instruction hit might be a 32-bit arm break (be it
> A32 or T2), and sends a SIGTRAP to userspace if it is so that it can
> be handled.
>
> However, this is badly represented in the naming of the function, and
> is not consistent with the other functions called with the same logic
> in `do_el0_undef()`.
>
> Rename it `try_handle_aarch32_break()` and change the return value to
> a boolean to align with the logic of the other tentative handlers in
> `do_el0_undef()`, the previous error code being ignored anyway.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
> Acked-by: Mark Rutland <mark.rutland@arm.com>
> ---
> arch/arm64/include/asm/debug-monitors.h | 2 +-
> arch/arm64/kernel/debug-monitors.c | 10 +++++-----
> arch/arm64/kernel/traps.c | 2 +-
> 3 files changed, 7 insertions(+), 7 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 03/13] arm64: debug: call software breakpoint handlers statically
2025-06-20 21:11 ` [PATCH v4 03/13] arm64: debug: call software breakpoint handlers statically Ada Couprie Diaz
@ 2025-06-27 15:45 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:45 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:11:57PM +0100, Ada Couprie Diaz wrote:
> Software breakpoints pass an immediate value in ESR ("comment") that can
> be used to call a specialized handler (KGDB, KASAN...).
> We do so in two different ways :
> - During early boot, `early_brk64` statically checks against known
> immediates and calls the corresponding handler,
> - During init, handlers are dynamically registered into a list. When
> called, the generic software breakpoint handler will iterate over
> the list to find the appropriate handler.
>
> The dynamic registration does not provide any benefit here as it is not
> exported and all its uses are within the arm64 tree. It also depends on an
> RCU list, whose safe access currently relies on the non-preemptible state
> of `do_debug_exception`.
>
> Replace the list iteration logic in `call_break_hooks` to call
> the breakpoint handlers statically if they are enabled, like in
> `early_brk64`.
> Expose the handlers in their respective headers to be reachable from
> `arch/arm64/kernel/debug-monitors.c` at link time.
>
> Unify the naming of the software breakpoint handlers to XXX_brk_handler(),
> making it clear they are related and to differentiate from the
> hardware breakpoints.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> ---
> arch/arm64/include/asm/kgdb.h | 3 +
> arch/arm64/include/asm/kprobes.h | 8 +++
> arch/arm64/include/asm/traps.h | 6 ++
> arch/arm64/include/asm/uprobes.h | 2 +
> arch/arm64/kernel/debug-monitors.c | 54 +++++++++++++----
> arch/arm64/kernel/kgdb.c | 22 ++-----
> arch/arm64/kernel/probes/kprobes.c | 31 ++--------
> arch/arm64/kernel/probes/kprobes_trampoline.S | 2 +-
> arch/arm64/kernel/probes/uprobes.c | 9 +--
> arch/arm64/kernel/traps.c | 59 ++++---------------
> 10 files changed, 83 insertions(+), 113 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 04/13] arm64: debug: call step handlers statically
2025-06-20 21:11 ` [PATCH v4 04/13] arm64: debug: call step " Ada Couprie Diaz
@ 2025-06-27 15:45 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:45 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:11:58PM +0100, Ada Couprie Diaz wrote:
> Software stepping checks for the correct handler by iterating over a list
> of dynamically registered handlers and calling all of them until one
> handles the exception.
>
> This is the only generic way to handle software stepping handlers in arm64
> as the exception does not provide an immediate that could be checked,
> contrary to software breakpoints.
>
> However, the registration mechanism is not exported and has only
> two current users : the KGDB stepping handler, and the uprobe single step
> handler.
> Given that one comes from user mode and the other from kernel mode, call
> the appropriate one by checking the source EL of the exception.
> Add a stand-in that returns DBG_HOOK_ERROR when the configuration
> options are not enabled.
>
> Remove `arch_init_uprobes()` as it is not useful anymore and is
> specific to arm64.
>
> Unify the naming of the handler to XXX_single_step_handler(), making it
> clear they are related.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> ---
> arch/arm64/include/asm/kgdb.h | 9 +++++++++
> arch/arm64/include/asm/uprobes.h | 9 +++++++++
> arch/arm64/kernel/debug-monitors.c | 25 ++++++-------------------
> arch/arm64/kernel/kgdb.c | 17 +++--------------
> arch/arm64/kernel/probes/uprobes.c | 15 +--------------
> 5 files changed, 28 insertions(+), 47 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 05/13] arm64: debug: remove break/step handler registration infrastructure
2025-06-20 21:11 ` [PATCH v4 05/13] arm64: debug: remove break/step handler registration infrastructure Ada Couprie Diaz
@ 2025-06-27 15:45 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:45 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:11:59PM +0100, Ada Couprie Diaz wrote:
> Remove all infrastructure for the dynamic registration previously used by
> software breakpoints and stepping handlers.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
> ---
> arch/arm64/include/asm/debug-monitors.h | 24 ----------
> arch/arm64/kernel/debug-monitors.c | 63 -------------------------
> 2 files changed, 87 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 06/13] arm64: entry: Add entry and exit functions for debug exceptions
2025-06-20 21:12 ` [PATCH v4 06/13] arm64: entry: Add entry and exit functions for debug exceptions Ada Couprie Diaz
@ 2025-06-27 15:46 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:46 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:12:00PM +0100, Ada Couprie Diaz wrote:
> Move the `debug_exception_enter()` and `debug_exception_exit()`
> functions from mm/fault.c, as they are needed to split
> the debug exceptions entry paths from the current unified one.
>
> Make them externally visible in include/asm/exception.h until
> the caller in mm/fault.c is cleaned up.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
> ---
> arch/arm64/include/asm/exception.h | 4 ++++
> arch/arm64/kernel/entry-common.c | 22 ++++++++++++++++++++++
> arch/arm64/mm/fault.c | 22 ----------------------
> 3 files changed, 26 insertions(+), 22 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 07/13] arm64: debug: split hardware breakpoint exception entry
2025-06-20 21:12 ` [PATCH v4 07/13] arm64: debug: split hardware breakpoint exception entry Ada Couprie Diaz
@ 2025-06-27 15:46 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:46 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:12:01PM +0100, Ada Couprie Diaz wrote:
> Currently all debug exceptions share common entry code and are routed
> to `do_debug_exception()`, which calls dynamically-registered
> handlers for each specific debug exception. This is unfortunate as
> different debug exceptions have different entry handling requirements,
> and it would be better to handle these distinct requirements earlier.
>
> Hardware breakpoints exceptions are generated by the hardware after user
> configuration. As such, they can be exploited when training branch
> predictors outside of the userspace VA range: they still need to call
> `arm64_apply_bp_hardening()` if needed to mitigate against this attack.
>
> However, they do not need to handle the Cortex-A76 erratum #1463225 as
> it only applies to single stepping exceptions.
> It does not set an address in FAR_EL1 either, only the hardware
> watchpoint does.
>
> As the hardware breakpoint handler only returns 0 and never triggers
> the call to `arm64_notify_die()`, we can call it directly from
> `entry-common.c`.
> Split the hardware breakpoint exception entry, adjust
> the function signature, and handling of the Cortex-A76 erratum to fit
> the behaviour of the exception.
>
> Move the call to `arm64_apply_bp_hardening()` to `entry-common.c` so that
> we can do it as early as possible, and only for the exceptions coming
> from EL0, where it is needed.
> This is safe to do as it is `noinstr`, as are all the functions it
> may call. `el0_ia()` and `el0_pc()` already call it this way.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> ---
> arch/arm64/include/asm/exception.h | 1 +
> arch/arm64/kernel/entry-common.c | 28 ++++++++++++++++++++++++++++
> arch/arm64/kernel/hw_breakpoint.c | 16 ++++++----------
> 3 files changed, 35 insertions(+), 10 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 08/13] arm64: debug: refactor reinstall_suspended_bps()
2025-06-20 21:12 ` [PATCH v4 08/13] arm64: debug: refactor reinstall_suspended_bps() Ada Couprie Diaz
@ 2025-06-27 15:46 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:46 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:12:02PM +0100, Ada Couprie Diaz wrote:
> `reinstall_suspended_bps()` plays a key part in the stepping process
> when we have hardware breakpoints and watchpoints enabled.
> It checks if we need to step one, will re-enable it if it has
> been handled and will return whether or not we need to proceed with
> a single-step.
>
> However, the current naming and return values make it harder to understand
> the logic and goal of the function.
>
> Rename it `try_step_suspended_breakpoints()` and change the return value
> to a boolean, aligning it with similar functions used in
> `do_el0_undef()` like `try_emulate_mrs()`, and making its behaviour
> more obvious.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com>
> ---
> arch/arm64/include/asm/debug-monitors.h | 6 +++---
> arch/arm64/kernel/debug-monitors.c | 2 +-
> arch/arm64/kernel/hw_breakpoint.c | 25 ++++++++++++-------------
> 3 files changed, 16 insertions(+), 17 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 09/13] arm64: debug: split single stepping exception entry
2025-06-20 21:12 ` [PATCH v4 09/13] arm64: debug: split single stepping exception entry Ada Couprie Diaz
@ 2025-06-27 15:46 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:46 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:12:03PM +0100, Ada Couprie Diaz wrote:
> Currently all debug exceptions share common entry code and are routed
> to `do_debug_exception()`, which calls dynamically-registered
> handlers for each specific debug exception. This is unfortunate as
> different debug exceptions have different entry handling requirements,
> and it would be better to handle these distinct requirements earlier.
>
> The single stepping exception has the most constraints : it can be
> exploited to train branch predictors and it needs special handling at EL1
> for the Cortex-A76 erratum #1463225. We need to conserve all those
> mitigations.
> However, it does not write an address at FAR_EL1, as only hardware
> watchpoints do so.
>
> The single-step handler does its own signaling if it needs to and only
> returns 0, so we can call it directly from `entry-common.c`.
>
> Split the single stepping exception entry, adjust the function signature,
> keep the security mitigation and erratum handling.
> Further, as the EL0 and EL1 code paths are cleanly separated, we can split
> `do_softstep()` into `do_el0_softstep()` and `do_el1_softstep()` and
> call them directly from the relevant entry paths.
> We can also remove `NOKPROBE_SYMBOL` for the EL0 path, as it cannot
> lead to a kprobe recursion.
>
> Move the call to `arm64_apply_bp_hardening()` to `entry-common.c` so that
> we can do it as early as possible, and only for the exceptions coming
> from EL0, where it is needed.
> This is safe to do as it is `noinstr`, as are all the functions it
> may call. `el0_ia()` and `el0_pc()` already call it this way.
>
> When taking a soft-step exception from EL0, most of the single stepping
> handling is safely preemptible : the only possible handler is
> `uprobe_single_step_handler()`. It only operates on task-local data and
> properly checks its validity, then raises a Thread Information Flag,
> processed before returning to userspace in `do_notify_resume()`, which
> is already preemptible.
> However, the soft-step handler first calls `reinstall_suspended_bps()`
> to check if there is any hardware breakpoint or watchpoint pending
> or already stepped through.
> This cannot be preempted as it manipulates the hardware breakpoint and
> watchpoint registers.
>
> Move the call to `try_step_suspended_breakpoints()` to `entry-common.c`
> and adjust the relevant comments.
> We can now safely unmask interrupts before handling the step itself,
> fixing a PREEMPT_RT issue where the handler could call a sleeping function
> with preemption disabled.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Closes: https://lore.kernel.org/linux-arm-kernel/Z6YW_Kx4S2tmj2BP@uudg.org/
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> ---
> arch/arm64/include/asm/exception.h | 2 +
> arch/arm64/kernel/debug-monitors.c | 77 +++++++++++-------------------
> arch/arm64/kernel/entry-common.c | 43 +++++++++++++++++
> arch/arm64/kernel/hw_breakpoint.c | 2 +-
> 4 files changed, 75 insertions(+), 49 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 10/13] arm64: debug: split hardware watchpoint exception entry
2025-06-20 21:12 ` [PATCH v4 10/13] arm64: debug: split hardware watchpoint " Ada Couprie Diaz
@ 2025-06-27 15:46 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:46 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:12:04PM +0100, Ada Couprie Diaz wrote:
> Currently all debug exceptions share common entry code and are routed
> to `do_debug_exception()`, which calls dynamically-registered
> handlers for each specific debug exception. This is unfortunate as
> different debug exceptions have different entry handling requirements,
> and it would be better to handle these distinct requirements earlier.
>
> Hardware watchpoints are the only debug exceptions that will write
> FAR_EL1, so we need to preserve it and pass it down.
> However, they cannot be used to maliciously train branch predictors, so
> we can omit calling `arm64_bp_hardening()`, nor do they need to handle
> the Cortex-A76 erratum #1463225, as it only applies to single stepping
> exceptions.
>
> As the hardware watchpoint handler only returns 0 and never triggers
> the call to `arm64_notify_die()`, we can call it directly from
> `entry-common.c`.
> Split the hardware watchpoint exception entry and adjust the behaviour
> to match the lack of needed mitigations.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> ---
> arch/arm64/include/asm/exception.h | 2 ++
> arch/arm64/kernel/entry-common.c | 31 +++++++++++++++++++++++++++++-
> arch/arm64/kernel/hw_breakpoint.c | 17 +++++-----------
> 3 files changed, 37 insertions(+), 13 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 12/13] arm64: debug: split bkpt32 exception entry
2025-06-20 21:12 ` [PATCH v4 12/13] arm64: debug: split bkpt32 " Ada Couprie Diaz
@ 2025-06-27 15:47 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:47 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:12:06PM +0100, Ada Couprie Diaz wrote:
> Currently all debug exceptions share common entry code and are routed
> to `do_debug_exception()`, which calls dynamically-registered
> handlers for each specific debug exception. This is unfortunate as
> different debug exceptions have different entry handling requirements,
> and it would be better to handle these distinct requirements earlier.
>
> The BKPT32 exception can only be triggered by a BKPT instruction. Thus,
> we know that the PC is a legitimate address and isn't being used to train
> a branch predictor with a bogus address : we don't need to call
> `arm64_apply_bp_hardening()`.
>
> The handler for this exception only pends a signal and doesn't depend
> on any per-CPU state : we don't need to inhibit preemption, nor do we
> need to keep the DAIF exceptions masked, so we can unmask them earlier.
>
> Split the BKPT32 exception entry and adjust function signatures and its
> behaviour to match its relaxed constraints compared to other
> debug exceptions.
> We can also remove `NOKRPOBE_SYMBOL`, as this cannot lead to a kprobe
> recursion.
>
> This replaces the last usage of `el0_dbg()`, so remove it.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> ---
> arch/arm64/include/asm/exception.h | 1 +
> arch/arm64/kernel/debug-monitors.c | 7 +++++++
> arch/arm64/kernel/entry-common.c | 21 +++++++++------------
> 3 files changed, 17 insertions(+), 12 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 11/13] arm64: debug: split brk64 exception entry
2025-06-20 21:12 ` [PATCH v4 11/13] arm64: debug: split brk64 " Ada Couprie Diaz
@ 2025-06-27 15:47 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:47 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:12:05PM +0100, Ada Couprie Diaz wrote:
> Currently all debug exceptions share common entry code and are routed
> to `do_debug_exception()`, which calls dynamically-registered
> handlers for each specific debug exception. This is unfortunate as
> different debug exceptions have different entry handling requirements,
> and it would be better to handle these distinct requirements earlier.
>
> The BRK64 instruction can only be triggered by a BRK instruction. Thus,
> we know that the PC is a legitimate address and isn't being used to train
> a branch predictor with a bogus address : we don't need to call
> `arm64_apply_bp_hardening()`.
>
> We do not need to handle the Cortex-A76 erratum #1463225 either, as it
> only relevant for single stepping at EL1.
> BRK64 does not write FAR_EL1 either, as only hardware watchpoints do so.
>
> Split the BRK64 exception entry, adjust the function signature, and its
> behaviour to match the lack of needed mitigations.
> Further, as the EL0 and EL1 code paths are cleanly separated, we can split
> `do_brk64()` into `do_el0_brk64()` and `do_el1_brk64()`, and call them
> directly from the relevant entry paths.
> Use `die()` directly for the EL1 error path, as in `do_el1_bti()` and
> `do_el1_undef()`.
> We can also remove `NOKRPOBE_SYMBOL` for the EL0 path, as it cannot
> lead to a kprobe recursion.
>
> When taking a BRK64 exception from EL0, the exception handling is safely
> preemptible : the only possible handler is `uprobe_brk_handler()`.
> It only operates on task-local data and properly checks its validity,
> then raises a Thread Information Flag, processed before returning
> to userspace in `do_notify_resume()`, which is already preemptible.
> Thus we can safely unmask interrupts and enable preemption before
> handling the break itself, fixing a PREEMPT_RT issue where the handler
> could call a sleeping function with preemption disabled.
>
> Given that the break hook registration is handled statically in
> `call_break_hook` since
> (arm64: debug: call software break handlers statically)
> and that we now bypass the exception handler registration, this change
> renders `early_brk64` redundant : its functionality is now handled through
> the post-init path.
>
> This also removes the last usage of `el1_dbg()`.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> ---
> arch/arm64/include/asm/exception.h | 2 ++
> arch/arm64/kernel/debug-monitors.c | 48 ++++++++++++++----------------
> arch/arm64/kernel/entry-common.c | 19 ++++++++----
> 3 files changed, 38 insertions(+), 31 deletions(-)
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 13/13] arm64: debug: remove debug exception registration infrastructure
2025-06-20 21:12 ` [PATCH v4 13/13] arm64: debug: remove debug exception registration infrastructure Ada Couprie Diaz
@ 2025-06-27 15:47 ` Will Deacon
0 siblings, 0 replies; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:47 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
On Fri, Jun 20, 2025 at 10:12:07PM +0100, Ada Couprie Diaz wrote:
> Now that debug exceptions are handled individually and without the need
> for dynamic registration, remove the unused registration infrastructure.
>
> This removes the external caller for `debug_exception_enter()` and
> `debug_exception_exit()`.
> Make them static again and remove them from the header.
>
> Remove `early_brk64()` as it has been made redundant by
> (arm64: debug: split brk64 exception entry) and is not used anymore.
> Note : in `early_brk64()` `bug_brk_handler()` is called unconditionally
> as a fall-through, but now `call_break_hook()` only calls it if the
> immediate matches.
> This does not change the behaviour in early boot, as if
> `bug_brk_handler()` was called on a non-BUG immediate it would return
> DBG_HOOK_ERROR anyway, which `call_break_hook()` will do if no immediate
> matches.
>
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
> ---
> arch/arm64/include/asm/debug-monitors.h | 2 -
> arch/arm64/include/asm/exception.h | 6 ---
> arch/arm64/include/asm/system_misc.h | 4 --
> arch/arm64/kernel/debug-monitors.c | 3 --
> arch/arm64/kernel/entry-common.c | 4 +-
> arch/arm64/kernel/traps.c | 26 +-----------
> arch/arm64/mm/fault.c | 53 -------------------------
> 7 files changed, 3 insertions(+), 95 deletions(-)
[...]
> diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
> index 654c8ea46ec6..d6c5268e6947 100644
> --- a/arch/arm64/kernel/traps.c
> +++ b/arch/arm64/kernel/traps.c
> @@ -1101,29 +1101,5 @@ int ubsan_brk_handler(struct pt_regs *regs, unsigned long esr)
> }
> #endif
>
> -/*
> - * Initial handler for AArch64 BRK exceptions
> - * This handler only used until debug_traps_init().
> - */
> -int __init early_brk64(unsigned long addr, unsigned long esr,
> - struct pt_regs *regs)
> -{
> -#ifdef CONFIG_CFI_CLANG
> - if (esr_is_cfi_brk(esr))
> - return cfi_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
> -#endif
> -#ifdef CONFIG_KASAN_SW_TAGS
> - if ((esr_brk_comment(esr) & ~KASAN_BRK_MASK) == KASAN_BRK_IMM)
> - return kasan_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
> -#endif
> -#ifdef CONFIG_UBSAN_TRAP
> - if (esr_is_ubsan_brk(esr))
> - return ubsan_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
> -#endif
> - return bug_brk_handler(regs, esr) != DBG_HOOK_HANDLED;
> -}
> -
> void __init trap_init(void)
> -{
> - debug_traps_init();
> -}
> +{}
nit: I think you can actually just remove trap_init() entirely as there's
a weak definition in init/main.c
With that:
Reviewed-by: Will Deacon <will@kernel.org>
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
` (12 preceding siblings ...)
2025-06-20 21:12 ` [PATCH v4 13/13] arm64: debug: remove debug exception registration infrastructure Ada Couprie Diaz
@ 2025-06-27 15:48 ` Will Deacon
2025-06-27 17:27 ` Ada Couprie Diaz
13 siblings, 1 reply; 29+ messages in thread
From: Will Deacon @ 2025-06-27 15:48 UTC (permalink / raw)
To: Ada Couprie Diaz
Cc: linux-arm-kernel, Catalin Marinas, Mark Rutland,
Anshuman Khandual, Luis Claudio R . Goncalves
Hi Ada,
On Fri, Jun 20, 2025 at 10:11:54PM +0100, Ada Couprie Diaz wrote:
> This series simplifies the debug exception entry path by removing handler
> registration mechanisms for the debug exception handlers, a holdover from
> the arm kernel, as well as the break and stepping handlers.
> This moves much of the code related to debug exceptions outside of
> `mm/fault.c` where it didn't make much sense.
> This allows us to split the debug exception entries: going from one common
> path per EL for all debug exceptions to a unique one per exception and EL.
>
> The result is a much simpler and fully static exception entry path, which
> we tailor to the different exceptions and their constraints.
I think this looks great, thanks. I've left Reviewed-bys on everything
and a minor nit on the last patch. However, the linker is less
enthusiastic than me because it fails with this series and 'allnoconfig':
ld.lld: error: undefined symbol: do_breakpoint
>>> referenced by entry-common.c
>>> arch/arm64/kernel/entry-common.o:(el1_breakpt) in archive vmlinux.a
>>> referenced by entry-common.c
>>> arch/arm64/kernel/entry-common.o:(el0_breakpt) in archive vmlinux.a
ld.lld: error: undefined symbol: do_watchpoint
>>> referenced by entry-common.c
>>> arch/arm64/kernel/entry-common.o:(el1_watchpt) in archive vmlinux.a
>>> referenced by entry-common.c
>>> arch/arm64/kernel/entry-common.o:(el0_watchpt) in archive vmlinux.a
make[2]: *** [scripts/Makefile.vmlinux:91: vmlinux] Error 1
Please can you take a look?
Will
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry
2025-06-27 15:48 ` [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Will Deacon
@ 2025-06-27 17:27 ` Ada Couprie Diaz
0 siblings, 0 replies; 29+ messages in thread
From: Ada Couprie Diaz @ 2025-06-27 17:27 UTC (permalink / raw)
To: Will Deacon
Cc: Mark Rutland, Anshuman Khandual, Luis Claudio R . Goncalves,
Catalin Marinas, linux-arm-kernel
Hi Will,
On 27/06/2025 16:48, Will Deacon wrote:
> Hi Ada,
>
> On Fri, Jun 20, 2025 at 10:11:54PM +0100, Ada Couprie Diaz wrote:
>> This series simplifies the debug exception entry path by removing handler
>> registration mechanisms for the debug exception handlers, a holdover from
>> the arm kernel, as well as the break and stepping handlers.
>> This moves much of the code related to debug exceptions outside of
>> `mm/fault.c` where it didn't make much sense.
>> This allows us to split the debug exception entries: going from one common
>> path per EL for all debug exceptions to a unique one per exception and EL.
>>
>> The result is a much simpler and fully static exception entry path, which
>> we tailor to the different exceptions and their constraints.
> I think this looks great, thanks. I've left Reviewed-bys on everything
> and a minor nit on the last patch.
Thanks ! Added your tag and applied the nit, I raised the possibility
earlier
so absolutely fine with it.
> However, the linker is less
> enthusiastic than me because it fails with this series and 'allnoconfig':
>
> ld.lld: error: undefined symbol: do_breakpoint
> >>> referenced by entry-common.c
> >>> arch/arm64/kernel/entry-common.o:(el1_breakpt) in archive vmlinux.a
> >>> referenced by entry-common.c
> >>> arch/arm64/kernel/entry-common.o:(el0_breakpt) in archive vmlinux.a
>
> ld.lld: error: undefined symbol: do_watchpoint
> >>> referenced by entry-common.c
> >>> arch/arm64/kernel/entry-common.o:(el1_watchpt) in archive vmlinux.a
> >>> referenced by entry-common.c
> >>> arch/arm64/kernel/entry-common.o:(el0_watchpt) in archive vmlinux.a
> make[2]: *** [scripts/Makefile.vmlinux:91: vmlinux] Error 1
>
> Please can you take a look?
Apologies, I knew exactly what that was : hardware breakpoints being
behind a config,
which I failed to consider when exposing the handlers in
`include/asm/exception.h`.
I added a static inline stub for this case for both and built
successfully with `allnoconfig`.
> Will
I sent a v5 with the changes mentioned here and the fix for `allnoconfig`.
Thanks,
Ada
^ permalink raw reply [flat|nested] 29+ messages in thread
end of thread, other threads:[~2025-06-27 19:04 UTC | newest]
Thread overview: 29+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-20 21:11 [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Ada Couprie Diaz
2025-06-20 21:11 ` [PATCH v4 01/13] arm64: debug: clean up single_step_handler logic Ada Couprie Diaz
2025-06-27 15:44 ` Will Deacon
2025-06-20 21:11 ` [PATCH v4 02/13] arm64: refactor aarch32_break_handler() Ada Couprie Diaz
2025-06-27 15:45 ` Will Deacon
2025-06-20 21:11 ` [PATCH v4 03/13] arm64: debug: call software breakpoint handlers statically Ada Couprie Diaz
2025-06-27 15:45 ` Will Deacon
2025-06-20 21:11 ` [PATCH v4 04/13] arm64: debug: call step " Ada Couprie Diaz
2025-06-27 15:45 ` Will Deacon
2025-06-20 21:11 ` [PATCH v4 05/13] arm64: debug: remove break/step handler registration infrastructure Ada Couprie Diaz
2025-06-27 15:45 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 06/13] arm64: entry: Add entry and exit functions for debug exceptions Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 07/13] arm64: debug: split hardware breakpoint exception entry Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 08/13] arm64: debug: refactor reinstall_suspended_bps() Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 09/13] arm64: debug: split single stepping exception entry Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 10/13] arm64: debug: split hardware watchpoint " Ada Couprie Diaz
2025-06-27 15:46 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 11/13] arm64: debug: split brk64 " Ada Couprie Diaz
2025-06-27 15:47 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 12/13] arm64: debug: split bkpt32 " Ada Couprie Diaz
2025-06-27 15:47 ` Will Deacon
2025-06-20 21:12 ` [PATCH v4 13/13] arm64: debug: remove debug exception registration infrastructure Ada Couprie Diaz
2025-06-27 15:47 ` Will Deacon
2025-06-27 15:48 ` [PATCH v4 00/13] arm64: debug: remove hook registration, split exception entry Will Deacon
2025-06-27 17:27 ` Ada Couprie Diaz
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).