On Sat, Jan 10, 2015 at 11:03:18PM -0500, David Long wrote: > From: Sandeepa Prabhu <sandeepa.pra...@linaro.org> > > Add support for basic kernel probes(kprobes) and jump probes > (jprobes) for ARM64. > > Kprobes will utilize software breakpoint and single step debug > exceptions supported on ARM v8. > > Software breakpoint is placed at the probe address to trap the > kernel execution into kprobe handler. > > ARM v8 supports single stepping to be enabled while exception return > (ERET) with next PC in exception return address (ELR_EL1). The > kprobe handler prepares an executable memory slot for out-of-line > execution with a copy of the original instruction being probed, and > enables single stepping from the instruction slot. With this scheme, > the instruction is executed with the exact same register context > 'except PC' that points to instruction slot. > > Debug mask(PSTATE.D) is enabled only when single stepping a recursive > kprobe, e.g.: during kprobes reenter so that probed instruction can be > single stepped within the kprobe handler -exception- context. > The recursion depth of kprobe is always 2, i.e. upon probe re-entry, > any further re-entry is prevented by not calling handlers and the case > counted as a missed kprobe). > > Single stepping from slot has a drawback on PC-relative accesses > like branching and symbolic literals access as offset from new PC > (slot address) may not be ensured to fit in immediate value of > opcode. Such instructions needs simulation, so reject > probing such instructions. > > Instructions generating exceptions or cpu mode change are rejected, > and not allowed to insert probe for these instructions. > > Instructions using Exclusive Monitor are rejected too. > > System instructions are mostly enabled for stepping, except MSR > immediate that updates "daif" flags in PSTATE, which are not safe > for probing. > > Changes since v3: > from David Long: > 1) Removed unnecessary addtion of NOP after out-of-line instruction. > 2) Replaced table-driven instruction parsing with calls to external > test functions. > from Steve Capper: > 3) Disable local irq while executing out of line instruction. > > Signed-off-by: Sandeepa Prabhu <sandeepa.pra...@linaro.org> > Signed-off-by: Steve Capper <steve.cap...@linaro.org> > Signed-off-by: David A. Long <dave.l...@linaro.org> > --- > arch/arm64/Kconfig | 1 + > arch/arm64/include/asm/kprobes.h | 60 +++++ > arch/arm64/include/asm/probes.h | 50 ++++ > arch/arm64/include/asm/ptrace.h | 3 +- > arch/arm64/kernel/Makefile | 1 + > arch/arm64/kernel/kprobes-arm64.c | 65 +++++ > arch/arm64/kernel/kprobes-arm64.h | 28 ++ > arch/arm64/kernel/kprobes.c | 551 > ++++++++++++++++++++++++++++++++++++++ > arch/arm64/kernel/kprobes.h | 30 +++ > arch/arm64/kernel/vmlinux.lds.S | 1 + > 10 files changed, 789 insertions(+), 1 deletion(-) > create mode 100644 arch/arm64/include/asm/kprobes.h > create mode 100644 arch/arm64/include/asm/probes.h > create mode 100644 arch/arm64/kernel/kprobes-arm64.c > create mode 100644 arch/arm64/kernel/kprobes-arm64.h > create mode 100644 arch/arm64/kernel/kprobes.c > create mode 100644 arch/arm64/kernel/kprobes.h > > diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig > index 12b3fd6..b3f61ba 100644 > --- a/arch/arm64/Kconfig > +++ b/arch/arm64/Kconfig > @@ -67,6 +67,7 @@ config ARM64 > select HAVE_REGS_AND_STACK_ACCESS_API > select HAVE_RCU_TABLE_FREE > select HAVE_SYSCALL_TRACEPOINTS > + select HAVE_KPROBES if !XIP_KERNEL
I don't think we need "if !XIP_KERNEL" for arm64? > select IRQ_DOMAIN > select MODULES_USE_ELF_RELA > select NO_BOOTMEM > diff --git a/arch/arm64/include/asm/kprobes.h > b/arch/arm64/include/asm/kprobes.h > new file mode 100644 > index 0000000..b35d3b9 > --- /dev/null > +++ b/arch/arm64/include/asm/kprobes.h > @@ -0,0 +1,60 @@ > +/* > + * arch/arm64/include/asm/kprobes.h > + * > + * Copyright (C) 2013 Linaro Limited > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + */ > + > +#ifndef _ARM_KPROBES_H > +#define _ARM_KPROBES_H > + > +#include <linux/types.h> > +#include <linux/ptrace.h> > +#include <linux/percpu.h> > + > +#define __ARCH_WANT_KPROBES_INSN_SLOT > +#define MAX_INSN_SIZE 1 > +#define MAX_STACK_SIZE 128 > + > +#define flush_insn_slot(p) do { } while (0) > +#define kretprobe_blacklist_size 0 > + > +#include <asm/probes.h> > + > +struct prev_kprobe { > + struct kprobe *kp; > + unsigned int status; > +}; > + > +/* Single step context for kprobe */ > +struct kprobe_step_ctx { > +#define KPROBES_STEP_NONE 0x0 > +#define KPROBES_STEP_PENDING 0x1 > + unsigned long ss_status; > + unsigned long match_addr; > +}; > + > +/* per-cpu kprobe control block */ > +struct kprobe_ctlblk { > + unsigned int kprobe_status; > + unsigned long saved_irqflag; > + struct prev_kprobe prev_kprobe; > + struct kprobe_step_ctx ss_ctx; > + struct pt_regs jprobe_saved_regs; > + char jprobes_stack[MAX_STACK_SIZE]; > +}; > + > +void arch_remove_kprobe(struct kprobe *); > +int kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr); > +int kprobe_exceptions_notify(struct notifier_block *self, > + unsigned long val, void *data); > + > +#endif /* _ARM_KPROBES_H */ > diff --git a/arch/arm64/include/asm/probes.h b/arch/arm64/include/asm/probes.h > new file mode 100644 > index 0000000..9dba74d > --- /dev/null > +++ b/arch/arm64/include/asm/probes.h > @@ -0,0 +1,50 @@ > +/* > + * arch/arm64/include/asm/probes.h > + * > + * Copyright (C) 2013 Linaro Limited > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + */ > +#ifndef _ARM_PROBES_H > +#define _ARM_PROBES_H > + > +struct kprobe; > +struct arch_specific_insn; > + > +typedef u32 kprobe_opcode_t; > +typedef unsigned long (kprobes_pstate_check_t)(unsigned long); > +typedef unsigned long > +(kprobes_condition_check_t)(struct kprobe *p, struct pt_regs *); > +typedef void > +(kprobes_prepare_t)(struct kprobe *, struct arch_specific_insn *); > +typedef void (kprobes_handler_t) (u32 opcode, long addr, struct pt_regs *); > + > +enum pc_restore_type { > + NO_RESTORE, > + RESTORE_PC, > +}; > + > +struct kprobe_pc_restore { > + enum pc_restore_type type; > + unsigned long addr; > +}; > + > +/* architecture specific copy of original instruction */ > +struct arch_specific_insn { > + kprobe_opcode_t *insn; > + kprobes_pstate_check_t *pstate_cc; > + kprobes_condition_check_t *check_condn; > + kprobes_prepare_t *prepare; > + kprobes_handler_t *handler; > + /* restore address after step xol */ > + struct kprobe_pc_restore restore; > +}; > + > +#endif > diff --git a/arch/arm64/include/asm/ptrace.h b/arch/arm64/include/asm/ptrace.h > index 3613e49..e436b49 100644 > --- a/arch/arm64/include/asm/ptrace.h > +++ b/arch/arm64/include/asm/ptrace.h > @@ -203,7 +203,8 @@ static inline int valid_user_regs(struct user_pt_regs > *regs) > return 0; > } > > -#define instruction_pointer(regs) ((unsigned long)(regs)->pc) > +#define instruction_pointer(regs) ((regs)->pc) > +#define stack_pointer(regs) ((regs)->sp) > > #ifdef CONFIG_SMP > extern unsigned long profile_pc(struct pt_regs *regs); > diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile > index eaa77ed..6ca9fc0 100644 > --- a/arch/arm64/kernel/Makefile > +++ b/arch/arm64/kernel/Makefile > @@ -31,6 +31,7 @@ arm64-obj-$(CONFIG_ARM64_CPU_SUSPEND) += sleep.o > suspend.o > arm64-obj-$(CONFIG_CPU_IDLE) += cpuidle.o > arm64-obj-$(CONFIG_JUMP_LABEL) += jump_label.o > arm64-obj-$(CONFIG_KGDB) += kgdb.o > +arm64-obj-$(CONFIG_KPROBES) += kprobes.o kprobes-arm64.o > arm64-obj-$(CONFIG_EFI) += efi.o efi-stub.o efi-entry.o > arm64-obj-$(CONFIG_PCI) += pci.o > arm64-obj-$(CONFIG_ARMV8_DEPRECATED) += armv8_deprecated.o > diff --git a/arch/arm64/kernel/kprobes-arm64.c > b/arch/arm64/kernel/kprobes-arm64.c > new file mode 100644 > index 0000000..a698bd3 > --- /dev/null > +++ b/arch/arm64/kernel/kprobes-arm64.c > @@ -0,0 +1,65 @@ > +/* > + * arch/arm64/kernel/kprobes-arm64.c > + * > + * Copyright (C) 2013 Linaro Limited. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + */ > + > +#include <linux/kernel.h> > +#include <linux/kprobes.h> > +#include <linux/module.h> > +#include <asm/kprobes.h> > +#include <asm/insn.h> > + > +#include "kprobes-arm64.h" > + > +static bool aarch64_insn_is_steppable(u32 insn) > +{ > + if (aarch64_get_insn_class(insn) == AARCH64_INSN_CLS_BR_SYS) { > + if (aarch64_insn_is_branch(insn)) > + return false; > + > + /* modification of daif creates issues */ > + if (aarch64_insn_is_msr_daif(insn)) > + return false; > + > + if (aarch64_insn_is_hint(insn)) > + return aarch64_insn_is_nop(insn); > + > + return true; > + } > + > + if (aarch64_insn_uses_literal(insn)) > + return false; > + > + if (aarch64_insn_is_exclusive(insn)) > + return false; > + > + return true; > +} > + > +/* Return: > + * INSN_REJECTED If instruction is one not allowed to kprobe, > + * INSN_GOOD If instruction is supported and uses instruction slot, > + * INSN_GOOD_NO_SLOT If instruction is supported but doesn't use its slot. > + */ > +enum kprobe_insn __kprobes > +arm_kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi) > +{ > + /* > + * Instructions reading or modifying the PC won't work from the XOL > + * slot. > + */ > + if (aarch64_insn_is_steppable(insn)) > + return INSN_GOOD; > + else > + return INSN_REJECTED; > +} > diff --git a/arch/arm64/kernel/kprobes-arm64.h > b/arch/arm64/kernel/kprobes-arm64.h > new file mode 100644 > index 0000000..87e7891 > --- /dev/null > +++ b/arch/arm64/kernel/kprobes-arm64.h > @@ -0,0 +1,28 @@ > +/* > + * arch/arm64/kernel/kprobes-arm64.h > + * > + * Copyright (C) 2013 Linaro Limited. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + */ > + > +#ifndef _ARM_KERNEL_KPROBES_ARM64_H > +#define _ARM_KERNEL_KPROBES_ARM64_H > + > +enum kprobe_insn { > + INSN_REJECTED, > + INSN_GOOD_NO_SLOT, > + INSN_GOOD, > +}; > + > +enum kprobe_insn __kprobes > +arm_kprobe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi); > + > +#endif /* _ARM_KERNEL_KPROBES_ARM64_H */ > diff --git a/arch/arm64/kernel/kprobes.c b/arch/arm64/kernel/kprobes.c > new file mode 100644 > index 0000000..65e22d8 > --- /dev/null > +++ b/arch/arm64/kernel/kprobes.c > @@ -0,0 +1,551 @@ > +/* > + * arch/arm64/kernel/kprobes.c > + * > + * Kprobes support for ARM64 > + * > + * Copyright (C) 2013 Linaro Limited. > + * Author: Sandeepa Prabhu <sandeepa.pra...@linaro.org> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + */ > +#include <linux/kernel.h> > +#include <linux/kprobes.h> > +#include <linux/module.h> > +#include <linux/slab.h> > +#include <linux/stop_machine.h> > +#include <linux/stringify.h> > +#include <asm/traps.h> > +#include <asm/ptrace.h> > +#include <asm/cacheflush.h> > +#include <asm/debug-monitors.h> > +#include <asm/system_misc.h> > +#include <asm/insn.h> > + > +#include "kprobes.h" > +#include "kprobes-arm64.h" > + > +#define MIN_STACK_SIZE(addr) min((unsigned long)MAX_STACK_SIZE, \ > + (unsigned long)current_thread_info() + THREAD_START_SP - (addr)) > + > +DEFINE_PER_CPU(struct kprobe *, current_kprobe) = NULL; > +DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk); > + > +static void __kprobes arch_prepare_ss_slot(struct kprobe *p) > +{ > + /* prepare insn slot */ > + p->ainsn.insn[0] = p->opcode; > + > + flush_icache_range((uintptr_t) (p->ainsn.insn), > + (uintptr_t) (p->ainsn.insn) + MAX_INSN_SIZE); > + > + /* > + * Needs restoring of return address after stepping xol. > + */ > + p->ainsn.restore.addr = (unsigned long) p->addr + > + sizeof(kprobe_opcode_t); > + p->ainsn.restore.type = RESTORE_PC; > +} > + > +int __kprobes arch_prepare_kprobe(struct kprobe *p) > +{ > + kprobe_opcode_t insn; > + unsigned long probe_addr = (unsigned long)p->addr; > + > + /* copy instruction */ > + insn = *p->addr; > + p->opcode = insn; > + > + if (in_exception_text(probe_addr)) > + return -EINVAL; > + > + /* decode instruction */ > + switch (arm_kprobe_decode_insn(insn, &p->ainsn)) { > + case INSN_REJECTED: /* insn not supported */ > + return -EINVAL; > + > + case INSN_GOOD_NO_SLOT: /* insn need simulation */ > + return -EINVAL; > + > + case INSN_GOOD: /* instruction uses slot */ > + p->ainsn.insn = get_insn_slot(); > + if (!p->ainsn.insn) > + return -ENOMEM; > + break; > + }; > + > + /* prepare the instruction */ > + arch_prepare_ss_slot(p); > + > + return 0; > +} > + > +static int __kprobes patch_text(kprobe_opcode_t *addr, u32 opcode) > +{ > + void *addrs[1]; > + u32 insns[1]; > + > + addrs[0] = (void *)addr; > + insns[0] = (u32)opcode; > + > + return aarch64_insn_patch_text_sync(addrs, insns, 1); > +} > + > +/* arm kprobe: install breakpoint in text */ > +void __kprobes arch_arm_kprobe(struct kprobe *p) > +{ > + patch_text(p->addr, BRK64_OPCODE_KPROBES); > +} > + > +/* disarm kprobe: remove breakpoint from text */ > +void __kprobes arch_disarm_kprobe(struct kprobe *p) > +{ > + patch_text(p->addr, p->opcode); > +} > + > +void __kprobes arch_remove_kprobe(struct kprobe *p) > +{ > + if (p->ainsn.insn) { > + free_insn_slot(p->ainsn.insn, 0); > + p->ainsn.insn = NULL; > + } > +} > + > +static void __kprobes save_previous_kprobe(struct kprobe_ctlblk *kcb) > +{ > + kcb->prev_kprobe.kp = kprobe_running(); > + kcb->prev_kprobe.status = kcb->kprobe_status; > +} > + > +static void __kprobes restore_previous_kprobe(struct kprobe_ctlblk *kcb) > +{ > + __this_cpu_write(current_kprobe, kcb->prev_kprobe.kp); > + kcb->kprobe_status = kcb->prev_kprobe.status; > +} > + > +static void __kprobes set_current_kprobe(struct kprobe *p) > +{ > + __this_cpu_write(current_kprobe, p); > +} > + > +/* > + * Debug flag (D-flag) is disabled upon exception entry. > + * Kprobes need to unmask D-flag -ONLY- in case of recursive > + * probe i.e. when probe hit from kprobe handler context upon > + * executing the pre/post handlers. In this case we return with > + * D-flag unmasked so that single-stepping can be carried-out. > + * > + * Keep D-flag masked in all other cases. > + */ > +static void __kprobes > +spsr_set_debug_flag(struct pt_regs *regs, int mask) > +{ > + unsigned long spsr = regs->pstate; > + > + if (mask) > + spsr |= PSR_D_BIT; > + else > + spsr &= ~PSR_D_BIT; > + > + regs->pstate = spsr; > +} > + > +/* > + * Interrupt needs to be disabled for the duration from probe hitting > + * breakpoint exception until kprobe is processed completely. I don't think that's correct? We only really need to disable interrupts when embarking on the single-step? > + * 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 > + * the interrupt handler. > + */ > +static void __kprobes kprobes_save_local_irqflag(struct pt_regs *regs) > +{ > + struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); > + > + kcb->saved_irqflag = regs->pstate; > + regs->pstate |= PSR_I_BIT; > +} > + > +static void __kprobes kprobes_restore_local_irqflag(struct pt_regs *regs) > +{ > + struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); > + > + if (kcb->saved_irqflag & PSR_I_BIT) > + regs->pstate |= PSR_I_BIT; > + else > + regs->pstate &= ~PSR_I_BIT; > +} > + > +static void __kprobes > +set_ss_context(struct kprobe_ctlblk *kcb, unsigned long addr) > +{ > + kcb->ss_ctx.ss_status = KPROBES_STEP_PENDING; > + kcb->ss_ctx.match_addr = addr + sizeof(kprobe_opcode_t); > +} > + > +static void __kprobes clear_ss_context(struct kprobe_ctlblk *kcb) > +{ > + kcb->ss_ctx.ss_status = KPROBES_STEP_NONE; > + kcb->ss_ctx.match_addr = 0; > +} > + > +static void __kprobes > +skip_singlestep_missed(struct kprobe_ctlblk *kcb, struct pt_regs *regs) > +{ > + /* set return addr to next pc to continue */ > + instruction_pointer(regs) += sizeof(kprobe_opcode_t); > +} > + > +static void __kprobes setup_singlestep(struct kprobe *p, > + struct pt_regs *regs, > + struct kprobe_ctlblk *kcb, int reenter) > +{ > + unsigned long slot; > + > + if (reenter) { > + save_previous_kprobe(kcb); > + set_current_kprobe(p); > + kcb->kprobe_status = KPROBE_REENTER; > + } else { > + kcb->kprobe_status = KPROBE_HIT_SS; > + } > + > + if (p->ainsn.insn) { > + /* prepare for single stepping */ > + slot = (unsigned long)p->ainsn.insn; > + > + set_ss_context(kcb, slot); /* mark pending ss */ > + > + if (kcb->kprobe_status == KPROBE_REENTER) > + spsr_set_debug_flag(regs, 0); > + > + /* IRQs and single stepping do not mix well. */ > + kprobes_save_local_irqflag(regs); > + kernel_enable_single_step(regs); > + instruction_pointer(regs) = slot; > + } else { > + BUG(); > + } > +} > + > +static int __kprobes reenter_kprobe(struct kprobe *p, > + struct pt_regs *regs, > + struct kprobe_ctlblk *kcb) > +{ > + switch (kcb->kprobe_status) { > + case KPROBE_HIT_SSDONE: > + case KPROBE_HIT_ACTIVE: > + if (!p->ainsn.check_condn || p->ainsn.check_condn(p, regs)) { > + kprobes_inc_nmissed_count(p); > + setup_singlestep(p, regs, kcb, 1); > + } else { > + /* condition check failed, skip stepping */ > + skip_singlestep_missed(kcb, regs); > + } > + break; > + case KPROBE_HIT_SS: > + pr_warn("Unrecoverable kprobe detected at %p.\n", p->addr); > + dump_kprobe(p); > + BUG(); > + break; > + default: > + WARN_ON(1); > + return 0; > + } > + > + return 1; > +} > + > +static void __kprobes > +post_kprobe_handler(struct kprobe_ctlblk *kcb, struct pt_regs *regs) > +{ > + struct kprobe *cur = kprobe_running(); > + > + if (!cur) > + return; > + > + /* return addr restore if non-branching insn */ > + if (cur->ainsn.restore.type == RESTORE_PC) { > + instruction_pointer(regs) = cur->ainsn.restore.addr; > + if (!instruction_pointer(regs)) > + BUG(); > + } > + > + /* restore back original saved kprobe variables and continue */ > + if (kcb->kprobe_status == KPROBE_REENTER) { > + restore_previous_kprobe(kcb); > + return; > + } > + /* call post handler */ > + kcb->kprobe_status = KPROBE_HIT_SSDONE; > + if (cur->post_handler) { > + /* post_handler can hit breakpoint and single step > + * again, so we enable D-flag for recursive exception. > + */ > + cur->post_handler(cur, regs, 0); > + } > + > + reset_current_kprobe(); > +} > + > +int __kprobes kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr) > +{ > + struct kprobe *cur = kprobe_running(); > + struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); > + > + switch (kcb->kprobe_status) { > + case KPROBE_HIT_SS: > + case KPROBE_REENTER: > + /* > + * We are here because the instruction being single > + * stepped caused a page fault. We reset the current > + * kprobe and the ip points back to the probe address > + * and allow the page fault handler to continue as a > + * normal page fault. > + */ > + instruction_pointer(regs) = (unsigned long)cur->addr; > + if (!instruction_pointer(regs)) > + BUG(); > + if (kcb->kprobe_status == KPROBE_REENTER) > + restore_previous_kprobe(kcb); > + else > + reset_current_kprobe(); > + > + break; > + case KPROBE_HIT_ACTIVE: > + case KPROBE_HIT_SSDONE: > + /* > + * We increment the nmissed count for accounting, > + * we can also use npre/npostfault count for accounting > + * these specific fault cases. > + */ > + kprobes_inc_nmissed_count(cur); > + > + /* > + * We come here because instructions in the pre/post > + * handler caused the page_fault, this could happen > + * if handler tries to access user space by > + * copy_from_user(), get_user() etc. Let the > + * user-specified handler try to fix it first. > + */ > + if (cur->fault_handler && cur->fault_handler(cur, regs, fsr)) > + return 1; > + > + /* > + * In case the user-specified fault handler returned > + * zero, try to fix up. > + */ > + if (fixup_exception(regs)) > + return 1; > + > + break; > + } > + return 0; > +} How is kprobe_fault_handler executed? For arch/arm I see that it's wired in via: 25ce1dd ARM kprobes: add the kprobes hook to the page fault handler > + > +int __kprobes kprobe_exceptions_notify(struct notifier_block *self, > + unsigned long val, void *data) > +{ > + return NOTIFY_DONE; > +} > + > +void __kprobes kprobe_handler(struct pt_regs *regs) > +{ > + struct kprobe *p, *cur; > + struct kprobe_ctlblk *kcb; > + unsigned long addr = instruction_pointer(regs); > + > + kcb = get_kprobe_ctlblk(); > + cur = kprobe_running(); > + > + p = get_kprobe((kprobe_opcode_t *) addr); > + > + if (p) { > + if (cur) { > + if (reenter_kprobe(p, regs, kcb)) > + return; > + } else if (!p->ainsn.check_condn || > + p->ainsn.check_condn(p, regs)) { > + /* Probe hit and conditional execution check ok. */ > + set_current_kprobe(p); > + kcb->kprobe_status = KPROBE_HIT_ACTIVE; > + > + /* > + * If we have no pre-handler or it returned 0, we > + * continue with normal processing. If we have a > + * pre-handler and it returned non-zero, it prepped > + * for calling the break_handler below on re-entry, > + * so get out doing nothing more here. > + * > + * pre_handler can hit a breakpoint and can step thru > + * before return, keep PSTATE D-flag enabled until > + * pre_handler return back. > + */ > + if (!p->pre_handler || !p->pre_handler(p, regs)) { > + kcb->kprobe_status = KPROBE_HIT_SS; > + setup_singlestep(p, regs, kcb, 0); > + return; > + } > + } else { > + /* > + * Breakpoint hit but conditional check failed, > + * so just skip the instruction (NOP behaviour) > + */ > + skip_singlestep_missed(kcb, regs); > + return; > + } > + } else if (*(kprobe_opcode_t *) addr != BRK64_OPCODE_KPROBES) { > + /* > + * The breakpoint instruction was removed right > + * after we hit it. Another cpu has removed > + * either a probepoint or a debugger breakpoint > + * at this address. In either case, no further > + * handling of this interrupt is appropriate. > + * Return back to original instruction, and continue. > + */ > + return; > + } else if (cur) { > + /* We probably hit a jprobe. Call its break handler. */ > + if (cur->break_handler && cur->break_handler(cur, regs)) { > + kcb->kprobe_status = KPROBE_HIT_SS; > + setup_singlestep(cur, regs, kcb, 0); > + return; > + } > + } else { > + /* breakpoint is removed, now in a race > + * Return back to original instruction & continue. > + */ > + } > +} > + > +static int __kprobes > +kprobe_ss_hit(struct kprobe_ctlblk *kcb, unsigned long addr) > +{ > + if ((kcb->ss_ctx.ss_status == KPROBES_STEP_PENDING) > + && (kcb->ss_ctx.match_addr == addr)) { > + clear_ss_context(kcb); /* clear pending ss */ > + return DBG_HOOK_HANDLED; > + } > + /* not ours, kprobes should ignore it */ > + return DBG_HOOK_ERROR; > +} > + > +static int __kprobes > +kprobe_single_step_handler(struct pt_regs *regs, unsigned int esr) > +{ > + struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); > + int retval; > + > + /* return error if this is not our step */ > + retval = kprobe_ss_hit(kcb, instruction_pointer(regs)); > + > + if (retval == DBG_HOOK_HANDLED) { > + kprobes_restore_local_irqflag(regs); > + kernel_disable_single_step(); > + > + if (kcb->kprobe_status == KPROBE_REENTER) > + spsr_set_debug_flag(regs, 1); > + > + post_kprobe_handler(kcb, regs); > + } > + > + return retval; > +} > + > +static int __kprobes > +kprobe_breakpoint_handler(struct pt_regs *regs, unsigned int esr) > +{ > + kprobe_handler(regs); > + return DBG_HOOK_HANDLED; > +} > + > +int __kprobes setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs) > +{ > + struct jprobe *jp = container_of(p, struct jprobe, kp); > + struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); > + long stack_ptr = stack_pointer(regs); > + > + kcb->jprobe_saved_regs = *regs; > + memcpy(kcb->jprobes_stack, (void *)stack_ptr, > + MIN_STACK_SIZE(stack_ptr)); > + > + instruction_pointer(regs) = (long)jp->entry; > + preempt_disable(); > + return 1; > +} > + > +void __kprobes jprobe_return(void) > +{ > + struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); > + > + /* > + * Jprobe handler return by entering break exception, > + * encoded same as kprobe, but with following conditions > + * -a magic number in x0 to identify from rest of other kprobes. > + * -restore stack addr to original saved pt_regs > + */ > + asm volatile ("ldr x0, [%0]\n\t" > + "mov sp, x0\n\t" > + "ldr x0, =" __stringify(JPROBES_MAGIC_NUM) "\n\t" > + "BRK %1\n\t" > + "NOP\n\t" > + : > + : "r"(&kcb->jprobe_saved_regs.sp), > + "I"(BRK64_ESR_KPROBES) > + : "memory"); > +} > + > +int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs) > +{ > + struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); > + long stack_addr = kcb->jprobe_saved_regs.sp; > + long orig_sp = stack_pointer(regs); > + struct jprobe *jp = container_of(p, struct jprobe, kp); > + > + if (regs->regs[0] == JPROBES_MAGIC_NUM) { > + if (orig_sp != stack_addr) { > + struct pt_regs *saved_regs = > + (struct pt_regs *)kcb->jprobe_saved_regs.sp; > + pr_err("current sp %lx does not match saved sp %lx\n", > + orig_sp, stack_addr); > + pr_err("Saved registers for jprobe %p\n", jp); > + show_regs(saved_regs); > + pr_err("Current registers\n"); > + show_regs(regs); > + BUG(); > + } > + *regs = kcb->jprobe_saved_regs; > + memcpy((void *)stack_addr, kcb->jprobes_stack, > + MIN_STACK_SIZE(stack_addr)); > + preempt_enable_no_resched(); > + return 1; > + } > + return 0; > +} > + > +/* Break Handler hook */ > +static struct break_hook kprobes_break_hook = { > + .esr_mask = BRK64_ESR_MASK, > + .esr_val = BRK64_ESR_KPROBES, > + .fn = kprobe_breakpoint_handler, > +}; > + > +/* Single Step handler hook */ > +static struct step_hook kprobes_step_hook = { > + .fn = kprobe_single_step_handler, > +}; > + > +int __init arch_init_kprobes(void) > +{ > + register_break_hook(&kprobes_break_hook); > + register_step_hook(&kprobes_step_hook); > + > + return 0; > +} > diff --git a/arch/arm64/kernel/kprobes.h b/arch/arm64/kernel/kprobes.h > new file mode 100644 > index 0000000..93c54b4 > --- /dev/null > +++ b/arch/arm64/kernel/kprobes.h > @@ -0,0 +1,30 @@ > +/* > + * arch/arm64/kernel/kprobes.h > + * > + * Copyright (C) 2013 Linaro Limited. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + */ > + > +#ifndef _ARM_KERNEL_KPROBES_H > +#define _ARM_KERNEL_KPROBES_H > + > +/* BRK opcodes with ESR encoding */ > +#define BRK64_ESR_MASK 0xFFFF > +#define BRK64_ESR_KPROBES 0x0004 > +#define BRK64_OPCODE_KPROBES 0xD4200080 /* "brk 0x4" */ > +#define ARCH64_NOP_OPCODE 0xD503201F > + > +#define JPROBES_MAGIC_NUM 0xa5a5a5a5a5a5a5a5 > + > +/* Move this out to appropriate header file */ > +int fixup_exception(struct pt_regs *regs); > + > +#endif /* _ARM_KERNEL_KPROBES_H */ > diff --git a/arch/arm64/kernel/vmlinux.lds.S b/arch/arm64/kernel/vmlinux.lds.S > index 9965ec8..5402a98 100644 > --- a/arch/arm64/kernel/vmlinux.lds.S > +++ b/arch/arm64/kernel/vmlinux.lds.S > @@ -80,6 +80,7 @@ SECTIONS > TEXT_TEXT > SCHED_TEXT > LOCK_TEXT > + KPROBES_TEXT > HYPERVISOR_TEXT > *(.fixup) > *(.gnu.warning) > -- > 1.8.1.2 > -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/