Skip to content

Commit 68c5786

Browse files
committed
arm64: debug: split brk64 exception entry
JIRA: https://issues.redhat.com/browse/RHEL-65658 commit 31575e1 Author: Ada Couprie Diaz <ada.coupriediaz@arm.com> Date: Mon Jul 7 12:41:07 2025 +0100 arm64: debug: split brk64 exception entry 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: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
1 parent ec7dca6 commit 68c5786

File tree

3 files changed

+39
-33
lines changed

3 files changed

+39
-33
lines changed

arch/arm64/include/asm/exception.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ static inline void do_watchpoint(unsigned long addr, unsigned long esr,
7070
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
7171
void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
7272
void do_el1_softstep(unsigned long esr, struct pt_regs *regs);
73+
void do_el0_brk64(unsigned long esr, struct pt_regs *regs);
74+
void do_el1_brk64(unsigned long esr, struct pt_regs *regs);
7375
void do_fpsimd_acc(unsigned long esr, struct pt_regs *regs);
7476
void do_sve_acc(unsigned long esr, struct pt_regs *regs);
7577
void do_sme_acc(unsigned long esr, struct pt_regs *regs);

arch/arm64/kernel/debug-monitors.c

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -207,15 +207,8 @@ void do_el1_softstep(unsigned long esr, struct pt_regs *regs)
207207
}
208208
NOKPROBE_SYMBOL(do_el1_softstep);
209209

210-
static int call_break_hook(struct pt_regs *regs, unsigned long esr)
210+
static int call_el1_break_hook(struct pt_regs *regs, unsigned long esr)
211211
{
212-
if (user_mode(regs)) {
213-
if (IS_ENABLED(CONFIG_UPROBES) &&
214-
esr_brk_comment(esr) == UPROBES_BRK_IMM)
215-
return uprobe_brk_handler(regs, esr);
216-
return DBG_HOOK_ERROR;
217-
}
218-
219212
if (esr_brk_comment(esr) == BUG_BRK_IMM)
220213
return bug_brk_handler(regs, esr);
221214

@@ -249,24 +242,30 @@ static int call_break_hook(struct pt_regs *regs, unsigned long esr)
249242

250243
return DBG_HOOK_ERROR;
251244
}
252-
NOKPROBE_SYMBOL(call_break_hook);
245+
NOKPROBE_SYMBOL(call_el1_break_hook);
253246

254-
static int brk_handler(unsigned long unused, unsigned long esr,
255-
struct pt_regs *regs)
247+
/*
248+
* We have already unmasked interrupts and enabled preemption
249+
* when calling do_el0_brk64() from entry-common.c.
250+
*/
251+
void do_el0_brk64(unsigned long esr, struct pt_regs *regs)
256252
{
257-
if (call_break_hook(regs, esr) == DBG_HOOK_HANDLED)
258-
return 0;
253+
if (IS_ENABLED(CONFIG_UPROBES) &&
254+
esr_brk_comment(esr) == UPROBES_BRK_IMM &&
255+
uprobe_brk_handler(regs, esr) == DBG_HOOK_HANDLED)
256+
return;
259257

260-
if (user_mode(regs)) {
261-
send_user_sigtrap(TRAP_BRKPT);
262-
} else {
263-
pr_warn("Unexpected kernel BRK exception at EL1\n");
264-
return -EFAULT;
265-
}
258+
send_user_sigtrap(TRAP_BRKPT);
259+
}
266260

267-
return 0;
261+
void do_el1_brk64(unsigned long esr, struct pt_regs *regs)
262+
{
263+
if (call_el1_break_hook(regs, esr) == DBG_HOOK_HANDLED)
264+
return;
265+
266+
die("Oops - BRK", regs, esr);
268267
}
269-
NOKPROBE_SYMBOL(brk_handler);
268+
NOKPROBE_SYMBOL(do_el1_brk64);
270269

271270
bool try_handle_aarch32_break(struct pt_regs *regs)
272271
{
@@ -308,10 +307,7 @@ bool try_handle_aarch32_break(struct pt_regs *regs)
308307
NOKPROBE_SYMBOL(try_handle_aarch32_break);
309308

310309
void __init debug_traps_init(void)
311-
{
312-
hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,
313-
TRAP_BRKPT, "BRK handler");
314-
}
310+
{}
315311

316312
/* Re-enable single step for syscall restarting. */
317313
void user_rewind_single_step(struct task_struct *task)

arch/arm64/kernel/entry-common.c

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -524,13 +524,12 @@ static void noinstr el1_watchpt(struct pt_regs *regs, unsigned long esr)
524524
arm64_exit_el1_dbg(regs);
525525
}
526526

527-
static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
527+
static void noinstr el1_brk64(struct pt_regs *regs, unsigned long esr)
528528
{
529-
unsigned long far = read_sysreg(far_el1);
530-
531529
arm64_enter_el1_dbg(regs);
532-
if (!cortex_a76_erratum_1463225_debug_handler(regs))
533-
do_debug_exception(far, esr, regs);
530+
debug_exception_enter(regs);
531+
do_el1_brk64(esr, regs);
532+
debug_exception_exit(regs);
534533
arm64_exit_el1_dbg(regs);
535534
}
536535

@@ -576,7 +575,7 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
576575
el1_watchpt(regs, esr);
577576
break;
578577
case ESR_ELx_EC_BRK64:
579-
el1_dbg(regs, esr);
578+
el1_brk64(regs, esr);
580579
break;
581580
case ESR_ELx_EC_FPAC:
582581
el1_fpac(regs, esr);
@@ -804,7 +803,16 @@ static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
804803
exit_to_user_mode(regs);
805804
}
806805

807-
static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
806+
static void noinstr el0_brk64(struct pt_regs *regs, unsigned long esr)
807+
{
808+
enter_from_user_mode(regs);
809+
local_daif_restore(DAIF_PROCCTX);
810+
do_el0_brk64(esr, regs);
811+
exit_to_user_mode(regs);
812+
}
813+
814+
static void noinstr __maybe_unused
815+
el0_dbg(struct pt_regs *regs, unsigned long esr)
808816
{
809817
/* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
810818
unsigned long far = read_sysreg(far_el1);
@@ -888,7 +896,7 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
888896
el0_watchpt(regs, esr);
889897
break;
890898
case ESR_ELx_EC_BRK64:
891-
el0_dbg(regs, esr);
899+
el0_brk64(regs, esr);
892900
break;
893901
case ESR_ELx_EC_FPAC:
894902
el0_fpac(regs, esr);

0 commit comments

Comments
 (0)