作者 王亚刚

2.2 GNU gdb调试工具

调试工具是代码分析中至关重要的工具之一。在使用vim+ctags查看代码时,经常会遇到难以理解的部分,此时,可以借助调试工具,对代码的运行过程进行跟踪,通过跟踪运行过程以及关键数据的变化,可以从程序执行的过程中理解源代码的功能。
调试工具有很多种,最常用的是GNU gdb工具。下面通过一个例子,介绍如何使用gdb,这些调试命令几乎就是笔者调试程序的所有命令,简单且实用。关于完整的gdb的使用,请参与GNU gdb文档,或者使用man gdb进行在线查询。
本例主要使用gdb来跟踪GCC的运行过程,因此,需要事先编译GCC源代码(编译时需要使用-g选项),生成可执行的编译程序cc1,下面利用gdb对cc1程序的运行进行跟踪。
首先,可以在程序入口处设置断点(Break Point):

[GCC@localhost paag-gcc]$ gdb host-i686-pc-linux-gnu/gcc/cc1
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 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 "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/GCC/paag-gcc/host-i686-pc-linux-gnu/gcc/cc1...done.
(gdb) b main            ? 设置执行断点
Breakpoint 1 at 0x80c253d: file ../.././gcc/main.c, line 35.
(gdb) info break        ? 查看断点设置情况
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x080c253d in main at ../.././gcc/main.c:35
(gdb)
执行程序,gdb执行到断点处会自动停止,返回交互界面。
(gdb) run            ? 运行程序
Starting program: /home/GCC/paag-gcc/host-i686-pc-linux-gnu/gcc/cc1 
Breakpoint 1, main (argc=1, argv=0xbffff434) at ../.././gcc/main.c:35
35      return toplev_main (argc, (const char **) argv);
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6.i686 gmp-4.3.1-7.el6_2.2.i686 mpfr-2.4.1-6.el6.i686

单步跟踪程序的执行,step命令和next命令均可以进行单步跟踪,二者的主要区别在于step在单步执行函数代码时,会进入被调用的函数,而next则会将函数调用看作“单步”,一次执行完一个函数的调用。对于其他代码,step和next命令的功能基本相同。
此时可以看到,使用run命令执行程序后,程序执行到前面定义的断点处暂停执行。
如果此时需要查看toplev_main函数的执行细节,应该使用step命令进入该函数。
(gdb) step ? 单步跟踪toplev_main (argc=1, argv=0xbffff434) at ../.././gcc/toplev.c:2212对于程序执行过程中,需要查看某些变量的值,可以使用print命令。

(gdb) print argc        ? 打印变量值
$1 = 1
(gdb) print argv[0]    ? 打印变量值
$2 = 0xbffff5b5 "/home/GCC/paag-gcc/host-i686-pc-linux-gnu/gcc/cc1"

查看变量的值可以使用print命令,如果在每一条指令后都需要查看某些变量的值,使用print显得有些烦琐,可以使用display命令,设置显示的变量。

(gdb) disp argc        ? 设置变量查看
1: argc = 1
(gdb) next            ? 单步执行
2215      general_init (argv[0]);
1: argc = 1
(gdb) next
2219      decode_options (argc, argv);
1: argc = 1
(gdb) next
2221      init_local_tick ();
1: argc = 1

可以看出,每执行一步,变量argc的值都会输出显示。
当需要连续执行程序时,可以使用continue命令,程序则恢复运行,直到下一个断点处再次暂停运行。
通常,在执行到某个断点处时,当需要了解当前函数的调用情况时,可以使用bt命令(backtrace)。
(gdb) bt ? 显示函数调用的堆栈

0 toplev_main (argc=1, argv=0xbffff434) at ../.././gcc/toplev.c:2212

1 main (argc=1, argv=0xbffff434) at ../.././gcc/main.c:35

可以看出当前执行的函数为toplev_main函数,其调用者为函数main,并且这两个函数所在的文件及位置信息也在bt的输出中给出。bt命令的输出可以很详细地展示当前函数的调用关系,对于理解程序的执行流程非常有帮助。
另外,gdb在输入命令时,如果输入命令的开始部分可以完全确定一个命令时,则可以简写该命令,例如,一般用户经常将命令run简写为r,step命令简写为s,next命令简写为n,continue命令简写为c等,如果用户没有输入命令,直接按回车键,则gdb默认会执行上一次输入的命令。例如在单步跟踪时,如果输入了命令next,后续单步跟踪则可以只需要按[Enter]键就可以了。这些规律,读者可以在使用过程中不断总结,提高调试效率。
另外,还有其他众多的调试工具,这些工具大都对gdb程序进行了封装,例如cgdb,可以提供一些方便地实现源代码查看等其他很有特色的功能,其官网地址为http://cgdb.sourceforge.net/。可以通过以下代码进行cgdb程序的安装。

[root@localhost ~]# wget -c http://prdownloads.sourceforge.net/cgdb/cgdb-0.6.6.tar.gz?download
[root@localhost cgdb-0.6.6]# tar xzvf cgdb-0.6.6.tar.gz
[root@localhost cgdb-0.6.6]# cd cgdb
[root@localhost cgdb-0.6.6]# yum install readline*
[root@localhost cgdb-0.6.6]# ./configure
[root@localhost cgdb-0.6.6]# make; make install
例如,可以使用cgdb对cc1进行调试。
[GCC@localhost gcc-4.4.0]$ cgdb ~/paag-gcc/host-i686-pc-linux-gnu/gcc/cc1

界面如图2-3所示,可以看到cgdb中能够很方便地查看源代码。关于cgdb的使用请查阅相关文档,不再赘述。