歡迎您光臨本站 註冊首頁

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

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  多數 Linux 分發版包含一個 Electric Fence 包,不過您也可以選擇下載它。Electric Fence 是一個由 Bruce Perens 編寫的 malloc() 調試庫。它就在您分配內存後分配受保護的內存。如果存在 fencepost 錯誤(超過數組末尾運行),程序就會產生保護錯誤,並立即結束。通過結合 Electric Fence 和 gdb,您可以精確地跟蹤到哪一行試圖訪問受保護內存。 Electric Fence 的另一個功能就是能夠檢測內存泄漏。

  strace 命令是一種強大的工具,它能夠顯示所有由用戶空間程序發出的系統調用。strace 顯示這些調用的參數並返回符號形式的值。 strace 從內核接收信息,而且不需要以任何特殊的方式來構建內核。將跟蹤信息發送到應用程序及內核開發者都很有用。在清單 6 中,分區的一種格式有錯誤,清單顯示了 strace 的開頭部分,內容是關於調出創建文件系統操作(mkfs)的。strace 確定哪個調用導致問題出現。

  清單 6. mkfs 上 strace 的開頭部分

  [code:1:95b8e28830]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)

  = ?[/code:1:95b8e28830]

  清單 6 顯示 ioctl 調用導致用來格式化分區的 mkfs 程序失敗。ioctl BLKGETSIZE64 失敗。(BLKGET- SIZE64 在調用 ioctl 的源代碼中定義。) BLKGETSIZE64 ioctl 將被添加到 Linux 中所有的設備,而在這裡,邏輯卷管理器還不支持它。因此,如果 BLKGETSIZE64 ioctl 調用失敗,mkfs 代碼將改為調用較早的 ioctl 調用;這使得 mkfs 適用於邏輯卷管理器。

[b:627becdd94][size=18:627becdd94] 第 3 種情況:使用 gdb 和 Oops[/size:627becdd94][/b:627becdd94]

  您可以從命令行使用 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 在線幫助和詳細的教程。

  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 中有下面四行代碼:

  [code:1:627becdd94]oset remotebaud 115200

  osymbol-file vmlinux

  otarget remote /dev/ttyS0

  oset output-radix 16 [/code:1:627becdd94]

  5.將 append=gdb 這一行添加到 lilo,lilo 是用來在引導內核時選擇使用哪個內核的引導載入程序。

  [code:1:627becdd94]oimage=/boot/bzImage-2.4.17

  olabel=gdb2417

  oread-only

  oroot=/dev/sda8

  oappend="gdb gdbttyS=1 gdb-baud=115200 nmi_watchdog=0" [/code:1:627becdd94]

  清單 7 是一個腳本示例,它將您在開發機器上構建的內核和模塊引入測試機器。您需要修改下面幾項:

  ?best@sfb:用戶標識和機器名。

  ?/usr/src/linux-2.4.17:內核源代碼樹的目錄。

  ?bzImage-2.4.17:測試機器上將引導的內核名。

  ?rcp 和 rsync:必須允許它在構建內核的機器上運行。

清單 7. 引入測試機器的內核和模塊的腳本

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

  現在我們可以通過改為使用內核源代碼樹開始的目錄來啟動開發機器上的 gdb 程序了。在本示例中,內核源代碼樹位於 /usr/src/linux-2.4.17。輸入 gdb 啟動程序。

  如果一切正常,測試機器將在啟動過程中停止。輸入 gdb 命令 cont 以繼續啟動過程。一個常見的問題是,空數據機電纜可能會被連接到錯誤的串口。如果 gdb 不啟動,將埠改為第二個串口,這會使 gdb 啟動。

使用 kgdb 調試內核問題

  清單 8 列出了 jfs_mount.c 文件的源代碼中被修改過的代碼,我們在代碼中創建了一個空指針異常,從而使代碼在第 109 行產生段錯誤。

  清單 8. 修改過後的 jfs_mount.c 代碼

  [code:1:627becdd94]int jfs_mount(struct super_block *sb)

  {

  ...

  int ptr; /* line 1 added */

  jFYI(1, ("

  Mount JFS

  "));

  / *

  * read/validate superblock

  * (initialize mount inode from the superblock)

  * /

  if ((rc = chkSuper(sb))) {

  goto errout20;

  }

  108 ptr=0; /* line 2 added */

  109 printk("%d

  ",*ptr); /* line 3 added */[/code:1:627becdd94]

  清單 9 在向文件系統發出 mount 命令之後顯示一個 gdb 異常。kgdb 提供了幾條命令,如顯示數據結構和變數值以及顯示系統中的所有任務處於什麼狀態、它們駐留在何處、它們在哪些地方使用了 CPU 等等。清單 9 將顯示回溯跟蹤為該問題提供的信息;where 命令用來執行反跟蹤,它將告訴被執行的調用在代碼中的什麼地方停止。

  清單 9. gdb 異常和反跟蹤

  [code:1:627becdd94]mount -t jfs /dev/sdb /jfs

  Program received signal SIGSEGV, Segmentation fault.

  jfs_mount (sb=0xf78a3800) at jfs_mount.c:109

  109 printk("%d

  ",*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)[/code:1:627becdd94]

  下一部分還將討論這個相同的 JFS 段錯誤問題,但不設置調試器,如果您在非 kgdb 內核環境中執行清單 8 中的代碼,那麼它使用內核可能生成的 Oops 消息。

  [color=darkblue:627becdd94]Oops 分析[/color:627becdd94]

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

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

  [color=darkblue:627becdd94]格式化 Oops 消息[/color:627becdd94]

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 消息

  [code:1:627becdd94]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: []...

  ... 15:59:37 sfb1 kernel: [

  ... 15:59:37 sfb1 kernel: Code: 8b 2d 00 00 00 00 55 ...

  >>EIP; c01588fc <=====

  ...

  Trace; c0106cf3

  Code; c01588fc

  00000000 <_EIP>:

  Code; c01588fc <=====

  0: 8b 2d 00 00 00 00 mov 0x0,%ebp <=====

  Code; c0158902

  6: 55 push %ebp[/code:1:627becdd94]

  接下來,您要確定 jfs_mount 中的哪一行代碼引起了這個問題。Oops 消息告訴我們問題是由位於偏移地址 3c 的指令引起的。做這件事的辦法之一是對 jfs_mount.o 文件使用 objdump 實用程序,然後查看偏移地址 3c。Objdump 用來反彙編模塊函數,看看您的 C 源代碼會產生什麼彙編指令。清單 11 顯示了使用 objdump 后您將看到的內容,接著,我們查看 jfs_mount 的 C 代碼,可以看到空值是第 109 行引起的。偏移地址 3c 之所以很重要,是因為 Oops 消息將該處標識為引起問題的位置。

  清單 11. jfs_mount 的彙編程序清單

  [code:1:627becdd94]109 printk("%d

  ",*ptr);

  objdump jfs_mount.o

  jfs_mount.o: file format elf32-i386

  Disassembly of section .text:

  00000000 :

  0:55 push %ebp

  ...

  2c: e8 cf 03 00 00 call 400

  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

  3c: 8b 2d 00 00 00 00 mov 0x0,%ebp << problem line above

  42: 55 push %ebp[/code:1:627becdd94]

  [color=darkblue:627becdd94]kdb[/color:627becdd94]

Linux 內核調試器(Linux kernel debugger,kdb)是 Linux 內核的補丁,它提供了一種在系統能運行時對內核內存和數據結構進行檢查的辦法。請注意,kdb 不需要兩台機器,不過它也不允許您像 kgdb 那樣進行源代碼級別上的調試。您可以添加額外的命令,給出該數據結構的標識或地址,這些命令便可以格式化和顯示基本的系統數據結構。目前的命令集允許您控 制包括以下操作在內的內核操作:

  處理器單步執行

  執行到某條特定指令時停止

  當存取(或修改)某個特定的虛擬內存位置時停止

  當存取輸入/輸出地址空間中的寄存器時停止

  對當前活動的任務和所有其它任務進行堆棧回溯跟蹤(通過進程 ID)

  對指令進行反彙編

  [color=blue:627becdd94]追擊內存溢出[/color:627becdd94]

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

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

  我們用多種不同技術來解決這個問題,其中一種是使用調試器,另一種是在源代碼中添加跟蹤功能。在我職業生涯的大概也是這個時候,我便開始關注內 存調試工具,希望能更快更有效地解決這些類型的問題。在開始一個新項目時,我最先做的事情之一就是運行 MEMWATCH 和 YAMD,看看它們是不是會指出內存管理方面的問題。

  內存泄漏是應用程序中常見的問題,不過您可以使用本文所講述的工具來解決這些問題。



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

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