C / C ++需要一种聪明的方法来跟踪函数调用

时间:2010-07-23 03:47:07

标签: c++ c gdb c-preprocessor

我正在寻找一种聪明的方法来跟踪函数调用和返回。 我知道我可以使用调试器,但是我希望有一种方法可以让它在调用函数时向终端输出一些东西而不必逐步执行代码。
我想我可以使用预处理器,但我不确定最好的方法是什么。
或者有没有办法使用gdb打印出有用的信息,而无需单步执行代码。

10 个答案:

答案 0 :(得分:14)

大多数编译器允许您在函数调用之前和之后注入检测函数。

在msvc中,它们是_penter和_pexit
好文章 http://www.drdobbs.com/184403601

在gcc中你会使用-finstrument-functions
http://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Code-Gen-Options.html

您可以使用调试库或映射文件来获取更多信息。

答案 1 :(得分:4)

一个非常具有侵入性的解决方案是使用RAII来控制功能的范围。这将对性能产生很大影响,但在日志中会非常明确,而无需用户在可能会离开函数的所有可能代码路径中添加检测:

class ScopeLogger {
public:
   ScopeLogger( std::string const & msg ) : msg(msg)
   {   std::cout << "Enter: " << msg << std::endl; }
   ~ScopeLogger()
   {   std::cout << "Exit:  " << msg << std::endl; }
   std::string msg;
};
#if DEBUG
#define FUNCTION(x) ScopeLogger l_##x##_scope(x);
#endif

void foo( int value ) {
   FUNCTION( __FUNCTION__ );
   if ( value > 10 ) throw std::exception;
   std::cout << "." << std::endl;
}

int main() {
   foo(0);    // Enter: foo\n.\nExit:  foo
   foo(100);  // Enter: foo\nExit:  foo
}

如果代码是单线程的,您甚至可能希望添加一个静态变量,其缩进级别为ScopedLogger,而不会对已经很重的性能影响增加太多:

class ScopeLogger {
public:
   ScopeLogger( std::string const & msg ) : msg(msg)
   {   std::cout << std::string(indent++,' ') << "Enter: " << msg << std::endl; }
   ~ScopeLogger()
   {   std::cout << std::string(--indent,' ') << "Exit:  " << msg << std::endl; }
   std::string msg;
   static int indent;
};
int ScopeLogger::indent = 0;

答案 2 :(得分:4)

由于您使用的是GCC,因此您也可以使用链接器函数包装。

Link-Time Replacement / Wrapping
– GCC option: -Wl,--wrap,function_name

基本上,您可以使用名为“function_name()”的函数并使用名为“__wrap_function_name()”的函数对其进行包装。您可以通过调用“__real_function_name()”来访问原始函数。

答案 3 :(得分:3)

#define BEGIN_FUNC(X) printf("Function %s Entered",X)
#define END_FUNC(X)  printf("Function %s End",X)

foo()
{
BEGIN_FUNC(__func__);

//Your code here


END_FUNC(__func__);


}

我认为如果你像上面那样编写一个宏并将其用于所描述的每个函数,那么你可以在终端上获取日志。

答案 4 :(得分:3)

您可能希望查看可以将函数调用跟踪到漂亮图表的Valgrind's Callgrind。它将显示函数调用,但不显示参数或返回值。

答案 5 :(得分:2)

  

或者有没有办法使用gdb打印出有用的信息,而无需单步执行代码

是。仅在您真正关心的功能上设置断点。使用“继续”直到进入这些功能或程序崩溃。然后使用“backtrace”(或“bt”)来获得堆栈跟踪。

答案 6 :(得分:1)

如果您需要自动化,可以查看TARGET_ASM_FUNCTION_END_PROLOGUETARGET_ASM_FUNCTION_BEGIN_EPILOGUE。这些是编译器挂钩,可以让你指定要与正常函数prologue / epilogue一起发出的程序集 - 在你的情况下,你将使用它们发出一个小程序集来记录相关函数的入口/出口。您还可以查看FUNCTION_PROFILE和/或PROFILE_HOOK(例如:http://gcc.gnu.org/onlinedocs/gccint/Function-Entry.html)。

答案 7 :(得分:1)

下面是说明the answer by Jonathan Fischoff的GCC边的示例。

在这里,我们调用外部工具addr2line将位置打印为functionName at /path/to/file.cpp:line而不是地址。我曾尝试为此使用dladdr(如对上面链接的答案的注释中所建议),但是它仅为我返回了dli_sname中的空指针。

这种解决地址的方法有一些缺点:

  • 由于fork / execve /文件读取,速度很慢。
  • 它需要包含地址的二进制文件的确切文件路径,因此下面的简单代码无法在共享库中打印符号。
// Instrumentation
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void __attribute__((no_instrument_function))
    log_func(const void* funcAddr, const char* action, const void* callSite)
{
    char cmd[50];
    snprintf(cmd, sizeof cmd, "addr2line -Cpfe /proc/%d/exe %p", getpid(), funcAddr);
    fprintf(stderr, "%p %s %p ", callSite, action, funcAddr);
    system(cmd);
}

extern "C" void __attribute__((no_instrument_function))
    __cyg_profile_func_enter(void* this_fn, void* call_site)
{
    log_func(this_fn, "->", call_site);
}

extern "C" void __attribute__((no_instrument_function))
    __cyg_profile_func_exit(void* this_fn, void* call_site)
{
    log_func(this_fn, "<-", call_site);
}

// Actual code we're tracing
#include <iostream>

struct Test
{
    Test() { std::cout << "Hi, I'm Test constructor\n"; }
    void method() const { std::cout << "And I'm Test method\n"; }
};

int main()
{
    std::cout << "Hello, my name is main\n";
    Test test;
    test.method();
}

编译并运行:

$ g++ test.cpp -o test -g -finstrument-functions && time ./test
0x8048b0b -> 0x804899b _GLOBAL__sub_I___cyg_profile_func_enter at /tmp/test.cpp:41
0x80489c4 -> 0x804890b __static_initialization_and_destruction_0(int, int) at /tmp/test.cpp:41
0x80489c4 <- 0x804890b __static_initialization_and_destruction_0(int, int) at /tmp/test.cpp:41
0x8048b0b <- 0x804899b _GLOBAL__sub_I___cyg_profile_func_enter at /tmp/test.cpp:41
0xf7a0de71 -> 0x804886a main at /tmp/test.cpp:37
Hello, my name is main
0x80488b1 -> 0x80489de Test::Test() at /tmp/test.cpp:32
Hi, I'm Test constructor
0x80488b1 <- 0x80489de Test::Test() at /tmp/test.cpp:32
0x80488c0 -> 0x8048a4a Test::method() const at /tmp/test.cpp:33
And I'm Test method
0x80488c0 <- 0x8048a4a Test::method() const at /tmp/test.cpp:33
0xf7a0de71 <- 0x804886a main at /tmp/test.cpp:37

real    0m0.062s
user    0m0.054s
sys     0m0.008s

答案 8 :(得分:0)

有一个__FUNCTION__ (Reference)宏用于确定您所处的方法(格式为Class::Method),但这更像是一个手动过程。

但是,当我最近需要相同的“跟踪”信息时,我找不到自动方法。

答案 9 :(得分:0)

前段时间我听了一篇关于面向方面编程的讨论,其中包括你想要实现的目标。也许搜索该术语会有所帮助。