JFS 核心小組成員,IBM
2002 年 8 月
您可以用各種方法來(lái)監控運行著(zhù)的用戶(hù)空間程序:可以為其運行調試器并單步調試該程序,添加打印語(yǔ)句,或者添加工具來(lái)分析程序。本文描述了幾種可以用來(lái)調試在 Linux 上運行的程序的方法。我們將回顧四種調試問(wèn)題的情況,這些問(wèn)題包括段錯誤,內存溢出和泄漏,還有掛起。
本文討論了四種調試 Linux 程序的情況。在第 1 種情況中,我們使用了兩個(gè)有內存分配問(wèn)題的樣本程序,使用 MEMWATCH 和 Yet Another Malloc Debugger(YAMD)工具來(lái)調試它們。在第 2 種情況中,我們使用了 Linux 中的 strace 實(shí)用程序,它能夠跟蹤系統調用和信號,從而找出程序發(fā)生錯誤的地方。在第 3 種情況中,我們使用 Linux 內核的 Oops 功能來(lái)解決程序的段錯誤,并向您展示如何設置內核源代碼級調試器(kernel source level debugger,kgdb),以使用 GNU 調試器(GNU debugger,gdb)來(lái)解決相同的問(wèn)題;kgdb 程序是使用串行連接的 Linux 內核遠程 gdb。在第 4 種情況中,我們使用 Linux 上提供的魔術(shù)鍵控順序(magic key sequence)來(lái)顯示引發(fā)掛起問(wèn)題的組件的信息。
常見(jiàn)調試方法
當您的程序中包含錯誤時(shí),很可能在代碼中某處有一個(gè)條件,您認為它為真(true),但實(shí)際上是假(false)。找出錯誤的過(guò)程也就是在找出錯誤后推翻以前一直確信為真的某個(gè)條件過(guò)程。
以下幾個(gè)示例是您可能確信成立的條件的一些類(lèi)型:
if-then-else 語(yǔ)句,if 部分就是被執行的路徑。
找出錯誤也就是要確定上述所有情況是否存在。如果您確信在子例程被調用時(shí)某變量應該有特定的值,那么就檢查一下情況是否如此。如果您相信 if 結構會(huì )被執行,那么也檢查一下情況是否如此。通常,您的假設都會(huì )是正確的,但最終您會(huì )找到與假設不符的情況。結果,您就會(huì )找出發(fā)生錯誤的地方。
調試是您無(wú)法逃避的任務(wù)。進(jìn)行調試有很多種方法,比如將消息打印到屏幕上、使用調試器,或只是考慮程序執行的情況并仔細地揣摩問(wèn)題所在。
在修正問(wèn)題之前,您必須找出它的源頭。舉例來(lái)說(shuō),對于段錯誤,您需要了解段錯誤發(fā)生在代碼的哪一行。一旦您發(fā)現了代碼中出錯的行,請確定該方法中變量的值、方法被調用的方式以及關(guān)于錯誤如何發(fā)生的詳細情況。使用調試器將使找出所有這些信息變得很簡(jiǎn)單。如果沒(méi)有調試器可用,您還可以使用其它的工具。(請注意,產(chǎn)品環(huán)境中可能并不提供調試器,而且 Linux 內核沒(méi)有內建的調試器。)
| 實(shí)用的內存和內核工具 您可以使用 Linux 上的調試工具,通過(guò)各種方式跟蹤用戶(hù)空間和內核問(wèn)題。請使用下面的工具和技術(shù)來(lái)構建和調試您的源代碼: 用戶(hù)空間工具:
內核工具:
|
本文將討論一類(lèi)通過(guò)人工檢查代碼不容易找到的問(wèn)題,而且此類(lèi)問(wèn)題只在很少見(jiàn)的情況下存在。內存錯誤通常在多種情況同時(shí)存在時(shí)出現,而且您有時(shí)只能在部署程序之后才能發(fā)現內存錯誤。
第 1 種情況:內存調試工具
C 語(yǔ)言作為 Linux 系統上標準的編程語(yǔ)言給予了我們對動(dòng)態(tài)內存分配很大的控制權。然而,這種自由可能會(huì )導致嚴重的內存管理問(wèn)題,而這些問(wèn)題可能導致程序崩潰或隨時(shí)間的推移導致性能降級。
內存泄漏(即 malloc() 內存在對應的 free() 調用執行后永不被釋放)和緩沖區溢出(例如對以前分配到某數組的內存進(jìn)行寫(xiě)操作)是一些常見(jiàn)的問(wèn)題,它們可能很難檢測到。這一部分將討論幾個(gè)調試工具,它們極大地簡(jiǎn)化了檢測和找出內存問(wèn)題的過(guò)程。
MEMWATCH
MEMWATCH 由 Johan Lindh 編寫(xiě),是一個(gè)開(kāi)放源代碼 C 語(yǔ)言?xún)却驽e誤檢測工具,您可以自己下載它(請參閱本文后面部分的參考資料)。只要在代碼中添加一個(gè)頭文件并在 gcc 語(yǔ)句中定義了 MEMWATCH 之后,您就可以跟蹤程序中的內存泄漏和錯誤了。MEMWATCH 支持 ANSI C,它提供結果日志紀錄,能檢測雙重釋放(double-free)、錯誤釋放(erroneous free)、沒(méi)有釋放的內存(unfreed memory)、溢出和下溢等等。
|
清單 1 中的代碼將分配兩個(gè) 512 字節的內存塊,然后指向第一個(gè)內存塊的指針被設定為指向第二個(gè)內存塊。結果,第二個(gè)內存塊的地址丟失,從而產(chǎn)生了內存泄漏。
現在我們編譯清單 1 的 memwatch.c。下面是一個(gè) makefile 示例:
test1
|
當您運行 test1 程序后,它會(huì )生成一個(gè)關(guān)于泄漏的內存的報告。清單 2 展示了示例 memwatch.log 輸出文件。
清單 2. test1 memwatch.log 文件
|
MEMWATCH 為您顯示真正導致問(wèn)題的行。如果您釋放一個(gè)已經(jīng)釋放過(guò)的指針,它會(huì )告訴您。對于沒(méi)有釋放的內存也一樣。日志結尾部分顯示統計信息,包括泄漏了多少內存,使用了多少內存,以及總共分配了多少內存。
YAMD
YAMD 軟件包由 Nate Eldredge 編寫(xiě),可以查找 C 和 C++ 中動(dòng)態(tài)的、與內存分配有關(guān)的問(wèn)題。在撰寫(xiě)本文時(shí),YAMD 的最新版本為 0.32。請下載 yamd-0.32.tar.gz(請參閱參考資料)。執行 make 命令來(lái)構建程序;然后執行 make install 命令安裝程序并設置工具。
一旦您下載了 YAMD 之后,請在 test1.c 上使用它。請刪除 #include memwatch.h 并對 makefile 進(jìn)行如下小小的修改:
|
清單 3 展示了來(lái)自 test1 上的 YAMD 的輸出。
清單 3. 使用 YAMD 的 test1 輸出
|
YAMD 顯示我們已經(jīng)釋放了內存,而且存在內存泄漏。讓我們在清單 4 中另一個(gè)樣本程序上試試 YAMD。
清單 4. 內存代碼(test2.c)
|
您可以使用下面的命令來(lái)啟動(dòng) YAMD:
./run-yamd /usr/src/test/test2/test2
清單 5 顯示了在樣本程序 test2 上使用 YAMD 得到的輸出。YAMD 告訴我們在 for 循環(huán)中有“越界(out-of-bounds)”的情況。
|
MEMWATCH 和 YAMD 都是很有用的調試工具,它們的使用方法有所不同。對于 MEMWATCH,您需要添加包含文件 memwatch.h 并打開(kāi)兩個(gè)編譯時(shí)間標記。對于鏈接(link)語(yǔ)句,YAMD 只需要 -g 選項。
Electric Fence
多數 Linux 分發(fā)版包含一個(gè) Electric Fence 包,不過(guò)您也可以選擇下載它。Electric Fence 是一個(gè)由 Bruce Perens 編寫(xiě)的 malloc() 調試庫。它就在您分配內存后分配受保護的內存。如果存在 fencepost 錯誤(超過(guò)數組末尾運行),程序就會(huì )產(chǎn)生保護錯誤,并立即結束。通過(guò)結合 Electric Fence 和 gdb,您可以精確地跟蹤到哪一行試圖訪(fǎng)問(wèn)受保護內存。Electric Fence 的另一個(gè)功能就是能夠檢測內存泄漏。
第 2 種情況:使用 stracestrace 命令是一種強大的工具,它能夠顯示所有由用戶(hù)空間程序發(fā)出的系統調用。strace 顯示這些調用的參數并返回符號形式的值。strace 從內核接收信息,而且不需要以任何特殊的方式來(lái)構建內核。將跟蹤信息發(fā)送到應用程序及內核開(kāi)發(fā)者都很有用。在清單 6 中,分區的一種格式有錯誤,清單顯示了 strace 的開(kāi)頭部分,內容是關(guān)于調出創(chuàng )建文件系統操作(mkfs)的。strace 確定哪個(gè)調用導致問(wèn)題出現。
|
清單 6 顯示 ioctl 調用導致用來(lái)格式化分區的 mkfs 程序失敗。ioctl BLKGETSIZE64 失敗。(BLKGET-SIZE64 在調用 ioctl 的源代碼中定義。) BLKGETSIZE64 ioctl 將被添加到 Linux 中所有的設備,而在這里,邏輯卷管理器還不支持它。因此,如果 BLKGETSIZE64 ioctl 調用失敗,mkfs 代碼將改為調用較早的 ioctl 調用;這使得 mkfs 適用于邏輯卷管理器。
第 3 種情況:使用 gdb 和 Oops
您可以從命令行使用 gdb 程序(Free Software Foundation 的調試器)來(lái)找出錯誤,也可以從諸如 Data Display Debugger(DDD)這樣的幾個(gè)圖形工具之一使用 gdb 程序來(lái)找出錯誤。您可以使用 gdb 來(lái)調試用戶(hù)空間程序或 Linux 內核。這一部分只討論從命令行運行 gdb 的情況。
使用 gdb program name 命令啟動(dòng) gdb。gdb 將載入可執行程序符號并顯示輸入提示符,讓您可以開(kāi)始使用調試器。您可以通過(guò)三種方式用 gdb 查看進(jìn)程:
gdb programname corefilename
要用核心文件進(jìn)行調試,您不僅需要程序的可執行文件和源文件,還需要核心文件本身。要用核心文件啟動(dòng) gdb,請使用 -c 選項:
gdb -c core programname
gdb 顯示哪行代碼導致程序發(fā)生核心轉儲。
在運行程序或連接到已經(jīng)運行的程序之前,請列出您覺(jué)得有錯誤的源代碼,設置斷點(diǎn),然后開(kāi)始調試程序。您可以使用 help 命令查看全面的 gdb 在線(xiàn)幫助和詳細的教程。
kgdb
kgdb 程序(使用 gdb 的遠程主機 Linux 內核調試器)提供了一種使用 gdb 調試 Linux 內核的機制。kgdb 程序是內核的擴展,它讓您能夠在遠程主機上運行 gdb 時(shí)連接到運行用 kgdb 擴展的內核機器。您可以接著(zhù)深入到內核中、設置斷點(diǎn)、檢查數據并進(jìn)行其它操作(類(lèi)似于您在應用程序上使用 gdb 的方式)。這個(gè)補丁的主要特點(diǎn)之一就是運行 gdb 的主機在引導過(guò)程中連接到目標機器(運行要被調試的內核)。這讓您能夠盡早開(kāi)始調試。請注意,補丁為 Linux 內核添加了功能,所以 gdb 可以用來(lái)調試 Linux 內核。
使用 kgdb 需要兩臺機器:一臺是開(kāi)發(fā)機器,另一臺是測試機器。一條串行線(xiàn)(空調制解調器電纜)將通過(guò)機器的串口連接它們。您希望調試的內核在測試機器上運行;gdb 在開(kāi)發(fā)機器上運行。gdb 使用串行線(xiàn)與您要調試的內核通信。
請遵循下面的步驟來(lái)設置 kgdb 調試環(huán)境:
set remotebaud 115200
symbol-file vmlinux
target remote /dev/ttyS0
set output-radix 16 image=/boot/bzImage-2.4.17
label=gdb2417
read-only
root=/dev/sda8
append="gdb gdbttyS=1 gdb-baud=115200 nmi_watchdog=0"
清單 7 是一個(gè)腳本示例,它將您在開(kāi)發(fā)機器上構建的內核和模塊引入測試機器。您需要修改下面幾項:
best@sfb:用戶(hù)標識和機器名。
/usr/src/linux-2.4.17:內核源代碼樹(shù)的目錄。
bzImage-2.4.17:測試機器上將引導的內核名。
rcp 和 rsync:必須允許它在構建內核的機器上運行。 清單 7. 引入測試機器的內核和模塊的腳本
|
現在我們可以通過(guò)改為使用內核源代碼樹(shù)開(kāi)始的目錄來(lái)啟動(dòng)開(kāi)發(fā)機器上的 gdb 程序了。在本示例中,內核源代碼樹(shù)位于 /usr/src/linux-2.4.17。輸入 gdb 啟動(dòng)程序。
如果一切正常,測試機器將在啟動(dòng)過(guò)程中停止。輸入 gdb 命令 cont 以繼續啟動(dòng)過(guò)程。一個(gè)常見(jiàn)的問(wèn)題是,空調制解調器電纜可能會(huì )被連接到錯誤的串口。如果 gdb 不啟動(dòng),將端口改為第二個(gè)串口,這會(huì )使 gdb 啟動(dòng)。
使用 kgdb 調試內核問(wèn)題
清單 8 列出了 jfs_mount.c 文件的源代碼中被修改過(guò)的代碼,我們在代碼中創(chuàng )建了一個(gè)空指針異常,從而使代碼在第 109 行產(chǎn)生段錯誤。
|
清單 9 在向文件系統發(fā)出 mount 命令之后顯示一個(gè) gdb 異常。kgdb 提供了幾條命令,如顯示數據結構和變量值以及顯示系統中的所有任務(wù)處于什么狀態(tài)、它們駐留在何處、它們在哪些地方使用了 CPU 等等。清單 9 將顯示回溯跟蹤為該問(wèn)題提供的信息;where 命令用來(lái)執行反跟蹤,它將告訴被執行的調用在代碼中的什么地方停止。
|
下一部分還將討論這個(gè)相同的 JFS 段錯誤問(wèn)題,但不設置調試器,如果您在非 kgdb 內核環(huán)境中執行清單 8 中的代碼,那么它使用內核可能生成的 Oops 消息。
Oops 分析
Oops(也稱(chēng) panic,慌張)消息包含系統錯誤的細節,如 CPU 寄存器的內容。在 Linux 中,調試系統崩潰的傳統方法是分析在發(fā)生崩潰時(shí)發(fā)送到系統控制臺的 Oops 消息。一旦您掌握了細節,就可以將消息發(fā)送到 ksymoops 實(shí)用程序,它將試圖將代碼轉換為指令并將堆棧值映射到內核符號。在很多情況下,這些信息就足夠您確定錯誤的可能原因是什么了。請注意,Oops 消息并不包括核心文件。
讓我們假設系統剛剛創(chuàng )建了一條 Oops 消息。作為編寫(xiě)代碼的人,您希望解決問(wèn)題并確定什么導致了 Oops 消息的產(chǎn)生,或者您希望向顯示了 Oops 消息的代碼的開(kāi)發(fā)者提供有關(guān)您的問(wèn)題的大部分信息,從而及時(shí)地解決問(wèn)題。Oops 消息是等式的一部分,但如果不通過(guò) ksymoops 程序運行它也于事無(wú)補。下面的圖顯示了格式化 Oops 消息的過(guò)程。
格式化 Oops 消息

ksymoops 需要幾項內容:Oops 消息輸出、來(lái)自正在運行的內核的 System.map 文件,還有 /proc/ksyms、vmlinux 和 /proc/modules。關(guān)于如何使用 ksymoops,內核源代碼 /usr/src/linux/Documentation/oops-tracing.txt 中或 ksymoops 手冊頁(yè)上有完整的說(shuō)明可以參考。Ksymoops 反匯編代碼部分,指出發(fā)生錯誤的指令,并顯示一個(gè)跟蹤部分表明代碼如何被調用。
首先,將 Oops 消息保存在一個(gè)文件中以便通過(guò) ksymoops 實(shí)用程序運行它。清單 10 顯示了由安裝 JFS 文件系統的 mount 命令創(chuàng )建的 Oops 消息,問(wèn)題是由清單 8 中添加到 JFS 安裝代碼的那三行代碼產(chǎn)生的。
清單 10. ksymoops 處理后的 Oops 消息
|
接下來(lái),您要確定 jfs_mount 中的哪一行代碼引起了這個(gè)問(wèn)題。Oops 消息告訴我們問(wèn)題是由位于偏移地址 3c 的指令引起的。做這件事的辦法之一是對 jfs_mount.o 文件使用 objdump 實(shí)用程序,然后查看偏移地址 3c。Objdump 用來(lái)反匯編模塊函數,看看您的 C 源代碼會(huì )產(chǎn)生什么匯編指令。清單 11 顯示了使用 objdump 后您將看到的內容,接著(zhù),我們查看 jfs_mount 的 C 代碼,可以看到空值是第 109 行引起的。偏移地址 3c 之所以很重要,是因為 Oops 消息將該處標識為引起問(wèn)題的位置。
清單 11. jfs_mount 的匯編程序清單
|
kdb
Linux 內核調試器(Linux kernel debugger,kdb)是 Linux 內核的補丁,它提供了一種在系統能運行時(shí)對內核內存和數據結構進(jìn)行檢查的辦法。請注意,kdb 不需要兩臺機器,不過(guò)它也不允許您像 kgdb 那樣進(jìn)行源代碼級別上的調試。您可以添加額外的命令,給出該數據結構的標識或地址,這些命令便可以格式化和顯示基本的系統數據結構。目前的命令集允許您控制包括以下操作在內的內核操作:
| 追擊內存溢出 您肯定不想陷入類(lèi)似在幾千次調用之后發(fā)生分配溢出這樣的情形。 我們的小組花了許許多多時(shí)間來(lái)跟蹤稀奇古怪的內存錯誤問(wèn)題。應用程序在我們的開(kāi)發(fā)工作站上能運行,但在新的產(chǎn)品工作站上,這個(gè)應用程序在調用 我們用多種不同技術(shù)來(lái)解決這個(gè)問(wèn)題,其中一種是使用調試器,另一種是在源代碼中添加跟蹤功能。在我職業(yè)生涯的大概也是這個(gè)時(shí)候,我便開(kāi)始關(guān)注內存調試工具,希望能更快更有效地解決這些類(lèi)型的問(wèn)題。在開(kāi)始一個(gè)新項目時(shí),我最先做的事情之一就是運行 MEMWATCH 和 YAMD,看看它們是不是會(huì )指出內存管理方面的問(wèn)題。 內存泄漏是應用程序中常見(jiàn)的問(wèn)題,不過(guò)您可以使用本文所講述的工具來(lái)解決這些問(wèn)題。 |
第 4 種情況:使用魔術(shù)鍵控順序進(jìn)行回溯跟蹤
如果在 Linux 掛起時(shí)您的鍵盤(pán)仍然能用,那請您使用以下方法來(lái)幫助解決掛起問(wèn)題的根源。遵循這些步驟,您便可以顯示當前運行的進(jìn)程和所有使用魔術(shù)鍵控順序的進(jìn)程的回溯跟蹤。
CONFIG_MAGIC_SYS-REQ 的情況下構建的。您還必須處在文本模式。CLTR+ALT+F1 會(huì )使您進(jìn)入文本模式,CLTR+ALT+F7 會(huì )使您回到 X Windows。
結束語(yǔ)
幫助調試 Linux 上的程序有許多不同的工具可供使用。本文講述的工具可以幫助您解決許多編碼問(wèn)題。能顯示內存泄漏、溢出等等的位置的工具可以解決內存管理問(wèn)題,我發(fā)現 MEMWATCH 和 YAMD 很有幫助。
使用 Linux 內核補丁會(huì )使 gdb 能在 Linux 內核上工作,這對解決我工作中使用的 Linux 的文件系統方面的問(wèn)題很有幫助。此外,跟蹤實(shí)用程序能幫助確定在系統調用期間文件系統實(shí)用程序什么地方出了故障。下次當您要擺平 Linux 中的錯誤時(shí),請試試這些工具中的某一個(gè)。
聯(lián)系客服