掌握 Linux 調試技術

 

掌握 Linux 調試技術

在 Linux 上找出並解決程序錯誤的主要方法html

Steve Best ( sbest@us.ibm.com)JFS 核心小組成員,IBM

 

簡介: 您能夠用各類方法來監控運行着的用戶空間程序:能夠爲其運行調試器並單步調試該程序,添加打印語句,或者添加工具來分析程序。本文描述了幾種能夠用來調試在 Linux 上運行的程序的方法。咱們將回顧四種調試問題的狀況,這些問題包括段錯誤,內存溢出和泄漏,還有掛起。node

 

本文討論了四種調試 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)來顯示引起掛起問題的組件的信息。linux

常見調試方法算法

當您的程序中包含錯誤時,極可能在代碼中某處有一個條件,您認爲它爲真(true),但其實是假(false)。找出錯誤的過程也就是在找出錯誤後推翻之前一直確信爲真的某個條件過程。編程

如下幾個示例是您可能確信成立的條件的一些類型:數組

  • 在源代碼中的某處,某變量有特定的值。
  • 在給定的地方,某個結構已被正確設置。
  • 對於給定的 if-then-else 語句, if 部分就是被執行的路徑。
  • 當子例程被調用時,該例程正確地接收到了它的參數。

找出錯誤也就是要肯定上述全部狀況是否存在。若是您確信在子例程被調用時某變量應該有特定的值,那麼就檢查一下狀況是否如此。若是您相信 if 結構會被執行,那麼也檢查一下狀況是否如此。一般,您的假設都會是正確的,但最終您會找到與假設不符的狀況。結果,您就會找出發生錯誤的地方。sass

調試是您沒法逃避的任務。進行調試有不少種方法,好比將消息打印到屏幕上、使用調試器,或只是考慮程序執行的狀況並仔細地揣摩問題所在。安全

在修正問題以前,您必須找出它的源頭。舉例來講,對於段錯誤,您須要瞭解段錯誤發生在代碼的哪一行。一旦您發現了代碼中出錯的行,請肯定該方法中變量的值、方法被調用的方式以及關於錯誤如何發生的詳細狀況。使用調試器將使找出全部這些信息變得很簡單。若是沒有調試器可用,您還可使用其它的工具。(請注意,產品環境中可能並不提供調試器,並且 Linux 內核沒有內建的調試器。)數據結構

實用的內存和內核工具

您可使用 Linux 上的調試工具,經過各類方式跟蹤用戶空間和內核問題。請使用下面的工具和技術來構建和調試您的源代碼: 
用戶空間工具app

 

  • 內存工具: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 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 調試環境:

  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 中的錯誤時,請試試這些工具中的某一個。


參考資料

關於做者

Steve Best 目前在作 Linux 項目的日誌紀錄文件系統(Journaled File System,JFS)的工做。Steve 在操做系統方面有豐富的從業經驗,他的着重的領域是文件系統、國際化和安全性。

 
 
 

使用strace+pstack利器分析程序性能

引言

有時咱們須要對程序進行優化、減小程序響應時間。除了一段段地對代碼進行時間複雜度分析,咱們還有更便捷的方法嗎?

若能直接找到影響程序運行時間的函數調用,再有針對地對相關函數進行代碼分析和優化,那相比漫無目的地看代碼,效率就高多了。

將strace和pstack工具結合起來使用,就能夠達到以上目的。strace跟蹤程序使用的底層系統調用,可輸出系統調用被執行的時間點以及各個調用耗時;pstack工具對指定PID的進程輸出函數調用棧。

下面咱們經過一個簡單的消息收發程序,說明使用strace、pstack進行程序分析的具體方法。

程序說明
該程序是一個簡單的socket程序,由server/client組成。server端監聽某端口,等待client的鏈接,client鏈接server後定時向server發送消息,server每接收一條消息後向client發送響應消息。程序server與client交互以下圖示:

在程序運行起來以後,發現server接收到client的submit消息以後,須要較長時間才發出resp響應。經過tcpdump抓包發現,time2與time1的時間間隔在1s左右:


由上初步分析可知,消息響應慢是server端程序問題。下面咱們來看如何使用strace和pstack分析server端程序響應慢的緣由。

 

strace查看系統調用
首先咱們拉起server/client程序,並使用strace對server進程進行跟蹤:

# ps -elf | grep server | grep -v grep
0 S root 16739 22642 0 76 0 - 634 1024 14:26 pts/2 00:00:00 ./server
# strace -o server.strace -Ttt -p 16739
Process 16739 attached - interrupt to quit

 

稍等一段時間以後,咱們將strace停掉, server.strace文件中有如下輸出:

複製代碼
14:46:39.741366 select(8, [3 4], NULL, NULL, {1, 0}) = 1 (in [4], left {0, 1648}) <0.998415>
14:46:40.739965 recvfrom(4, "hello", 6, 0, NULL, NULL) = 5 <0.000068>
14:46:40.740241 write(1, "hello\n", 6)  = 6 <0.000066>
14:46:40.740414 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 <0.000046>
14:46:40.740565 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 <0.000048>
14:46:40.740715 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 <0.000046>
14:46:40.740853 nanosleep({1, 0}, {1, 0}) = 0 <1.000276>
14:46:41.741284 sendto(4, "hello\0", 6, 0, NULL, 0) = 6 <0.000111>
複製代碼

能夠看到server接收數據以後(對應recvfrom調用),通過1s左右時間將消息發出(對應sendto調用),從響應時間看,與抓包的結果吻合。又能夠看出nanosleep系統調用耗費了1s時間。

於是能夠判定響應延時由nanosleep對應的函數調用形成。

那具體是哪個函數調用呢?在strace輸出結果中並不能找到答案,因其輸出顯示都是系統調用,要顯示程序中函數調用棧信息,就輪到pstack上場了。

 

pstack查看函數堆棧
pstack是一個腳本工具,其核心實現就是使用了gdb以及thread apply all bt命令,下面咱們使用pstack查看server進程函數堆棧:

# sh pstack.sh 16739
#0 0x00002ba1f8152650 in __nanosleep_nocancel () from /lib64/libc.so.6
#1 0x00002ba1f8152489 in sleep () from /lib64/libc.so.6
#2 0x00000000004007bb in ha_ha ()
#3 0x0000000000400a53 in main ()


從以上信息能夠看出,函數調用關係爲:main->ha_ha->sleep,於是咱們能夠找到ha_ha函數進行分析和優化修改。

小結
本文經過一個server/client程序事例,說明了使用strace和pstack分析響應延時的方法。

由最初server端響應慢現象,到使用strace跟蹤出具體耗時的系統調用,再到使用pstack查到程序中具體的耗時函數,一步步找到了影響程序運行時間的程序代碼。

更多地瞭解底層,從操做系統層面着手,更有助於程序性能分析與優化。

相關文章
相關標籤/搜索