BPF函数的"钩子"定义位置及kfunc挂载实现原理问询
问题:kfunc/kretfunc探测内核函数的底层原理
我们可以通过bpftrace脚本探测内核函数vfs_read,示例代码如下:
kfunc:vfs_read { printf("Entered vfs_read.\n"); } kretfunc:vfs_read { printf("Exited vfs_read.\n"); }
但查看vfs_read的源码后发现,函数首尾并没有预设的钩子点。我想了解当将探测程序挂载到kfunc的开头/结尾时,实际发生了什么。
我曾猜测是编译Linux内核时,通过预处理器指令在每个kfunc的首尾注入代码,于是编译6.6.22版本内核时,在Makefile中添加了KBUILD_CFLAGS += -save-temps=obj来保存预处理后的*.i文件,但并未找到预期的注入代码,因此产生疑问。
解答
kfunc/kretfunc这类BPF探测机制不需要内核源码预设钩子,也不是靠编译时注入代码实现的,核心依赖内核的运行时动态指令修改能力:
- kfunc(函数入口探测):内核会动态修改目标函数开头的机器指令(比如x86架构下用int3断点指令替换原指令),让函数执行流先跳转到挂载的BPF程序。等BPF程序执行完毕后,再恢复原指令并继续执行函数的正常逻辑。
- kretfunc(函数返回探测):内核会在函数入口处动态插入代码,把函数的返回地址替换为一个专门的跳板(trampoline)函数地址。当原函数执行到return指令时,会先跳转到这个跳板函数,触发对应的BPF程序执行,之后再跳回原本的返回地址完成函数返回。
整个过程都是运行时动态完成的,完全不需要编译内核时做预处理注入,所以你查看预处理后的*.i文件自然找不到相关代码。内核会维护探测点的管理结构,确保探测卸载时能恢复被修改的指令,不会影响内核的正常运行。
内容的提问来源于stack exchange,提问作者Rohit D
相关产品推荐
相关产品推荐

