Linux调试追踪: kprobe实现简析

Linux调试追踪: kprobe实现简析,第1张

1. 前言

限于作者能力水平,本文可能存在的谬误,因此而给读者带来的损失,作者不做任何承诺。

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])

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1329841.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存