掌握 Linux 除錯技術

工程師WWW發表於2013-12-04

本文討論了四種除錯 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 上的除錯工具,通過各種方式跟蹤使用者空間和核心問題。請使用下面的工具和技術來構建和除錯您的原始碼: 
使用者空間工具

  • 記憶體工具:MEMWATCH 和 YAMD
  • strace
  • GNU 偵錯程式(gdb)
  • 魔術鍵控順序

核心工具

  • 核心原始碼級偵錯程式(kgdb)
  • 內建核心偵錯程式(kdb)
  • Oops

本文將討論一類通過人工檢查程式碼不容易找到的問題,而且此類問題只在很少見的情況下存在。記憶體錯誤通常在多種情況同時存在時出現,而且您有時只能在部署程式之後才能發現記憶體錯誤。

第 1 種情況:記憶體除錯工具

C 語言作為 Linux 系統上標準的程式語言給予了我們對動態記憶體分配很大的控制權。然而,這種自由可能會導致嚴重的記憶體管理問題,而這些問題可能導致程式崩潰或隨時間的推移導致效能降級。

記憶體洩漏(即 malloc() 記憶體在對應的 free() 呼叫執行後永不被釋放)和緩衝區溢位(例如對以前分配到某陣列的記憶體進行寫操作)是一些常見的問題,它們可能很難檢測到。這一部分將討論幾個除錯工具,它們極大地簡化了檢測和找出記憶體問題的過程。

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 為您顯示真正導致問題的行。如果您釋放一個已經釋放過的指標,它會告訴您。對於沒有釋放的記憶體也一樣。日誌結尾部分顯示統計資訊,包括洩漏了多少記憶體,使用了多少記憶體,以及總共分配了多少記憶體。

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

gcc -g test1.c -o 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 選項。

Electric Fence

多數 Linux 分發版包含一個 Electric Fence 包,不過您也可以選擇下載它。Electric Fence 是一個由 Bruce Perens 編寫的 malloc() 除錯庫。它就在您分配記憶體後分配受保護的記憶體。如果存在 fencepost 錯誤(超過陣列末尾執行),程式就會產生保護錯誤,並立即結束。通過結合 Electric Fence 和 gdb,您可以精確地跟蹤到哪一行試圖訪問受保護記憶體。Electric Fence 的另一個功能就是能夠檢測記憶體洩漏。

第 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 適用於邏輯卷管理器。

第 3 種情況:使用 gdb 和 Oops

您可以從命令列使用 gdb 程式(Free Software Foundation 的偵錯程式)來找出錯誤,也可以從諸如 Data Display Debugger(DDD)這樣的幾個圖形工具之一使用 gdb 程式來找出錯誤。您可以使用 gdb 來除錯使用者空間程式或 Linux 核心。這一部分只討論從命令列執行 gdb 的情況。

使用 gdb programname 命令啟動 gdb。gdb 將載入可執行程式符號並顯示輸入提示符,讓您可以開始使用偵錯程式。您可以通過三種方式用 gdb 檢視程式:

  • 使用 attach 命令開始檢視一個已經執行的程式;attach 將停止程式。
  • 使用 run 命令執行程式並從頭開始除錯程式。
  • 檢視已有的core檔案來確定程式終止時的狀態。要檢視核心檔案,請用下面的命令啟動 gdb。 gdb programname corefilename

    要用核心檔案進行除錯,您不僅需要程式的可執行檔案和原始檔,還需要核心檔案本身。要用核心檔案啟動 gdb,請使用 -c 選項: gdb -c core programname

    gdb 顯示哪行程式碼導致程式發生core dump。

在執行程式或連線到已經執行的程式之前,請列出您覺得有錯誤的原始碼,設定斷點,然後開始除錯程式。您可以使用 help 命令檢視全面的 gdb 線上幫助和詳細的教程。

kgdb

kgdb 程式(使用 gdb 的遠端主機 Linux 核心偵錯程式)提供了一種使用 gdb 除錯 Linux 核心的機制。kgdb 程式是核心的擴充套件,它讓您能夠在遠端主機上執行 gdb 時連線到執行用 kgdb 擴充套件的核心機器。您可以接著深入到核心中、設定斷點、檢查資料並進行其它操作(類似於您在應用程式上使用 gdb 的方式)。這個補丁的主要特點之一就是執行 gdb 的主機在引導過程中連線到目標機器(執行要被除錯的核心)。這讓您能夠儘早開始除錯。請注意,補丁為 Linux 核心新增了功能,所以 gdb 可以用來除錯 Linux 核心。

使用 kgdb 需要兩臺機器:一臺是開發機器,另一臺是測試機器。一條序列線(空調變解調器電纜)將通過機器的串列埠連線它們。您希望除錯的核心在測試機器上執行;gdb 在開發機器上執行。gdb 使用序列線與您要除錯的核心通訊。

請遵循下面的步驟來設定 kgdb 除錯環境:

  1. 下載您的 Linux 核心版本適用的補丁。
  2. 將元件構建到核心,因為這是使用 kgdb 最簡單的方法。(請注意,有兩種方法可以構建多數核心元件,比如作為模組或直接構建到核心中。舉例來說,日誌紀錄檔案系統(Journaled File System,JFS)可以作為模組構建,或直接構建到核心中。通過使用 gdb 補丁,我們就可以將 JFS 直接構建到核心中。)
  3. 應用核心補丁並重新構建核心。
  4. 建立一個名為 .gdbinit 的檔案,並將其儲存在核心原始檔子目錄中(換句話說就是 /usr/src/linux)。檔案 .gdbinit 中有下面四行程式碼:
    • set remotebaud 115200
    • symbol-file vmlinux
    • target remote /dev/ttyS0
    • set output-radix 16
  5. 將 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 是一個指令碼示例,它將您在開發機器上構建的核心和模組引入測試機器。您需要修改下面幾項:

  • best@sfb :使用者標識和機器名。
  • /usr/src/linux-2.4.17 :核心原始碼樹的目錄。
  • bzImage-2.4.17 :測試機器上將引導的核心名。
  • rcp 和 rsync :必須允許它在構建核心的機器上執行。

清單 7. 引入測試機器的核心和模組的指令碼

set -x
rcp best@sfb: /usr/src/linux-2.4.17/arch/i386/boot/bzImage /boot/bzImage-2.4.17
rcp best@sfb:/usr/src/linux-2.4.17/System.map /boot/System.map-2.4.17
rm -rf /lib/modules/2.4.17
rsync -a best@sfb:/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 啟動。

使用 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 訊息。

Oops 分析

Oops(也稱 panic,慌張)訊息包含系統錯誤的細節,如 CPU 暫存器的內容。在 Linux 中,除錯系統崩潰的傳統方法是分析在發生崩潰時傳送到系統控制檯的 Oops 訊息。一旦您掌握了細節,就可以將訊息傳送到 ksymoops 實用程式,它將試圖將程式碼轉換為指令並將堆疊值對映到核心符號。在很多情況下,這些資訊就足夠您確定錯誤的可能原因是什麼了。請注意,Oops 訊息並不包括核心檔案。

讓我們假設系統剛剛建立了一條 Oops 訊息。作為編寫程式碼的人,您希望解決問題並確定什麼導致了 Oops 訊息的產生,或者您希望向顯示了 Oops 訊息的程式碼的開發者提供有關您的問題的大部分資訊,從而及時地解決問題。Oops 訊息是等式的一部分,但如果不通過 ksymoops 程式執行它也於事無補。下面的圖顯示了格式化 Oops 訊息的過程。

格式化 Oops 訊息
格式化 Oops 訊息

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

kdb

Linux 核心偵錯程式(Linux kernel debugger,kdb)是 Linux 核心的補丁,它提供了一種在系統能執行時對核心記憶體和資料結構進行檢查的辦法。請注意,kdb 不需要兩臺機器,不過它也不允許您像 kgdb 那樣進行原始碼級別上的除錯。您可以新增額外的命令,給出該資料結構的標識或地址,這些命令便可以格式化和顯示基本的系統資料結構。目前的命令集允許您控制包括以下操作在內的核心操作:

  • 處理器單步執行
  • 執行到某條特定指令時停止
  • 當存取(或修改)某個特定的虛擬記憶體位置時停止
  • 當存取輸入/輸出地址空間中的暫存器時停止
  • 對當前活動的任務和所有其它任務進行堆疊回溯跟蹤(通過程式 ID)
  • 對指令進行反彙編

追擊記憶體溢位

您肯定不想陷入類似在幾千次呼叫之後發生分配溢位這樣的情形。

我們的小組花了許許多多時間來跟蹤稀奇古怪的記憶體錯誤問題。應用程式在我們的開發工作站上能執行,但在新的產品工作站上,這個應用程式在呼叫 malloc() 兩百萬次之後就不能執行了。真正的問題是在大約一百萬次呼叫之後發生了溢位。新系統之所有存在這個問題,是因為被保留的 malloc() 區域的佈局有所不同,從而這些零散記憶體被放置在了不同的地方,在發生溢位時破壞了一些不同的內容。

我們用多種不同技術來解決這個問題,其中一種是使用偵錯程式,另一種是在原始碼中新增跟蹤功能。在我職業生涯的大概也是這個時候,我便開始關注記憶體除錯工具,希望能更快更有效地解決這些型別的問題。在開始一個新專案時,我最先做的事情之一就是執行 MEMWATCH 和 YAMD,看看它們是不是會指出記憶體管理方面的問題。

記憶體洩漏是應用程式中常見的問題,不過您可以使用本文所講述的工具來解決這些問題。

第 4 種情況:使用魔術鍵控順序進行回溯跟蹤

如果在 Linux 掛起時您的鍵盤仍然能用,那請您使用以下方法來幫助解決掛起問題的根源。遵循這些步驟,您便可以顯示當前執行的程式和所有使用魔術鍵控順序的程式的回溯跟蹤。

  1. 您正在執行的核心必須是在啟用 CONFIG_MAGIC_SYS-REQ 的情況下構建的。您還必須處在文字模式。CLTR+ALT+F1 會使您進入文字模式,CLTR+ALT+F7 會使您回到 X Windows。
  2. 當在文字模式時,請按 <ALT+ScrollLock>,然後按 <Ctrl+ScrollLock>。上述魔術的擊鍵會分別給出當前執行的程式和所有程式的堆疊跟蹤。
  3. 請查詢 /var/log/messages。如果一切設定正確,則系統應該已經為您轉換了核心的符號地址。回溯跟蹤將被寫到 /var/log/messages 檔案中。

結束語

幫助除錯 Linux 上的程式有許多不同的工具可供使用。 本文講述的工具可以幫助您解決許多編碼問題。能顯示記憶體洩漏、溢位等等的位置的工具可以解決記憶體管理問題,我發現 MEMWATCH 和 YAMD 很有幫助。

使用 Linux 核心補丁會使 gdb 能在 Linux 核心上工作,這對解決我工作中使用的 Linux 的檔案系統方面的問題很有幫助。此外,跟蹤實用程式能幫助確定在系統呼叫期間檔案系統實用程式什麼地方出了故障。下次當您要擺平 Linux 中的錯誤時,請試試這些工具中的某一個。

參考資料

相關文章