0


Linux上段错误(SegFault)的9种实用调试方法

引言:什么是段错误

每个在Linux环境下工作的程序员,都遇到过**段错误(segmentation fault)**。

所谓段错误,本质上是程序访问了非法内存地址而引起的一种错误类型。

导致程序访问非法地址的原因有很多,如野指针、内存被踩、栈溢出、访问没有权限的内存等。

之前更新调试专题文章时,有朋友问到段错误的调试方法,我承诺会更新文章专门介绍,本文就是来填这个坑的。

本文将介绍9种非常实用的段错误调试方法。

1. 日志

日志是一种非常实用的调试手段,我们可以从系统日志中获得很多非常有用的信息,从而反推问题出现的前后系统中究竟发生了什么异常状况。

printf可能是最简单的日志记录方法,大家都懂的,不再赘述。

2. GDB

GDB的强大无需多言,对于段错误,利用GDB很容易就能定位到触发问题的那一行代码。如下图示例代码:

  1. void test_3(int *p)
  2. {
  3. *p = 1;
  4. }
  5. void test_2(int *p)
  6. {
  7. test_3(p);
  8. }
  9. void test_1(int *p)
  10. {
  11. test_2(p);
  12. }
  13. int main(int argc, char *argv[])
  14. {
  15. int *p = (int *)0x12345678;
  16. test_1(p);
  17. return 0;
  18. }

编译时加上-g选项:

  1. gcc -g test.c -o test

在GDB中运行程序:

  1. root@ubuntu:debug# gdb test
  2. Reading symbols from test...
  3. (gdb) r
  4. Starting program: /opt/data/workspace/test/debug/test
  5. Program received signal SIGSEGV, Segmentation fault.
  6. 0x0000555555555139 in test_3 (p=0x12345678) at test.c:3
  7. 3 *p = 1;
  8. (gdb) bt
  9. #0 0x0000555555555139 in test_3 (p=0x12345678) at test.c:3
  10. #1 0x000055555555515e in test_2 (p=0x12345678) at test.c:8
  11. #2 0x000055555555517d in test_1 (p=0x12345678) at test.c:13
  12. #3 0x00005555555551a7 in main (argc=1, argv=0x7fffffffe498) at test.c:20
  13. (gdb)

段错误触发时,GDB会直接告诉我们问题出现在哪一行代码,并且可以利用backtrace命令查看完整调用栈信息。此外,还可以利用其他常规调试命令来查看参数、变量、内存等数据。

这种方式虽然非常有效,但很多时候,问题并不是100%必现的,我们不可能一直把程序运行在GDB中,这对程序的执行性能等会有很大的影响。

这时,我们可以让程序在异常终止时生成core dump文件,然后用调试工具对它进行离线调试。

3. Core Dump + GDB

Core dump是Linux提供的一种非常实用的程序调试手段,在程序异常终止时,Linux会把程序的上下文信息记录在一个core文件中,然后可以利用GDB等调试工具对core文件进行离线调试。

很多系统中,根据默认配置,程序异常退出时不会产生core dump文件。可以通过下面这条命令查看:

  1. ulimit -c

如果值是0,则默认不会产生core dump文件。可以用下面命令设置生成core dump文件的大小:

  1. ulimit -c 10240

上面命令把core dump文件大小设置为10MB。如果存储空间不受限的话,可以直接取消大小限制:

  1. ulimit -c unlimited

然后重新运行示例程序,段错误触发后,默认会在当前目录下生产一个core文件:

  1. root@ubuntu:debug# ./test
  2. Segmentation fault (core dumped)
  3. root@ubuntu:debug# ls
  4. core-test-2113875-1705030770 test test.c
  5. root@ubuntu:debug#

然后用GDB加载调试core文件。调试时,除了core dump文件外,GDB还需要从可执行文件中加载调试信息。

  1. gdb ./test ./core-test-2113875-1705030770

结果如下图:

  1. root@ubuntu:debug# gdb ./test ./core-test-2113875-1705030770
  2. Reading symbols from ./test...
  3. [New LWP 2113875]
  4. Core was generated by `./test'.
  5. Program terminated with signal SIGSEGV, Segmentation fault.
  6. #0 0x000055ccfa65b139 in test_3 (p=0x12345678) at test.c:3
  7. 3 *p = 1;
  8. (gdb) bt
  9. #0 0x000055ccfa65b139 in test_3 (p=0x12345678) at test.c:3
  10. #1 0x000055ccfa65b15e in test_2 (p=0x12345678) at test.c:8
  11. #2 0x000055ccfa65b17d in test_1 (p=0x12345678) at test.c:13
  12. #3 0x000055ccfa65b1a7 in main (argc=1, argv=0x7ffeac135938) at test.c:20
  13. (gdb)

与直接在GDB运行程序类似,core dump文件加载起来之后,GDB会直接显示触发问题的那一行代码,也可以使用backtrace、print等常规命令从core dump文件中获取信息。

在大多数系统中,这种core dump + GDB的手段非常有效,而且应该优先考虑使用。

但是有时候,由于某种原因,系统可能无法生存core dump文件。比如出于安全考虑,core dump功能可能是被彻底禁止的,或者在一些存储空间受限的嵌入式系统中,也无法生成core dump文件。

此时,我们就不得不考虑其它的调试手段了。

4. signal capture + backtrace

4.1 段错误在Linux系统上的处理过程

在Linux系统中,程序访问非法地址时,会被CPU捕获后触发硬件异常处理机制,并通知Linux kernel程序运行出现异常,kernel会对各种异常进行区分,然后向应用程序发送不同的signal,由应用程序自己进行故障恢复处理。

对于访问非法地址引起的段错误,Linux kernel会向应用程序发送11号signal,也就是SIGSEGV信号,该信号的默认处理是终止程序运行。

我们可以注册一个信号处理函数,当接受到Linux kernel发送过来的SIGSEGV信号后,在信号处理函数中把当前程序的上下文信息记录下来,方面后续问题定位。

4.2 两个有用的函数

  1. int backtrace(void **buffer, int size);
  2. void backtrace_symbols_fd(void *const *buffer, int size, int fd);

backtrace获取程序的调用栈地址信息,并存储在buffer指定的一个数组中,数组大小为size。

backtrace_symbols_fd根据backtrace得到的调用栈地址数据,获取地址对应的符号信息,并把结果写到fd指定的文件中。

4.3 示例

对上面的示例做下修改,增加一个信号处理函数,如下:

  1. #define _GNU_SOURCE
  2. #include <ucontext.h>
  3. #include <stdio.h>
  4. #include <execinfo.h>
  5. #include <signal.h>
  6. #include <stdlib.h>
  7. static void signal_handler(int sig, siginfo_t *info, void *ctx)
  8. {
  9. ucontext_t *context = (ucontext_t *)ctx;
  10. /* dump registers, x64 CPU specific */
  11. printf( "Signal = %d Memory location = %p\n"
  12. "RIP = %016X RSP = %016X RBP = %016X\n"
  13. "RAX = %016X RBX = %016X RCX = %016X\n"
  14. "RDX = %016X RSI = %016X RDI = %016X\n"
  15. "R8 = %016X R9 = %016X R10 = %016X\n"
  16. "R11 = %016X R12 = %016X R13 = %016X\n"
  17. "R14 = %016X R15 = %016X RFLAGS = %016X\n\n",
  18. sig, info->si_addr,
  19. context->uc_mcontext.gregs[REG_RIP],
  20. context->uc_mcontext.gregs[REG_RSP],
  21. context->uc_mcontext.gregs[REG_RBP],
  22. context->uc_mcontext.gregs[REG_RAX],
  23. context->uc_mcontext.gregs[REG_RBX],
  24. context->uc_mcontext.gregs[REG_RCX],
  25. context->uc_mcontext.gregs[REG_RDX],
  26. context->uc_mcontext.gregs[REG_RSI],
  27. context->uc_mcontext.gregs[REG_RDI],
  28. context->uc_mcontext.gregs[REG_R8],
  29. context->uc_mcontext.gregs[REG_R9],
  30. context->uc_mcontext.gregs[REG_R10],
  31. context->uc_mcontext.gregs[REG_R11],
  32. context->uc_mcontext.gregs[REG_R12],
  33. context->uc_mcontext.gregs[REG_R13],
  34. context->uc_mcontext.gregs[REG_R14],
  35. context->uc_mcontext.gregs[REG_R15],
  36. context->uc_mcontext.gregs[REG_EFL]);
  37. /* get call stack and write to stdout */
  38. void *buf[256] = {0};
  39. int size = backtrace(buf, 256);
  40. backtrace_symbols_fd(buf, size, fileno(stdout));
  41. exit(-1);
  42. }

在信号处理函数signal_handler中,先把寄存器信息打印出来,然后用backtrace和backtrace_symbols_fd获取调用栈信息,并写入stdout。

然后,在main函数中注册SIGSEGV的信号处理函数,如下:

  1. void test_3(int *p)
  2. {
  3. *p = 1;
  4. }
  5. void test_2(int *p)
  6. {
  7. test_3(p);
  8. }
  9. void test_1(int *p)
  10. {
  11. test_2(p);
  12. }
  13. int main(int argc, char *argv[])
  14. {
  15. int *p = 0x12345678;
  16. struct sigaction action;
  17. sigemptyset(&action.sa_mask);
  18. action.sa_sigaction = signal_handler;
  19. action.sa_flags = SA_SIGINFO;
  20. sigaction(SIGSEGV, &action, NULL);
  21. test_1(p);
  22. return 0;
  23. }

编译一下:

  1. gcc -g -rdynamic test1.c -o test1

看下运行结果:

  1. root@ubuntu:debug# ./test1
  2. Signal = 11 Memory location = 0x12345678
  3. RIP = 000000008F57C44F RSP = 0000000090348BF0 RBP = 0000000090348BF0
  4. RAX = 0000000012345678 RBX = 000000008F57C540 RCX = 00000000BD0F7166
  5. RDX = 0000000000000000 RSI = 0000000090348AE0 RDI = 0000000012345678
  6. R8 = 0000000000000000 R9 = 0000000000000000 R10 = 0000000000000008
  7. R11 = 0000000000000246 R12 = 000000008F57C140 R13 = 0000000090348DE0
  8. R14 = 0000000000000000 R15 = 0000000000000000 RFLAGS = 0000000000010206
  9. ./test1(+0x1407)[0x555b8f57c407]
  10. /lib/x86_64-linux-gnu/libc.so.6(+0x43090)[0x7f97bd0f7090]
  11. ./test1(test_3+0x10)[0x555b8f57c44f]
  12. ./test1(test_2+0x1c)[0x555b8f57c474]
  13. ./test1(test_1+0x1c)[0x555b8f57c493]
  14. ./test1(main+0x86)[0x555b8f57c51c]
  15. /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x7f97bd0d8083]
  16. ./test1(_start+0x2e)[0x555b8f57c16e]
  17. root@ubuntu:debug#

为了方便演示,示例中的信号处理函数只记录了寄存器和调用栈信息,实际项目中根据需求,可以同时记录其它重要信息,如stack dump、全局变量、数据段dump等。

有两点需要注意:

  • 示例信号处理函数中打印寄存器的部分是针对x64 CPU的,其它CPU请参考sys/ucontext.h文件中对mcontext_t的定义。
  • 编译时需要加上-rdynamic选项,否则backtrace_symbols_fd无法正确获取符号信息。

5. signal capture + GDB

有些问题很难重现,直接在GDB里运行调试的话,可能要浪费很多时间去不停的尝试重现它。

那有没有一种方式,可以让问题重现时自动启动GDB呢?当然有!

与上面的一种方法类似,我们仍然利用signal capture的方式。只不过,在信号处理函数中,我们不再使用backtrace获取调用栈信息,而是直接启动GDB。

对信号处理函数作一些修改,如下:

  1. static void signal_handler(int sig, siginfo_t *info, void *ctx)
  2. {
  3. char cmd[256];
  4. printf("\n*** Segmentation fault happened, starting GDB ... \n\n");
  5. snprintf(cmd, 256, "gdb --pid=%d -ex bt -q", getpid());
  6. system(cmd);
  7. printf("\n*** Finish debugging, now quit! \n");
  8. exit(-1);
  9. }

原理很简单,就是段错误发生时,在SIGSEGV信号处理函数中执行命令:

  1. gdb --pid=xxx -ex bt -q

启动GDB,并attach到当前进程,然后执行backtrace命令打印调用栈信息。-q选项只是让GDB启动时不要打印版本信息,避免视觉干扰。

编译一下,需要加上-g选项:

  1. gcc -g siggdb.c -o siggdb

运行,结果如下图:

  1. root@ubuntu:debug# ./siggdb
  2. attach: No such file or directory.
  3. Attaching to process 2114093
  4. Reading symbols from /opt/data/workspace/articles/debug/siggdb...
  5. Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
  6. (No debugging symbols found in /lib/x86_64-linux-gnu/libc.so.6)
  7. Reading symbols from /lib64/ld-linux-x86-64.so.2...
  8. (No debugging symbols found in /lib64/ld-linux-x86-64.so.2)
  9. 0x00007f26e68f3c3a in wait4 () from /lib/x86_64-linux-gnu/libc.so.6
  10. #0 0x00007f26e68f3c3a in wait4 () from /lib/x86_64-linux-gnu/libc.so.6
  11. #1 0x00007f26e6862f67 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
  12. #2 0x0000559bb65ff1fb in signal_handler (sig=11, info=0x7ffd8bbee570, ctx=0x7ffd8bbee440) at siggdb.c:16
  13. #3 <signal handler called>
  14. #4 0x0000559bb65ff211 in test_3 (p=0x12345678) at siggdb.c:23
  15. #5 0x0000559bb65ff232 in test_2 (p=0x12345678) at siggdb.c:28
  16. #6 0x0000559bb65ff24d in test_1 (p=0x12345678) at siggdb.c:33
  17. #7 0x0000559bb65ff2d2 in main (argc=1, argv=0x7ffd8bbeebc8) at siggdb.c:47
  18. (gdb)

注意:这种方法只能在测试环境中使用,且要确保GDB可以正常使用。生产环境中不要使用!

6. libSegFault.so

除了上面提到的几种方式外,其实glibc也已经很贴心地提供了一种问题定位的方案:libSegFault.so

libSegFault.so是glibc提供的一个动态链接库,用于捕捉程序运行异常并记录调用栈等调试信息。

它的实现原理和上面提到的第4种方法是一样的,即通过signal capture的方式,程序发生异常时,在信号处理函数中记录调试信息。

使用时,先确定系统中是否存在这个动态链接库。在我的系统中,有这么几个:

  1. root@ubuntu:debug# find / -name libSegFault.so
  2. /snap/snapd/20671/lib/x86_64-linux-gnu/libSegFault.so
  3. /snap/snapd/20290/lib/x86_64-linux-gnu/libSegFault.so
  4. /snap/core20/2105/usr/lib/i386-linux-gnu/libSegFault.so
  5. /snap/core20/2105/usr/lib/x86_64-linux-gnu/libSegFault.so
  6. /snap/core20/2015/usr/lib/i386-linux-gnu/libSegFault.so
  7. /snap/core20/2015/usr/lib/x86_64-linux-gnu/libSegFault.so
  8. /snap/core18/2812/lib/i386-linux-gnu/libSegFault.so
  9. /snap/core18/2812/lib/x86_64-linux-gnu/libSegFault.so
  10. /snap/core18/2796/lib/i386-linux-gnu/libSegFault.so
  11. /snap/core18/2796/lib/x86_64-linux-gnu/libSegFault.so
  12. /usr/lib32/libSegFault.so
  13. /usr/lib/x86_64-linux-gnu/libSegFault.so
  14. root@ubuntu:debug#

根据自己的实际情况,选择一个使用。比如我的测试环境是x64的,我选择使用:

  1. /usr/lib/x86_64-linux-gnu/libSegFault.so

然后利用环境变量LD_PRELOAD,在测试程序运行前,把libSegFault.so链接进来。

  1. LD_PRELOAD=/usr/lib/debug/lib/x86_64-linux-gnu/libSegFault.so ./myapp

仍以本文第一个测试程序为例:

  1. void test_3(int *p)
  2. {
  3. *p = 1;
  4. }
  5. void test_2(int *p)
  6. {
  7. test_3(p);
  8. }
  9. void test_1(int *p)
  10. {
  11. test_2(p);
  12. }
  13. int main(int argc, char *argv[])
  14. {
  15. int *p = 0x12345678;
  16. struct sigaction action;
  17. sigemptyset(&action.sa_mask);
  18. action.sa_sigaction = signal_handler;
  19. action.sa_flags = SA_SIGINFO;
  20. sigaction(SIGSEGV, &action, NULL);
  21. test_1(p);
  22. return 0;
  23. }

编译:

  1. gcc -rdynamic test.c -o test

运行:

  1. LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libSegFault.so ./test

测试程序触发段错误后,libSegFault.so中的信号处理函数会把寄存器、调用栈、内存映射全部dump出来。结果如下:

  1. root@ubuntu:debug# LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libSegFault.so ./test
  2. *** Segmentation fault
  3. Register dump:
  4. RAX: 0000000012345678 RBX: 0000557bb87bb1b0 RCX: 0000557bb87bb1b0
  5. RDX: 00007fff1d833198 RSI: 00007fff1d833188 RDI: 0000000012345678
  6. RBP: 00007fff1d833030 R8 : 0000000000000000 R9 : 00007f61cdce9d60
  7. R10: 0000000000000008 R11: 0000000000000246 R12: 0000557bb87bb040
  8. R13: 00007fff1d833180 R14: 0000000000000000 R15: 0000000000000000
  9. RSP: 00007fff1d833030
  10. RIP: 0000557bb87bb139 EFLAGS: 00010202
  11. CS: 0033 FS: 0000 GS: 0000
  12. Trap: 0000000e Error: 00000006 OldMask: 00000000 CR2: 12345678
  13. FPUCW: 0000037f FPUSW: 00000000 TAG: 00000000
  14. RIP: 00000000 RDP: 00000000
  15. ST(0) 0000 0000000000000000 ST(1) 0000 0000000000000000
  16. ST(2) 0000 0000000000000000 ST(3) 0000 0000000000000000
  17. ST(4) 0000 0000000000000000 ST(5) 0000 0000000000000000
  18. ST(6) 0000 0000000000000000 ST(7) 0000 0000000000000000
  19. mxcsr: 1f80
  20. XMM0: 00000000000000000000000000000000 XMM1: 00000000000000000000000000000000
  21. XMM2: 00000000000000000000000000000000 XMM3: 00000000000000000000000000000000
  22. XMM4: 00000000000000000000000000000000 XMM5: 00000000000000000000000000000000
  23. XMM6: 00000000000000000000000000000000 XMM7: 00000000000000000000000000000000
  24. XMM8: 00000000000000000000000000000000 XMM9: 00000000000000000000000000000000
  25. XMM10: 00000000000000000000000000000000 XMM11: 00000000000000000000000000000000
  26. XMM12: 00000000000000000000000000000000 XMM13: 00000000000000000000000000000000
  27. XMM14: 00000000000000000000000000000000 XMM15: 00000000000000000000000000000000
  28. Backtrace:
  29. ./test(+0x1139)[0x557bb87bb139]
  30. ./test(+0x115e)[0x557bb87bb15e]
  31. ./test(+0x117d)[0x557bb87bb17d]
  32. ./test(+0x11a7)[0x557bb87bb1a7]
  33. /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x7f61cdaf4083]
  34. ./test(+0x106e)[0x557bb87bb06e]
  35. Memory map:
  36. 557bb87ba000-557bb87bb000 r--p 00000000 fc:10 550237 /opt/data/workspace/test/debug/test
  37. 557bb87bb000-557bb87bc000 r-xp 00001000 fc:10 550237 /opt/data/workspace/test/debug/test
  38. 557bb87bc000-557bb87bd000 r--p 00002000 fc:10 550237 /opt/data/workspace/test/debug/test
  39. 557bb87bd000-557bb87be000 r--p 00002000 fc:10 550237 /opt/data/workspace/test/debug/test
  40. 557bb87be000-557bb87bf000 rw-p 00003000 fc:10 550237 /opt/data/workspace/test/debug/test
  41. 557bba6d5000-557bba6f6000 rw-p 00000000 00:00 0 [heap]
  42. 7f61cdab2000-7f61cdab5000 r--p 00000000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  43. 7f61cdab5000-7f61cdac7000 r-xp 00003000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  44. 7f61cdac7000-7f61cdacb000 r--p 00015000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  45. 7f61cdacb000-7f61cdacc000 r--p 00018000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  46. 7f61cdacc000-7f61cdacd000 rw-p 00019000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  47. 7f61cdacd000-7f61cdad0000 rw-p 00000000 00:00 0
  48. 7f61cdad0000-7f61cdaf2000 r--p 00000000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  49. 7f61cdaf2000-7f61cdc6a000 r-xp 00022000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  50. 7f61cdc6a000-7f61cdcb8000 r--p 0019a000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  51. 7f61cdcb8000-7f61cdcbc000 r--p 001e7000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  52. 7f61cdcbc000-7f61cdcbe000 rw-p 001eb000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  53. 7f61cdcbe000-7f61cdcc2000 rw-p 00000000 00:00 0
  54. 7f61cdccf000-7f61cdcd0000 r--p 00000000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  55. 7f61cdcd0000-7f61cdcd3000 r-xp 00001000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  56. 7f61cdcd3000-7f61cdcd4000 r--p 00004000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  57. 7f61cdcd4000-7f61cdcd5000 r--p 00004000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  58. 7f61cdcd5000-7f61cdcd6000 rw-p 00005000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  59. 7f61cdcd6000-7f61cdcd8000 rw-p 00000000 00:00 0
  60. 7f61cdcd8000-7f61cdcd9000 r--p 00000000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  61. 7f61cdcd9000-7f61cdcfc000 r-xp 00001000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  62. 7f61cdcfc000-7f61cdd04000 r--p 00024000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  63. 7f61cdd05000-7f61cdd06000 r--p 0002c000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  64. 7f61cdd06000-7f61cdd07000 rw-p 0002d000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  65. 7f61cdd07000-7f61cdd08000 rw-p 00000000 00:00 0
  66. 7fff1d813000-7fff1d834000 rw-p 00000000 00:00 0 [stack]
  67. 7fff1d967000-7fff1d96a000 r--p 00000000 00:00 0 [vvar]
  68. 7fff1d96a000-7fff1d96b000 r-xp 00000000 00:00 0 [vdso]
  69. ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
  70. Segmentation fault (core dumped)
  71. root@ubuntu:debug#

libSegFault.so默认只捕捉SIGSEGV,可以通过设置环境变量SEGFAULT_SIGNALS指定要捕捉的信号,如:

  1. export SEGFAULT_SIGNALS="all" # "all" signals
  2. export SEGFAULT_SIGNALS="segv bus abrt " #SIGSEGV, SIGBUS and SIGABRT

环境变量SEGFAULT_USE_ALTSTACK可以指定是否让信号处理函数使用独立的栈,这在程序发送栈溢出时会很有用。

  1. export SEGFAULT_USE_ALTSTACK=1

libSegFault.so默认把调试信息输出到stderr,可以通过设置环境变量SEGFAULT_OUTPUT_NAME,指定调试信息记录到一个文件中。比如:

  1. export SEGFAULT_OUTPUT_NAME="./debug.log"

此外,为了方便用户使用,很多系统中还提供了一个名为catchsegv的脚本:

  1. catchsegv ./test

其效果与通过LD_PRELOAD加载libSegFault.so是相同的:

  1. root@ubuntu:debug# whereis catchsegv
  2. catchsegv: /usr/bin/catchsegv /usr/share/man/man1/catchsegv.1.gz
  3. root@ubuntu:debug#
  4. root@ubuntu:debug# catchsegv ./test
  5. Segmentation fault (core dumped)
  6. *** Segmentation fault
  7. Register dump:
  8. RAX: 0000000012345678 RBX: 0000556c4e8d91b0 RCX: 0000556c4e8d91b0
  9. RDX: 00007ffdd20b2a68 RSI: 00007ffdd20b2a58 RDI: 0000000012345678
  10. RBP: 00007ffdd20b2900 R8 : 0000000000000000 R9 : 00007fdd23dc4d60
  11. R10: 00007fdd23daa730 R11: 00007fdd23d97be0 R12: 0000556c4e8d9040
  12. R13: 00007ffdd20b2a50 R14: 0000000000000000 R15: 0000000000000000
  13. RSP: 00007ffdd20b2900
  14. RIP: 0000556c4e8d9139 EFLAGS: 00010202
  15. CS: 0033 FS: 0000 GS: 0000
  16. Trap: 0000000e Error: 00000006 OldMask: 00000000 CR2: 12345678
  17. FPUCW: 0000037f FPUSW: 00000000 TAG: 00000000
  18. RIP: 00000000 RDP: 00000000
  19. ST(0) 0000 0000000000000000 ST(1) 0000 0000000000000000
  20. ST(2) 0000 0000000000000000 ST(3) 0000 0000000000000000
  21. ST(4) 0000 0000000000000000 ST(5) 0000 0000000000000000
  22. ST(6) 0000 0000000000000000 ST(7) 0000 0000000000000000
  23. mxcsr: 1f80
  24. XMM0: 00000000000000000000000000000000 XMM1: 00000000000000000000000000000000
  25. XMM2: 00000000000000000000000000000000 XMM3: 00000000000000000000000000000000
  26. XMM4: 00000000000000000000000000000000 XMM5: 00000000000000000000000000000000
  27. XMM6: 00000000000000000000000000000000 XMM7: 00000000000000000000000000000000
  28. XMM8: 00000000000000000000000000000000 XMM9: 00000000000000000000000000000000
  29. XMM10: 00000000000000000000000000000000 XMM11: 00000000000000000000000000000000
  30. XMM12: 00000000000000000000000000000000 XMM13: 00000000000000000000000000000000
  31. XMM14: 00000000000000000000000000000000 XMM15: 00000000000000000000000000000000
  32. Backtrace:
  33. ./test(+0x1139)[0x556c4e8d9139]
  34. ./test(+0x115e)[0x556c4e8d915e]
  35. ./test(+0x117d)[0x556c4e8d917d]
  36. ./test(+0x11a7)[0x556c4e8d91a7]
  37. /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x7fdd23bcf083]
  38. ./test(+0x106e)[0x556c4e8d906e]
  39. Memory map:
  40. 556c4e8d8000-556c4e8d9000 r--p 00000000 fc:10 550237 /opt/data/workspace/test/debug/test
  41. 556c4e8d9000-556c4e8da000 r-xp 00001000 fc:10 550237 /opt/data/workspace/test/debug/test
  42. 556c4e8da000-556c4e8db000 r--p 00002000 fc:10 550237 /opt/data/workspace/test/debug/test
  43. 556c4e8db000-556c4e8dc000 r--p 00002000 fc:10 550237 /opt/data/workspace/test/debug/test
  44. 556c4e8dc000-556c4e8dd000 rw-p 00003000 fc:10 550237 /opt/data/workspace/test/debug/test
  45. 556c4ead1000-556c4eaf2000 rw-p 00000000 00:00 0 [heap]
  46. 7fdd23b8d000-7fdd23b90000 r--p 00000000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  47. 7fdd23b90000-7fdd23ba2000 r-xp 00003000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  48. 7fdd23ba2000-7fdd23ba6000 r--p 00015000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  49. 7fdd23ba6000-7fdd23ba7000 r--p 00018000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  50. 7fdd23ba7000-7fdd23ba8000 rw-p 00019000 fc:01 24328 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
  51. 7fdd23ba8000-7fdd23bab000 rw-p 00000000 00:00 0
  52. 7fdd23bab000-7fdd23bcd000 r--p 00000000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  53. 7fdd23bcd000-7fdd23d45000 r-xp 00022000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  54. 7fdd23d45000-7fdd23d93000 r--p 0019a000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  55. 7fdd23d93000-7fdd23d97000 r--p 001e7000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  56. 7fdd23d97000-7fdd23d99000 rw-p 001eb000 fc:01 17718 /usr/lib/x86_64-linux-gnu/libc-2.31.so
  57. 7fdd23d99000-7fdd23d9d000 rw-p 00000000 00:00 0
  58. 7fdd23daa000-7fdd23dab000 r--p 00000000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  59. 7fdd23dab000-7fdd23dae000 r-xp 00001000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  60. 7fdd23dae000-7fdd23daf000 r--p 00004000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  61. 7fdd23daf000-7fdd23db0000 r--p 00004000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  62. 7fdd23db0000-7fdd23db1000 rw-p 00005000 fc:01 17711 /usr/lib/x86_64-linux-gnu/libSegFault.so
  63. 7fdd23db1000-7fdd23db3000 rw-p 00000000 00:00 0
  64. 7fdd23db3000-7fdd23db4000 r--p 00000000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  65. 7fdd23db4000-7fdd23dd7000 r-xp 00001000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  66. 7fdd23dd7000-7fdd23ddf000 r--p 00024000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  67. 7fdd23de0000-7fdd23de1000 r--p 0002c000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  68. 7fdd23de1000-7fdd23de2000 rw-p 0002d000 fc:01 17690 /usr/lib/x86_64-linux-gnu/ld-2.31.so
  69. 7fdd23de2000-7fdd23de3000 rw-p 00000000 00:00 0
  70. 7ffdd2093000-7ffdd20b4000 rw-p 00000000 00:00 0 [stack]
  71. 7ffdd21dd000-7ffdd21e0000 r--p 00000000 00:00 0 [vvar]
  72. 7ffdd21e0000-7ffdd21e1000 r-xp 00000000 00:00 0 [vdso]
  73. ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
  74. root@ubuntu:debug#

7. Valgrind

Valgrind是一个很强大的工具集,它可以检测内存泄露、栈溢出、非法内存访问等多种内存相关的错误,还可以对程序进行性能剖析、生成函数调用关系图、统计Cache命中率、监测多线程竞争等,是程序调试的利器。

Valgrind功能非常强大,但文章篇幅有限,不对其展开讨论,后续会更新文章专门讲解它的各种功能,感兴趣的朋友可以右上角关注一下。

下面演示用Valgrind检测示例程序的内存访问错误。

编译时加上-g选项:

  1. gcc -g test.c -o test

然后用Valgrind启动示例程序:

  1. valgrind --tool=memcheck --leak-check=yes -v --leak-check=full --show-reachable=yes ./test

显示数据较多, 如下图所示:

  1. root@ubuntu:debug# valgrind --tool=memcheck --leak-check=yes -v --leak-check=full --show-reachable=yes ./test
  2. ==2114522== Memcheck, a memory error detector
  3. ==2114522== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
  4. ==2114522== Using Valgrind-3.15.0-608cb11914-20190413 and LibVEX; rerun with -h for copyright info
  5. ==2114522== Command: ./test
  6. ==2114522==
  7. --2114522-- Valgrind options:
  8. --2114522-- --tool=memcheck
  9. --2114522-- --leak-check=yes
  10. --2114522-- -v
  11. --2114522-- --leak-check=full
  12. --2114522-- --show-reachable=yes
  13. --2114522-- Contents of /proc/version:
  14. --2114522-- Linux version 5.4.0-90-generic (buildd@lgw01-amd64-054) (gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)) #101-Ubuntu SMP Fri Oct 15 20:00:55 UTC 2021
  15. --2114522--
  16. --2114522-- Arch and hwcaps: AMD64, LittleEndian, amd64-cx16-lzcnt-rdtscp-sse3-ssse3-avx-avx2-bmi-f16c-rdrand
  17. --2114522-- Page sizes: currently 4096, max supported 4096
  18. --2114522-- Valgrind library directory: /usr/lib/x86_64-linux-gnu/valgrind
  19. --2114522-- Reading syms from /opt/data/workspace/test/debug/test
  20. --2114522-- Reading syms from /usr/lib/x86_64-linux-gnu/ld-2.31.so
  21. --2114522-- Considering /usr/lib/debug/.build-id/7a/e2aaae1a0e5b262df913ee0885582d2e327982.debug ..
  22. --2114522-- .. build-id is valid
  23. --2114522-- Reading syms from /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux
  24. --2114522-- object doesn't have a symbol table
  25. --2114522-- object doesn't have a dynamic symbol table
  26. --2114522-- Scheduler: using generic scheduler lock implementation.
  27. --2114522-- Reading suppressions file: /usr/lib/x86_64-linux-gnu/valgrind/default.supp
  28. ==2114522== embedded gdbserver: reading from /tmp/vgdb-pipe-from-vgdb-to-2114522-by-root-on-???
  29. ==2114522== embedded gdbserver: writing to /tmp/vgdb-pipe-to-vgdb-from-2114522-by-root-on-???
  30. ==2114522== embedded gdbserver: shared mem /tmp/vgdb-pipe-shared-mem-vgdb-2114522-by-root-on-???
  31. ==2114522==
  32. ==2114522== TO CONTROL THIS PROCESS USING vgdb (which you probably
  33. ==2114522== don't want to do, unless you know exactly what you're doing,
  34. ==2114522== or are doing some strange experiment):
  35. ==2114522== /usr/lib/x86_64-linux-gnu/valgrind/../../bin/vgdb --pid=2114522 ...command...
  36. ==2114522==
  37. ==2114522== TO DEBUG THIS PROCESS USING GDB: start GDB like this
  38. ==2114522== /path/to/gdb ./test
  39. ==2114522== and then give GDB the following command
  40. ==2114522== target remote | /usr/lib/x86_64-linux-gnu/valgrind/../../bin/vgdb --pid=2114522
  41. ==2114522== --pid is optional if only one valgrind process is running
  42. ==2114522==
  43. --2114522-- REDIR: 0x4022e20 (ld-linux-x86-64.so.2:strlen) redirected to 0x580c9ce2 (???)
  44. --2114522-- REDIR: 0x4022bf0 (ld-linux-x86-64.so.2:index) redirected to 0x580c9cfc (???)
  45. --2114522-- Reading syms from /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_core-amd64-linux.so
  46. --2114522-- object doesn't have a symbol table
  47. --2114522-- Reading syms from /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so
  48. --2114522-- object doesn't have a symbol table
  49. ==2114522== WARNING: new redirection conflicts with existing -- ignoring it
  50. --2114522-- old: 0x04022e20 (strlen ) R-> (0000.0) 0x580c9ce2 ???
  51. --2114522-- new: 0x04022e20 (strlen ) R-> (2007.0) 0x0483f060 strlen
  52. --2114522-- REDIR: 0x401f600 (ld-linux-x86-64.so.2:strcmp) redirected to 0x483ffd0 (strcmp)
  53. --2114522-- REDIR: 0x4023380 (ld-linux-x86-64.so.2:mempcpy) redirected to 0x4843a20 (mempcpy)
  54. --2114522-- Reading syms from /usr/lib/x86_64-linux-gnu/libc-2.31.so
  55. --2114522-- Considering /usr/lib/debug/.build-id/ee/be5d5f4b608b8a53ec446b63981bba373ca0ca.debug ..
  56. --2114522-- .. build-id is valid
  57. --2114522-- REDIR: 0x48f7480 (libc.so.6:memmove) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  58. --2114522-- REDIR: 0x48f6780 (libc.so.6:strncpy) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  59. --2114522-- REDIR: 0x48f77b0 (libc.so.6:strcasecmp) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  60. --2114522-- REDIR: 0x48f60a0 (libc.so.6:strcat) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  61. --2114522-- REDIR: 0x48f67e0 (libc.so.6:rindex) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  62. --2114522-- REDIR: 0x48f8c50 (libc.so.6:rawmemchr) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  63. --2114522-- REDIR: 0x4913ce0 (libc.so.6:wmemchr) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  64. --2114522-- REDIR: 0x4913820 (libc.so.6:wcscmp) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  65. --2114522-- REDIR: 0x48f75e0 (libc.so.6:mempcpy) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  66. --2114522-- REDIR: 0x48f7410 (libc.so.6:bcmp) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  67. --2114522-- REDIR: 0x48f6710 (libc.so.6:strncmp) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  68. --2114522-- REDIR: 0x48f6150 (libc.so.6:strcmp) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  69. --2114522-- REDIR: 0x48f7540 (libc.so.6:memset) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  70. --2114522-- REDIR: 0x49137e0 (libc.so.6:wcschr) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  71. --2114522-- REDIR: 0x48f6670 (libc.so.6:strnlen) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  72. --2114522-- REDIR: 0x48f6230 (libc.so.6:strcspn) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  73. --2114522-- REDIR: 0x48f7800 (libc.so.6:strncasecmp) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  74. --2114522-- REDIR: 0x48f61d0 (libc.so.6:strcpy) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  75. --2114522-- REDIR: 0x48f7950 (libc.so.6:memcpy@@GLIBC_2.14) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  76. --2114522-- REDIR: 0x4914f50 (libc.so.6:wcsnlen) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  77. --2114522-- REDIR: 0x4913860 (libc.so.6:wcscpy) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  78. --2114522-- REDIR: 0x48f6820 (libc.so.6:strpbrk) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  79. --2114522-- REDIR: 0x48f6100 (libc.so.6:index) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  80. --2114522-- REDIR: 0x48f6630 (libc.so.6:strlen) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  81. --2114522-- REDIR: 0x48ffbb0 (libc.so.6:memrchr) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  82. --2114522-- REDIR: 0x48f7850 (libc.so.6:strcasecmp_l) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  83. --2114522-- REDIR: 0x48f73d0 (libc.so.6:memchr) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  84. --2114522-- REDIR: 0x4913930 (libc.so.6:wcslen) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  85. --2114522-- REDIR: 0x48f6ae0 (libc.so.6:strspn) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  86. --2114522-- REDIR: 0x48f7750 (libc.so.6:stpncpy) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  87. --2114522-- REDIR: 0x48f76f0 (libc.so.6:stpcpy) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  88. --2114522-- REDIR: 0x48f8c90 (libc.so.6:strchrnul) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  89. --2114522-- REDIR: 0x48f78a0 (libc.so.6:strncasecmp_l) redirected to 0x48311d0 (_vgnU_ifunc_wrapper)
  90. --2114522-- REDIR: 0x49df730 (libc.so.6:__strrchr_avx2) redirected to 0x483ea10 (rindex)
  91. ==2114522== Invalid write of size 4
  92. ==2114522== at 0x109139: test_3 (test.c:3)
  93. ==2114522== by 0x10915D: test_2 (test.c:8)
  94. ==2114522== by 0x10917C: test_1 (test.c:13)
  95. ==2114522== by 0x1091A6: main (test.c:20)
  96. ==2114522== Address 0x12345678 is not stack'd, malloc'd or (recently) free'd
  97. ==2114522==
  98. ==2114522==
  99. ==2114522== Process terminating with default action of signal 11 (SIGSEGV): dumping core
  100. ==2114522== Access not within mapped region at address 0x12345678
  101. ==2114522== at 0x109139: test_3 (test.c:3)
  102. ==2114522== by 0x10915D: test_2 (test.c:8)
  103. ==2114522== by 0x10917C: test_1 (test.c:13)
  104. ==2114522== by 0x1091A6: main (test.c:20)
  105. ==2114522== If you believe this happened as a result of a stack
  106. ==2114522== overflow in your program's main thread (unlikely but
  107. ==2114522== possible), you can try to increase the size of the
  108. ==2114522== main thread stack using the --main-stacksize= flag.
  109. ==2114522== The main thread stack size used in this run was 8388608.
  110. --2114522-- REDIR: 0x48f16d0 (libc.so.6:free) redirected to 0x483c9d0 (free)
  111. ==2114522==
  112. ==2114522== HEAP SUMMARY:
  113. ==2114522== in use at exit: 0 bytes in 0 blocks
  114. ==2114522== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
  115. ==2114522==
  116. ==2114522== All heap blocks were freed -- no leaks are possible
  117. ==2114522==
  118. ==2114522== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
  119. ==2114522==
  120. ==2114522== 1 errors in context 1 of 1:
  121. ==2114522== Invalid write of size 4
  122. ==2114522== at 0x109139: test_3 (test.c:3)
  123. ==2114522== by 0x10915D: test_2 (test.c:8)
  124. ==2114522== by 0x10917C: test_1 (test.c:13)
  125. ==2114522== by 0x1091A6: main (test.c:20)
  126. ==2114522== Address 0x12345678 is not stack'd, malloc'd or (recently) free'd
  127. ==2114522==
  128. ==2114522== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
  129. Segmentation fault
  130. root@ubuntu:debug#

Valgrind成功检测出地址0x12345678既不是栈地址,也不是malloc分配的动态内存。并且它也会把调用栈信息dump出来。

Valgrind虽然在检测内存相关的错误时非常强大,但是它有一个致命的缺点,就是。据统计,通过Valgrind运行程序时,速度会降低10倍。这在调试大型项目时,尤其是对实时性非常敏感的程序,是无法接受的。

不过,我们还有一个更好的选择 — AddressSanitizer。

8. AddressSanitizer

AddressSanitizer最初是Google开发的一个检测多种内存相关问题的工具,AddressSanitizer现在已经集成到GCC和LLVM中。它最大的特点是:

  • 功能强大。它可以检测内存泄露、访问越界、栈溢出、多次释放等各种内存问题。
  • 。使用AddressSanitizer检测内存问题时,原始程序运行速度只会降低2倍左右,相比Vagrind来说,运行效率有了很大的提升。

本文只简单演示用AddressSanitizer检测示例程序中的内存访问错误,后续会专门更新文章详细讲解它的各种功能,感兴趣的朋友可以关注一下。

AddressSanitizer的使用方法也非常简单,只需要在编译时加上相应的编译选项,然后正常运行程序即可。

这里,我只使用最简单的一个编译选项-fsanitize=address开启AddressSanitizer功能。

  1. gcc -g -fsanitize=address test.c -o test

然后正常运行即可,如下图:

  1. root@ubuntu:debug# gcc -g -fsanitize=address test.c -o test
  2. root@ubuntu:debug# ./test
  3. AddressSanitizer:DEADLYSIGNAL
  4. =================================================================
  5. ==2114531==ERROR: AddressSanitizer: SEGV on unknown address 0x000012345678 (pc 0x55669475e1d4 bp 0x7ffcf6b43ad0 sp 0x7ffcf6b43ac0 T0)
  6. ==2114531==The signal is caused by a WRITE memory access.
  7. #0 0x55669475e1d3 in test_3 /opt/data/workspace/test/debug/test.c:3
  8. #1 0x55669475e1f8 in test_2 /opt/data/workspace/test/debug/test.c:8
  9. #2 0x55669475e217 in test_1 /opt/data/workspace/test/debug/test.c:13
  10. #3 0x55669475e241 in main /opt/data/workspace/test/debug/test.c:20
  11. #4 0x7f02b03ea082 in __libc_start_main ../csu/libc-start.c:308
  12. #5 0x55669475e0cd in _start (/opt/data/workspace/test/debug/test+0x10cd)
  13. AddressSanitizer can not provide additional info.
  14. SUMMARY: AddressSanitizer: SEGV /opt/data/workspace/test/debug/test.c:3 in test_3
  15. ==2114531==ABORTING
  16. root@ubuntu:debug#

9. dmesg + objdump

有时,可能由于各种原因,以上几种方法都不适用,比如程序中无法添加调试信息、程序无法重新编译、没有GDB和Valgrind等调试工具等。

这种情况下,调试起来,会相对比较困难一些,但也并不是完全不可能。

大多数情况下,程序发生segmentation fault而异常退出时,会在系统日志中记录一些信息,可以用dmesg查看:

  1. root@ubuntu:debug# dmesg
  2. [68302968.931073] test[2113875]: segfault at 12345678 ip 000055ccfa65b139 sp 00007ffeac1357e0 error 6 in test[55ccfa65b000+1000]
  3. [68302968.931091] Code: 2e 00 00 01 5d c3 0f 1f 00 c3 0f 1f 80 00 00 00 00 f3 0f 1e fa e9 77 ff ff ff f3 0f 1e fa 55 48 89 e5 48 89 7d f8 48 8b 45 f8 <c7> 00 01 00 00 00 90 5d c3 f3 0f 1e fa 55 48 89 e5 48 83 ec 08 48
  4. root@ubuntu:debug#

可以从中得到触发异常的指令地址和被访问的内存地址,然后利用系统中现有的一些工具进行调试,如利用objdump对可执行文件进行反汇编,然后从汇编代码入手进行分析,限于篇幅,不再展开讨论,后续会有专门文章详细讲解。

Linux下有很多非常有用的工具,如binutils工具集(objdump、nm、readelf等)、strace等,熟悉并善用这些工具,会事半功倍。

欢迎关注微信公众号:【原点技术】,分享真正有用的东西!

进技术交流群,欢迎添加作者微信:CreCoding

原创文章,未经允许禁止转载,转载请联系作者:CreCoding

标签: linux c++ c语言

本文转载自: https://blog.csdn.net/xzh203/article/details/135548102
版权归原作者 原点技术 所有, 如有侵权,请联系我们删除。

“Linux上段错误(SegFault)的9种实用调试方法”的评论:

还没有评论