第5篇:Kprobe、Uprobe 与 Tracepoint — 三大探测机制与 perf 集成¶
源码:
kernel/kprobes.ckernel/events/uprobes.ckernel/trace/trace_event_perf.c| 头文件:include/linux/kprobes.h
系列目录:perf 内核源码深度解析
1. 三大探测机制概览¶
| 机制 | 探测点 | 触发方式 | 开销 | 精度 |
|---|---|---|---|---|
| tracepoint | 内核静态埋点(TRACE_EVENT 宏) | 函数调用 | 极低(条件判断 + 一个 NOP) | 只在有埋点的地方可用 |
| kprobe | 内核任意指令地址 + 函数入口 | int3 断点 | 中等(int3 陷入 + 单步) | 任意地址,包括 inline 函数 |
| uprobe | 用户态进程任意指令地址 | int3 断点(同进程页表) | 较高(int3 + 页表操作) | 任意地址,用户空间包括 JIT 代码 |
2. Kprobe — 内核断点探测¶
2.1 struct kprobe¶
// include/linux/kprobes.h:59
struct kprobe {
struct hlist_node hlist; // 哈希表节点(按地址索引)
unsigned long nmissed; // 衰减计数器(用于优化/去优化决策)
kprobe_opcode_t *addr; // ★ 探测点的实际地址
const char *symbol_name; // ★ 符号名称(如 "do_sys_open")
unsigned int offset; // ★ 符号内的偏移(0=函数入口)
kprobe_pre_handler_t pre_handler; // ★ 断点命中的回调
kprobe_post_handler_t post_handler; // 单步执行后的回调
kprobe_opcode_t opcode; // 保存的原指令字节(用于恢复)
struct arch_specific_insn ainsn; // ★ 架构特定的复制的原指令(x86: 5 字节)
u32 flags; // KPROBE_FLAG_GONE/DISABLED/OPTIMIZED/FTRACE
};
标志位(kprobes.h:97-105):
- KPROBE_FLAG_GONE — 探测点已失效(模块卸载等原因)
- KPROBE_FLAG_DISABLED — 用户已禁用
- KPROBE_FLAG_OPTIMIZED — 已优化为 jmp 而非 int3
- KPROBE_FLAG_FTRACE — 通过 ftrace mcount 桩注入(零 int3 开销)
2.2 哈希表¶
// kernel/kprobes.c:51-64
#define KPROBE_HASH_BITS 6
#define KPROBE_TABLE_SIZE (1 << KPROBE_HASH_BITS) // 64 桶
static struct hlist_head kprobe_table[KPROBE_TABLE_SIZE]; // 按地址哈希
static DEFINE_MUTEX(kprobe_mutex); // 序列化注册/注销
2.3 register_kprobe — 注册¶
// kernel/kprobes.c:1708
int register_kprobe(struct kprobe *p)
{
// ① 规范地址:从 symbol_name + offset 解析为实际地址
addr = _kprobe_addr(p->addr, p->symbol_name, p->offset);
// ② 安全检查:地址是否在黑名单中?是否在 NMI 区域?
check_kprobe_address_safe(p, ...);
// ③ 检查是否有已存在的探测点 → 合并为 aggregated probe
// ④ hlist_add_head_rcu → 把 kprobe 加上哈希表
// ⑤ arm_kprobe(p) → 在 addr 处写入 int3 (x86: 0xCC)
// ⑥ try_to_optimize_kprobe(p) → 如果可能,改为 jmp 优化(无 int3 开销)
}
2.4 kprobe_int3_handler — 命中断点¶
// arch/x86/kernel/kprobes/core.c:978
int kprobe_int3_handler(struct pt_regs *regs)
{
// ① 用户态 → 忽略(uprobe 的 int3 有独立的处理)
if (user_mode(regs)) return 0;
// ② IP 中含有 int3 指令 → 回退到 int3 之前的地址
addr = regs->ip - sizeof(kprobe_opcode_t); // x86: -1
// ③ 查找探测点
p = get_kprobe(addr);
if (p) {
// ④ 设置状态 = KPROBE_HIT_ACTIVE
kcb->kprobe_status = KPROBE_HIT_ACTIVE;
// ⑤ ★ 调用 pre_handler
if (p->pre_handler(p, regs))
return 1; // pre_handler 处理完毕,不需要单步
// ⑥ 设置单步执行(恢复原指令,执行 1 条指令,调用 post_handler,重新插入 int3)
setup_singlestep(p, regs, kcb, 0);
return 1;
}
}
2.5 kretprobe — 函数返回探测¶
// include/linux/kprobes.h:146
struct kretprobe {
struct kprobe kp;
kretprobe_handler_t handler; // ★ 函数返回时调用的回调
kretprobe_handler_t entry_handler; // ★ 函数进入时调用的回调
int maxactive; // 最大并发探测数
int nmissed; // 衰减计数器
struct rethook *rh; // rethook 引擎
};
// kernel/kprobes.c:~2255
int register_kretprobe(struct kretprobe *rp)
{
// 在函数入口处设置 kprobe(pre_handler = pre_handler_kretprobe)
rp->kp.pre_handler = pre_handler_kretprobe;
register_kprobe(&rp->kp);
// 在函数返回处设置 rethook(替换返回地址)
}
3. Uprobe — 用户态断点探测¶
3.1 struct uprobe¶
// kernel/events/uprobes.c:62
struct uprobe {
struct rb_node rb_node; // 在 inode 的红黑树中的节点
struct rw_semaphore register_rwsem; // 消费者修改锁
struct list_head consumers; // ★ uprobe_consumer 链表
struct inode *inode; // 包含探测点的文件 inode
loff_t offset; // ★ 探测点的文件偏移
struct arch_uprobe arch; // 架构特定的原指令副本
};
3.2 uprobe_register¶
// kernel/events/uprobes.c:1390
struct uprobe *uprobe_register(struct inode *inode,
loff_t offset, loff_t ref_ctr_offset,
struct uprobe_consumer *uc)
{
// ① 在 inode 的红黑树中查找/创建 uprobe
// ② 添加 consumer 到 uprobe->consumers 链表
// ③ 在 inode 的页面缓存中写入断点指令 (int3 on x86)
}
3.3 handle_swbp — 命中断点¶
// kernel/events/uprobes.c:2712
static void handle_swbp(struct pt_regs *regs)
{
// ① 获取断点 VA(有符号扩展)
// ② 查找对应 uprobe(通过 mm→inode→offset)
// ③ 恢复原指令,调用 handler_chain
// ④ 设置单步执行或 SSOL
}
4. Perf 集成 — perf_kprobe_init / perf_uprobe_init¶
4.1 kprobe 作为 perf_event¶
// kernel/trace/trace_event_perf.c:247
int perf_kprobe_init(struct perf_event *p_event, bool is_retprobe)
{
// ① 从用户态复制 kprobe 符号名: p_event->attr.kprobe_func
// ② 创建 trace_kprobe: create_local_trace_kprobe()
// ③ 初始化 perf trace_event: perf_trace_event_init(tp_event, p_event)
}
使用方式:
4.2 uprobe 作为 perf_event¶
// kernel/trace/trace_event_perf.c:298
int perf_uprobe_init(struct perf_event *p_event,
unsigned long ref_ctr_offset, bool is_retprobe)
{
// ① 从用户态复制文件路径: p_event->attr.uprobe_path
// ② 创建 trace_uprobe
// ③ 初始化 perf trace_event
}
使用方式:
5. 完整探测路径¶
用户态 perf probe 命令
→ 写入 /sys/kernel/tracing/kprobe_events 或 uprobe_events
→ trace_kprobe_create / trace_uprobe_create (dyn_event 框架)
→ register_kprobe / uprobe_register
→ 在探测地址写入 int3 (0xCC)
执行流:
进程/中断到达探测地址
→ int3 陷入内核
→ kprobe_int3_handler (内核地址) 或 handle_swbp (用户态地址)
→ pre_handler 回调:
→ 如果是 perf 事件: perf_trace_perf_submit → perf_output_sample
→ 如果是 ftrace: trace_event_buffer_reserve → commit
→ 如果是 BPF: bpf_prog 回调
→ 单步执行原指令
→ post_handler 回调
6. 总结¶
| 组件 | 关键文件 | 核心作用 |
|---|---|---|
| kprobe 核心 | kprobes.c:1708 register_kprobe |
int3 替换 + 哈希表管理 |
| kprobe 命中 | core.c:978 kprobe_int3_handler |
int3 陷入分发 + 单步 |
| kretprobe | kprobes.c:~2255 register_kretprobe |
返回地址 rethook |
| uprobe 核心 | uprobes.c:1390 uprobe_register |
文件 inode 红黑树 + 断点注入 |
| uprobe 命中 | uprobes.c:2712 handle_swbp |
查找 uprobe + handler_chain |
| perf kprobe | trace_event_perf.c:247 perf_kprobe_init |
kprobe → perf_event 桥接 |
| perf uprobe | trace_event_perf.c:298 perf_uprobe_init |
uprobe → perf_event 桥接 |