26.Linux:用GDB调试程序(转载)

好烦躁的段错误,转了几篇文章放一起,后面再按照遇到的问题详细研究。


第0篇:http://blog.chinaunix.net/uid-24690947-id-3079020.html

Linux环境下段错误的产生原因及调试方法小结 

最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多、花费时间最长的问题就是著名的“段错误”(Segmentation Fault)。借此机会系统学习了一下,这里对Linux环境下的段错误做个小结,方便以后同类问题的排查与解决。

1. 段错误是什么

       一句话来说,段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况。这里贴一个对于“段错误”的准确定义(参考Answers.com):

A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation fault occurs  when a program attempts to access a memory location that it is  not allowed to access, or attempts to access a memory location in a way that is  not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based  on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors.

Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging  for most purposes, but much of the terminology of segmentation is still used, "segmentation fault" being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy.

On Unix-like operating systems, a process that accesses invalid memory receives the SIGSEGV signal.  On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.

2. 段错误产生的原因 
2.1 访问不存在的内存地址
#include
#include
void main()
{
int *ptr = NULL;
*ptr =  0;
}

2.2 访问系统保护的内存地址
#include
#include
void main()
{
int *ptr = ( int *) 0;
*ptr =  100;
}

2.3 访问只读的内存地址
#include
#include
#include< string.h>
void main()
{
char *ptr =  " test ";
strcpy(ptr,  " TEST ");
}

2.4 栈溢出
#include
#include
void main()
{
main();
}

等等其他原因。

3. 段错误信息的获取

程序发生段错误时,提示信息很少,下面有几种查看段错误的发生信息的途径。

3.1 dmesg

dmesg可以在应用程序crash掉时,显示内核中保存的相关信息。如下所示,通过dmesg命令可以查看发生段错误的程序名称、引起段错误发生的内存地址、指令指针地址、堆栈指针地址、错误代码、错误原因等。以程序2.3为例:

panfeng@ubuntu:~/segfault$ dmesg
[ 2329.479037] segfault3[2700]: segfault at 80484e0 ip 00d2906a sp bfbbec3c error 7 in libc-2.10.1.so[cb4000+13e000]

使用gcc编译程序的源码时,加上-g参数,这样可以使得生成的二进制文件中加入可以用于gdb调试的有用信息。以程序2.3为例:

panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
3.3 nm

使用nm命令列出二进制文件中的符号表,包括符号地址、符号类型、符号名等,这样可以帮助定位在哪里发生了段错误。以程序2.3为例:

panfeng@ubuntu:~/segfault$ nm segfault3
08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
080484dc R _IO_stdin_used
w _Jv_RegisterClasses
08049f10 d __CTOR_END__
08049f0c d __CTOR_LIST__
08049f18 D __DTOR_END__
08049f14 d __DTOR_LIST__
080484ec r __FRAME_END__
08049f1c d __JCR_END__
08049f1c d __JCR_LIST__
0804a014 A __bss_start
0804a00c D __data_start
08048490 t __do_global_ctors_aux
08048360 t __do_global_dtors_aux
0804a010 D __dso_handle
w __gmon_start__
0804848a T __i686.get_pc_thunk.bx
08049f0c d __init_array_end
08049f0c d __init_array_start
08048420 T __libc_csu_fini
08048430 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A _end
080484bc T _fini
080484d8 R _fp_hw
080482bc T _init
08048330 T _start
0804a014 b completed.6990
0804a00c W data_start
0804a018 b dtor_idx.6992
080483c0 t frame_dummy
080483e4 T main
U memcpy@@GLIBC_2.0

使用ldd命令查看二进制程序的共享链接库依赖,包括库的名称、起始地址,这样可以确定段错误到底是发生在了自己的程序中还是依赖的共享库中。以程序2.3为例:

panfeng@ubuntu:~/segfault$ ldd ./segfault3
linux-gate.so.1 => (0x00e08000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00675000)
/lib/ld-linux.so.2 (0x00482000)

4. 段错误的调试方法 
4.1 使用printf输出信息

这个是看似最简单但往往很多情况下十分有效的调试方式,也许可以说是程序员用的最多的调试方式。简单来说,就是在程序的重要代码附近加上像printf这类输出信息,这样可以跟踪并打印出段错误在代码中可能出现的位置。

为了方便使用这种方法,可以使用条件编译指令#ifdef DEBUG和#endif把printf函数包起来。这样在程序编译时,如果加上-DDEBUG参数就能查看调试信息;否则不加该参数就不会显示调试信息。

4.2 使用gcc和gdb 4.2.1 调试步骤

 1、为了能够使用gdb调试程序,在编译阶段加上-g参数,以程序2.3为例:

panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c

2、使用gdb命令调试程序:

panfeng@ubuntu:~/segfault$ gdb ./segfault3 
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
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 "i486-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/panfeng/segfault/segfault3...done.
(gdb)

3、进入gdb后,运行程序:

(gdb)  run
Starting program: /home/panfeng/segfault/segfault3 

Program received signal SIGSEGV, Segmentation fault.
0x001a306a in memcpy () from /lib/tls/i686/cmov/libc.so.6
(gdb)

从输出看出,程序2.3收到SIGSEGV信号,触发段错误,并提示地址0x001a306a、调用memcpy报的错,位于/lib/tls/i686/cmov/libc.so.6库中。

4、完成调试后,输入quit命令退出gdb:

(gdb) quit
A debugging session is active.

Inferior 1 [process 3207] will be killed.

Quit anyway? (y or n) y

4.2.2 适用场景

1、仅当能确定程序一定会发生段错误的情况下使用。

2、当程序的源码可以获得的情况下,使用-g参数编译程序。

3、一般用于测试阶段,生产环境下gdb会有副作用:使程序运行减慢,运行不够稳定,等等。

4、即使在测试阶段,如果程序过于复杂,gdb也不能处理。

4.3 使用core文件和gdb

在4.2节中提到段错误会触发SIGSEGV信号,通过man 7 signal,可以看到SIGSEGV默认的handler会打印段错误出错信 息,并产生core文件,由此我们可以借助于程序异常退出时生成的core文件中的调试信息,使用gdb工具来调试程序中的段错误。

4.3.1 调试步骤

1、在一些Linux版本下,默认是不产生core文件的,首先可以查看一下系统core文件的大小限制:

panfeng@ubuntu:~/segfault$ ulimit -c
0

2、可以看到默认设置情况下,本机Linux环境下发生段错误时不会自动生成core文件,下面设置下core文件的大小限制(单位为KB):

panfeng@ubuntu:~/segfault$ ulimit -c 1024
panfeng@ubuntu:~/segfault$ ulimit -c
1024

3、运行程序2.3,发生段错误生成core文件:

panfeng@ubuntu:~/segfault$ ./segfault3
段错误 (core dumped)

4、加载core文件,使用gdb工具进行调试:

panfeng@ubuntu:~/segfault$ gdb ./segfault3 ./core 
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
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 "i486-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/panfeng/segfault/segfault3...done.

warning: Can't read pathname  for load map: 输入/输出错误.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...(no debugging symbols found)...done.
Loaded symbols  for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols  for /lib/ld-linux.so.2
Core was generated by `./segfault3'.
Program terminated with signal 11, Segmentation fault.
#0 0x0018506a in memcpy () from /lib/tls/i686/cmov/libc.6

从输出看出,同4.2.1中一样的段错误信息。

5、完成调试后,输入quit命令退出gdb:

(gdb) quit
4.3.2 适用场景

1、适合于在实际生成环境下调试程序的段错误(即在不用重新发生段错误的情况下重现段错误)。

2、当程序很复杂,core文件相当大时,该方法不可用。

4.4 使用objdump 4.4.1 调试步骤

1、使用dmesg命令,找到最近发生的段错误输出信息:

panfeng@ubuntu:~/segfault$ dmesg
... ...
[17257.502808] segfault3[3320]: segfault at 80484e0 ip 0018506a sp bfc1cd6c error 7 in libc-2.10.1.so[110000+13e000]

其中,对我们接下来的调试过程有用的是发生段错误的地址:80484e0和指令指针地址:0018506a。

2、使用objdump生成二进制的相关信息,重定向到文件中:

panfeng@ubuntu:~/segfault$ objdump -d ./segfault3 > segfault3Dump

其中,生成的segfault3Dump文件中包含了二进制文件的segfault3的汇编代码。

3、在segfault3Dump文件中查找发生段错误的地址:

panfeng@ubuntu:~/segfault$ grep -n -A 10 -B 10 "80484e0" ./segfault3Dump 
121- 80483df: ff d0  call *%eax
122- 80483e1: c9 leave 
123- 80483e2: c3 ret 
124- 80483e3: 90 nop
125-
126-080483e4:
127- 80483e4: 55 push %ebp
128- 80483e5: 89 e5 mov %esp,%ebp
129- 80483e7: 83 e4 f0 and $0xfffffff0,%esp
130- 80483ea: 83 ec 20 sub $0x20,%esp
131: 80483ed: c7 44 24 1c e0 84 04 movl $0x80484e0,0x1c(%esp)
132- 80483f4: 08 
133- 80483f5: b8 e5 84 04 08 mov $0x80484e5,%eax
134- 80483fa: c7 44 24 08 05 00 00 movl $0x5,0x8(%esp)
135- 8048401: 00 
136- 8048402: 89 44 24 04 mov %eax,0x4(%esp)
137- 8048406: 8b 44 24 1c mov 0x1c(%esp),%eax
138- 804840a: 89 04 24 mov %eax,(%esp)
139- 804840d: e8 0a ff ff ff  call 804831c 
140- 8048412: c9 leave 
141- 8048413: c3 ret

通过对以上汇编代码分析,得知段错误发生main函数,对应的汇编指令是movl $0x80484e0,0x1c(%esp),接下来打开程序的源码,找到汇编指令对应的源码,也就定位到段错误了。

4.4.2 适用场景

1、不需要-g参数编译,不需要借助于core文件,但需要有一定的汇编语言基础。

2、如果使用了gcc编译优化参数(-O1,-O2,-O3)的话,生成的汇编指令将会被优化,使得调试过程有些难度。

4.5 使用catchsegv

catchsegv命令专门用来扑获段错误,它通过动态加载器(ld-linux.so)的预加载机制(PRELOAD)把一个事先写好的库(/lib/libSegFault.so)加载上,用于捕捉断错误的出错信息。

panfeng@ubuntu:~/segfault$ catchsegv ./segfault3
Segmentation fault (core dumped)
*** Segmentation fault
Register dump:

EAX: 00000000 EBX: 00fb3ff4 ECX: 00000002 EDX: 00000000
ESI: 080484e5 EDI: 080484e0 EBP: bfb7ad38 ESP: bfb7ad0c

EIP: 00ee806a EFLAGS: 00010203

CS: 0073 DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 007b

Trap: 0000000e Error: 00000007 OldMask: 00000000
ESP/signal: bfb7ad0c CR2: 080484e0

Backtrace:
/lib/libSegFault.so[0x3b606f]
??:0(??)[0xc76400]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xe89b56]
/build/buildd/eglibc-2.10.1/csu/../sysdeps/i386/elf/ start.S:122(_start)[0x8048351]

Memory map:

00258000-00273000 r-xp 00000000 08:01 157 /lib/ld-2.10.1.so
00273000-00274000 r--p 0001a000 08:01 157 /lib/ld-2.10.1.so
00274000-00275000 rw-p 0001b000 08:01 157 /lib/ld-2.10.1.so
003b4000-003b7000 r-xp 00000000 08:01 13105 /lib/libSegFault.so
003b7000-003b8000 r--p 00002000 08:01 13105 /lib/libSegFault.so
003b8000-003b9000 rw-p 00003000 08:01 13105 /lib/libSegFault.so
00c76000-00c77000 r-xp 00000000 00:00 0 [vdso]
00e0d000-00e29000 r-xp 00000000 08:01 4817 /lib/libgcc_s.so.1
00e29000-00e2a000 r--p 0001b000 08:01 4817 /lib/libgcc_s.so.1
00e2a000-00e2b000 rw-p 0001c000 08:01 4817 /lib/libgcc_s.so.1
00e73000-00fb1000 r-xp 00000000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb1000-00fb2000 ---p 0013e000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb2000-00fb4000 r--p 0013e000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb4000-00fb5000 rw-p 00140000 08:01 1800 /lib/tls/i686/cmov/libc-2.10.1.so
00fb5000-00fb8000 rw-p 00000000 00:00 0
08048000-08049000 r-xp 00000000 08:01 303895 /home/panfeng/segfault/segfault3
08049000-0804a000 r--p 00000000 08:01 303895 /home/panfeng/segfault/segfault3
0804a000-0804b000 rw-p 00001000 08:01 303895 /home/panfeng/segfault/segfault3
09432000-09457000 rw-p 00000000 00:00 0 [heap]
b78cf000-b78d1000 rw-p 00000000 00:00 0
b78df000-b78e1000 rw-p 00000000 00:00 0
bfb67000-bfb7c000 rw-p 00000000 00:00 0 [stack]

5. 一些注意事项

1、出现段错误时,首先应该想到段错误的定义,从它出发考虑引发错误的原因。

2、在使用指针时,定义了指针后记得初始化指针,在使用的时候记得判断是否为NULL。

3、在使用数组时,注意数组是否被初始化,数组下标是否越界,数组元素是否存在等。

4、在访问变量时,注意变量所占地址空间是否已经被程序释放掉。

5、在处理变量时,注意变量的格式控制是否合理等。


6. 参考资料列表

1、http://www.docin.com/p-105923877.html

2、http://blog.chinaunix.net/space.php?uid=317451&do=blog&id=92412




来自:http://blog.csdn.net/haoel/article/details/2879/

GDB概述
————

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。

一般来说,GDB主要帮忙你完成下面四个方面的功能:

    1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
    2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
    3、当程序被停住时,可以检查此时你的程序中所发生的事。
    4、动态的改变你程序的执行环境。

从上面看来,GDB和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。让我们一一看来。


一个调试示例
——————

源程序:tst.c

     1 #include <stdio.h>
     2
     3 int func(int n)
     4 {
     5         int sum=0,i;
     6         for(i=0; i<n; i++)
     7         {
     8                 sum+=i;
     9         }
    10         return sum;
    11 }
    12
    13
    14 main()
    15 {
    16         int i;
    17         long result = 0;
    18         for(i=1; i<=100; i++)
    19         {
    20                 result += i;
    21         }
    22
    23        printf("result[1-100] = %d /n", result );
    24        printf("result[1-250] = %d /n", func(250) );
    25 }

编译生成执行文件:(Linux下)
    hchen/test> cc -g tst.c -o tst

使用GDB调试:

hchen/test> gdb tst  <---------- 启动GDB
GNU gdb 5.1.1
Copyright 2002 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 certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-suse-linux"...
(gdb) l     <-------------------- l命令相当于list,从第一行开始例出原码。
1        #include <stdio.h>
2
3        int func(int n)
4        {
5                int sum=0,i;
6                for(i=0; i<n; i++)
7                {
8                        sum+=i;
9                }
10               return sum;
(gdb)       <-------------------- 直接回车表示,重复上一次命令
11       }
12
13
14       main()
15       {
16               int i;
17               long result = 0;
18               for(i=1; i<=100; i++)
19               {
20                       result += i;    
(gdb) break 16    <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func  <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break  <-------------------- 查看断点信息。
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08048496 in main at tst.c:16
2   breakpoint     keep y   0x08048456 in func at tst.c:5
(gdb) r           <--------------------- 运行程序,run命令简写
Starting program: /home/hchen/test/tst

Breakpoint 1, main () at tst.c:17    <---------- 在断点处停住。
17               long result = 0;
(gdb) n          <--------------------- 单条语句执行,next命令简写。
18               for(i=1; i<=100; i++)
(gdb) n
20                       result += i;
(gdb) n
18               for(i=1; i<=100; i++)
(gdb) n
20                       result += i;
(gdb) c          <--------------------- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050       <----------程序输出。

Breakpoint 2, func (n=250) at tst.c:5
5                int sum=0,i;
(gdb) n
6                for(i=1; i<=n; i++)
(gdb) p i        <--------------------- 打印变量i的值,print命令简写。
$1 = 134513808
(gdb) n
8                        sum+=i;
(gdb) n
6                for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8                        sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6                for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt        <--------------------- 查看函数堆栈。
#0  func (n=250) at tst.c:5
#1  0x080484e4 in main () at tst.c:24
#2  0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish    <--------------------- 退出函数。
Run till exit from #0  func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24              printf("result[1-250] = %d /n", func(250) );
Value returned is $6 = 31375
(gdb) c     <--------------------- 继续运行。
Continuing.
result[1-250] = 31375    <----------程序输出。

Program exited with code 027. <--------程序退出,调试结束。
(gdb) q     <--------------------- 退出gdb。
hchen/test>

好了,有了以上的感性认识,还是让我们来系统地认识一下gdb吧。

 


使用GDB
————

一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:

    > cc -g hello.c -o hello
    > g++ -g hello.cpp -o hello

如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。当你用-g把调试信息加入之后,并成功编译目标代码以后,让我们来看看如何用gdb来调试他。

启动GDB的方法有以下几种:

    1、gdb <program> 
       program也就是你的执行文件,一般在当然目录下。

    2、gdb <program> core
       用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。

    3、gdb <program> <PID>
       如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。

 

GDB启动时,可以加上一些GDB的启动开关,详细的开关可以用gdb -help查看。我在下面只例举一些比较常用的参数:

    -symbols <file> 
    -s <file> 
    从指定文件中读取符号表。

    -se file 
    从指定文件中读取符号表信息,并把他用在可执行文件中。

    -core <file>
    -c <file> 
    调试时core dump的core文件。

    -directory <directory>
    -d <directory>
    加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。

下一页->

(版权所有,转载时请注明作者和出处) 

第二篇:来自:http://blog.csdn.net/deutschester/article/details/6739861

我们打算使用gdb去解决为什么下面的程序(文件为segfault.c)引起了段错误的问题。下面的这段程序是从用户那里读入一行文本字串然后显示在屏幕上。然而,如下当前的程序并不会如期执行...

[cpp]  view plain  copy
 print ?
  1. <span style="font-size:18px;">#include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. int main(int argc, char **argv)  
  5. {  
  6.    char *buf;  
  7.   
  8.    buf = malloc(1<<31);  
  9.   
  10.    fgets(buf, 1024, stdin);  
  11.    printf("%s\n", buf);  
  12.   
  13.    return 1;  
  14. }</span>  


 

 第一步是使用带有调试标志(debugging flags)的方式编译这段代码,如下:

~# gcc -g segfault.c

然后运行:

~# a.out

Hello World!

Segmentation fault

 

这并不是我们所期待的。是时候启动强大的gdb了。

~# gdb a.out
GNU gdb 5.0
Copyright 2000 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 certain conditions.
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"...
(gdb) 

 

我们直接运行就来看看到底发生了什么:

(gdb) run
Starting program: /home/dgawd/cpsc/363/a.out 
test string

Program received signal SIGSEGV, Segmentation fault.
0x4007fc13 in _IO_getline_info () from /lib/libc.so.6

 

我们收到了来自操作系统的SIGSEGV信号。这就意味着我们试图去访问一段非法的内存,让我们试试backtrace(= bt)命令:

 

(gdb) backtrace
#0  0x4007fc13 in _IO_getline_info () from /lib/libc.so.6
#1  0x4007fb6c in _IO_getline () from /lib/libc.so.6
#2  0x4007ef51 in fgets () from /lib/libc.so.6
#3  0x80484b2 in main (argc=1, argv=0xbffffaf4) at segfault.c:10
#4  0x40037f5c in __libc_start_main () from /lib/libc.so.6

 

这里我们只关心我们自己的代码,因此我们就切换到3号堆栈帧(stack frame3)来看看程序在哪里崩溃的:

(gdb) frame 3
#3  0x80484b2 in main (argc=1, argv=0xbffffaf4) at segfault.c:10
10        fgets(buf, 1024, stdin)

 

哦,原来是调用fgets引起的崩溃。一般的,我们都假设库函数比如fgets都可以正确地工作(如果不是这样的话,我们的麻烦就大了)。因此这个问题的原因就一定是其中我们的一个参数的问题。你也许不知道‘stdin’是一个全局的变量,它是被stdio 库创建的。因此我们假定这个参数是正确的。那么剩下的就只能是‘buf’了,然后查看buf当前的值:

(gdb) print buf
$1 = 0x0

buf的值是0x0,也就是NULL指针。这并不是我们锁期待的 —— buf应该指向第8行代码分配到的内存。因此我们需要返回到第8行并看看在哪里发生了什么。首先kill掉我们程序当前运行的调用:

(gdb) kill
Kill the program being debugged? (y or n) y

(注意:不用使用quit直接退出gdb,这样比较麻烦。直接kill掉当前的程序调用即可

 

然后在第8行设置一个断点:

(gdb) break segfault.c:8
Breakpoint 1 at 0x8048486: file segfault.c, line 8.

 

再次运行程序:

(gdb) run
Starting program: /home/dgawd/cpsc/363/a.out 

Breakpoint 1, main (argc=1, argv=0xbffffaf4) at segfault.c:8
8         buf = malloc(1<<31);

 

我们检查malloc调用前后buf值的变化。初始化buf以前,其值应该是一个随机杂乱值(garbage),就像这里的:

(gdb) print buf
$2 = 0xbffffaa8 "鳃?\177\003@t`\001@\001"

 

我们step over(单步执行)malloc调用然后再次检查buf的值:

(gdb) next
10        fgets(buf, 1024, stdin);
(gdb) print buf
$3 = 0x0

 

可见调用了malloc之后,buf是NULL。如果你查看malloc的手册页(man page),你就会发现malloc在不能分配够所需的内存的时候就会返回NULL。因此确定是我们的malloc失败了。让我们返回到代码再次看看:

7 :   buf = malloc(1<<31);

哦,表达式1<<31(整型1左移31次,原文错写为右移)是429497295, 或4GB (gigabytes).很少有机器会有这样的内存——大多数只有256MB(显然这篇文章有年头了,都什么年代了,这点内存操作系统估计启动一半就挂了)。因此malloc必然会失败。此外,在fgets中我们只读入1024字节。所有的额外空间都会白白浪费掉,尽管我们可以分配到。这里我们将1<<31改为1024(或者1<<9),这样程序就会按照我们的期望运行了:

 

~# a.out
Hello World!
Hello World!

这样你就可以知道怎样使用gdb来调试段错误了,这是非常有用的。这个例子同时也说明了一个非常重要的准则:总是检查malloc的返回值!拥有美好的一天(说实在,我让段错误恶心了一天。但以后就应该不太恶心了,以后每一天都美好吧 ^_^)。



第三篇:http://blog.csdn.net/21cnbao/article/details/7385161

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具,GDB主要可帮助工程师完成下面4个方面的功能:

  • 启动程序,可以按照工程师自定义的要求随心所欲的运行程序。
  • 让被调试的程序在工程师指定的断点处停住,断点可以是条件表达式。
  • 当程序被停住时,可以检查此时程序中所发生的事,并追索上文。
  • 动态地改变程序的执行环境。
不管是调试Linux内核空间的驱动还是调试用户空间的应用程序,掌握gdb的用法都是必须。而且,调试内核和调试应用程序时使用的gdb命令是完全相同的,下面以代码清单22.2的应用程序为例演示gdb调试器的用法。

 
 
  1. 1  int add(int a, int b)  
  2. 2  {  
  3. 3    return a + b;  
  4. 4  }  
  5. 5    
  6. 6  main()  
  7. 7  {  
  8. 8    int sum[10] =   
  9. 9    {  
  10. 10     0, 0, 0, 0, 0, 0, 0, 0, 0, 0       
  11. 11   }  ;  
  12. 12   int i;  
  13. 13     
  14. 14   int array1[10] =  
  15. 15   {  
  16. 16     48, 56, 77, 33, 33, 11, 226, 544, 78, 90  
  17. 17   };  
  18. 18   int array2[10] =  
  19. 19   {  
  20. 20     85, 99, 66, 0x199, 393, 11, 1, 2, 3, 4  
  21. 21   };  
  22. 22   
  23. 23   for (i = 0; i < 10; i++)  
  24. 24   {  
  25. 25     sum[i] = add(array1[i], array2[i]);  
  26. 26   }  
  27. 27 }  

使用命令gcc –g gdb_example.c –o gdb_example编译上述程序,得到包含调试信息的二进制文件example,执行gdb gdb_example命令进入调试状态:

[cpp]  view plain  copy
  1. [root@localhost driver_study]# gdb gdb_example  
  2. GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)  
  3. Copyright 2003 Free Software Foundation, Inc.  
  4. GDB is free software, covered by the GNU General Public License, and you are  
  5. welcome to change it and/or distribute copies of it under certain conditions.  
  6. Type "show copying" to see the conditions.  
  7. There is absolutely no warranty for GDB.  Type "show warranty" for details.  
  8. This GDB was configured as "i386-redhat-linux-gnu"...  
  9. (gdb)  

1、list命令

在gdb中运行list命令(缩写l)可以列出代码,list的具体形式包括:

  • list <linenum> ,显示程序第linenum行周围的源程序,如:
[cpp]  view plain  copy
  1. (gdb) list 15  
  2. 10          
  3. 11        int array1[10] =  
  4. 12        {  
  5. 13          48, 56, 77, 33, 33, 11, 226, 544, 78, 90  
  6. 14        };  
  7. 15        int array2[10] =  
  8. 16        {  
  9. 17          85, 99, 66, 0x199, 393, 11, 1, 2, 3, 4  
  10. 18        };  
  11. 19  
  • list <function> ,显示函数名为function的函数的源程序,如:
[cpp]  view plain  copy
  1. (gdb) list main  
  2. 2       {  
  3. 3         return a + b;  
  4. 4       }  
  5. 5  
  6. 6       main()  
  7. 7       {  
  8. 8         int sum[10];  
  9. 9         int i;  
  10. 10          
  11. 11        int array1[10] =  
  • list,显示当前行后面的源程序。
  • list - ,显示当前行前面的源程序。

下面演示了使用gdb中的run(缩写r)、break(缩写b)、next(缩写n)命令控制程序的运行,并使用print(缩写p)命令打印程序中的变量sum的过程:

(gdb) break add
Breakpoint 1 at 0x80482f7: file gdb_example.c, line 3.
(gdb) run  
Starting program: /driver_study/gdb_example 

Breakpoint 1, add (a=48, b=85) at gdb_example.c:3
warning: Source file is more recent than executable.

3         return a + b;
(gdb) next
4       }
(gdb) next
main () at gdb_example.c:23
23        for (i = 0; i < 10; i++)
(gdb) next
25          sum[i] = add(array1[i], array2[i]);
(gdb) print sum
$1 = {133, 0, 0, 0, 0, 0, 0, 0, 0, 0}

2、run命令

在gdb中,运行程序使用run命令。在程序运行前,我们可以设置如下4方面的工作环境:

  • 程序运行参数
set args  可指定运行时参数,如: set args 10 20 30 40 50;show args  命令可以查看设置好的运行参数。
  • 运行环境

path <dir> 可设定程序的运行路径;how paths可查看程序的运行路径;set environment varname [=value]用于设置环境变量,如set env USER=baohua;

show environment [varname]则用于查看环境变量。

  • 工作目录
cd <dir>  相当于shell的cd命令;pwd 显示当前所在的目录。
  • 程序的输入输出

info terminal 用于显示程序用到的终端的模式;gdb中也可以使用重定向控制程序输出,如run > outfile

tty命令可以指定输入输出的终端设备,如:tty /dev/ttyS1

3、break命令

在gdb中用break命令来设置断点,设置断点的方法包括:

  • break <function>
在进入指定函数时停住,C++中可以使用class::function或function(type, type)格式来指定函数名。
  • break <linenum>
在指定行号停住。
  • break +offset / break -offset
在当前行号的前面或后面的offset行停住,offiset为自然数。
  • break filename:linenum
在源文件filename的linenum行处停住。
  • break filename:function
在源文件filename的function函数的入口处停住。
  • break *address
在程序运行的内存地址处停住。
  • break
break命令没有参数时,表示在下一条指令处停住。
  • break ... if <condition>

“...”可以是上述的break <linenum>break +offset / break –offset中的参数,condition表示条件,在条件成立时停住。比如在循环体中,可以设置break if i=100,表示当i为100时停住程序。

查看断点时,可使用info命令,如info breakpoints [n]info break [n](n表示断点号)。

4、单步命令

在调试过程中,next命令用于单步执行,类似VC++中的step over。next的单步不会进入函数的内部,与next对应的step(缩写s)命令则在单步执行一个函数时,会进入其内部,类似VC++中的step into。下面演示了step命令的执行情况,在23行的add()函数调用处执行step会进入其内部的“return a+b;”语句:

[cpp]  view plain  copy
  1. (gdb) break 25  
  2. Breakpoint 1 at 0x8048362: file gdb_example.c, line 25.  
  3. (gdb) run  
  4. Starting program: /driver_study/gdb_example   
  5.   
  6. Breakpoint 1, main () at gdb_example.c:25  
  7. 25          sum[i] = add(array1[i], array2[i]);  
  8. (gdb) step  
  9. add (a=48, b=85) at gdb_example.c:3  
  10. 3         return a + b;  
单步执行的更复杂用法包括:

  • step <count>

单步跟踪,如果有函数调用,则进入该函数(进入函数的前提是,此函数被编译有debug信息)。step后面不加count表示一条条地执行,加表示执行后面的count条指令,然后再停住。

  • next <count>
单步跟踪,如果有函数调用,它不会进入该函数。同样地,next后面不加count表示一条条地执行,加表示执行后面的count条指令,然后再停住。
  • set step-mode
set step-mode on用于打开step-mode模式,这样,在进行单步跟踪时,程序不会因为没有debug信息而不停住,这个参数的设置可便于查看机器码。set step-mod off用于关闭step-mode模式。
  • finish
运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
  • until (缩写u)
一直在循环体内执行单步,退不出来是一件令人烦恼的事情,until命令可以运行程序直到退出循环体。
  • stepi(缩写si)和nexti(缩写ni)
stepi和nexti用于单步跟踪一条机器指令,一条程序代码有可能由数条机器指令完成,stepi和nexti可以单步执行机器指令。 另外,运行“display/i $pc”命令后,单步跟踪会在打出程序代码的同时打出机器指令,即汇编代码。

5、continue命令

当程序被停住后,可以使用continue命令(缩写c,fg命令同continue命令)恢复程序的运行直到程序结束,或到达下一个断点,命令格式为: 

[cpp]  view plain  copy
  1. continue [ignore-count]  
  2. c [ignore-count]  
  3. fg [ignore-count]  
ignore-count表示忽略其后多少次断点。 假设我们设置了函数断点 add() ,并 watch i ,则在continue过程中,每次遇到 add() 函数或i发生变化,程序就会停住,如:

[cpp]  view plain  copy
  1. (gdb) continue  
  2. Continuing.  
  3. Hardware watchpoint 3: i  
  4.   
  5. Old value = 2  
  6. New value = 3  
  7. 0x0804838d in main () at gdb_example.c:23  
  8. 23        for (i = 0; i < 10; i++)  
  9. (gdb) continue  
  10. Continuing.  
  11.   
  12. Breakpoint 1, main () at gdb_example.c:25  
  13. 25          sum[i] = add(array1[i], array2[i]);  
  14. (gdb) continue  
  15. Continuing.  
  16. Hardware watchpoint 3: i  
  17.   
  18. Old value = 3  
  19. New value = 4  
  20. 0x0804838d in main () at gdb_example.c:23  
  21. 23        for (i = 0; i < 10; i++)  

6、print命令

在调试程序时,当程序被停住时,可以使用print命令(缩写为p),或是同义命令inspect来查看当前程序的运行数据。print命令的格式是: 

[cpp]  view plain  copy
  1. print <expr>  
  2. print /<f> <expr>  

<expr> 是表达式,是被调试的程序中的表达式, <f> 是输出的格式,比如,如果要把表达式按16进制的格式输出,那么就是 /x 。在表达式中,有几种GDB所支持的操作符,它们可以用在任何一种语言中, “@” 是一个和数组有关的操作符, “::” 指定一个在文件或是函数中的变量, “{<type>} <addr>” 表示一个指向内存地址 <addr> 的类型为type的一个对象。

下面演示了查看sum[]数组的值的过程: 

[cpp]  view plain  copy
  1. (gdb) print sum  
  2. $2 = {133, 155, 0, 0, 0, 0, 0, 0, 0, 0}  
  3. (gdb) next  
  4.   
  5. Breakpoint 1, main () at gdb_example.c:25  
  6. 25          sum[i] = add(array1[i], array2[i]);  
  7. (gdb) next  
  8. 23        for (i = 0; i < 10; i++)  
  9. (gdb) print sum  
  10. $3 = {133, 155, 143, 0, 0, 0, 0, 0, 0, 0}  
当需要查看一段连续内存空间的值的时间,可以使用GDB的 “@” 操作符, “@” 的左边是第一个内存地址, “@” 的右边则是想查看内存的长度。例如如下动态申请的内存:

int *array = (int *) malloc (len * sizeof (int));

在GDB调试过程中这样显示出这个动态数组的值:

p *array@len

print的输出格式包括:

  • x 按十六进制格式显示变量。
  • d 按十进制格式显示变量。
  • u 按十六进制格式显示无符号整型。
  • o 按八进制格式显示变量。
  • t 按二进制格式显示变量。
  • a 按十六进制格式显示变量。
  • c 按字符格式显示变量。
  • f 按浮点数格式显示变量。
我们可用display命令设置一些自动显示的变量,当程序停住时,或是单步跟踪时,这些变量会自动显示。 如果要修改变量,如x的值,可使用如下命令:
print x=4

当用GDB的print查看程序运行时的数据时,每一个print都会被GDB记录下来。GDB会以$1,$2,$3 …这样的方式为每一个print命令编号。我们可以使用这个编号访问以前的表达式,如$1

7、watch命令

watch一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点: watch <expr>:为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序。rwatch <expr>:当表达式(变量)expr被读时,停住程序。awatch <expr>:当表达式(变量)的值被读或被写时,停住程序。info watchpoints:列出当前所设置了的所有观察点。 下面演示了观察i并在连续运行next时一旦发现i变化,i值就会显示出来的过程:

[cpp]  view plain  copy
  1. (gdb) watch i  
  2. Hardware watchpoint 3: i  
  3. (gdb) next  
  4. 23        for (i = 0; i < 10; i++)  
  5. (gdb) next  
  6. Hardware watchpoint 3: i  
  7.   
  8. Old value = 0  
  9. New value = 1  
  10. 0x0804838d in main () at gdb_example.c:23  
  11. 23        for (i = 0; i < 10; i++)  
  12. (gdb) next  
  13.   
  14. Breakpoint 1, main () at gdb_example.c:25  
  15. 25          sum[i] = add(array1[i], array2[i]);  
  16. (gdb) next  
  17. 23        for (i = 0; i < 10; i++)  
  18. (gdb) next  
  19. Hardware watchpoint 3: i  
  20.   
  21. Old value = 1  
  22. New value = 2  
  23. 0x0804838d in main () at gdb_example.c:23  
  24. 23        for (i = 0; i < 10; i++)  

8、examine命令

我们可以使用examine命令(缩写为x)来查看内存地址中的值。examine命令的语法如下所示:

x/<n/f/u> <addr> 

<addr>表示一个内存地址。“x/”后的n、f、u都是可选的参数,n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容;f 表示显示的格式,如果地址所指的是字符串,那么格式可以是s,如果地址是指令地址,那么格式可以是i;u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4字节。u参数可以被一些字符代替:b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了字节长度后,GDB会从指定的内存地址开始,读写指定字节,并把其当作一个值取出来。n、f、u这3个参数可以一起使用,例如命令“x/3uh 0x54320”表示从内存地址0x54320开始以双字节为1个单位(h)、16进制方式(u)显示3个单位(3)的内存。 == 

譬如下面的例子:

[cpp]  view plain  copy
  1. main()  
  2. {  
  3.         char *c = "hello world";  
  4.         printf("%s\n", c);  
  5. }  

我们在
[cpp]  view plain  copy
  1. char *c = "hello world";  
下一行设置断点后:
[cpp]  view plain  copy
  1. (gdb) l  
  2. 1    main()  
  3. 2    {  
  4. 3        char *c = "hello world";  
  5. 4        printf("%s\n", c);  
  6. 5    }  
  7. (gdb) b 4  
  8. Breakpoint 1 at 0x100000f17: file main.c, line 4.  
  9. (gdb) r  
  10. Starting program: /Users/songbarry/main  
  11. Reading symbols for shared libraries +. done  
  12.   
  13. Breakpoint 1, main () at main.c:4  
  14. 4        printf("%s\n", c);  
可以通过多种方式看C指向的字符串:

方法1:

[cpp]  view plain  copy
  1. (gdb) p c  
  2. $1 = 0x100000f2e "hello world"  
方法2:
[cpp]  view plain  copy
  1. (gdb) x/s 0x100000f2e  
  2. 0x100000f2e:     "hello world"  
方法3:
[cpp]  view plain  copy
  1. (gdb) p (char *)0x100000f2e  
  2. $3 = 0x100000f2e "hello world"  
将第一个字符改为大写:
[cpp]  view plain  copy
  1. (gdb) p *(char *)0x100000f2e='H'  
  2. $4 = 72 'H'  
再看看C:
[cpp]  view plain  copy
  1. (gdb) p c  
  2. $5 = 0x100000f2e "Hello world"  

9、set命令

修改寄存器:

[cpp]  view plain  copy
  1. (gdb) set $v0 = 0x004000000  
  2. (gdb) set $epc = 0xbfc00000   

修改内存:

[cpp]  view plain  copy
  1. (gdb) set {unsigned int}0x8048a51=0x0  
譬如对于第8节的例子:

[cpp]  view plain  copy
  1. (gdb) set {unsigned int}0x100000f2e=0x0         
  2. (gdb) x/10cb 0x100000f2e  
  3. 0x100000f2e:    0 '\0'  0 '\0'  0 '\0'  0 '\0'  111 'o' 32 ' '  119 'w' 111 'o'  
  4. 0x100000f36:    114 'r' 108 'l'  
  5. (gdb) p c  
  6. $10 = 0x100000f2e ""  

10、jump命令

一般来说,被调试程序会按照程序代码的运行顺序依次执行,但是GDB也提供了乱序执行的功能,也就是说,GDB可以修改程序的执行顺序,从而让程序随意跳跃。这个功能可以由GDB的jump命令:jump <linespec> 来指定下一条语句的运行点。<linespec>可以是文件的行号,可以是file:line格式,也可以是+num这种偏移量格式,表示下一条运行语句从哪里开始。jump <address> 这里的<address>是代码行的内存地址。 注意,jump命令不会改变当前的程序栈中的内容,所以,如果使用jump从一个函数跳转到另一个函数,当跳转到的函数运行完返回,进行出栈操作时必然会发生错误,这可能导致意想不到的结果,所以最好只用jump在同一个函数中进行跳转。

11、signal命令

使用singal命令,可以产生一个信号量给被调试的程序,如中断信号“Ctrl+C”。这非常方便于程序的调试,可以在程序运行的任意位置设置断点,并在该断点用GDB产生一个信号量,这种精确地在某处产生信号的方法非常有利于程序的调试。 signal命令的语法是:signal <signal>,UNIX的系统信号量通常从1到15,所以<signal>取值也在这个范围。

12、return命令

如果在函数中设置了调试断点,在断点后还有语句没有执行完,这时候我们可以使用return命令强制函数忽略还没有执行的语句并返回。 

[cpp]  view plain  copy
  1. return  
  2. return <expression>  
上述return命令用于取消当前函数的执行,并立即返回,如果指定了 <expression> ,那么该表达式的值会被作为函数的返回值。

13、call命令

call命令用于强制调用某函数: call <expr> 表达式中可以一是函数,以此达到强制调用函数的目的,它会显示函数的返回值(如果函数返回值不是void)。 其实,前面介绍的print命令也可以完成强制调用函数的功能。

14、info命令

info命令可以在调试时用来查看寄存器、断点、观察点和信号等信息。要查看寄存器的值,可以使用如下命令: info registers (查看除了浮点寄存器以外的寄存器)info all-registers (查看所有寄存器,包括浮点寄存器)info registers <regname ...> (查看所指定的寄存器) 要查看断点信息,可以使用如下命令:info break 列出当前所设置的所有观察点,使用如下命令:info watchpoints 查看有哪些信号正在被GDB检测,使用如下命令:info signals info handle 也可以使用info line命令来查看源代码在内存中的地址。info threads可以看多线程。info line后面可以跟行号、函数名、文件名:行号、文件名:函数名等多种形式,例如下面的命令会打印出所指定的源码在运行时的内存地址:

[cpp]  view plain  copy
  1. info line tst.c:func  

15、set scheduler-locking off|on|step

off 不锁定任何线程,也就是所有线程都执行,这是默认值。 
on 只有当前被调试程序会执行。 
step 在单步的时候,除了next过一个函数的情况以外,只有当前线程会执行。

与多线程调试相关的命令还包括:

thread ID 
切换当前调试的线程为指定ID的线程。 
 
break thread_test.c:123 thread all
在所有线程中相应的行上设置断点
 
thread apply ID1 ID2 command 
让一个或者多个线程执行GDB命令command。 
 
thread apply all command 
让所有被调试线程执行GDB命令command。

16、disassemble

disassemble命令用于反汇编,它可被用来查看当前执行时的源代码的机器码,其实际上只是把目前内存中的指令dump出来。下面的示例用于查看函数func的汇编代码:

[cpp]  view plain  copy
  1. (gdb) disassemble func  
  2. Dump of assembler code for function func:  
  3. 0x8048450 <func>:       push   %ebp  
  4. 0x8048451 <func+1>:     mov    %esp,%ebp  
  5. 0x8048453 <func+3>:     sub    $0x18,%esp  
  6. 0x8048456 <func+6>:     movl   $0x0,0xfffffffc(%ebp)  
  7. ...  
  8. End of assembler dump.  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值