如何在 C 中跟踪函数调用?
Posted
技术标签:
【中文标题】如何在 C 中跟踪函数调用?【英文标题】:how to trace function call in C? 【发布时间】:2012-05-09 13:57:13 【问题描述】:在不修改源代码的情况下,当调用某些函数(例如以下示例中的 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)
这可行吗?或者对源代码进行最少修改的解决方案是什么?
【问题讨论】:
使用调试器。或者调用某种形式的 fprintf 记录到文件中。但也许最后一个选项不太好,因为您不想修改源代码。 也许是一个分析器来获取调用图? 您在寻找类似的东西吗? ***.com/questions/311840/… 我目前使用 gdb(breakpoint,next,step,print,display),但我认为在这种特殊情况下不方便。 也相关:***.com/q/1472769/694576 【参考方案1】:如果使用gcc
,则可以使用-finstrument-functions
编译标志。
它添加了在函数进入/退出时调用两个函数 __cyg_profile_func_enter
和 __cyg_profile_func_exit
的代码。
您需要实现这些功能,才能做您想做的事。确保在没有标志的情况下编译它们,或者使用__attribute__((no_instrument_function))
,这样它们就不会尝试调用自己。
函数的第二个参数是指向调用站点的指针(即调用函数中的返回地址)。您可以使用%p
打印它,但使用起来会有些困难。您可以使用nm
找出包含该地址的真正函数。
这种方式无法获取函数参数。
【讨论】:
可惜只是地址。 这是example implementation。 还没有机会尝试一下,但听起来很强大。我认为有一些漂亮/可怕的 sed/awk/nm 单行程序可以将所有日志地址转换为函数名称 其实有一个工具叫etrace,它是cyg_profile和nm的组合。【参考方案2】:借助 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();
使用 -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
的大小。
【讨论】:
【参考方案3】:如果您使用的是 linux,callgrind 可能会有所帮助。它基本上会收集您要查找的内容的统计信息,因此,它可能会提供一种访问其原始数据的方法。
【讨论】:
我查看了 callgrind 手册,没有提到在进入/退出函数时如何收集参数的值。【参考方案4】:我也遇到过函数调用跟踪良好的问题。因此,我编写了一个 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);
我得到了详细的跟踪,其中每一行都是一个 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)
要点包含有关日志文件格式的更多信息。
【讨论】:
这很酷 :-) 它确实对我有用,但是我使用它的特定程序变得非常慢;目前它已经运行了半个小时,而在 GDB(没有 Python 脚本)下正常运行只需要几分之一秒。这个程序是一个相当复杂的程序,所以我想我不应该太惊讶,但对于可能想尝试这个的人来说,减速绝对是需要注意的。【参考方案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
Silent 命令用于在找到断点时隐藏 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
如果您想要更详细的信息,也可以打印给定断点的局部变量,只需在command
和end
之间插入更多命令即可。
额外提示:将所有这些添加到您的 .gdbinit
文件中并将执行通过管道传输到文件中。
【讨论】:
【参考方案6】:使用调试器设置断点和相关操作。例如,在 gdb 中,您可以在要跟踪的每个函数的开头和结尾处设置断点。你可以给每个断点一个命令来执行,例如:
printf("Enter func100(p1001=%d, p1002=%d)", p1001, p1002)
然后,当您运行程序(在调试器中)时,它会打印每个命令的文本以及相关参数。
看看relevant documentation for gdb。
【讨论】:
它就像一个魅力。唯一的问题是我不知道如何在每个函数的末尾设置断点。对于函数开头的断点,我只是将 break 命令放入我的命令文件,然后是函数名。对于函数末尾的断点,我不能这样做。我可以使用 break 命令后跟行号吗?那么如何指定断点所在的源文件呢? 您可以在特定的行号上中断。此外,大多数体面的 IDE 使设置断点变得简单,通常只需单击边缘即可。你还没有说你使用的是什么工具,所以很难给出具体的建议。 我使用 GDB。所以我需要准备一个命令文件,该文件将通过 --command 选项传递给 gdb。或者,Eclipse CDT 可以帮助生成 gdb 命令文件吗?我肯定需要命令文件并进行一些调整。【参考方案7】:您可以查看 log4cxx,这是一个由 apache 基金会托管的项目。我知道 log4j,java 变体允许您设置敏感度,并且您可以跟踪程序中所做的每一件事。也许 c++ 变体是相同的,但有几个替代方案 - 有一个面向方面的 c++ 编译器,您可以在所有函数中定义一个方面,并让它捕获并打印变量。另一种选择是使用调试器。
总结一下:调试器、log4cxx 或 AOP
【讨论】:
AFAIK,要使用log4cxx,我应该通过添加代码 sn-p 来修改我的源代码,例如 "LOG4CXX_DEBUG(barlogger, "Exiting func100");" 啊不知道。是的,那个呼叫磨合看起来是最好的解决方案【参考方案8】:如果您使用动态模块,您可以使用命令 ltrace 获取此信息。您甚至可以使用 -l 标志指定监视的库
【讨论】:
【参考方案9】:这是一个完美的答案,我一直在寻找一个类似的工具,我发现了 John Panzer 于 1998 年制作的一个名为 CallMonitor 的工具。这是该工具的输出。是的,它是为 Visual C++ 6.0 制作的,但它可以在 Visual Studio 2019 中运行,完全无需编辑。
(除了缺少一些我可以不用的参数。)
适用于Windows!!!
我将代码放在我的个人 github 上用于历史备份。 (它还包含我自己构建的已编译调试,因此您可以立即对其进行测试,只需在命令提示符下运行 Test.exe 即可查看结果。
https://github.com/fatrolls/CallMonitor
这里是 test.cpp 代码
// Test driver for CallMon
#include <windows.h>
#include <iostream>
using namespace std;
void child2(bool doThrow)
Sleep(5);
if (doThrow) throw "random exception";
void child1(bool doThrow)
child2(false);
child2(doThrow);
// Waits a bit, then calls a child function
// that might throw an exception
DWORD WINAPI threadFunc(LPVOID param)
Sleep(1);
try
child1(param ? true : false);
catch (char *)
child1(false);
return 0;
// Starts argv[1] threads running, of which argv[2] throw
// exceptions.
void main(int argc,char *argv[])
if (argc!=3)
cout << "Usage: " << argv[0] << " <number of threads, or 0> <number of exceptions>" << endl;
cout << " Pass 0 for number of threads for simple nonthreaded test" << endl;
cout << " Pass the number of threads which should throw exceptions as the second arg" << endl;
cout << " Note: The test output for the multithreaded case gives " << endl;
cout << " nearly unreadable output." << endl;
return;
const int MAX_THREADS=MAXIMUM_WAIT_OBJECTS;
DWORD id;
HANDLE th[MAX_THREADS];
int numThreads = atol(argv[1]);
int numExc = atol(argv[2]);
if (numThreads == 0)
threadFunc((void*)(numExc));
else
int i;
for(i=0;i<numThreads;i++)
void * param= (void*)(i < numExc ? 1 : 0);
th[i] = CreateThread(NULL,0,threadFunc,param,0,&id);
WaitForMultipleObjects(numThreads,th,TRUE,50000);
for(i=0;i<numThreads;i++)
CloseHandle(th[i]);
这是 Test.exe 0 20 的输出
C:\Users\User\Desktop\callmonitor\Debug>Test 0 20
Test!main (002B2160)
Test!threadFunc (002B20A0)
Test!child1 (002B1F70)
Test!child2 (002B1FB0)
exit 002B1FB0, elapsed time=4 ms (13738100 ticks)
Test!child2 (002B1FB0)
Test!child1 (002B1F70)
Test!child2 (002B1FB0)
exit 002B1FB0, elapsed time=4 ms (123855
75 ticks)
Test!child2 (002B1FB0)
exit 002B1FB0, elapsed time=5 ms (150199
75 ticks)
exit 002B1F70, elapsed time=27 ms (82797408 tick
s)
exception exit 002B1FB0, elapsed time=40 ms (122291193 t
icks)
exception exit 002B1F70, elapsed time=56 ms (170411060 ticks)
exit 002B20A0, elapsed time=61 ms (184553835 ticks)
exit 002B2160, elapsed time=74 ms (222897953 ticks)
C:\Users\User\Desktop\callmonitor\Debug>
这是Test.exe的输出
Test!main (01132160)
Test!std::operator<<<std::char_traits<char> > (01131740)
Test!<unknown symbol> (01132070)
exit 01132070, elapsed time=0 ms (1960 ticks)
Test!<unknown symbol> (01131CB0)
Test!<unknown symbol> (01131C40)
exit 01131C40, elapsed time=0 ms (3123 ticks)
exit 01131CB0, elapsed time=2 ms (7502464 ticks)
Test!<unknown symbol> (01131F00)
exit 01131F00, elapsed time=0 ms (378 ticks)
Usage: Test!<unknown symbol> (01131E60)
Test!<unknown symbol> (01131DC0)
exit 01131DC0, elapsed time=0 ms (1740 ticks)
exit 01131E60, elapsed time=2 ms (6846975 ticks)
exit 01131740, elapsed time=15 ms (47014729 ticks)
Test!std::operator<<<std::char_traits<char> > (01131740)
Test!<unknown symbol> (01132070)
exit 01132070, elapsed time=0 ms (1485 ticks)
Test!<unknown symbol> (01131CB0)
Test!<unknown symbol> (01131C40)
exit 01131C40, elapsed time=0 ms (2268 ticks)
exit 01131CB0, elapsed time=2 ms (8479308 ticks)
Test!<unknown symbol> (01131F00)
exit 01131F00, elapsed time=0 ms (285 ticks)
Test Test!<unknown symbol> (01131E60)
Test!<unknown symbol> (01131DC0)
exit 01131DC0, elapsed time=0 ms (2415 ticks)
exit 01131E60, elapsed time=3 ms (10897188 ticks)
exit 01131740, elapsed time=17 ms (52439945 ticks)
Test!std::operator<<<std::char_traits<char> > (01131740)
Test!<unknown symbol> (01132070)
exit 01132070, elapsed time=0 ms (1443 ticks)
Test!<unknown symbol> (01131CB0)
Test!<unknown symbol> (01131C40)
exit 01131C40, elapsed time=0 ms (2550 ticks)
exit 01131CB0, elapsed time=2 ms (8731260 ticks)
Test!<unknown symbol> (01131F00)
exit 01131F00, elapsed time=0 ms (354 ticks)
<number of threads, or 0> <number of exceptions> Test!<unknown sy
mbol> (01131E60)
Test!<unknown symbol> (01131DC0)
exit 01131DC0, elapsed time=0 ms (2397 ticks)
exit 01131E60, elapsed time=3 ms (10455030 ticks)
exit 01131740, elapsed time=18 ms (54133983 ticks)
Test!std::endl<char,std::char_traits<char> > (01131BE0)
exit 01131BE0, elapsed time=0 ms (1256316 ticks)
Test!std::operator<<<std::char_traits<char> > (01131740)
Test!<unknown symbol> (01132070)
exit 01132070, elapsed time=0 ms (1554 ticks)
Test!<unknown symbol> (01131CB0)
Test!<unknown symbol> (01131C40)
exit 01131C40, elapsed time=0 ms (1983 ticks)
exit 01131CB0, elapsed time=2 ms (8839671 ticks)
Test!<unknown symbol> (01131F00)
exit 01131F00, elapsed time=0 ms (2736 ticks)
Pass 0 for number of threads for simple nonthreaded test Test!<un
known symbol> (01131E60)
Test!<unknown symbol> (01131DC0)
exit 01131DC0, elapsed time=0 ms (1605 ticks)
exit 01131E60, elapsed time=2 ms (8375898 ticks)
exit 01131740, elapsed time=20 ms (61957080 ticks)
Test!std::endl<char,std::char_traits<char> > (01131BE0)
exit 01131BE0, elapsed time=0 ms (1303008 ticks)
Test!std::operator<<<std::char_traits<char> > (01131740)
Test!<unknown symbol> (01132070)
exit 01132070, elapsed time=0 ms (1245 ticks)
Test!<unknown symbol> (01131CB0)
Test!<unknown symbol> (01131C40)
exit 01131C40, elapsed time=0 ms (1548 ticks)
exit 01131CB0, elapsed time=2 ms (8249352 ticks)
Test!<unknown symbol> (01131F00)
exit 01131F00, elapsed time=0 ms (408 ticks)
Pass the number of threads which should throw exceptions as the second arg
Test!<unknown symbol> (01131E60)
Test!<unknown symbol> (01131DC0)
exit 01131DC0, elapsed time=0 ms (1503 ticks)
exit 01131E60, elapsed time=2 ms (8074389 ticks)
exit 01131740, elapsed time=17 ms (52143870 ticks)
Test!std::endl<char,std::char_traits<char> > (01131BE0)
exit 01131BE0, elapsed time=0 ms (1312485 ticks)
Test!std::operator<<<std::char_traits<char> > (01131740)
Test!<unknown symbol> (01132070)
exit 01132070, elapsed time=0 ms (1392 ticks)
Test!<unknown symbol> (01131CB0)
Test!<unknown symbol> (01131C40)
exit 01131C40, elapsed time=0 ms (2043 ticks)
exit 01131CB0, elapsed time=2 ms (8364006 ticks)
Test!<unknown symbol> (01131F00)
exit 01131F00, elapsed time=0 ms (357 ticks)
Note: The test output for the multithreaded case gives Test!<un
known symbol> (01131E60)
Test!<unknown symbol> (01131DC0)
exit 01131DC0, elapsed time=0 ms (1557 ticks)
exit 01131E60, elapsed time=2 ms (8433210 ticks)
exit 01131740, elapsed time=17 ms (51733404 ticks)
Test!std::endl<char,std::char_traits<char> > (01131BE0)
exit 01131BE0, elapsed time=0 ms (1323972 ticks)
Test!std::operator<<<std::char_traits<char> > (01131740)
Test!<unknown symbol> (01132070)
exit 01132070, elapsed time=0 ms (1257 ticks)
Test!<unknown symbol> (01131CB0)
Test!<unknown symbol> (01131C40)
exit 01131C40, elapsed time=0 ms (1668 ticks)
exit 01131CB0, elapsed time=2 ms (7994649 ticks)
Test!<unknown symbol> (01131F00)
exit 01131F00, elapsed time=0 ms (393 ticks)
nearly unreadable output. Test!<unknown symbol> (01131E60)
Test!<unknown symbol> (01131DC0)
exit 01131DC0, elapsed time=0 ms (1572 ticks)
exit 01131E60, elapsed time=2 ms (8090115 ticks)
exit 01131740, elapsed time=17 ms (52094652 ticks)
Test!std::endl<char,std::char_traits<char> > (01131BE0)
exit 01131BE0, elapsed time=0 ms (1355601 ticks)
exit 01132160, elapsed time=162 ms (486552183 ticks)
【讨论】:
以上是关于如何在 C 中跟踪函数调用?的主要内容,如果未能解决你的问题,请参考以下文章