内核中的插桩调试

插桩法是内核常用的一种调试手段,利用代码中插桩,执行到此时执行对应的钩子函数(hook)来达到调试的目的,从实现来说,它不可避免的会带来一些性能上的开销,不过随着实现的不断优化,这部分的开销已经越来越小了。比如使能了 dynamic ftrace 后的内核,在关闭 ftrace 开关的情况下,实际上只是多了个几个 nop 指令,并不会带来很大的性能开销。下面就来看下内核中都有哪几种常用的插桩调试机制。

ftrace 我在其他博客中有介绍过,它的原理是使用 gcc 编译器的 -pg 选项,达到在内核代码中插桩的目的。该选项会自动在函数的入口处加上对 mcount 的调用指令。这样每次进入一个函数时都会调用对应的钩子函数来打印函数信息。

tracepoint 的原理和 ftrace 很类似,只不过它并不是利用 gcc 的特性来实现的,而是在内核代码中提前做了插桩,代码运行到跟踪点(桩)上注册了钩子函数(hook),那么就会执行钩子函数达到调试的目的。和 ftrace 相比,它的劣势是实现更加复杂,需要程序员按需要在特定位置加入跟踪点代码,ftrace 可以实现所有函数的跟踪,但是 tracepoint 就只会跟踪添加了代码的跟踪点。除了劣势,自然 tracepoint 也有它的优点,它的灵活性更加好,我们可以自己定制hook函数,添加想要获取的信息,而 ftrace 默认只会打印函数名。

eventtrace 这种机制实际上是相当于 tracepoint + ftrace 的组合,它的前端使用了 tracepoint 来实现跟踪点,后端使用 ftrace 的架构,把调试信息输出到 ftrace ring buffer 中,这样在内核中可以通过 cat /sys/kernel/debug/tracing/trace 来获取 eventtrace 中的调试信息。

kprobe 是另外一种基于打桩机制实现的调试手法,在注册一个kprobe时会先拷贝出内核函数指令,然后在该函数前插入一条指令,以i386/x86_64为例,它是一个 int3 指令,相当于是插入桩位,在执行到此时会触发内核进入 int3 中断,然后跳转执行注册的hook和已经拷贝的代码段,最后再返回到函数后面的地址处继续运行,它支持在监测的函数前后分别插入hook函数。在使用时可以实现一个kernel module的方式注册 kprobe ,并且可以对所有的内核函数进行动态插桩,只要我们能够找到对应的地址。

jprobekretprobe 这两种是和 kprobe 类似的实现机制,也是通过注入中断指令执行hook来添加调试信息的,和kprobe的区别是,jprobe只在函数入口处被调用,而kretprobe只在函数return处被调用。

你可能感兴趣的:(内核调试)