目录

Linux下段错误问题定位

本文总结程序开发中段错误的常见定位思路与实用排查方法。

一、 相关知识

  1. ulimit命令 来自英文词组“user limit”的缩写,是一条bash解释器的内部命令,其功能是用于限制使用系统资源。通过使用ulimit命令能够限制用户最大启动进程数、最长CPU使用时长、最高内存占用量等资源,提高整体服务器的运行稳定性,让每位用户都可以充分、合理、公平地利用系统资源。

二、 环境准备

  1. 查看core dump功能是否打开,如果为0则表示处于关闭状态
(base) kwephispre11269:~ # ulimit -c
0
  1. 指定core dump文件大小 1)在当前session生效
ulimit -c 1024         # 1024个blocks,一般1block=512bytes
ulimit -c unlimited    # 取消大小限制,开发,试运行阶段可以使用次参数

2)持久化配置,修改limits.conf文件

(base) kwephispre11269:~ # cat /etc/security/limits.conf
#<domain>      <type>  <item>         <value>
#*               soft    core            0

3.设置core dump文件名及存储位置 1)查看默认位置

(base) kwephispre11269:~ # cat /proc/sys/kernel/core_pattern
|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %e

核心文件由上述程序处理,通常会保存到/var/lib/systemd/coredump/目录下。 2)修改存储位置及名称

(base) kwephispre11269:~ # echo "/path/coredump/file-%e-%P-%t" > /proc/sys/kernel/core_pattern
(base) kwephispre11269:~ # cat /proc/sys/kernel/core_pattern
/path/coredump/file-%e-%P-%t

3)文件参数说明

%% - 单个%字符
%p - 添加pid
%u - 添加当前uid
%g - 添加当前gid
%s - 添加导致产生core的信号
%t - 添加core文件生成时的unix时间
%h - 添加主机名
%e - 添加程序文件名

三、使用GDB调试 TODO 待补充链接

一、可执行程序错误 注意代码需要指定-g选项,以调试模式编译 0)示例代码

  1 #include <stdio.h>
  2
  3 int main(void)
  4 {
  5     int *p = NULL;
  6     *p = 0;
  7     printf("bad\n");
  8     return 0;
  9 }

1)查看错误信息

(base) kwephispre11269:/data # dmesg | grep segfault
[1467662.487224] test[12986]: segfault at 0 ip 00000000004003a0 sp 00007ffe6c077ca8 error 6 in test[400000+1000]

(base) kwephispre11269:/data # cat /var/log/messages | grep segfault
2024-05-23T20:29:10.756390+08:00 kwephispre11269 kernel: [1467662.487224] test[12986]: segfault at 0 ip 00000000004003a0 sp 00007ffe6c077ca8 error 6 in test[400000+1000]

可以使用dmesg命令或查看message日志文件查看。上述日志指出错误发生在test程序中。

上述error转换为二进制110,表示用户态程序访问无效地址 error的取值范围是0~7,从高到底分别为bit2、bit1和bit0 bit2: 1表示用户态程序内存访问越界,0表示是内核态程序内存访问越界 bit1: 1表示写操作导致内存访问越界,0表示是读操作导致内存访问越界 bit0: 1表示没有足够的权限访问非法地址的内容,0表示访问的非法地址根本没有对应的页面,也就是无效地址

2)查找错误行

(base) kwephispre11269:/data # addr2line -e test 00000000004003a0
/data/test.c:6

使用指令指针寄存器(IP)的值,调用addr2line命令得到源码具体行号。

二、非可执行程序错误 0)示例代码

test.c
  1 #include "foo.h"
  2
  3 int main(void)
  4 {
  5     foo();
  6     return 0;
  7 }

foo.h
  1 #ifndef __FOO_LIB_H__
  2 #define __FOO_LIB_H__
  3
  4 int foo(void);
  5
  6 #endif

foo.c
  1 #include "foo.h"
  2
  3 int foo()
  4 {
  5     int *p = 0;
  6     *p = 0;
  7     return 0;
  8 }

1)编译链接并运行

(base) kwephispre11269:/data # gcc -O3 -g -o libfoo.so -shared -fPIC foo.c
(base) kwephispre11269:/data # gcc -O3 -g -o test test.c -L. -lfoo
(base) kwephispre11269:/data # export LD_LIBRARY_PATH=.
(base) kwephispre11269:/data # ./test
Segmentation fault

注:编译时使用-O1/2/3优化后,可能导致信息不准确,建议使用-O0

2)查看错误信息

(base) kwephispre11269:/data # dmesg | grep segfault
[1513188.944663] test[15961]: segfault at 0 ip 00007f873d18c4e0 sp 00007ffccc5edf58 error 6 in libfoo.so[7f873d18c000+1000]

3)查找错误行 计算动态库相对地址值:00007f873d18c4e0 - 00007f873d18c4e0 = 4e0,并指定动态库为-e输入

(base) kwephispre11269:/data # addr2line -e libfoo.so 4e0
/data/foo.c:6

一、相关知识 1)信号是内核提供的向用户进程发送信息的机制。SIGSEGV是当一个进程执行了无效的内存引用,或发生段错误时发送给它的信息。 2)栈回溯back trace当用户态或内核态程序异常退出时,回溯堆栈信息。由于不同操作系统堆栈实现不同(向上增长或向下增长),分析结果时需要考虑栈增长方向。

二、编码实现 1)使用execinfo.hbacktrace函数

  1 #include <unistd.h>
  2 #include <signal.h>
  3 #include <execinfo.h>
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 #include <stdint.h>
  7 #include <string.h>
  8 #include <stdarg.h>
  9
 10 #define BACKTRACE_SIZE 256
 11
 12
 13 void segv_handler(int sig)
 14 {
 15     void *func[BACKTRACE_SIZE];
 16     char **symb = NULL;
 17     int size;
 18
 19     size = backtrace(func, BACKTRACE_SIZE);
 20     backtrace_symbols_fd(func, size, STDERR_FILENO);
 21     exit(1);
 22 }
 23
 24
 25 int main(void)
 26 {
 27     int *p = NULL;
 28     signal(SIGSEGV, segv_handler);
 29     *p = 0xdeadbeef;
 30     return 0;
 31 }

2)编译运行

(base) kwephispre11269:/data # gcc -O3 -g -o auto auto.c
(base) kwephispre11269:/data # ./auto
./auto[0x400614]
/lib64/libc.so.6(+0x362b0)[0x7fe41e3b92b0]
./auto[0x400513]
/lib64/libc.so.6(__libc_start_main+0xed)[0x7fe41e3a525d]
./auto[0x40054a]

3)查找错误行 当前使用x86_64系统,堆栈逆序查看,先调用的函数在下方。前两行为信号SIGSEGV的处理函数调用,则判断段错误发生在./auto[0x400614]处。

(base) kwephispre11269:/data # addr2line -e auto 0x400513
/data/auto.c:29