使用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就好了。

时间: 2024-09-20 14:28:33

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

函数定位-Sublime Text 3 有没有好用的函数跟踪插件

问题描述 Sublime Text 3 有没有好用的函数跟踪插件 小弟主要用于PHP和Python开发. 安装过了ctags,在自己的项目里,可能是项目过于复杂,生成的.ctags文件足足有12G之大,耗1+小时.遂放弃之. 再使用SublimeCodeIntel,跟踪自定义函数的目的可以满足,但是反应太慢,基本要操作2-5+次才能定位. ST3的开发环境速度真是没有的说,在项目大的情况下,不可能记住每一个函数,如果可以快速的定位,将大大的提高开发效率.求高手指点,万分感谢. 解决方案 按 f1

GCC编译器优化选项分析及具体优化了什么

问题 在使用gcc作为编译器.在设定编译条件时,在debug模式下生成的程序正常,但是在release模式下往往会出现很多种预料之外的结果,尤其在嵌入式环境中,程序在板子上运行的时候,问题就愈发明显. 为了了解具体为什么造成该问题,对两种模式下的配置做了对比 debug模式编译器参数为-O0 -g –Wall release模式编译器参数为-O2 -g –Wall 通过对比可以发现两种模式主要的不同在于编译器优化程度不同,那么编译器在两种优化下究竟做了什么优化那?现在我们来看看gcc编译器的优化

2.3. Functions (函数)

2.3.1. 匿名函数(Anonymous functions) 匿名函数(Anonymous functions)也叫闭包函数(closures)允许 临时创建一个没有指定名称的函数. 闭包函数也可以作为变量的值来使用. <?php $put = function($name) { printf("%s\r\n", $name); }; $put('World'); $put('PHP'); ?> <?php $aaa = 111; $func = function

gcc编译选项总结

常用编译选项 [plain] view plain copy print? #--------------------------------------------------------------------------------   [介绍]       gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步       1.预处理,生成.i的文件[预处理器cpp]    2.将预处理后的文件不转换成汇编语言,生成文件.s[编译器eg

GCC 编译使用动态链接库和静态链接库的方法

1 库的分类 根据链接时期的不同,库又有静态库和动态库之分. 静态库是在链接阶段被链接的,所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行. 有别于静态库,动态库的链接是在程序执行的时候被链接的.所以,即使程序编译完,库仍须保留在系统上,以供程序运行时调用. 2 静态库和动态库的比较 链接静态库其实从某种意义上来说也是一种粘贴复制,只不过它操作的对象是目标代码而不是源码而已.因为静态库被链接后库就直接嵌入可执行文件中了,这样就带来了两个问题. 首先就是系统空间被浪费了.

GCC编译器使用

一.GCC简介 通常所说的GCC是GUN Compiler Collection的简称,除了编译程序之外,它还含其他相关工具, 所以它能把易于人类使用的高级语言编写的源代码构建成计算机能够直接执行的二进制代码.GCC是Linux平台下最常用的编译程序,它是Linux平台编 译器的事实标准.同时,在Linux平台下的嵌入式开发领域,GCC也是用得最普遍的一种编译器.GCC之所以被广泛采用,是因为它能支持各种不同的目标体系结构.例如,它既支持基于宿主的开发(简单讲就是要为某平台编译程序,就在该平台上

gcc参数详解

这篇文档是我的关于gcc参数的笔记,我很怀念dos年代我用小本子,纪录所有的dos 命令的参数.哈哈,下面的东西可能也不是很全面,我参考了很多的书,和gcc的帮助.不全的原因是,有可能我还没有看到这个参数, 另一种原因是,我可能还不会用它 不过,我会慢慢的补齐的.哈哈 如果你要转在本文章请保留我email(pianopan@beeship.com)和文章的全面性.  [介绍]  gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步  1.预处

GCC参数祥解

gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步  1.预处理,生成.i的文件[预处理器cpp] 2.将预处理后的文件不转换成汇编语言,生成文件.s[编译器egcs] 3.有汇编变为目标代码(机器代码)生成.o的文件[汇编器as] 4.连接目标代码,生成可执行程序[链接器ld] [参数详解] -x language filename  设定文件所使用的语言,使后缀名无效,对以后的多个有效.也就是根据约定C语言的后 缀名称是.c的,而C++

《嵌入式 Linux C 语言应用程序设计(修订版)》——2.3 嵌入式Linux编译器GCC的使用

2.3 嵌入式Linux编译器GCC的使用 2.3.1 GCC概述 作为自由软件的旗舰项目,Richard Stallman在十多年前刚开始写作GCC的时候,还只是仅仅把它当作一个C程序语言的编译器,GCC的意思也只是GNU C Compiler而已. 经过了这么多年的发展,GCC已经不仅仅能支持C语言,它现在还支持Ada语言.C++语言.Java语言.Objective C语言.PASCAL语言.COBOL语言,并支持函数式编程和逻辑编程的Mercury语言等.而GCC也不再单只GNU C语言