用GCC来跟踪程序的函数调用关系
2011-12-07 23:49
295 查看
GCC就像一个巨大的宝藏,只要你愿意花时间,总能淘到好东西。
在看一些大中型的软件的源代码时,你是不是非常希望有一个工具能够方便的生成各个函数之间的调用关系图呢?为了实现这个目标,你可以通过对源代码进行静态扫描得到函数的调用关系,但是你无法通过这种方法获得更多的信息,(如:对某个函数的调用次数,被调用的函数执行了多长时间等,这些信息对于软件的优化具有很好的参考价值)除了静态扫描之外,还存在一些动态的方法,即在程序的运行过程中记录相关的信息,不过这些动态的方法通常都需要有编译器的支持,通过编译器在编译的过程中插入相应的代码。下面简单的介绍一下,GCC的function instrumentation机制。
简单的来说,gcc function instrumentation就是在每个函数调用之前调用一个名为__cyg_profile_func_enter的函数,在函数调用介绍的时候调用__cyg_profile_func_exit。这两个函数在gcc看来只是两个普通的函数,是可以由用户自己进行定制的。下面先看个例子:
#include <stdio.h>
#define DUMP(func, call) \
printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call)
void __attribute__((__no_instrument_function__))
__cyg_profile_func_enter(void *this_func, void *call_site)
{
DUMP(this_func, call_site);
}
void __attribute__((__no_instrument_function__))
__cyg_profile_func_exit(void *this_func, void *call_site)
{
DUMP(this_func, call_site);
}
main()
{
puts("Hello World!");
return 0;
}
编译与运行:
$ gcc -finstrument-functions hello.c -o hello
$ ./hello
__cyg_profile_func_enter: func = 0x8048468, called by = 0xb7e36ebc
Hello World!
__cyg_profile_func_exit: func = 0x8048468, called by = 0xb7e36ebc
呵呵,是不是很神奇,只需要在软件中简单加入__cyg_profile_func_enter __cyg_profile_func_exit这两个函数的定义,然后再编译的时候加上-finstrument-functions选项,就可以在每个函数调用前后调用这两个函数获取你所感兴趣的信息。为了对其具体的实现细节有所了解,可以通过”objdump -d hello“来看下程序反汇编之后的结果。
08048488 <main>:
8048488: 8d 4c 24 04 lea 0x4(%esp),%ecx
804848c: 83 e4 f0 and $0xfffffff0,%esp
804848f: ff 71 fc pushl 0xfffffffc(%ecx)
8048492: 55 push %ebp
8048493: 89 e5 mov %esp,%ebp
8048495: 53 push %ebx
8048496: 51 push %ecx
8048497: 83 ec 10 sub $0x10,%esp
804849a: 8b 45 04 mov 0x4(%ebp),%eax
804849d: 89 44 24 04 mov %eax,0x4(%esp)
80484a1: c7 04 24 88 84 04 08 movl $0x8048488,(%esp)
80484a8: e8 87 ff ff ff call 8048434 <__cyg_profile_func_enter>
80484ad: c7 04 24 03 86 04 08 movl $0x8048603,(%esp)
80484b4: e8 8f fe ff ff call 8048348 <puts@plt>
80484b9: bb 00 00 00 00 mov $0x0,%ebx
80484be: 8b 45 04 mov 0x4(%ebp),%eax
80484c1: 89 44 24 04 mov %eax,0x4(%esp)
80484c5: c7 04 24 88 84 04 08 movl $0x8048488,(%esp)
80484cc: e8 8d ff ff ff call 804845e <__cyg_profile_func_exit>
80484d1: 89 d8 mov %ebx,%eax
80484d3: 83 c4 10 add $0x10,%esp
80484d6: 59 pop %ecx
80484d7: 5b pop %ebx
80484d8: 5d pop %ebp
80484d9: 8d 61 fc lea 0xfffffffc(%ecx),%esp
80484dc: c3 ret
从上面的汇编代码可以看出,gcc是在进入函数完成堆栈的初始化之后调用__cyg_profile_func_enter的,在函数返回清理堆栈之前调用__cyg_profile_func_exit的。
经过上面的步骤,我们还只能获取到函数调用时的信息,还没有获取到函数之间的调用关系,但是明白了上述的原理之后,参看参看参考资料[1]就能够很容易的实现目标了。
参考资料
[1] http://www.ibm.com/developerworks/cn/linux/l-graphvis/
[2] http://blog.linux.org.tw/~jserv/archives/001870.html
原文地址:/article/9346081.html
在看一些大中型的软件的源代码时,你是不是非常希望有一个工具能够方便的生成各个函数之间的调用关系图呢?为了实现这个目标,你可以通过对源代码进行静态扫描得到函数的调用关系,但是你无法通过这种方法获得更多的信息,(如:对某个函数的调用次数,被调用的函数执行了多长时间等,这些信息对于软件的优化具有很好的参考价值)除了静态扫描之外,还存在一些动态的方法,即在程序的运行过程中记录相关的信息,不过这些动态的方法通常都需要有编译器的支持,通过编译器在编译的过程中插入相应的代码。下面简单的介绍一下,GCC的function instrumentation机制。
简单的来说,gcc function instrumentation就是在每个函数调用之前调用一个名为__cyg_profile_func_enter的函数,在函数调用介绍的时候调用__cyg_profile_func_exit。这两个函数在gcc看来只是两个普通的函数,是可以由用户自己进行定制的。下面先看个例子:
#include <stdio.h>
#define DUMP(func, call) \
printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call)
void __attribute__((__no_instrument_function__))
__cyg_profile_func_enter(void *this_func, void *call_site)
{
DUMP(this_func, call_site);
}
void __attribute__((__no_instrument_function__))
__cyg_profile_func_exit(void *this_func, void *call_site)
{
DUMP(this_func, call_site);
}
main()
{
puts("Hello World!");
return 0;
}
编译与运行:
$ gcc -finstrument-functions hello.c -o hello
$ ./hello
__cyg_profile_func_enter: func = 0x8048468, called by = 0xb7e36ebc
Hello World!
__cyg_profile_func_exit: func = 0x8048468, called by = 0xb7e36ebc
呵呵,是不是很神奇,只需要在软件中简单加入__cyg_profile_func_enter __cyg_profile_func_exit这两个函数的定义,然后再编译的时候加上-finstrument-functions选项,就可以在每个函数调用前后调用这两个函数获取你所感兴趣的信息。为了对其具体的实现细节有所了解,可以通过”objdump -d hello“来看下程序反汇编之后的结果。
08048488 <main>:
8048488: 8d 4c 24 04 lea 0x4(%esp),%ecx
804848c: 83 e4 f0 and $0xfffffff0,%esp
804848f: ff 71 fc pushl 0xfffffffc(%ecx)
8048492: 55 push %ebp
8048493: 89 e5 mov %esp,%ebp
8048495: 53 push %ebx
8048496: 51 push %ecx
8048497: 83 ec 10 sub $0x10,%esp
804849a: 8b 45 04 mov 0x4(%ebp),%eax
804849d: 89 44 24 04 mov %eax,0x4(%esp)
80484a1: c7 04 24 88 84 04 08 movl $0x8048488,(%esp)
80484a8: e8 87 ff ff ff call 8048434 <__cyg_profile_func_enter>
80484ad: c7 04 24 03 86 04 08 movl $0x8048603,(%esp)
80484b4: e8 8f fe ff ff call 8048348 <puts@plt>
80484b9: bb 00 00 00 00 mov $0x0,%ebx
80484be: 8b 45 04 mov 0x4(%ebp),%eax
80484c1: 89 44 24 04 mov %eax,0x4(%esp)
80484c5: c7 04 24 88 84 04 08 movl $0x8048488,(%esp)
80484cc: e8 8d ff ff ff call 804845e <__cyg_profile_func_exit>
80484d1: 89 d8 mov %ebx,%eax
80484d3: 83 c4 10 add $0x10,%esp
80484d6: 59 pop %ecx
80484d7: 5b pop %ebx
80484d8: 5d pop %ebp
80484d9: 8d 61 fc lea 0xfffffffc(%ecx),%esp
80484dc: c3 ret
从上面的汇编代码可以看出,gcc是在进入函数完成堆栈的初始化之后调用__cyg_profile_func_enter的,在函数返回清理堆栈之前调用__cyg_profile_func_exit的。
经过上面的步骤,我们还只能获取到函数调用时的信息,还没有获取到函数之间的调用关系,但是明白了上述的原理之后,参看参看参考资料[1]就能够很容易的实现目标了。
参考资料
[1] http://www.ibm.com/developerworks/cn/linux/l-graphvis/
[2] http://blog.linux.org.tw/~jserv/archives/001870.html
原文地址:/article/9346081.html
相关文章推荐
- 用GCC来跟踪程序的函数调用关系
- Linux C++ 跟踪程序奔溃及函数调用关系
- pvtrace和Graphviz实现对linux下C程序的函数调用跟踪
- 源码分析:静态分析 C 程序函数调用关系图
- pvtrace和Graphviz实现对linux下C程序的函数调用跟踪
- c c++ 函数入口和出口的hook(gcc 编译选项),然后打印出函数调用关系的方法
- [授权发表]源码分析:动态分析 C 程序函数调用关系
- 源码分析:静态分析 C 程序函数调用关系图
- 跟踪程序在Linux下的执行的函数调用
- [授权发表]源码分析:静态分析 C 程序函数调用关系
- Intel和ARM GCC程序函数调用的参数传递
- 用CodeViz绘制函数调用关系图(call graph)
- 计算机系统学习2:程序的机器级表示之函数调用
- Delphi调用外部程序函数:WinExec() 和ShellExecute详解
- Java 中的 int 与 Integer 用于 List<Integer> 时,以及通过打印变量检測程序执行和函数调用次数计数
- 在C++ 程序中调用被C编译器编译后的函数,为什么要加extern “C”?
- 分析函数调用关系图(call graph)的几种方法
- java程序调用序列,存储过程,函数等
- ALSA驱动、设备函数调用关系
- 程序的内存布局——函数调用栈的那点事