Linux addr2line具體應(yīng)用指南
調(diào)試zSeries上的Linux應(yīng)用程序類似于調(diào)試其他體系結(jié)構(gòu)上的Linux應(yīng)用程序。對于有經(jīng)驗的Linux開發(fā)人員,最大的挑戰(zhàn)是理解新的系統(tǒng)體系結(jié)構(gòu)。對于剛接觸Linux的大型機(jī)開發(fā)人員,掌握新的調(diào)試工具似乎是一項令人畏懼的任務(wù)。不要害怕。本文將提供Linux addr2line一些有用的提示來幫助您入門。
UserDebug日志記錄
調(diào)試一個崩潰的程序的第一步是弄清哪里出了錯。zSeries上的Linux內(nèi)核具有這樣一個內(nèi)置特性,它在用戶進(jìn)程崩潰時記錄一些基本的調(diào)試信息。要啟用這個特性,請以root用戶身份執(zhí)行如下命令:
echo1>>/proc/sys/kernel/userprocess_debug
當(dāng)某個進(jìn)程崩潰時,日志文件(/var/log/messages)中就會給出附加的信息,包括程序終止原因、故障地址,以及包含程序狀態(tài)字(PSW)、通用寄存器和訪問寄存器的簡要寄存器轉(zhuǎn)儲。
- Mar3111:34:28l02kernel:Userprocessfault:interruptioncode0x10
- Mar3111:34:28l02kernel:failingaddress:0
- Mar3111:34:28l02kernel:CPU:1
- Mar3111:34:28l02kernel:Processsimple(pid:30122,stackpage=05889000)
- Mar3111:34:28l02kernel:
- Mar3111:34:28l02kernel:UserPSW:070dc000c00ab738
- Mar3111:34:28l02kernel:task:05888000ksp:05889f08pt_regs:05889f68
- Mar3111:34:28l02kernel:UserGPRS:Mar3111:34:28l02kernel:00000000004019a0004019a000000000
- Mar3111:34:28l02kernel:00000003c00ab732004008f800400338
- Mar3111:34:28l02kernel:40018ffc0040061c40018e347ffff800
- Mar3111:34:28l02kernel:00400434804006248040066e7ffff800
- Mar3111:34:28l02kernel:UserACRS:
- Mar3111:34:28l02kernel:00000000000000000000000000000000
- Mar3111:34:28l02kernel:00000001000000000000000000000000
- Mar3111:34:28l02kernel:00000000000000000000000000000000
- Mar3111:34:28l02kernel:00000000000000000000000000000000
- Mar3111:34:28l02kernel:UserCode:
- Mar3111:34:28l02kernel:4440500007fea74a0001185418431835a8240000
上面表明程序(名為“simple”)以一個程序中斷代碼0x10終止(操作系統(tǒng)原理表明這是一個段轉(zhuǎn)換錯誤),而故障地址為0。毫無疑問,有人使用了空指針?,F(xiàn)在我們知道發(fā)生了什么,下面需要弄清它發(fā)生在何處。
Linux addr2line基本的診斷
UserDebug日志條目所提供的信息可用于確定程序的崩潰位置。一些可用的工具可幫助解決您可能會遇到的各種程序終止問題。我們將在本文中逐步介紹那些工具。
首先,讓我們檢查一下該日志條目中的用戶PSW。該P(yáng)SW包含指令地址、狀態(tài)碼以及關(guān)于機(jī)器狀態(tài)的其他信息。眼下,我們僅關(guān)心指令地址(第33至第63位)。為簡化起見,讓我們假設(shè)用戶PSW是070dc00080400618。記住,我們是在考察一個ESA/390(31位尋址)PSW。第32位不是指令地址的一部分,它是指示31位尋址模式的標(biāo)志,但是在研究PSW值時必須處理它。為了獲得實際的指令指針,可把PSW的第二個字減去0x80000000。結(jié)果是一個指令地址0x400618。為了定位代碼,您需要可執(zhí)行文件中的一些信息。首先使用readelf來打印一些程序頭信息。
- ElffiletypeisEXEC(Executablefile)Entrypoint0x400474Thereare6programheaders,startingatoffset52ProgramHeaders:
- TypeOffsetVirtAddrPhysAddrFileSizMemSizFlgAlign
- PHDR0x0000340x004000340x004000340x000c00x000c0RE0x4
- INTERP0x0000f40x004000f40x004000f40x0000d0x0000dR0x1[Requestingprograminterpreter:/lib/ld.so.1]
- LOAD0x0000000x004000000x004000000x009900x00990RE0x1000
- LOAD0x0009900x004019900x004019900x000fc0x00114RW0x1000
- DYNAMIC0x0009ac0x004019ac0x004019ac0x000a00x000a0RW0x4
- NOTE0x0001040x004001040x004001040x000200x00020R0x4
- SectiontoSegmentmapping:SegmentSections...
- 00
- 01.interp
- 02.interp.note.ABI-tag.hash.dynsym.dynstr.gnu.version
- .gnu.version_r.rela.got.rela.plt.init.plt.text.fini.rodata
- 03.data.eh_frame.dynamic.ctors.dtors.got.bss
- 04.dynamic
- 05.note.ABI-tag
上述顯示了readelf-lsimple的結(jié)果(記住“simple”是我們的測試程序的名稱)。在ProgramHeaders部分,第一個LOAD行提供了關(guān)于程序從哪里加載的信息。在Flg列,該段被標(biāo)記為R(read)E(executable)。VirtAddr是程序開始加載的地址。MemSiz是正在被加載到這個段中的代碼長度。把它加到VirtAddr上,這個程序的基本地址范圍就是0x400000-0x400990。程序發(fā)生崩潰的指令地址為0x400618,在程序的加載范圍之內(nèi)?,F(xiàn)在我們知道了問題直接發(fā)生在代碼中。
如果可執(zhí)行文件包括調(diào)試符號,那么確定哪一行代碼導(dǎo)致了問題是可以做到的。對該地址和可執(zhí)行文件使用addr2line程序,如下所示:
addr2line-esimple0x400618
將返回:
/home/devuser/simple.c:34
要研究該問題,可以檢查第34行。
對于Linux addr2line原始的程序崩潰,PSW為070dc000c00ab738。要獲得指令地址,可減去0x80000000。結(jié)果為0x400ab738。這個地址并不準(zhǔn)確地落在我們的小程序之內(nèi)。那么,它是什么呢?是來自共享庫的代碼。如果對可執(zhí)行文件運(yùn)行l(wèi)dd命令(lddsimple),將會返回程序運(yùn)行所需的共享對象的列表,以及該庫在那里可用的地址。
libc.so.6=>/lib/libc.so.6(0x40021000)/lib/ld.so.1=>/lib/ld.so.1(0x40000000)
該指令地址對應(yīng)于加載libc.so.6的地址。在我們的簡單測試案例中,只需要兩個共享對象。其他應(yīng)用程序可能需要更多共享對象,這使得ldd的輸出更加復(fù)雜。我們將以perl作為例子。輸入:
ldd/usr/bin/perl
將得到:
- libnsl.so.1=>
- /lib/libnsl.so.1(0x40021000)libdl.so.2=>
- /lib/libdl.so.2(0x40039000)libm.so.6=>
- /lib/libm.so.6(0x4003d000)libc.so.6=>
- /lib/libc.so.6(0x40064000)libcrypt.so.1=>
- /lib/libcrypt.so.1(0x4018f000)/lib/ld.so.1=>
- /lib/ld.so.1(0x40000000)
所需要的一切都在那里了,但是我發(fā)現(xiàn)對于這個進(jìn)程,下面的內(nèi)容讀起來更快一點(diǎn):
- ldd/usr/bin/perl|awk‘{print?$4““$3}’
- |sort(0x40000000)/lib/ld.so.1(0x40021000)
- /lib/libnsl.so.1(0x40039000)
- /lib/libdl.so.2(0x4003d000)
- /lib/libm.so.6(0x40064000)
- /lib/libc.so.6(0x4018f000)
- /lib/libcrypt.so.1
現(xiàn)在我們來確定Linux addr2line崩潰發(fā)生在libc中的何處。假設(shè)libc.so.6的加載地址是0x40021000,從指令地址0x400ab738減去它,結(jié)果為0x8a738。這是進(jìn)入libc.so.6的偏移。使用nm命令,從libc.so.6轉(zhuǎn)儲符號,然后嘗試確定該地址位于哪個函數(shù)中。對于libc.so.6,nm將生成7,000多行輸出。通過對計算得出的偏移部分執(zhí)行g(shù)rep(正則表達(dá)式查找程序)可以削減必須檢查的數(shù)據(jù)量。輸入:
nm/lib/libc.so.6|sort|grep0008a
將返回66行,在該輸出的中間,我們會發(fā)現(xiàn):
0008a6fcTmemcpy0008a754t_wordcopy_fwd_aligned
該偏移落在memcpy中的某個位置。在此例中,一個空指針被當(dāng)作目標(biāo)地址傳遞給了memcpy。我們在何處調(diào)用的memcpy呢?問得好。我們可以通過檢查輸出在日志文件中的寄存器轉(zhuǎn)儲來確定目標(biāo)區(qū)域。寄存器14包含執(zhí)行某個函數(shù)調(diào)用時的返回地址。根據(jù)圖1,R14是0x8040066e,它在截去高位之后產(chǎn)生一個地址0x40066e。這個地址落在我們的程序范圍之內(nèi),因此可以運(yùn)行addr2line來確定該地址在何處。輸入:
addr2line-esimple0x40066e
將返回:
/home/devuser/simple.c:36
這是我們調(diào)用memcpy之后的那一行。關(guān)于addr2line的一點(diǎn)補(bǔ)充:如果可執(zhí)行文件中沒有包括調(diào)試符號,您將獲得??:0作為響應(yīng)。
【編輯推薦】