Kprobes 使用与原理介绍
.
简介
Kprobes (kprobe and kretprobe)是一种Linux的调试机制,用于监控系统内部事件。你可以使用 Kprobes 来解决性能瓶颈、记录特定事件、跟踪问题等。
使用方法
kprobes 接口
内核源码samples目录下 kprobes/kprobe_example.c 提供了 Kprobes 测试用例。代码非常简单,主要配置了 struct kprobe 如下几个字段:
- symbol_name
- offset
- pre_handler
- post_handler
最后调用 register_kprobe() 注册定义的kprobe。
1 | |
1 | |
kprobe trace
使用方法和基于tracepoint的events类似. 不同的是Kprobes可以probe几乎所有函数(除了标注__kprobes/nokprobe_inline 和被标记为 NOKPROBE_SYMBOL的函数),且在系统运行时能够动态添加/删除。使用方法详见内核文档 kprobetrace。主要是使用如下接口:
1 | |
bpftrace
bpftrace使用很简单,详见 bpftrace guide。例如,使用 bpftrace 通过 kretprobe 获取 ktime_get 返回的时间:
1 | |
Kprobes在arm64上的实现
kprobe 和 kretprobe 原理相似,因此本文只分析kprobe流程。
register_kprobe流程
arch_prepare_kprobe() → arch_prepare_ss_slot(), 拷贝probe点的指令(接下来需要替换probe位置的指令),调用get_insn_slot()获取一个slot,按照如下顺序排放:- 拷贝的probe点的指令
brk 0x6
arm_kprobe() → arch_arm_kprobe(),probe位置的指令替换为brk 0x4
BRK #<imm>,立即数imm会保存会在ESR_ELx.ISS。
异常处理流程
1、执行到probe点后,由于已经替换成了 brk 0x4,执行后陷入el1 debug异常。 call_break_hook 根据 ESR.ISS(0x4)调用 kprobe_breakpoint_handler,然后再根据probe点的 addr 在kprobes的哈希链表找到注册的kprobe结构体,再调用结构体里的 pre_handler() 就完成了。调用栈如下:
1 | |
2、执行完 pre_handler() 后,跳转到之前保存的slot执行,第一条指令就是原来probe点的指令,接下来执行下一条指令 brk 0x6 后再次陷入异常。流程和上文相似,只不过 ESR.ISS 变成了 0x6, 因此 call_break_hook 调用 kprobe_breakpoint_ss_handler,最后执行完 handler_post() 并退出异常,继续正常的代码流程。调用栈如下:
1 | |
Jump Optimization
ToDo
