gdb指令碼編寫
【原文:http://blog.chinaunix.net/uid-21977330-id-3707554.html】
關於本主題的前一篇文章“Fun with strace and GDB”提供了相關基礎知識,介紹瞭如何使用這些工具來探索您的系統並附加到已在執行的程式,以瞭解它們正在做的工作。本文繼續討論該偵錯程式的自定義,以使其使用體驗更加個性化和更加高效。
當 GDB(即 GNU Project Debugger)啟動時,它在當前使用者的主目錄中尋找一個名為 .gdbinit 的檔案;如果該檔案存在,則 GDB 就執行該檔案中的所有命令。通常,該檔案用於簡單的配置命令,如設定所需的預設彙編程式格式(Intel? 或 Motorola)或用於顯示輸入和輸出資料的預設基數(十進位制或十六進位制)。它還可以讀取巨集編碼語言,從而允許實現更強大的自定義。該語言遵循如下基本 格式:
define <command> end document <command> <help text> end |
該命令稱為使用者命令。可以將所有其他標準 GDB 命令與流控制指令結合使用並向其傳遞引數,從而建立一種語言,以允許為正在除錯的特定應用程式而自定義偵錯程式的行為。
從簡單開始並在此基礎上逐步發展始終是個好主意。啟動 xterm,調出您最喜歡的編輯器,讓我們開始建立一個有用的 .gdbinit 檔案吧!偵錯程式產生的輸出可能非常零亂,根據個人偏好,在使用任何可能產生混亂的工具時,許多人都希望能夠清屏。GDB 沒有用於清屏的內建命令,但它可以呼叫 shell 函式;下面的程式碼跳到偵錯程式之外以使用 cls 命令來清除 xterm 控制檯:
define cls shell clear end document cls Clears the screen with a simple command. end |
此定義的上半部分(在 define ... end 動詞所界定的範圍內)構成了在呼叫該命令時所執行的程式碼。
此定義的下半部分(在 document ... end 所界定的範圍內)由 GDB 命令直譯器使用,用於在您鍵入 help cls 時顯示與 cls 命令關聯的文字。
在將該程式碼鍵入 .gdbinit 檔案以後,調出 GDB 並輸入 cls 命令。此時螢幕被清除,您所看到的就只有 GDB 提示符。您的 GDB 自定義之旅已經開始了!
如果輸入 help user 命令,您會看到已在 .gdbinit 檔案中輸入的所有使用者命令的摘要。.gdbinit 使用者定義命令的設計者提供了一個重要特性,您在編寫自己的命令時不應忽略該特性:document ... end 子句。隨著這些命令數量的增加,維護有關命令如何工作的功能文件將變得非常關鍵。
您可能已經遇到過此問題。假設您在若干年前編寫了一些程式碼;後來當您重新處理它(也許是為了修正錯誤或通過新增新特性來修改它)時,您發現自己很難理解您自己的程式碼。優秀的程式設計師習慣將程式碼保持得簡短、簡單和具有良好的文件記錄,以便使其可維護。
適用於程式設計程式碼的一般規則也適用於偵錯程式程式碼。當您在這個最有價值的職業中披荊斬棘地拼搏時,保留仔細的註釋和有良好文件記錄的程式碼將為您帶來豐厚的回報。
人類通過多種方式學習新知識,包括研究其他人已做的工作。初學的汽車工程師首先開啟他們第一輛車的發動機罩,拔出他們的工具,開始拆卸部件以進行清理和研究。此類活動使他們能夠在保持汽車清潔的同時,還了解了汽車引擎是如何工作的。
初學的電腦科學家也沒有什麼不同,因為他們想了解程式究竟是如何工作的——它們如何與動態庫和本機作業系統互動。用於查 看這些程式如何工作的工具就是偵錯程式。計算機程式設計是一項複雜的活動,通過與志趣相投的人群社群交流、提問並獲得答案,新的電腦科學家能夠滿足他們對知識 的需要。
在全球程式設計社群中,始終存在大量渴求知識的人。他們不再滿足於在計算機上執行程式——他們還想知道得更多。他們想知道這些程式是如何執行的,並樂此不疲地使用最適當的可用工具來探索系統的功能:偵錯程式。通過逆向工程(一種在偵錯程式下執行程式並密切注意它們如何完成所做的工作,從而瞭解程式工作原理的方法),您可以從所研究程式的創作者已完成的工作中學到大量的知識。程式設計中涉及的大量底層詳細資訊沒有相關的文件記錄;瞭解它們的唯一方法就是在它們的實際工作中觀察它們。
逆向工程背上了不應有的壞名聲,彷彿那只是黑客和犯罪分子企圖破壞副本保護系統和編寫蠕蟲及病毒來對計算機世界造成損害才 會幹的事情。雖然存在這樣的人,但絕大多數使用偵錯程式和逆向工程來研究程式如何工作的人都是當前和將來的軟體工程師,他們希望並需要知道這些程式是如何工 作的。他們已形成了線上社群以共享他們的知識和發現;抵制該活動是非建設性的,會阻礙電腦科學的未來發展。
本文中定義的許多使用者函式就來自於此類知識渴求者的社群。如果希望瞭解有關他們的更多資訊,建議您研究本文的參考資料部分所提到的網站。
許多 GDB 命令太繁瑣,這是眾所周知的事實。儘管可以對它們進行縮寫,但是 GDB 巨集語言允許實現進一步的簡化。諸如 info breakpoints 這樣的命令可以變得像 bpl 一樣簡單。清單 1 顯示了一組此類簡單和高度有用的斷點別名 使用者命令,您可以將它們新增到不斷增長的 .gdbinit 檔案中。
清單 1:斷點別名命令
define bpl info breakpoints end document bpl List breakpoints end define bp set $SHOW_CONTEXT = 1 break * $arg0 end document bp Set a breakpoint on address Usage: bp addr end define bpc clear $arg0 end document bpc Clear breakpoint at function/address Usage: bpc addr end define bpe enable $arg0 end document bpe Enable breakpoint # Usage: bpe num end define bpd disable $arg0 end document bpd Disable breakpoint # Usage: bpd num end define bpt set $SHOW_CONTEXT = 1 tbreak $arg0 end document bpt Set a temporary breakpoint on address Usage: bpt addr end define bpm set $SHOW_CONTEXT = 1 awatch $arg0 end document bpm Set a read/write breakpoint on address Usage: bpm addr end |
一旦您習慣了使用斷點別名命令,除錯會話就變得更有價值了;這些命令極大地提高了偵錯程式的效率,因為它能使您事半功倍。
使用者定義的 GDB 命令可由其他使用者定義的命令呼叫,從而為各方都帶來更高的效率。這就是程式語言的遞增性質——編寫底層函式,逐漸由更高層的函式呼叫,直到您只需最少的工 作即可讓那些工具方便地完成您想要它們完成的任務。要整合到 .gdbinit 檔案中的下一組 GDB 定義將在程式被呼叫時顯示有用的程式資訊,如清單 2 所示。
清單 2: 程式資訊命令
define argv show args end document argv Print program arguments end define stack info stack end document stack Print call stack end define frame info frame info args info locals end document frame Print stack frame end define flags if (($eflags >> 0xB) & 1 ) printf "O " else printf "o " end if (($eflags >> 0xA) & 1 ) printf "D " else printf "d " end if (($eflags >> 9) & 1 ) printf "I " else printf "i " end if (($eflags >> 8) & 1 ) printf "T " else printf "t " end if (($eflags >> 7) & 1 ) printf "S " else printf "s " end if (($eflags >> 6) & 1 ) printf "Z " else printf "z " end if (($eflags >> 4) & 1 ) printf "A " else printf "a " end if (($eflags >> 2) & 1 ) printf "P " else printf "p " end if ($eflags & 1) printf "C " else printf "c " end printf "\n" end document flags Print flags register end define eflags printf " OF <%d> DF <%d> IF <%d> TF <%d>",\ (($eflags >> 0xB) & 1 ), (($eflags >> 0xA) & 1 ), \ (($eflags >> 9) & 1 ), (($eflags >> 8) & 1 ) printf " SF <%d> ZF <%d> AF <%d> PF <%d> CF <%d>\n",\ (($eflags >> 7) & 1 ), (($eflags >> 6) & 1 ),\ (($eflags >> 4) & 1 ), (($eflags >> 2) & 1 ), ($eflags & 1) printf " ID <%d> VIP <%d> VIF <%d> AC <%d>",\ (($eflags >> 0x15) & 1 ), (($eflags >> 0x14) & 1 ), \ (($eflags >> 0x13) & 1 ), (($eflags >> 0x12) & 1 ) printf " VM <%d> RF <%d> NT <%d> IOPL <%d>\n",\ (($eflags >> 0x11) & 1 ), (($eflags >> 0x10) & 1 ),\ (($eflags >> 0xE) & 1 ), (($eflags >> 0xC) & 3 ) end document eflags Print entire eflags register end define reg printf " eax:%08X ebx:%08X ecx:%08X ", $eax, $ebx, $ecx printf " edx:%08X eflags:%08X\n", $edx, $eflags printf " esi:%08X edi:%08X esp:%08X ", $esi, $edi, $esp printf " ebp:%08X eip:%08X\n", $ebp, $eip printf " cs:%04X ds:%04X es:%04X", $cs, $ds, $es printf " fs:%04X gs:%04X ss:%04X ", $fs, $gs, $ss flags end document reg Print CPU registers end define func info functions end document func Print functions in target end define var info variables end document var Print variables (symbols) in target end define lib info sharedlibrary end document lib Print shared libraries linked to target end define sig info signals end document sig Print signal actions for target end define thread info threads end document thread Print threads in target end define u info udot end document u Print kernel 'user' struct for target end define dis disassemble $arg0 end document dis Disassemble address Usage: dis addr end |
要整合進 .gdbinit 檔案中的下一組定義包括增強的十六進位制和 ASCII 轉儲函式,如清單 3 所示。程式設計師注意:若想建立卓越的軟體,則應新增巨集程式設計功能,從而允許使用者社群能夠增強您的工具以適應他們自己的偏好。GDB 就是一個卓越的軟體!
清單 3: 十六進位制和 ASCII 轉儲命令
define ascii_char set $_c=*(unsigned char *)($arg0) if ( $_c < 0x20 || $_c > 0x7E ) printf "." else printf "%c", $_c end end document ascii_char Print the ASCII value of arg0 or '.' if value is unprintable end define hex_quad printf "%02X %02X %02X %02X %02X %02X %02X %02X", \ *(unsigned char*)($arg0), *(unsigned char*)($arg0 + 1), \ *(unsigned char*)($arg0 + 2), *(unsigned char*)($arg0 + 3), \ *(unsigned char*)($arg0 + 4), *(unsigned char*)($arg0 + 5), \ *(unsigned char*)($arg0 + 6), *(unsigned char*)($arg0 + 7) end document hex_quad Print eight hexadecimal bytes starting at arg0 end define hexdump printf "%08X : ", $arg0 hex_quad $arg0 printf " - " hex_quad ($arg0+8) printf " " ascii_char ($arg0) ascii_char ($arg0+1) ascii_char ($arg0+2) ascii_char ($arg0+3) ascii_char ($arg0+4) ascii_char ($arg0+5) ascii_char ($arg0+6) ascii_char ($arg0+7) ascii_char ($arg0+8) ascii_char ($arg0+9) ascii_char ($arg0+0xA) ascii_char ($arg0+0xB) ascii_char ($arg0+0xC) ascii_char ($arg0+0xD) ascii_char ($arg0+0xE) ascii_char ($arg0+0xF) printf "\n" end document hexdump Display a 16-byte hex/ASCII dump of arg0 end define ddump printf "[%04X:%08X]------------------------", $ds, $data_addr printf "---------------------------------[ data]\n" set $_count=0 while ( $_count < $arg0 ) set $_i=($_count*0x10) hexdump ($data_addr+$_i) set $_count++ end end document ddump Display $arg0 lines of hexdump for address $data_addr end define dd if ( ($arg0 & 0x40000000) || ($arg0 & 0x08000000) || ($arg0 & 0xBF000000) ) set $data_addr=$arg0 ddump 0x10 else printf "Invalid address: %08X\n", $arg0 end end document dd Display 16 lines of a hex dump for $arg0 end define datawin if ( ($esi & 0x40000000) || ($esi & 0x08000000) || ($esi & 0xBF000000) ) set $data_addr=$esi else if ( ($edi & 0x40000000) || ($edi & 0x08000000) || ($edi & 0xBF000000) ) set $data_addr=$edi else if ( ($eax & 0x40000000) || ($eax & 0x08000000) || \ ($eax & 0xBF000000) ) set $data_addr=$eax else set $data_addr=$esp end end end ddump 2 end document datawin Display esi, edi, eax, or esp in the data window end |
最後,當您除錯正在執行的程式時,獲得程式上下文的總體檢視通常是必要的。清單 4 中有用的程式上下文命令是使用前面定義的資料轉儲函式來構建的。
清單 4: 程式上下文命令
define context printf "_______________________________________" printf "________________________________________\n" reg printf "[%04X:%08X]------------------------", $ss, $esp printf "---------------------------------[stack]\n" hexdump $sp+0x30 hexdump $sp+0x20 hexdump $sp+0x10 hexdump $sp datawin printf "[%04X:%08X]------------------------", $cs, $eip printf "---------------------------------[ code]\n" x /6i $pc printf "---------------------------------------" printf "---------------------------------------\n" end document context Print regs, stack, ds:esi, and disassemble cs:eip end define context-on set $SHOW_CONTEXT = 1 end document context-on Enable display of context on every program stop end define context-off set $SHOW_CONTEXT = 1 end document context-on Disable display of context on every program stop end # Calls "context" at every breakpoint. define hook-stop context end # Init parameters set output-radix 0x10 set input-radix 0x10 set disassembly-flavor intel |
hook-stop 是 GDB 在每次發生斷點事件時呼叫的特殊定義。此例中生成了 context 清單,以便您能清楚看到處理器執行每條指令的結果。
讓我們試驗一下這組新工具,以瞭解它們在除錯我們的“老朋友”(由 IBM developerWorks 供稿作家 Nigel Griffiths 編寫的 nweb 伺服器程式碼)時的工作情況。(請參見參考資料部分以獲得指向 Nigel 的文章“nweb: a tiny, safe Web server (static pages only)”的連結。)
在將 es-nweb.zip 檔案下載到 $HOME/downloads 目錄後,鍵入如下命令以提取、編譯和執行 nweb。(請注意,這裡假設您是將該程式編譯到中央處理單元 (CPU) 為 Intel Pentium 的 Linux? 工作站——該 .gdbinit 程式碼是僅為 Intel Pentium 型別的處理器和相容處理器而編寫的。)
$ cd src $ mkdir nweb $ cd nweb $ unzip $HOME/downloads/es-nweb.zip $ gcc -ggdb -O -DLINUX nweb.c -o nweb $ ./nweb 9090 $HOME/src/nweb & |
注意:此示例中的 -ggdb 選項與 Nigel 文章中所述的選項不同,因為它告訴 GNU 編譯器集 (GCC) 優化該程式,以便於使用 GDB 來進行除錯。
接下來,為驗證 nweb 伺服器正在執行,可使用 ps 命令來對它進行檢查。
$ ps PID TTY TIME CMD 2913 pts/5 00:00:00 bash 4009 pts/5 00:00:00 nweb 4011 pts/5 00:00:00 ps |
最後,在您的計算機上啟動 Web 瀏覽器,並在位址列鍵入:http://localhost:9090。
下一步是啟動 GDB,並與以前一樣附加到當前執行的 nweb 例項,如 清單 5 所示。
清單 5:執行 GDB
$ gdb --quiet (gdb) attach 4009 Attaching to process 4009 Reading symbols from /home/bill/src/nweb/nweb...done. Reading symbols from /lib/tls/libc.so.6...done. Loaded symbols for /lib/tls/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 _______________________________________________________________________________ eax:FFFFFE00 ebx:00000005 ecx:BFFFF680 edx:00000001 eflags:00000246 esi:00000005 edi:00000000 esp:BFFFF66C ebp:BFFFF6A8 eip:FFFFE410 cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I t s Z a P c [007B:BFFFF66C]---------------------------------------------------------[stack] BFFFF69C : 14 0A 13 42 60 53 01 40 - 24 8F 04 08 C8 F6 FF BF ...B`S.@$....... BFFFF68C : A6 8E 04 08 14 0A 13 42 - 70 C6 00 40 10 00 00 00 .......Bp..@.... BFFFF67C : 82 8E 04 08 00 00 00 00 - C4 C6 04 08 98 F6 FF BF ................ BFFFF66C : A8 F6 FF BF 01 00 00 00 - 80 F6 FF BF 81 EA 0D 42 ...............B [007B:FFFFFE00]---------------------------------------------------------[ data] FFFFFE00 : Error while running hook_stop: Cannot access memory at address 0xfffffe00 0xffffe410 in ?? () (gdb) |
-quiet 選項告訴 GDB 偵錯程式僅顯示其提示符,而不要顯示所有其他通常顯示的啟動資訊。如果需要顯示額外的文字資訊,可以去掉 -quiet 選項。
attach 4009 命令開始對當前正在執行的 nweb 伺服器的除錯,並且 GDB 偵錯程式通過讀取有關該程式的所有符號資訊來做出同樣方式的響應。
您將會注意到 context 程式碼執行並顯示大量有關當前程式的有用資訊,但它不能訪問資料段中的記憶體。這不是個嚴重問題,並且應該忽略它。有時,保護模式處理器的保護方案不允許您看到您可能希望看到的所有內容。在此情況下,該問題並不重要。
下一步,使用 info 命令來列出有關您所研究的程式的資訊(請參見清單 6)。
清單 6:info 命令列出程式資訊
(gdb) info proc process 4009 cmdline = './nweb' cwd = '/home/bill/src/nweb' exe = '/home/bill/src/nweb/nweb' (gdb) |
由於您所觀察的是一個實際執行的程式,所以可以設定相應的斷點,然後在它響應瀏覽器請求並向發出請求的瀏覽器傳輸 .html 和 .jpg 檔案時,對該程式進行觀察。清單 7 表明瞭如何完成該任務。
清單 7:設定斷點
(gdb) b 188 Breakpoint 1 at 0x8048e70: file nweb.c, line 188. (gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >continue >end (gdb) c Continuing. |
此時,GDB 除錯工具已設定為在 nweb 伺服器接受 瀏覽器請求時所在的行中斷,偵錯程式將簡單地顯示請求並繼續處理其他請求,而不會中斷正在執行的程式。重新整理幾次瀏覽器中的 http://localhost:9090/ 頁面,可以觀察到,GDB 偵錯程式顯示了斷點並繼續執行。
在重新整理瀏覽器頁面的同時,您應該看到如清單 8 所示的斷點資訊,在 GDB 偵錯程式 xterm 中滾動輸出。還可以通過按 Ctrl+C 來停止在 nweb 伺服器中的除錯。停止跟蹤以後,可以通過鍵入 quit 命令來退出 GDB 偵錯程式。
清單 8: GDB 偵錯程式 xterm 中的斷點資訊
_______________________________________________________________________________ eax:00000000 ebx:00000001 ecx:00000000 edx:00000001 eflags:00000206 esi:00000006 edi:00000000 esp:BFFFF690 ebp:BFFFF6A8 eip:08048E70 cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I t s z a P c [007B:BFFFF690]---------------------------------------------------------[stack] BFFFF6C0 : 03 00 00 00 D4 86 04 08 - 00 00 00 00 F5 86 04 08 ................ BFFFF6B0 : 03 00 00 00 F4 F6 FF BF - 04 F7 FF BF 2C 58 01 40 ............,X.@ BFFFF6A0 : 60 53 01 40 24 8F 04 08 - C8 F6 FF BF 04 55 01 42 `S.@$........U.B BFFFF690 : 14 0A 13 42 70 C6 00 40 - 10 00 00 00 14 0A 13 42 ...Bp..@.......B [007B:BFFFF690]---------------------------------------------------------[ data] BFFFF690 : 14 0A 13 42 70 C6 00 40 - 10 00 00 00 14 0A 13 42 ...Bp..@.......B BFFFF6A0 : 60 53 01 40 24 8F 04 08 - C8 F6 FF BF 04 55 01 42 `S.@$........U.B [0073:08048E70]---------------------------------------------------------[ code] 0x8048e70 <main+718>: sub esp,0x4 0x8048e73 <main+721>: lea eax,[ebp-16] 0x8048e76 <main+724>: push eax 0x8048e77 <main+725>: push 0x804c6c4 0x8048e7c <main+730>: push edi 0x8048e7d <main+731>: call 0x80485e4 <accept> ------------------------------------------------------------------------------ Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188 188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) Program received signal SIGINT, Interrupt. 0xffffe410 in ?? () (gdb) quit The program is running. Quit anyway (and detach it)? (y or n) y Detaching from program: /home/bill/src/nweb/nweb, process 4009 $ |
可以看到,context 函式所顯示的資訊遠比您通常使用預設 GDB hook_stop 函式所看到的資訊更詳細。(您還會注意到,現在也可以訪問資料段了。)使用這些 GDB 增強,您可以看到每次到達斷點和執行每步操作時的確切 CPU 狀態。單步執行每個命令並觀察暫存器和記憶體值如何受影響,這也是學習 Intel 機器語言命令基礎知識的理想方法。
與所有程式一樣,.gdbinit 檔案中的程式碼提供了無窮無盡的增強和改進機會。無論如何,這都不是結束!強烈建議您使用這裡描述的命令,併為不斷增長的 .gdbinit 自定義集新增更多命令。在您研究和使用這些工具時,請與更廣泛的社群分享它們,以使每個人都能增長知識。
應該有更多的人來從事有關程式設計工具如何工作的深入研究,各抒己見、博採眾長,為尋求此類知識的整個使用者社群作出貢獻。訪問一些此類線上社群,甚至考慮組建您自己的社群,以便能夠更快地實現將來的技術創新。
相關文章
- Shell 指令碼編寫指令碼
- gdb的指令碼使用 -- gdbinit指令碼
- EA指令碼編寫要點指令碼
- 編寫執行R指令碼指令碼
- 編譯FFMPEG原始碼的指令碼編寫案例編譯原始碼指令碼
- 編寫shell指令碼的規範指令碼
- 如何編寫高效的 Shell 指令碼指令碼
- 如何使用zx編寫shell指令碼指令碼
- systemd 編寫服務管理指令碼指令碼
- Mac 編寫oracle 連線指令碼MacOracle指令碼
- Linux 指令碼編寫基礎Linux指令碼
- Linux指令碼編寫基礎Linux指令碼
- nGrinder中快速編寫groovy指令碼01-指令碼結構指令碼
- 從零開始編寫指令碼引擎指令碼
- 技能篇:shell教程及指令碼編寫指令碼
- scala入門之編寫scala指令碼指令碼
- 專案啟動指令碼的編寫指令碼
- shell 指令碼如何編寫-致初學者指令碼
- shell編寫服務啟動指令碼指令碼
- isql指令碼編寫建立資料庫SQL指令碼資料庫
- Linux指令碼編寫基礎(五)Linux指令碼
- Linux 指令碼編寫基礎(轉)Linux指令碼
- GDB除錯指令除錯
- awk命令和指令碼的編寫啟蒙指令碼
- 如何編寫冪等的 Bash 指令碼?- Arslan指令碼
- kernel 4.4.12 外部模組Makefile 指令碼編寫指令碼
- 編寫安裝配置mail服務指令碼AI指令碼
- 使用Lua編寫可嵌入式指令碼指令碼
- 如何用 JMeter 編寫效能測試指令碼?JMeter指令碼
- Ollydbg 編寫指令碼的一些語法及例子(OD指令碼)指令碼
- 編寫自己的Acunetix WVS漏洞指令碼指令碼
- Linux編寫Bash指令碼的10個技巧Linux指令碼
- 從0到1編寫一個指令碼引擎指令碼
- Linux之rsync同步分發指令碼編寫.Linux指令碼
- 編寫可靠 shell 指令碼的 8 個建議指令碼
- DBA日常維護SQL指令碼_自己編寫的SQL指令碼
- 編寫更好 Bash 指令碼的 8 個建議指令碼
- 透過 Prometheus 編寫 TiDB 巡檢指令碼(指令碼已開源,內附連結)PrometheusTiDB指令碼