調 試zSeries上的Linux應用程序類似於調試其他體系結構上的Linux應用程序。對於有經驗的Linux開發人員,最大的挑戰是理解新的系統體系 結構。對於剛接觸Linux的大型機開發人員,掌握新的調試工具似乎是一項令人畏懼的任務。不要害怕。本文將提供一些有用的提示來幫助您入門。
學問來自實踐,但是對於調試工具,在沒有出現問題而迫使您去修復它們之前,“實踐”是不會發生的。考慮到這點,下面將提供讓您入門的“速成”指南。
User Debug 日志記錄
調試一個崩潰的程序的第一步是弄清哪里出了錯。zSeries 上的Linux內核具有這樣一個內置特性,它在用戶進程崩潰時記錄一些基本的調試信息。要啟用這個特性,請以 root 用戶身份執行如下命令:
<ccid_code>echo 1 >> /proc/sys/kernel/userprocess_debug |
當某個進程崩潰時,日志文件(/var/log/messages)中就會給出附加的信息,包括程序終止原因、故障地址,以及包含程序狀態字(PSW)、通用寄存器和訪問寄存器的簡要寄存器轉儲。
<ccid_code>Mar 31 11:34:28 l02 kernel: User process fault: interruption code 0x10 |
圖 1 表明程序(名為“simple”)以一個程序中斷代碼 0x10 終止(操作系統原理表明這是一個段轉換錯誤),而故障地址為 0。毫無疑問,有人使用了空指針。現在我們知道發生了什么,下面需要弄清它發生在何處。
基本的診斷
User Debug日志條目所提供的信息可用於確定程序的崩潰位置。一些可用的工具可幫助解決您可能會遇到的各種程序終止問題。我們將在本文中逐步介紹那些工具。
首 先,讓我們檢查一下該日志條目中的用戶 PSW。該 PSW 包含指令地址、狀態碼以及關於機器狀態的其他信息。眼下,我們僅關心指令地址(第33至第63位)。為簡化起見,讓我們假設用戶PSW是 070dc000 80400618。記住,我們是在考察一個 ESA/390(31 位尋址)PSW。第32位不是指令地址的一部分,它是指示 31 位尋址模式的標志,但是在研究 PSW 值時必須處理它。為了獲得實際的指令指針,可把PSW的第二個字減去 0x80000000。結果是一個指令地址 0x400618。為了定位代碼,您需要可執行文件中的一些信息。首先使用readelf來打印一些程序頭信息。
<ccid_code>Elf file type is EXEC (Executable file) |
圖 2 顯示了readelf -l simple的結果(記住“simple”是我們的測試程序的名稱)。在Program Headers部分,第一個 LOAD 行提供了關於程序從哪里加載的信息。在 Flg 列,該段被標記為 R(read)E(executable)。VirtAddr是程序開始加載的地址。MemSiz是正在被加載到這個段中的代碼長度。把它加到 VirtAddr上,這個程序的基本地址范圍就是0x400000-0x400990。程序發生崩潰的指令地址為0x400618,在程序的加載范圍之 內。現在我們知道了問題直接發生在代碼中。
如果可執行文件包括調試符號,那么確定哪一行代碼導致了問題是可以做到的。對該地址和可執行文件使用addr2line 程序,如下所示:
<ccid_code>addr2line -e simple 0x400618 |
將返回:
<ccid_code>/home/devuser/simple.c:34 |
要研究該問題,可以檢查第 34 行。
對 於圖1中原始的程序崩潰,PSW 為070dc000 c00ab738。要獲得指令地址,可減去0x80000000。結果為0x400ab738。這個地址並不准確地落在我們的小程序之內。那么,它是什么 呢?是來自共享庫的代碼。如果對可執行文件運行ldd 命令(ldd simple),將會返回程序運行所需的共享對象的列表,以及該庫在那里可用的地址。
<ccid_code>libc.so.6 => /lib/libc.so.6 (0x40021000) |
該指令地址對應於加載libc.so.6的地址。在我們的簡單測試案例中,只需要兩個共享對象。其他應用程序可能需要更多共享對象,這使得ldd的輸出更加復雜。我們將以perl作為例子。 輸入:
<ccid_code>ldd /usr/bin/perl |
將得到:
<ccid_code>libnsl.so.1 => /lib/libnsl.so.1 (0x40021000) |
所需要的一切都在那里了,但是我發現對於這個進程,下面的內容讀起來更快一點:
<ccid_code>ldd /usr/bin/perl | awk ‘{print? $4 “ “ $3 }’ | sort |
現 在我們來確定崩潰發生在libc中的何處。假設libc.so.6的加載地址是0x40021000,從指令地址 0x400ab738減去它,結果為0x8a738。這是進入libc.so.6 的偏移。使用nm命令,從libc.so.6轉儲符號,然后嘗試確定該地址位於哪個函數中。對於libc.so.6,nm將生成7,000多行輸出。通過 對計算得出的偏移部分執行 grep(正則表達式查找程序)可以削減必須檢查的數據量。輸入:
<ccid_code>nm /lib/libc.so.6 | sort | grep 0008a |
將返回 66 行,在該輸出的中間,我們會發現:
<ccid_code>0008a6fc T memcpy |
該 偏移落在memcpy中的某個位置。在此例中,一個空指針被當作目標地址傳遞給了memcpy。我們在何處調用的memcpy呢?問得好。我們可以通過檢 查輸出在日志文件中的寄存器轉儲來確定目標區域。寄存器14包含執行某個函數調用時的返回地址。根據圖1,R14是0x8040066e,它在截去高位之 后產生一個地址 0x40066e。這個地址落在我們的程序范圍之內,因此可以運行addr2line來確定該地址在何處。輸入:
<ccid_code>addr2line -e simple 0x40066e |
將返回:
<ccid_code>/home/devuser/simple.c:36 |
這是我們調用memcpy之后的那一行。關於addr2line的一點補充:如果可執行文件中沒有包括調試符號,您將獲得??:0 作為響應。
