在C ++中,如何获取当前线程的调用堆栈?

时间:2013-08-07 15:04:49

标签: c++ error-handling stack-trace

我正在为我在C ++中工作的一些代码编写这个错误处理程序。我希望能够对堆栈中的任何内容进行某种引用,而不是明确地传递给我。具体来说,让我们说我想按顺序打印调用堆栈上函数的名称。这在像JVM这样的托管运行时环境中是微不足道的,可能不是那么简单的“简单'编译代码。我可以这样做吗?

备注:

  • 为简单起见,假设我使用调试信息编译代码而没有优化。
  • 我想写一些与平台无关或多平台的东西。很喜欢前者。
  • 如果您认为我正在尝试重新发明轮子,只需链接到相关车轮的来源,我就会看到那里。

更新

我不能相信你需要向后弯腰多少才能做到这一点......几乎让我松了另一个language,这是不可提及的。

3 个答案:

答案 0 :(得分:2)

有一种方法可以在C ++中获得反向跟踪,尽管它不可​​移植。我不能代表Windows,但在类Unix系统上有一个backtrace API,主要包含以下功能:

  • int backtrace(void** array, int size);
  • char** backtrace_symbols(void* const* array, int size);
  • void backtrace_symbols_fd(void* const* array, int size, int fd);

您可以在GNU网站here上找到最新的文档和示例。还有其他来源,例如OS X的this manual page等等。

请注意,使用此API获取回溯有一些问题。首先,没有文件名和行号。其次,在某些情况下甚至无法获得回溯,如果完全省略了帧指针(x86_64平台的最新GCC编译器的默认行为)。或者二进制文件没有任何调试符号。在某些系统上,您还必须在编译二进制文件时指定-rdynamic标志(其他可能存在不良影响)。

答案 1 :(得分:1)

不幸的是,使用标准C ++没有内置的方法。您可以构建一个类系统来帮助您构建堆栈跟踪器实用程序,但是您需要在要跟踪的每个方法中放置一个特殊的宏。

我已经看到它使用下面列出的策略完成(甚至实现了部分内容):

  • 定义您自己的类,用于存储有关堆栈帧的信息。每个节点至少应包含被调用函数的名称,文件名/行号信息紧随其后。
  • 堆栈框架节点存储在链接列表中,如果存在则会重复使用,如果不存在则会创建
  • 通过实例化特殊对象,创建堆栈帧并将其添加到列表中。对象的构造函数将帧节点添加到列表中; object的析构函数从列表中删除该节点。
  • 相同的构造函数/析构函数对负责在线程本地存储中创建框架列表,并删除它创建的列表
  • 特殊对象的构造由宏处理。宏使用特殊的预处理器标记将函数标识和位置信息传递给帧创建者对象。

这是一种相当严格的概念验证实现方法:

#include <iostream>
#include <list>

using namespace std;

struct stack_frame {
    const char *funName;
    const char *fileName;
    int line;
    stack_frame(const char* func, const char* file, int ln)
    : funName(func), fileName(file), line(ln) {}
};

thread_local list<stack_frame> *frames = 0;

struct entry_exit {
    bool delFrames;
    entry_exit(const char* func, const char* file, int ln) {
        if (!frames) {
            frames = new list<stack_frame>();
            delFrames = true;
        } else {
            delFrames = false;
        }
        frames->push_back(stack_frame(func, file, ln));
    }
    ~entry_exit() {
        frames ->pop_back();
        if (delFrames) {
            delete frames;
            frames = 0;
        }
    }
};

void show_stack() {
    for (list<stack_frame>::const_iterator i = frames->begin() ; i != frames->end() ; ++i) {
        cerr << i->funName << " - " << i->fileName << " (" << i->line << ")" << endl;
    }
}

#define FUNCTION_ENTRY entry_exit _entry_exit_(__func__, __FILE__, __LINE__);

void foo() {
    FUNCTION_ENTRY;
    show_stack();
}
void bar() {
    FUNCTION_ENTRY;
    foo();
}
void baz() {
    FUNCTION_ENTRY;
    bar();
}

int main() {
        baz();
        return 0;
}

上面的代码用C ++ 11编译并打印出来:

baz - prog.cpp (52)
bar - prog.cpp (48)
foo - prog.cpp (44)

没有该宏的函数在堆栈上是不可见的。性能关键功能不应该有这样的宏。

这是demo on ideone

答案 2 :(得分:0)

这并不容易。确切的解决方案在很大程度上取决于操作系统和执行环境。

打印堆栈通常并不困难,但查找符号可能非常棘手,因为它通常意味着读取调试符号。

另一种方法是使用侵入式方法并为每个函数添加一些“我在哪里”类型代码(大概是“仅用于调试版本”):

#ifdef DEBUG
struct StackEntry
{
   const char *file;
   const char *func;
   int         line;
   StackEntry(const char *f, const char *fn, int ln) : file(f), func(fn), line(ln) {}
};

std::stack<StackEntry> call_stack;

class FuncEntry
{
   public:
    FuncEntry(const char *file, const char *func, int line)
    {
       StackEntry se(file, func, line);
       call_stack.push_back(se);
    }
    ~FuncEntry()
    {
        call_stack.pop_back();
    }

    void DumpStack()
    {
         for(sp : call_stack)
         {
             cout << sp->file << ":" << sp->line << ": " << sp->func << "\n";
         }
    }
 };


 #define FUNC() FuncEntry(__FILE__, __func__, __LINE__); 
 #else
 #define FUNC()
 #endif


 void somefunction()
 {
     FUNC();
     ... more code here. 
 }

我过去曾使用过这种技术,但我只输入了这段代码,它可能无法编译,但我认为它已经足够清楚了。一个主要的好处是你不必把它放在每个功能 - 只是“重要的”。 [您甚至可以根据不同的调试级别启用或禁用不同类型的FUNC宏。