打印堆栈的常用方法包括:
__builtin_return_address
#include
/*
* 功能: 获取当前线程的调用堆栈并存放在buffer中(指向字符串数组的指针)
* @param buffer: 存放当前线程的调用堆栈
* @param size: 指定buffer中可以保存多少个 void* 元素(void* 元素实际上是从堆栈中获取的返回地址)
* @return 实际返回的 void* 元素个数
* */
int backtrace(void **buffer, int size);
/*
* 功能: 将backtrace函数获取的信息转化为一个字符串数组
* @param buffer: backtrace获取的堆栈指针
* @param size: backtrace返回值
* @return: 一个指向字符串数组的指针, 包含 size 个 char* 元素, 每个元素包含了一个相对于buffer中对应元素的可打印信息(函数名、函数偏移地址和实际返回地址)
* */
char **backtrace_symbols(void *const *buffer, int size);
/*
* 功能: 与backtrace_symbols函数功能相同, 但是不会malloc内存, 而是将结果写入文件描述符为fd的文件中
* */
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
backtrace()
和backtrace_symbols()
都是不可重入的函数,原因在于它们内部都使用了malloc
函数,而malloc
内部是有锁的。假设某个线程正在调用malloc
分配堆空间,此时程序捕捉到信号发生中断,而信号处理函数中恰好也调用了malloc
函数就会发生死锁导致该线程hang住,随后所有调用malloc
的其他线程也会相继hang住。
backtrace_symbols_fd
函数是可重入的,我们用它代替backtrace_symbols
打印堆栈信息。
backtrace
的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On
参数)或加入了栈指针优化参数-fomit-frame-pointer
后可能不能正确得到程序栈信息backtrace_symbols
的实现需要符号名称的支持,在gcc编译时需要加入-rdynamic
参数Tail-Call Optimization
将复用当前函数栈而不再生成新的函数栈,这将导致栈信息不能被正确获取#include
#include
void show_backtrace(void)
{
#define SIZE 200
int nptrs;
void *buffer[SIZE];
char **strings;
nptrs = backtrace(buffer, SIZE);
printf("backtrace() returned %d address\n", nptrs);
// backtrace_symbols函数不可重入, 可以使用backtrace_symbols_fd替换
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (int j = 0; j < nptrs; j++)
{
printf("%s\n", strings[j]);
}
free(strings);
}
这种方法使用起来很方便,也不需要引入什么第三方库,但是这种方法要求交叉编译器必须是支持glibc的,比如海思的hi3536是uclibc的,就没有backtrace接口,只能使用别的方法。
我们可以使用gcc内置函数__builtin_return_address(level)
打印出一个函数的堆栈地址,其中level表示堆栈中第几层调用地址。
#include
void f() {
printf("%p,%p\n", __builtin_return_address(0), __builtin_return_address(1));
}
void g() {
f();
}
int main() {
g();
}
下载地址:http://download-mirror.savannah.gnu.org/releases/libunwind/
这里我下载1.5版本,文件名为:libunwind-1.5.0.tar.gz。
$tar -zxvf libunwind-1.5.0.tar.gz
$cd libunwind-1.5.0
$CFLAGS=-fPIC ./configure --prefix=$(pwd)/.libs
$make CFLAGS=-fPIC
$make CFLAGS=-fPIC install
如果是编译arm版本,需要在configure的时候指定交叉编译链用 --host=xxxxxx,比如海思hi3536:
$CFLAGS=-fPIC ./configure --host=arm-hisiv500-linux --prefix=$(pwd)/.libs
#include "backtrace.h"
#ifdef __cplusplus
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#define UNW_LOCAL_ONLY
#include
#ifndef __USE_GNU
#define __USE_GNU
#endif
#include
void show_backtrace(void)
{
unw_cursor_t cursor;
unw_context_t uc;
unw_word_t ip, sp;
char func_name_cache[4096];
func_name_cache[sizeof(func_name_cache) - 1] = 0;
unw_word_t unw_offset;
unw_proc_info_t unw_proc;
int frame_id = 0;
#ifdef __cplusplus
int status;
#endif
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
printf("++++++++ backtrace ++++++++\n");
while (unw_step(&cursor) > 0)
{
unw_get_reg(&cursor, UNW_REG_IP, &ip);
unw_get_reg(&cursor, UNW_REG_SP, &sp);
unw_get_proc_info(&cursor, &unw_proc);
unw_get_proc_name(&cursor, func_name_cache, sizeof(func_name_cache) - 1,
&unw_offset);
#ifdef __cplusplus
char *func_name = abi::__cxa_demangle(func_name_cache, 0, 0, &status);
#else
const char *func_name = func_name_cache;
#endif
printf("Frame #%02d: (%s+0x%llx) [0x%llx]\n", frame_id,
func_name ? func_name : func_name_cache,
static_cast(unw_offset),
static_cast(unw_proc.start_ip));
#ifdef __cplusplus
if (func_name)
free((void *)func_name);
#endif
frame_id++;
}
printf("+++++++++++++++++++++++++++\n");
}
#ifndef __BACKTRACE_H
#define __BACKTRACE_H
#ifdef __cplusplus
extern "C"
{
#endif
void show_backtrace();
#ifdef __cplusplus
}
#endif
#endif /* __BACKTRACE_H */
#include
#include
#include
#include
#include
#include
#include
#include
#include "backtrace.h"
typedef void (*sighandler_t)(int);
static const char *program_exec = "trace_cpp";
void crash()
{
volatile int i = *(int *)7;
(void)i;
}
void foo2(void)
{
crash();
}
void foo1(void)
{
foo2();
}
static void signal_handler(int signo)
{
printf("Aborting (signal %d) [%s]\n", signo, program_exec);
show_backtrace();
exit(EXIT_FAILURE);
}
static void signal_setup(sighandler_t handler)
{
struct sigaction sa;
sigset_t mask;
sigemptyset(&mask);
sa.sa_handler = handler;
sa.sa_mask = mask;
sa.sa_flags = 0;
sigaction(SIGBUS, &sa, NULL);
sigaction(SIGILL, &sa, NULL);
sigaction(SIGFPE, &sa, NULL);
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
}
int main(int argc, char **argv)
{
signal_setup(signal_handler);
foo1();
return 0;
}
编译:
可以通过命令行编译,或者通过cmake构建文件进行编译,然后链接libunwind的静态库以及头文件即可生成测试可执行文件。
运行log:
/mnt/hi3536-workspace # ./trace_cpp
Aborting (signal 11) [trace_cpp]
++++++++ backtrace ++++++++
Frame #00: (signal_handler(int)+0x2c) [0x10eac]
Frame #01: (_setjmp+0xc) [0x10eac]
Frame #02: (crash()+0x10) [0x10e64]
Frame #03: (foo2()+0xc) [0x10e8c]
Frame #04: (foo1()+0xc) [0x10e9c]
Frame #05: (main+0x20) [0x10fa4]
Frame #06: (__uClibc_main+0x298) [0x10fa4]
+++++++++++++++++++++++++++
分析:
从log中,我们看到最上面的一个接口并且是我们测试代码中用到的是,Frame #02,后面的地址是0x10e64,偏移地址是0x10,我们用addr2line在pc上找到具体出问题的地方:
zl@zl-Lenovo:~/vstdio-workspace/hi3536-webapp/backtrace-test/build$ arm-hisiv500-linux-addr2line -C -f -e trace_cpp 0x10e74
crash()
/home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:18
注意addr2line必须要用执行测试代码的交叉编译链里面的,从结果结合我们的测试程序,很明显打印的堆栈信息可以分析出出问题的文件,以及行号,以及出问题的接口。
其实对于异常崩溃的调试,可以通过上面的方法,崩溃的时候打印堆栈信息,然后结合工程源码来找到出问题的地方,另外其实还可以用linux非常非常强大的工具,gdb来运行程序调试。比如上面的应用,我们用gdb来运行:
/mnt/hi3536-workspace # gdb trace_cpp
GNU gdb (GDB) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-hisiv500-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from trace_cpp...done.
(gdb) r
Starting program: /mnt/hi3536-workspace/trace_cpp
Program received signal SIGSEGV, Segmentation fault.
0x00010e74 in crash () at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:18
18 /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp: No such file or directory.
(gdb) pt
The history is empty.
(gdb) bt
#0 0x00010e74 in crash () at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:18
#1 0x00010e98 in foo2 () at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:24
#2 0x00010ea8 in foo1 () at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:29
#3 0x00010fc4 in main (argc=1, argv=0xbefffe04) at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:61
(gdb)
可以很清楚的看到出问题的接口,以及位于哪个文件的哪一行。