使用文件系统作为接口的优势自然不必多说,它可以将任意复杂的操作映射到既有的简单的读,写,控制,打开,关闭等简单操作上,ftrace的另一个妙点在于其动态二进制修正技术。其实kprobe也是使用了二进制修正技术,然而它做的很硬,而ftrace则使用了GCC内置的mcount机制,通过重载mcount函数来完成对任意函数调用的统计。
mcount机制是GCC的一个特性,在任何函数调用时,会纪录关于该函数的一些信息。比如以下的程序:
mcount.c:
#include <stdio.h> void mcount() { printf("@@@@\n"); }gcc -c mcount.c
main.c:
#include <stdlib.h> #include <stdio.h> extern void mcount(void); void b(int i) { printf("b:%d\n", i); } int a(int i) { b(i); return 3; } int main() { int i = 3; int k = a(i); return k; }gcc -c main.c -pg
gcc mcount.o main.o -o test
执行test,则会发现每个函数调用都会打印出@@@,这说明我们重载mcount成功了。如果能将mcount做成一个只执行ret的stub函数,或者连call mcount一起都执行nop的stub,那么相当于没有这个mcount函数,如果某个时间用户启用了ftrace,则将上述stub替换为真正的trace函数,那不就可以动态开启/关闭trace功能了么?Linux kernel正是这么做的。要想这么做,stub函数要做的足够灵活,以上面的mcount.c/main.c为例,一个比较灵活但不绝对灵活的设计框架如下:
char code[] = {0xc3, 0x90, 0x90...} //0xc3为直接ret void mcount() { int (*pf)(void); pf = &code[0]; pf(); }如果用户开启了trace,则将code进行替换,替换成call real_func的操作码,而real_func不止一个固定的函数,而是可以register的,那么我们就可以根据自己的爱好来任意替换trace函数进而实现任意的trace风格了。Linux内核的做法比我这个要灵活的多,通过回调func的机制,它甚至可以画出一副函数调用图,十分强大。顺便说一句,trace回调函数的实现利用了内核编译时产生的内核函数位置表,它的条目就是函数名和位置这一对映射,trace回调函数会根据当前的地址查到函数名。
我上述的框架只是一个框架,如果你真的去编译运行了,会发现出现了恶心的segment fault,这是因为如今大多数的内核都实施了data section不可执行,text section不可写的保护功能,如果你硬要那么做,会出现通用保护异常,因此还要做大量的链接脚本的工作。这个费力的事就不说了,说多了都是泪!
ftrace的核心在于利用了mcount机制以及文件系统机制,它的使用非常简单,只需要挂载debugfs,你就可以任意调试了:
mount -t debugfs debugfs /debug
然后进入/debug/tracing目录,检查available_tracers,看看你当前的kernel支持的trace功能都有哪些,如果有function,说明你的内核支持函数跟踪功能,ftrace支持过滤功能,比如按照内核函数过滤,按照进程过滤。下面是对于一个长ping的trace结果片断:
0) | sys_socketcall() {
0) | copy_from_user() {
0) | _copy_from_user() {
0) 0.137 us | _cond_resched();
0) 0.457 us | }
0) 0.806 us | }
0) 0.130 us | audit_socketcall();
0) | sys_recvmsg() {
0) | sockfd_lookup_light() {
0) 0.228 us | fget_light();
0) 0.558 us | }
0) | __sys_recvmsg() {
0) | _copy_from_user() {
0) 0.130 us | _cond_resched();
0) 0.405 us | }
0) | verify_iovec() {
0) | _copy_from_user() {
0) 0.129 us | _cond_resched();
0) 0.429 us | }
0) 0.736 us | }
0) | sock_recvmsg() {
0) | security_socket_recvmsg() {
0) | apparmor_socket_recvmsg() {
0) 0.179 us | aa_revalidate_sk();
0) 0.482 us | }
0) 0.945 us | }
0) 0.210 us | sock_update_classid();
0) | inet_recvmsg() {
不光可以绘制出函数调用图,其计时统计信息对于性能分析也是很有参考意义的。
ftrace很强大,不需要你对系统做任何额外的配置,不需要安装额外的软件,直接使用文件系统接口即可。我十分喜欢这个机制,因为我讨厌需要额外配置的机制,那样促使很多人走偏了路,他们在炫耀敲命令的技巧的同时,给了别人很大的压力,其实行内人士都知道,他们的大部分命令都是为了搭建这个trace环境,而不是解决真正的问题,因此这种命令也包括apt-get,你不懂,你就会觉得他很猛!