[PATCH] arm64: kprobes: Use BRK instead of single-step when executing instructions out-of-line

Will Deacon will at kernel.org
Mon Nov 2 06:41:52 EST 2020


On Thu, Oct 29, 2020 at 06:34:42PM +0100, Jean-Philippe Brucker wrote:
> Commit 36dadef23fcc ("kprobes: Init kprobes in early_initcall") enabled
> using kprobes from early_initcall. Unfortunately at this point the
> hardware debug infrastructure is not operational. The OS lock may still
> be locked, and the hardware watchpoints may have unknown values when
> kprobe enables debug monitors to single-step instructions.
> 
> Rather than using hardware single-step, append a BRK instruction after
> the instruction to be executed out-of-line.
> 
> Fixes: 36dadef23fcc ("kprobes: Init kprobes in early_initcall")
> Suggested-by: Will Deacon <will at kernel.org>
> Signed-off-by: Jean-Philippe Brucker <jean-philippe at linaro.org>
> ---
> 
> An alternative to fix [1]. I haven't done uprobes yet since they don't
> suffer the same problem, but could add it to the bottom of my list.
> Lightly tested with kprobe smoke test and the BPF selftests.
> Interestingly on Seattle when running the "overhead" BPF selftest, that
> triggers a kprobe a bunch of times, I see a 20-30% improvement with this
> patch. I'm guessing it's the cost of touching the debug sysregs?
> 
> [1] https://lore.kernel.org/linux-arm-kernel/20201026172907.1468294-1-jean-philippe@linaro.org/
> ---
>  arch/arm64/include/asm/brk-imm.h        |  2 +
>  arch/arm64/include/asm/debug-monitors.h |  1 +
>  arch/arm64/include/asm/kprobes.h        |  2 +-
>  arch/arm64/kernel/probes/kprobes.c      | 56 +++++++++----------------
>  4 files changed, 24 insertions(+), 37 deletions(-)
> 
> diff --git a/arch/arm64/include/asm/brk-imm.h b/arch/arm64/include/asm/brk-imm.h
> index e3d47b52161d..ec7720dbe2c8 100644
> --- a/arch/arm64/include/asm/brk-imm.h
> +++ b/arch/arm64/include/asm/brk-imm.h
> @@ -10,6 +10,7 @@
>   * #imm16 values used for BRK instruction generation
>   * 0x004: for installing kprobes
>   * 0x005: for installing uprobes
> + * 0x006: for kprobe software single-step
>   * Allowed values for kgdb are 0x400 - 0x7ff
>   * 0x100: for triggering a fault on purpose (reserved)
>   * 0x400: for dynamic BRK instruction
> @@ -19,6 +20,7 @@
>   */
>  #define KPROBES_BRK_IMM			0x004
>  #define UPROBES_BRK_IMM			0x005
> +#define KPROBES_BRK_SS_IMM		0x006
>  #define FAULT_BRK_IMM			0x100
>  #define KGDB_DYN_DBG_BRK_IMM		0x400
>  #define KGDB_COMPILED_DBG_BRK_IMM	0x401
> diff --git a/arch/arm64/include/asm/debug-monitors.h b/arch/arm64/include/asm/debug-monitors.h
> index 0b298f48f5bf..657c921fd784 100644
> --- a/arch/arm64/include/asm/debug-monitors.h
> +++ b/arch/arm64/include/asm/debug-monitors.h
> @@ -53,6 +53,7 @@
>  
>  /* kprobes BRK opcodes with ESR encoding  */
>  #define BRK64_OPCODE_KPROBES	(AARCH64_BREAK_MON | (KPROBES_BRK_IMM << 5))
> +#define BRK64_OPCODE_KPROBES_SS	(AARCH64_BREAK_MON | (KPROBES_BRK_SS_IMM << 5))
>  /* uprobes BRK opcodes with ESR encoding  */
>  #define BRK64_OPCODE_UPROBES	(AARCH64_BREAK_MON | (UPROBES_BRK_IMM << 5))
>  
> diff --git a/arch/arm64/include/asm/kprobes.h b/arch/arm64/include/asm/kprobes.h
> index 97e511d645a2..8699ce30f587 100644
> --- a/arch/arm64/include/asm/kprobes.h
> +++ b/arch/arm64/include/asm/kprobes.h
> @@ -16,7 +16,7 @@
>  #include <linux/percpu.h>
>  
>  #define __ARCH_WANT_KPROBES_INSN_SLOT
> -#define MAX_INSN_SIZE			1
> +#define MAX_INSN_SIZE			2
>  
>  #define flush_insn_slot(p)		do { } while (0)
>  #define kretprobe_blacklist_size	0
> diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
> index deba738142ed..ec1446ceacc9 100644
> --- a/arch/arm64/kernel/probes/kprobes.c
> +++ b/arch/arm64/kernel/probes/kprobes.c
> @@ -36,21 +36,25 @@ DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);
>  static void __kprobes
>  post_kprobe_handler(struct kprobe_ctlblk *, struct pt_regs *);
>  
> -static int __kprobes patch_text(kprobe_opcode_t *addr, u32 opcode)
> +static int __kprobes patch_text(kprobe_opcode_t *addr, u32 opc1, u32 opc2)
>  {
> -	void *addrs[1];
> -	u32 insns[1];
> +	void *addrs[2];
> +	u32 insns[2];
>  
>  	addrs[0] = addr;
> -	insns[0] = opcode;
> +	insns[0] = opc1;
> +	if (opc2) {
> +		addrs[1] = addr + 1;
> +		insns[1] = opc2;
> +	}
>  
> -	return aarch64_insn_patch_text(addrs, insns, 1);
> +	return aarch64_insn_patch_text(addrs, insns, opc2 ? 2 : 1);
>  }
>  
>  static void __kprobes arch_prepare_ss_slot(struct kprobe *p)
>  {
>  	/* prepare insn slot */
> -	patch_text(p->ainsn.api.insn, p->opcode);
> +	patch_text(p->ainsn.api.insn, p->opcode, BRK64_OPCODE_KPROBES_SS);
>  
>  	flush_icache_range((uintptr_t) (p->ainsn.api.insn),
>  			   (uintptr_t) (p->ainsn.api.insn) +
> @@ -128,13 +132,13 @@ void *alloc_insn_page(void)
>  /* arm kprobe: install breakpoint in text */
>  void __kprobes arch_arm_kprobe(struct kprobe *p)
>  {
> -	patch_text(p->addr, BRK64_OPCODE_KPROBES);
> +	patch_text(p->addr, BRK64_OPCODE_KPROBES, 0);
>  }
>  
>  /* disarm kprobe: remove breakpoint from text */
>  void __kprobes arch_disarm_kprobe(struct kprobe *p)
>  {
> -	patch_text(p->addr, p->opcode);
> +	patch_text(p->addr, p->opcode, 0);

I don't think patch_text() is offering an awful lot to these callers. Why
don't we just call aarch64_insn_patch_text() directly, which I think will
make the code easier to read too?

>  void __kprobes arch_remove_kprobe(struct kprobe *p)
> @@ -163,20 +167,14 @@ static void __kprobes set_current_kprobe(struct kprobe *p)
>  }
>  
>  /*
> - * Interrupts need to be disabled before single-step mode is set, and not
> - * reenabled until after single-step mode ends.
> - * Without disabling interrupt on local CPU, there is a chance of
> - * interrupt occurrence in the period of exception return and  start of
> - * out-of-line single-step, that result in wrongly single stepping
> - * into the interrupt handler.
> + * Keep interrupts disabled while executing the instruction out-of-line. Since
> + * the kprobe state is per-CPU, we can't afford to be migrated.
>   */
>  static void __kprobes kprobes_save_local_irqflag(struct kprobe_ctlblk *kcb,
>  						struct pt_regs *regs)
>  {
>  	kcb->saved_irqflag = regs->pstate & DAIF_MASK;
>  	regs->pstate |= PSR_I_BIT;
> -	/* Unmask PSTATE.D for enabling software step exceptions. */
> -	regs->pstate &= ~PSR_D_BIT;

Can we set the D bit now? Would be one less thing to worry about.

Cheers,

Will



More information about the linux-arm-kernel mailing list