linux 应用异常堆栈捕获

linux 应用异常堆栈捕获

前言

概述

本文档讲述两种应用死机堆栈的方法,堆栈跟踪可以帮助开发人员快速定位导致死机的代码位置,从而更快地解决问题。方便后续有工程师在其他应用代码出现死机时,可以参考此文档快速的进行代码移植。

1 backtrace打印堆栈

1.1 获取程序堆栈API介绍

int backtrace(void **buffer, int size);

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

char **backtrace_symbols(void _const _buffer, int size);

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

void backtrace_symbols_fd(void _const _buffer, int size, int fd);

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

建议使用backtrace_symbols_fd函数,因为backtrace()和backtrace_symbols()都是不可重入的函数,原因在于它们内部都使用了malloc函数,而malloc内部是有锁的。假设某个线程正在调用malloc分配堆空间,此时程序捕捉到信号发生中断,而信号处理函数中恰好也调用了malloc函数就会发生死锁导致该线程hang住,随后所有调用malloc的其他线程也会相继hang住。

1.2 编译选项

1.2.1 编译的时候需要加上参数:-g -rdynamic

-g 编译生成的可执行文件会包含源代码的行号、变量名和调试符号,这些信息对于调试器来说非常有用;

-rdynamic 参数用于将所有符号(包括动态库中的符号)都添加到动态符号表中,使得程序在运行时可以通过 dlopen 和 dlsym 等函数来动态加载和查找符号;

发布正式固件时,需要加上-strip,用于减少程序的大小;

1.2.2 嵌入式系统还需要加上参数: -funwind-tables -ffunction-sections

-funwind-tables参数会生成用于异常处理和栈展开的表格,对于调试和异常处理非常有用;

-ffunction-sections选项告诉编译器将每个函数放入一个独立的代码段中,这对于优化和减小可执行文件的大小非常有帮助;

Linux常见的死机信号

信号名编号值描述系统默认处理方式
SIGINT2正常终止进程(ctrl + c)终止进程
SIGQUIT3异常终止进程终止进程,并允许产生core文件
SIGABRT6异常终止进程终止进程,并允许产生core文件
SIGBUS7异常终止进程,硬件错误终止进程,并允许产生core文件
SIGFPE8错误的算术运算,比如除数为0终止进程
SIGKILL9异常终止进程,非法指令终止进程
SIGSEGV11异常终止进程,访问无效的地址终止进程,并允许产生core文件
SIGPIPE13向没有读权限的管道文件写操作终止进程
SIGTERM15kill PID时默认发送的信号终止进程

addr2line 工具使用

用addr2line 定位程序地址对应的源代码位置,使用的参数如下所示:

addr2line -C -f -p -e 程序名(strip前的程序)地址

arm-linux-gnueabihf-addr2line.exe -C -p -e  test_segment 0x12df2
H:/eg/debugger/src/demo.c:81

未打印完整堆栈的注意事项:

  • backtrace 实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On参数)或加入了栈指针优化参数-fomit-frame-pointer后多将不能正确得到程序栈信息;
  • backtrace_symbols 的实现需要符号名称的支持,在gcc编译过程中需要加入-rdynamic参数;
  • 内联函数没有栈帧,它在编译过程中被展开在调用的位置;
  • 尾调用优化 (Tail-call Optimization)将复用当前函数栈,而不再生成新的函数栈,这将导致栈信息不能正确被获取

2 libunwind

库源码仓库

https://github.com/libunwind/libunwind
编译完后用libunwind.a libunwind-ptrace.a libunwind-arm.a和相关的头文件

debuggerd流程图

需要单独创建一个debuggerd进程用来接收奔溃进程的pid,并将死机信息打印出来和存储在flash中;

参考demo移植

代码目录结构

后台运行编译好的debuggerd,需要定位死机的进程开机调用signal_handler_task()进行信号处理的初始化即可;

3 测试验证

写只读数据段:

  • 测试代码如下
void write_rodata(void)
{
    /*
    ** 字符串常量保存在rodata区域
    */
    char *s = "hello world";
    printf("write rodata\n");
    s[0] = 'H';
    printf("write rodata end\n");
}
  • bracktrace 运行结果
[backtrace:] Signal Handler: SIG is [11] Segmentation fault
[backtrace:] Stack information:
[backtrace:] backtrace() returned 5 addresses
./backtrace[0x10da0]
/lib/libc.so.6(+0x25140)[0xb6da2140]
./backtrace(write_rodata+0x1f)[0x1102c]
./backtrace(main+0x83)[0x112c0]
/lib/libc.so.6(__libc_start_main+0x9d)[0xb6d938ae]
  • debuggerd 运行结果
############## Thread Info ###################
#### tid: 821, thread name: test_segment #### pid: 821, process name: ./test_segment#### dump time at 2023-12-24 18:04:54

############## Fault Addr ###################
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 00023fd0
[DumpRegisters] iov_len: 72
    x0  000000000000000d  x1  0000000000000000  x2  0000000000000048  x3  0000000000023fd0
    x4  00000000be9339b8  x5  0000000000000000  x6  0000000000000000  x7  00000000be933978
    x8  0000000000000000  x9  0000000000000000  x10 00000000b6fec000  x11 0000000000000000
    x12 0000000000000000  x13 00000000be933978  x14 00000000b6e4d6d1  x15 0000000000012ce8

############## Backtrace ###################
23-12-24 18:04:54 [W][786][ 4533.346525] [dump_thread] dump thread 821

"test_segment" sysTid=821
#### 0x12ce8 : (write_rodata+0x1f) [0x12ce8]
#### 0xb6e4d6d1 : (_IO_file_write+0x5f) [0xb6e4d6d1]
#### 0x2 : (+0x5f) [0x2]
  • 结果分析
    bracktrace 和 debuggerd 通过栈调用日志直接定位问题行数

访问不属于进程地址空间的内存:

  • 测试代码如下
void write_kernel_memory(void)
{
    /*
    ** 0xC0000fff是内核地址
    */
    printf("write kernel_memory\n");
    int* p = (int*)0xC0000fff;
    *p = 10;
}
  • bracktrace 运行结果
[backtrace:] Signal Handler: SIG is [11] Segmentation fault
[backtrace:] Stack information:
[backtrace:] backtrace() returned 5 addresses
./backtrace[0x10da0]
/lib/libc.so.6(+0x25140)[0xb6d8c140]
./backtrace(write_kernel_memory+0x1f)[0x11062]
./backtrace(main+0x89)[0x112c6]
/lib/libc.so.6(__libc_start_main+0x9d)[0xb6d7d8ae]
Segmentation fault
  • debuggerd 运行结果
############## Thread Info ###################
#### tid: 823, thread name: test_segment #### pid: 823, process name: ./test_segment#### dump time at 2023-12-24 18:07:27

############## Fault Addr ###################
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr c0000fff
[DumpRegisters] iov_len: 72
    x0  0000000000000014  x1  0000000000000000  x2  000000000000000a  x3  00000000c0000fff
    x4  00000000bec839b8  x5  0000000000000000  x6  0000000000000000  x7  00000000bec83978
    x8  0000000000000000  x9  0000000000000000  x10 00000000b6f33000  x11 0000000000000000
    x12 0000000000000000  x13 00000000bec83978  x14 00000000b6d946d1  x15 0000000000012d1e

############## Backtrace ###################
23-12-24 18:07:27 [W][786][ 4686.455577] [dump_thread] dump thread 823

"test_segment" sysTid=823
#### 0x12d1e : (write_kernel_memory+0x1f) [0x12d1e]
#### 0x1309b : (main+0xb2) [0x1309b]
#### 0xb6d5e8af : (__libc_start_main+0x9e) [0xb6d5e8af]
#### 0x12c09 : (_start+0x20) [0x12c09]
  • 结果分析
    bracktrace 和 debuggerd通过栈调用日志直接定位问题行数

访问不存在的内存:

  • 测试代码如下
int write_null_memory(void)
{
    char *p = NULL;

    printf("write null_memory \n");
    *p = 123;
    printf("write null_memory end\n");
}
  • bracktrace 运行结果
[backtrace:] Signal Handler: SIG is [11] Segmentation fault
[backtrace:] Stack information:
[backtrace:] backtrace() returned 5 addresses
./backtrace[0x10da0]
/lib/libc.so.6(+0x25140)[0xb6e08140]
./backtrace(write_null_memory+0x19)[0x11086]
./backtrace(main+0x8f)[0x112cc]
/lib/libc.so.6(__libc_start_main+0x9d)[0xb6df98ae]
  • debuggerd 运行结果
############## Thread Info ###################
#### tid: 826, thread name: test_segment #### pid: 826, process name: ./test_segment#### dump time at 2023-12-24 18:09:30

############## Fault Addr ###################
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000
[DumpRegisters] iov_len: 72
    x0  0000000000000013  x1  0000000000000000  x2  000000000000007b  x3  0000000000000000
    x4  00000000bec249b8  x5  0000000000000000  x6  0000000000000000  x7  00000000bec24978
    x8  0000000000000000  x9  0000000000000000  x10 00000000b6f4f000  x11 0000000000000000
    x12 0000000000000000  x13 00000000bec24978  x14 00000000b6db06d1  x15 0000000000012d42

############## Backtrace ###################
23-12-24 18:09:30 [W][786][ 4809.782866] [dump_thread] dump thread 826

"test_segment" sysTid=826
#### 0x12d42 : (write_null_memory+0x19) [0x12d42]
#### 0xb6db06d1 : (_IO_file_write+0x5f) [0xb6db06d1]
#### 0x2 : (+0x5f) [0x2]
  • 结果分析
    bracktrace 和 debuggerd 通过栈调用日志直接定位问题行数

访问已释放的栈内存:

  • 测试代码如下
int* gp(void)
{
    int i = 42;
    int *p = &i;
    return p;
}
int gv(int *p)
{
    return *p;
}
int stack_use_after_return(void)
{
    printf("stack use_after_return \n");
    return gv(gp());
}
  • bracktrace 运行结果
未死机
  • debuggerd 运行结果
未死机
  • 结果分析
    访问已释放但未回收的栈内存不一定会发生SIGSEGV

访问未分配的栈内存:

  • 测试代码如下
int* gp2(void)
{
    int buf[8192];
    memset (buf, 123, sizeof(buf));
    return buf;
}
int stack_overflow(void)
{
    printf("stack overflow\n");
    int *p = gp2();
    return p[5000];
}
  • bracktrace 运行结果
[backtrace:] Signal Handler: SIG is [11] Segmentation fault
[backtrace:] Stack information:
[backtrace:] backtrace() returned 5 addresses
./backtrace[0x10da0]
/lib/libc.so.6(+0x25140)[0xb6d45140]
./backtrace(stack_overflow+0x1f)[0x11136]
./backtrace(main+0x9b)[0x112d8]
/lib/libc.so.6(__libc_start_main+0x9d)[0xb6d368ae]
Segmentation fault
  • debuggerd 运行结果
############## Thread Info ###################
#### tid: 834, thread name: test_segment #### pid: 834, process name: ./test_segment#### dump time at 2023-12-24 18:16:38

############## Fault Addr ###################
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00004e20
[DumpRegisters] iov_len: 72
    x0  0000000000000000  x1  000000007b7b7b7b  x2  00000000ffffffff  x3  0000000000004e20
    x4  00000000bec449b8  x5  0000000000000000  x6  0000000000000000  x7  00000000bec44978
    x8  0000000000000000  x9  0000000000000000  x10 00000000b6f75000  x11 0000000000000000
    x12 000000007b7b7b7b  x13 00000000bec44978  x14 0000000000012dc7  x15 0000000000012df2

############## Backtrace ###################
23-12-24 18:16:38 [W][786][ 5237.900177] [dump_thread] dump thread 834

"test_segment" sysTid=834
#### 0x12df2 : (stack_overflow+0x1f) [0x12df2]
#### 0xbec44c5f : (+0x1f) [0xbec44c5f]
  • 结果分析
    bracktrace 和 debuggerd通过栈调用日志直接定位问题行数

堆访问溢出:

  • 测试代码如下
int heap_overflow(void)
{
    printf("heap overflow\n");
    char* c;
    int i = 0;
    c = malloc(1);
    while (1) {
    c += i * 1024;
    *c = 'a';
    printf("overflow %dK\n", i);
    i++;
    }
}
  • bracktrace 运行结果
...
overflow 15K
[backtrace:] Signal Handler: SIG is [11] Segmentation fault
[backtrace:] Stack information:
[backtrace:] backtrace() returned 5 addresses
./backtrace[0x10da0]
/lib/libc.so.6(+0x25140)[0xb6de8140]
./backtrace(heap_overflow+0x2f)[0x11170]
./backtrace(main+0xa1)[0x112de]
/lib/libc.so.6(__libc_start_main+0x9d)[0xb6dd98ae]
  • debuggerd 运行结果
############## Thread Info ###################
#### tid: 836, thread name: test_segment #### pid: 836, process name: ./test_segment#### dump time at 2023-12-24 18:18:38

############## Fault Addr ###################
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 000734a0
[DumpRegisters] iov_len: 72
    x0  000000000000000d  x1  0000000000000000  x2  0000000000000061  x3  00000000000734a0
    x4  00000000be9b09b8  x5  0000000000000000  x6  0000000000000000  x7  00000000be9b0978
    x8  0000000000000000  x9  0000000000000000  x10 00000000b6fa8000  x11 0000000000000000
    x12 0000000000000000  x13 00000000be9b0978  x14 0000000000012e3d  x15 0000000000012e2c

############## Backtrace ###################
23-12-24 18:18:38 [W][786][ 5357.743673] [dump_thread] dump thread 836

"test_segment" sysTid=836
#### 0x12e2c : (heap_overflow+0x2f) [0x12e2c]
#### 0xbe9b0c5f : (+0x2f) [0xbe9b0c5f]
  • 结果分析
    不一定产生SIGSEGV,如果产生SIGSEGV时可以通过堆栈定位代码的行数

访问释放后的堆内存:

  • 测试代码如下
int use_afterd_free(int index)
{
    printf("use afterd_free\n");
    int *array = malloc(sizeof(int) * 100);
    for (int i = 0; i < 100; i++)
    array[i] = i;
    free(array);
    return array[index];
}
  • bracktrace 运行结果
未死机
  • debuggerd 运行结果
未死机
- 结果分析
不一定会发生SIGSEGV

函数跳转到非法地址上执行:

  • 测试代码如下
int fault_after_stack_overrun(void)
{
    printf("fault after_stack_overrun\n");
    char c;
    memset(&c, 0x55, 128);
    return c;
}
  • bracktrace 运行结果
[backtrace:] Signal Handler: SIG is [11] Segmentation fault
[backtrace:] Stack information:
[backtrace:] backtrace() returned 2 addresses
./backtrace[0x10da0]
/lib/libc.so.6(+0x25140)[0xb6d60140]
  • debuggerd 运行结果
############## Thread Info ###################
#### tid: 840, thread name: test_segment #### pid: 840, process name: ./test_segment#### dump time at 2023-12-24 18:21:06

############## Fault Addr ###################
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 55555554
[DumpRegisters] iov_len: 72
    x0  0000000000000055  x1  0000000055555555  x2  00000000ffffffff  x3  0000000000000055
    x4  00000000beaac9b8  x5  0000000000000000  x6  0000000000000000  x7  0000000055555555
    x8  0000000000000000  x9  0000000000000000  x10 00000000b6fb1000  x11 0000000000000000
    x12 0000000055555555  x13 00000000beaac988  x14 0000000000012eb9  x15 0000000055555554

############## Backtrace ###################
23-12-24 18:21:06 [W][786][ 5505.867491] [dump_thread] dump thread 840

"test_segment" sysTid=840
#### 0x55555554 : (+0xf5e) [0x55555554]

"signal_handler" sysTid=841
#### 0xb6e52a70 : (__poll+0x2f) [0xb6e52a70]
#### 0x13559 : (signal_handler_thread+0x4a) [0x13559]
#### 0xb6eb75d3 : (__pthread_get_minstack+0xf5e) [0xb6eb75d3]
23-12-24 18:21:06 [D][786][ 5505.881195] dump backtrace ...

############## maps ###################
00010000-00028000 r-xp 00000000 1f:04 19240      /root/test_segment
00037000-00038000 rw-p 00017000 1f:04 19240      /root/test_segment
00038000-00072000 rw-p 00000000 00:00 0          [heap]
b65c6000-b65c7000 ---p 00000000 00:00 0 
b65c7000-b6dc6000 rw-p 00000000 00:00 0 
b6dc6000-b6e9d000 r-xp 00000000 1f:03 125        /lib/libc-2.23.so
b6e9d000-b6eac000 ---p 000d7000 1f:03 125        /lib/libc-2.23.so
b6eac000-b6eae000 r--p 000d6000 1f:03 125        /lib/libc-2.23.so
b6eae000-b6eaf000 rw-p 000d8000 1f:03 125        /lib/libc-2.23.so
b6eaf000-b6eb2000 rw-p 00000000 00:00 0 
b6eb2000-b6ec3000 r-xp 00000000 1f:03 140        /lib/libpthread-2.23.so
b6ec3000-b6ed2000 ---p 00011000 1f:03 140        /lib/libpthread-2.23.so
b6ed2000-b6ed3000 r--p 00010000 1f:03 140        /lib/libpthread-2.23.so
b6ed3000-b6ed4000 rw-p 00011000 1f:03 140        /lib/libpthread-2.23.so
b6ed4000-b6ed6000 rw-p 00000000 00:00 0 
b6ed6000-b6eee000 r-xp 00000000 1f:03 131        /lib/libgcc_s.so.1
b6eee000-b6efd000 ---p 00018000 1f:03 131        /lib/libgcc_s.so.1
b6efd000-b6efe000 rw-p 00017000 1f:03 131        /lib/libgcc_s.so.1
b6efe000-b6f00000 r-xp 00000000 1f:03 129        /lib/libdl-2.23.so
b6f00000-b6f0f000 ---p 00002000 1f:03 129        /lib/libdl-2.23.so
b6f0f000-b6f10000 r--p 00001000 1f:03 129        /lib/libdl-2.23.so
b6f10000-b6f11000 rw-p 00002000 1f:03 129        /lib/libdl-2.23.so
b6f11000-b6f78000 r-xp 00000000 1f:03 132        /lib/libm-2.23.so
b6f78000-b6f87000 ---p 00067000 1f:03 132        /lib/libm-2.23.so
b6f87000-b6f88000 r--p 00066000 1f:03 132        /lib/libm-2.23.so
b6f88000-b6f89000 rw-p 00067000 1f:03 132        /lib/libm-2.23.so
b6f89000-b6fa1000 r-xp 00000000 1f:03 119        /lib/ld-2.23.so
b6fad000-b6fb0000 rw-p 00000000 00:00 0 
b6fb0000-b6fb1000 r--p 00017000 1f:03 119        /lib/ld-2.23.so
b6fb1000-b6fb2000 rw-p 00018000 1f:03 119        /lib/ld-2.23.so
bea8c000-beaad000 rw-p 00000000 00:00 0          [stack]
beeb6000-beeb7000 r-xp 00000000 00:00 0          [sigpage]
beeb7000-beeb8000 r--p 00000000 00:00 0          [vvar]
beeb8000-beeb9000 r-xp 00000000 00:00 0          [vdso]
ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]
  • 结果分析
    bracktrace 栈破坏导致函数返回到非法地址执行会发生SIGSEGV,无法通过栈调用定位到栈溢出点;
    debuggerd 栈破坏导致无法通过栈调用定位到栈溢出,但可以知道是哪个线程出现的死机

4 总结

在能产生死机信号并且没有破坏栈的情况下,可以捕获到完整的堆栈信息,进而通过堆栈信息来定位问题;
在栈被破坏的场景下,debuggerd比bracktrace能获取到是哪个线程导致的死机以及cpu的通用寄存器、线程虚拟内存的分布,获取到死机更多的相关信息,进而帮助问题的分析与定位;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值