您的位置:首页 > 运维架构 > Linux

linux下追踪函数调用堆栈backtrace

2014-06-18 21:06 701 查看
一般察看函数运行时堆栈的方法是使用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中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址

该函数的返回值是通过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解决大问题()

善用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 <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <ucontext.h>
#include <dlfcn.h>
#include <execinfo.h>
#ifndef NO_CPP_DEMANGLE
#include <cxxabi.h>
#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->si_errno);
  fprintf(stderr, "info.si_code  = %d (%s)/n", info->si_code, si_codes[info->si_code]);
  fprintf(stderr, "info.si_addr  = %p/n", info->si_addr);
  for(i = 0; i < NGREG; i++)
    fprintf(stderr, "reg[%02d]       = 0x" REGFORMAT "/n", i, ucontext->uc_mcontext.gregs[i]);

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

  fprintf(stderr, "Stack trace:/n");
  while(bp && 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 && tmp)
      symname = tmp;
#endif

    fprintf(stderr, "% 2d: %p <%s+%u> (%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 && !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 < 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) < 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 <string.h>

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

int main() {
  return die();
}



下面来编译上面的main.c程序看看将会产生什么样的信息呢,不过要注意的就是如果要在你的程序里引用sigsegv.h、sigsegv.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 <die+16> (/data/codes/c/test/backtraces/test)

2: 0x8048a16 <main+19> (/data/codes/c/test/backtraces/test)

End of stack trace

/data/codes/c/test/backtraces $

下面用gdb来看看出错的地方左右的代码:
/data/codes/c/test/backtraces $ gdb ./test

gdb> disassemble die+16

Dump of assembler code for function die:

0x080489dc <die+0>: push %ebp

0x080489dd <die+1>: mov %esp,%ebp

0x080489df <die+3>: sub $0x10,%esp

0x080489e2 <die+6>: movl $0x0,0xfffffffc(%ebp)

0x080489e9 <die+13>: mov 0xfffffffc(%ebp),%eax

0x080489ec <die+16>: movl $0x6e6e6f67,(%eax)

0x080489f2 <die+22>: movw $0x7265,0x4(%eax)

0x080489f8 <die+28>: movb $0x0,0x6(%eax)

0x080489fc <die+32>: mov $0x0,%eax

0x08048a01 <die+37>: leave

0x08048a02 <die+38>: ret

End of assembler dump.

gdb>

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

gdb> break *die+16

Breakpoint 1 at 0x80489f2: file main.c, line 6.

gdb> list *die+16

0x80489f2 is in die (main.c:6).

1 #include "sigsegv.h"

2 #include <string.h>

3

4 int die() {

5 char *err = NULL;

6 strcpy(err, "gonner");

7 return 0;

8 }

9

10 int main() {

gdb>

现在看看定位错误将会多么方便,上面的调试指令中list之前break不是必须的,只是让你可以看到break其实就已经指出了哪一行代码导致 Segmentation fault了。如果你要发布你的程序你一般会为了减少体积不会附带调试信息的(也就是不加-ggdb -g参数),不过没关系,你一样可以得到上面stack-trace信息,然后你调试之前只要加上调试信息即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: