调试基础:内存转储
core dump, 通常译作内存转储,core之所以译作内存,而不是核心,纯属“著名”的历史原因,因为早期的内存有一个叫磁芯(magnectic core)的东西。内存转储会在磁盘中产生一个文件,是某个进程在转储时刻的内存映像及寄存器等信息。内存转储通常发生在进程执行了有致命错误的指令时,常见的就是著了名的Segmentation fault,即段错误,而导致段错误的就是C/C++编程中经常发生的内存非法访问。内存转储操作由操作系统内核进行。当然,内存转储还可以是用户主动发起请求,内核执行转储。
内存转储对于程序调试是至关重要的。利用内存转储文件(core files)对程序进行调试是一种异步的静态调试,所谓异步是指异常发生与调试不是在同一时刻,所谓静态指的是core files保存的只是转储时刻进程的内存状态。比如,一个长期运行的服务程序,我们不知道哪里有bug,该bug何时被触发,这种情况下,利用异常发生时的转储文件就可以在一定程度上定位出异常发生的原因。另外,对于非致命性错误,比如死锁或者死循环,进程不会自动结束,通常内核也不会主动杀掉这种进程,这时用户就可以请求内核将该进程进行内存转储,以此来查看进程的运行状态(比如各进程的堆栈)。
使能/生成内核转储
ulimit -c命令可以查看和控制转储文件允许的生成大小,如果你的程序发生了致命错误却没有被转储,很可能是该值被设为零了。ulimit -c CORESIZE可以将core files所允许的最大值设为CORESIZE,ulimit -c unlimited取消对core files大小的限制,内核为尽量多的转储进程的内存。
异常发生时,内核会根据设定大小生成转储文件。手动生成转储文件有两种方法,使用gdb attach到目标进程执行generate-core-file(缩写为gcore),但由于attach到进程会导致进程在较长时间内挂起,对某些程序(比如网络程序)会造成不必要的影响,这时使用随gdb一起发布的gcore命令就比较合适了,且更加方便。
1 2 3 4 5 | $ pgrep -l foobar 12345 foobar $ gdb gdb> attach 12345 gdb> gcore |
1 | $ gcore `pidof foobar` |
控制转储文件的路径和格式
很多系统中转储文件的默认文件名就是core,这时很难确定它是由哪个进程生成的,且可能导致同目录下多个转储文件的相互覆盖。这时可以在/etc/sysctl.conf文件中定制core文件的格式,比如,
1 2 3 | $ cat /etc/sysctl.conf kernel.core_uses_pid = 1 $ sysctl -p # to take effect |
将kernel.core_uses_pid设为1,core文件就会以core.PID的形式生成。更复杂的格式可以由kernel.core_pattern变量控制。比如,
1 2 3 4 | $ cat /etc/sysctl.conf kernel.core_pattern = %e-%p-%u-%t.core kernel.core_uses_pid = 0 $ sysctl -p # to take effect |
这时生成的core文件就是EXECUTABLE-PID-UID-TIME.core的形式,将kernel.core_uses_pid设成0是为了将core文件末尾的pid去掉。kernel.core_pattern可以使用的格式字符如下,
格式符 | 说明 |
---|---|
%% | % |
%p | pid |
%u | uid |
%g | gid |
%s | 引发core dump的信号编号 |
%t | 转储时间 |
%h | 主机名 |
%e | 可执行文件名 |
%c | ulimit -c |
此外,kernel.core_pattern还可以加上路径前缀,用以使core文件转储到特定的目录。
使用gdb调试内存转储文件
使用gdb可以方便的查看转储文件,同时还需要进程的可执行文件(包含进程的虚拟地址空间),若想调试时能够查看源代码, 编译可执行文件时需要加上-g调试选项,同时还要有源代码可用(注意:编译器的-g选项并没有把源代码包含到可执行文件中)。
使用gdb调试转储文件的命令为
1 | $ gdb EXECUTABLE -c COREFILE |
或者
1 | $ gdb EXECUTABLE COREFILE |
或者
1 2 | $ gdb EXECUTABLE gdb> core COREFILES |
调试core file的具体方法属于gdb的使用问题,之前有一篇我学习gdb时写的总结,也画过一张思维导图,仅供参考。
另外,像前面说的,利用转储文件进行调试属于静态调试,gdb的很多命令是“无法使用”的,比如i proc mapping。当然更无法进行单步调试。
另外,转储文件为ELF格式文件,可以使用objdump/readelf等工具查看,但相比动态库、可执行文件等其他ELF二进制文件,它包含的信息是有限的。