天天看點

GDB調試

用 gdb 調試程式

gdb 概述

————

gdb 是 gnu 開源組織釋出的一個強大的 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 下)

    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    <---------- 在斷點處停住。

(gdb) n          <--------------------- 單條語句執行, next 指令簡寫。

(gdb) n

20                       result += i;

(gdb) c          <--------------------- 繼續運作程式, continue 指令簡寫。

continuing.

result[1-100] = 5050       <---------- 程式輸出。

breakpoint 2, func (n=250) at tst.c:5

6                for(i=1; i<=n; i++)

(gdb) p i        <--------------------- 列印變量 i 的值, print 指令簡寫。

$1 = 134513808

(gdb) p sum

$2 = 1

(gdb) p i

$3 = 2

$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     <--------------------- 繼續運作。

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) 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 中使用 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 就行了。

七、斷點菜單

在 c++ 中,可能會重複出現同一個名字的函數若幹次(函數重載),在這種情況下, break <function> 不能告訴gdb 要停在哪個函數的入口。當然,你可以使用 break <function(type)> 也就是把函數的參數類型告訴 gdb ,以指定一個函數。否則的話, gdb 會給你列出一個斷點菜單供你選擇你所需要的斷點。你隻要輸入你菜單清單中的編号就可以了。如:

    (gdb) b string::after

    [0] cancel

    [1] all

    [2] file:string.cc; line number:867

    [3] file:string.cc; line number:860

    [4] file:string.cc; line number:875

    [5] file:string.cc; line number:853

    [6] file:string.cc; line number:846

    [7] file:string.cc; line number:735

    > 2 4 6

    breakpoint 1 at 0xb26c: file string.cc, line 867.

    breakpoint 2 at 0xb344: file string.cc, line 875.

    breakpoint 3 at 0xafcc: file string.cc, line 846.

    multiple breakpoints were set.

    use the "delete" command to delete unwanted

     breakpoints.

可見, gdb 列出了所有 after 的重載函數,你可以選一下清單編号就行了。 0 表示放棄設定斷點, 1 表示所有函數都設定斷點。

八、恢複程式運作和單步調試

當程式被停住了,你可以用 continue 指令恢複程式的運作直到程式結束,或下一個斷點到來。也可以使用 step 或next 指令單步跟蹤程式。

    continue [ignore-count]

    c [ignore-count]

    fg [ignore-count]

        恢複程式運作,直到程式結束,或是下一個斷點到來。 ignore-count 表示忽略其後的斷點次數。continue , c , fg 三個指令都是一樣的意思。

    step <count>

        單步跟蹤,如果有函數調用,他會進入該函數。進入函數的前提是,此函數被編譯有 debug 資訊。很像 vc等工具中的 step in 。後面可以加 count 也可以不加,不加表示一條條地執行,加表示執行後面的 count 條指令,然後再停住。

    next <count>

        同樣單步跟蹤,如果有函數調用,他不會進入該函數。很像 vc 等工具中的 step over 。後面可以加count 也可以不加,不加表示一條條地執行,加表示執行後面的 count 條指令,然後再停住。

    set step-mode

    set step-mode on

        打開 step-mode 模式,于是,在進行單步跟蹤時,程式不會因為沒有 debug 資訊而不停住。這個參數有很利于檢視機器碼。

    set step-mod off

        關閉 step-mode 模式。

    finish

        運作程式,直到目前函數完成傳回。并列印函數傳回時的堆棧位址和傳回值及參數值等資訊。

    until 或 u

        當你厭倦了在一個循環體内單步跟蹤時,這個指令可以運作程式直到退出循環體。

    stepi 或 si

    nexti 或 ni

        單步跟蹤一條機器指令!一條程式代碼有可能由數條機器指令完成, stepi 和 nexti 可以單步執行機器指令。與之一樣有相同功能的指令是 “ display/i $pc ” ,當運作完這個指令後,單步跟蹤會在打出程式代碼的同時打出機器指令(也就是彙編代碼)

九、信号( signals )

信号是一種軟中斷,是一種處理異步事件的方法。一般來說,作業系統都支援許多信号。尤其是 unix ,比較重要應用程式一般都會處理信号。 unix 定義了許多信号,比如 sigint 表示中斷字元信号,也就是 ctrl+c 的信号,sigbus 表示硬體故障的信号; sigchld 表示子程序狀态改變信号; sigkill 表示終止程式運作的信号,等等。信号量程式設計是 unix 下非常重要的一種技術。

gdb 有能力在你調試程式的時候處理任何一種信号,你可以告訴 gdb 需要處理哪一種信号。你可以要求 gdb 收到你所指定的信号時,馬上停住正在運作的程式,以供你進行調試。你可以用 gdb 的 handle 指令來完成這一功能。

    handle <signal> <keywords...>

        在 gdb 中定義一個信号處理。信号 <signal> 可以以 sig 開頭或不以 sig 開頭,可以用定義一個要處理信号的範圍(如: sigio-sigkill ,表示處理從 sigio 信号到 sigkill 的信号,其中包括 sigio ,sigiot , sigkill 三個信号),也可以使用關鍵字 all 來标明要處理所有的信号。一旦被調試的程式接收到信号,運作程式馬上會被 gdb 停住,以供調試。其 <keywords> 可以是以下幾種關鍵字的一個或多個。

        nostop

            當被調試的程式收到信号時, gdb 不會停住程式的運作,但會打出消息告訴你收到這種信号。

        stop

            當被調試的程式收到信号時, gdb 會停住你的程式。

        print

            當被調試的程式收到信号時, gdb 會顯示出一條資訊。

        noprint

            當被調試的程式收到信号時, gdb 不會告訴你收到信号的資訊。

        pass

        noignore

            當被調試的程式收到信号時, gdb 不處理信号。這表示, gdb 會把這個信号交給被調試程式會處理。

        nopass

        ignore

            當被調試的程式收到信号時, gdb 不會讓被調試程式來處理這個信号。

    info signals

    info handle

        檢視有哪些信号在被 gdb 檢測中。

十、線程( thread stops )

如果你程式是多線程的話,你可以定義你的斷點是否在所有的線程上,或是在某個特定的線程。 gdb 很容易幫你完成這一工作。

    break <linespec> thread <threadno>

    break <linespec> thread <threadno> if ...

        linespec 指定了斷點設定在的源程式的行号。 threadno 指定了線程的 id ,注意,這個 id 是 gdb配置設定的,你可以通過 “ info threads ” 指令來檢視正在運作程式中的線程資訊。如果你不指定 thread <threadno> 則表示你的斷點設在所有線程上面。你還可以為某線程指定斷點條件。如:

        (gdb) break frik.c:13 thread 28 if bartab > lim

    當你的程式被 gdb 停住時,所有的運作線程都會被停住。這友善你你檢視運作程式的總體情況。而在你恢複程式運作時,所有的線程也會被恢複運作。那怕是主程序在被單步調試時。

檢視棧資訊

—————

當程式被停住了,你需要做的第一件事就是檢視程式是在哪裡停住的。當你的程式調用了一個函數,函數的位址,函數參數,函數内的局部變量都會被壓入 “ 棧 ” ( stack )中。你可以用 gdb 指令來檢視目前的棧中的資訊。

下面是一些檢視函數調用棧資訊的 gdb 指令:

    backtrace

    bt

        列印目前的函數調用棧的所有資訊。如:

        (gdb) bt

        #0  func (n=250) at tst.c:6

        #1  0x08048524 in main (argc=1, argv=0xbffff674) at tst.c:30

        #2  0x400409ed in __libc_start_main () from /lib/libc.so.6

        從上可以看出函數的調用棧資訊: __libc_start_main --> main() --> func()

    backtrace <n>

    bt <n>

        n 是一個正整數,表示隻列印棧頂上 n 層的棧資訊。

    backtrace <-n>

    bt <-n>

        -n 表一個負整數,表示隻列印棧底下 n 層的棧資訊。

如果你要檢視某一層的資訊,你需要在切換目前的棧,一般來說,程式停止時,最頂層的棧就是目前棧,如果你要檢視棧下面層的詳細資訊,首先要做的是切換目前棧。

    frame <n>

    f <n>

        n 是一個從 0 開始的整數,是棧中的層編号。比如: frame 0 ,表示棧頂, frame 1 ,表示棧的第二層。

    up <n>

        表示向棧的上面移動 n 層,可以不打 n ,表示向上移動一層。

    down <n>

        表示向棧的下面移動 n 層,可以不打 n ,表示向下移動一層。

    上面的指令,都會列印出移動到的棧層的資訊。如果你不想讓其打出資訊。你可以使用這三個指令:

            select-frame <n> 對應于 frame 指令。

            up-silently <n> 對應于 up 指令。

            down-silently <n> 對應于 down 指令。

檢視目前棧層的資訊,你可以用以下 gdb 指令:

    frame 或 f

        會列印出這些資訊:棧的層編号,目前的函數名,函數參數值,函數所在檔案及行号,函數執行到的語句。

    info frame

    info f

        這個指令會列印出更為詳細的目前棧層的資訊,隻不過,大多數都是運作時的内内位址。比如:函數位址,調用函數的位址,被調用函數的位址,目前的函數是由什麼樣的程式語言寫成的、函數參數位址及值、局部變量的位址等等。如:

            (gdb) info f

            stack level 0, frame at 0xbffff5d4:

             eip = 0x804845d in func (tst.c:6); saved eip 0x8048524

             called by frame at 0xbffff60c

             source language c.

             arglist at 0xbffff5d4, args: n=250

             locals at 0xbffff5d4, previous frame's sp is 0x0

             saved registers:

              ebp at 0xbffff5d4, eip at 0xbffff5d8

     info args

        列印出目前函數的參數名及其值。

     info locals

        列印出目前函數中所有局部變量及其值。

     info catch

        列印出目前的函數中的異常處理資訊。

檢視源程式

一、顯示源代碼

    gdb 可以列印出所調試程式的源代碼,當然,在程式編譯時一定要加上 -g 的參數,把源程式資訊編譯到執行檔案中。不然就看不到源程式了。當程式停下來以後, gdb 會報告程式停在了那個檔案的第幾行上。你可以用 list 指令來列印程式的源代碼。還是來看一看檢視源代碼的 gdb 指令吧。

    list <linenum>

        顯示程式第 linenum 行的周圍的源程式。

    list <function>

        顯示函數名為 function 的函數的源程式。

    list

        顯示目前行後面的源程式。

    list -

        顯示目前行前面的源程式。

一般是列印目前行的上 5 行和下 5 行,如果顯示函數是是上 2 行下 8 行,預設是 10 行,當然,你也可以定制顯示的範圍,使用下面指令可以設定一次顯示源程式的行數。

    set listsize <count>

        設定一次顯示源代碼的行數。

    show listsize

        檢視目前 listsize 的設定。

list 指令還有下面的用法:

    list <first>, <last>

        顯示從 first 行到 last 行之間的源代碼。

    list , <last>

        顯示從目前行到 last 行之間的源代碼。

    list +

        往後顯示源代碼。

一般來說在 list 後面可以跟以下這們的參數:

    <linenum>   行号。

    <+offset>   目前行号的正偏移量。

    <-offset>   目前行号的負偏移量。

    <filename:linenum>  哪個檔案的哪一行。

    <function>  函數名。

    <filename:function> 哪個檔案中的哪個函數。

    <*address>  程式運作時的語句在記憶體中的位址。

二、搜尋源代碼

不僅如此, gdb 還提供了源代碼搜尋的指令:

    forward-search <regexp>

    search <regexp>

        向前面搜尋。

    reverse-search <regexp>

        全部搜尋。

其中, <regexp> 就是正規表達式,也主一個字元串的比對模式,關于正規表達式,我就不在這裡講了,還請各位檢視相關資料。

三、指定源檔案的路徑

某些時候,用 -g 編譯過後的執行程式中隻是包括了源檔案的名字,沒有路徑名。 gdb 提供了可以讓你指定源檔案的路徑的指令,以便 gdb 進行搜尋。

    directory <dirname ... >

    dir <dirname ... >

        加一個源檔案路徑到目前路徑的前面。如果你要指定多個路徑, unix 下你可以使用 “ : ” , windows下你可以使用 “ ; ” 。

    directory

        清除所有的自定義的源檔案搜尋路徑資訊。

    show directories

        顯示定義了的源檔案搜尋路徑。

四、源代碼的記憶體

你可以使用 info line 指令來檢視源代碼在記憶體中的位址。 info line 後面可以跟 “ 行号 ” , “ 函數名 ”, “ 檔案名 : 行号 ” , “ 檔案名 : 函數名 ” ,這個指令會列印出所指定的源碼在運作時的記憶體位址,如:

        (gdb) info line tst.c:func

        line 5 of "tst.c" starts at address 0x8048456 <func+6> and ends at 0x804845d <func+13>.

還有一個指令( disassemble )你可以檢視源程式的目前執行時的機器碼,這個指令會把目前記憶體中的指令 dump出來。如下面的示例表示檢視函數 func 的彙編代碼。

        (gdb) disassemble func

        dump of assembler code for function func:

        0x8048450 <func>:       push   %ebp

        0x8048451 <func+1>:     mov    %esp,%ebp

        0x8048453 <func+3>:     sub    $0x18,%esp

        0x8048456 <func+6>:     movl   $0x0,0xfffffffc(%ebp)

        0x804845d <func+13>:    movl   $0x1,0xfffffff8(%ebp)

        0x8048464 <func+20>:    mov    0xfffffff8(%ebp),%eax

        0x8048467 <func+23>:    cmp    0x8(%ebp),%eax

        0x804846a <func+26>:    jle    0x8048470 <func+32>

        0x804846c <func+28>:    jmp    0x8048480 <func+48>

        0x804846e <func+30>:    mov    %esi,%esi

        0x8048470 <func+32>:    mov    0xfffffff8(%ebp),%eax

        0x8048473 <func+35>:    add    %eax,0xfffffffc(%ebp)

        0x8048476 <func+38>:    incl   0xfffffff8(%ebp)

        0x8048479 <func+41>:    jmp    0x8048464 <func+20>

        0x804847b <func+43>:    nop

        0x804847c <func+44>:    lea    0x0(%esi,1),%esi

        0x8048480 <func+48>:    mov    0xfffffffc(%ebp),%edx

        0x8048483 <func+51>:    mov    %edx,%eax

        0x8048485 <func+53>:    jmp    0x8048487 <func+55>

        0x8048487 <func+55>:    mov    %ebp,%esp

        0x8048489 <func+57>:    pop    %ebp

        0x804848a <func+58>:    ret

        end of assembler dump.

檢視運作時資料

    在你調試程式時,當程式被停住時,你可以使用 print 指令(簡寫指令為 p ),或是同義指令 inspect 來檢視目前程式的運作資料。 print 指令的格式是:

    print <expr>

    print /<f> <expr>

        <expr> 是表達式,是你所調試的程式的語言的表達式( gdb 可以調試多種程式設計語言), <f> 是輸出的格式,比如,如果要把表達式按 16 進制的格式輸出,那麼就是 /x 。

一、表達式

    print 和許多 gdb 的指令一樣,可以接受一個表達式, gdb 會根據目前的程式運作的資料來計算這個表達式,既然是表達式,那麼就可以是目前程式運作中的 const 常量、變量、函數等内容。可惜的是 gdb 不能使用你在程式中所定義的宏。

    表達式的文法應該是目前所調試的語言的文法,由于 c/c++ 是一種大衆型的語言,是以,本文中的例子都是關于c/c++ 的。(而關于用 gdb 調試其它語言的章節,我将在後面介紹)

    在表達式中,有幾種 gdb 所支援的操作符,它們可以用在任何一種語言中。

    @

        是一個和數組有關的操作符,在後面會有更詳細的說明。

    ::

        指定一個在檔案或是一個函數中的變量。

    {<type>} <addr>

        表示一個指向記憶體位址 <addr> 的類型為 type 的一個對象。

二、程式變量

    在 gdb 中,你可以随時檢視以下三種變量的值:

        1 、全局變量(所有檔案可見的)

        2 、靜态全局變量(目前檔案可見的)

        3 、局部變量(目前 scope 可見的)

    如果你的局部變量和全局變量發生沖突(也就是重名),一般情況下是局部變量會隐藏全局變量,也就是說,如果一個全局變量和一個函數中的局部變量同名時,如果目前停止點在函數中,用 print 顯示出的變量的值會是函數中的局部變量的值。如果此時你想檢視全局變量的值時,你可以使用 “ :: ” 操作符:

        file::variable

    function::variable

    可以通過這種形式指定你所想檢視的變量,是哪個檔案中的或是哪個函數中的。例如,檢視檔案 f2.c 中的全局變量 x 的值:

    gdb) p 'f2.c'::x

    當然, “ :: ” 操作符會和 c++ 中的發生沖突, gdb 能自動識别 “ :: ” 是否 c++ 的操作符,是以你不必擔心在調試 c++ 程式時會出現異常。

    另外,需要注意的是,如果你的程式編譯時開啟了優化選項,那麼在用 gdb 調試被優化過的程式時,可能會發生某些變量不能通路,或是取值錯誤碼的情況。這個是很正常的,因為優化程式會删改你的程式,整理你程式的語句順序,剔除一些無意義的變量等,是以在 gdb 調試這種程式時,運作時的指令和你所編寫指令就有不一樣,也就會出現你所想象不到的結果。對付這種情況時,需要在編譯程式時關閉編譯優化。一般來說,幾乎所有的編譯器都支援編譯優化的開關,例如, gnu 的 c/c++ 編譯器 gcc ,你可以使用 “ -gstabs ” 選項來解決這個問題。關于編譯器的參數,還請檢視編譯器的使用說明文檔。

繼續閱讀