Linux下关于coredump的定位方法说明
作者:凌肖战
一. Linux 下 cordump 机制
Linux 系统下存在一种 coredump 机制。
当程序运行过程中异常终止或崩溃,Linux 操作系统会将程序当时的堆栈等内存状态信息记录下来,会在指定文件下生成一个 coredump 相关的 Log 文件。
软件开发人员可以通过对 coredump文件进行分析,即可定位到导致程序运行崩溃的 bug。
当程序访问的内存超过了系统给定的内存空间,就会产生 Segmentation fault(core dumped),所以:
一般产生段错误的原因有如下:
- 1. 访问了不存在的内存地址。
- 2. 访问了系统保护的内存地址。
- 3. 数组或堆空间访问越界,等等一系列内存非法访问操作。
二. coredump 文件定位方法
根据上一篇配置 coredump 机制的方法,成功配置好之后。
就可以运行代码,当程序运行中崩溃时即可产生相应的 coredump 文件。
操作如下:
- 1. 首先,开启 coredump 功能。设置成功后,可以通过 ulimit -c 命令验证。
- 2. 其次,关闭 ubuntu 系统下 apport.service 服务程序。
- 3. 其次,编译代码。编译时必须加 -g 编译选项(带调试信息的编译选项)。
- 4. 最后,运行程序。当程序运行时出现 核心转储等提示错误时,即可生成 coredump 文件。
这里为了方便起见,不更改 coredump 文件的生成路径,即生成的 coredump 文件会与可执行程序在同一目录下,文件默认叫 core 的文件。
下面以一段测试代码说明一下,Linux 环境下 使用 gdb 调试器对 coredump 文件进行分析调试。
代码如下:
#include <stdio.h> #include <string.h> #include <unistd.h> int fun() { printf("---fun()\n"); char buffer[3] = {0}; memcpy(buffer, "Hello", 6); printf("---buffer: %s\n", buffer); return 0; } int main() { printf("---main()---\n"); int pid = 0; pid = getpid(); printf("pid %d\n", pid); fun(); printf("---End main\n"); return 0; }
1. 编译运行
操作如下:
编译代码。输入 gcc -g main.c -o main.out 命令
运行程序。输入 ./main.out 命令。
如下所示:
wangtian@wangtian-virtual-machine:~/Code_Learns/C_Learns/debug/debug2$ ./main.out ---main()--- pid 3814 ---fun() ---buffer: Hello *** stack smashing detected ***: terminated 已放弃 (核心已转储)
当程序出现段错误时崩溃时,可查看程序当前目录下是否生成 coredump 相关文件。
如下所示:
wangtian@wangtian-virtual-machine:~/Code_Learns/C_Learns/debug/debug2$ ls -l 总用量 180 -rw------- 1 wangtian wangtian 393216 12月 13 16:55 core drwxrwxr-x 2 wangtian wangtian 4096 11月 25 22:24 debug -rw-rw-r-- 1 wangtian wangtian 90 10月 28 13:32 debug.c -rw-rw-r-- 1 wangtian wangtian 81 10月 20 18:12 debug.h -rw-rw-r-- 1 wangtian wangtian 5688 12月 13 16:55 debug.o -rw-rw-r-- 1 wangtian wangtian 356 12月 13 16:55 main.c -rw-rw-r-- 1 wangtian wangtian 7144 12月 13 16:55 main.o -rwxrwxr-x 1 wangtian wangtian 21144 12月 13 16:55 main.out
2. gdb 调试 coredump 文件
查看 coredump 文件。
输入 objdump -t core 命令:
wangtian@wangtian-virtual-machine:~/Code_Learns/C_Learns/debug/debug2$ objdump -t core core: 文件格式 elf64-x86-64 SYMBOL TABLE: 无符号
可以看到,coredump 文件是不带符号表的。
所以调试时,需要同时加上 可执行程序文件。
输入命令 gdb main.out core 进行调试,如下所示:
wangtian@wangtian-virtual-machine:~/Code_Learns/C_Learns/debug/debug2$ gdb main.out core For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from main.out... [New LWP 4272] Core was generated by `./main.out'. Program terminated with signal SIGABRT, Aborted. #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 50 ../sysdeps/unix/sysv/linux/raise.c: 没有那个文件或目录.
可以看到,程序收到 signal SIGABRT 后崩溃了。
输入 bt (backtrace 缩写) 命令,可以查看下程序崩溃时的堆栈信息,
如下所示:
(gdb) bt #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 #1 0x00007ff8d6f5c859 in __GI_abort () at abort.c:79 #2 0x00007ff8d6fc729e in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7ff8d70f108f "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:155 #3 0x00007ff8d7069aea in __GI___fortify_fail ( msg=msg@entry=0x7ff8d70f1077 "stack smashing detected") at fortify_fail.c:26 #4 0x00007ff8d7069ab6 in __stack_chk_fail () at stack_chk_fail.c:24 #5 0x000056183642c243 in fun () at main.c:12 #6 0x000056183642c28c in main () at main.c:20
以上的堆栈信息,显示 __stack_chk_fail () 错误。可以看到调用 fun() 函数过程中发生了栈问题。
__stack_chk_fail () 打印信息说明发生了缓冲区溢出,即数组越界问题。
从这段栈信息中并不能直观的定位到出现问题的地方,因为出现问题的地方是之前的数组越界,但是这里的栈信息已经执行到主函数 main中, 特别是在大项目中更是无法准确的定位出错的地方。
越界写坏内存,什么时候会 crash 取决于我们写坏的内存什么时候被用到,有时候会离犯罪现场特别远。所以,导致定位这样情况的崩溃问题比较麻烦。
在发生__stack_chk_fail () 错误的附近函数 fun() 中第11 行打断点,如下所示:
(gdb) b main.c:11 Breakpoint 1 at 0x56183642c22a: file main.c, line 11. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x000056183642c22a in fun at main.c:11 (gdb) r Starting program: /home/wangtian/Code_Learns/C_Learns/debug/debug2/main.out ---main()--- pid 3868 ---fun() ---buffer: Hello Breakpoint 1, fun () at main.c:11 11 return 0; (gdb) n 12 } (gdb) *** stack smashing detected ***: terminated Program received signal SIGABRT, Aborted. __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 50 ../sysdeps/unix/sysv/linux/raise.c: 没有那个文件或目录. (gdb)
初步可以定位到,是在函数 fun() 内部发生了数组越界问题。目前还不能定位到具体某一行。
canary 栈溢出保护机制的具体原理:
- gcc 编译器默认开启了 canary 栈保护机制。
- canray 栈保护机制是专门针对栈溢出攻击涉及的一中保护机制。
- 由于栈溢出攻击的主要目标是通过溢出覆盖函数栈高位的返回地址,因此,其思路是在函数开始执行前,即返回地址前写入一个字长的随机数据(canary),在函数返回前校验该值是否被改变,如果改变则认为是栈溢出,程序直接终止,以此来防止信息泄露。
总结
以上一系列的操作,说明通过 gdb 对 数据越界问题的调试定位,这种方法不是太好用。
原因:越界写坏内存,什么时候会 crash 取决于我们写坏的内存什么时候被用到,有时候会离犯罪现场特别远。所以,导致定位这样情况的崩溃问题比较麻烦。后续文章再针对数组越界问题定位方法进行新的总结。
好了,以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。