Skip to content

Commit 31575e1

Browse files
Ada Couprie Diazwilldeacon
authored andcommitted
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 <[email protected]> Tested-by: Luis Claudio R. Goncalves <[email protected]> Reviewed-by: Will Deacon <[email protected]> Acked-by: Mark Rutland <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Will Deacon <[email protected]>
1 parent 413f0bb commit 31575e1

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
@@ -72,6 +72,8 @@ static inline void do_watchpoint(unsigned long addr, unsigned long esr,
7272
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
7373
void do_el0_softstep(unsigned long esr, struct pt_regs *regs);
7474
void do_el1_softstep(unsigned long esr, struct pt_regs *regs);
75+
void do_el0_brk64(unsigned long esr, struct pt_regs *regs);
76+
void do_el1_brk64(unsigned long esr, struct pt_regs *regs);
7577
void do_fpsimd_acc(unsigned long esr, struct pt_regs *regs);
7678
void do_sve_acc(unsigned long esr, struct pt_regs *regs);
7779
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

@@ -252,24 +245,30 @@ static int call_break_hook(struct pt_regs *regs, unsigned long esr)
252245

253246
return DBG_HOOK_ERROR;
254247
}
255-
NOKPROBE_SYMBOL(call_break_hook);
248+
NOKPROBE_SYMBOL(call_el1_break_hook);
256249

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

263-
if (user_mode(regs)) {
264-
send_user_sigtrap(TRAP_BRKPT);
265-
} else {
266-
pr_warn("Unexpected kernel BRK exception at EL1\n");
267-
return -EFAULT;
268-
}
261+
send_user_sigtrap(TRAP_BRKPT);
262+
}
269263

270-
return 0;
264+
void do_el1_brk64(unsigned long esr, struct pt_regs *regs)
265+
{
266+
if (call_el1_break_hook(regs, esr) == DBG_HOOK_HANDLED)
267+
return;
268+
269+
die("Oops - BRK", regs, esr);
271270
}
272-
NOKPROBE_SYMBOL(brk_handler);
271+
NOKPROBE_SYMBOL(do_el1_brk64);
273272

274273
bool try_handle_aarch32_break(struct pt_regs *regs)
275274
{
@@ -311,10 +310,7 @@ bool try_handle_aarch32_break(struct pt_regs *regs)
311310
NOKPROBE_SYMBOL(try_handle_aarch32_break);
312311

313312
void __init debug_traps_init(void)
314-
{
315-
hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,
316-
TRAP_BRKPT, "BRK handler");
317-
}
313+
{}
318314

319315
/* Re-enable single step for syscall restarting. */
320316
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
@@ -565,13 +565,12 @@ static void noinstr el1_watchpt(struct pt_regs *regs, unsigned long esr)
565565
arm64_exit_el1_dbg(regs);
566566
}
567567

568-
static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
568+
static void noinstr el1_brk64(struct pt_regs *regs, unsigned long esr)
569569
{
570-
unsigned long far = read_sysreg(far_el1);
571-
572570
arm64_enter_el1_dbg(regs);
573-
if (!cortex_a76_erratum_1463225_debug_handler(regs))
574-
do_debug_exception(far, esr, regs);
571+
debug_exception_enter(regs);
572+
do_el1_brk64(esr, regs);
573+
debug_exception_exit(regs);
575574
arm64_exit_el1_dbg(regs);
576575
}
577576

@@ -623,7 +622,7 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
623622
el1_watchpt(regs, esr);
624623
break;
625624
case ESR_ELx_EC_BRK64:
626-
el1_dbg(regs, esr);
625+
el1_brk64(regs, esr);
627626
break;
628627
case ESR_ELx_EC_FPAC:
629628
el1_fpac(regs, esr);
@@ -859,7 +858,16 @@ static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
859858
exit_to_user_mode(regs);
860859
}
861860

862-
static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
861+
static void noinstr el0_brk64(struct pt_regs *regs, unsigned long esr)
862+
{
863+
enter_from_user_mode(regs);
864+
local_daif_restore(DAIF_PROCCTX);
865+
do_el0_brk64(esr, regs);
866+
exit_to_user_mode(regs);
867+
}
868+
869+
static void noinstr __maybe_unused
870+
el0_dbg(struct pt_regs *regs, unsigned long esr)
863871
{
864872
/* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
865873
unsigned long far = read_sysreg(far_el1);
@@ -947,7 +955,7 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
947955
el0_watchpt(regs, esr);
948956
break;
949957
case ESR_ELx_EC_BRK64:
950-
el0_dbg(regs, esr);
958+
el0_brk64(regs, esr);
951959
break;
952960
case ESR_ELx_EC_FPAC:
953961
el0_fpac(regs, esr);

0 commit comments

Comments
 (0)