使用gcc的-finstrument-functions选项进行函数跟踪【转】

转自:http://blog.csdn.net/jasonchen_gbd/article/details/44044899

GCC Function instrumentation机制可以用来跟踪函数的调用关系,在gcc中对应的选项为“-finstrument-functions”。可查看gcc的man page来获取更详细信息。
编译时如果为gcc加上“-finstrument-functions”选项,那在每个函数的入口和出口处会各增加一个额外的hook函数的调用,增加的这两个函数分别为:
[cpp] view plain copy
 
  1. void __cyg_profile_func_enter (void *this_fn, void *call_site);  
  2. void __cyg_profile_func_exit  (void *this_fn, void *call_site);  
其中第一个参数为当前函数的起始地址,第二个参数为返回地址,即caller函数中的地址。
这是什么意思呢?例如我们写了一个函数func_test(),定义如下:
[cpp] view plain copy
 
  1. static void func_test(v)  
  2. {  
  3.     /* your code... */  
  4. }  
那通过-finstrument-functions选项编译后,这个函数的定义就变成了:
[cpp] view plain copy
 
  1. static void func_test(v)  
  2. {  
  3.     __cyg_profile_func_enter(this_fn, call_site);  
  4.     /* your code... */  
  5.     __cyg_profile_func_exit(this_fn, call_site);  
  6. }  
我们可以按照自己的需要去实现这两个hook函数,这样我们就可以利用this_fn和call_site这两个参数大做文章。
例如下面这段代码:
[cpp] view plain copy
 
  1. instrfunc.c:   
  2. #include <stdio.h>  
  3.   
  4.   
  5. #define DUMP(func, call) \  
  6.     printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call)  
  7.   
  8.   
  9. void __attribute__((no_instrument_function))  
  10. __cyg_profile_func_enter(void *this_func, void *call_site)  
  11. {  
  12.     DUMP(this_func, call_site);  
  13. }  
  14.   
  15.   
  16. void __attribute__((no_instrument_function))  
  17. __cyg_profile_func_exit(void *this_func, void *call_site)  
  18. {  
  19.     DUMP(this_func, call_site);  
  20. }  
  21.   
  22.   
  23. int do_multi(int a, int b)  
  24. {  
  25.     return a * b;  
  26. }  
  27.   
  28.   
  29. int do_calc(int a, int b)  
  30. {  
  31.     return do_multi(a, b);  
  32. }  
  33.   
  34.   
  35. int main()  
  36. {  
  37.     int a = 4, b = 5;  
  38.     printf("result: %d\n", do_calc(a, b));  
  39.     return 0;  
  40. }  
这段代码中实现了两个hook函数,即打印出所在函数的函数地址以及返回地址。
编译代码:
[plain] view plain copy
 
  1. [zhenfg@ubuntu]code:$ gcc -finstrument-functions instrfunc.c -o instrfunc  
  2. [zhenfg@ubuntu]code:$ ./instrfunc   
  3. __cyg_profile_func_enter: func = 0x8048521, called by = 0xb75554e3  
  4. __cyg_profile_func_enter: func = 0x80484d8, called by = 0x8048562  
  5. __cyg_profile_func_enter: func = 0x804849a, called by = 0x8048504  
  6. __cyg_profile_func_exit: func = 0x804849a, called by = 0x8048504  
  7. __cyg_profile_func_exit: func = 0x80484d8, called by = 0x8048562  
  8. result: 20  
  9. __cyg_profile_func_exit: func = 0x8048521, called by = 0xb75554e3  
通过反汇编的代码(objdump -D instrfunc)可以看到,这些地址和函数的对应关系为:
[plain] view plain copy
 
  1. __cyg_profile_func_enter: func = 0x8048521(main), called by = 0xb75554e3  
  2. __cyg_profile_func_enter: func = 0x80484d8(do_calc), called by = 0x8048562(main)  
  3. __cyg_profile_func_enter: func = 0x804849a(do_multi), called by = 0x8048504(do_calc)  
  4. __cyg_profile_func_exit: func = 0x804849a(do_multi), called by = 0x8048504(do_calc)  
  5. __cyg_profile_func_exit: func = 0x80484d8(do_calc), called by = 0x8048562(main)  
  6. result: 20  
  7. __cyg_profile_func_exit: func = 0x8048521(main), called by = 0xb75554e3  
实际上这就给出了函数的调用关系。

如果不想跟踪某个函数,可以给该函数指定“no_instrument_function”属性。需要注意的是,__cyg_profile_func_enter()和__cyg_profile_func_exit()这两个hook函数是一定要加上“no_instrument_function”属性的,不然,自己跟踪自己就会无限循环导致程序崩溃,当然,也不能在这两个hook函数中调用其他需要被跟踪的函数。

得到一系列的地址看起来不太直观,我们更希望看到函数名,幸运的是,addr2line工具为我们提供了这种可能。我们先看一下addr2line的使用方法:
[plain] view plain copy
 
  1. [zhenfg@ubuntu]code:$ addr2line --help  
  2. Usage: addr2line [option(s)] [addr(s)]  
  3.  Convert addresses into line number/file name pairs.  
  4.  If no addresses are specified on the command line, they will be read from stdin  
  5.  The options are:  
  6.   @<file>                Read options from <file>  
  7.   -a --addresses         Show addresses  
  8.   -b --target=<bfdname>  Set the binary file format  
  9.   -e --exe=<executable>  Set the input file name (default is a.out)  
  10.   -i --inlines           Unwind inlined functions  
  11.   -j --section=<name>    Read section-relative offsets instead of addresses  
  12.   -p --pretty-print      Make the output easier to read for humans  
  13.   -s --basenames         Strip directory names  
  14.   -f --functions         Show function names  
  15.   -C --demangle[=style]  Demangle function names  
  16.   -h --help              Display this information  
  17.   -v --version           Display the program's version  
首先要注意,使用addr2line工具时,需要用gcc的“-g”选项编译程序增加调试信息。
同样是上面的程序,我们加上-g选项再编译一次:
[plain] view plain copy
 
  1. [zhenfg@ubuntu]code:$ gcc -g -finstrument-functions instrfunc.c -o instrfunc  
  2. [zhenfg@ubuntu]code:$ ./instrfunc   
  3. __cyg_profile_func_enter: func = 0x8048521, called by = 0xb757d4e3  
  4. __cyg_profile_func_enter: func = 0x80484d8, called by = 0x8048562  
  5. __cyg_profile_func_enter: func = 0x804849a, called by = 0x8048504  
  6. __cyg_profile_func_exit: func = 0x804849a, called by = 0x8048504  
  7. __cyg_profile_func_exit: func = 0x80484d8, called by = 0x8048562  
  8. result: 20  
  9. __cyg_profile_func_exit: func = 0x8048521, called by = 0xb757d4e3  
使用addr2line尝试查找0x8048504地址所在的函数:
[plain] view plain copy
 
  1. [zhenfg@ubuntu]code:$ addr2line -e instrfunc -a 0x8048504 -fp -s  
  2. 0x08048504: do_calc at instrfunc.c:25  
这样一来,就可以通过gcc的“-finstrument-functions”选项结合addr2line工具,方便的对一个程序中的函数进行跟踪。并且既然我们可以自己实现hook函数,那不仅仅可以用来跟踪函数调用关系,你可以在hook函数中添加自己想做的事情,例如添加一些统计信息。
另外,我们知道__builtin_return_address(level)宏可以获得不同层级的函数返回地址,但是在某些体系架构(如mips)中,__builtin_return_address(level)只能获得当前函数的直接调用者的地址,即level只能是0,那这时,就可使用上述方法来跟踪函数调用关系(mips中竟然能用,确实有些小吃惊)。

接下来可以看一下gcc是如何将hook函数嵌入各个函数中的,以反汇编代码中的do_multi()函数为例(这是mips的汇编代码),在mips中,ra寄存器用来存储返回地址,a0-a3用来做函数参数。
[cpp] view plain copy
 
  1. 004006c8 <do_multi>:  
  2.   4006c8:   27bdffd8    addiu   sp,sp,-40  
  3.   4006cc:   afbf0024    sw  ra,36(sp)   ;;存储ra寄存器(返回地址)的值  
  4.   4006d0:   afbe0020    sw  s8,32(sp)  
  5.   4006d4:   afb1001c    sw  s1,28(sp)  
  6.   4006d8:   afb00018    sw  s0,24(sp)  
  7.   4006dc:   03a0f021    move    s8,sp  
  8.   4006e0:   03e08021    move    s0,ra   ;;s0 = ra  
  9.   4006e4:   afc40028    sw  a0,40(s8)  
  10.   4006e8:   afc5002c    sw  a1,44(s8)  
  11.   4006ec:   02001021    move    v0,s0   ;;v0 = s0  
  12.   4006f0:   3c030040    lui v1,0x40  
  13.   4006f4:   246406c8    addiu   a0,v1,1736  ;;将本函数的地址赋值给a0寄存器  
  14.   4006f8:   00402821    move    a1,v0       ;;将返回地址ra的值赋值给a1寄存器  
  15.   4006fc:   0c100188    jal 400620 <__cyg_profile_func_enter> ;;调用hook函数  
  16.   400700:   00000000    nop  
  17.   400704:   8fc30028    lw  v1,40(s8)  
  18.   400708:   8fc2002c    lw  v0,44(s8)  
  19.   40070c:   00000000    nop  
  20.   400710:   00620018    mult    v1,v0  
  21.   400714:   00008812    mflo    s1  
  22.   400718:   02001021    move    v0,s0  
  23.   40071c:   3c030040    lui v1,0x40  
  24.   400720:   246406c8    addiu   a0,v1,1736  ;;将本函数的地址赋值给a0寄存器  
  25.   400724:   00402821    move    a1,v0       ;;将返回地址ra的值赋值给a1寄存器  
  26.   400728:   0c10019d    jal 400674 <__cyg_profile_func_exit> ;;调用hook函数  
  27.   40072c:   00000000    nop  
  28.   400730:   02201021    move    v0,s1  
  29.   400734:   03c0e821    move    sp,s8  
  30.   400738:   8fbf0024    lw  ra,36(sp)   ;;恢复ra寄存器(返回地址)的值  
  31.   40073c:   8fbe0020    lw  s8,32(sp)  
  32.   400740:   8fb1001c    lw  s1,28(sp)  
  33.   400744:   8fb00018    lw  s0,24(sp)  
  34.   400748:   27bd0028    addiu   sp,sp,40  
  35.   40074c:   03e00008    jr  ra  
  36.   400750:   00000000    nop  
上述反汇编的代码中,使用“-finstrument-functions”选项编译程序所增加的指令都已注释出来,实现没什么复杂的,在函数中获得自己的地址和上一级caller的地址并不是什么难事,然后将这两个地址传给__cyg_profile_func_enter和__cyg_profile_func_exit就好了。
【作者】张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
上一篇:首个全球智慧城市在线社区启动 将锁定对城市发展构成挑战的困难


下一篇:一文详解 m3u8 视频格式与分析视频秒开优化