linux下gdb單步調試(下)(轉摘)

來源:互聯網
上載者:User
四、原始碼的記憶體

你可以使用 info line 命令來查看原始碼在記憶體中的地址。 info line 後面可以跟 “ 行號 ” , “ 函數名 ” , “ 檔案名稱 : 行號 ” , “ 檔案名稱 : 函數名 ” ,這個命令會列印出所指定的源碼在運行時的記憶體位址,如:

(gdb) info line tst.c:func
Line 5 of "tst.c" starts at address 0x8048456 and ends at 0x804845d .

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

(gdb) disassemble func
Dump of assembler code for function func:
0x8048450 : push %ebp
0x8048451 : mov %esp,%ebp
0x8048453 : sub $0x18,%esp
0x8048456 : movl $0x0,0xfffffffc(%ebp)
0x804845d : movl $0x1,0xfffffff8(%ebp)
0x8048464 : mov 0xfffffff8(%ebp),%eax
0x8048467 : cmp 0x8(%ebp),%eax
0x804846a : jle 0x8048470
0x804846c : jmp 0x8048480
0x804846e : mov %esi,%esi
0x8048470 : mov 0xfffffff8(%ebp),%eax
0x8048473 : add %eax,0xfffffffc(%ebp)
0x8048476 : incl 0xfffffff8(%ebp)
0x8048479 : jmp 0x8048464
0x804847b : nop
0x804847c : lea 0x0(%esi,1),%esi
0x8048480 : mov 0xfffffffc(%ebp),%edx
0x8048483 : mov %edx,%eax
0x8048485 : jmp 0x8048487
0x8048487 : mov %ebp,%esp
0x8048489 : pop %ebp
0x804848a : ret
End of assembler dump.

查看運行時資料
———————

在你偵錯工具時,當程式被停住時,你可以使用 print 命令(簡寫命令為 p ),或是同義命令 inspect 來查看當前程式的運行資料。 print 命令的格式是:

print
print / 是運算式,是你所調試的程式的語言的運算式( GDB 可以調試多種程式設計語言), 是輸出的格式,比如,如果要把運算式按 16 進位的格式輸出,那麼就是 /x 。

一、運算式

print 和許多 GDB 的命令一樣,可以接受一個運算式, GDB 會根據當前的程式啟動並執行資料來計算這個運算式,既然是運算式,那麼就可以是當前程式運行中的 const 常量、變數、函數等內容。可惜的是 GDB 不能使用你在程式中所定義的宏。

運算式的文法應該是當前所調試的語言的文法,由於 C/C++ 是一種福士型的語言,所以,本文中的例子都是關於 C/C++ 的。(而關於用 GDB 調試其它語言的章節,我將在後面介紹)

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

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

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

{}
表示一個指向記憶體位址 的類型為 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 ” 選項來解決這個問題。關於編譯器的參數,還請查看編譯器的使用說明文檔。

三、數組

有時候,你需要查看一段連續的記憶體空間的值。比如數組的一段,或是動態分配的資料的大小。你可以使用 GDB 的 “ @ ” 操作符, “ @ ” 的左邊是第一個記憶體的地址的值, “ @ ” 的右邊則你你想查看記憶體的長度。例如,你的程式中有這樣的語句:

int *array = (int *) malloc (len * sizeof (int));

於是,在 GDB 調試過程中,你可以以如下命令顯示出這個動態數組的取值:

p *array@len

@ 的左邊是數組的首地址的值,也就是變數 array 所指向的內容,右邊則是資料的長度,其儲存在變數 len 中,其輸出結果,大約是下面這個樣子的:

(gdb) p *array@len
$1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40}

如果是靜態數組的話,可以直接用 print 數組名,就可以顯示數組中所有資料的內容了。

四、輸出格式

一般來說, GDB 會根據變數的類型輸出變數的值。但你也可以自訂 GDB 的輸出的格式。例如,你想輸出一個整數的十六進位,或是二進位來查看這個整型變數的中的位的情況。要做到這樣,你可以使用 GDB 的資料顯示格式:

x 按十六進位格式顯示變數。
d 按十進位格式顯示變數。
u 按十六進位格式顯示無符號整型。
o 按八進位格式顯示變數。
t 按二進位格式顯示變數。
a 按十六進位格式顯示變數。
c 按字元格式設定顯示變數。
f 按浮點數格式顯示變數。

(gdb) p i
$21 = 101

(gdb) p/a i
$22 = 0x65

(gdb) p/c i
$23 = 101 'e'

(gdb) p/f i
$24 = 1.41531145e-43

(gdb) p/x i
$25 = 0x65

(gdb) p/t i
$26 = 1100101

五、查看記憶體

你可以使用 examine 命令(簡寫是 x )來查看記憶體位址中的值。 x 命令的文法如下所示:

x/

n 、 f 、 u 是可選的參數。

n 是一個正整數,表示顯示記憶體的長度,也就是說從當前地址向後顯示幾個地址的內容。
f 表示顯示的格式,參見上面。如果地址所指的是字串,那麼格式可以是 s ,如果地十是指令地址,那麼格式可以是 i 。
u 表示從當前地址往後請求的位元組數,如果不指定的話, GDB 預設是 4 個 bytes 。 u 參數可以用下面的字元來代替, b 表示單位元組, h 表示雙位元組, w 表示四位元組, g 表示八位元組。當我們指定了位元組長度後, GDB 會從指記憶體定的記憶體位址開始,讀寫指定位元組,並把其當作一個值取出來。

表示一個記憶體位址。

n/f/u 三個參數可以一起使用。例如:

命令: x/3uh 0x54320 表示,從記憶體位址 0x54320 讀取內容, h 表示以雙位元組為一個單位, 3 表示三個單位, u 表示按十六進位顯示。

六、自動顯示

你可以設定一些自動顯示的變數,當程式停住時,或是在你單步跟蹤時,這些變數會自動顯示。相關的 GDB 命令是 display 。

display
display/
display/

expr 是一個運算式, fmt 表示顯示的格式, addr 表示記憶體位址,當你用 display 設定好了一個或多個運算式後,只要你的程式被停下來, GDB 會自動顯示你所設定的這些運算式的值。

格式 i 和 s 同樣被 display 支援,一個非常有用的命令是:

display/i $pc

$pc 是 GDB 的環境變數,表示著指令的地址, /i 則表示輸出格式為機器指令碼,也就是彙編。於是當程式停下後,就會出現原始碼和機器指令碼相對應的情形,這是一個很有意思的功能。

下面是一些和 display 相關的 GDB 命令:

undisplay
delete display
刪除自動顯示, dnums 意為所設定好了的自動顯式的編號。如果要同時刪除幾個,編號可以用空格分隔,如果要刪除一個範圍內的編號,可以用減號表示(如: 2-5 )

disable display
enable display
disable 和 enalbe 不刪除自動顯示的設定,而只是讓其失效和恢複。

info display
查看 display 設定的自動顯示的資訊。 GDB 會打出一張表格,向你報告當然調試中設定了多少個自動顯示設定,其中包括,設定的編號,運算式,是否 enable 。

七、設定顯示選項

GDB 中關於顯示的選項比較多,這裡我只例舉大多數常用的選項。

set print address
set print address on
開啟地址輸出,當程式顯示函數資訊時, GDB 會顯出函數的參數地址。系統預設為開啟的,如:

(gdb) f
#0 set_quotes (lq=0x34c78 ">")
at input.c:530
530 if (lquote != def_lquote)

set print address off
關閉函數的參數地址顯示,如:

(gdb) set print addr off
(gdb) f
#0 set_quotes (lq=">") at input.c:530
530 if (lquote != def_lquote)

show print address
查看當前地址顯示選項是否開啟。

set print array
set print array on
開啟數組顯示,開啟後當數組顯示時,每個元素佔一行,如果不開啟的話,每個元素則以逗號分隔。這個選項預設是關閉的。與之相關的兩個命令如下,我就不再多說了。

set print array off
show print array

set print elements
這個選項主要是設定數組的,如果你的數組太大了,那麼就可以指定一個 來指定資料顯示的最大長度,當到達這個長度時, GDB 就不再往下顯示了。如果設定為 0 ,則表示不限制。

show print elements
查看 print elements 的選項資訊。

set print null-stop
如果開啟了這個選項,那麼當顯示字串時,遇到結束符則停止顯示。這個選項預設為 off 。

set print pretty on
如果開啟 printf pretty 這個選項,那麼當 GDB 顯示結構體時會比較漂亮。如:

$1 = {
next = 0x0,
flags = {
sweet = 1,
sour = 1
},
meat = 0x54 "Pork"
}

set print pretty off
關閉 printf pretty 這個選項, GDB 顯示結構體時會如下顯示:

$1 = {next = 0x0, flags = {sweet = 1, sour = 1}, meat = 0x54 "Pork"}

show print pretty
查看 GDB 是如何顯示結構體的。

set print sevenbit-strings
設定字元顯示,是否按 “ /nnn ” 的格式顯示,如果開啟,則字串或字元資料按 /nnn 顯示,如 “ /065 ” 。

show print sevenbit-strings
查看字元顯示開關是否開啟。

set print union
設定顯示結構體時,是否顯式其內的聯合體資料。例如有以下資料結構:

typedef enum {Tree, Bug} Species;
typedef enum {Big_tree, Acorn, Seedling} Tree_forms;
typedef enum {Caterpillar, Cocoon, Butterfly}
Bug_forms;

struct thing {
Species it;
union {
Tree_forms tree;
Bug_forms bug;
} form;
};

struct thing foo = {Tree, {Acorn}};

當開啟這個開關時,執行 p foo 命令後,會如下顯示:
$1 = {it = Tree, form = {tree = Acorn, bug = Cocoon}}

當關閉這個開關時,執行 p foo 命令後,會如下顯示:
$1 = {it = Tree, form = {...}}

show print union
查看聯合體資料的顯示方式

set print object
在 C++ 中,如果一個對象指標指向其衍生類別,如果開啟這個選項, GDB 會自動按照虛方法調用的規則顯示輸出,如果關閉這個選項的話, GDB 就不管虛函數表了。這個選項預設是 off 。

show print object
查看對象選項的設定。

set print static-members
這個選項表示,當顯示一個 C++ 對象中的內容是,是否顯示其中的待用資料成員。預設是 on 。

show print static-members
查看待用資料成員選項設定。

set print vtbl
當此選項開啟時, GDB 將用比較規整的格式來顯示虛函數表時。其預設是關閉的。

show print vtbl
查看虛函數顯示格式的選項。

八、記錄

當你用 GDB 的 print 查看程式運行時的資料時,你每一個 print 都會被 GDB 記錄下來。 GDB 會以 $1, $2, $3 ..... 這樣的方式為你每一個 print 命令編上號。於是,你可以使用這個編號訪問以前的運算式,如 $1 。這個功能所帶來的好處是,如果你先前輸入了一個比較長的運算式,如果你還想查看這個運算式的值,你可以使用記錄來訪問,省去了重複輸入。

九、 GDB 環境變數

你可以在 GDB 的調試環境中定義自己的變數,用來儲存一些偵錯工具中的運行資料。要定義一個 GDB 的變數很簡單只需。使用 GDB 的 set 命令。 GDB 的環境變數和 UNIX 一樣,也是以 $ 起頭。如:

set $foo = *object_ptr

使用環境變數時, GDB 會在你第一次使用時建立這個變數,而在以後的使用中,則直接對其賦值。環境變數沒有類型,你可以給環境變數定義任一的類型。包括結構體和數組。

show convenience
該命令查看當前所設定的所有的環境變數。

這是一個比較強大的功能,環境變數和程式變數的互動使用,將使得程式調試更為靈活便捷。例如:

set $i = 0
print bar[$i++]->contents

於是,當你就不必, print bar[0]->contents, print bar[1]->contents 地輸入命令了。輸入這樣的命令後,只用敲斷行符號,重複執行上一條語句,環境變數會自動累加,從而完成逐個輸出的功能。

十、查看寄存器

要查看寄存器的值,很簡單,可以使用如下命令:

info registers
查看寄存器的情況。(除了浮點寄存器)

info all-registers
查看所有寄存器的情況。(包括浮點寄存器)

info registers
查看所指定的寄存器的情況。

寄存器中放置了程式運行時的資料,比如程式當前啟動並執行指令地址( ip ),程式的當前堆棧地址( sp )等等。你同樣可以使用 print 命令來訪問寄存器的情況,只需要在寄存器名字前加一個 $ 符號就可以了。如: p $eip 。

改變程式的執行
———————

一旦使用 GDB 掛上被偵錯工具,當程式運行起來後,你可以根據自己的調試思路來動態地在 GDB 中更改當前被偵錯工具的運行線路或是其變數的值,這個強大的功能能夠讓你更好的調試你的程式,比如,你可以在程式的一次運行中走遍程式的所有分支。

一、修改變數值

修改被偵錯工具運行時的變數值,在 GDB 中很容易實現,使用 GDB 的 print 命令即可完成。如:

(gdb) print x=4

x=4 這個運算式是 C/C++ 的文法,意為把變數 x 的值修改為 4 ,如果你當前調試的語言是 Pascal ,那麼你可以使用 Pascal 的文法: x:=4 。

在某些時候,很有可能你的變數和 GDB 中的參數衝突,如:

(gdb) whatis width
type = double
(gdb) p width
$4 = 13
(gdb) set width=47
Invalid syntax in expression.

因為, set width 是 GDB 的命令,所以,出現了 “ Invalid syntax in expression ” 的設定錯誤,此時,你可以使用 set var 命令來告訴 GDB , width 不是你 GDB 的參數,而是程式的變數名,如:

(gdb) set var width=47

另外,還可能有些情況, GDB 並不報告這種錯誤,所以保險起見,在你改變程式變數取值時,最好都使用 set var 格式的 GDB 命令。

二、跳轉執行

一般來說,被偵錯工具會按照程式碼的運行順序依次執行。 GDB 提供了亂序執行的功能,也就是說, GDB 可以修改程式的執行順序,可以讓程式執行隨意跳躍。這個功能可以由 GDB 的 jump 命令來完:

jump指定下一條語句的運行點。 可以是檔案的行號,可以是 file:line 格式,可以是 +num 這種位移量格式。表式著下一條運行語句從哪裡開始。

jump
這裡的
是程式碼的記憶體位址。

注意, jump 命令不會改變當前的程式棧中的內容,所以,當你從一個函數跳到另一個函數時,當函數運行完返回時進行彈棧操作時必然會發生錯誤,可能結果還是非常奇怪的,甚至於產生程式 Core Dump 。所以最好是同一個函數中進行跳轉。

熟悉彙編的人都知道,程式運行時,有一個寄存器用於儲存當前代碼所在的記憶體位址。所以, jump 命令也就是改變了這個寄存器中的值。於是,你可以使用 “ set $pc ” 來更改跳轉執行的地址。如:

set $pc = 0x485

三、產生訊號量

使用 singal 命令,可以產生一個訊號量給被調試的程式。如:中斷訊號 Ctrl+C 。這非常方便於程式的調試,可以在程式啟動並執行任意位置設定斷點,並在該斷點用 GDB 產生一個訊號量,這種精確地在某處產生訊號非常有利程式的調試。

文法是: signal , UNIX 的系統訊號量通常從 1 到 15 。所以 取值也在這個範圍。

single 命令和 shell 的 kill 命令不同,系統的 kill 命令發訊號給被偵錯工具時,是由 GDB 截獲的,而 single 命令所發出一訊號則是直接發給被偵錯工具的。

四、強制函數返回

如果你的調試斷點在某個函數中,並還有語句沒有執行完。你可以使用 return 命令強制函數忽略還沒有執行的語句並返回。

return
return
使用 return 命令取消當前函數的執行,並立即返回,如果指定了 ,那麼該運算式的值會被認作函數的傳回值。

五、強制調用函數

call
運算式中可以一是函數,以此達到強制調用函數的目的。並顯示函數的傳回值,如果函數傳回值是 void ,那麼就不顯示。

另一個相似的命令也可以完成這一功能 —— print , print 後面可以跟運算式,所以也可以用他來調用函數, print 和 call 的不同是,如果函數返回 void , call 則不顯示, print 則顯示函數傳回值,並把該值存入曆史資料中。

在不同語言中使用 GDB
——————————

GDB 支援下列語言: C, C++, Fortran, PASCAL, Java, Chill, assembly, 和 Modula-2 。一般說來, GDB 會根據你所調試的程式來確定當然的調試語言,比如:發現檔案名稱尾碼為 “ .c ” 的, GDB 會認為是 C 程式。檔案名稱尾碼為 “ .C, .cc, .cp, .cpp, .cxx, .c++ ” 的, GDB 會認為是 C++ 程式。而尾碼是 “ .f, .F ” 的, GDB 會認為是 Fortran 程式,還有,尾碼為如果是 “ .s, .S ” 的會認為是組合語言。

也就是說, GDB 會根據你所調試的程式的語言,來設定自己的語言環境,並讓 GDB 的命令跟著語言環境的改變而改變。比如一些 GDB 命令需要用到運算式或變數時,這些運算式或變數的文法,完全是根據當前的語言環境而改變的。例如 C/C++ 中對指標的文法是 *p ,而在 Modula-2 中則是 p^ 。並且,如果你當前的程式是由幾種不同語言一同編譯成的,那到在調試過程中, GDB 也能根據不同的語言自動地切換語言環境。這種跟著語言環境而改變的功能,真是體貼開發人員的一種設計。

下面是幾個相關於 GDB 語言環境的命令:

show language
查看當前的語言環境。如果 GDB 不能識為你所調試的程式設計語言,那麼, C 語言被認為是預設的環境。

info frame
查看當前函數的程式語言。

info source
查看當前檔案的程式語言。

如果 GDB 沒有檢測出當前的程式語言,那麼你也可以手動設定當前的程式語言。使用 set language 命令即可做到。

當 set language 命令後什麼也不跟的話,你可以查看 GDB 所支援的語言種類:

(gdb) set language
The currently understood settings are:

local or auto Automatic setting based on source file
c Use the C language
c++ Use the C++ language
asm Use the Asm language
chill Use the Chill language
fortran Use the Fortran language
java Use the Java language
modula-2 Use the Modula-2 language
pascal Use the Pascal language
scheme Use the Scheme language

於是你可以在 set language 後跟上被列出來的程式語言名,來設定當前的語言環境。

後記
——

GDB 是一個強大的命令列調試工具。大家知道命令列的強大就是在於,其可以形成執行序列,形成指令碼。 UNIX 下的軟體全是命令列的,這給程式開發提代供了極大的便利,命令列軟體的優勢在於,它們可以非常容易的整合在一起,使用幾個簡單的已有工具的命令,就可以做 出一個非常強大的功能。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.