And*_*rew 11 c trace call-hierarchy
在不修改源代码的情况下,当调用某个函数(例如下例中的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)
Run Code Online (Sandbox Code Playgroud)
这可行吗?或者最少修改源代码的解决方案是什么?
ugo*_*ren 16
如果使用gcc,则可以使用-finstrument-functions编译标志.它添加了调用两个函数的代码,__cyg_profile_func_enter并且__cyg_profile_func_exit每当函数进入/退出时都会调用它.
您需要实现这些功能,以实现您想要的功能.确保在没有标志的情况下编译它们,或者使用attribute((no_instrument_function))它们,因此它们不会尝试自己调用它们.
函数的第二个参数是指向调用站点的指针(即调用函数中的返回地址).你可以打印它%p,但它会有点难以使用.您可以nm用来计算包含该地址的实际函数.
你不能用这种方式得到函数参数.
Abh*_*jit 12
使用GNU C库,您可以使用该backtrace模块.这是一个例子:
#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
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();
}
Run Code Online (Sandbox Code Playgroud)
使用-g -rdynamic编译器选项编译以加载符号
gcc -g -rdynamic Test1.c -o Test
Run Code Online (Sandbox Code Playgroud)
您将看到类似的输出
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
Run Code Online (Sandbox Code Playgroud)
您可以编写此处理程序函数,并在任何时间从程序中的任何位置进行调用.请记住array根据需要增加大小.
小智 6
我也遇到了这个有良好的函数调用跟踪的问题。因此,我编写了一个 Python GDB 脚本(https://gist.github.com/stettberger/e6f2fe61206471e22e9e6f1926668093),在每个有趣的函数(由环境变量 TRACE_FUNCTION 定义)上设置断点。然后,GDB 调用 python 函数,该函数对帧及其所有参数进行解码。如果它遇到一个指针,它会尝试取消引用它,并使用参数将函数调用跟踪打印到 TRACE_FILE (默认:/tmp/log)。对于以下程序
#include <stdio.h>
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);
}
Run Code Online (Sandbox Code Playgroud)
我得到了详细的跟踪,其中每一行都是一个 python 元组,可以使用以下命令读取eval():
('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)
Run Code Online (Sandbox Code Playgroud)
要点包含有关日志文件格式的更多信息。
有时我必须跟踪大量函数调用,即使对于我没有任何控制权的外部库,或者我不想修改。
不久前,我意识到您可以组合 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
Run Code Online (Sandbox Code Playgroud)
Silent 命令用于在发现断点时隐藏 gdb 消息。我通常打印几行空行,这样更容易阅读。
然后,您只需运行该程序:(gdb) run
一旦你的程序开始运行,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
Run Code Online (Sandbox Code Playgroud)
如果您想要更详细的信息,也可以打印给定断点的局部变量,只需在command和之间插入更多命令即可end。
额外提示:将所有这些添加到您的.gdbinit文件中,并将执行通过管道传输到文件中。