打印异常堆栈_backtrace打印函数调用栈

本文介绍了如何在VPP代码中利用`unix_signal_handler`捕获异常信号,如SIGSEGV,以打印调用栈信息。通过`backtrace`函数获取当前线程的函数调用堆栈,并借助`journalctl`查询日志。同时,提出了自定义`myassert`宏以更好地定位代码中的异常问题。
摘要由CSDN通过智能技术生成

微信公众号:DPDK VPP源码分析
关注公众号可了解更多的学习资源,请留言:资料。问题或建议,请公众号留言;如果你觉得内容对你有帮助,欢迎赞赏[1]

7f88418ff6f733e1b704dd28e888ef44.png

异常调用栈信息跟踪

vpp代码中设置捕捉异常信号的函数unix_signal_handler,对一些信号SIGSEGV、SIGABRT、SIGILL等等会打印出异常的调用栈信息,方便我们定位问题。异常调用栈信息可以在系统日志中查询。通常我会使用journalctl -n xxx 来查询日志的打印。

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

int backtrace(void **buffer,int size)  

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

下面是vpp代码中clib_backtrace函数的定义,。

/* 使用 glibc backtrace 函数打印调用栈信息 */
#include 

uwordclib_backtrace (uword * callers, uword max_callers, uword n_frames_to_skip){
  int size;
  void *array[20];
  /* Also skip current frame. 跳过本函数*/
  n_frames_to_skip += 1;

  size = clib_min (ARRAY_LEN (array), max_callers + n_frames_to_skip);

  size = backtrace (array, size);

  uword i;

  for (i = 0; i     {
      if (i >= n_frames_to_skip)
    callers[i - n_frames_to_skip] = pointer_to_uword (array[i]);
    }

  if (i     return 0;
  else
    return i - n_frames_to_skip;
}

下面截取

 /* Address of callers: outer first, inner last. 
    最多打印15个*/
 uword callers[15];
 uword n_callers = clib_backtrace (callers, ARRAY_LEN (callers), 0);
 int i;
 for (i = 0; i {
    vec_reset_length (syslog_msg);
    syslog_msg = format (syslog_msg, "#%-2d 0x%016lx %U%c", i, callers[i],
    format_clib_elf_symbol_with_address, callers[i], 0);
/*format_clib_elf_symbol_with_address会将根据函数地址打印出函数名称,具体底层实现待跟踪*/
/*打印系统日志*/
syslog (LOG_ERR | LOG_DAEMON, "%s", syslog_msg);

个人测试举例

我们参考vpp的ASSERT可以写个myassert 用来对代码中异常分支进行捕捉,方便定位问题。

#include
#include 
#include 
#include 
#include 
#include 

#define myassert(flg)\
do{\
    int pid = getpid();\if(!flg)\
    {\
        printf("\r\n file:%s,Line:%d,fuc:%s\r\n",__FILE__,__LINE__,__FUNCTION__);\
        kill(pid,SIGUSR1);\
    }\
}while(0)
void dump(int signo){
    void *buffer[30] = {0};
    size_t size = 0;
    size_t i = 0;
    char ** strings = NULL;

    size = backtrace(buffer,30);

    strings = backtrace_symbols(buffer,size);
    if(strings == NULL)
    {
        return;
    }

    for (i = 0 ; i    {
        printf("%s\n",strings[i]);  
    }

    free(strings);

    //exit(0);
}

void trace_3(){
    int * p = NULL;
        /*为空时表示是异常,触发函数调用栈打印*/
    myassert((p != NULL));

}

void trace_2(){
    trace_3();
}

void trace_1(){
    trace_2();
}

int main(){
    char c = 0;
    /*用户捕捉myassert 信号*/
    signal(SIGUSR1,dump);

    /*捕捉assert 异常信号*/
    signal(SIGABRT,dump);

    trace_1();
    c = getchar();
    assert(0);

    return;
}

代码运行后打印如下:

[jsh@localhost work]$ gcc backtrace.c -g -rdynamic
[jsh@localhost work]$
[jsh@localhost work]$ ./a.out
 /*这里打印具体的函数名和代码行*/ 
 file:backtrace.c,Line:52,fuc:trace_3
./a.out(dump+0x4c) [0x8048820]
[0xac9400]
./a.out(trace_2+0xb) [0x80488e1]
./a.out(trace_1+0xb) [0x80488ee]
./a.out(main+0x3b) [0x804892b]
/lib/libc.so.6(__libc_start_main+0xe6) [0x182ce6]
./a.out() [0x8048741]
/*这里是系统assert的打印如下*/
a.out: backtrace.c:77: main: Assertion `0' failed.
./a.out(dump+0x4c) [0x8048820]
[0xac9400]
/lib/libc.so.6(abort+0x17a) [0x1983aa]
/lib/libc.so.6(-0xff52f215) [0x18fdeb]
/lib/libc.so.6(-0xff52f15a) [0x18fea6]
./a.out() [0x8048958]
/lib/libc.so.6(__libc_start_main+0xe6) [0x182ce6]
./a.out() [0x8048741]
Aborted (core dumped)

总结

本文简单介绍了定位vpp异常的一点思路,可以通过journalctl -n xxx 来找到异常调用站信息来定位BUG。当然journalctl命令还是很强大的。查看所有内核和应用的日志,并且提供了很多可以检索的参数。

   你们关于定位异常有什么高效的方法,欢迎交流

d5deb7378a9e42f713396b82b77b6d45.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值