简介
GDB(GNU debugger)是GNU开源组织发布的一个强大的UNIX下的程序调试工具。可以使用它通过命令行的方式调试程序。它使你能在程序运行时观察程序的内部结构和内存的使用情况。你也可以使用它分析程序崩溃前的发生了什么,从而找出程序崩溃的原因。相对于windows下的图形界面的VC等调试工具,它提供了更强大的功能。如果想在Windows下使用gdb,需要安装MinGW或者CygWin,并且需要配置环境变量才可以使用。
一般来说,gdb完成以下四个方面的工作:
1、启动你的程序,修改一些东西,从而影响程序运行的行为。
2、可以指定断点。程序执行到断点处会暂停执行。
3、当你的程序停掉时,你可以用它来观察发生了什么事情。
4、动态的改变你程序的执行环境,尝试修正bug。
安装
在安装gdb之前,先确定你的linux操作系统是否安装了gdb。你可以使用如下命令来确定是否安装了gdb。
1
|
#gdb -help
|
如果已经安装了gdb,那么将会显示它能使用的所有参数。如果没有安装,我们可以通过以下几种方式来安装。
通过yum命令安装
通过yum安装的命令行如下:
1
|
#yum install gdb
|
通过rpm包方式安装
从http://rpmfind.net/linux/rpm2html/search.php?query=gdb上下载合适的rpm安装包。假如我们下载的安装名称为gdb-7.8.1.rpm。然后通过如下命令安装。
1
|
#rpm -ivh ./gdb-7.8.1.rpm
|
通过源码方式安装
安装gdb是很容易的。只要按照以下步骤一步步操作即可。
1、但是安装之前,必须保证它所依赖的环境没问题。下面是它依赖的依赖环境。
* c语言编译器。推荐使用gcc。
* 确保有不少于150M的磁盘空间。
2、然后打开这个网址 ftp://sourceware.org/pub/gdb/releases/,下载需要安装的gdb源码包。我们下载的源码包是gdb-7.8.1.tar.gz。
3、解压压缩包。压缩包解压结束后,进入解压后的目录。
1
2
3
|
#gzip -d gdb-7.8.1.tar.gz
#tar xfv gdb-7.8.1.tar.gz
#cd gdb-7.8.1
|
4、然后一次执行如下命令,以便完成安装
1
2
3
|
#./configure
#make
#make install
|
基本使用
命令行格式
gdb [-help] [-nx] [-q] [-batch] [-cd=dir] [-f] [-b bps] [-tty=dev] [-s symfile] [-e prog] [-se prog] [-c core] [-x
cmds] [-d dir] [prog[core|procID]]
常用功能介绍
网上的一些教程基本上都是介绍使用gdb调试c或者c++语言编写的程序的。我们这节主要说明如何使用gdb调试php程序。我们的php脚本如下:
文件名为test.php,代码如下:
1
2
3
4
5
6
|
<?php
echo
"hello \n"
;
for
(
$i
= 0;
$i
< 10;
$i
++){
echo
$i
.
"\n"
;
sleep(10); }
?>
|
启动gdb
启动gdb可以使用如下几种方式:
第一种方式:
启动的时候指定要执行的脚本。
1
2
3
4
5
6
7
|
#sudo gdb /usr/bin/php
......
Reading symbols from
/home/fpm-php5
.5.15
/bin/php
...
done
.
(
gdb
)
set
args .
/test
.php
(
gdb
) r
Starting program:
/home/fpm-php5
.5.15
/bin/php
.
/test
.php
......
|
启动的时候指定php程序的路径。
Reading symbols from /home/fpm-php5.5.15/bin/php...done. 说明已经加载了php程序的符号表。
使用set args 命令指定php命令的参数。
使用r命令开始执行脚本。r即为run的简写形式。也可以使用run命令开始执行脚本。
第二种方式:
启动后通过file命令指定要调试的程序。当你使用gdb调试完一个程序,想调试另外一个程序时,就可以不退出gdb便能切换要调试的程序。具体操作步骤如下:
1
2
3
4
5
6
7
8
9
|
#sudo gdb ~/home/test/exproxy
......
Reading symbols from
/home/hailong
.xhl
/exproxy/test/exproxy
...(no debugging symbols found)...
done
.
(
gdb
)
file
/home/fpm-php/bin/php
Reading symbols from
/usr/bin/php
...
done
.
(
gdb
)
set
args .
/test
.php
(
gdb
) r
Starting program:
/home/fpm-php5
.5.15
/bin/php
.
/test
.php
......
|
上面的例子中我们先使用gdb加载了程序exproxy进行调试。然后通过file命令加载了php程序,从而切换了要调试的程序。
获取帮助信息
gdb的子命令很多,可能有些你也不太熟悉。没关系,gdb提供了help子命令。通过这个help子命令,我们可以了解指定子命令的一些用法。如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#gdb
......
(
gdb
) help
set
Evaluate expression EXP and assign result to variable VAR, using assignment
syntax appropriate
for
the current language (VAR = EXP or VAR := EXP
for
example). VAR may be a debugger
"convenience"
variable (names starting
with $), a register (a few standard names starting with $), or an actual
variable
in
the program being debugged. EXP is any valid expression.
Use
"set variable"
for
variables with names identical to
set
subcommands.
With a subcommand, this
command
modifies parts of the
gdb
environment.
You can see these environment settings with the
"show"
command
.
List of
set
subcommands:
set
annotate -- Set annotation_level
set
architecture -- Set architecture of target
set
args -- Set argument list to give program being debugged when it is started
.......
(
gdb
) help
set
args
Set argument list to give program being debugged when it is started.
Follow this
command
with any number of args, to be passed to the program.
......
|
可见,通过help命令,我们可以了解命令的功能和使用方法。如果这个子命令还有一些子命令,那么它的所有子命令也会列出来。如上,set命令的set args等子命令也都列出来了。你还可以使用help命令来了解set args的更详尽的信息。
设置断点
为什么要设置断点呢?设置断点后,我们就可以指定程序执行到指定的点后停止。以便我们更详细的跟踪断点附近程序的执行情况。
设置断点的命令是break,缩写形式为b。
设置断点有很多方式。下面我们举例说明下常用的几种方式。
根据文件名和行号指定断点
如果你的程序是用c或者c++写的,那么你可以使用“文件名:行号”的形式设置断点。示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
#gdb /usr/bin/php
(
gdb
)
set
args .
/test
.php
(
gdb
) b basic_functions.c:4439
Breakpoint 6 at 0x624740:
file
/home/php-5
.5.15
/ext/standard/basic_functions
.c, line 4439.
(
gdb
) r
Starting program:
/home/fpm-php5
.5.15
/bin/php
.
/test
.php
hello
0
Breakpoint 1, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
(
gdb
)
|
示例中的(gdb) b basic_functions.c:4439 是设置了断点。断点的位置是basic_functions.c文件的4439行。使用r命令执行脚本时,当运行到4439行时就会暂停。暂停的时候会把断点附近的代码给显示出来。可见,断点处是zif_sleep方法。这个zif_sleep方法就是我们php代码中sleep方法在php内核中的实现。根据规范,php内置提供的方法名前面加上zif_,就是这个方法在php内核或者扩展中实现时定义的方法名。
根据文件名和方法名指定断点
有些时候,手头没有源代码,不知道要打断点的具体位置。但是我们根据php的命名规范知道方法名。如,我们知道php程序中调用的sleep方法,在php内核中实现时的方法名是zif_sleep。这时,我们就可以通过"文件名:方法名"的方式打断点。示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
#gdb /usr/bin/php
(
gdb
)
set
args .
/test
.php
(
gdb
) b basic_functions.c:zif_sleep
Breakpoint 6 at 0x624740:
file
/home/php-5
.5.15
/ext/standard/basic_functions
.c, line 4439.
(
gdb
) r
Starting program:
/home/fpm-php5
.5.15
/bin/php
.
/test
.php
hello
0
Breakpoint 1, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
(
gdb
)
|
另外,如果你不知道文件名的话,你也可以只指定方法名。命令示例如下:
1
2
3
|
......
(
gdb
)b zif_sleep
......
|
设置条件断点
如果按上面的方法设置断点后,每次执行到断点位置都会暂停。有时候非常讨厌。我们只想在指定条件下才暂停。这时候根据条件设置断点就有了用武之地。设置条件断点的形式,就是在设置断点的基本形式后面增加 if条件。示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
......
(
gdb
) b zif_sleep
if
num > 0
Breakpoint 9 at 0x624740:
file
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c, line 4439.
(
gdb
) r
Starting program:
/home/fpm-php5
.5.15
/bin/php
.
/test
.php
hello
0
Breakpoint 9, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
(
gdb
)
......
|
查看断点
可以使用info breakpoint查看断点的情况。包含都设置了那些断点,断点被命中的次数等信息。示例如下:
1
2
3
4
5
6
7
8
|
......
(
gdb
) info breakpoint
Num Type Disp Enb Address What
9 breakpoint keep y 0x0000000000624740
in
zif_sleep at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
stop only
if
num > 0
breakpoint already hit 1
time
10 breakpoint keep y 0x0000000000624740
in
zif_sleep at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
......
|
删除断点
对于无用的断点我们可以删除。删除的命令格式为 delete breakpoint 断点编号。info breakpoint命令显示结果中的num列就是编号。删除断点的示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
......
(
gdb
) info breakpoints
Num Type Disp Enb Address What
9 breakpoint keep y 0x0000000000624740
in
zif_sleep at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
stop only
if
num > 0
breakpoint already hit 1
time
10 breakpoint keep y 0x0000000000624740
in
zif_sleep at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
(
gdb
) info breakpoint
Num Type Disp Enb Address What
9 breakpoint keep y 0x0000000000624740
in
zif_sleep at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
stop only
if
num > 0
breakpoint already hit 1
time
10 breakpoint keep y 0x0000000000624740
in
zif_sleep at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
(
gdb
) delete 9
(
gdb
) info breakpoint
Num Type Disp Enb Address What
10 breakpoint keep y 0x0000000000624740
in
zif_sleep at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
(
gdb
)
......
|
上面的例子中我们删除了编号为9的断点。
查看代码
断点设置完后,当程序运行到断点处就会暂停。暂停的时候,我们可以查看断点附近的代码。查看代码的子命令是list,缩写形式为l。显示的代码行数为10行,基本上以断点处为中心,向上向下各显示几行代码。示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#gdb /usr/bin/php
(
gdb
)
set
args .
/test
.php
(
gdb
) b basic_functions.c:zif_sleep
Breakpoint 6 at 0x624740:
file
/home/php-5
.5.15
/ext/standard/basic_functions
.c, line 4439.
(
gdb
) r
Starting program:
/home/fpm-php5
.5.15
/bin/php
.
/test
.php
hello
0
Breakpoint 1, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
(
gdb
)l
4434 /* }}} */
4435
4436 /* {{{ proto void
sleep
(int seconds)
4437 Delay
for
a given number of seconds */
4438 PHP_FUNCTION(
sleep
)
4439 {
4440 long num;
4441
4442
if
(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"l"
, &num) == FAILURE) {
4443 RETURN_FALSE;
|
另外,你可以可以通过指定行号或者方法名来查看相关代码。
指定行号查看代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
......
(
gdb
) list 4442
4437 Delay
for
a given number of seconds */
4438 PHP_FUNCTION(
sleep
)
4439 {
4440 long num;
4441
4442
if
(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"l"
, &num) == FAILURE) {
4443 RETURN_FALSE;
4444 }
4445
if
(num < 0) {
4446 php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Number of seconds must be greater than or equal to 0"
);
......
|
指定方法名查看代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
......
(
gdb
) list zif_sleep
4434 /* }}} */
4435
4436 /* {{{ proto void
sleep
(int seconds)
4437 Delay
for
a given number of seconds */
4438 PHP_FUNCTION(
sleep
)
4439 {
4440 long num;
4441
4442
if
(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"l"
, &num) == FAILURE) {
4443 RETURN_FALSE;
......
|
单步执行
断点附近的代码你了解后,这时候你就可以使用单步执行一条一条语句的去执行。可以随时查看执行后的结果。单步执行有两个命令,分别是step和next。这两个命令的区别在于:
step 一条语句一条语句的执行。它有一个别名,s。
next 和step类似。只是当遇到被调用的方法时,不会进入到被调用方法中一条一条语句执行。它有一个别名n。
可能你对这两个命令还有些迷惑。下面我们用两个例子来给你演示下。
step命令示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#gdb /usr/bin/php
(
gdb
)
set
args .
/test
.php
(
gdb
) b basic_functions.c:zif_sleep
Breakpoint 6 at 0x624740:
file
/home/php-5
.5.15
/ext/standard/basic_functions
.c, line 4439.
(
gdb
) r
Starting program:
/home/fpm-php5
.5.15
/bin/php
.
/test
.php
hello
0
Breakpoint 1, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
.......
(
gdb
) s
4442
if
(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"l"
, &num) == FAILURE) {
(
gdb
) s
zend_parse_parameters (num_args=1, type_spec=0x81fc41
"l"
) at
/home/php_src/php-5
.5.15
/Zend/zend_API
.c:917
917 {
|
可见,step命令进入到了被调用函数中zend_parse_parameters。使用step命令也会在这个方法中一行一行的单步执行。
next命令示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#gdb /usr/bin/php
(
gdb
)
set
args .
/test
.php
(
gdb
) b basic_functions.c:zif_sleep
Breakpoint 6 at 0x624740:
file
/home/php-5
.5.15
/ext/standard/basic_functions
.c, line 4439.
(
gdb
) r
Starting program:
/home/fpm-php5
.5.15
/bin/php
.
/test
.php
hello
0
Breakpoint 1, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
.......
(
gdb
) n
4442
if
(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"l"
, &num) == FAILURE) {
(
gdb
) n
4445
if
(num < 0) {
|
可见,使用next命令只会在本方法中单步执行。
继续执行
run命令是从头开始执行,如果我们只是想继续执行就可以使用continue命令。它的作用就是从暂停处继续执行。命令的简写形式为c。继续执行过程中遇到断点或者观察点变化依然会暂停。示例代码如下:
1
2
3
4
5
6
7
8
9
|
......
(
gdb
) c
Continuing.
6
Breakpoint 1, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
......
|
查看变量
现在你已经会设置断点,查看断点附近的代码,并可以单步执行和继续执行。接下来你可能会想知道程序运行的一些情况,如查看变量的值。print命令正好满足了你的需求。使用它打印出变量的值。print命令的简写形式为p。示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
......
Breakpoint 1, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
......
(
gdb
) n
4442
if
(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"l"
, &num) == FAILURE) {
(
gdb
) n
4445
if
(num < 0) {
(
gdb
) print num
$1 = 10
(
gdb
)
......
|
打印出的num的值为10,正好是我们在php代码中调用sleep方法传的值。另外可以使用“print/x my var” 的形式可以以十六进制形式查看变量值。
设置变量
使用print命令查看了变量的值,如果感觉这个值不符合预期,想修改下这个值,再看下执行效果。这种情况下,我们该怎么办呢?通常情况下,我们会修改代码,再重新执行代码。使用gdb的set命令,一切将变得更简单。set命令可以直接修改变量的值。示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
|
......
Breakpoint 1, zif_sleep (ht=1, return_value=0x7ffff425a398, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0)
at
/home/php_src/php-5
.5.15
/ext/standard/basic_functions
.c:4439
4439 {
......
(
gdb
) print num
$4 = 10
(
gdb
)
set
num = 2
(
gdb
) print num
$5 = 2
......
|
上面的代码中我们是把sleep函数传入的10改为了2。即,sleep 2秒。注意,我们示例中修改的变量num是局部变量,只能对本次函数调用有效。下次再调用zif_sleep方法时,又会被设置为10。
设置观察点
设置观察点的作用就是,当被观察的变量发生变化后,程序就会暂停执行,并把变量的原值和新值都会显示出来。设置观察点的命令是watch。示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
......
(
gdb
)
watch
num
Hardware watchpoint 3: num
(
gdb
) c
Continuing.
Hardware watchpoint 3: num
Old value = 1
New value = 10
0x0000000000713333
in
zend_parse_arg_impl (arg_num=1, arg=0x7ffff42261a8, va=0x7fffffffaf70, spec=0x7fffffffaf30, quiet=0)
at
/home/php_src/php-5
.5.15
/Zend/zend_API
.c:372
372 *p = Z_LVAL_PP(arg);
(
gdb
)
......
|
上例中num值从1变成了10时,程序暂停了。需要注意的是,你的程序中可能有多个同名的变量。那么使用watch命令会观察那个变量呢?这个要依赖于变量的作用域。即,在使用watch设置观察点时,可以直接访问的变量就是被观察的变量。
其他有用的命令
backtrace 简写形式为bt。查看程序执行的堆栈信息。
finish 执行到当前函数的结束。