linux下追踪函数调用堆栈

一般察看函数运行时堆栈的方法是使用GDB之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。

在头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈

Function: int backtrace(void **buffer,int size)

该函数用与获取当前线程的调用堆栈,获取的信息将会被存放在buffer,它是一个指针列表。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小

buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址

注意某些编译器的优化选项对获取正确的调用堆栈有干扰,另外内联函数没有堆栈框架;删除框架指针也会使无法正确解析堆栈内容

Function: char ** backtrace_symbols (void *const *buffer, int size)

backtrace_symbols
将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的数组指针,size是该数组中的元素个数(backtrace的返回值)   
   
函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址

现在,只有使用ELF二进制格式的程序和苦衷才能获取函数名称和偏移地址.在其他系统,只有16进制的返回地址能被获取.另外,你可能需要传递相应的标志给链接器,以能支持函数名功能(比如,在使用GNU ld的系统中,你需要传递(-rdynamic))

该函数的返回值是通过malloc函数申请的空间,因此调用这必须使用free函数来释放指针
.

注意:如果不能为字符串获取足够的空间函数的返回值将会为
NULL

Function:void backtrace_symbols_fd (void *const *buffer, int size, int fd)

backtrace_symbols_fd
backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况


下面的例子显示了这三个函数的用法

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;

size = backtrace (array, 10);
strings = backtrace_symbols (array, size);

printf ("Obtained %zd stack frames./n", size);

for (i = 0; i < size; i++)
     printf ("%s/n", strings
);

free (strings);
}

/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)
{
print_trace ();
}

int
main (void)
{
dummy_function ();
return 0;
}

备注:void *const *buffer -- buffer指向char类型的常量指针的指针(很是拗口)

 

 

 

 

善用backtrace解决大问题()

 

 

程序在得到一个Segmentation fault这样的错误信息毫无保留地就跳出来了,遇到这样的问题让人很痛苦,查找问题不亚于你N多天辛苦劳累编写代码的难度。那么有没有更好的方法可以在产生SIGSEGV信号的时候得到调试可用的信息呢?看看下面的例程吧!

sigsegv.h

#ifndef __sigsegv_h__
#define __sigsegv_h__

#ifdef __cplusplus
extern "C" {
#endif

  int setup_sigsegv();

#ifdef __cplusplus
}
#endif

#endif /* __sigsegv_h__ */

 


sigsegv.c

#define _GNU_SOURCE
#include &lt;memory.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
#include &lt;signal.h&gt;
#include &lt;ucontext.h&gt;
#include &lt;dlfcn.h&gt;
#include &lt;execinfo.h&gt;
#ifndef NO_CPP_DEMANGLE
#include &lt;cxxabi.h&gt;
#endif

#if defined(REG_RIP)
# define SIGSEGV_STACK_IA64
# define REGFORMAT "%016lx"
#elif defined(REG_EIP)
# define SIGSEGV_STACK_X86
# define REGFORMAT "%08x"
#else
# define SIGSEGV_STACK_GENERIC
# define REGFORMAT "%x"
#endif

static void signal_segv(int signum, siginfo_t* info, void*ptr) {
  static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"};

  size_t i;
  ucontext_t *ucontext = (ucontext_t*)ptr;

#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
  int f = 0;
  Dl_info dlinfo;
  void **bp = 0;
  void *ip = 0;
#else
  void *bt[20];
  char **strings;
  size_t sz;
#endif

  fprintf(stderr, "Segmentation Fault!/n");
  fprintf(stderr, "info.si_signo = %d/n", signum);
  fprintf(stderr, "info.si_errno = %d/n", info-&gt;si_errno);
  fprintf(stderr, "info.si_code  = %d (%s)/n", info-&gt;si_code, si_codes[info-&gt;si_code]);
  fprintf(stderr, "info.si_addr  = %p/n", info-&gt;si_addr);
  for(i = 0; i &lt; NGREG; i++)
    fprintf(stderr, "reg[%02d]       = 0x" REGFORMAT "/n", i, ucontext-&gt;uc_mcontext.gregs[i]);

#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
# if defined(SIGSEGV_STACK_IA64)
  ip = (void*)ucontext-&gt;uc_mcontext.gregs[REG_RIP];
  bp = (void**)ucontext-&gt;uc_mcontext.gregs[REG_RBP];
# elif defined(SIGSEGV_STACK_X86)
  ip = (void*)ucontext-&gt;uc_mcontext.gregs[REG_EIP];
  bp = (void**)ucontext-&gt;uc_mcontext.gregs[REG_EBP];
# endif

  fprintf(stderr, "Stack trace:/n");
  while(bp &amp;& ip) {
    if(!dladdr(ip, &dlinfo))
      break;

    const char *symname = dlinfo.dli_sname;
#ifndef NO_CPP_DEMANGLE
    int status;
    char *tmp = __cxa_demangle(symname, NULL, 0, &status);

    if(status == 0 &amp;& tmp)
      symname = tmp;
#endif

    fprintf(stderr, "% 2d: %p &lt;%s+%u&gt; (%s)/n",
            ++f,
            ip,
            symname,
            (unsigned)(ip - dlinfo.dli_saddr),
            dlinfo.dli_fname);

#ifndef NO_CPP_DEMANGLE
    if(tmp)
      free(tmp);
#endif

    if(dlinfo.dli_sname &amp;& !strcmp(dlinfo.dli_sname, "main"))
      break;

    ip = bp[1];
    bp = (void**)bp[0];
  }
#else
  fprintf(stderr, "Stack trace (non-dedicated):/n");
  sz = backtrace(bt, 20);
  strings = backtrace_symbols(bt, sz);

  for(i = 0; i &lt; sz; ++i)
    fprintf(stderr, "%s/n", strings[i]);
#endif
  fprintf(stderr, "End of stack trace/n");
  exit (-1);
}

int setup_sigsegv() {
  struct sigaction action;
  memset(&action, 0, sizeof(action));
  action.sa_sigaction = signal_segv;
  action.sa_flags = SA_SIGINFO;
  if(sigaction(SIGSEGV, &action, NULL) &lt; 0) {
    perror("sigaction");
    return 0;
  }

  return 1;
}

#ifndef SIGSEGV_NO_AUTO_INIT
static void __attribute((constructor)) init(void) {
  setup_sigsegv();
}
#endif

 

main.c

#include "sigsegv.h"
#include &lt;string.h&gt;

int die() {
  char *err = NULL;
  strcpy(err, "gonner");
  return 0;
}

int main() {
  return die();
}

 

下面来编译上面的main.c程序看看将会产生什么样的信息呢,不过要注意的就是如果要在你的程序里引用sigsegv.hsigsegv.c得到堆栈信息的话记得加上-rdynamic -ldl参数。

/data/codes/c/test/backtraces $ gcc -o test -rdynamic -ldl -ggdb -g sigsegv.c main.c
/data/codes/c/test/backtraces $ ./test
Segmentation Fault!
info.si_signo = 11
info.si_errno = 0
info.si_code  = 1 (SEGV_MAPERR)
info.si_addr  = (nil)
reg[00]       = 0x00000033
reg[01]       = 0x00000000
reg[02]       = 0xc010007b
reg[03]       = 0x0000007b
reg[04]       = 0x00000000
reg[05]       = 0xb7fc8ca0
reg[06]       = 0xbff04c2c
reg[07]       = 0xbff04c1c
reg[08]       = 0xb7f8cff4
reg[09]       = 0x00000001
reg[10]       = 0xbff04c50
reg[11]       = 0x00000000
reg[12]       = 0x0000000e
reg[13]       = 0x00000006
reg[14]       = 0x080489ec
reg[15]       = 0x00000073
reg[16]       = 0x00010282
reg[17]       = 0xbff04c1c
reg[18]       = 0x0000007b
Stack trace:
 1: 0x80489ec &lt;die+16&gt; (/data/codes/c/test/backtraces/test)
 2: 0x8048a16 &lt;main+19&gt; (/data/codes/c/test/backtraces/test)
End of stack trace
/data/codes/c/test/backtraces $

 

下面用gdb来看看出错的地方左右的代码:

/data/codes/c/test/backtraces $ gdb ./test
gdb&gt; disassemble die+16
Dump of assembler code for function die:
0x080489dc &lt;die+0&gt;:     push   %ebp
0x080489dd &lt;die+1&gt;:     mov    %esp,%ebp
0x080489df &lt;die+3&gt;:     sub    $0x10,%esp
0x080489e2 &lt;die+6&gt;:     movl   $0x0,0xfffffffc(%ebp)
0x080489e9 &lt;die+13&gt;:    mov    0xfffffffc(%ebp),%eax
0x080489ec &lt;die+16&gt;:    movl   $0x6e6e6f67,(%eax)
0x080489f2 &lt;die+22&gt;:    movw   $0x7265,0x4(%eax)
0x080489f8 &lt;die+28&gt;:    movb   $0x0,0x6(%eax)
0x080489fc &lt;die+32&gt;:    mov    $0x0,%eax
0x08048a01 &lt;die+37&gt;:    leave 
0x08048a02 &lt;die+38&gt;:    ret   
End of assembler dump.
gdb&gt;

 

也可以直接break *die+16进行调试,看看在出错之前的堆栈情况,那么下面我们再来看看代码问题到底出在什么地方了。

/data/codes/c/test/backtraces $ gdb ./test
gdb&gt; break *die+16
Breakpoint 1 at 0x80489f2: file main.c, line 6.
gdb&gt; list *die+16
0x80489f2 is in die (main.c:6).
1       #include "sigsegv.h"
2       #include &lt;string.h&gt;
3      
4       int die() {
5         char *err = NULL;
6         strcpy(err, "gonner");
7         return 0;
8       }
9      
10      int main() {
gdb&gt;

 

现在看看定位错误将会多么方便,上面的调试指令中list之前break不是必须的,只是让你可以看到break其实就已经指出了哪一行代码导致 Segmentation fault了。如果你要发布你的程序你一般会为了减少体积不会附带调试信息的(也就是不加-ggdb -g参数),不过没关系,你一样可以得到上面stack-trace信息,然后你调试之前只要加上调试信息即可。

Logo

更多推荐