限于作者能力水平,本文可能存在的谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 本文目标从一个kprobe的使用例子出发,继而简要分析kprobe的实现,以期能帮助读者形成对kprobe的基本使
用方法和实现原理的全局视图。
3. kprobe实例
下面是一个对 fork() 系统调用进行追踪的kprobe例子,基于对内核提供的kprobe例子 samples/kprobes/kprobe_example.c 进行改写:
#include
#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 = %x\n",
p->symbol_name, p->addr, p->flags);
/* Return 0 means that a dump_stack() here will give a stack backtrace */
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 = %x\n",
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 because we don't handle the fault. */
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 %d\n", ret);
return ret;
}
pr_info("Planted kprobe at %p\n", kp.addr);
return 0;
}
static void __exit kprobe_test_exit(void)
{
unregister_kprobe(&kp);
pr_info("kprobe at %p unregistered\n", kp.addr);
}
module_init(kprobe_test_init)
module_exit(kprobe_test_exit)
MODULE_LICENSE("GPL");
4. kprobe实现分析
4.1 实现原理
kprobe实现首先将探测点的指令,替换为【架构未定义指令】;
当程序执行到探测点时,触发【未定义指令异常】函数,该函数依次做如下动作:
(1) 执行krobe的pre_handler
(2) 单步模拟执行探测点被替换的原指令
(3) 执行kprobe的post_handler
然后继续执行被探测点之后的指令序列。
4.2 分析背景
. 硬件架构: ARM32
. 内核版本: Linux 4.14.111
4.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);
...
/*
* a. 保存探测点的原指令,解码探测点的指令,设定该指令的单步模拟执行接口
*/
prepare_kprobe(p) = arch_prepare_kprobe(p)
insn = __mem_to_opcode_arm(*p->addr); /* 读取探测位置的指令内容 */
p->opcode = insn; /* 保存探测位置指令内容到kprobe */
/* 解码探测点的指令,设定该指令的单步模拟执行接口 */
arm_probes_decode_insn(insn, &p->ainsn, true, kprobes_arm_actions, kprobes_arm_checkers)
...
/*
* b. 替换被探测点的指令为【架构未定义指令】
*/
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();
/* 获取对应的kprobe对象 */
p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc);
/* 设置当前cpu上的kprobe */
set_current_kprobe(p);
kcb->kprobe_status = KPROBE_HIT_ACTIVE;
/* 调用【可能的】kprobe的pre_handler */
p->pre_handler(p, regs)
kcb->kprobe_status = KPROBE_HIT_SS;
/* 单步模拟指令被替换的指令 */
singlestep(p, regs, kcb);
/* 调用【可能的】kprobe的post_handler */
kcb->kprobe_status = KPROBE_HIT_SSDONE;
p->post_handler(p, regs, 0);
/* 当前cpu的kprobe重置为NULL */
reset_current_kprobe();
}
/* 如果是非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])
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)