跳转到内容

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。可能的替代方案:libbacktracehttp://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);
    }
}


C++ 包装器

[编辑 | 编辑源代码]

使用这个简单的类,获取堆栈只需一行代码

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&lt;40ul&gt;::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&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::init()+0x722
        At ??:0
        In binaries/bin/papitest
0x426815 lpt::papi::library&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::library()+0x5b
        At ??:0
        In binaries/bin/papitest
0x4265d4 lpt::singleton&lt;lpt::papi::library&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt; &gt;::singleton()+0x18
        At ??:0
        In binaries/bin/papitest
0x42624e lpt::singleton&lt;lpt::papi::library&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt; &gt;::instance()+0x42
        At ??:0
        In binaries/bin/papitest
0x425b47 lpt::papi::thread&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::init()+0x25
        At ??:0
        In binaries/bin/papitest
0x425502 lpt::papi::thread&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::thread()+0x2e
        At ??:0
        In binaries/bin/papitest
0x424554 lpt::singleton&lt;lpt::papi::thread&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt; &gt;::singleton()+0x18
        At ??:0
        In binaries/bin/papitest
0x4230a5 lpt::singleton&lt;lpt::papi::thread&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt; &gt;::instance()+0x42
        At ??:0
        In binaries/bin/papitest
0x421d33 lpt::papi::counters&lt;lpt::papi::stdout_print, lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::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&lt;40ul&gt;::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&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::init()+0x722
        At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:371
        In binaries/bin/papitest
[0x426983] lpt::papi::library&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::library()+0x5b
        At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:289
        In binaries/bin/papitest
[0x426742] lpt::singleton&lt;lpt::papi::library&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt; &gt;::singleton()+0x18
        At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/singleton.hpp:19
        In binaries/bin/papitest
[0x4263bc] lpt::singleton&lt;lpt::papi::library&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt; &gt;::instance()+0x42
        At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/singleton.hpp:28
        In binaries/bin/papitest
[0x425cb5] lpt::papi::thread&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::init()+0x25
        At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:433
        In binaries/bin/papitest
[0x425670] lpt::papi::thread&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::thread()+0x2e
        At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/papi.hpp:409
        In binaries/bin/papitest
[0x4246c2] lpt::singleton&lt;lpt::papi::thread&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt; &gt;::singleton()+0x18
        At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/singleton.hpp:19
        In binaries/bin/papitest
[0x423213] lpt::singleton&lt;lpt::papi::thread&lt;lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt; &gt;::instance()+0x42
        At /home/amelinte/projects/lpt/lpt/lpt/include/lpt/singleton.hpp:28
        In binaries/bin/papitest
[0x421ea1] lpt::papi::counters&lt;lpt::papi::stdout_print, lpt::papi::counting_per_thread, lpt::papi::multiplex_none&gt;::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 中

[编辑 | 编辑源代码]

一个在 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
华夏公益教科书