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

时间:2012-04-29 16:57:19

标签: 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)

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

9 个答案:

答案 0 :(得分:14)

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

你需要实现这些功能,做你想做的事。确保在没有标志或attribute((no_instrument_function))的情况下编译它们,这样他们就不会试图自己打电话。

功能'第二个参数是指向调用站点的指针(即调用函数中的返回地址)。您可以使用%p进行打印,但有些难以使用。您可以使用nm找出包含此地址的实际函数。

您无法通过这种方式获取功能参数。

答案 1 :(得分: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(); 
}

使用-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尺寸。

答案 2 :(得分:4)

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

答案 3 :(得分:3)

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

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

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

查看relevant documentation for gdb

答案 4 :(得分:2)

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

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

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

<ui-select tagging tagging-label="new tag" multiple ng-model="multipleDemo" theme="select2" ng-disabled="disabled" style="width: 300px;">
    <ui-select-match placeholder="Select colors...">{{$item.name}}</ui-select-match>
    <ui-select-choices repeat="color in people | filter:$select.search">
      {{color.name}}
    </ui-select-choices>
  </ui-select>

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

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

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

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

如果您需要更详细的信息,也可以打印给定断点的局部变量,只需在#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文件中,并将执行内容传输到文件中。

答案 5 :(得分:2)

我还遇到了具有良好函数调用跟踪的问题。因此,我编写了一个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);
}

我得到一个详细的跟踪,其中每一行都是一个可以用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)

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

答案 6 :(得分:0)

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

总结:debugger,log4cxx或AOP

答案 7 :(得分:0)

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

指定监视的库

答案 8 :(得分:0)

这是一个完美的答案,我正在寻找一个类似的工具,我找到了一个由 John Panzer 于 1998 年制作的名为 CallMonitor 的工具。这是该工具的输出。是的,它是为 Visual C++ 6.0 制作的,但它可以在 Visual Studio 2019 中运行,无需任何编辑。

(除了缺少我可以没有的参数。)

适用于 Windows!!!

我把代码放在我的个人 github 上用于历史备份。 (它还包含我自己构建的已编译的 Debug,因此您可以立即对其进行测试,只需在命令提示符中运行 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)