zoukankan      html  css  js  c++  java
  • 使用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 ", __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 ", 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就好了。
  • 相关阅读:
    rest-assured : Restful API 测试利器
    Bootstrap 模态框 + iframe > 打开子页面 > 数据传输/关闭模态框
    angular js 自定义js错误处理(Angularjs js error handler)
    Spring Boot + Bootstrap 出现"Failed to decode downloaded font"和"OTS parsing error: Failed to convert WOFF 2.0 font to SFNT"
    Use Apache HttpClient to Post json data
    小程序-走迷宫
    Java getResourceAsStream() 方法会缓存文件的问题
    【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便
    DIV布局-高度不同DIV,自动换行并对齐
    Excel表格常用的函数,留着备用
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/6297690.html
Copyright © 2011-2022 走看看