backtrace函数与堆栈

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信息,然后你调试之前只要加上调试信息即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回溯算法是一种通过穷举所有可能的解来求解问题的算法。它通常用于解组合优化问题,其中需要在给约束条件下找到最优解。01背包问题是一个经典的组合优化问题,它要求在给定背包容量和一组物品的重量和价值的情况下,选择一些物品放入背包中,使得背包中物品的总价值最大,同时不能超过背包的容量。 回溯算法解决01背包问题的基本思想是通过递归的方式遍历所有可能的解空间,并在搜索过程中进行剪枝,以提高搜索效率。具体步骤如下: 1. 定义一个递归函数backtrack,该函数接受当前背包容量、当前物品索引和当前背包中物品的总价值作为参数。 2. 在递归函数中,首先判断当前物品索引是否超过物品总数或者当前背包容量是否小于等于0,如果是,则返回当前背包中物品的总价值。 3. 如果不满足上述条件,则有两种情况: - 将当前物品放入背包中,更新背包容量和物品总价值,并递归调用backtrack函数。 - 不将当前物品放入背包中,直接递归调用backtrack函数。 4. 在递归调用后,比较两种情况的结果,返回较大的总价值作为当前背包中物品的最大总价值。 下面是一个使用回溯算法解决01背包问题的Python示例代码: ```python def backtrack(capacity, weights, values, index, total_value): if index >= len(weights) or capacity <= 0: return total_value # 将当前物品放入背包中 if capacity >= weights[index]: total_value1 = backtrack(capacity - weights[index], weights, values, index + 1, total_value + values[index]) else: total_value1 = 0 # 不将当前物品放入背包中 total_value2 = backtrack(capacity, weights, values, index + 1, total_value) return max(total_value1, total_value2) # 示例数据 capacity = 10 weights = [2, 3, 4, 5] values = [3, 4, 5, 6] max_value = backtrack(capacity, weights, values, 0, 0) print("Max value: ", max_value) ``` 这段代码中,我们定义了一个backtrack函数来求解01背包问题。在示例数据中,背包容量为10,物品的重量和价值分别为[2, 3, 4, 5]和[3, 4, 5, 6]。运行代码后,将输出最大总价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值