本章我们主要讲述一下Linux下C/C++程序coredump功能的使用。

Linux中很多信号(signal)的默认动作会使进程结束并产生一个coredump文件,即一个该进程结束时的内存镜像文件。一些调试器(如GDB)可以通过该镜像来获得程序结束时候的一些状态。下面列出几种信号,它们在发生时会产生coredump文件:

Signal    Action         Comment
SIGQUIT Core Quit from keyboard
SIGILL Core Illegal Instruction
SIGABRT Core Abort signal from abort
SIGSEGV Core Invalid memory reference
SIGTRAP Core Trace/breakpoint trap

在控制终端,我们可以使用 ctrl+\ 来终止一个进程,这会向进程发出SIGQUIT信号,默认是会产生coredump的。还有其他情景也会产生coredump,如:程序调用abort()函数、访存错误、非法指令等等。

1. 开启coredump功能

1.1 修改coredump文件的最大值

1) 在当前会话终端中输入命令ulimit -c,若输出结果为0,说明默认是关闭core dump的。即当程序异常终止时,也不会产生coredump文件。

2) 我们可以使用ulimit -c unlimited来开启coredump功能,并且不限制coredump文件的大小;如果需要限制文件的大小,将unlimited改成你想生成core文件最大的大小(注意单位为blocks)。

3) 用上面的命令只会对当前会话终端有效,如果想永久生效,可以修改/etc/security/limits.conf文件,增加如下一行

[root@localhost test-src]# vi /etc/security/limits.conf 
# /etc/security/limits.conf
#
#Each line describes a limit for a user in the form:
#
#<domain>        <type>  <item>  <value>
*                 soft    core    unlimited

注意: 此种方式会在系统下一次重启时生效

1.2 修改core文件保存路径

1) 默认情况下,生成的core文件保存在可执行文件所在的目录下,文件名就为core

2) 通过修改/proc/sys/kernel/core_uses_pid文件,可以让生成的core文件名是否自动加上pid号。例如:

# echo 1>/proc/sys/kernel/core_uses_pid

这样生成的core文件名将会变成core.pid,其中pid表示该进程的进程号。

3) 还可以通过修改/proc/sys/kernel/core_pattern来控制生成core文件的保存位置以及文件名格式。格式说明如下:

  • %% 指示一个百分号字符

  • %p 被dumped进程的进程号

  • %u 被dumped进程的实际用户ID

  • %g 被dumped进程的实际组ID

  • %s 引发产生core文件的信号

  • %t 产生dump时候的时间。从1970-1-1 00:00:00到当前的秒数

  • %h 主机名字

  • %e 可执行文件的文件名(不包括路径前缀)

  • %E 可执行文件的文件名(包括路径前缀)

  • %c 指定产生core文件的大小限制

例如可以用如下命令:

# cat /proc/sys/kernel/core_pattern
|/usr/libexec/abrt-hook-ccpp %s %c %p %u %g %t e %P %I

# echo "/tmp/corefile-%e-%p-%t" > /proc/sys/kernel/core_pattern
# cat /proc/sys/kernel/core_pattern
/tmp/corefile-%e-%p-%t

这样设置之后生成的core文件保存在/tmp/目录下,文件名格式为”corefile-progname-pid-timestap”。

2. 使用GDB调试core文件

产生了core文件,我们该如何使用该core文件进行调试呢? Linux可以使用GDB来调试core文件,步骤如下:

1) 首先使用gcc编译源文件,加上-g以增加调试信息

2) 按照上面打开coredump,以使程序异常终止时能生成core文件

3) 运行程序,当core dump之后,使用命令 gdb program core 来查看core文件,其中program为可执行程序名,core为生成的core文件名.

下面用一个简单的例子test.c来说明:

#include <stdio.h>

int func(int *p)
{
   int y = *p;

   return y;
}

int main(int argc,char *argv[])
{
    int *p = NULL;
   
    return func(p);
}

编译运行(添加-g选项,以增加调试信息):

[root@localhost test-src]# gcc -g -o test test.c
[root@localhost test-src]# ./test
Segmentation fault (core dumped)

查看/tmp目录我们看到产生了coredump文件:

[root@localhost test-src]# cat /proc/sys/kernel/core_pattern
/tmp/corefile-%e-%p-%t
[root@localhost test-src]# ls -al /tmp/corefile-test-26150-1514885060 
-rw-------. 1 root root 249856 Jan  2 01:24 /tmp/corefile-test-26150-1514885060

采用gdb调试该coredump文件:

[root@localhost test-src]# gdb ./test /tmp/corefile-test-26150-1514885060 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-94.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/test-src/test...done.
[New LWP 26150]
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004004f9 in func (p=0x0) at test.c:5
5          int y = *p;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7.x86_64
(gdb) r
Starting program: /root/test-src/./test 

Program received signal SIGSEGV, Segmentation fault.
0x00000000004004f9 in func (p=0x0) at test.c:5
5          int y = *p;
(gdb) bt
#0  0x00000000004004f9 in func (p=0x0) at test.c:5
#1  0x0000000000400526 in main (argc=1, argv=0x7fffffffe4c8) at test.c:14
(gdb) 

2.1 无调试信息的情况下找core的代码行

如下代码(test.c):

#include <stdio.h>
#include <stdlib.h>


void dump_crash()
{
    char *pstr = "test_content";

    free(pstr);
}

int main(int argc, char *argv[])
{
    dump_crash();

    return 0x0;
}

1) 编译

执行如下命令进行编译(注: 这里特意不加上调试信息):

# gcc -o test test.c
# ls
test  test.c

2) 产生coredump文件

开启coredump并运行:

# ulimit -c unlimited
# ./test
# ls
core.25238  test  test.c

3) gdb调试coredump文件

如下我们使用gdb来调试该coredump文件:

# gdb -q ./test core.25238 
Reading symbols from /data/home/lzy/just_for_test/test...(no debugging symbols found)...done.
[New LWP 25238]
Core was generated by `./test'.
Program terminated with signal 6, Aborted.
#0  0x00007f0c269ef387 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-307.el7.1.x86_64
(gdb) bt
#0  0x00007f0c269ef387 in raise () from /lib64/libc.so.6
#1  0x00007f0c269f0a78 in abort () from /lib64/libc.so.6
#2  0x00007f0c26a31ed7 in __libc_message () from /lib64/libc.so.6
#3  0x00007f0c26a383e4 in malloc_printerr () from /lib64/libc.so.6
#4  0x0000000000400542 in dump_crash ()
#5  0x000000000040055e in main ()
(gdb) frame 4
#4  0x0000000000400542 in dump_crash ()
(gdb) disassemble
Dump of assembler code for function dump_crash:
   0x0000000000400526 <+0>:     push   %rbp
   0x0000000000400527 <+1>:     mov    %rsp,%rbp
   0x000000000040052a <+4>:     sub    $0x10,%rsp
   0x000000000040052e <+8>:     movq   $0x4005f4,-0x8(%rbp)
   0x0000000000400536 <+16>:    mov    -0x8(%rbp),%rax
   0x000000000040053a <+20>:    mov    %rax,%rdi
   0x000000000040053d <+23>:    callq  0x400400 <free@plt>
=> 0x0000000000400542 <+28>:    nop
   0x0000000000400543 <+29>:    leaveq 
   0x0000000000400544 <+30>:    retq   
End of assembler dump.

在上面没有调试信息的情况下,打开coredump堆栈,并不会直接显示core的代码行。

此时我们用frame 4跳到堆栈的第4帧,然后用disassemble命令打开该帧函数的汇编代码。从上面可以看到,coredump是发生在箭头所指定位置的。接下来,我们就可以重点分析那一段代码就可以了。



[参看]:

  1. Linux优化之IO子系统监控与调优

  2. gdb调试coredump