利用kprobe探测内核信息
printk是内核调试常用的调试手段,但是每次更改都需要重新编译启动内核,调试效率很低。
kprobe根据实现原理也可以分为三种:基于动态ftrace的kprobe,基于int3的kprobe和基于jump相对跳转指令实现的kprobe。
这里主要说int3的kprobe,int3是x86架构下的trap类型的断点指令。代码执行到对应位置会发生trap,然后执行kprobe的代码,执行后恢复现场。
使用
第一步是先获取要插入点的地址。可以通过cat /proc/kallsyms | grep <symbol_name>
设置addr
成员或者设置symbol_name
成员由kprobe自行查找获取。
1 |
|
这是kprobe的示例代码, 以内核模块的方式编译生成ko文件,insmod file.ko
即可生效。
举个例子,系统写数据到硬盘前是有缓存的,如果想知道系统真实提交给磁盘的io请求,就可以用kprobe来实现。
先看一下探测点放在哪里
submit_bio
就是kernel提交io请求给块设备的函数了。
1 | /** |
这里可以看到这个函数有个block_dump的判断分支,如果开启,会打印哪个程序/pid 读/写 了在哪个设备的哪个扇区多少块。
这个可以通过echo 1 > /proc/sys/vm/block_dump
来开启这个功能。
这里也可以使用kprobe实现这个功能,或者对这个功能增加自己想要的处理.
1 |
|
other
基于kprobe,还有jprobe,kretprobe等方便使用的包装。
这里提到内核探测,还有许多工具。
systemtap 是利用Kprobe 提供的API来实现动态地监控和跟踪运行中的Linux内核的工具,相比Kprobe,systemtap更加简单,提供给用户简单的命令行接口,以及编写内核指令的脚本语言。
ebpf是一个新的流行方向。
旧的内核不支持ebpf,而新内核ebpf可能成为了首选。
技巧-如何在任意地址做探测
前面提到的探测点都只是挂在函数前,函数后,那么函数中怎么办?
内核大部分用C语言写成,遗憾的是kprobe不能向源代码任意行插入侦测点,但是可以在任意地址插入侦测点。
objdump可以给出目标文件的汇编代码,可以通过工具帮助/二分法定位源代码对应的汇编代码。需要注意的是源代码和汇编不是一一对应的,还有编译器优化也可能改变些许逻辑。
我这里常用的技巧是,找callq指令,这是调用指令,找到源代码中的调用和汇编中的调用位置,可以快速确定相对位置。
对汇编不熟悉,查位置还是有些麻烦的,只能这样吗?
1 | /* |
1 | kernel: mymod: Unknown symbol xxx |
这样编译生成的ko模块,安装时可能会报Unknown symbol xxx
的问题,这是linux的限制,但是可以回避。
编辑一个addrs.dat,里面给出上面找不到符号的地址
1 | SECTIONS |
ld -r -o mymod.ko mymod.ko -R addrs.dat
重新解析地址
再次安装就可以了