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

版权声明:本文为博主原创文章,转载请附上原博链接。

GCC Function instrumentation机制可以用来跟踪函数的调用关系,在gcc中对应的选项为“-finstrument-functions”。可查看gcc的man page来获取更详细信息。
编译时如果为gcc加上“-finstrument-functions”选项,那在每个函数的入口和出口处会各增加一个额外的hook函数的调用,增加的这两个函数分别为:

  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(),定义如下:

  1. static void func_test(v)
  2. {
  3. /* your code... */
  4. }

那通过-finstrument-functions选项编译后,这个函数的定义就变成了:

  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这两个参数大做文章。
例如下面这段代码:

  1. instrfunc.c:
  2. #include <stdio.h>
  3. #define DUMP(func, call) \
  4. printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call)
  5. void __attribute__((no_instrument_function))
  6. __cyg_profile_func_enter(void *this_func, void *call_site)
  7. {
  8. DUMP(this_func, call_site);
  9. }
  10. void __attribute__((no_instrument_function))
  11. __cyg_profile_func_exit(void *this_func, void *call_site)
  12. {
  13. DUMP(this_func, call_site);
  14. }
  15. int do_multi(int a, int b)
  16. {
  17. return a * b;
  18. }
  19. int do_calc(int a, int b)
  20. {
  21. return do_multi(a, b);
  22. }
  23. int main()
  24. {
  25. int a = 4, b = 5;
  26. printf("result: %d\n", do_calc(a, b));
  27. return 0;
  28. }

这段代码中实现了两个hook函数,即打印出所在函数的函数地址以及返回地址。
编译代码:

  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)可以看到,这些地址和函数的对应关系为:

  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的使用方法:

  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选项再编译一次:

  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地址所在的函数:

  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用来做函数参数。

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

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

  1. GCC编译命令常用选项

    GCC是GUN Compiler Collection的简称,除编译程序外,还包含其他相关工具.GCC可将高级语言编写的源代码构建成计算机直接执行的二进制代码.GCC是Linux平台下最常用的编译程序 ...

  2. go1.6.2 linux/amd64 的一个bug: gcc: 无法识别的选项‘-no-pie’

    如果你的linux是中文版的. 即:执行 # echo $LANG 命令,返回的是 zh_CN.UTF-8 则会出现这个bug, 如果是 en_US.UTF-8 则没有这个问题.   这个bug的一个 ...

  3. 前端测试框架Jest系列教程 -- Global Functions(全局函数)

    写在前面: Jest中定义了很多全局性的Function供我们使用,我们不必再去引用别的包来去实现类似的功能,下面将列举Jest中实现的全局函数. Jest Global Functions afte ...

  4. Ubuntu gcc错误:对'log'等函数未定义的引用

    Ubuntu gcc错误:对'log'等函数未定义的引用 a.c #include <stdio.h>#include <math.h>int main(){    float ...

  5. gcc/g++ -O 优化选项说明

    查查gcc手册就知道了,每个编译选项都控制着不同的优化选项 下面从网络上copy过来的,真要用到这些还是推荐查阅手册 -O设置一共有五种:-O0.-O1.-O2.-O3和-Os. 除了-O0以外,每一 ...

  6. Jmeter_选项_函数助手对话框_CSVRead

    1.之前有写过Jmeter_请求原件之参数化CSV 2.这次是使用函数助手 CSVRead去进行处理 3.作用注册10个账户 4.线程组设置,10个线程进行,因为是注册10个账户,如果线程设置为1,循 ...

  7. Xdebug文档(四)函数跟踪

    Xdebug能让你把所有函数调用,包括参数和返回值以不同的格式记录到文件中. 这些号称“函数跟踪”功能能帮助你面对一个新应用程序,亦或者在程序运行时你想弄清楚它在做什么.函数跟踪功能可以选择性地显示函 ...

  8. Linux TTY函数跟踪

    1. 介绍 本文介绍了TTY打开.TTY读和TTY写操作的函数跟踪过程 2. 示例 下面是一个简单的Linux TTY打开和读写过程 #include <termios.h> #inclu ...

  9. Linux下gcc编译控制动态库导出函数小结

    根据说明文档“How To Write Shared Libraries"介绍, 有四种方法: 1. 在方法声明定义时,加修饰:__attribute__((visibility(" ...

随机推荐

  1. 使用vs2013打开VS2015的工程文件的解决方案(适用于大多数vs低版本打开高版本)

    前言:重装系统前我使用的是vs2015(有点装*),由于使用2015实在在班上太另类了, 导致我想在其他同学的vs下看一看我写的代码都无法达成! 而且最关键的是交作业的时候,老师的2013也没有办法打 ...

  2. token验证机制

    最近在vue-cli项目实现登录的过程中用到了token验证,在此总结如下 1. 登录时,客户端通过用户名与密码请求登录 2. 服务端收到请求去验证用户名与密码 3. 验证通过,服务端会签发一个Tok ...

  3. 初级练手项目——用Python一步一步实现“智能”贪吃蛇!

    贪吃蛇作为一款经典的小游戏,想必大家一定并不陌生,今天我们就来用Python来设计与实现贪吃蛇游戏,回味一下童年的快乐.快跟着小编来看看吧! ​ 基本环境配置 ●版本:Python3 ●系统:Wind ...

  4. JAVA基础篇—String和StringBuffer

    区别: (1)String类对象为不可变对象,一旦你修改了String对象的值,隐性重新创建了一个新的对象,释放原String对象,StringBuffer类对象为可修改对象,可以通过append() ...

  5. Developing for nRF52810(转载)

    Table of Contents Introduction Hardware emulation of nRF52810 Limitations Software emulation of nRF5 ...

  6. ubuntu 设置Path 开机启动脚本

    vim /etc/rc.local export PATH=$PATH:/work/apps/node-v0.12.7-linux-x64/bin:/work/apps/ledisdb/binexpo ...

  7. java append方法

    JAVA 中 Stringbuffer 有append()方法  Stringbuffer其实是动态字符串数组  append()是往动态字符串数组添加,跟“xxxx”+“yyyy”相当那个‘+’号  ...

  8. Oracle 表空间的日常维护与管理

    目录 Oracle 表空间的日常维护与管理 1.创建数据表空间 2.创建临时表空间 3.创建 UNDO 表空间 4.表空间的扩展与修改大小 5.表空间重命名 6.表空间的删除 7.更改表空间的读写模式 ...

  9. 用asp.net+Jquery+Ajax+sqlserver编写的 (英语六级记单词)

    开始(英语对程序员的重要性引用) 出处 英语的重要性已经毋庸置疑,对于程序员来说更甚,一些最新的技术资料是英文的,如果想进入外企英语也是一个很重要的条件.对于程序员来说怎样学习好英语,在此谈一下我的一 ...

  10. python + selenium - selenium常用元素定位

    1. Chrome定位工具 打开Chrome浏览器,按F12会弹出开发者工具选项,选择Elements: 1)鼠标点击最左边箭头 2)鼠标滑动到页面中你要操作的元素,单击一下 3)对应的html元素内 ...