Commit 6adfdc5e authored by Ada Couprie Diaz's avatar Ada Couprie Diaz Committed by Will Deacon
Browse files

arm64: debug: call software breakpoint handlers statically



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: default avatarAda Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: default avatarLuis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: default avatarWill Deacon <will@kernel.org>
Acked-by: default avatarMark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-4-ada.coupriediaz@arm.com


Signed-off-by: default avatarWill Deacon <will@kernel.org>
parent b1e2d955
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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__ */

/*
+8 −0
Original line number Diff line number Diff line
@@ -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 */
+6 −0
Original line number Diff line number Diff line
@@ -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);

/*
+2 −0
Original line number Diff line number Diff line
@@ -28,4 +28,6 @@ struct arch_uprobe {
	bool simulate;
};

int uprobe_brk_handler(struct pt_regs *regs, unsigned long esr);

#endif
+40 −10
Original line number Diff line number Diff line
@@ -21,8 +21,11 @@
#include <asm/cputype.h>
#include <asm/daifflags.h>
#include <asm/debug-monitors.h>
#include <asm/kgdb.h>
#include <asm/kprobes.h>
#include <asm/system_misc.h>
#include <asm/traps.h>
#include <asm/uprobes.h>

/* Determine debug architecture. */
u8 debug_monitors_arch(void)
@@ -299,20 +302,47 @@ void unregister_kernel_break_hook(struct break_hook *hook)

static int call_break_hook(struct pt_regs *regs, unsigned long esr)
{
	struct break_hook *hook;
	struct list_head *list;
	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;
	}

	list = user_mode(regs) ? &user_break_hook : &kernel_break_hook;
	if (esr_brk_comment(esr) == BUG_BRK_IMM)
		return bug_brk_handler(regs, esr);

	/*
	 * Since brk exception disables interrupt, this function is
	 * entirely not preemptible, and we can use rcu list safely here.
	 */
	list_for_each_entry_rcu(hook, list, node) {
		if ((esr_brk_comment(esr) & ~hook->mask) == hook->imm)
			return hook->fn(regs, esr);
	if (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);
Loading