使用 Strace 和 GDB 調試工具的樂趣
編寫 UNIX® 系統程序充滿樂趣,并且具有教育意義。使用 UNIX strace 工具和 GDB(GNU 項目調試工具),您可以真正地深入研究系統的功能,并了解組成這些功能的各種各樣的程序。同時使用這兩種工具,能夠在查看 UNIX 計算機底層信息 的時候,給您帶來更好的體驗。
UNIX 家族總是為用戶提供了豐富的工具。UNIX 是一個工具財寶箱,有了這些工具,您不僅可以完成具有創造性的工作,還可以在深入研究該操作系統的同時得到教育和娛樂。strace(用來跟蹤任何程序的系統調用)和 GDB 調試工具(用來在受控的環境中運行程序的功能齊全的調試工具)是實現這個目標的兩個有價值的工具。
UNIX 的設計由大量的函數調用(稱為系統調用)組成,其中包括一些簡單的任務,如在屏幕上顯示字符串來設置任務優先級。所有的 UNIX 程序都是通過調用操作系統提供的這些底層服務來完成它們的任務,使用 strace 工具,您可以清楚地看到這些調用過程及其使用的參數。通過這種方式,您可以操作這些程序,以了解它們與操作系統之間的底層交互。
開始游戲
讓我們以一個簡單的 UNIX 命令 pwd
作為開始,然后更深入地研究該命令在完成其任務的過程中進行了哪些工作。啟動 xterm 以創建一個進行實驗的受控環境,然后輸入下面的命令:
$ pwd
這個 pwd
命令顯示了當前的工作目錄。在我的計算機上,當時的輸出是:
/home/bill/
一個如此簡單的函數掩飾了該命令底層的復雜性(順便說一下,所有的計算機程序都是這樣的)。要真正地了解其復雜性,請使用 strace 工具再次運行 pwd
命令:
$ strace pwd
通過該命令,您可以看到,在顯示和列舉當前工作目錄的過程中,UNIX 計算機執行了相當多的操作(請參見清單 1)。
清單 1:strace pwd 命令的輸出
execve("/bin/pwd", ["pwd"], [/* 39 vars */]) = 0 uname({sys="Linux", node="sammy", ...}) = 0 brk(0) = 0x804c000 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4001... . . . fstat64(3, {st_mode=S_IFREG|0644, st_size=115031, ...}) = 0 old_mmap(NULL, 115031, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40017000 close(3) = 0 open("/lib/tls/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360U\1"..., 1024) = 1024 fstat64(3, {st_mode=S_IFREG|0755, st_size=1547996, ...}) = 0 old_mmap(0x42000000, 1257224, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x42000000 mprotect(0x4212e000, 20232, PROT_NONE) = 0 old_mmap(0x4212e000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x12e000)... old_mmap(0x42131000, 7944, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,... close(3) = 0 set_thread_area({entry_number:-1 -> 6, base_addr:0x40016ac0, limit:1048575, seg_32bit... munmap(0x40017000, 115031) = 0 brk(0) = 0x804c000 brk(0x804d000) = 0x804d000 brk(0) = 0x804d000 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=30301680, ...}) = 0 mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40017000 close(3) = 0 brk(0) = 0x804d000 brk(0x804e000) = 0x804e000 getcwd("/home/bill", 4096) = 11 fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 6), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4021700... write(1, "/home/bill\n", 11/home/bill ) = 11 munmap(0x40217000, 4096) = 0 exit_group(0) = ?
UNIX 系統調用的具體細節
關于檢索和顯示當前工作目錄所需的所有系統調用的詳細細節,已經超出了本文的討論范圍,但我會介紹如何獲取這些信息。清單 1 中的每一行都以類似 C
的格式,清楚地說明了一項系統調用及其參數,這也是 C
程序員希望看到的。Strace 同樣以這種方式顯示這些調用,不管在實際創建該程序時使用的是何種編程語言。
如果要了解清單 1 中的所有細節信息,對于所有這些系統調用,UNIX 為您提供了大量的文檔。清單 1 中“最重要”的函數是 getcwd()
函數,它表示獲取當前工作目錄。當前的 xterm 顯示了 strace pwd
的輸出,同時再啟動另一個 xterm 并輸入下面的命令以查看 UNIX 對該函數的顯示:
$ man getcwd
您所看到的應該是 getcwd()
函數完整的清單以及這個重要的 C
函數需要的和返回的參數清單。同樣地,您可以輸入 man brk
或 man fstat64
等等。通常,UNIX 系統通過文檔對這些系統函數進行了詳細的說明,如果花些時間仔細地研究它們,您將逐漸地了解到 UNIX 的功能是多么的強大,以及學習這些底層系統細節是多么的容易。在所有的操作系統中,UNIX 最善于幫助您理解其底層的處理過程。
觀察 nweb
對于下面幾個步驟,您需要使用更龐大且更復雜的程序,而不是像 pwd
這樣簡單的 UNIX 命令。簡單的超文本傳輸協議 (HTTP) 服務器,如 nweb,是非常適合的。當您在 Internet 上沖浪 的時候,HTTP 服務器偵聽瀏覽器請求,然后通過發送所請求的對象,如 Web 頁面和圖形文件,以此響應瀏覽器的請求。
下載并安裝 nweb,該軟件由 IBM developerWorks 投稿作家 Nigel Griffiths 編寫。 (請參閱參考資料部分提供的 Nigel 的文章“nweb: ?"a tiny, safe Web server (static pages only)”(developerWorks,2004 年 6 月)的鏈接。)
下載 es-nweb.zip 到 $HOME/downloads 目錄,然后輸入清單 2 中所示的簡單命令,以提取、編譯并啟動該程序:
注意:我假設您需要為 Linux® 工作站編譯這個程序。如果實際情況并非如此,那么有關在其他的 UNIX 操作系統上對該程序進行編譯的詳細信息,請閱讀這篇 nweb 文章。
清單 2. 用于提取、編譯和啟動 nweb 的命令
$ 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 &
注意:清單 2 中的 -ggdb
選項和 Nigel 的文章中的內容有些不同,該選項用于告訴 GCC 編譯器對該程序進行優化,以便使用 GDB 調試工具對其進行調試,您將在以后用到該調試工具。
接下來,要確認 nweb 服務器已經運行,可以使用清單 3 中所示的 ps
命令來對它進行檢查。
清單 3. 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
最后,要確認 nweb 確實正在運行并且狀態正常,可以在您的計算機上啟動一個 Web 瀏覽器并在地址欄中輸入 http://localhost:9090
。
針對 nweb 使用 strace
現在,讓我們來進行一些有趣的工作。啟動另一個 xterm,然后使用 strace 來跟蹤正在運行的 nweb 服務器。要完成該任務,您必須清楚該程序的進程 ID,并且必須具有適當的權限。您僅僅可以看到一組特定的系統調用,即那些與網絡相關的系統調用。輸入清單 4 第一行所示的命令作為開始,其中使用了前面顯示的 nweb 的進程 ID。您應該看到如下的輸出(清單 4 中的第二行)。
清單 4. 開始對 nweb 進行跟蹤
$ strace -e trace=network -p 4009 accept(0,
請注意,在調用網絡 accept()
函數的過程中停止了跟蹤操作。在瀏覽器中刷新幾次 http://localhost:9090
頁面,請注意每次刷新該頁面時 strace 的顯示。這是不是很棒呢?您所看到的是,當 Web 瀏覽器調用 HTTP 服務器 (nweb
) 時,服務器所進行的底層網絡調用。簡單地說,nweb
正在接受 來自您的瀏覽器的調用。
您可以在運行 strace 的具有窗口焦點的 xterm 中按下 Ctrl+C 以停止對網絡調用的跟蹤。
再來研究 GDB 調試工具
正如您所看到的,strace 可以作為了解用戶程序如何通過某些系統調用與操作系統進行交互的一個很好的程序。GDB 調試工具本身也可以附加于一個正在運行的進程,并幫助您進行更深入的研究。
GDB 調試工具非常有用,Internet 上提供了大量的有關該工具的可用信息。通常,調試工具是很有價值的工具,并且任何負責開發和維護計算機系統的人員應該了解如何使用它們。因此,在 nweb 運行于另一個 xterm 會話的同時,按下 Ctrl+C 停止 strace,然后輸入清單 5 中所示的命令啟動 GDB 調試工具。
清單 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 0xffffe410 in ?? () (gdb)
-quiet
選項告訴 GDB 調試工具僅顯示其提示符,而不要顯示所有其他的啟動信息。如果需要顯示額外的文本信息,可以去掉 -quiet
選項。
attach 4009
命令啟動對當前正在運行的 nweb 服務器的調試工作,并且 GDB 調試工具通過讀取有關該進程的所有的符號信息來做出同樣方式的響應。下一步,使用 info
命令來列舉您所研究的程序的相關信息(請參見清單 6)。
清單 6. info 命令列出程序信息
(gdb) info proc process 4009 cmdline = './nweb' cwd = '/home/bill/src/nweb' exe = '/home/bill/src/nweb/nweb' (gdb)
info
命令(請參見清單 7)的另一個有用的變種是 info functions
,然而,函數的列表可能很長。
清單 7. info functions 命令得到的函數列表
(gdb) info functions All defined functions: File nweb.c: void log(int, char *, char *, int); int main(int, char **); void web(int, int); File __finite: int __finite(); . . . (gdb)
因為使用 -ggdb
選項對 nweb 程序進行了編譯,所以可執行文件中包含了大量的調試信息,允許該調試工具查看文件中列舉的已定義的函數,如清單 7 所示。
list 和 disassemble 命令
有兩個重要的 GDB 調試工具命令,它們分別是 list
和 disassemble
。通過使用清單 8 中所示的代碼嘗試使用這些命令。
清單 8. list 命令
(gdb) list main 121 exit(1); 122 } 123 124 125 main(int argc, char **argv) 126 { 127 int i, port, pid, listenfd, socketfd, hit; 128 size_t length; 129 char *str; 130 static struct sockaddr_in cli_addr; /* static = initialised to zeros */ (gdb) 131 static struct sockaddr_in serv_addr; /* static = initialised to zeros */ 132 133 if( argc < 3 || argc > 3 || !strcmp(argv[1], "-?") ) { 134 (void)printf("hint: nweb Port-Number Top-Directory\n\n" 135 "\tnweb is a small and very safe mini web server\n" 136 "\tnweb only servers out file/web pages with extensions named below\n" 137 "\t and only from the named directory or its sub-directories.\n" 138 "\tThere is no fancy features = safe and secure.\n\n" 139 "\tExample: nweb 8181 /home/nwebdir &\n\n" 140 "\tOnly Supports:");
正如您所看到的,list
命令以源文件的形式列出了正在運行的程序,并標注了相應的行號。按下 Return 鍵(如第 130 行和第 131 行之間所示)以接著上次的列表繼續進行列舉。現在,嘗試使用 disassemble
命令,可以縮寫為 disass
(請參見清單 9)。
清單 9. disassemble 命令
(gdb) disass main Dump of assembler code for function main: 0x08048ba2 <main+0>: push ebp 0x08048ba3 <main+1>: mov ebp,esp 0x08048ba5 <main+3>: push edi 0x08048ba6 <main+4>: push esi 0x08048ba7 <main+5>: push ebx 0x08048ba8 <main+6>: sub esp,0xc 0x08048bab <main+9>: mov ebx,DWORD PTR [ebp+12] 0x08048bae <main+12>: and esp,0xfffffff0 . . . 0x08048c01 <main+95>: call 0x8048664 <printf> 0x08048c06 <main+100>: add esp,0x10 0x08048c09 <main+103>: inc esi 0x08048c0a <main+104>: cmp DWORD PTR [ebx+esi],0x0 ---Type <return> to continue, or q <return> to quit---
反匯編清單顯示了該 main
函數的匯編語言清單。在本示例中,匯編代碼指示出運行該代碼的計算機使用的是 Intel® Pentium® 處理器。如果該程序運行于不同類型的處理器上,如基于 IBM Power PC® 的計算機,那么您的代碼看上去將有很大的區別。
在其運行的過程中進行監視
因為您所監視的是一個正在運行的程序,所以可以設置相應的斷點,然后在它響應瀏覽器請求并向提出請求的瀏覽器傳輸 .html 和 .jpg 文件的同時,對該程序進行監視。清單 10 介紹了如何完成該任務。
清單 10. 設置斷點
(gdb) break 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 調試工具顯示了斷點并繼續運行。
在刷新瀏覽器頁面的同時,您應該看到如清單 11 所示的斷點信息,在 GDB 調試工具 xterm 中滾動輸出。與 strace 相同,您可以按下 Ctrl+C 來停止對 nweb 服務器的調試。在停止了跟蹤操作之后,您可以輸入 quit
命令以退出 GDB 調試工具。
清單 11. GDB 調試工具 xterm 中的斷點信息
Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188 188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188 188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188 188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) 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 $
請注意,您正告訴 GDB 調試工具停止對一個仍在內存中活動的程序的調試。即使是在退出了調試工具之后,您還可以刷新瀏覽器頁面,并將看到 nweb 仍在運行。可以輸入 kill 4009
命令來停止該程序,或者在您退出會話時,該頁面將會消失。
和平常一樣,您可以通過其 man 和 info 頁面來了解各種各樣的工具,如 strace 和 GDB 調試工具。請確保使用 UNIX 為您提供的這些工具!
了解盡可能多的信息
了解關于您所使用的計算機的盡可能多的信息,絕不是件壞事,并且還可以從這個過程中獲得樂趣。實際上,UNIX 通過提供各種工具,如 strace 和 GDB 調試工具以及包含在相應的 man 和 info 頁面中的大量的信息,鼓勵您對系統進行研究和學習。計算機是人類智慧的延伸,并且我們對其了解得越多,它們將變得越有用。
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成