GDB 调试(转)

来源:互联网 发布:什么是爱情 知乎 编辑:程序博客网 时间:2024/06/06 07:51
*GDB* 是 GNU _*开源*_ <javascript:;>组织_*发布*_ <javascript:;>的一个强
大的 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*_ <javascript:;> 下)
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 所定义的路径。

**GDB* **的命令概貌**
*———————**

启动 *gdb* 后,就你被带入 *gdb* 的调试环境中,就可以使用 *gdb* 的命令开
始调试程序了, *gdb* 的命令可以使用 help 命令来查看,如下所示:

/home/hchen> *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*) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(*gdb*)

*gdb* 的命令很多, *gdb* 把之分成许多个种类。 help 命令只是例出 *gdb*
命令种类,如果要看种类中的命令,可以使用 help <class> 命令,如: help
breakpoints ,查看设置断点的所有命令。也可以直接 help <command> 来查看命
令的帮助。


*gdb* 中,输入命令时,可以不用打全命令,只用打命令的前几个字符就可以了,
当然,命令的前几个字符应该要标志着一个唯一的命令,在 Linux 下,你可以敲
击两次 TAB 键来补齐命令的全称,如果有重复的,那么 *gdb* 会把其例出来。

示例一:在进入函数 func 时,设置一个断点。可以敲入 break func ,或是直接
就是 b func
(*gdb*) b func
Breakpoint 1 at 0x8048458: file hello.c, line 10.

示例二:敲入 b 按两次 TAB 键,你会看到所有 b 打头的命令:
(*gdb*) b
backtrace break bt
(*gdb*)

示例三:只记得函数的前缀,可以这样:
(*gdb*) b make_ < 按 TAB 键 >
(再按下一次 TAB 键,你会看到 : )
make_a_section_from_file make_environ
make_abs_section make_function_type
make_blockvector make_pointer_type
make_cleanup make_reference_type
make_command make_symbol_completion_list
(*gdb*) b make_
*GDB* 把所有 make 开头的函数全部例出来给你查看。

示例四:调试 C++ 的程序时,有可以函数名一样。如:
(*gdb*) b 'bubble( M-?
bubble(double,double) bubble(int,int)
(*gdb*) b 'bubble(
你可以查看到 C++ 中的所有的重载函数及参数。(注: M-? 和 “ 按两次 TAB 键
” 是一个意思)

要退出 *gdb* 时,只用发 quit 或命令简称 q 就行了。

**GDB* **中运行** UNIX **的** shell **程序**
*————————————**

*gdb* 环境中,你可以执行 UNIX 的 shell 的命令,使用 *gdb* 的 shell 命
令来完成:

shell <command string>
调用 UNIX 的 shell 来执行 <command string> ,环境变量 SHELL 中定义的
UNIX 的 shell 将会被用来执行 <command string> ,如果 SHELL 没有定义,那
就使用 UNIX 的标准 shell : /bin/sh 。(在 _*Windows*_ <javascript:;> 中
使用 Command.com 或 cmd.exe )

还有一个 *gdb* 命令是 make :
make <make-args>
可以在 *gdb* 中执行 make 命令来重新 build 自己的程序。这个命令等价于 “
shell make <make-args> ” 。

*在** *GDB* **中运行程序**
*————————**

当以 *gdb* <program> 方式启动 *gdb* 后, *gdb* 会在 PATH 路径和当前目录
中搜索 <program> 的源文件。如要确认 *gdb* 是否读到源文件,可使用 l 或
list 命令,看看 *gdb* 是否能列出源代码。

*gdb* 中,运行程序使用 r 或是 run 命令。程序的运行,你有可能需要设置
下面四方面的事。

1 、程序运行参数。
set args 可指定运行时参数。(如: set args 10 20 30 40 50 )
show args 命令可以查看设置好的运行参数。

2 、运行环境。
path <dir> 可设定程序的运行路径。
show paths 查看程序的运行路径。
set environment varname [=value] 设置环境变量。如: set env USER=hchen
show environment [varname] 查看环境变量。

3 、工作目录。
cd <dir> 相当于 shell 的 cd 命令。
pwd 显示当前的所在目录。

4 、程序的输入输出。
info terminal 显示你程序用到的终端的模式。
使用重定向控制程序输出。如: run > outfile
tty 命令可以指写输入输出的终端设备。如: tty /dev/ttyb


*调试已运行的程序**
*————————**

两种方法:
1 、在 UNIX 下用 ps 查看正在运行的程序的 PID (进程 ID ),然后用 *gdb*
<program> PID 格式挂接正在运行的程序。
2 、先用 *gdb* <program> 关联上源代码,并进行 *gdb* ,在 *gdb* 中用
attach 命令来挂接进程的 PID 。并用 detach 来取消挂接的进程。

*暂停** / **恢复程序运行**
*—————————**

调试程序中,暂停程序运行是必须的, *GDB* 可以方便地暂停程序的运行。你可
以设置程序的在哪行停住,在什么条件下停住,在收到什么信号时停往等等。以便
于你查看运行时的变量,以及运行时的流程。

当进程被 *gdb* 停住时,你可以使用 info program 来查看程序的是否在运行,
进程号,被暂停的原因。

*gdb* 中,我们可以有以下几种暂停方式:断点( BreakPoint )、观察点(
WatchPoint )、捕捉点( CatchPoint )、信号( Signals )、线程停止(
Thread Stops )。如果要恢复程序运行,可以使用 c 或是 continue 命令。


*一、设置断点(** BreakPoint **)*

我们用 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>
... 可以是上述的参数, condition 表示条件,在条件成立时停住。比如在循环
境体中,可以设置 break if i=100 ,表示当 i 为 100 时停住程序。

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

*二、设置观察点(** WatchPoint **)*

观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有
变化,马上停住程序。我们有下面的几种方法来设置观察点:

watch <expr>
为表达式(变量) expr 设置一个观察点。一量表达式值有变化时,马上停住程序。

rwatch <expr>
当表达式(变量) expr 被读时,停住程序。

awatch <expr>
当表达式(变量)的值被读或被写时,停住程序。

info watchpoints
列出当前所设置了的所有观察点。


*三、设置捕捉点(** CatchPoint **)*

你可设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或
是 C++ 的异常。设置捕捉点的格式为:

catch <event>
当 event 发生时,停住程序。 event 可以是下面的内容:
1 、 throw 一个 C++ 抛出的异常。( throw 为关键字)
2 、 catch 一个 C++ 捕捉到的异常。( catch 为关键字)
3 、 exec 调用系统调用 exec 时。( exec 为关键字,目前此功能只在 HP-UX
下有用)
4 、 fork 调用系统调用 fork 时。( fork 为关键字,目前此功能只在 HP-UX
下有用)
5 、 vfork 调用系统调用 vfork 时。( vfork 为关键字,目前此功能只在
HP-UX 下有用)
6 、 load 或 load <libname> 载入共享库(动态链接库)时。( load 为关键
字,目前此功能只在 HP-UX 下有用)
7 、 unload 或 unload <libname> 卸载共享库(动态链接库)时。( unload 为
关键字,目前此功能只在 HP-UX 下有用)

tcatch <event>
只设置一次捕捉点,当程序停住以后,应点被自动删除。

*四、维护停止点*

上面说了如何设置程序的停止点, *GDB* 中的停止点也就是上述的三类。在
*GDB* 中,如果你觉得已定义好的停止点没有用了,你可以使用 delete 、 clear
、 disable 、 enable 这几个命令来进行维护。

clear
清除所有的已定义的停止点。

clear <function>
clear <filename:function>
清除所有设置在函数上的停止点。

clear <linenum>
clear <filename:linenum>
清除所有设置在指定行上的停止点。

delete [breakpoints] [range...]
删除指定的断点, breakpoints 为断点号。如果不指定断点号,则表示删除所有
的断点。 range 表示断点号的范围(如: 3-7 )。其简写命令为 d 。


比删除更好的一种方法是 disable 停止点, disable 了的停止点, *GDB* 不会
删除,当你还需要时, enable 即可,就好像回收站一样。

disable [breakpoints] [range...]
disable 所指定的停止点, breakpoints 为停止点号。如果什么都不指定,表示
disable 所有的停止点。简写命令是 dis.

enable [breakpoints] [range...]
enable 所指定的停止点, breakpoints 为停止点号。

enable [breakpoints] once range...
enable 所指定的停止点一次,当程序停止后,该停止点马上被 *GDB* 自动
disable 。

enable [breakpoints] delete range...
enable 所指定的停止点一次,当程序停止后,该停止点马上被 *GDB* 自动删除。

*五、停止条件维护*

前面在说到设置断点时,我们提到过可以设置一个条件,当条件成立时,程序自动
停止,这是一个非常强大的功能,这里,我想专门说说这个条件的相关维护命令。
一般来说,为断点设置一个条件,我们使用 if 关键词,后面跟其断点条件。并
且,条件设置好后,我们可以用 condition 命令来修改断点的条件。(只有
break 和 watch 命令支持 if , catch 目前暂不支持 if )

condition <bnum> <expression>
修改断点号为 bnum 的停止条件为 expression 。

condition <bnum>
清除断点号为 bnum 的停止条件。


还有一个比较特殊的维护命令 ignore ,你可以指定程序运行时,忽略停止条件几次。

ignore <bnum> <count>
表示忽略断点号为 bnum 的停止条件 count 次。

*六、为停止点设定运行命令*

我们可以使用 *GDB* 提供的 command 命令来设置停止点的运行命令。也就是说,
当运行的程序在被停止住时,我们可以让其自动运行一些别的命令,这很有利行自
动化调试。对基于 *GDB* 的自动化调试是一个强大的支持。


commands [bnum]
... command-list ...
end

为断点号 bnum 指写一个命令列表。当程序被该断点停住时, *gdb* 会依次运行
命令列表中的命令。

例如:

break foo if x>0
commands
printf "x is %d/n",x
continue
end
断点设置在函数 foo 中,断点条件是 x>0 ,如果程序被断住后,也就是,一旦 x
的值在 foo 函数中大于 0 , *GDB* 会自动打印出 x 的值,并继续运行程序。

如果你要清除断点上的命令序列,那么只要简单的执行一下 commands 命令,并直
接在打个 end 就行了。


原创粉丝点击