* [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry
@ 2026-06-01 10:25 Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 01/15] arm64: Introduce esr_is_ubsan_brk() Sebastian Andrzej Siewior
` (15 more replies)
0 siblings, 16 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable; +Cc: Sebastian Andrzej Siewior
Hi,
this is a backport of the "arm64: debug: remove hook registration, split
exception entry" series
https://lore.kernel.org/all/20250707114109.35672-1-ada.coupriediaz@arm.com/
which has been merged as of v6.17-rc1. It fixes the HW breakpoint issue
on PREEMPT_RT.
I only picked one dependency and manually fixed the other conflicts to
avoid a larger backport.
This has been prepared against v6.12.91. v6.12-stable is the only
relevant tree for a backport (earlier stable version have no PREEMPT_RT
support).
v1…v2: https://lore.kernel.org/all/20260528144825.850351-1-bigeasy@linutronix.de/
- Added "arm64: debug: always unmask interrupts in el0_softstp()" because it
has a fixes: tag on one of the added patches. (Suggested by Ada)
- Added Ada's Reviewed-by tag.
Ada Couprie Diaz (14):
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
arm64: debug: always unmask interrupts in el0_softstp()
Mostafa Saleh (1):
arm64: Introduce esr_is_ubsan_brk()
arch/arm64/include/asm/debug-monitors.h | 34 +--
arch/arm64/include/asm/esr.h | 5 +
arch/arm64/include/asm/exception.h | 14 +-
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 | 258 +++++++-----------
arch/arm64/kernel/entry-common.c | 148 +++++++++-
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 | 80 +-----
arch/arm64/mm/fault.c | 75 -----
17 files changed, 340 insertions(+), 471 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 01/15] arm64: Introduce esr_is_ubsan_brk()
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 02/15] arm64: debug: clean up single_step_handler logic Sebastian Andrzej Siewior
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Mostafa Saleh, Kees Cook, Marc Zyngier, Sebastian Andrzej Siewior,
Ada Couprie Diaz
From: Mostafa Saleh <smostafa@google.com>
Upstream commit dc1fd37a7f501731e488c1c6f86b2f591632a4ad
Soon, KVM is going to use this logic for hypervisor panics,
so add it in a wrapper that can be used by the hypervisor exit
handler to decode hyp panics.
Signed-off-by: Mostafa Saleh <smostafa@google.com>
Reviewed-by: Kees Cook <kees@kernel.org>
Link: https://lore.kernel.org/r/20250430162713.1997569-2-smostafa@google.com
Signed-off-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
---
arch/arm64/include/asm/esr.h | 5 +++++
arch/arm64/kernel/traps.c | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
index 5f4dc6364dbb9..b0520b18192c5 100644
--- a/arch/arm64/include/asm/esr.h
+++ b/arch/arm64/include/asm/esr.h
@@ -409,6 +409,11 @@ static inline bool esr_is_cfi_brk(unsigned long esr)
(esr_brk_comment(esr) & ~CFI_BRK_IMM_MASK) == CFI_BRK_IMM_BASE;
}
+static inline bool esr_is_ubsan_brk(unsigned long esr)
+{
+ return (esr_brk_comment(esr) & ~UBSAN_BRK_MASK) == UBSAN_BRK_IMM;
+}
+
static inline bool esr_fsc_is_translation_fault(unsigned long esr)
{
esr = esr & ESR_ELx_FSC;
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index e2e8ffa65aa58..5e138cf5d4ade 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -1136,7 +1136,7 @@ int __init early_brk64(unsigned long addr, unsigned long esr,
return kasan_handler(regs, esr) != DBG_HOOK_HANDLED;
#endif
#ifdef CONFIG_UBSAN_TRAP
- if ((esr_brk_comment(esr) & ~UBSAN_BRK_MASK) == UBSAN_BRK_IMM)
+ if (esr_is_ubsan_brk(esr))
return ubsan_handler(regs, esr) != DBG_HOOK_HANDLED;
#endif
return bug_handler(regs, esr) != DBG_HOOK_HANDLED;
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 02/15] arm64: debug: clean up single_step_handler logic
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 01/15] arm64: Introduce esr_is_ubsan_brk() Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 03/15] arm64: refactor aarch32_break_handler() Sebastian Andrzej Siewior
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Anshuman Khandual,
Mark Rutland, Will Deacon, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit ad8b22648b7d0bc6f84230508436b1aafc2e2516
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>
Reviewed-by: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20250707114109.35672-2-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@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 024a7b245056a..b7a2155bca42b 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
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 03/15] arm64: refactor aarch32_break_handler()
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 01/15] arm64: Introduce esr_is_ubsan_brk() Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 02/15] arm64: debug: clean up single_step_handler logic Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 04/15] arm64: debug: call software breakpoint handlers statically Sebastian Andrzej Siewior
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Anshuman Khandual,
Mark Rutland, Will Deacon, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit b1e2d95524e4d0f5b643394c739212869e95cf6a
`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>
Reviewed-by: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20250707114109.35672-3-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@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 13d437bcbf58c..3eeea1c9f0666 100644
--- a/arch/arm64/include/asm/debug-monitors.h
+++ b/arch/arm64/include/asm/debug-monitors.h
@@ -115,7 +115,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 b7a2155bca42b..8275b7f575462 100644
--- a/arch/arm64/kernel/debug-monitors.c
+++ b/arch/arm64/kernel/debug-monitors.c
@@ -335,7 +335,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;
@@ -343,7 +343,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 */
@@ -367,12 +367,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 5e138cf5d4ade..c38ebf715be76 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -462,7 +462,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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 04/15] arm64: debug: call software breakpoint handlers statically
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (2 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 03/15] arm64: refactor aarch32_break_handler() Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 05/15] arm64: debug: call step " Sebastian Andrzej Siewior
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Will Deacon,
Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit 6adfdc5e2ef9c71a76d8d127a2eb54f0fbe9be5e
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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-4-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.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 | 57 +++++++++++++-----
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, 84 insertions(+), 115 deletions(-)
diff --git a/arch/arm64/include/asm/kgdb.h b/arch/arm64/include/asm/kgdb.h
index 21fc85e9d2bed..82a76b2102fb6 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 be7a3680dadff..f2782560647be 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 82cf1f879c61d..e3e8944a71c3e 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 014b02897f8e2..3659a79a9f325 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 8275b7f575462..5e89244803000 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,22 +302,48 @@ 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;
- int (*fn)(struct pt_regs *regs, unsigned long esr) = NULL;
-
- 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)
- fn = hook->fn;
+ 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;
}
- return fn ? fn(regs, esr) : 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 4e1f983df3d1c..e3c9e6e11a318 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 b0e0f0aed748a..8661cd4064732 100644
--- a/arch/arm64/kernel/probes/kprobes.c
+++ b/arch/arm64/kernel/probes/kprobes.c
@@ -306,8 +306,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;
@@ -350,13 +350,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);
@@ -374,13 +369,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;
@@ -389,11 +379,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).
@@ -436,9 +421,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 a362f3dbb3d11..b60739d3983f6 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 a2f137a595fc1..fc1bd19c827e6 100644
--- a/arch/arm64/kernel/probes/uprobes.c
+++ b/arch/arm64/kernel/probes/uprobes.c
@@ -165,7 +165,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))
@@ -186,12 +186,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,
@@ -199,7 +193,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 c38ebf715be76..013159bc0882e 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -978,7 +978,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
@@ -988,7 +988,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:
@@ -1008,13 +1008,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;
@@ -1037,15 +1032,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",
@@ -1055,11 +1044,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
@@ -1067,7 +1051,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;
@@ -1098,26 +1082,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(regs, 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
/*
@@ -1129,31 +1101,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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 05/15] arm64: debug: call step handlers statically
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (3 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 04/15] arm64: debug: call software breakpoint handlers statically Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 06/15] arm64: debug: remove break/step handler registration infrastructure Sebastian Andrzej Siewior
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Will Deacon,
Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit 403b48aad5b3e857b8c2576ce6a421f3d23dd6a6
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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-5-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.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 82a76b2102fb6..3184f5d1e3ae4 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 3659a79a9f325..89bfb0213a500 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 5e89244803000..f929b107840de 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 e3c9e6e11a318..f8eaf6084c3d5 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 fc1bd19c827e6..6ae4396577d4a 100644
--- a/arch/arm64/kernel/probes/uprobes.c
+++ b/arch/arm64/kernel/probes/uprobes.c
@@ -174,7 +174,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;
@@ -186,16 +186,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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 06/15] arm64: debug: remove break/step handler registration infrastructure
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (4 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 05/15] arm64: debug: call step " Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 07/15] arm64: entry: Add entry and exit functions for debug exceptions Sebastian Andrzej Siewior
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Anshuman Khandual,
Will Deacon, Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit d4e0b12620946a4011ad695490211fc38bf5cb42
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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-6-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@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 3eeea1c9f0666..5319da0f0ca4e 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 f929b107840de..a28482e25c4c3 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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 07/15] arm64: entry: Add entry and exit functions for debug exceptions
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (5 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 06/15] arm64: debug: remove break/step handler registration infrastructure Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 08/15] arm64: debug: split hardware breakpoint exception entry Sebastian Andrzej Siewior
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Anshuman Khandual,
Will Deacon, Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit eaff68b3286116d499a3d4e513a36d772faba587
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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-7-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@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 f296662590c7f..b1d6a65f6d225 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -77,4 +77,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 d23315ef7b679..2e04e04aaf2ad 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 2d1ebc0c3437f..7c87d2b3b06ea 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -939,28 +939,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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 08/15] arm64: debug: split hardware breakpoint exception entry
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (6 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 07/15] arm64: entry: Add entry and exit functions for debug exceptions Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 09/15] arm64: debug: refactor reinstall_suspended_bps() Sebastian Andrzej Siewior
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Will Deacon,
Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit 43e2ae77fcab8a01101a2e5da528b5222b338e5f
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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-8-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
---
arch/arm64/include/asm/exception.h | 5 +++++
arch/arm64/kernel/entry-common.c | 28 ++++++++++++++++++++++++++++
arch/arm64/kernel/hw_breakpoint.c | 16 ++++++----------
3 files changed, 39 insertions(+), 10 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index b1d6a65f6d225..94f46e9651516 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -59,6 +59,11 @@ void do_el0_bti(struct pt_regs *regs);
void do_el1_bti(struct pt_regs *regs, unsigned long esr);
void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
struct pt_regs *regs);
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+void do_breakpoint(unsigned long esr, struct pt_regs *regs);
+#else
+static inline void do_breakpoint(unsigned long esr, struct pt_regs *regs) {}
+#endif /* CONFIG_HAVE_HW_BREAKPOINT */
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 2e04e04aaf2ad..af0d7575dcfd9 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -508,6 +508,15 @@ static void noinstr el1_bti(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);
@@ -551,6 +560,8 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
el1_bti(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:
@@ -737,6 +748,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 */
@@ -813,6 +837,8 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
el0_mops(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:
@@ -933,6 +959,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 722ac45f9f7b1..d7eede5d869c2 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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 09/15] arm64: debug: refactor reinstall_suspended_bps()
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (7 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 08/15] arm64: debug: split hardware breakpoint exception entry Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 10/15] arm64: debug: split single stepping exception entry Sebastian Andrzej Siewior
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Anshuman Khandual,
Will Deacon, Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit 80691d35523de3292b64c2ffa444aab3d55e51ba
`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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-9-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@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 5319da0f0ca4e..24c7981abeb0b 100644
--- a/arch/arm64/include/asm/debug-monitors.h
+++ b/arch/arm64/include/asm/debug-monitors.h
@@ -83,11 +83,11 @@ int kernel_active_single_step(void);
void kernel_rewind_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 a28482e25c4c3..b95a135ef10a9 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 d7eede5d869c2..309ae24d45480 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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 10/15] arm64: debug: split single stepping exception entry
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (8 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 09/15] arm64: debug: refactor reinstall_suspended_bps() Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 11/15] arm64: debug: split hardware watchpoint " Sebastian Andrzej Siewior
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Will Deacon,
Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit 0ac7584c08ceff13fc1e3082a0104548688d6b00
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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-10-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.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 94f46e9651516..6d40efc28be40 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -64,6 +64,8 @@ void do_breakpoint(unsigned long esr, struct pt_regs *regs);
#else
static inline void do_breakpoint(unsigned long esr, struct pt_regs *regs) {}
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
+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 b95a135ef10a9..10d2bc51a32f7 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 af0d7575dcfd9..c22cc4d0052d5 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -517,6 +517,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);
@@ -563,6 +581,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);
@@ -761,6 +781,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 */
@@ -840,6 +879,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);
@@ -962,6 +1003,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 309ae24d45480..8a80e13347c88 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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 11/15] arm64: debug: split hardware watchpoint exception entry
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (9 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 10/15] arm64: debug: split single stepping exception entry Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 12/15] arm64: debug: split brk64 " Sebastian Andrzej Siewior
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Will Deacon,
Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit 413f0bba005dacf2484bb8ecce212fab9be79d81
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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-11-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
---
arch/arm64/include/asm/exception.h | 4 ++++
arch/arm64/kernel/entry-common.c | 31 ++++++++++++++++++++++++++++++
arch/arm64/kernel/hw_breakpoint.c | 17 +++++-----------
3 files changed, 40 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 6d40efc28be40..594350e552e11 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -61,8 +61,12 @@ void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
struct pt_regs *regs);
#ifdef CONFIG_HAVE_HW_BREAKPOINT
void do_breakpoint(unsigned long esr, struct pt_regs *regs);
+void do_watchpoint(unsigned long addr, unsigned long esr,
+ struct pt_regs *regs);
#else
static inline void do_breakpoint(unsigned long esr, struct pt_regs *regs) {}
+static inline void do_watchpoint(unsigned long addr, unsigned long esr,
+ struct pt_regs *regs) {}
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
void do_el1_softstep(unsigned long esr, struct pt_regs *regs);
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index c22cc4d0052d5..b90babcf2e2b1 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -535,6 +535,18 @@ static void noinstr el1_softstp(struct pt_regs *regs, unsigned long esr)
arm64_exit_el1_dbg(regs);
}
+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)
{
unsigned long far = read_sysreg(far_el1);
@@ -584,6 +596,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;
@@ -800,6 +814,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 */
@@ -882,6 +909,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;
@@ -1006,6 +1035,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 8a80e13347c88..ab76b36dce820 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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 12/15] arm64: debug: split brk64 exception entry
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (10 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 11/15] arm64: debug: split hardware watchpoint " Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 13/15] arm64: debug: split bkpt32 " Sebastian Andrzej Siewior
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Will Deacon,
Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit 31575e11ecf7e44face72d1e624cb147a9283733
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()`.
This also removes the last usage of `el0_dbg()` without `CONFIG_COMPAT`.
Mark it `__maybe_unused`, to prevent a warning when building this patch
without `CONFIG_COMPAT`, as the following patch removes `el0_dbg()`.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-12-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
---
arch/arm64/include/asm/exception.h | 2 ++
arch/arm64/kernel/debug-monitors.c | 48 ++++++++++++++----------------
arch/arm64/kernel/entry-common.c | 24 ++++++++++-----
3 files changed, 40 insertions(+), 34 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 594350e552e11..7bc79602840fd 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -70,6 +70,8 @@ static inline void do_watchpoint(unsigned long addr, unsigned long esr,
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
void do_el1_softstep(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 10d2bc51a32f7..45e0dbe17c82f 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 b90babcf2e2b1..ba114bfdb32b5 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -547,13 +547,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)
{
- unsigned long far = read_sysreg(far_el1);
-
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);
}
@@ -599,7 +598,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);
@@ -827,7 +826,16 @@ static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
exit_to_user_mode(regs);
}
-static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
+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 __maybe_unused
+el0_dbg(struct pt_regs *regs, unsigned long esr)
{
/* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
unsigned long far = read_sysreg(far_el1);
@@ -912,7 +920,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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 13/15] arm64: debug: split bkpt32 exception entry
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (11 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 12/15] arm64: debug: split brk64 " Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 14/15] arm64: debug: remove debug exception registration infrastructure Sebastian Andrzej Siewior
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Will Deacon,
Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit fc5e5d0477c532054ce8692fd16fdaab2cb8946f
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>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-13-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
---
arch/arm64/include/asm/exception.h | 1 +
arch/arm64/kernel/debug-monitors.c | 7 +++++++
arch/arm64/kernel/entry-common.c | 22 +++++++++-------------
3 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index 7bc79602840fd..9b05c6f487ccf 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -72,6 +72,7 @@ void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
void do_el1_softstep(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 45e0dbe17c82f..ed03270fa3437 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 ba114bfdb32b5..9a1ea5a6e6b72 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -834,18 +834,6 @@ static void noinstr el0_brk64(struct pt_regs *regs, unsigned long esr)
exit_to_user_mode(regs);
}
-static void noinstr __maybe_unused
-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);
@@ -1003,6 +991,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);
@@ -1046,7 +1042,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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 14/15] arm64: debug: remove debug exception registration infrastructure
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (12 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 13/15] arm64: debug: split bkpt32 " Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 15/15] arm64: debug: always unmask interrupts in el0_softstp() Sebastian Andrzej Siewior
2026-06-02 18:21 ` [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sasha Levin
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Luis Claudio R. Goncalves, Will Deacon,
Mark Rutland, Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit a8b8cce9d96d65dfe3d89abf02033151f8b7d670
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.
Remove `trap_init()`, as it would be empty and a weak definition already
exists in `init/main.c`.
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-14-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.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 | 27 -------------
arch/arm64/mm/fault.c | 53 -------------------------
7 files changed, 2 insertions(+), 97 deletions(-)
diff --git a/arch/arm64/include/asm/debug-monitors.h b/arch/arm64/include/asm/debug-monitors.h
index 24c7981abeb0b..4f3901884c5d8 100644
--- a/arch/arm64/include/asm/debug-monitors.h
+++ b/arch/arm64/include/asm/debug-monitors.h
@@ -93,7 +93,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 9b05c6f487ccf..50c5329ff2eda 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -57,8 +57,6 @@ void do_el0_undef(struct pt_regs *regs, unsigned long esr);
void do_el1_undef(struct pt_regs *regs, unsigned long esr);
void do_el0_bti(struct pt_regs *regs);
void do_el1_bti(struct pt_regs *regs, unsigned long esr);
-void do_debug_exception(unsigned long addr_if_watchpoint, unsigned long esr,
- struct pt_regs *regs);
#ifdef CONFIG_HAVE_HW_BREAKPOINT
void do_breakpoint(unsigned long esr, struct pt_regs *regs);
void do_watchpoint(unsigned long addr, unsigned long esr,
@@ -91,8 +89,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 c343442567625..344b1c1a4bbb6 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 ed03270fa3437..16390fd4ba5ed 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 9a1ea5a6e6b72..b98d6d1a1dfd6 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 013159bc0882e..e6e815ef03c77 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -1091,30 +1091,3 @@ int ubsan_brk_handler(struct pt_regs *regs, unsigned long esr)
return DBG_HOOK_HANDLED;
}
#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 7c87d2b3b06ea..9ee5a2d2b3215 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);
@@ -911,53 +905,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.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6.12-stable v2 15/15] arm64: debug: always unmask interrupts in el0_softstp()
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (13 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 14/15] arm64: debug: remove debug exception registration infrastructure Sebastian Andrzej Siewior
@ 2026-06-01 10:25 ` Sebastian Andrzej Siewior
2026-06-02 18:21 ` [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sasha Levin
15 siblings, 0 replies; 17+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-06-01 10:25 UTC (permalink / raw)
To: stable
Cc: Ada Couprie Diaz, Mark Rutland, Catalin Marinas,
Sebastian Andrzej Siewior
From: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Upstream commit ea0d55ae4b3207c33691a73da3443b1fd379f1d2
We intend that EL0 exception handlers unmask all DAIF exceptions
before calling exit_to_user_mode().
When completing single-step of a suspended breakpoint, we do not call
local_daif_restore(DAIF_PROCCTX) before calling exit_to_user_mode(),
leaving all DAIF exceptions masked.
When pseudo-NMIs are not in use this is benign.
When pseudo-NMIs are in use, this is unsound. At this point interrupts
are masked by both DAIF.IF and PMR_EL1, and subsequent irq flag
manipulation may not work correctly. For example, a subsequent
local_irq_enable() within exit_to_user_mode_loop() will only unmask
interrupts via PMR_EL1 (leaving those masked via DAIF.IF), and
anything depending on interrupts being unmasked (e.g. delivery of
signals) will not work correctly.
This was detected by CONFIG_ARM64_DEBUG_PRIORITY_MASKING.
Move the call to `try_step_suspended_breakpoints()` outside of the check
so that interrupts can be unmasked even if we don't call the step handler.
Fixes: 0ac7584c08ce ("arm64: debug: split single stepping exception entry")
Cc: <stable@vger.kernel.org> # 6.17
Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Acked-by: Mark Rutland <mark.rutland@arm.com>
[catalin.marinas@arm.com: added Mark's rewritten commit log and some whitespace]
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
---
arch/arm64/kernel/entry-common.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index b98d6d1a1dfd6..ea3876d99c2ec 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -796,6 +796,8 @@ static void noinstr el0_breakpt(struct pt_regs *regs, unsigned long esr)
static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
{
+ bool step_done;
+
if (!is_ttbr0_addr(regs->pc))
arm64_apply_bp_hardening();
@@ -806,10 +808,10 @@ static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
* 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);
+ step_done = try_step_suspended_breakpoints(regs);
+ local_daif_restore(DAIF_PROCCTX);
+ if (!step_done)
do_el0_softstep(esr, regs);
- }
exit_to_user_mode(regs);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
` (14 preceding siblings ...)
2026-06-01 10:25 ` [PATCH v6.12-stable v2 15/15] arm64: debug: always unmask interrupts in el0_softstp() Sebastian Andrzej Siewior
@ 2026-06-02 18:21 ` Sasha Levin
15 siblings, 0 replies; 17+ messages in thread
From: Sasha Levin @ 2026-06-02 18:21 UTC (permalink / raw)
To: Sebastian Andrzej Siewior, stable; +Cc: Sasha Levin
On Mon, Jun 01, 2026 at 12:25:39PM +0200, Sebastian Andrzej Siewior wrote:
> [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry
Thanks, all 15 patches now applied to 6.12.y.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-06-02 18:21 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-01 10:25 [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 01/15] arm64: Introduce esr_is_ubsan_brk() Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 02/15] arm64: debug: clean up single_step_handler logic Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 03/15] arm64: refactor aarch32_break_handler() Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 04/15] arm64: debug: call software breakpoint handlers statically Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 05/15] arm64: debug: call step " Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 06/15] arm64: debug: remove break/step handler registration infrastructure Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 07/15] arm64: entry: Add entry and exit functions for debug exceptions Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 08/15] arm64: debug: split hardware breakpoint exception entry Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 09/15] arm64: debug: refactor reinstall_suspended_bps() Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 10/15] arm64: debug: split single stepping exception entry Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 11/15] arm64: debug: split hardware watchpoint " Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 12/15] arm64: debug: split brk64 " Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 13/15] arm64: debug: split bkpt32 " Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 14/15] arm64: debug: remove debug exception registration infrastructure Sebastian Andrzej Siewior
2026-06-01 10:25 ` [PATCH v6.12-stable v2 15/15] arm64: debug: always unmask interrupts in el0_softstp() Sebastian Andrzej Siewior
2026-06-02 18:21 ` [PATCH v6.12-stable v2 00/15] Backport: arm64: debug: remove hook registration, split exception entry Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox