Linux 应用程序调试技术/调用堆栈
外观
有时我们需要程序中某个特定点的调用堆栈。以下是获取基本堆栈信息的 API 函数
#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);
#include <cxxabi.h>
char* __cxa_demangle(const char* __mangled_name, char* __output_buffer, size_t* __length, int* __status);
#include <dlfcn.h>
int dladdr(void *addr, Dl_info *info);
注意
- C++ 符号仍然被混淆。使用 abi::__cxa_demangle() 或类似方法。
- 这些函数中的一些会分配内存 - 可能是临时性的,也可能是明确的 - 如果程序已经不稳定,这可能是一个问题。
- 这些函数中的一些会获取锁(例如dladdr()).
- backtrace*在某些平台(例如 x86_64)上是昂贵的调用,调用堆栈越深,调用越昂贵。
- dladdr无法解析任何未导出的符号。因此,要充分利用它
- 链接到-rdynamic和-ldl
要提取更多信息(文件、行号),请使用 libbfd。可能的替代方案:libbacktrace; http://libcsdbg.sourceforge.net/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <execinfo.h>
#include <signal.h>
#include <bfd.h>
#include <unistd.h>
/* globals retained across calls to resolve. */
static bfd* abfd = 0;
static asymbol **syms = 0;
static asection *text = 0;
static void resolve(char *address) {
if (!abfd) {
char ename[1024];
int l = readlink("/proc/self/exe",ename,sizeof(ename));
if (l == -1) {
perror("failed to find executable\n");
return;
}
ename[l] = 0;
bfd_init();
abfd = bfd_openr(ename, 0);
if (!abfd) {
perror("bfd_openr failed: ");
return;
}
/* oddly, this is required for it to work... */
bfd_check_format(abfd,bfd_object);
unsigned storage_needed = bfd_get_symtab_upper_bound(abfd);
syms = (asymbol **) malloc(storage_needed);
unsigned cSymbols = bfd_canonicalize_symtab(abfd, syms);
text = bfd_get_section_by_name(abfd, ".text");
}
long offset = ((long)address) - text->vma;
if (offset > 0) {
const char *file;
const char *func;
unsigned line;
if (bfd_find_nearest_line(abfd, text, syms, offset, &file, &func, &line) && file)
printf("file: %s, line: %u, func %s\n",file,line,func);
}
}
使用这个简单的类,获取堆栈只需一行代码
class call_stack
{
public:
static const int depth = 40;
typedef std::array<void *, depth> stack_t;
class const_iterator;
class frame
{
public:
frame(void *addr = 0)
: _addr(0)
, _dladdr_ret(false)
, _binary_name(0)
, _func_name(0)
, _demangled_func_name(0)
, _delta_sign('+')
, _delta(0L)
, _source_file_name(0)
, _line_number(0)
{
resolve(addr);
}
// frame(stack_t::iterator& it) : frame(*it) {} //C++0x
frame(stack_t::const_iterator const& it)
: _addr(0)
, _dladdr_ret(false)
, _binary_name(0)
, _func_name(0)
, _demangled_func_name(0)
, _delta_sign('+')
, _delta(0L)
, _source_file_name(0)
, _line_number(0)
{
resolve(*it);
}
frame(frame const& other)
{
resolve(other._addr);
}
frame& operator=(frame const& other)
{
if (this != &other) {
resolve(other._addr);
}
return *this;
}
~frame()
{
resolve(0);
}
std::string as_string() const
{
std::ostringstream s;
s << "[" << std::hex << _addr << "] "
<< demangled_function()
<< " (" << binary_file() << _delta_sign << "0x" << std::hex << _delta << ")"
<< " in " << source_file() << ":" << line_number()
;
return s.str();
}
const void* addr() const { return _addr; }
const char* binary_file() const { return safe(_binary_name); }
const char* function() const { return safe(_func_name); }
const char* demangled_function() const { return safe(_demangled_func_name); }
char delta_sign() const { return _delta_sign; }
long delta() const { return _delta; }
const char* source_file() const { return safe(_source_file_name); }
int line_number() const { return _line_number; }
private:
const char* safe(const char* p) const { return p ? p : "??"; }
friend class const_iterator; // To call resolve()
void resolve(const void * addr)
{
if (_addr == addr)
return;
_addr = addr;
_dladdr_ret = false;
_binary_name = 0;
_func_name = 0;
if (_demangled_func_name) {
free(_demangled_func_name);
_demangled_func_name = 0;
}
_delta_sign = '+';
_delta = 0L;
_source_file_name = 0;
_line_number = 0;
if (!_addr)
return;
_dladdr_ret = (::dladdr(_addr, &_info) != 0);
if (_dladdr_ret)
{
_binary_name = safe(_info.dli_fname);
_func_name = safe(_info.dli_sname);
_delta_sign = (_addr >= _info.dli_saddr) ? '+' : '-';
_delta = ::labs(static_cast<const char *>(_addr) - static_cast<const char *>(_info.dli_saddr));
int status = 0;
_demangled_func_name = abi::__cxa_demangle(_func_name, 0, 0, &status);
}
}
private:
const void* _addr;
const char* _binary_name;
const char* _func_name;
const char* _demangled_func_name;
char _delta_sign;
long _delta;
const char* _source_file_name; //TODO: libbfd
int _line_number;
Dl_info _info;
bool _dladdr_ret;
}; //frame
class const_iterator
: public std::iterator< std::bidirectional_iterator_tag
, ptrdiff_t
>
{
public:
const_iterator(stack_t::const_iterator const& it)
: _it(it)
, _frame(it)
{}
bool operator==(const const_iterator& other) const
{
return _frame.addr() == other._frame.addr();
}
bool operator!=(const const_iterator& x) const
{
return !(*this == x);
}
const frame& operator*() const
{
return _frame;
}
const frame* operator->() const
{
return &_frame;
}
const_iterator& operator++()
{
++_it;
_frame.resolve(*_it);
return *this;
}
const_iterator operator++(int)
{
const_iterator tmp = *this;
++_it;
_frame.resolve(*_it);
return tmp;
}
const_iterator& operator--()
{
--_it;
_frame.resolve(*_it);
return *this;
}
const_iterator operator--(int)
{
const_iterator tmp = *this;
--_it;
_frame.resolve(*_it);
return tmp;
}
private:
const_iterator();
private:
frame _frame;
stack_t::const_iterator _it;
}; //const_iterator
call_stack() : _num_frames(0)
{
_num_frames = ::backtrace(_stack.data(), depth);
assert(_num_frames >= 0 && _num_frames <= depth);
}
std::string as_string()
{
std::string s;
const_iterator itEnd = end();
for (const_iterator it = begin(); it != itEnd; ++it) {
s += it->as_string();
s += "\n";
}
return std::move(s);
}
virtual ~call_stack()
{
}
const_iterator begin() const { return _stack.cbegin(); }
const_iterator end() const { return stack_t::const_iterator(&_stack[_num_frames]); }
private:
stack_t _stack;
int _num_frames;
};
此类可与断言、日志记录或异常一起使用。
完整代码可在 LPT 网站 找到,它提供了三种符号解析器类型。此代码需要 C++11 编译器。
- 一个“基本”地址解析器,没有内存副作用
- 一个 libc 解析器
- 一个 libbfd 解析器
一个基于 boost 的调用堆栈实用程序集合版本可在 https://github.com/melintea/Boost-Call_stack 找到。
使用 libc 解析器解析的堆栈,嵌入到异常中,使用调试编译的代码将如下所示
Exception PAPI_add_event: Event exists, but cannot be counted due to hardware resource limits
At:
0x41e11f ??+0x41e11f
At ??:0
In binaries/bin/papitest
0x421ae0 lpt::stack::call_stack<40ul>::call_stack(bool)+0x52
At ??:0
In binaries/bin/papitest
0x420653 lpt::papi::papi_error::papi_error(std::string const&, int)+0x6f
At ??:0
In binaries/bin/papitest
0x426ffe lpt::papi::library<lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::init()+0x722
At ??:0
In binaries/bin/papitest
0x426815 lpt::papi::library<lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::library()+0x5b
At ??:0
In binaries/bin/papitest
0x4265d4 lpt::singleton<lpt::papi::library<lpt::papi::counting_per_thread, lpt::papi::multiplex_none> >::singleton()+0x18
At ??:0
In binaries/bin/papitest
0x42624e lpt::singleton<lpt::papi::library<lpt::papi::counting_per_thread, lpt::papi::multiplex_none> >::instance()+0x42
At ??:0
In binaries/bin/papitest
0x425b47 lpt::papi::thread<lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::init()+0x25
At ??:0
In binaries/bin/papitest
0x425502 lpt::papi::thread<lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::thread()+0x2e
At ??:0
In binaries/bin/papitest
0x424554 lpt::singleton<lpt::papi::thread<lpt::papi::counting_per_thread, lpt::papi::multiplex_none> >::singleton()+0x18
At ??:0
In binaries/bin/papitest
0x4230a5 lpt::singleton<lpt::papi::thread<lpt::papi::counting_per_thread, lpt::papi::multiplex_none> >::instance()+0x42
At ??:0
In binaries/bin/papitest
0x421d33 lpt::papi::counters<lpt::papi::stdout_print, lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::counters(std::string const&)+0xc9
At ??:0
In binaries/bin/papitest
0x41e206 tests()+0x67
At ??:0
In binaries/bin/papitest
0x41edb8 ??+0x11
At ??:0
In binaries/bin/papitest
0x2ad66a7b9ead ??+0xfd
At ??:0
In /lib/x86_64-linux-gnu/libc.so.6
0x41dea9 ??+0x41dea9
At ??:0
In binaries/bin/papitest
注意如何dladdr()无法解析某些帧。而使用 bfd 解析器解析的堆栈将显示文件和行号,并将解析所有帧
Exception PAPI_add_event: Event exists, but cannot be counted due to hardware resource limits
At:
[0x41e20f] backtrace+0x41e20f
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/detail/glibc_call_stack.hpp:61
In binaries/bin/papitest
[0x421c4e] lpt::stack::call_stack<40ul>::call_stack(bool)+0x52
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/call_stack.hpp:74
In binaries/bin/papitest
[0x4207a1] lpt::papi::papi_error::papi_error(std::string const&, int)+0x6f
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:112
In binaries/bin/papitest
[0x42716c] lpt::papi::library<lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::init()+0x722
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:371
In binaries/bin/papitest
[0x426983] lpt::papi::library<lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::library()+0x5b
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:289
In binaries/bin/papitest
[0x426742] lpt::singleton<lpt::papi::library<lpt::papi::counting_per_thread, lpt::papi::multiplex_none> >::singleton()+0x18
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/singleton.hpp:19
In binaries/bin/papitest
[0x4263bc] lpt::singleton<lpt::papi::library<lpt::papi::counting_per_thread, lpt::papi::multiplex_none> >::instance()+0x42
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/singleton.hpp:28
In binaries/bin/papitest
[0x425cb5] lpt::papi::thread<lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::init()+0x25
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:433
In binaries/bin/papitest
[0x425670] lpt::papi::thread<lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::thread()+0x2e
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:409
In binaries/bin/papitest
[0x4246c2] lpt::singleton<lpt::papi::thread<lpt::papi::counting_per_thread, lpt::papi::multiplex_none> >::singleton()+0x18
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/singleton.hpp:19
In binaries/bin/papitest
[0x423213] lpt::singleton<lpt::papi::thread<lpt::papi::counting_per_thread, lpt::papi::multiplex_none> >::instance()+0x42
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/singleton.hpp:28
In binaries/bin/papitest
[0x421ea1] lpt::papi::counters<lpt::papi::stdout_print, lpt::papi::counting_per_thread, lpt::papi::multiplex_none>::counters(std::string const&)+0xc9
At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:646
In binaries/bin/papitest
[0x41e2f6] tests()+0x67
At /home/amelinte/projects/lpt/lpt/tests/papitest.cpp:51
In binaries/bin/papitest
[0x41eea8] main+0x11
At /home/amelinte/projects/lpt/lpt/tests/papitest.cpp:176
In binaries/bin/papitest
[0x2b5f7aa1bead] __libc_start_main+0xfd
At ??:0
In /lib/x86_64-linux-gnu/libc.so.6
[0x41df99] _start+0x41df99
At ??:0
In binaries/bin/papitest
- 不要在异步中断(例如信号处理程序)中使用。
- 当程序不稳定时,它可能有效也可能无效 - 如果可能,最好使用核心转储。内存问题处理程序也是如此。
- 性能影响因平台和编译器版本而异。
一个在 gdb 中解析堆栈地址的预定义命令
define addrtosym
if $argc == 1
printf "[%u]: ", $arg0
#whatis/ptype EXPR
#info frame ADDR
info symbol $arg0
end
end
document addrtosym
Resolve the address (e.g. of one stack frame). Usage: addrtosym addr0
end