Linux下coredump调试1:使用

949 阅读4分钟

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

调试是程序员的一项基本能力,经历过大大小小的实战,随着见识的增长,只要用心留意并做总结,相信调试的能力会越来越好。写程序不可能没有bug,只是bug容易不容易被发现,bug的危害大不大。笔者使用coredump调试很多年了,也有部分的工作笔记,无奈事多人懒,一直没有好好总结。直到最近帮同学排查bug时,才真正下定决心写几篇文章。本文为开篇,主要描述coredump作用及配置的一些注意事项,并给出简单示例。

一、介绍

程序运行过程中,难免会出现这种或那种错误导致其异常崩溃。如果使能了coredump,则操系统在程序出现异常时生成一个文件,一般名称为core。该文件有大量可以帮助我们分析程序状态的信息——比如可回溯函数栈帧。与gdb实时运行程序调试不同,coredump是一种事后分析的手段。特别适合一些错误很难重现的程序,例如设备运行三天后,挂掉了,可能是内存泄漏导致内存不够用了,可能哪个模块在一个条件语句中出现了指针非法。使用gdb对coredump文件可以分析程序崩溃之前执行的代码片段,可以帮助我们快速定位错误。

二、配置coredump

2.1 使用调试版本

一般地,在软件开发阶段,都会使用调试版本。亦即程序使用-g来编译,另外要注意,不能用strip命令对生成的可执行文件进行精简。这一切均是为了可执行文件保留着调试信息,以方便使用gdb进行调用。

2.2 设置ulimit

为了在程序崩溃时产生coredump文件,要设置coredump大小。先进行查看:

root@latelee:~# ulimit -a
time(seconds)        unlimited
file(blocks)         unlimited
data(kb)             unlimited
stack(kb)            8192
coredump(blocks)     0
memory(kb)           unlimited
locked memory(kb)    64
process              545
nofiles              1024
vmemory(kb)          unlimited
locks                unlimited

可以看到,其中coredump一行值为0,此时无法产生coredump文件。因此要设置其文件为无限大,命令:

# ulimit -c unlimited

结果如下:

root@latelee:~# ulimit -a
time(seconds)        unlimited
file(blocks)         unlimited
data(kb)             unlimited
stack(kb)            8192
coredump(blocks)     unlimited
memory(kb)           unlimited
locked memory(kb)    64
process              545
nofiles              1024
vmemory(kb)          unlimited
locks                unlimited

另外,要程序运行的同一环境下执行。比如,不能在一个终端设置ulimit,而在另一个终端运行程序,这样不能生成coredump文件的。
为了保证coredump文件名称的唯一性,可以用下面命令设置:

# echo 'core.%e.%s.%t' > /proc/sys/kernel/core_pattern  # 设置生成的coredump文件名称

2.3 文件生成

万一程序因段错误等原因崩溃,则会生成core文件。

2.4 3、调试

有了coredump文件,就可以结合可执行文件,使用gdb进行调试了,命令形式如下:

# gdb hello core (注:需保证编译器一致)

三、实例演示

下面使用李迟当年参考uboot源码实现的命令行解析代码为例。
首先,代码编译时必须带-g选项。
然后,在命令行中设置core文件:

[latelee@latelee creadline]$ ulimit -c unlimited

运行程序:

[latelee@latelee creadline]$ ./a.out
Hit any key to stop autoboot:  0
You abort.
NotAShell> print
in print ...
Segmentation fault (core dumped)     
(已经产生了coredump文件)

下面使用gdb调试:

[latelee@latelee creadline]$ gdb a.out core.2896

gdb打印信息如下:

GNU gdb Fedora (6.8-29.fc10)
Copyright (C) 2008 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 "i386-redhat-linux-gnu"...

warning: Can't read pathname for load map: Input/output error.
Reading symbols from /usr/lib/libstdc++.so.6...done.
Loaded symbols for /usr/lib/libstdc++.so.6
Reading symbols from /lib/libm.so.6...done.
Loaded symbols for /lib/libm.so.6
Reading symbols from /lib/libgcc_s.so.1...done.
Loaded symbols for /lib/libgcc_s.so.1
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
[New process 2896]
#0  0x0084d626 in memcpy () from /lib/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.9-2.i686 libgcc-4.3.2-7.i386 libstdc++-4.3.2-7.i386
(gdb)

使用bt命令查看函数调用的栈帧(用where命令也可以):

(gdb) bt
#0  0x0084d626 in memcpy () from /lib/libc.so.6
#1  0x08048660 in ?? ()
#2  0x08049f5d in do_print (argc=1, argv=0xbf8935dc) at my_command.c:31
#3  0x08048cc2 in run_command (cmd=0x804cca0 "print") at command.c:380
#4  0x08049e93 in readline_test ()
#5  0x08049ee6 in main ()
(gdb)

可以看到最后出现的可疑的代码函数为do_print,里面使用到了memcpy,具体是哪里,可以用frame NUM查看栈帧。比如,查看第2个栈帧:

(gdb) frame 2
#2  0x08049f5d in do_print (argc=1, argv=0xbf8935dc) at my_command.c:31
31          memcpy(p, buffer, 5);
(gdb) 

可以看到,已经输出了文件名称、行号、函数等重要信息。 至此,就可以根据信息修改代码了。 下面给出错误函数代码片段:

int do_print(int argc, char * const argv[])
{
    printf("in print ...\n");
    char* p = NULL;
    char* buffer = "hello";
    memcpy(p, buffer, 5);     //  注:拷贝到空指针,错误!!!
    return 0;
}

下面在某款嵌入式平台上进行同时的演示,这里只给出最后gdb调用的过程。

latelee@latelee:creadline$ arm_v5t_le-gdb readline_arm readline_arm.core.11.1941
GNU gdb 6.3 (MontaVista 6.3-20.0.66.0600975 2006-07-06)
...
Core was generated by `./readline_arm'.
Program terminated with signal 11, Segmentation fault.
...
#0  0x4020f164 in memcpy ()
   from /opt/mv_pro_4.0.1/montavista/pro/devkit/arm/v5t_le/target/lib/libc.so.6
(gdb) bt
#0  0x4020f164 in memcpy ()
   from /opt/mv_pro_4.0.1/montavista/pro/devkit/arm/v5t_le/target/lib/libc.so.6
#1  0x0000b660 in do_print (argc=1, argv=0xbefffcd0) at my_command.c:242
#2  0x00009574 in run_command (cmd=0x16c64 "print") at command.c:380
#3  0x0000af7c in readline_test () at main.c:78
#4  0x0000b20c in main (argc=1, argv=0xbefffe64) at main.c:139
(gdb) frame 1
#1  0x0000b660 in do_print (argc=1, argv=0xbefffcd0) at my_command.c:242
242         memcpy(p, buf, 5);
(gdb)

注:行文不特别区别交叉编译,对于交叉版本,自行补充交叉前缀即可。文中“gdb”表示gdb调用器,在x86-linux上其文件为gdb,而在不同的arm-linux上,文件名称不同,如可能是arm_v5t_le-gdb,也可能是arm-linux-gdb,还可能是arm-arago-linux-gnueabi-gdb。
下一篇文章将演示几个实际中遇到过的bug的排查实例。