調試工具之GDB

jopen 9年前發布 | 33K 次閱讀 GDB 程序調試

簡介

   GDB(GNU debugger)是GNU開源組織發布的一個強大的UNIX下的程序調試工具。可以使用它通過命令行的方式調試程序。它使你能在程序運行時觀察程序的內部結構和內存的使用情況。你也可以使用它分析程序崩潰前的發生了什么,從而找出程序崩潰的原因。相對于windows下的圖形界面的VC等調試工具,它提供了更強大的功能。如果想在Windows下使用gdb,需要安裝MinGW或者CygWin,并且需要配置環境變量才可以使用。

   一般來說,gdb完成以下四個方面的工作:

   1、啟動你的程序,修改一些東西,從而影響程序運行的行為。

   2、可以指定斷點。程序執行到斷點處會暫停執行。

   3、當你的程序停掉時,你可以用它來觀察發生了什么事情。

   4、動態的改變你程序的執行環境,嘗試修正bug。

安裝

   在安裝gdb之前,先確定你的linux操作系統是否安裝了gdb。你可以使用如下命令來確定是否安裝了gdb。

#gdb -help

   如果已經安裝了gdb,那么將會顯示它能使用的所有參數。如果沒有安裝,我們可以通過以下幾種方式來安裝。

通過yum命令安裝

   通過yum安裝的命令行如下:

#yum install gdb

通過rpm包方式安裝

   從http://rpmfind.net/linux/rpm2html/search.php?query=gdb上下載合適的rpm安裝包。假如我們下載的安裝名稱為gdb-7.8.1.rpm。然后通過如下命令安裝。

#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、解壓壓縮包。壓縮包解壓結束后,進入解壓后的目錄。

#gzip -d gdb-7.8.1.tar.gz
#tar xfv gdb-7.8.1.tar.gz
#cd gdb-7.8.1

   4、然后一次執行如下命令,以便完成安裝

#./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,代碼如下:

<?php 
echo "hello \n";
for($i = 0; $i < 10; $i++){     
    echo $i."\n";     
    sleep(10); } 
?>

啟動gdb

   啟動gdb可以使用如下幾種方式:

   第一種方式:

   啟動的時候指定要執行的腳本。

#sudo gdb /usr/bin/php
......
Reading symbols from /home/admin/fpm-php5.5.15/bin/php...done.
(gdb) set args ./test.php
(gdb) r
Starting program: /home/admin/fpm-php5.5.15/bin/php ./test.php
......

   啟動的時候指定php程序的路徑。

   Reading symbols from /home/admin/fpm-php5.5.15/bin/php…done. 說明已經加載了php程序的符號表。

   使用set args 命令指定php命令的參數。

   使用r命令開始執行腳本。r即為run的簡寫形式。也可以使用run命令開始執行腳本。

   第二種方式:

   啟動后通過file命令指定要調試的程序。當你使用gdb調試完一個程序,想調試另外一個程序時,就可以不退出gdb便能切換要調試的程序。具體操作步驟如下:

#sudo gdb ~/home/test/exproxy
......
Reading symbols from /home/hailong.xhl/exproxy/test/exproxy...(no debugging symbols found)...done.
(gdb) file /home/admin/fpm-php/bin/php
Reading symbols from /usr/bin/php...done.
(gdb) set args ./test.php
(gdb) r
Starting program: /home/admin/fpm-php5.5.15/bin/php ./test.php
......

   上面的例子中我們先使用gdb加載了程序exproxy進行調試。然后通過file命令加載了php程序,從而切換了要調試的程序。

獲取幫助信息

   gdb的子命令很多,可能有些你也不太熟悉。沒關系,gdb提供了help子命令。通過這個help子命令,我們可以了解指定子命令的一些用法。如:

#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++寫的,那么你可以使用“文件名:行號”的形式設置斷點。示例如下:

#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。這時,我們就可以通過”文件名:方法名”的方式打斷點。示例如下:

#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)

   另外,如果你不知道文件名的話,你也可以只指定方法名。命令示例如下:

......
(gdb)b zif_sleep
......

   設置條件斷點

   如果按上面的方法設置斷點后,每次執行到斷點位置都會暫停。有時候非常討厭。我們只想在指定條件下才暫停。這時候根據條件設置斷點就有了用武之地。設置條件斷點的形式,就是在設置斷點的基本形式后面增加 if條件。示例如下:

......
(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查看斷點的情況。包含都設置了那些斷點,斷點被命中的次數等信息。示例如下:

......
(gdb) info breakpoint
Num     Type           Disp Enb Address            What
9       breakpoint     keep y   0x0000000000624740 in zif_sleep at /home/admin/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/admin/php_src/php-5.5.15/ext/standard/basic_functions.c:4439
......

   刪除斷點

   對于無用的斷點我們可以刪除。刪除的命令格式為 delete breakpoint 斷點編號。info breakpoint命令顯示結果中的num列就是編號。刪除斷點的示例如下:

......
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
9       breakpoint     keep y   0x0000000000624740 in zif_sleep at /home/admin/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/admin/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/admin/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/admin/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/admin/php_src/php-5.5.15/ext/standard/basic_functions.c:4439
(gdb)
......

   上面的例子中我們刪除了編號為9的斷點。

查看代碼

   斷點設置完后,當程序運行到斷點處就會暫停。暫停的時候,我們可以查看斷點附近的代碼。查看代碼的子命令是list,縮寫形式為l。顯示的代碼行數為10行,基本上以斷點處為中心,向上向下各顯示幾行代碼。示例代碼如下:

#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;

   另外,你可以可以通過指定行號或者方法名來查看相關代碼。

   指定行號查看代碼示例:

......
(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");
......

   指定方法名查看代碼示例:

......
(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命令示例:

#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/admin/php_src/php-5.5.15/Zend/zend_API.c:917
917 {

   可見,step命令進入到了被調用函數中zend_parse_parameters。使用step命令也會在這個方法中一行一行的單步執行。

   next命令示例:

#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。繼續執行過程中遇到斷點或者觀察點變化依然會暫停。示例代碼如下:

......
(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/admin/php_src/php-5.5.15/ext/standard/basic_functions.c:4439
4439    {
......

查看變量

   現在你已經會設置斷點,查看斷點附近的代碼,并可以單步執行和繼續執行。接下來你可能會想知道程序運行的一些情況,如查看變量的值。print命令正好滿足了你的需求。使用它打印出變量的值。print命令的簡寫形式為p。示例代碼如下:

......
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命令可以直接修改變量的值。示例代碼如下:

......
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。示例代碼如下:

......
(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/admin/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  執行到當前函數的結束。

   參考文檔

   http://www.cs.umd.edu/~srhuang/teaching/cmsc212/gdb-tutorial-handout.pdf

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!