限于作者能力水平,本文可能存在的谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 本文目标从一个kprobe的使用例子出发,继而简要分析kprobe的实现,以期能帮助读者形成对kprobe的基本使 用方法和实现原理的全局视图。3. kprobe实例
下面是一个对 fork() 系统调用进行追踪的kprobe例子,基于对内核提供的kprobe例子 samples/kprobes/kprobe_example.c 进行改写:
#include4. kprobe实现分析 4.1 实现原理#include #include #include static int handle_pre(struct kprobe *p, struct pt_regs *regs) { pr_info("pre handler: " "p->symbol_name = %s, p->addr = 0x%p, p->flags = %xn", p->symbol_name, p->addr, p->flags); return 0; } static void handle_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) { pr_info("post handler: " "p->symbol_name = %s, p->addr = 0x%p, p->flags = %xn", p->symbol_name, p->addr, p->flags); } static int handle_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) { pr_info("fault handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr); return 0; } static struct kprobe kp = { .symbol_name = "_do_fork", .pre_handler = handle_pre, .post_handler = handle_post, .fault_handler = handle_fault, }; static int __init kprobe_test_init(void) { int ret; ret = register_kprobe(&kp); if (ret < 0) { pr_err("register_kprobe failed, returned %dn", ret); return ret; } pr_info("Planted kprobe at %pn", kp.addr); return 0; } static void __exit kprobe_test_exit(void) { unregister_kprobe(&kp); pr_info("kprobe at %p unregisteredn", kp.addr); } module_init(kprobe_test_init) module_exit(kprobe_test_exit) MODULE_LICENSE("GPL");
kprobe实现首先将探测点的指令,替换为【架构未定义指令】; 当程序执行到探测点时,触发【未定义指令异常】函数,该函数依次做如下动作: (1) 执行krobe的pre_handler (2) 单步模拟执行探测点被替换的原指令 (3) 执行kprobe的post_handler 然后继续执行被探测点之后的指令序列。4.2 分析背景
. 硬件架构: ARM32 . 内核版本: Linux 4.14.1114.3 实现分析 4.3.1 注册【未定义指令异常】处理接口
static struct undef_hook kprobes_arm_break_hook = {
.instr_mask = 0x0fffffff,
.instr_val = KPROBE_ARM_BREAKPOINT_INSTRUCTION,
.cpsr_mask = MODE_MASK,
.cpsr_val = SVC_MODE,
.fn = kprobe_trap_handler,
};
arch_init_kprobes()
arm_kprobe_decode_init()
register_undef_hook(&kprobes_arm_break_hook)
4.3.2 kprobe的安装准备工作
a. 保存探测点的原指令,解码探测点的指令,设定该指令的单步模拟执行接口;
b. 然后替换探测点指令为【架构未定义指令】。
kprobe_test_init() register_kprobe(&kp) p->addr = kprobe_addr(p); ... prepare_kprobe(p) = arch_prepare_kprobe(p) insn = __mem_to_opcode_arm(*p->addr); p->opcode = insn; arm_probes_decode_insn(insn, &p->ainsn, true, kprobes_arm_actions, kprobes_arm_checkers) ... arm_kprobe(p) ... __arm_kprobe(p) = arch_arm_kprobe(p) addr = p->addr; brkp = KPROBE_ARM_BREAKPOINT_INSTRUCTION; ... patch_text(addr, brkp) ...4.3.3 kprobe的触发
当执行到kprobe点,由于指令被替换为未定义指令,所以触发了未定义指令异常,进入未定义指令处理流程:
__und_svc
__und_fault
do_undefinstr(regs)
pc = (void __user *)instruction_pointer(regs);
instr = __mem_to_opcode_arm(*(u32 *) pc);
...
if (call_undef_hook(regs, instr) == 0) {
list_for_each_entry(hook, &undef_hook, node)
...
if ((instr & hook->instr_mask) == hook->instr_val &&
(regs->ARM_cpsr & hook->cpsr_mask) == hook->cpsr_val)
fn = hook->fn;
...
fn(regs, instr) = kprobe_trap_handler(regs, instr)
kprobe_handler(regs)
kcb = get_kprobe_ctlblk();
p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc);
set_current_kprobe(p);
kcb->kprobe_status = KPROBE_HIT_ACTIVE;
p->pre_handler(p, regs)
kcb->kprobe_status = KPROBE_HIT_SS;
singlestep(p, regs, kcb);
kcb->kprobe_status = KPROBE_HIT_SSDONE;
p->post_handler(p, regs, 0);
reset_current_kprobe();
}
info.si_signo = SIGILL;
info.si_errno = 0;
info.si_code = ILL_ILLOPC;
info.si_addr = pc;
arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6)
4.3.4 kprobe的移除
禁用kprobe,恢复被替换的指令,释放资源,移除kprobe等等。
unregister_kprobe() unregister_kprobes(&p, 1) __unregister_kprobe_top(kps[i]) __unregister_kprobe_bottom(kps[i])5. 参考资料
ARM Architecture Reference Manual.pdf



