段错误bug的调试

来源:互联网 发布:人工语音软件 编辑:程序博客网 时间:2024/06/05 04:42
我们在用C/C++语言写程序的时侯,内存管理的绝大部分工作都是需要我们来做的。实际上,内存管理是一个比较繁琐的工作,无论你多高明,经验多丰富,难免会在此处犯些小错误,而通常这些错误又是那么的浅显而易于消除。但是手工“除虫”(debug),往往是效率低下且让人厌烦的,本文将就"段错误"这个内存访问越界的错误谈谈如何快速定位这些"段错误"的语句。

下面将就以下的一个存在段错误的程序介绍几种调试方法:

     dummy_function (void)
     {
            unsigned char *ptr = 0x00;
            *ptr = 0x00;
     }
    6
     int main (void)
     {
            dummy_function ();
    10
   11         return 0;
   12  }
作为一个熟练的C/C++程序员,以上代码的bug应该是很清楚的,因为它尝试操作地址为0的内存区域,而这个内存区域通常是不可访问的禁区,当然就会出错了。我们尝试编译运行它:
xiaosuo@gentux test $./a.out
段错误
果然不出所料,它出错并退出了。
1.利用gdb逐步查找段错误:
这种方法也是被大众所熟知并广泛采用的方法,首先我们需要一个带有调试信息的可执行程序,所以我们加上“-g-rdynamic"的参数进行编译,然后用gdb调试运行这个新编译的程序,具体步骤如下:
xiaosuo@gentux test $ gcc -g-rdynamic d.c
xiaosuo@gentux test $ gdb ./a.out
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License,and you are
welcome to change it and/or distribute copies of it under certainconditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type"show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using hostlibthread_db library "/lib/libthread_db.so.1".

(gdb) r
Starting program: /home/xiaosuo/test/a.out

Program received signal SIGSEGV, Segmentation fault.
0x08048524 in dummy_function () at d.c:4
             *ptr = 0x00;
(gdb)                       
哦?!好像不用一步步调试我们就找到了出错位置d.c文件的第4行,其实就是如此的简单。
从这里我们还发现进程是由于收到了SIGSEGV信号而结束的。通过进一步的查阅文档(man 7signal),我们知道SIGSEGV默认handler的动作是打印”段错误"的出错信息,并产生Core文件,由此我们又产生了方法二。
2.分析Core文件:
Core文件是什么呢?
The  defaultaction of certain signals is to cause a process to terminate andproduce a core dump file, a disk file containing an image of theprocess's memory  at the time oftermination.  A list of the signals which cause aprocess to dump core can be found in signal(7).以上资料摘自man page(man 5core)。不过奇怪了,我的系统上并没有找到core文件。后来,忆起为了渐少系统上的拉圾文件的数量(本人有些洁癖,这也是我喜欢Gentoo的原因之一),禁止了core文件的生成,查看了以下果真如此,将系统的core文件的大小限制在512K大小,再试:
xiaosuo@gentux test $ ulimit-c
0
xiaosuo@gentux test $ ulimit -c 1000
xiaosuo@gentux test $ ulimit -c
1000
xiaosuo@gentux test $ ./a.out
段错误 (core dumped)
xiaosuo@gentux test $ ls
a.out  core d.c  f.c  g.c pango.c  test_iconv.c test_regex.c
core文件终于产生了,用gdb调试一下看看吧:
xiaosuo@gentux test $ gdb./a.out core
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License,and you are
welcome to change it and/or distribute copies of it under certainconditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type"show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using hostlibthread_db library "/lib/libthread_db.so.1".


warning: Can't read pathname for load map: 输入/输出错误.
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.
#0  0x08048524 in dummy_function () atd.c:4
             *ptr = 0x00;
哇,好历害,还是一步就定位到了错误所在地,佩服一下Linux/Unix系统的此类设计。
接着考虑下去,以前用windows系统下的ie的时侯,有时打开某些网页,会出现“运行时错误”,这个时侯如果恰好你的机器上又装有windows的编译器的话,他会弹出来一个对话框,问你是否进行调试,如果你选择是,编译器将被打开,并进入调试状态,开始调试。
Linux下如何做到这些呢?我的大脑飞速地旋转着,有了,让它在SIGSEGV的handler中调用gdb,于是第三个方法又诞生了:
3.段错误时启动调试:
#include
#include
#include
#include

void dump(int signo)
{
       char buf[1024];
       char cmd[1024];
       FILE *fh;

       snprintf(buf, sizeof(buf), "/proc/%d/cmdline", getpid());
       if(!(fh = fopen(buf, "r")))
               exit(0);
       if(!fgets(buf, sizeof(buf), fh))
               exit(0);
       fclose(fh);
       if(buf[strlen(buf) - 1] == '\n')
               buf[strlen(buf) - 1] = '\0';
       snprintf(cmd, sizeof(cmd), "gdb %s %d", buf, getpid());
       system(cmd);

       exit(0);
}

       void
dummy_function (void)
{
       unsigned char *ptr = 0x00;
       *ptr = 0x00;
}

       int
main (void)
{
       signal(SIGSEGV, &dump);
       dummy_function ();

       return 0;
}
编译运行效果如下:
xiaosuo@gentux test $ gcc -g-rdynamic f.c
xiaosuo@gentux test $ ./a.out
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License,and you are
welcome to change it and/or distribute copies of it under certainconditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type"show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using hostlibthread_db library "/lib/libthread_db.so.1".

Attaching to program: /home/xiaosuo/test/a.out, process9563
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
0xffffe410 in __kernel_vsyscall ()
(gdb) bt
#0  0xffffe410 in __kernel_vsyscall ()
#1  0xb7ee4b53 in waitpid () from/lib/libc.so.6
#2  0xb7e925c9 in strtold_l () from/lib/libc.so.6
#3  0x08048830 in dump (signo=11) atf.c:22
#4 
#5  0x0804884c in dummy_function () atf.c:31
#6  0x08048886 in main () at f.c:38
怎么样?是不是依旧很酷?
以上方法都是在系统上有gdb的前提下进行的,如果没有呢?其实glibc为我们提供了此类能够dump栈内容的函数簇,详见/usr/include/execinfo.h(这些函数都没有提供manpage,难怪我们找不到),另外你也可以通过gnu的手册进行学习。
4.利用backtrace和objdump进行分析:
重写的代码如下:
#include
#include
#include
#include


       void
dummy_function (void)
{
       unsigned char *ptr = 0x00;
       *ptr = 0x00;
}

void dump(int signo)
{
       void *array[10];
       size_t size;
       char **strings;
       size_t i;

       size = backtrace (array, 10);
       strings = backtrace_symbols (array, size);

       printf ("Obtained %zd stack frames.\n", size);

       for (i = 0; i < size; i++)
               printf ("%s\n", strings[i]);

       free (strings);

       exit(0);
}

       int
main (void)
{
       signal(SIGSEGV, &dump);
       dummy_function ();

       return 0;
}
编译运行结果如下:
xiaosuo@gentux test $ gcc -g-rdynamic g.c
xiaosuo@gentux test $ ./a.out
Obtained 5 stack frames.
./a.out(dump+0x19) [0x80486c2]
[0xffffe420]
./a.out(main+0x35) [0x804876f]
/lib/libc.so.6(__libc_start_main+0xe6) [0xb7e02866]
./a.out [0x8048601]
这次你可能有些失望,似乎没能给出足够的信息来标示错误,不急,先看看能分析出来什么吧,用objdump反汇编程序,找到地址0x804876f对应的代码位置:
xiaosuo@gentux test $ objdump -da.out

 8048765:      e8 02 fe ffff         call   804856c
 804876a:      e8 25 ff ffff         call   8048694
 804876f     b8 00 00 0000         mov   $0x0,�x
 8048774:      c9                     leave
我们还是找到了在哪个函数(dummy_function)中出错的,信息已然不是很完整,不过有总比没有好的啊!
后记:
本文给出了分析"段错误"的几种方法,不要认为这是与孔乙己先生的"回"字四种写法一样的哦,因为每种方法都有其自身的适用范围和适用环境,请酌情使用,或遵医嘱。
0 0
原创粉丝点击