歡迎您光臨本站 註冊首頁

揭開Linux系統內核調試器神秘面紗(上)

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  調試內核問題時,能夠跟蹤內核執行情況並查看其內存和數據結構是非常有用的。Linux 中的內置內 核調試器 KDB 提供了這種功能。在本文中您把了解怎麼樣使用 KDB 所提供的功能,以及怎麼樣在 Linux 機器上安裝和設置 KDB。您還把熟悉 KDB 中可以使用的命令以及設置和顯示選項。

  Linux 內核調試器(KDB)允許您調試 Linux 內核。這個恰如其名的工具實質上是內核代碼的補丁,它允許高手訪問內核內存和數據結構。KDB 的主要優點之一就是它不需要用另一台機器進行調試:您可以調試正在運行的內核。

  設置一台用於 KDB 的機器需要花費一些工作,因為需要給內核打補丁並進行重新編譯。KDB 的用戶應當熟悉 Linux 內核的編譯(在一定程度上還要熟悉內核內部機理),但是如果您需要編譯內核方面的幫助,請參閱本文結尾處的參考資料一節。

  在本文中,我們把從有關下載 KDB 補丁、打補丁、(重新)編譯內核以及啟動 KDB 方面的信息著手。然後我們把了解 KDB 命令並研究一些較常用的命令。最後,我們把研究一下有關設置和顯示選項方面的一些詳細信息。

  入門

  KDB 項目是由 Silicon Graphics 維護的(請參閱參考資料以獲取鏈接),您需要從它的 FTP 站點下載與內核版本有關的補丁。(在編寫本文時)可用的最新 KDB 版本是 4.2。您把需要下載並應用兩個補丁。

  一個是“公共的”補丁,包含了對通用內核代碼的更改,另一個是特定於體系結構的補丁。補丁可作為 bz2 文件獲取。例如,在運行 2.4.20 內核的 x86 機器上,您會需要 kdb-v4.2-2.4.20-common-1.bz2 和 kdb-v4.2-2.4.20-i386-1.bz2。

  這裡所提供的所有示例都是針對 i386 體系結構和 2.4.20 內核的。您把需要根據您的機器和內核版本進行適當的更改。您還需要擁有 root 許可權以執行這些操作。

  把文件複製到 /usr/src/linux 目錄中並從用 bzip2 壓縮的文件解壓縮補丁文件:

  #bzip2 -d kdb-v4.2-2.4.20-common-1.bz2

  #bzip2 -d kdb-v4.2-2.4.20-i386-1.bz2

  您把獲得 kdb-v4.2-2.4.20-common-1 和 kdb-v4.2-2.4-i386-1 文件。

  現在,應用這些補丁:

  #patch -p1

  #patch -p1

  這些補丁應該乾淨利落地加以應用。查找任何以 .rej 結尾的文件。這個擴展名表明這些是失敗的補丁。如果內核樹沒問題,那麼補丁的應用就不會有任何問題。

  接下來,需要構建內核以支持 KDB。第一步是設置 CONFIG_KDB 選項。使用您喜歡的配置機制(xconfig 和 menuconfig 等)來完成這一步。轉到結尾處的“Kernel hacking”部分並選擇“Built-in Kernel Debugger support”選項。

  您還可以根據自己的偏好選擇其它兩個選項。選擇“Compile the kernel with frame pointers”選項(如果有的話)則設置 CONFIG_FRAME_POINTER 標誌。這把產生更好的堆棧回溯,因為幀指針寄存器被用作幀指針而不是通用寄存器。

  您還可以選擇“KDB off by default”選項。這把設置 CONFIG_KDB_OFF 標誌,並且在預設情況下把關閉 KDB。我們把在後面一節中對此進行詳細介紹。

  保存配置,然後退出。重新編譯內核。建議在構建內核之前執行“make clean”。用常用方式安裝內核並引導它。

初始化並設置環境變數

  您可以定義把在 KDB 初始化期間執行的 KDB 命令。需要在純文本文件 kdb_cmds 中定義這些命令,該文件位於 Linux 源代碼樹(當然是在打了補丁之後)的 KDB 目錄中。該文件還可以用來定義設置顯示和列印選項的環境變數。文件開頭的註釋提供了編輯文件方面的幫助。使用這個文件的缺點是,在您更改了文件之後需要重 新構建並重新安裝內核。

  激活 KDB

  如果編譯期間沒有選中 CONFIG_KDB_OFF,那麼在預設情況下 KDB 是活動的。否則,您需要顯式地激活它 - 通過在引導期間把 kdb=on 標誌傳遞給內核或者通過在掛裝了 /proc 之後執行該工作:

  #echo "1" >/proc/sys/kernel/kdb

  倒過來執行上述步驟則會取消激活 KDB。也就是說,如果預設情況下 KDB 是打開的,那麼把 kdb=off 標誌傳遞給內核或者執行下面這個操作把會取消激活 KDB:

  #echo "0" >/proc/sys/kernel/kdb

  我們可以看到 rmqueue() 被 __alloc_pages 調用,後者接下來又被 _alloc_pages 調用,以此類推。

  每一幀的第一個雙字(double word)指向下一幀,這後面緊跟著調用函數的地址。因此,跟蹤堆棧就變成一件輕鬆的工作了。

  go 命令可以有選擇地以一個地址作為參數。如果您想在某個特定地址處繼續執行,則可以提供該地址作為參數。另一個辦法是使用 rm 命令修改指令指針寄存器,然後只要輸入 go。如果您想跳過似乎會引起問題的某個特定指令或一組指令,這就會很有用。但是,請注意,該指令使用不慎會造成嚴重的問題,系統可能會嚴重崩潰。

  您可以利用一個名為 defcmd 的有用命令來定義自己的命令集。例如,每當遇到斷點時,您可能希望能同時檢查某個特殊變數、檢查某些寄存器的內容並轉儲堆棧。通常,您必須要輸入一系列命 令,以便能同時執行所有這些工作。defcmd 允許您定義自己的命令,該命令可以包含一個或多個預定義的 KDB 命令。然後只需要用一個命令就可以完成所有這三項工作。其語法如下:

  [code:1:6ddc15f4ad][0]kdb> defcmd name "usage" "help"

  [0]kdb> [defcmd] type the commands here

  [0]kdb> [defcmd] endefcmd [/code:1:6ddc15f4ad]

  例如,可以定義一個(簡單的)新命令 hari,它顯示從地址 0xc000000 開始的一行內存、顯示寄存器的內容並轉儲堆棧:

  [code:1:6ddc15f4ad][0]kdb> defcmd hari "" "no arguments needed"

  [0]kdb> [defcmd] md 0xc000000 1

  [0]kdb> [defcmd] rd

  [0]kdb> [defcmd] md %ebp 1

  [0]kdb> [defcmd] endefcmd [/code:1:6ddc15f4ad]

  該命令的輸出會是:

  [code:1:6ddc15f4ad][0]kdb> hari

  [hari]kdb> md 0xc000000 1

  0xc000000 00000001 f000e816 f000e2c3 f000e816

  [hari]kdb> rd

  eax = 0x00000000 ebx = 0xc0105330 ecx = 0xc0466000 edx = 0xc0466000

  ....

  ...

  [hari]kdb> md %ebp 1

  0xc0467fbc c0467fd0 c01053d2 00000002 000a0200

  [0]kdb> [/code:1:6ddc15f4ad]

  可以使用 bph 和 bpha 命令(假如體系結構支持使用硬體寄存器)來應用讀寫斷點。這意味著每當從某個特定地址讀取數據或將數據寫入該地址時,我們都可以對此進行控制。當調試數據 /內存毀壞問題時這可能會極其方便,在這種情況中您可以用它來識別毀壞的代碼/進程。

示例

  [code:1:6ddc15f4ad]每當將四個位元組寫入地址 0xc0204060 時就進入內核調試器:

  [0]kdb> bph 0xc0204060 dataw 4

  在讀取從 0xc000000 開始的至少兩個位元組的數據時進入內核調試器:

  [0]kdb> bph 0xc000000 datar 2[/code:1:6ddc15f4ad]

  [size=18:6ddc15f4ad]結束語[/size:6ddc15f4ad]

  對於執行內核調試,KDB 是一個方便的且功能強大的工具。它提供了各種選項,並且使我們能夠分析內存內容和數據結構。最妙的是,它不需要用另一台機器來執行調試。

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

  內存泄漏(即 malloc() 內存在對應的 free() 調用執行后永不被釋放)和緩衝區溢出(例如對以前分配到某數組的內存進行寫操作)是一些常見的問題,它們可能很難檢測到。這一部分將討論幾個調試工具,它們極大地簡化了檢測和找出內存問題的過程。

  MEMWATCH 由 Johan Lindh 編寫,是一個開放源代碼 C 語言內存錯誤檢測工具,您可以自己下載它(請參閱本文後面部分的參考資料)。只要在代碼中添加一個頭文件並在 gcc 語句中定義了 MEMWATCH 之後,您就可以跟蹤程序中的內存泄漏和錯誤了。MEMWATCH 支持 ANSI C,它提供結果日誌紀錄,能檢測雙重釋放(double-free)、錯誤釋放(erroneous free)、沒有釋放的內存(unfreed memory)、溢出和下溢等等。

  清單 1. 內存樣本(test1.c)

  [code:1:ff78191c7b]#include

  #include

  #include "memwatch.h"

  int main(void)

  {

  char *ptr1;

  char *ptr2;

  ptr1 = malloc(512);

  ptr2 = malloc(512);

  ptr2 = ptr1;

  free(ptr2);

  free(ptr1);

  }[/code:1:ff78191c7b]

  清單 1 中的代碼將分配兩個 512 位元組的內存塊,然後指向第一個內存塊的指針被設定為指向第二個內存塊。結果,第二個內存塊的地址丟失,從而產生了內存泄漏。

  現在我們編譯清單 1 的 memwatch.c。下面是一個 makefile 示例:

  test1

  [code:1:ff78191c7b]gcc -DMEMWATCH -DMW_STDIO test1.c memwatch c -o test1[/code:1:ff78191c7b]

  當您運行 test1 程序后,它會生成一個關於泄漏的內存的報告。清單 2 展示了示例 memwatch.log 輸出文件。

  清單 2. test1 memwatch.log 文件

  [code:1:ff78191c7b]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[/code:1:ff78191c7b]

MEMWATCH 為您顯示真正導致問題的行。如果您釋放一個已經釋放過的指針,它會告訴您。對於沒有釋放的內存也一樣。日誌結尾部分顯示統計信息,包括泄漏了多少內存,使用了多少內存,以及總共分配了多少內存。

  [color=blue:ff78191c7b]YAMD[/color:ff78191c7b]

  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 輸出

  [code:1:ff78191c7b]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.[/code:1:ff78191c7b]

  YAMD 顯示我們已經釋放了內存,而且存在內存泄漏。讓我們在清單 4 中另一個樣本程序上試試 YAMD。

清單 4. 內存代碼(test2.c)

  [code:1:ff78191c7b]#include

  #include

  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);

  }[/code:1:ff78191c7b]

  您可以使用下面的命令來啟動 YAMD:

  [code:1:ff78191c7b]./run-yamd /usr/src/test/test2/test2 [/code:1:ff78191c7b]

  清單 5 顯示了在樣本程序 test2 上使用 YAMD 得到的輸出。YAMD 告訴我們在 for 循環中有“越界(out-of-bounds)”的情況。

  清單 5. 使用 YAMD 的 test2 輸出

  [code:1:ff78191c7b]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.[/code:1:ff78191c7b]

  MEMWATCH 和 YAMD 都是很有用的調試工具,它們的使用方法有所不同。對於 MEMWATCH,您需要添加包含文件 memwatch.h 並打開兩個編譯時間標記。對於鏈接(link)語句,YAMD 只需要 -g 選項。

  [color=blue:ff78191c7b]Electric Fence[/color:ff78191c7b]

 

(未完,待續)



[火星人 ] 揭開Linux系統內核調試器神秘面紗(上)已經有609次圍觀

http://coctec.com/docs/linux/show-post-70182.html