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

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)

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


vin*_*nes 5

如果您使用的是 linux,callgrind可能会有所帮助。它基本上收集您正在寻找的统计信息,因此,它可能提供一种访问其原始数据的方法。


Jor*_*lon 5

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

不久前,我意识到您可以组合 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文件中,并将执行通过管道传输到文件中。