歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux服務器

掌握 Linux 調試技術

謝 謝 收 藏 http://www.qqread.com/linux/2006/06/f775123061.html
在 Linux 上找出並解決程序錯誤的主要方法
  您可以用各種方法來監控運行著的用戶空間程序:可以為其運行調試器並單步調試該程序,添加打印語句,或者添加工具來分析程序。本文描述了幾種可以用來調試在 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 上的調試工具,通過各種方式跟蹤用戶空間和內核問題。請使用下面的工具和技術來構建和調試您的源代碼:
  用戶空間工具:
  
  內存工具: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
  #include
  #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
  #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);
  }
  
  
  
  您可以使用下面的命令來啟動 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 program name 命令啟動 gdb。gdb 將載入可執行程序符號並顯示輸入提示符,讓您可以開始使用調試器。您可以通過三種方式用 gdb 查看進程:
  
  使用 attach 命令開始查看一個已經運行的進程;attach 將停止進程。
  
  
  使用 run 命令執行程序並從頭開始調試程序。
  
  
  查看已有的核心文件來確定進程終止時的狀態。要查看核心文件,請用下面的命令啟動 gdb。
  gdb programname corefilename
  要用核心文件進行調試,您不僅需要程序的可執行文件和源文件,還需要核心文件本身。要用核心文件啟動 gdb,請使用 -c 選項:
  
  gdb -c core programname
  
  gdb 顯示哪行代碼導致程序發生核心轉儲。
  
  在運行程序或連接到已經運行的程序之前,請列出您覺得有錯誤的源代碼,設置斷點,然後開始調試程序。您可以使用 help 命令查看全面的 gdb 在線幫助和詳細的教程。
  
  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 是一個腳本示例,它將您在開發機器上構建的內核和模塊引入測試機器。您需要修改下面幾項:
  
  
  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 JFSn"));
  / *
  * read/validate superblock
  * (initialize mount inode from the superblock)
  * /
  if ((rc = chkSuper(sb))) {
  goto errout20;
  }
  108 ptr=0; /* line 2 added */
  109 printk("%dn",*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("%dn",*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 消息的過程

Copyright © Linux教程網 All Rights Reserved