首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >backtrace打印函数调用栈

backtrace打印函数调用栈

作者头像
dpdk-vpp源码解读
发布2023-03-07 17:16:50
发布2023-03-07 17:16:50
2K0
举报
文章被收录于专栏:DPDK VPP源码分析DPDK VPP源码分析

异常调用栈信息跟踪

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

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

代码语言:javascript
复制
int backtrace(void **buffer,int size)  

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

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

代码语言:javascript
复制
/* 使用 glibc backtrace 函数打印调用栈信息 */
#include <execinfo.h>

uword
clib_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 < max_callers + n_frames_to_skip && i < size; i++)
    {
      if (i >= n_frames_to_skip)
    callers[i - n_frames_to_skip] = pointer_to_uword (array[i]);
    }

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

下面截取

代码语言:javascript
复制
 /* 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 < n_callers; 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 用来对代码中异常分支进行捕捉,方便定位问题。

代码语言:javascript
复制
#include<stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <execinfo.h>
#include <signal.h>
#include <assert.h>

#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< size; 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;
}

代码运行后打印如下:

代码语言:javascript
复制
[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命令还是很强大的。查看所有内核和应用的日志,并且提供了很多可以检索的参数。

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DPDK VPP源码分析 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 个人测试举例
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档