打印函数调用堆栈
2019-05-04 本文已影响0人
jiangling500
相关函数签名
#include <execinfo.h>
int backtrace(void **buffer, int size);
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
-
backtrace()
:栈回溯,保存各个栈帧的地址。该函数用于获取当前线程的函数调用堆栈,获取的信息将存放在buffer
中,buffer
是一个二级指针,可以当作指针数组来用,数组中的元素类型是void*
,即从堆栈中获取的返回地址,每一个堆栈框架stack frame有一个返回地址,参数size
用来指定buffer
中可以保存void*
元素的最大值,函数返回值是buffer
中实际获取的void*
指针个数,最大不超过参数size
的大小。 -
backtrace_symbols()
:根据地址,转成相应的函数符号。该函数把从backtrace()
函数获取的信息buffer
转化为一个字符串数组char**
,每个字符串包含了相对于buffer
中对应元素的可打印信息,包括函数名、函数的偏移地址和实际的返回地址,size
指定了该数组中的元素个数,可以是backtrace()
函数的返回值,也可以小于这个值。需要注意的是,backtrace_symbols()
的返回值调用了malloc
以分配存储空间,为了防止内存泄露,我们要手动调用free
来释放这块内存。 -
backtrace_symbols_fd()
:把字符串堆栈信息输出到文件中。该函数与backtrace_symbols()
函数功能类似,不同的是,这个函数直接把结果输出到文件描述符为fd
的文件中,且没有调用malloc
。
注意: - 编译时加上
-rdynamic
,这样所有的符号信息symbols就会添加到动态符号表中,以便查看完整的堆栈信息。 -
static
函数不会导出符号信息symbols,在backtrace
中无效。
使用示例
#define BT_BUF_SIZE 100
void printStackTrace(void)
{
int i, nptrs;
void *buffer[BT_BUF_SIZE];
char **strings;
nptrs = backtrace(buffer, BT_BUF_SIZE);
printf("backtrace() returned %d addresses\n", nptrs);
/* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
would produce similar output to the following: */
// backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO);
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (i = 0; i < nptrs; i++)
{
printf("%s\n", strings[j]);
}
free(strings);
}
在C++中打印函数调用堆栈
由于C++编译器会对函数名进行mangle,使用backtrace()
函数解析出来的函数名可读性较差,可使用demangle来提高可读性。
string stackTrace(bool demangle)
{
string stack;
const int max_frames = 200;
void* frame[max_frames];
int nptrs = ::backtrace(frame, max_frames);
char** strings = ::backtrace_symbols(frame, nptrs);
if (strings)
{
size_t len = 256;
char* demangled = demangle ? static_cast<char*>(::malloc(len)) : nullptr;
for (int i = 1; i < nptrs; ++i) // skipping the 0-th, which is this function
{
if (demangle)
{
// https://panthema.net/2008/0901-stacktrace-demangled/
// bin/exception_test(_ZN3Bar4testEv+0x79) [0x401909]
char* left_par = nullptr;
char* plus = nullptr;
for (char* p = strings[i]; *p; ++p)
{
if (*p == '(')
left_par = p;
else if (*p == '+')
plus = p;
}
if (left_par && plus)
{
*plus = '\0';
int status = 0;
char* ret = abi::__cxa_demangle(left_par+1, demangled, &len, &status);
*plus = '+';
if (status == 0)
{
demangled = ret; // ret could be realloc()
stack.append(strings[i], left_par+1);
stack.append(demangled);
stack.append(plus);
stack.push_back('\n');
continue;
}
}
}
// Fallback to mangled names
stack.append(strings[i]);
stack.push_back('\n');
}
free(demangled);
free(strings);
}
return stack;
}
在段错误等致命信号的处理函数中打印函数调用堆栈
static void sigsegvHandler(int sig, siginfo_t *info, void *secret)
{
void *trace[100];
char **messages = NULL;
int i, trace_size = 0;
struct sigaction act;
trace_size = backtrace(trace, 100);
messages = backtrace_symbols(trace, trace_size);
for (i=1; i<trace_size; ++i)
{
fprintf(stderr,"%s", messages[i]);
}
/* Make sure we exit with the right signal at the end. So for instance
* the core will be dumped if enabled. */
sigemptyset (&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND;
act.sa_handler = SIG_DFL;
sigaction (sig, &act, NULL);
kill(getpid(),sig);
}
void setupSignalHandlers(void)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND | SA_SIGINFO;
act.sa_sigaction = sigsegvHandler;
sigaction(SIGSEGV, &act, NULL); // 段错误信号
sigaction(SIGBUS, &act, NULL); // 总线错误信号
sigaction(SIGFPE, &act, NULL); // 除零异常信号
sigaction(SIGILL, &act, NULL); // 非法程序映像信号
return;
}
在try-catch语句块中打印函数调用堆栈
class Bar
{
public:
void test(std::vector<std::string> names = {})
{
printf("Stack:\n%s\n", muduo::CurrentThread::stackTrace(true).c_str());
throw muduo::Exception("oops");
}
};
void foo()
{
Bar b;
b.test();
}
int main()
{
try
{
foo();
}
catch (const muduo::Exception& ex)
{
printf("reason: %s\n", ex.what());
printf("stack trace:\n%s\n", ex.stackTrace());
}
}