针对段错误排查
一、 发生段错误情况分类
- 访问不存在的内存
- 访问系统保护的内存
- 指针操作越界(包括数组)
测试代码如下
#include <stdio.h> void test(int *ptr, int val) { //对空指针指向的内存区域写,会发生段错误 ptr[10] = val; } int main() { int *null_ptr = NULL; //*null_ptr = 10; int val = 10; test(null_ptr, val); return 0; }
调试方法
在编写过程中,程序启动就出现 core dump
, 直接使用 gdb 调试即可, 主要说明以下两种方法:
- 代码写完之后运行一段时间之后, 或者是一些不确定条件导致的
core dump
, 编译加-g
- 同1, 生产环境 不加
-g
, 特定条件触发core dump
- 栈回溯:
backtrace
和backtrace_symbols
, 可以利用SIGSEGV
打印出栈回溯
说明 gcc 编译后的文件有很多符号之类的, 使用指令strip
可以上文件变小, 并且不影响程序运行, 使用man strip
查看strip
使用手册: GNU strip丢弃对象文件objfile中的所有符号
1. gdb 调试 core 文件
代码写完之后运行一段时间之后, 或者是一些不确定条件导致的 core dump
# 1. 首先, 在编译代码的时候需要加上 `-g` 参数; # 2. ulimit -c #查看是否开启core文件, 0则不会生成core文件 # 3. ulimit -c [size] #生成限制core文件大小 ulimit -c unlimited #生成core文件不受大小限制, 临时设置, 永久设置在 /etc/security/limits.conf 填写以下两行 @root soft core unlimited @root hard core unlimited # 4. 指定core文件保存位置和文件命名方式 `echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern` # 5. 指定core文件生成是否添加pid作为扩展 `echo 1 > /proc/sys/kernel/core_uses_pid` # 6. mkdir /corefile 目录 # 7. gcc -g xx.c && a.out # 8. 会在/corefile 下看生成的 .core 文件 # 9. gdb -core /corefile/xxx.core # 10 进入gdb之后, 执行 file a.out # 11. where/bt # 10. 分析, 可以查看到对应的栈, 参数传递信息等
2. objdump 排查 core dump
在 报错core dump
的时候, 内核中会提示如下错误
[ 6629.248711] a.out[2891]: segfault at 28 ip 000000000040054c sp 00007fffd1e9ee20 error 6 in a.out[400000+1000] [ 6629.248716] Code: 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 f3 0f 1e fa eb 8a 55 48 89 e5 48 89 7d f8 89 75 f4 48 8b 45 f8 48 8d 50 28 8b 45 f4 <89> 02 90 5d c3 55 48 89 e5 48 83 ec 10 48 c7 45 f8 00 00 00 00 c7
其中 打印出来的二进制code就是 编译过后的 二进制执行文件, 即a.out中的内容, objdump 就是将 a.out 二进制文件反汇编成汇编语言, 然后通过汇编语言定位问题
获取信息:
-
segfault at 28 ip 000000000040054c, 这里能获取的就是 000000000040054c, 这个是汇编的指令地址
0x40054c
如果加-g
参数, 直接使用addr2line -e a.out 40054c
,即可定位到问题
-
sp 00007fffd1e9ee20 error 6 in a.out[400000+1000], 这里能获取的就是代码出现错误的所在文件,可sp(rsp)栈所在位置
-
打开二进制文件 a.out, 搜索code打印出来的二进制文件, 就能搜索到对应的汇编行数, 其中 <89>, 也就是在 89 指令出发生的错误
X86汇编代码说明链接:https://www.cnblogs.com/han-guang-xue/p/16525292.html -
生成汇编, objdump -d a.out > a.outDump
-
查看发生的错误位置 grep -n -A 10 -B 10 "40054c" a.outDump
-
根据以下代码能很明确的定位到代码出现的位置:40054c, 就是 *ptr[10] = 10; 报错
-
通过 nm a.out 可以直接看到方法的地址,定位大概问题, 使用 ldd a.out 可以查看链接库
-
问题1: 无法查看参数传递
问题2: 如果优化编译的话,看汇编对比源码会比较困难
3. 通过signal配合栈回溯实现问题定位
在嵌入式中, 程序执行由于内存访问错误会直接导致程序崩溃, 程序崩溃的时候会触发硬件错误中断, 然后通过更改代码中断函数, 实现了对数据寄存器的打印和对汇编指令的打印, 然后通过对二进制bin反编译成汇编定位问题。
在linux系统下不同于嵌入式, 之前做过 signal
对ctrl+C
的截取, 后来查看 signal代码, 发现支持很多 signal,查看源码发现,其中 SIGSEGV Invalid access to storage
对存储的无效访问, 就是 core dump
/* ISO C99 signals. */ #define SIGINT 2 /* Interactive attention signal. */ #define SIGILL 4 /* Illegal instruction. */ #define SIGABRT 6 /* Abnormal termination. */ #define SIGFPE 8 /* Erroneous arithmetic operation. */ #define SIGSEGV 11 /* Invalid access to storage. */ #define SIGTERM 15 /* Termination request. */ /* Historical signals specified by POSIX. */ #define SIGHUP 1 /* Hangup. */ #define SIGQUIT 3 /* Quit. */ #define SIGTRAP 5 /* Trace/breakpoint trap. */ #define SIGKILL 9 /* Killed. */ #define SIGBUS 10 /* Bus error. */ #define SIGSYS 12 /* Bad system call. */ #define SIGPIPE 13 /* Broken pipe. */ #define SIGALRM 14 /* Alarm clock. */
以下方式就是通过接管 signal 实现对栈和寄存器的打印输出
Linux c 代码
#include <stdio.h> #include <signal.h> #include <stdlib.h> #include <execinfo.h> void ShowStack() { int i; void *buffer[1024]; int n = backtrace(buffer, 1024); //n, buffer 越大, size越大, 回溯的栈就越多 char **symbols = backtrace_symbols(buffer, n); for (i = 0; i < n; i++) { printf("%s\n", symbols[i]); } } void signal_coredump(int sigval) { if (sigval == SIGSEGV) { printf("Receive SIGSEGV signal\n"); ShowStack(); exit(-1); } else { printf("this is sig %d", sigval); } } void test(int *ptr, int val, unsigned short val1) { ptr[10] = val + val1; } int main() { signal(SIGSEGV, signal_coredump); printf("start run \n"); int * null_ptr = NULL; int val = 0xABCD1234; int val1 = 0xEAAE1234; test(null_ptr, val, val1); printf("end run \n"); return 0; }
运行结果如下, 其中中括号中就是 objdump 之后反汇编中的代码行数, 如下为 _start -> main -> test -> signal_coredump -> ShowStack
中调用栈
[root@cen8 han]# ./a.out start run Receive SIGSEGV signal ./a.out() [0x400725] ./a.out() [0x40079c] /lib64/libc.so.6(+0x37400) [0x7f6705fe9400] ./a.out() [0x4007df] ./a.out() [0x400832] /lib64/libc.so.6(__libc_start_main+0xf3) [0x7f6705fd5493] ./a.out() [0x40064e]
如何打印寄存器的值
以下值可以打印数据寄存器的值 64位寄存器地址分析
__asm__ __volatile__ ("movq %%rax, %0;":"=r" (rax));
这个在GUN/linux 系统中有个问题, 因为其本身系统的问题, 在触发signal
信号时, 调用的堆栈已经有两层了, 而寄存器的值都是临时的,值早已变化, 所以该方式不可取, 研究中,后期补上
内存检测工具
使用 memwatch 调试内存检测
memwatch 内存错误检查工具,检查内存泄漏,错误释放,内存溢出等
原理: memwatch.c
文件中重新定义了 malloc
, free
, calloc
等相关函数, 然后引入自己的程序当中, 可以对自己的程序内存的处理做出监听
内存上溢: 内存覆盖超出缓存区上部分
内存下溢: 内存覆盖超出缓存区下部分
类似的内存检测工具还有:
- mtrace: GUN C 的库函数, 他给malloc,realloc,free安装钩子函数,用于检测内存状况
- dmalloc: 使用比较复杂
- yamd: 只能用于 x86
- valgrind: 对strcpy, strncpy, memcpy, strcat,free,malloc等内存操作函数进行检测
资源调用调试工具
使用 strace 调试程序
strace 执行程序时, 它会记录用户空间和内核空间资源边界处理的状况,如对文件的open,read,write,ioctl等
应用场景: 当不熟悉某个程序所依赖哪些文件目录或者其它资源,可以使用该方法
简单示例:
strace cat /dev/null
- -f: 除了跟踪当前进程,还跟踪子进程
- -o: 将输出信息写入文件
- -p: 绑定到一个由pid对应的正在运行的进程,用来调试后台进程
本文来自博客园踩坑狭,作者:韩若明瞳,转载请注明原文链接:https://www.cnblogs.com/han-guang-xue/p/16932168.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2018-11-28 spark几种读文件的方式
2018-11-28 RDD