GDB十分鐘教程

壹頁書發表於2015-11-26

GDB十分鐘教程

作者: liigo
原文連結: 
http://blog.csdn.net/liigo/archive/2006/01/17/582231.aspx
日期: 2006年1月16日

本文寫給主要工作在Windows作業系統下而又需要開發一些跨平臺軟體的程式設計師朋友,以及程式愛好者。

GDB是一個由GNU開源組織釋出的、UNIX/LINUX作業系統下的、基於命令列的、功能強大的程式除錯工具。

GDB中的命令固然很多,但我們只需掌握其中十個左右的命令,就大致可以完成日常的基本的程式除錯工作。

 命令  解釋  示例
file <檔名> 載入被除錯的可執行程式檔案。
因為一般都在被除錯程式所在目錄下執行GDB,因而文字名不需要帶路徑。
(gdb) file gdb-sample
r Run的簡寫,執行被除錯的程式。
如果此前沒有下過斷點,則執行完整個程式;如果有斷點,則程式暫停在第一個可用斷點處。
(gdb) r
c Continue的簡寫,繼續執行被除錯程式,直至下一個斷點或程式結束。 (gdb) c
b <行號>
b <函式名稱>
b *<函式名稱>
b *<程式碼地址>

d [編號]

b: Breakpoint的簡寫,設定斷點。兩可以使用“行號”“函式名稱”“執行地址”等方式指定斷點位置。
其中在函式名稱前面加“*”符號表示將斷點設定在“由編譯器生成的prolog程式碼處”。如果不瞭解彙編,可以不予理會此用法。

d: Delete breakpoint的簡寫,刪除指定編號的某個斷點,或刪除所有斷點。斷點編號從1開始遞增。

(gdb) b 8
(gdb) b main
(gdb) b *main
(gdb) b *0x804835c

(gdb) d

s, n s: 執行一行源程式程式碼,如果此行程式碼中有函式呼叫,則進入該函式;
n: 執行一行源程式程式碼,此行程式碼中的函式呼叫也一併執行。

s 相當於其它偵錯程式中的“Step Into (單步跟蹤進入)”;
n 相當於其它偵錯程式中的“Step Over (單步跟蹤)”。

這兩個命令必須在有原始碼除錯資訊的情況下才可以使用(GCC編譯時使用“-g”引數)。

(gdb) s
(gdb) n
si, ni si命令類似於s命令,ni命令類似於n命令。所不同的是,這兩個命令(si/ni)所針對的是彙編指令,而s/n針對的是原始碼。 (gdb) si
(gdb) ni
p <變數名稱> Print的簡寫,顯示指定變數(臨時變數或全域性變數)的值。 (gdb) p i
(gdb) p nGlobalVar
display ...

undisplay <編號>

display,設定程式中斷後欲顯示的資料及其格式。
例如,如果希望每次程式中斷後可以看到即將被執行的下一條彙編指令,可以使用命令
“display /i $pc”
其中 $pc 代表當前彙編指令,/i 表示以十六進行顯示。當需要關心彙編程式碼時,此命令相當有用。

undispaly,取消先前的display設定,編號從1開始遞增。

(gdb) display /i $pc

(gdb) undisplay 1

i Info的簡寫,用於顯示各類資訊,詳情請查閱“help i”。 (gdb) i r
q Quit的簡寫,退出GDB除錯環境。 (gdb) q
help [命令名稱] GDB幫助命令,提供對GDB名種命令的解釋說明。
如果指定了“命令名稱”引數,則顯示該命令的詳細說明;如果沒有指定引數,則分類顯示所有GDB命令,供使用者進一步瀏覽和查詢。
(gdb) help display

 

廢話不多說,下面開始實踐。

先給出一個示例用的小程式,C語言程式碼,簡單的不能再簡單了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

//此程式僅作為“GDB十分鐘教程”的示例程式碼, by liigo
//Email: 

//blog: http://blog.csdn.net/liigo
//WebSite: 
 

#include 
<stdio.h>

int
 nGlobalVar = 0;

int
 tempFunction(int aint b)
{
    printf("tempFunction is called, a = %d, b = %d /n"ab
);
    return (a + b
);
}


int
 main()
{
    int n
;
    n = 1
;
    n
++;
    n
--;

    nGlobalVar += 100
;
    nGlobalVar -= 12
;

    printf("n = %d, nGlobalVar = %d /n"nnGlobalVar
);

    n = tempFunction(12
);
    printf("n = %d"n
);

    return 0
;
}

請將此程式碼複製出來並儲存到檔案 gdb-sample.c 中,然後切換到此檔案所在目錄,用GCC編譯之:

gcc gdb-sample.c -o gdb-sample -g

在上面的命令列中,使用 -o 引數指定了編譯生成的可執行檔名為 gdb-sample,使用引數 -g 表示將原始碼資訊編譯到可執行檔案中。如果不使用引數 -g,會給後面的GDB除錯造成不便。當然,如果我們沒有程式的原始碼,自然也無從使用 -g 引數,除錯/跟蹤時也只能是彙編程式碼級別的除錯/跟蹤。

下面“gdb”命令啟動GDB,將首先顯示GDB說明,不管它:

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 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".
(gdb) 

上面最後一行“(gdb) ”為GDB內部命令引導符,等待使用者輸入GDB命令。

下面使用“file”命令載入被除錯程式 gdb-sample(這裡的 gdb-sample 即前面 GCC 編譯輸出的可執行檔案):

(gdb) file gdb-sample
Reading symbols from gdb-sample...done.

上面最後一行提示已經載入成功。

下面使用“r”命令執行(Run)被除錯檔案,因為尚未設定任何斷點,將直接執行到程式結束:

(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample
n = 1, nGlobalVar = 88
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.

下面使用“b”命令在 main 函式開頭設定一個斷點(Breakpoint):

(gdb) b main
Breakpoint 1 at 0x804835c: file gdb-sample.c, line 19.

上面最後一行提示已經成功設定斷點,並給出了該斷點資訊:在原始檔 gdb-sample.c 第19行處設定斷點;這是本程式的第一個斷點(序號為1);斷點處的程式碼地址為 0x804835c(此值可能僅在本次除錯過程中有效)。回過頭去看原始碼,第19行中的程式碼為“n = 1”,恰好是 main 函式中的第一個可執行語句(前面的“int n;”為變數定義語句,並非可執行語句)。

再次使用“r”命令執行(Run)被除錯程式:

(gdb) r
Starting program: /home/liigo/temp/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;

程式中斷在gdb-sample.c第19行處,即main函式是第一個可執行語句處。

上面最後一行資訊為:下一條將要執行的原始碼為“n = 1;”,它是原始碼檔案gdb-sample.c中的第19行。

下面使用“s”命令(Step)執行下一行程式碼(即第19行“n = 1;”):

(gdb) s
20 n++;

上面的資訊表示已經執行完“n = 1;”,並顯示下一條要執行的程式碼為第20行的“n++;”。

既然已經執行了“n = 1;”,即給變數 n 賦值為 1,那我們用“p”命令(Print)看一下變數 n 的值是不是 1 :

(gdb) p n
$1 = 1

果然是 1。($1大致是表示這是第一次使用“p”命令——再次執行“p n”將顯示“$2 = 1”——此資訊應該沒有什麼用處。)

下面我們分別在第26行、tempFunction 函式開頭各設定一個斷點(分別使用命令“b 26”“b tempFunction”):

(gdb) b 26
Breakpoint 2 at 0x804837b: file gdb-sample.c, line 26.
(gdb) b tempFunction
Breakpoint 3 at 0x804832e: file gdb-sample.c, line 12.

使用“c”命令繼續(Continue)執行被除錯程式,程式將中斷在第二個斷點(26行),此時全域性變數 nGlobalVar 的值應該是 88;再一次執行“c”命令,程式將中斷於第三個斷點(12行,tempFunction 函式開頭處),此時tempFunction 函式的兩個引數 a、b 的值應分別是 1 和 2:

(gdb) c
Continuing.

Breakpoint 2, main () at gdb-sample.c:26
26 printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);
(gdb) p nGlobalVar
$2 = 88
(gdb) c
Continuing.
n = 1, nGlobalVar = 88

Breakpoint 3, tempFunction (a=1, b=2) at gdb-sample.c:12
12 printf("tempFunction is called, a = %d, b = %d /n", a, b);
(gdb) p a
$3 = 1
(gdb) p b
$4 = 2

上面反饋的資訊一切都在我們預料之中,哈哈~~~

再一次執行“c”命令(Continue),因為後面再也沒有其它斷點,程式將一直執行到結束:

(gdb) c
Continuing.
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.

 

有時候需要看到編譯器生成的彙編程式碼,以進行彙編級的除錯或跟蹤,又該如何操作呢?

這就要用到display命令“display /i $pc”了(此命令前面已有詳細解釋):

(gdb) display /i $pc
(gdb) 

此後程式再中斷時,就可以顯示出彙編程式碼了:

(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;
1: x/i $pc 0x804835c <main+16>: movl $0x1,0xfffffffc(%ebp)

看到了彙編程式碼,“n = 1;”對應的彙編程式碼是“movl $0x1,0xfffffffc(%ebp)”。

並且以後程式每次中斷都將顯示下一條彙編指定(“si”命令用於執行一條彙編程式碼——區別於“s”執行一行C程式碼):

(gdb) si
20 n++;
1: x/i $pc 0x8048363 <main+23>: lea 0xfffffffc(%ebp),%eax
(gdb) si
0x08048366 20 n++;
1: x/i $pc 0x8048366 <main+26>: incl (%eax)
(gdb) si
21 n--;
1: x/i $pc 0x8048368 <main+28>: lea 0xfffffffc(%ebp),%eax
(gdb) si
0x0804836b 21 n--;
1: x/i $pc 0x804836b <main+31>: decl (%eax)
(gdb) si
23 nGlobalVar += 100;
1: x/i $pc 0x804836d <main+33>: addl $0x64,0x80494fc

 

接下來我們試一下命令“b *<函式名稱>”。

為了更簡明,有必要先刪除目前所有斷點(使用“d”命令——Delete breakpoint):

(gdb) d
Delete all breakpoints? (y or n) y
(gdb)

當被詢問是否刪除所有斷點時,輸入“y”並按Enter鍵即可。

下面使用命令“b *main”在 main 函式的 prolog 程式碼處設定斷點(prolog、epilog,分別表示編譯器在每個函式的開頭和結尾自行插入的程式碼):

(gdb) b *main
Breakpoint 4 at 0x804834c: file gdb-sample.c, line 17.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample

Breakpoint 4, main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834c <main>: push %ebp
(gdb) si
0x0804834d 17 {
1: x/i $pc 0x804834d <main+1>: mov %esp,%ebp
(gdb) si
0x0804834f in main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834f <main+3>: sub $0x8,%esp
(gdb) si
0x08048352 17 {
1: x/i $pc 0x8048352 <main+6>: and $0xfffffff0,%esp
(gdb) si
0x08048355 17 {
1: x/i $pc 0x8048355 <main+9>: mov $0x0,%eax
(gdb) si
0x0804835a 17 {
1: x/i $pc 0x804835a <main+14>: sub %eax,%esp
(gdb) si
19 n = 1;
1: x/i $pc 0x804835c <main+16>: movl $0x1,0xfffffffc(%ebp)

此時可以使用“i r”命令顯示暫存器中的當前值———“i r”即“Infomation Register”:

(gdb) i r
eax 0xbffff6a4 -1073744220
ecx 0x42015554 1107383636
edx 0x40016bc8 1073834952
ebx 0x42130a14 1108544020
esp 0xbffff6a0 0xbffff6a0
ebp 0xbffff6a8 0xbffff6a8
esi 0x40015360 1073828704
edi 0x80483f0 134513648
eip 0x8048366 0x8048366
eflags 0x386 902
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x33 51

當然也可以顯示任意一個指定的暫存器值:

(gdb) i r eax
eax 0xbffff6a4 -1073744220

 

最後一個要介紹的命令是“q”,退出(Quit)GDB除錯環境:

(gdb) q
The program is running. Exit anyway? (y or n) y

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29254281/viewspace-1847393/,如需轉載,請註明出處,否則將追究法律責任。

相關文章