掌握 Linux 調試技術 http://blog.csdn.net/adcxf/archive/2008/09/13/2924723.aspx
您可以用各種方法來監控運作着的使用者空間程式:可以為其運作調試器并單步調試該程式,添加列印語句,或者添加工具來分析程式。本文描述了幾種可以用來調試在 Linux 上運作的程式的方法。我們将回顧四種調試問題的情況,這些問題包括段錯誤,記憶體溢出和洩漏,還有挂起。 本文讨論了四種調試 Linux 程式的情況。在第 1 種情況中,我們使用了兩個有記憶體配置設定問題的樣本程式,使用 MEMWATCH 和 Yet Another Malloc Debugger(YAMD)工具來調試它們。在第 2 種情況中,我們使用了 Linux 中的 strace 實用程式,它能夠跟蹤系統調用和信号,進而找出程式發生錯誤的地方。在第 3 種情況中,我們使用 Linux 核心的 Oops 功能來解決程式的段錯誤,并向您展示如何設定核心源代碼級調試器(kernel source level debugger,kgdb),以使用 GNU 調試器(GNU debugger,gdb)來解決相同的問題;kgdb 程式是使用串行連接配接的 Linux 核心遠端 gdb。在第 4 種情況中,我們使用 Linux 上提供的魔術鍵控順序(magic key sequence)來顯示引發挂起問題的元件的資訊。
常見調試方法
當您的程式中包含錯誤時,很可能在代碼中某處有一個條件,您認為它為真(true),但實際上是假(false)。找出錯誤的過程也就是在找出錯誤後推翻以前一直确信為真的某個條件過程。
以下幾個示例是您可能确信成立的條件的一些類型:
在源代碼中的某處,某變量有特定的值。 在給定的地方,某個結構已被正确設定。 對于給定的 if-then-else
語句, if
部分就是被執行的路徑。 當子例程被調用時,該例程正确地接收到了它的參數。 找出錯誤也就是要确定上述所有情況是否存在。如果您确信在子例程被調用時某變量應該有特定的值,那麼就檢查一下情況是否如此。如果您相信
if
結構會被執行,那麼也檢查一下情況是否如此。通常,您的假設都會是正确的,但最終您會找到與假設不符的情況。結果,您就會找出發生錯誤的地方。
調試是您無法逃避的任務。進行調試有很多種方法,比如将消息列印到螢幕上、使用調試器,或隻是考慮程式執行的情況并仔細地揣摩問題所在。
在修正問題之前,您必須找出它的源頭。舉例來說,對于段錯誤,您需要了解段錯誤發生在代碼的哪一行。一旦您發現了代碼中出錯的行,請确定該方法中變量的值、方法被調用的方式以及關于錯誤如何發生的詳細情況。使用調試器将使找出所有這些資訊變得很簡單。如果沒有調試器可用,您還可以使用其它的工具。(請注意,産品環境中可能并不提供調試器,而且 Linux 核心沒有内建的調試器。)
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 實用的記憶體和核心工具
您可以使用 Linux 上的調試工具,通過各種方式跟蹤使用者空間和核心問題。請使用下面的工具和技術來建構和調試您的源代碼:
使用者空間工具 :
記憶體工具:MEMWATCH 和 YAMD strace GNU 調試器(gdb) 魔術鍵控順序 核心工具 : 核心源代碼級調試器(kgdb) 内建核心調試器(kdb) Oops
本文将讨論一類通過人工檢查代碼不容易找到的問題,而且此類問題隻在很少見的情況下存在。記憶體錯誤通常在多種情況同時存在時出現,而且您有時隻能在部署程式之後才能發現記憶體錯誤。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
第 1 種情況:記憶體調試工具
C 語言作為 Linux 系統上标準的程式設計語言給予了我們對動态記憶體配置設定很大的控制權。然而,這種自由可能會導緻嚴重的記憶體管理問題,而這些問題可能導緻程式崩潰或随時間的推移導緻性能降級。
記憶體洩漏(即
malloc()
記憶體在對應的
free()
調用執行後永不被釋放)和緩沖區溢出(例如對以前配置設定到某數組的記憶體進行寫操作)是一些常見的問題,它們可能很難檢測到。這一部分将讨論幾個調試工具,它們極大地簡化了檢測和找出記憶體問題的過程。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
MEMWATCH
MEMWATCH 由 Johan Lindh 編寫,是一個開放源代碼 C 語言記憶體錯誤檢測工具,您可以自己下載下傳它(請參閱本文後面部分的 參考資料 )。隻要在代碼中添加一個頭檔案并在 gcc 語句中定義了 MEMWATCH 之後,您就可以跟蹤程式中的記憶體洩漏和錯誤了。MEMWATCH 支援 ANSI C,它提供結果日志紀錄,能檢測雙重釋放(double-free)、錯誤釋放(erroneous free)、沒有釋放的記憶體(unfreed memory)、溢出和下溢等等。
清單 1. 記憶體樣本(test1.c)
#include <stdlib.h>
#include <stdio.h>
#include "memwatch.h"
int main(void)
{
char *ptr1;
char *ptr2;
ptr1 = malloc(512);
ptr2 = malloc(512);
ptr2 = ptr1;
free(ptr2);
free(ptr1);
}
清單 1 中的代碼将配置設定兩個 512 位元組的記憶體塊,然後指向第一個記憶體塊的指針被設定為指向第二個記憶體塊。結果,第二個記憶體塊的位址丢失,進而産生了記憶體洩漏。
現在我們編譯清單 1 的 memwatch.c。下面是一個 makefile 示例:
test1
gcc -DMEMWATCH -DMW_STDIO test1.c memwatch
c -o test1
當您運作 test1 程式後,它會生成一個關于洩漏的記憶體的報告。清單 2 展示了示例 memwatch.log 輸出檔案。
清單 2. test1 memwatch.log 檔案
MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh
...
double-free: <4> test1.c(15), 0x80517b4 was freed from test1.c(14)
...
unfreed: <2> test1.c(11), 512 bytes at 0x80519e4
{FE FE FE FE FE FE FE FE FE FE FE FE ..............}
Memory usage statistics (global):
N)umber of allocations made: 2
L)argest memory usage : 1024
T)otal of all alloc() calls: 1024
U)nfreed bytes totals : 512
MEMWATCH 為您顯示真正導緻問題的行。如果您釋放一個已經釋放過的指針,它會告訴您。對于沒有釋放的記憶體也一樣。日志結尾部分顯示統計資訊,包括洩漏了多少記憶體,使用了多少記憶體,以及總共配置設定了多少記憶體。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
YAMD
YAMD 軟體包由 Nate Eldredge 編寫,可以查找 C 和 C++ 中動态的、與記憶體配置設定有關的問題。在撰寫本文時,YAMD 的最新版本為 0.32。請下載下傳 yamd-0.32.tar.gz(請參閱 參考資料 )。執行
make
指令來建構程式;然後執行
make install
指令安裝程式并設定工具。
一旦您下載下傳了 YAMD 之後,請在 test1.c 上使用它。請删除
#include memwatch.h
并對 makefile 進行如下小小的修改:
使用 YAMD 的 test1
清單 3 展示了來自 test1 上的 YAMD 的輸出。
清單 3. 使用 YAMD 的 test1 輸出
YAMD version 0.32
Executable: /usr/src/test/yamd-0.32/test1
...
INFO: Normal allocation of this block
Address 0x40025e00, size 512
...
INFO: Normal allocation of this block
Address 0x40028e00, size 512
...
INFO: Normal deallocation of this block
Address 0x40025e00, size 512
...
ERROR: Multiple freeing At
free of pointer already freed
Address 0x40025e00, size 512
...
WARNING: Memory leak
Address 0x40028e00, size 512
WARNING: Total memory leaks:
1 unfreed allocations totaling 512 bytes
*** Finished at Tue ... 10:07:15 2002
Allocated a grand total of 1024 bytes 2 allocations
Average of 512 bytes per allocation
Max bytes allocated at one time: 1024
24 K alloced internally / 12 K mapped now / 8 K max
Virtual program size is 1416 K
End.
YAMD 顯示我們已經釋放了記憶體,而且存在記憶體洩漏。讓我們在清單 4 中另一個樣本程式上試試 YAMD。
清單 4. 記憶體代碼(test2.c)
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *ptr1;
char *ptr2;
char *chptr;
int i = 1;
ptr1 = malloc(512);
ptr2 = malloc(512);
chptr = (char *)malloc(512);
for (i; i <= 512; i++) {
chptr[i] = 'S';
}
ptr2 = ptr1;
free(ptr2);
free(ptr1);
free(chptr);
}
您可以使用下面的指令來啟動 YAMD:
./run-yamd /usr/src/test/test2/test2
清單 5 顯示了在樣本程式 test2 上使用 YAMD 得到的輸出。YAMD 告訴我們在
for
循環中有“越界(out-of-bounds)”的情況。
清單 5. 使用 YAMD 的 test2 輸出
Running /usr/src/test/test2/test2
Temp output to /tmp/yamd-out.1243
*********
./run-yamd: line 101: 1248 Segmentation fault (core dumped)
YAMD version 0.32
Starting run: /usr/src/test/test2/test2
Executable: /usr/src/test/test2/test2
Virtual program size is 1380 K
...
INFO: Normal allocation of this block
Address 0x40025e00, size 512
...
INFO: Normal allocation of this block
Address 0x40028e00, size 512
...
INFO: Normal allocation of this block
Address 0x4002be00, size 512
ERROR: Crash
...
Tried to write address 0x4002c000
Seems to be part of this block:
Address 0x4002be00, size 512
...
Address in question is at offset 512 (out of bounds)
Will dump core after checking heap.
Done.
MEMWATCH 和 YAMD 都是很有用的調試工具,它們的使用方法有所不同。對于 MEMWATCH,您需要添加包含檔案 memwatch.h 并打開兩個編譯時間标記。對于連結(link)語句,YAMD 隻需要
-g
選項。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
Electric Fence
多數 Linux 分發版包含一個 Electric Fence 包,不過您也可以選擇下載下傳它。Electric Fence 是一個由 Bruce Perens 編寫的
malloc()
調試庫。它就在您配置設定記憶體後配置設定受保護的記憶體。如果存在 fencepost 錯誤(超過數組末尾運作),程式就會産生保護錯誤,并立即結束。通過結合 Electric Fence 和 gdb,您可以精确地跟蹤到哪一行試圖通路受保護記憶體。Electric Fence 的另一個功能就是能夠檢測記憶體洩漏。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
第 2 種情況:使用 strace
strace
指令是一種強大的工具,它能夠顯示所有由使用者空間程式發出的系統調用。strace 顯示這些調用的參數并傳回符号形式的值。strace 從核心接收資訊,而且不需要以任何特殊的方式來建構核心。将跟蹤資訊發送到應用程式及核心開發者都很有用。在清單 6 中,分區的一種格式有錯誤,清單顯示了 strace 的開頭部分,内容是關于調出建立檔案系統操作(
mkfs
)的。strace 确定哪個調用導緻問題出現。
清單 6. mkfs 上 strace 的開頭部分
execve("/sbin/mkfs.jfs", ["mkfs.jfs", "-f", "/dev/test1"], &
...
open("/dev/test1", O_RDWR|O_LARGEFILE) = 4
stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
ioctl(4, 0x40041271, 0xbfffe128) = -1 EINVAL (Invalid argument)
write(2, "mkfs.jfs: warning - cannot setb" ..., 98mkfs.jfs: warning -
cannot set blocksize on block device /dev/test1: Invalid argument )
= 98
stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
open("/dev/test1", O_RDONLY|O_LARGEFILE) = 5
ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)
write(2, "mkfs.jfs: can/'t determine device"..., ..._exit(1)
= ?
清單 6 顯示
ioctl
調用導緻用來格式化分區的
mkfs
程式失敗。
ioctl BLKGETSIZE64
失敗。(
BLKGET-SIZE64
在調用
ioctl
的源代碼中定義。)
BLKGETSIZE64 ioctl
将被添加到 Linux 中所有的裝置,而在這裡,邏輯卷管理器還不支援它。是以,如果
BLKGETSIZE64 ioctl
調用失敗,mkfs 代碼将改為調用較早的
ioctl
調用;這使得
mkfs
适用于邏輯卷管理器。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
第 3 種情況:使用 gdb 和 Oops
您可以從指令行使用 gdb 程式(Free Software Foundation 的調試器)來找出錯誤,也可以從諸如 Data Display Debugger(DDD)這樣的幾個圖形工具之一使用 gdb 程式來找出錯誤。您可以使用 gdb 來調試使用者空間程式或 Linux 核心。這一部分隻讨論從指令行運作 gdb 的情況。
使用
gdb program name
指令啟動 gdb。gdb 将載入可執行程式符号并顯示輸入提示符,讓您可以開始使用調試器。您可以通過三種方式用 gdb 檢視程序:
使用 attach 指令開始檢視一個已經運作的程序;attach 将停止程序。 使用 run 指令執行程式并從頭開始調試程式。 檢視已有的核心檔案來确定程序終止時的狀态。要檢視核心檔案,請用下面的指令啟動 gdb。 gdb programname corefilename
要用核心檔案進行調試,您不僅需要程式的可執行檔案和源檔案,還需要核心檔案本身。要用核心檔案啟動 gdb,請使用 -c 選項: gdb -c core programname
gdb 顯示哪行代碼導緻程式發生核心轉儲。 在運作程式或連接配接到已經運作的程式之前,請列出您覺得有錯誤的源代碼,設定斷點,然後開始調試程式。您可以使用
help
指令檢視全面的 gdb 線上幫助和詳細的教程。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
kgdb
kgdb 程式(使用 gdb 的遠端主機 Linux 核心調試器)提供了一種使用 gdb 調試 Linux 核心的機制。kgdb 程式是核心的擴充,它讓您能夠在遠端主機上運作 gdb 時連接配接到運作用 kgdb 擴充的核心機器。您可以接着深入到核心中、設定斷點、檢查資料并進行其它操作(類似于您在應用程式上使用 gdb 的方式)。這個更新檔的主要特點之一就是運作 gdb 的主機在引導過程中連接配接到目标機器(運作要被調試的核心)。這讓您能夠盡早開始調試。請注意,更新檔為 Linux 核心添加了功能,是以 gdb 可以用來調試 Linux 核心。
使用 kgdb 需要兩台機器:一台是開發機器,另一台是測試機器。一條串行線(空數據機電纜)将通過機器的序列槽連接配接它們。您希望調試的核心在測試機器上運作;gdb 在開發機器上運作。gdb 使用串行線與您要調試的核心通信。
請遵循下面的步驟來設定 kgdb 調試環境:
下載下傳您的 Linux 核心版本适用的更新檔。 将元件建構到核心,因為這是使用 kgdb 最簡單的方法。(請注意,有兩種方法可以建構多數核心元件,比如作為子產品或直接建構到核心中。舉例來說,日志紀錄檔案系統(Journaled File System,JFS)可以作為子產品建構,或直接建構到核心中。通過使用 gdb 更新檔,我們就可以将 JFS 直接建構到核心中。) 應用核心更新檔并重新建構核心。 建立一個名為 .gdbinit 的檔案,并将其儲存在核心源檔案子目錄中(換句話說就是 /usr/src/linux)。檔案 .gdbinit 中有下面四行代碼: set remotebaud 115200
symbol-file vmlinux
target remote /dev/ttyS0
set output-radix 16
将 append=gdb 這一行添加到 lilo,lilo 是用來在引導核心時選擇使用哪個核心的引導載入程式。 image=/boot/bzImage-2.4.17
label=gdb2417
read-only
root=/dev/sda8
append="gdb gdbttyS=1 gdb-baud=115200 nmi_watchdog=0"
清單 7 是一個腳本示例,它将您在開發機器上建構的核心和子產品引入測試機器。您需要修改下面幾項:
[email protected]
:使用者辨別和機器名。 /usr/src/linux-2.4.17
:核心源代碼樹的目錄。 bzImage-2.4.17
:測試機器上将引導的核心名。 rcp
和 rsync
:必須允許它在建構核心的機器上運作。 清單 7. 引入測試機器的核心和子產品的腳本
set -x
rcp [email protected]: /usr/src/linux-2.4.17/arch/i386/boot/bzImage /boot/bzImage-2.4.17
rcp b[email protected]:/usr/src/linux-2.4.17/System.map /boot/System.map-2.4.17
rm -rf /lib/modules/2.4.17
rsync -a [email protected]:/lib/modules/2.4.17 /lib/modules
chown -R root /lib/modules/2.4.17
lilo
現在我們可以通過改為使用核心源代碼樹開始的目錄來啟動開發機器上的 gdb 程式了。在本示例中,核心源代碼樹位于 /usr/src/linux-2.4.17。輸入
gdb
啟動程式。
如果一切正常,測試機器将在啟動過程中停止。輸入
gdb
指令
cont
以繼續啟動過程。一個常見的問題是,空數據機電纜可能會被連接配接到錯誤的序列槽。如果 gdb 不啟動,将端口改為第二個序列槽,這會使 gdb 啟動。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
使用 kgdb 調試核心問題
清單 8 列出了 jfs_mount.c 檔案的源代碼中被修改過的代碼,我們在代碼中建立了一個空指針異常,進而使代碼在第 109 行産生段錯誤。
清單 8. 修改過後的 jfs_mount.c 代碼
int jfs_mount(struct super_block *sb)
{
...
int ptr; /* line 1 added */
jFYI(1, ("/nMount JFS/n"));
/ *
* read/validate superblock
* (initialize mount inode from the superblock)
* /
if ((rc = chkSuper(sb))) {
goto errout20;
}
108 ptr=0; /* line 2 added */
109 printk("%d/n",*ptr); /* line 3 added */
清單 9 在向檔案系統發出 mount 指令之後顯示一個 gdb 異常。kgdb 提供了幾條指令,如顯示資料結構和變量值以及顯示系統中的所有任務處于什麼狀态、它們駐留在何處、它們在哪些地方使用了 CPU 等等。清單 9 将顯示回溯跟蹤為該問題提供的資訊;
where
指令用來執行反跟蹤,它将告訴被執行的調用在代碼中的什麼地方停止。
清單 9. gdb 異常和反跟蹤
mount -t jfs /dev/sdb /jfs
Program received signal SIGSEGV, Segmentation fault.
jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
109 printk("%d/n",*ptr);
(gdb)where
#0 jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
#1 0xc01a0dbb in jfs_read_super ... at super.c:280
#2 0xc0149ff5 in get_sb_bdev ... at super.c:620
#3 0xc014a89f in do_kern_mount ... at super.c:849
#4 0xc0160e66 in do_add_mount ... at namespace.c:569
#5 0xc01610f4 in do_mount ... at namespace.c:683
#6 0xc01611ea in sys_mount ... at namespace.c:716
#7 0xc01074a7 in system_call () at af_packet.c:1891
#8 0x0 in -- ()
(gdb)
下一部分還将讨論這個相同的 JFS 段錯誤問題,但不設定調試器,如果您在非 kgdb 核心環境中執行清單 8 中的代碼,那麼它使用核心可能生成的 Oops 消息。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
Oops 分析
Oops(也稱 panic,慌張)消息包含系統錯誤的細節,如 CPU 寄存器的内容。在 Linux 中,調試系統崩潰的傳統方法是分析在發生崩潰時發送到系統控制台的 Oops 消息。一旦您掌握了細節,就可以将消息發送到 ksymoops 實用程式,它将試圖将代碼轉換為指令并将堆棧值映射到核心符号。在很多情況下,這些資訊就足夠您确定錯誤的可能原因是什麼了。請注意,Oops 消息并不包括核心檔案。
讓我們假設系統剛剛建立了一條 Oops 消息。作為編寫代碼的人,您希望解決問題并确定什麼導緻了 Oops 消息的産生,或者您希望向顯示了 Oops 消息的代碼的開發者提供有關您的問題的大部分資訊,進而及時地解決問題。Oops 消息是等式的一部分,但如果不通過 ksymoops 程式運作它也于事無補。下面的圖顯示了格式化 Oops 消息的過程。
格式化 Oops 消息
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 ksymoops 需要幾項内容:Oops 消息輸出、來自正在運作的核心的 System.map 檔案,還有 /proc/ksyms、vmlinux 和 /proc/modules。關于如何使用 ksymoops,核心源代碼 /usr/src/linux/Documentation/oops-tracing.txt 中或 ksymoops 手冊頁上有完整的說明可以參考。Ksymoops 反彙編代碼部分,指出發生錯誤的指令,并顯示一個跟蹤部分表明代碼如何被調用。
首先,将 Oops 消息儲存在一個檔案中以便通過 ksymoops 實用程式運作它。清單 10 顯示了由安裝 JFS 檔案系統的 mount 指令建立的 Oops 消息,問題是由清單 8 中添加到 JFS 安裝代碼的那三行代碼産生的。
清單 10. ksymoops 處理後的 Oops 消息
ksymoops 2.4.0 on i686 2.4.17. Options used
... 15:59:37 sfb1 kernel: Unable to handle kernel NULL pointer dereference at
virtual address 0000000
... 15:59:37 sfb1 kernel: c01588fc
... 15:59:37 sfb1 kernel: *pde = 0000000
... 15:59:37 sfb1 kernel: Oops: 0000
... 15:59:37 sfb1 kernel: CPU: 0
... 15:59:37 sfb1 kernel: EIP: 0010:[jfs_mount+60/704]
... 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688]
[get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208]
[do_page_fault+0/1264]
... 15:59:37 sfb1 kernel: Call Trace: [<c0155d4f>]...
... 15:59:37 sfb1 kernel: [<c0106e04 ...
... 15:59:37 sfb1 kernel: Code: 8b 2d 00 00 00 00 55 ...
>>EIP; c01588fc <jfs_mount+3c/2c0> <=====
...
Trace; c0106cf3 <system_call+33/40>
Code; c01588fc <jfs_mount+3c/2c0>
00000000 <_EIP>:
Code; c01588fc <jfs_mount+3c/2c0> <=====
0: 8b 2d 00 00 00 00 mov 0x0,%ebp <=====
Code; c0158902 <jfs_mount+42/2c0>
6: 55 push %ebp
接下來,您要确定 jfs_mount 中的哪一行代碼引起了這個問題。Oops 消息告訴我們問題是由位于偏移位址 3c 的指令引起的。做這件事的辦法之一是對 jfs_mount.o 檔案使用 objdump 實用程式,然後檢視偏移位址 3c。Objdump 用來反彙編子產品函數,看看您的 C 源代碼會産生什麼彙編指令。清單 11 顯示了使用 objdump 後您将看到的内容,接着,我們檢視 jfs_mount 的 C 代碼,可以看到空值是第 109 行引起的。偏移位址 3c 之是以很重要,是因為 Oops 消息将該處辨別為引起問題的位置。
清單 11. jfs_mount 的彙程式設計式清單
109 printk("%d/n",*ptr);
objdump jfs_mount.o
jfs_mount.o: file format elf32-i386
Disassembly of section .text:
00000000 <jfs_mount>:
0:55 push %ebp
...
2c: e8 cf 03 00 00 call 400 <chkSuper>
31: 89 c3 mov %eax,%ebx
33: 58 pop %eax
34: 85 db test %ebx,%ebx
36: 0f 85 55 02 00 00 jne 291 <jfs_mount+0x291>
3c: 8b 2d 00 00 00 00 mov 0x0,%ebp << problem line above
42: 55 push %ebp
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
kdb
Linux 核心調試器(Linux kernel debugger,kdb)是 Linux 核心的更新檔,它提供了一種在系統能運作時對核心記憶體和資料結構進行檢查的辦法。請注意,kdb 不需要兩台機器,不過它也不允許您像 kgdb 那樣進行源代碼級别上的調試。您可以添加額外的指令,給出該資料結構的辨別或位址,這些指令便可以格式化和顯示基本的系統資料結構。目前的指令集允許您控制包括以下操作在内的核心操作:
處理器單步執行 執行到某條特定指令時停止 當存取(或修改)某個特定的虛拟記憶體位置時停止 當存取輸入/輸出位址空間中的寄存器時停止 對目前活動的任務和所有其它任務進行堆棧回溯跟蹤(通過程序 ID) 對指令進行反彙編 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 追擊記憶體溢出
您肯定不想陷入類似在幾千次調用之後發生配置設定溢出這樣的情形。
我們的小組花了許許多多時間來跟蹤稀奇古怪的記憶體錯誤問題。應用程式在我們的開發工作站上能運作,但在新的産品工作站上,這個應用程式在調用
malloc()
兩百萬次之後就不能運作了。真正的問題是在大約一百萬次調用之後發生了溢出。新系統之所有存在這個問題,是因為被保留的 malloc()
區域的布局有所不同,進而這些零散記憶體被放置在了不同的地方,在發生溢出時破壞了一些不同的内容。
我們用多種不同技術來解決這個問題,其中一種是使用調試器,另一種是在源代碼中添加跟蹤功能。在我職業生涯的大概也是這個時候,我便開始關注記憶體調試工具,希望能更快更有效地解決這些類型的問題。在開始一個新項目時,我最先做的事情之一就是運作 MEMWATCH 和 YAMD,看看它們是不是會指出記憶體管理方面的問題。
記憶體洩漏是應用程式中常見的問題,不過您可以使用本文所講述的工具來解決這些問題。
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
第 4 種情況:使用魔術鍵控順序進行回溯跟蹤
如果在 Linux 挂起時您的鍵盤仍然能用,那請您使用以下方法來幫助解決挂起問題的根源。遵循這些步驟,您便可以顯示目前運作的程序和所有使用魔術鍵控順序的程序的回溯跟蹤。
您正在運作的核心必須是在啟用 CONFIG_MAGIC_SYS-REQ
的情況下建構的。您還必須處在文本模式。CLTR+ALT+F1 會使您進入文本模式,CLTR+ALT+F7 會使您回到 X Windows。 當在文本模式時,請按 <ALT+ScrollLock>,然後按 <Ctrl+ScrollLock>。上述魔術的擊鍵會分别給出目前運作的程序和所有程序的堆棧跟蹤。 請查找 /var/log/messages。如果一切設定正确,則系統應該已經為您轉換了核心的符号位址。回溯跟蹤将被寫到 /var/log/messages 檔案中。 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備
掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 +Debian/Ubuntu核心程式設計者必備 掌握 Linux 調試技術 Debian/Ubuntu核心程式設計者必備 回頁首
結束語
幫助調試 Linux 上的程式有許多不同的工具可供使用。本文講述的工具可以幫助您解決許多編碼問題。能顯示記憶體洩漏、溢出等等的位置的工具可以解決記憶體管理問題,我發現 MEMWATCH 和 YAMD 很有幫助。
使用 Linux 核心更新檔會使 gdb 能在 Linux 核心上工作,這對解決我工作中使用的 Linux 的檔案系統方面的問題很有幫助。此外,跟蹤實用程式能幫助确定在系統調用期間檔案系統實用程式什麼地方出了故障。下次當您要擺平 Linux 中的錯誤時,請試試這些工具中的某一個。
參考資料
您可以參閱本文在 developerWorks 全球站點上的 英文原文 . 下載下傳 MEMWATCH 。 請檢視 Dynamic Probes 調試功能程式 。 請閱讀文章“ Linux software debugging with GDB ”。( developerWorks ,2001 年 2 月) 請通路 IBM Linux Technology Center 。 在 developerWorks Linux 專區可以找到 更多的 Linux 文章 。 Debian/Ubuntu核心程式設計者必備 如果你想要更新你的Debian/Ubuntu Linux核心,或者你希望為核心開發新的子產品,或者您要為某個硬體寫新的驅動程式……這一切都涉及到Debian/Ubuntu Linux核心程式設計。
作為一個核心程式設計者,有那麼幾個軟體是你必須要有的,看作是你進行核心程式設計的幾件法寶吧,下面我一一列舉出來:
1、gcc
大名鼎鼎的gcc我想沒有人不知道的吧?它是任何程式設計者必然要先安裝的一個武器了。不過一般如果你是安裝的Debian系統,應該已經預設安裝了的。要是Ubuntu你就安裝一下吧,安裝方法嘛,就是輸入:
2、make
如果你實實在在地寫過有用的程式,你一定輸入過make clean、make、make install等指令的,make是一個源代碼維護工具,它能自動檢測出需要重新編譯的源檔案并根據你設定的編譯規則去重新編譯程式。這裡也不多說,如果不了解的朋友就google一下吧。不過一般如果你是安裝的Debian系統,應該已經預設安裝了的。要是Ubuntu你就安裝一下吧,安裝方法一樣,輸入:
3、kernel-source
從這個包的名稱上已經知道了,這是核心源碼包。你可以apt-cache search kernel-source搜尋到核心源代碼包,并用uname -r指令檢視到目前系統核心版本,然後用apt-get install kernel-source-xxxx來安裝和你核心版本一緻的核心源代碼包。
但我并不建議你這樣做,因為大多數發行版的Linux的核心源代碼包并不是和你從kernel.org上下載下傳來的完全一緻,應該是經過發行者們修改優化過的包,雖然發行版的制作者們花了心血在上面,但對我們程式設計者來說這并不是件好事,因為大多發行版的源代碼包缺少某些必要的頭檔案和某些有用的腳本程式,這會導緻我們在編寫核心子產品時遇到麻煩,比如編譯時可能會報錯說某個頭檔案找不到,又可能報錯說某個腳本程式不存在啦。
如果你直接去kernel.org上下載下傳一個新的核心來編譯更新你的核心,并在此基礎上進行核心程式設計可能會省事一些,至少我後面講到的這些包你都可以不用安裝了,把KERNEL_DIR變量設定為你核心源代碼包的位置就夠了。
在這裡還是以Debian 3.1、核心2.6.8-2-386為基礎來教大家準備核心程式設計環境吧。那麼你依次用這幾個指令來安裝kernel-source包吧。
apt-cache search kernel-source
uname -r
apt-get install kernel-source-xxxx
注:如果你不想重新編譯核心,核心源代碼包kernel-source是完全可以不下載下傳來的。
4、kernel-headers
這是核心源代碼頭檔案包,不管你是要進行核心子產品開發還是進行驅動程式開發,這個包都是必須要安裝的。因為作為一個核心子產品編寫者,通常會調用核心裡的一些東西,比如核心頭檔案,核心資料結構申明等。它裡面包含了一些關鍵的核心頭檔案。否則在編譯核心時會報類似下面的錯誤:
HOSTCC scripts/basic/fixdep
scripts/basic/fixdep.c:105:23: error: sys/types.h: 沒有那個檔案或目錄
輸入如下指令來安裝吧: apt-cache search kernel-headers
uname -r
apt-get instakk kernel-headers-xxxx
注:在Ubuntu下,好象這個包的前面都加上了linux-,進而變成了linux-kernel-headers。
如果之前沒有安裝過kernel-kbuild包,安裝此包的過程中會安裝kernel-kbuild包,這個包在下面介紹。
5、kernel-kbuild
這個包是用來編譯核心子產品的,下載下傳安裝這個包後會發現在/usr/src目錄下多了一個kernel-kbuild-xxxx開頭的目錄,下面隻有scripts一個目錄,顯然這是用來編譯核心子產品的一些腳本程式。安裝方法一樣:
apt-cache search kernel-kbuild
uname -r
apt-get install kernel-kbuild-xxxx
注:你也會發現,在上面安裝kernel-headers包後,/usr/src/kernel-headers-xxxx目錄下有個scripts目錄,其實是到/usr/src/kernel-kbuild-xxxx目錄下的scripts的一個連結。
6、build-essential
這個包包含一個在建立deb包過程中起關鍵作用的包的資訊清單,如果你不想建立deb包你就不需要安裝此表,如果需要生成deb包就最好安裝一下這個包吧。安裝方法:
apt-get install build-essential
7、kernel-package
如果你想把核心鏡像做成一個deb包來用,那麼必須用安裝這個包了。也就是說隻有安裝了這個軟體包你才能有make-kpkg指令可用。安裝方法一樣了:
apt-get install kernel-package
8、initrd-tools
如果你想制作啟動過程的initrd鏡像,則這個包是必不可少的。安裝了這個包之後才有mkinitrd指令可用的。安裝方法:
apt-get instakk initrd-tools
最後來一個核心子產品程式設計示例吧!
原文位址 http://hi.baidu.com/cai066/blog/item/561f29fed421ae345c600890.html