如何在eBPF的入口与返回探针中获取被调用函数名称
解决eBPF返回探针关联被调用函数的通用方法
核心问题是函数退出后,PT_REGS_IP(ctx)返回的是调用者的指令地址,无法直接拿到被追踪函数的信息。以下是无需硬编码的通用解决方案:
方法原理
利用哈希表存储入口事件的上下文(进程ID+返回地址作为唯一键),返回探针通过相同的键查找对应的函数信息,实现进出事件的关联。
1. eBPF核心代码(C部分)
#include <uapi/linux/ptrace.h> // 存储函数信息的结构体 struct func_info { char name[64]; u64 addr; }; // 哈希表:键为进程ID+返回地址,值为函数信息 BPF_HASH(func_map, struct { u32 pid; u64 ret_addr; }, struct func_info); // 通用入口探针:记录函数地址、名称及关联键 int kprobe__enter_func(struct pt_regs *ctx) { u32 pid = bpf_get_current_pid_tgid() >> 32; // 获取返回地址(x86_64下为当前指令地址,其他架构需适配) u64 ret_addr = PT_REGS_RIP(ctx); u64 func_addr = PT_REGS_IP(ctx); struct func_info info = {}; info.addr = func_addr; // 通过地址解析函数名称,无需硬编码 bpf_get_sym(func_addr, info.name, sizeof(info.name)); struct { u32 pid; u64 ret_addr; } key = {pid, ret_addr}; func_map.update(&key, &info); bpf_trace_printk("Enter: %s [0x%llx]\n", info.name, info.addr); return 0; } // 通用返回探针:通过键查找函数信息,关联退出事件 int kretprobe__exit_func(struct pt_regs *ctx) { u32 pid = bpf_get_current_pid_tgid() >> 32; u64 ret_addr = PT_REGS_IP(ctx); // 与入口时记录的返回地址一致 struct { u32 pid; u64 ret_addr; } key = {pid, ret_addr}; struct func_info *info = func_map.lookup(&key); if (info) { bpf_trace_printk("Exit: %s [0x%llx]\n", info->name, info->addr); func_map.delete(&key); // 清理条目,避免内存泄漏 } return 0; }
2. Python层挂载探针(动态支持任意函数)
from bcc import BPF # 可自定义要跟踪的函数列表,无需硬编码 TARGET_FUNCTIONS = ["my_func_1", "my_func_2", "my_func_3"] # 加载eBPF程序 bpf = BPF(text=open("trace_funcs.c").read()) # 为每个目标函数挂载入口和返回探针 for func in TARGET_FUNCTIONS: bpf.attach_kprobe(event=func, fn_name="kprobe__enter_func") bpf.attach_kretprobe(event=func, fn_name="kretprobe__exit_func") # 持续打印跟踪日志 bpf.trace_print()
关键注意事项
- 架构适配:不同CPU架构的返回地址获取方式不同,比如ARM架构需用
PT_REGS_LR(ctx)替代PT_REGS_RIP(ctx),可通过宏定义#ifdef __x86_64__做条件编译。 - 符号解析:
bpf_get_sym()依赖内核开启CONFIG_KALLSYMS和CONFIG_DEBUG_INFO_BTF(或传统符号表),确保内核支持地址到符号的解析。 - 内存管理:返回探针处理完成后必须删除哈希表条目,避免长时间运行导致内存泄漏。
内容的提问来源于stack exchange,提问作者TreeWater
相关产品推荐
相关产品推荐

