gdb調試相關

3.2.1 斷點的工作原理 在本書的所有地方都使用了STATUS_BREAKPOINT異常,尤其是在本章中,但卻沒有很明確地解釋這個異常的引發方式。現在,我們就來解釋如何在進程中產生這個異常。 在x86指令集中包含了一個特殊的指令int 3,這個指令將在處理器上產生硬件中斷STATUS_BREAKPOINT以用於調試。爲了響應異常STATUS_BREAKPOINT,處理器將執行位於中斷矢量3中的中斷處理器。中斷處理器將把這個硬件異常轉換爲在這條指令地址上引發的一個軟件異常。這條指令在指令流中被表示爲一個字節0xCC,也被稱之爲操作碼(Operation Code)或者opcode。在沒有調試器的情況下,軟件異常將被視作爲一個普通的異常;否則,Windows操作系統將告訴調試器在這條指令的地址上發生了中斷。 調試器將通過0xCC來設置斷點。在設置斷點時,調試器將首先修改斷點地址所在內存塊的保護模式,這是爲了接下來在這個地址上寫入一個int 3指令。這個地址上原來的值,以及關於斷點編號的信息,都將被保存在調試器的內存中。 斷點地址必須是指令流中的一個有效指令的地址,這個地址通常是一條機器指令的第一個字節。如果在機器指令的其他地址上設置的斷點,那麼將改變指令的含義,從而導致這條指令不會觸發硬件異常STATUS_BREAKPOINT。顯然,運行一個包含錯誤機器指令的程序是非常危險的,並且將產生不可預測的行爲。 這種修改內存的操作對於用戶來說應該是不可見的,因爲這些修改將影響對代碼進行反彙編的結果。因此,當調試器停止時,會把所設置的斷點又替換爲原來的指令,當調試目標再次運行時,將再次把int 3的操作碼插入到目標映象中。 爲了說明這種機制,我們在調試器中啓動調試目標notepad.exe。在觸發初始斷點時,我們在任意地址上設置一個斷點,在本示例中是notepad!WinMain的起始地址,並且將另一個調試器以非侵入的方式附加到同一進程並查看在這個地址上的指令。這將爲我們揭示調試目標真實的內存內容。 當用戶態調試器等待用戶輸入命令時,在內存中將包含最初的指令流。當執行調試目標時,我們將在用戶態命令窗口中鍵入命令g來改變內存,如清單3.30中第二部分所示。 在設置斷點時,內核態調試器將遵循相同的模型,只不過操作系統的內存管理機制將帶來一些差異。在Windows操作系統中,大多數包含可執行代碼的內存頁在多個進程之間是共享的,正是由於這個功能才使得同一個DLL可以被加載到不同的進程中。當用戶態調試器激活一個新的斷點時,它會把內存頁的保護狀態從“只讀”變爲“讀寫”。通過“寫時複製(Copy On Write,COW)”技術生成的新內存頁將作爲被調試進程的私有頁,並且在對其進行修改時不會影響共享這個頁的其他進程。由於內核態調試器無法通過COW技術來生成一個私有頁,它將直接在共享頁中設置斷點。 內核態斷點將會影響共享這個內存頁的所有進程。而且,根據系統中可用的內存量,在被調試進程執行完之後,內核態斷點仍有可能駐留在系統內存中。在實際的調試情況中,這種情況帶來的後果是很難預測的,這就像內存負載和整體系統的行爲將對Windows內存管理產生極大的影響。然而,我們可以得出一些關於內核態斷點的結論: ■如果在多個進程共享的內存頁中設置斷點,那麼在所有這些進程中都會產生中斷。由於內核調試器在處理斷點時相對較慢,尤其是當通過串行電纜來調試時,因此我們絕不應該在一些被頻繁調用的函數中設置斷點,例如ntdll!RtlAllocateHeap。我們可以通過EPROCESS地址或者KTHREAD地址來縮小斷點的範圍,從而減少調試器的停止次數。不幸的是,調試器在每次遇到斷點時仍然會收到通知,只不過對於所有不匹配的進程,調試器將自動處理斷點。 ■當內核調試器中的被調試進程結束後,所有的用戶態斷點都必須被刪除以避免與其他運行中的進程發生衝突(共享頁將仍然在內存中停留一段時間,其中保留了之前設置的所有斷點,即使進程被重新啓動也是如此)。 ■當用戶態調試器與內核態調試器一起使用時,通常必須從內核態調試器中設置這些斷點。否則,斷點異常將被分發給用戶態調試器。由於無法知道int 3其實是一個斷點而並非真實的int 3指令,因此執行流將被破壞。顯然,在鍵入命令g後執行的指令流將是完全錯誤的,最終將在某個調試器中出現大量的訪問違例異常或者單步異常。 GDB是一個極爲強大的調試工具,絕對比那些IDE下轄的調試器強大得多。在GDB的學習過程中一個核心的問題就是Call Stack的理解,這是GDB安身立命之根本。進一步的,就要涉及到core dump的使用了。既然是隻談論GDB的基礎,那麼本文中就不涉及到core dump的問題,有興趣的可以自己google一下。 準備工作 gdb需要的文件是可執行文件。要想使用gdb去調試某個可執行文件,必須在用gcc或g++生成該文件的時候使用-g選項加入調試信息。 載入可執行文件 產生了可調試的可執行文件後,必須將其載入到gdb中才可進行調試。載入文件有兩種辦法: 1> 使用命令 gdb execfilename 載入; 2> 在進入gdb後,使用 file execfilename 或是 target exec execfilename 斷點管理 使用break(簡寫爲b即可)命令設置程序斷點。 break:可以在程序運行時利用frame命令選定某一個stack frame,然後執行命令break。這樣使得一旦程序執行到該選中stack frame中時,就停下。 break location:其中的location可以指定爲當前源文件的第幾行,或是針對當前位置的偏移量,也可以指定爲函數名,甚至可以指定內存中的位置。 break location if condition:這是一個條件斷點設置語句。 除了break之外,還有幾個斷點設置的關鍵字: tbreak:在程序的一次運行中只停止一次。 rbreak regex:利用正則表達式設置一系列符合regex的斷點。 可以使用info命令來查看斷點信息,使用disable和enable來禁用啓用斷點,使用clear和delete來刪除指定斷點。 觀察點管理 使用watch命令設置程序觀察點。 watch expr:其中的expr可以是參數名,也可以是表達式。當expr的值被改寫的時候程序就會暫停執行。 除了watch之外,還有幾個觀察點設置的關鍵字: rwatch:當參數或是表達式的值被程序讀取的時候暫停。 awatch:當參數或是表達式的值被讀取或改寫的時候都會暫停。 可以使用info命令來查看斷點信息,使用disable和enable來禁用啓用觀察點,使用delete來刪除指定觀察點。 設置程序運行參數 set args:可指定運行時參數。(如:set args 10 20 30 40 50); show args:命令可以查看設置好的運行參數。 變量管理 變量值顯示:print var 變量值修改:set var=value 按指定地址輸出內存值:x /NFU ADDR 其中,U指定一個單元的大小,可以是b(單字節)、h(雙字節)、w(四字節)、g(八字節);N表示輸出的單元個數;F表示輸出的數據形式,可以是x(16進制整數格式 )、d(有符號十進制整數格式)、u(無符號十進制整數格式)、f(浮點數格式 )。 調試過程控制 step:單步執行,進入函數內部; next:單步執行,不進入函數內部; continue:恢復正常連續執行。 信息查詢 info:這個應該是調試時用得最多的信息顯示命令。它的主要作用是顯示正在被調試的程序的一些指定信息,如breakpoints、watchpoints,frame中的args等等。 print:一般用來打印變量的當前值。 show:顯示當前gdb本身的一些參數信息。 list:一般用於源文件的顯示。 Call Stack stack frame:當某個函數被調用的時候,它的調用入口,參數,局部變量都將會被保存在棧中的一塊內存中,這塊內存就叫stack frame。 call stack:所有的stack frames所佔據的空間就叫call stack。最近被調用的函數的stack frame總是位於call stack的最裏面,依次向外擴散:最裏面的stack frame編號爲0. 有了以上兩個概念之後,我們就可以講述gdb調試實現的機制了。gdb工作的基本單元就是stack frame,簡稱frame。比如用print var來打印變量名時,gdb是會在當前選中的frame(默認是當前工作的frame,除非手動選擇別的frame)中尋找並打印該變量。這就是理解gdb工作的關鍵! 以下是一些操作frame並顯示frame信息的命令: frame framlocation:指定gdb工作的frame。其中,framlocation可以是frame的編號,也可以是frame的內存地址。 bt:顯示當前程序Call stack中的函數調用順序,或者說是frame的順序。命令where和info stack和bt是一樣的。 info frame:顯示當前frame的詳細信息。 info args:顯示當前frame所對應的函數的參數信息。 info locals:顯示當前frame所對應的函數的局部變量信息。 執行Shell命令 直接在gdb提示符後輸入 shell commandname 。而如果需要在gdb中直接make源文件的話,直接在gdb提示符後輸入 make make_args 即可;也可輸入shell make make_args。 本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/dexingchen/archive/2008/12/10/3486732.aspx gdb使用 無論是多麼優秀的程序員,都難以保證自己在編寫代碼時不會出現任何錯誤,因此調試是軟件開發過程中的一個必不可少的組成部分。當程序完成編譯之後,它很可能無法正常運行,或者會徹底崩潰,或者不能實現預期的功能。此時如何通過調試找到問題的癥結所在,就變成了擺在開發人員面前最嚴峻的問題。通常說來,軟件項目的規模越大,調試起來就會越困難,越需要一個強大而高效的調試器作爲後盾。對於Linux程序員來講,目前可供使用的調試器非常多,GDB(GNU DeBugger)就是其中較爲優秀的。      初識GDB      GDB是自由軟件基金會(Free Software Foundation,FSF)的軟件工具之一。它的作用是協助程序員找到代碼中的錯誤。如果沒有GDB的幫助,程序員要想跟蹤代碼的執行流程,唯一的辦法就是添加大量的語句來產生特定的輸出。但這一手段本身就可能會引入新的錯誤,從而也就無法對那些導致程序崩潰的錯誤代碼進行分析。GDB的出現減輕了開發人員的負擔,他們可以在程序運行的時候單步跟蹤自己的代碼,或者通過斷點暫時中止程序的執行。此外,他們還能夠隨時察看變量和內存的當前狀態,並監視關鍵的數據結構是如何影響代碼運行的。      調試方法      如果想對程序進行調試,必須先在用GCC編譯源代碼時加上-g選項,以便產生GDB所需要的調試符號信息。例如,debugme.c是一個存在錯誤程序,可以使用如下的命令對其進行編譯,同時產生調試符號:   # gcc -g debugme.c -o debugme      如果願意的話,還可以在編譯時使用“-ggdb”選項來生成更多的調試信息。由於這些調試信息中的相當一部分是GDB所特有的,所以生成的代碼將無法在其它調試器中正常調試。對於大多數情況來說,普通的-g選項就足夠了。需要注意的是,GCC雖然允許同時使用-g(調試)和-o(優化)選項,但優化會影響最終生成的代碼,導致程序源代碼和二進制代碼之間的關係變得複雜起來。如果不想爲調試製造障礙,建議不要將-g和-o選項一同使用,並且只在程序徹底調試完後纔開始進行代碼優化。這樣調試過程將變得相對輕鬆和愉快。      基本應用      現在可以啓動GDB來調試已經生成的可執行程序debugme,命令如下:      # gdb debugme   GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)   ……   (gdb)      如果一切正常,GDB將被啓動並在屏幕上輸出版權信息,但如果使用了-q或--quiet選項則不會顯示它們。啓動GDB時另外一個有用的命令行選項是“-d dirname”,其中dirname是一個目錄名。該目錄名告訴GDB應該到哪裏去尋找源代碼。      一旦出現GDB的命令提示符(gdb),就表明GDB已經準備好接收來自用戶的各種調試命令了。如果想在調試環境下運行這個程序,可以使用GDB提供的“run”命令,而程序在正常運行時所需的各種參數可以作爲“run”命令的參數傳入,或者使用單獨的“set args”命令進行設置。如果在執行“run”命令時沒有給出任何參數,GDB將使用上一次“run”或“set args”命令指定的參數。如果想取消上次設置的參數,可以執行不帶任何參數的“set args”命令。下面嘗試在調試器中運行這個程序:      (gdb) run   ……   Program received signal SIGSEGV, Segmentation fault.   0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2      最後一行輸出表明程序在調用動態鏈接庫/lib/ld-linux.so.2中的_dl_fini()函數時出現了錯誤,地址是0x4000c6ac。這些對調試是非常重要的線索。另外還有一種信息對調試也很重要,就是錯誤發生時的函數調用層級關係,可以通過執行“backtrace”命令來獲得。在使用GDB調試命令時,用戶可以不必輸入完整的命令名稱,使用任何惟一的縮寫都可以。例如“backtrace”命令就可以縮寫成“back”甚至“bt”。GDB還支持很多常用的Shell命令編輯特徵,比如可以像在bash或tcsh中那樣按Tab鍵補齊命令。如果相關命令不惟一的話,則列出所有可能的匹配項。此外鍵盤上的方向鍵可用來翻動歷史命令。      GDB是一個源代碼級的調試器,使用“list”命令可以查看當前調試對象的源代碼。該命令的通用格式爲“list ”,表示顯示從m行開始到n行結束的代碼段,而不帶任何參數的“list”命令將顯示最近10行源代碼。      設置斷點   在調試有問題的代碼時,在某一點停止運行往往很管用。這樣程序運行到此外時會暫時掛起,等待用戶的進一步輸入。GDB允許在幾種不同的代碼結構上設置斷點,包括行號和函數名等,並且還允許設置條件斷點,讓程序只有在滿足一定的條件時才停止執行。要根據行號設置斷點,可以使用“ break linenum”命令。要根據函數名設置斷點,則應該使用“break funcname”命令。      在以上兩種情況中,GDB將在執行指定的行號或進入指定的函數之前停止執行程序。此時可以使用“print”顯示變量的值,或者使用“list”查看將要執行的代碼。對於由多個源文件組成的項目,如果想在執行到非當前源文件的某行或某個函數時停止執行,可以使用如下形式的命令:      # break filename:linenum   # break filename:funcname      條件斷點允許當一定條件滿足時暫時停止程序的執行。它對於調試來講非常有用。設置條件斷點的正確語法如下:      break linenum if expr   break funcname if expr      其中expr是一個邏輯表達式。當該表達式的值爲真時,程序將在該斷點處暫時掛起。例如,下面的命令將在debugme程序的第38行設置一個條件斷點。當程序運行到該行時,如果count的值等於3,就將暫時停止執行:   (gdb) break 38 if count==3      設置斷點是調試程序時最常用到的一種手段。它可以中斷程序的運行,給程序員一個單步跟蹤的機會。使用命令“ break main”在main函數上設置斷點可以在程序啓動時就開始進行跟蹤。      接下去使用“continue”命令繼續執行程序,直到遇到下一個斷點。如果在調試時設置了很多斷點,可以隨時使用“info breakpoints”命令來查看設置的斷點。此外,開發人員還可以使用“delete”命令刪除斷點,或者使用“disable”命令來使設置的斷點暫時無效。被設置爲無效的斷點在需要的時候可以用“enable”命令使其重新生效。      觀察變量   GDB最有用的特性之一是能夠顯示被調試程序中幾乎任何表達式、變量或數組的類型和值,並且能夠用編寫程序所用的語言打印出任何合法表達式的值。查看數據最簡單的辦法是使用“print”命令,只需在“print”命令後面加上變量表達式,就可以打印出此變量表達式的當前值,示例如下:      (gdb) print str   $1 = 0x40015360 "Happy new year!/n"      從輸出信息中可以看出,輸入字符串被正確地存儲在了字符指針str所指向的內存緩衝區中。除了給出變量表達式的值外,“print”命令的輸出信息中還包含變量標號($1)和對應的內存地址(0x40015360)。變量標號保存着被檢查數值的歷史記錄,如果此後還想訪問這些值,就可以直接使用別名而不用重新輸入變量表達式。      如果想知道變量的類型,可以使用“whatis”命令,示例如下:      (gdb) whatis str   type = char *      對於第一次調試別人的代碼,或者面對的是一個異常複雜的系統時,“whatis”命令的作用不容忽視。      單步執行   爲了單步跟蹤代碼,可以使用單步跟蹤命令“step”,它每次執行源代碼中的一行。      在GDB中可以使用許多方法來簡化操作,除了可以將“step”命令簡化爲“s”之外,還可以直接輸入回車鍵來重複執行前面一條命令。      除了可以用“step”命令來單步運行程序之外,GDB還提供了另外一條單步調試命令“next”。兩者功能非常相似,差別在於如果將要被執行的代碼行中包含函數調用,使用step命令將跟蹤進入函數體內,而使用next命令則不進入函數體內。      在進入下一部分之前,使用下面的命令退出GDB:   (gdb) quit      分析核心(core)文件      在程序發生崩潰時,有時可能無法直接運行GDB來進行調試。比如程序可能是在另外一臺機器上運行的,或者因爲程序對時間比較敏感,所以手動跟蹤調試會產生無法接受的延遲等。遇到這些情況,就只能等到程序運行結束後才能判斷崩潰的原因了。這時需要用到Linux提供的core dump機制。當程序中出現內存操作錯誤時,會發生崩潰併產生核心文件。使用GDB可以對產生的核心文件進行分析,找出程序是在什麼時候崩潰的和在崩潰之前程序都做了些什麼。當然,如果要用GDB來分析核心文件,也必須在編譯時加上-g選項來產生調試符號表。      在分析核心文件之前必須確認系統是否允許生成核心文件,很多Linux發行版在默認時禁止生成核心文件。爲了生成核心文件,首先必須執行下面的命令:   # ulimit -c unlimited      然後就可以生成核心文件了。這裏仍以前面的debugme程序爲例,再次執行下面命令將產生核心文件:      # ./debugme   Enter a string to count words:Happy new year!   The number of words is 3.   Segmentation fault (core dumped)      生成的核心文件名根據系統配置的不同會有所差異。要在GDB中分析核心文件,除了要給出核心文件的文件名外,還必須給出生成該核心文件的可執行程序的名稱,示例如下:      #gdb debugme core.547   ……   Program terminated with signal 11, Segmentation fault.   Reading symbols from /lib/libc.so.6...done.   ……      從GDB的輸出信息中可以看出,產生這個核心文件的原因是因爲程序收到了序號爲11的信號。如果想知道程序在崩潰之前運行到了哪裏,可以使用“backtrace”或“info stack”命令查看一下堆棧的歷史記錄。示例如下:      (gdb) info stack   #0 0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2   #1 0x40057940 in exit () from /lib/libc.so.6   #2 0x4004291f in _libc_start_main () from /lib/libc.so.6      由上可知,程序崩潰時正處於_dl_fini()函數之中。但很多時候程序員感興趣的可能並不是這個,而是exit()或_libc_start_main()函數,因爲它們纔可能是問題真正的癥結所在。GDB提供的“frame”命令可以用來在不同的調用上下文中切換。例如下面的命令可以查看exit()函數在執行時的狀況:      (gdb) frame 1   #1 0x40057940 in exit () from /lib/libc.so.6      此外還可以用“up”或“down”命令在不同的函數調用上下文中切換。開發人員使用這三條命令可以很輕鬆地實現調用棧的遍歷。在分析核心文件時,通過將遍歷棧的命令和檢查變量值的“print”命令結合起來,就能夠復原程序運行時的全部景象。      調試其它進程      有時會遇到一種很特殊的調試需求,對當前正在運行的其它進程進行調試。這種情況有可能發生在那些無法直接在調試器中運行的進程身上,例如有的進程只能在系統啓動時運行。另外如果需要對進程產生的子進程進行調試的話,也只能採用這種方式。GDB可以對正在執行的程序進行調度,它允許開發人員中斷程序並查看其狀態,之後還能讓這個程序正常地繼續執行。      GDB提供了兩種方式來調試正在運行的進程:一種是在GDB命令行上指定進程的PID,另一種是在GDB中使用“attach”命令。例如,開發人員可以先啓動debugme程序,讓其開始等待用戶的輸入。示例如下:      #./debugme   Enter a string to count words:      接下去在另一個虛擬控制檯中用下面的命令查出該進程對應的進程號:      # ps -ax | grep debugme   555 pts/1 S 0:00 ./debugme      得到進程的PID後,就可以使用GDB對其進行調試了:      # gdb debugme 555   GNU gd b Red Hat Linux (5.3post-0.20021129.18rh)   Attaching to program: /home/xiaowp/debugme, process 555   Reading symbols from /lib/libc.so.6...done.   ……      在上面的輸出信息中,以Attaching to program開始的行表明GDB已經成功地附加在PID爲555的進程上了。另外一種連接到其它進程的方法是先用file命令加載調試時所需的符號表,然後再通過“attaché”命令進行連接:      (gdb) file /home/xiaowp/debugme   Reading symbols from /home/xiaowp/debugme...done.   (gdb) attach 555   ……      如果想知道程序現在運行到了哪裏,同樣可以使用“backtrace”命令。當然也可以使用“step”命令對程序進行單步調試。      在完成調試之後,不要忘記用detach命令斷開連接,讓被調試的進程可以繼續正常運行:      GDB是Linux下一個最基本的調試器,其功能非常豐富。完整地介紹GDB的功能可能需要幾百頁,本文只涵蓋了GDB的一些最常見的用法。作爲一個合格的Linux程序員,花在GDB上的功夫和時間越多,從調試中獲得的益處就越多。 本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/bing_bing/archive/2010/11/16/6013697.aspx
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章