3.4  gdb调试器

调试是所有程序员都会面临的问题。如何提高程序员的调试效率,更好、更快地定位程序中的问题从而加快程序开发的进度,是大家都很关注的问题。就如读者熟知的Windows下的一些调试工具,如Visual Studio自带的设置断点、单步跟踪等,都受到了广大用户的赞赏。那么,在Linux下有什么很好的调试工具呢?

 

gdb调试器是一款GNU开发组织并发布的UNIX/Linux下的程序调试工具。虽然,它没有图形化的友好界面,但是它强大的功能也足以与微软的Visual Studio等工具媲美。下面就请跟随笔者一步步学习gdb调试器。

 

3.4.1  gdb使用流程

这里给出了一个短小的程序,由此带领读者熟悉gdb的使用流程。建议读者能够动手实际操作一下。

首先,打开Linux下的编辑器vi或者emacs,编辑如下代码(由于为了更好地熟悉gdb的操作,笔者在此使用vi编辑,希望读者能够参见3.3节中对vi的介绍,并熟练使用vi)。

 

/*test.c*/

#include <stdio.h>

int sum(int m);

int main()

{

    int i, n = 0;

    sum(50);

    for(i = 1; i<= 50; i++)

    {

        n += i;

    }

    printf("The sum of 1-50 is %d \n", n );

}

int sum(int m)

{

    int i, n = 0;

    for (i = 1; i <= m; i++)

    {

        n += i;

        printf("The sum of 1-m is %d\n", n);

    }

}

 

在保存退出后首先使用gcc对test.c进行编译,注意一定要加上选项“-g”,这样编译出的可执行代码中才包含调试信息,否则之后gdb无法载入该可执行文件。

 

[root@localhost gdb]# gcc -g test.c -o test

 

虽然这段程序没有错误,但调试完全正确的程序可以更加了解gdb的使用流程。接下来就启动gdb进行调试。注意,gdb进行调试的是可执行文件,而不是如“.c”的源代码,因此,需要先通过gcc编译生成可执行文件才能用gdb进行调试。

 

[root@localhost gdb]# gdb test

GNU gdb Red Hat Linux (6.3.0.0-1.21rh)

Copyright 2004 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-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb)

 

可以看出,在gdb的启动画面中指出了gdb的版本号、使用的库文件等信息,接下来就进入了由“(gdb)”开头的命令行界面了。

(1)查看文件。

在gdb中键入“l”(list)就可以查看所载入的文件,如下所示。

注意

在gdb的命令中都可使用缩略形式的命令,如“l”代表“list”,“b”代表“breakpoint”,“p”代表“print”等,读者也可使用“help”命令查看帮助信息。

 

(gdb) l

1       #include <stdio.h>

2       int sum(int m);

3       int main()

4       {

5            int i,n = 0;

6             sum(50);

7             for(i = 1; i <= 50; i++)

8             {

9              n += i;

10            }

(gdb) l

11            printf("The sum of 1~50 is %d \n", n );

12       

13      }

14      int sum(int m)

15      {

16         int i, n = 0;

17          for(i = 1; i <= m; i++)

18          {

19              n += i;

20          }

21            printf("The sum of 1~m is = %d\n", n);

20      }

 

可以看出,gdb列出的源代码中明确地给出了对应的行号,这样就可以大大地方便代码的定位。

 

(2)设置断点。

设置断点是调试程序中一个非常重要的手段,它可以使程序运行到一定位置时暂停。因此,程序员在该位置处可以方便地查看变量的值、堆栈情况等,从而找出代码的症结所在。

在gdb中设置断点非常简单,只需在“b”后加入对应的行号即可(这是最常用的方式,另外还有其他方式设置断点),如下所示:

 

(gdb) b 6

Breakpoint 1 at 0x804846d: file test.c, line 6.

 

要注意的是,在gdb中利用行号设置断点是指代码运行到对应行之前将其停止,如上例中,代码运行到第6行之前暂停(并没有运行第6行)。

(3)查看断点情况。

在设置完断点之后,用户可以键入“info b”来查看设置断点情况,在gdb中可以设置多个断点。

 

(gdb) info b

Num Type           Disp Enb Address    What

1   breakpoint     keep y   0x0804846d in main at test.c:6

 

用户在断点键入“backrace”(只输入“bt”即可)可以查到调用函数(堆栈)的情况,这个功能在程序调试之中使用非常广泛,经常用于排除错误或者监视调用堆栈的情况。

 

(gdb) b 19

(gdb) c

Breakpoin 2, sum(m=50) at test.c:19

19                        printf(“The sum of 1-m is %d\n”, n);

(gdb) bt

#0  sum(m=50) at test.c:19            /* 停在test.c的sum()函数,第19行*/

#1  0x080483e8 in main() at test.c:6  /* test.c的第6行调用sum函数*/

 

(4)运行代码。

接下来就可运行代码了,gdb默认从首行开始运行代码,键入“r”(run)即可(若想从程序中指定行开始运行,可在r后面加上行号)。

 

(gdb) r

Starting program: /root/workplace/gdb/test

Reading symbols from shared object read from target memory...done.

Loaded system supplied DSO at 0x5fb000

 

Breakpoint 1, main () at test.c:6

6                 sum(50);

 

可以看到,程序运行到断点处就停止了。

(5)查看变量值。

在程序停止运行之后,程序员所要做的工作是查看断点处的相关变量值。在gdb中键入“p”+变量值即可,如下所示:

 

(gdb) p n

$1 = 0

(gdb) p i

$2 = 134518440

 

在此处,为什么变量“i”的值为如此奇怪的一个数字呢?原因就在于程序是在断点设置的对应行之前停止的,那么在此时,并没有把“i”的数值赋为零,而只是一个随机的数字。但变量“n”是在第4行赋值的,故在此时已经为零。

 

小技巧

gdb在显示变量值时都会在对应值之前加上“$N”标记,它是当前变量值的引用标记,所以以后若想再次引用此变量就可以直接写作“$N”,而无需写冗长的变量名。

 

(6)单步运行。

单步运行可以使用命令“n”(next)或“s”(step),它们之间的区别在于:若有函数调用的时候,“s”会进入该函数而“n”不会进入该函数。因此,“s”就类似于Uisual等工具中的“step in”,“n”类似与Uisual等工具中的“step over”。它们的使用如下所示:

 

(gdb) n

The sum of 1-m is 1275

7            for (i = 1; i <= 50; i++) 

(gdb) s

sum (m=50) at test.c:16

16              int i, n = 0;

 

可见,使用“n”后,程序显示函数sum()的运行结果并向下执行,而使用“s”后则进入sum()函数之中单步运行。

(7)恢复程序运行

在查看完所需变量及堆栈情况后,就可以使用命令“c”(continue)恢复程序的正常运行了。这时,它会把剩余还未执行的程序执行完,并显示剩余程序中的执行结果。以下是之前使用“n”命令恢复后的执行结果:

 

(gdb) c

Continuing.

The sum of 1-50 is :1275

Program exited with code 031.

 

可以看出,程序在运行完后退出,之后程序处于“停止状态”。

 

小知识

在gdb中,程序的运行状态有“运行”、“暂停”和“停止”3种,其中“暂停”状态为程序遇到了断点或观察点之类的,程序暂时停止运行,而此时函数的地址、函数参数、函数内的局部变量都会被压入“栈”(Stack)中。故在这种状态下可以查看函数的变量值等各种属性。但在函数处于“停止”状态之后,“栈”就会自动撤消,它也就无法查看各种信息了。

 

3.4.2  gdb基本命令

gdb的命令可以通过查看help进行查找,由于gdb的命令很多,因此gdb的help将其分成了很多种类(class),用户可以通过进一步查看相关class找到相应命令,如下所示:

 

(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

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) help data

Examining data.

 

List of commands:

 

call -- Call a function in the program

delete display -- Cancel some expressions to be displayed when program stops

delete mem -- Delete memory region

disable display -- Disable some expressions to be displayed when program stops

Type "help" followed by command name for full documentation.

Command name abbreviations are allowed if unambiguous.

 

若用户想要查找call命令,就可键入“help call”。

 

(gdb) help call

Call a function in the program.

The argument is the function name and arguments, in the notation of the

current working language.  The result is printed and saved in the value

history, if it is not void.

 

当然,若用户已知命令名,直接键入“help [command]”也是可以的。

 

gdb中的命令主要分为以下几类:工作环境相关命令、设置断点与恢复命令、源代码查看命令、查看运行数据相关命令及修改运行参数命令。以下就分别对这几类命令进行讲解。

 

1.工作环境相关命令

gdb中不仅可以调试所运行的程序,而且还可以对程序相关的工作环境进行相应的设定,甚至还可以使用shell中的命令进行相关的操作,其功能极其强大。gdb常见工作环境相关命令如表3.11所示。

表3.11 gdb工作环境相关命令

命 令 格 式

含    义

set args运行时的参数

指定运行时参数,如set args 2

show args

查看设置好的运行参数

Path dir

设定程序的运行路径

show paths

查看程序的运行路径

set environment var [=value]

设置环境变量

show environment [var]

查看环境变量

cd dir

进入dir目录,相当于shell中的cd命令

Pwd

显示当前工作目录

shell command

运行shell的command命令

 

2.设置断点与恢复命令

gdb中设置断点与恢复的常见命令如表3.12所示。

表3.12 gdb设置断点与恢复相关命令

命 令 格 式

含    义

Info b

查看所设断点

break [文件名:]行号或函数名 <条件表达式>

设置断点

tbreak [文件名:]行号或函数名 <条件表达式>

设置临时断点,到达后被自动删除

delete [断点号]

删除指定断点,其断点号为“info b”中的第一栏。若缺省断点号则删除所有断点

disable [断点号]

停止指定断点,使用“info b”仍能查看此断点。同delete一样,若缺省断点号则停止所有断点

enable [断点号]

激活指定断点,即激活被disable停止的断点

condition [断点号] <条件表达式>

修改对应断点的条件

ignore [断点号]<num>

在程序执行中,忽略对应断点num次

Step

单步恢复程序运行,且进入函数调用

Next

单步恢复程序运行,但不进入函数调用

Finish

运行程序,直到当前函数完成返回

C

继续执行函数,直到函数结束或遇到新的断点

 

设置断点在gdb的调试中非常重要,下面着重讲解gdb中设置断点的方法。

 

gdb中设置断点有多种方式:其一是按行设置断点;另外还可以设置函数断点和条件断点。下面具体介绍后两种设置断点的方法。

 

① 函数断点。

gdb中按函数设置断点只需把函数名列在命令“b”之后,如下所示:

 

(gdb) b test.c:sum (可以简化为b sum)

Breakpoint 1 at 0x80484ba: file test.c, line 16.

(gdb) info b 

Num Type           Disp Enb Address    What

1   breakpoint     keep y   0x080484ba in sum at test.c:16

 

要注意的是,此时的断点实际是在函数的定义处,也就是在16行处(注意第16行还未执行)。

② 条件断点。

gdb中设置条件断点的格式为:b 行数或函数名 if 表达式。具体实例如下所示:

 

(gdb) b 8 if i==10

Breakpoint 1 at 0x804848c: file test.c, line 8.

(gdb) info b

Num Type           Disp Enb Address    What

1   breakpoint     keep y   0x0804848c in main at test.c:8

        stop only if i == 10

(gdb) r

Starting program: /home/yul/test 

The sum of 1-m is 1275

 

Breakpoint 1, main () at test.c:9

9               n += i;

(gdb) p i

$1 = 10

 

可以看到,该例中在第8行(也就是运行完第7行的for循环)设置了一个“i==0”的条件断点,在程序运行之后可以看出,程序确实在i为10时暂停运行。

 

3.gdb中源码查看相关命令

在gdb中可以查看源码以方便其他操作,它的常见相关命令如表3.13所示。

表3.13 gdb源码查看相关相关命令

命 令 格 式

含    义

list <行号>|<函数名>

查看指定位置代码

file [文件名]

加载指定文件

forward-search 正则表达式

源代码的前向搜索

reverse-search 正则表达式

源代码的后向搜索

dir DIR

将路径DIR添加到源文件搜索的路径的开头

show directories

显示源文件的当前搜索路径

info line

显示加载到gdb内存中的代码

 

4.gdb中查看运行数据相关命令

gdb中查看运行数据是指当程序处于“运行”或“暂停”状态时,可以查看的变量及表达式的信息,其常见命令如表3.14所示。

表3.14 gdb查看运行数据相关命令

命 令 格 式

含    义

print 表达式|变量

查看程序运行时对应表达式和变量的值

x <n/f/u>

查看内存变量内容。其中n为整数表示显示内存的长度,f表示显示的格式,u表示从当前地址往后请求显示的字节数

display 表达式

设定在单步运行或其他情况中,自动显示的对应表达式的内容

backtrace

查看当前栈的情况,即可以查到哪些被调用的函数尚未返回

 

5.gdb中修改运行参数相关命令

gdb还可以修改运行时的参数,并使该变量按照用户当前输入的值继续运行。它的设置方法为:在单步执行的过程中,键入命令“set 变量=设定值”。这样,在此之后,程序就会按照该设定的值运行了。下面,笔者结合上一节的代码将n的初始值设为4,其代码如下所示:

 

(gdb) b 7

Breakpoint 5 at 0x804847a: file test.c, line 7.

(gdb) r      

Starting program: /home/yul/test 

The sum of 1-m is 1275

 

Breakpoint 5, main () at test.c:7

7                 for(i=1; i <= 50; i++)

(gdb) set n=4

(gdb) c

Continuing.

The sum of 1-50 is 1279 

 

Program exited with code 031.

 

可以看到,最后的运行结果确实比之前的值大了4。

 

注意

gdb使用时的注意点:

·  在gcc编译选项中一定要加入“-g”。

·  只有在代码处于“运行”或“暂停”状态时才能查看变量值。

·  设置断点后程序在指定行之前停止。