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

简介: 转自:http://blog.csdn.net/jasonchen_gbd/article/details/44044899 版权声明:本文为博主原创文章,转载请附上原博链接。 GCC Function instrumentation机制可以用来跟踪函数的调用关系,在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
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
目录
相关文章
|
算法 Unix Linux
【C/C++ 实用工具】性能分析工具一览
【C/C++ 实用工具】性能分析工具一览
985 0
|
11月前
|
机器学习/深度学习 人工智能 监控
AI视频监控技术的核心优势与实践
AI视频监控技术结合了计算机视觉、深度学习和大数据分析,能够实时分析监控画面,识别异常行为和场景变化。其核心在于从“被动记录”转型为“主动识别”,提升监控效率并减少安全隐患。主要应用场景包括泳池管理、健身器械区域、人员密度预警和异常事件检测。系统架构支持多种摄像头设备,采用边缘计算和Docker部署,具备实时性、高准确率和扩展性等优势。未来将优化复杂场景适应性和实时计算负载,进一步提高系统性能。
2653 7
|
Kubernetes Java API
Kubernetes官方java客户端之六:OpenAPI基本操作
kubernetes官方java客户端的第二个基本能力:基于OpenAPI的功能接口
1107 0
Kubernetes官方java客户端之六:OpenAPI基本操作
|
Java 微服务 Spring
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
文章介绍了如何利用Spring Cloud Alibaba快速构建大型电商系统的分布式微服务,包括服务限流降级等主要功能的实现,并通过注解和配置简化了Spring Cloud应用的接入和搭建过程。
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
|
弹性计算 运维 Kubernetes
阿里云ECS与混合云策略结合,提供云上云下无缝对接,提升业务灵活性和运维效率。
【7月更文挑战第3天】阿里云ECS与混合云策略结合,提供云上云下无缝对接,提升业务灵活性和运维效率。ECS支持多种计费模式和先进架构,保证低延迟计算。混合云融合公有云灵活性与私有云安全,实现资源最优配置。通过VPC互通、应用迁移、数据同步实践,确保安全合规,助力企业数字化转型。阿里云服务展示技术实力,支持企业在混合云时代抓住机遇。
481 3
|
存储 Kubernetes Docker
Kubernetes(K8S)集群管理Docker容器(概念篇)
Kubernetes(K8S)集群管理Docker容器(概念篇)
|
监控 Linux 编译器
Linux C++ 定时器任务接口深度解析: 从理论到实践
Linux C++ 定时器任务接口深度解析: 从理论到实践
446 2
|
算法 决策智能
【MATLAB】LMD分解+FFT+HHT组合算法
【MATLAB】LMD分解+FFT+HHT组合算法
521 0
|
Python
Python中使用`requests`库进行异常处理与调试
【4月更文挑战第12天】在Python的网络编程中,使用`requests`库发送HTTP请求时,经常会遇到各种异常情况,如网络连接错误、请求超时、服务器错误等。为了确保程序的健壮性和稳定性,我们需要对这些异常进行妥善处理,并进行必要的调试。本文将详细介绍如何在Python中使用`requests`库进行异常处理与调试。
1494 2
|
Kubernetes 应用服务中间件 nginx
使用kubeadm搭建生产环境的多master节点k8s高可用集群
使用kubeadm搭建生产环境的多master节点k8s高可用集群
1287 0