天天看點

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

四、源代碼的記憶體 你可以使用 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 *[email protected] @ 的左邊是數組的首位址的值,也就是變量 array 所指向的内容,右邊則是資料的長度,其儲存在變量 len 中,其輸出結果,大約是下面這個樣子的: (gdb) p *[email protected] $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 "<<", rq=0x34c88 ">>") at input.c:530 530 if (lquote != def_lquote) set print address off 關閉函數的參數位址顯示,如: (gdb) set print addr off (gdb) f #0 set_quotes (lq="<<", rq=">>") 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 下的軟體全是指令行的,這給程式開發提代供了極大的便利,指令行軟體的優勢在于,它們可以非常容易的內建在一起,使用幾個簡單的已有工具的指令,就可以做 出一個非常強大的功能。

繼續閱讀