如何在C中跟踪函数调用?

在不修改源代码的情况下,如何调用调用哪些函数以及使用什么参数调用某些函数(例如下例中的func100)。 我想输出如下:

enter func100(p1001=xxx,p1002=xxx) enter func110(p1101=xxx,p1102=xxx) exit func110(p1101=xxx,p1102=xxx) enter func120(p1201=xxx,p1202=xxx,p1203=xxx) enter func121(p1211=xxx) exit func121(p1211=xxx) exit func120(p1201=xxx,p1202=xxx,p1203=xxx) exit func100(p1001=xxx,p1002=xxx) 

这可行吗? 或者最少修改源代码的解决方案是什么?

如果使用gcc ,则可以使用-finstrument-functions编译标志。 它添加了在函数进入/退出时调用两个函数__cyg_profile_func_enter__cyg_profile_func_exit代码。

你需要实现这些function,做你想做的事。 确保编译它们没有标志或attribute((no_instrument_function)) ,所以他们不会试图自己调用。

函数的第二个参数是指向调用站点的指针(即调用函数中的返回地址)。 你可以用%p打印它,但它有点难以使用。 您可以使用nm来计算包含此地址的实际函数。

你无法以这种方式获得函数参数。

使用GNU C库,您可以使用backtrace模块。 这是一个例子:

 #include  #include  #include  void handler(char *caller) { void *array[10]; size_t size; printf("Stack Trace Start for %s\n",caller); size = backtrace(array, 10); backtrace_symbols_fd(array, size, 2); printf("Stack Trace End\n"); } void car() { handler("car()"); printf("Continue Execution"); } void baz() {car(); } void bar() { baz(); } void foo() { bar(); } int main(int argc, char **argv) { foo(); } 

使用-g -rdynamic编译器选项进行编译以加载符号

 gcc -g -rdynamic Test1.c -o Test 

您将看到类似的输出

 Stack Trace Start for car() ./Test(handler+0x2d)[0x80486f1] ./Test(car+0x12)[0x804872e] ./Test(baz+0xb)[0x8048747] ./Test(bar+0xb)[0x8048754] ./Test(foo+0xb)[0x8048761] ./Test(main+0xb)[0x804876e] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xe7)[0x126e37] ./Test[0x8048631] Stack Trace End Continue Execution in car 

您可以编写此处理程序函数,并在任何时间从程序中的任何位置进行调用。 请记住根据需要增加array大小。

如果你在Linux上, callgrind可能会有所帮助。 它基本上收集了您要查找的内容的统计信息,因此,它可能提供访问其原始数据的方法。

使用调试器设置具有关联操作的断点。 例如,在gdb中,您可以在要跟踪的每个函数的开头和结尾设置断点。 您可以为每个断点提供执行命令,例如:

 printf("Enter func100(p1001=%d, p1002=%d)", p1001, p1002) 

然后,当您运行程序(在调试器中)时,它将打印每个命令的文本以及相关参数。

看一下gdb的相关文档 。

有时我必须跟踪大量的函数调用,即使对于我没有任何控制的外部库,或者我不想修改。

前段时间,我意识到你可以组合gdb的正则表达式断点(常规断点也可以),然后只需执行一组命令就可以在每次触发这些断点时运行。 请参阅: http : //www.ofb.net/gnu/gdb/gdb_35.html

例如,如果要跟踪以“MPI_”前缀开头的所有函数,可以执行以下操作:

 (gdb) rbreak MPI_ [...] (gdb) command 1-XX (gdb) silent (gdb) bt 1 (gdb) echo \n\n (gdb) continue (gdb) end 

静默命令用于在发现断点时隐藏gdb消息。 我通常打印几行空行,以便更容易阅读。

然后,您只需运行程序:(gdb)运行

程序开始运行后,gdb将打印N个最顶层的回溯级别。

 #0 0x000000000040dc60 in MPI_Initialized@plt () #0 PMPI_Initialized (flag=0x7fffffffba78) at ../../src/mpi/init/initialized.c:46 #0 0x000000000040d9b0 in MPI_Init_thread@plt () #0 PMPI_Init_thread (argc=0x7fffffffbe78, argv=0x7fffffffbde0, required=3, provided=0x7fffffffba74) at ../../src/mpi/init/initthread.c:946 #0 0x000000000040e390 in MPI_Comm_rank@plt () #0 PMPI_Comm_rank (comm=1140850688, rank=0x7fffffffba7c) at ../../src/mpi/comm/comm_rank.c:53 #0 0x000000000040e050 in MPI_Type_create_struct@plt () #0 PMPI_Type_create_struct (count=3, array_of_blocklengths=0x7fffffffba90, array_of_displacements=0x7fffffffbab0, array_of_types=0x7fffffffba80, newtype=0x69de20) at ../../src/mpi/datatype/type_create_struct.c:116 #0 0x000000000040e2a0 in MPI_Type_commit@plt () #0 PMPI_Type_commit (datatype=0x69de20) at ../../src/mpi/datatype/type_commit.c:75 

如果您需要更详细的信息,也可以打印给定断点的局部变量,只需在commandend之间插入更多命令。

额外提示:将所有这些添加到.gdbinit文件中,并将执行内容.gdbinit到文件中。

我也遇到了这个具有良好函数调用跟踪的问题。 因此,我编写了一个Python GDB脚本( https://gist.github.com/stettberger/e6f2fe61206471e22e9e6f1926668093 ),它在每个有趣的函数(由环境变量TRACE_FUNCTION定义)上设置断点。 然后GDB调用python函数,该函数解码框架及其所有参数。 如果遇到指针,它会尝试取消引用它,因此使用参数将函数调用跟踪打印到TRACE_FILE(默认值:/ tmp / log)。 对于以下程序

 #include  struct foo { int a; struct foo * next; }; int fib(int a, struct foo *b) { if (a <= 1) return 1; printf("%d\n", a); return fib(a-1, 0)+fib(a-2, 0); } int main() { struct foo b = {23, 0}; return fib(5, &b); } 

我得到一个详细的跟踪,其中每一行都是一个可以用eval()读取的python元组:

 ('call', None, 1, 'main', 'main', {}) ('call', 1, 2, 'fib', 'fib', {'a': {'type': 'int', 'value': 5}, 'b': {'type': 'struct foo *', 'value': 140737488344320, 'deref': {'type': 'struct foo', 'value': {'a': {'type': 'int', 'value': 23}, 'next': {'type': 'struct foo *', 'value': 0, 'deref': None}}}}}) ('call', 2, 3, 'fib', 'fib', {'a': {'type': 'int', 'value': 4}, 'b': {'type': 'struct foo *', 'value': 0, 'deref': None}}) .... ('return', 'fib', 2, {'type': 'int', 'value': 8}) ('exit', 8) 

要点包含有关日志文件格式的更多信息。

您可以查看由apache基金会托管的项目log4cxx。 我知道log4j,java变体允许你设置灵敏度,你可以跟踪在程序中完成的每一件事。 也许c ++变体是相同的,但有几种选择 – 有面向方面的c ++编译器,你可以在所有函数中定义一个方面,并让它捕获并打印变量。 另一种方法是使用调试器。

总结一下:debugger,log4cxx或AOP

如果使用动态模块,可以使用命令ltrace获取此信息。 您甚至可以使用-l标志指定监视库