天天看點

linux/unix 段錯誤捕獲【續】

背景知識:

·linux/unix下動态連結庫的基本原理

·/proc/pid/maps檔案的基本格式

·動态連結庫:在程序執行過程中動态加載,程序間可以共享代碼,可用在釋出更新包等場合

概述:

    為簡潔起見,後文用“原方法”指代前一文章内的分析方法。

正文:

linux/unix 段錯誤捕獲【續】

指令行下執行的指令行次序如下:

[root@redhat tcpBreak]# g++ -fPIC -shared -g -o libtest.so lib.cpp 

[root@redhat tcpBreak]# g++ -g test.cpp ./libtest.so

[root@redhat tcpBreak]# ./a.out

[root@redhat tcpBreak]# addr2line...(省略)

一、出錯代碼在動态連結庫内時,原方法的輸出

    有些情況下,我們會采用動态連結庫,如果出錯代碼行恰巧在動态連結庫内,原方法仍可得到出錯時的位址。例如:

signal[8] catched when running code at 8048ab3

signal[8] catched when running code at 4001771b

signal[8] catched when running code at 400176fd

    此例中,調用addr2line小工具的輸出為

[root@redhat tcpBreak]# addr2line 8048ab3 4001771b 400176fd -s -C -f -e a.out

main

test.cpp:15

??

??:0

    顯然,後面兩個位址翻譯不出來了,因為其實出錯代碼根本不在可執行檔案 a.out 内,而是位于一個動态連結庫内。

二、動态連結庫的偏移位址

     動态連結庫無非就是編譯後的代碼,裡面有一些基本的段、符号資訊。如果出錯代碼行在動态連結庫内,那必然可以從動态連結庫内找到出錯時的代碼行号。

     好吧,那就讓我們試一下:

[root@redhat tcpBreak]# addr2line 4001771b 400176fd -s -C -f -e libtest.so

    還是翻譯不出來。當然出不來了,因為程序挂掉時輸出的位址,和動态連結庫檔案内的靜态偏移位址根本就不是一回事。是以我們需要知道出錯時,所輸出的代碼位址與動态連結庫偏移位址之間的關系。

    事實上,每一個程序都對應了一個 /proc/pid 目錄,下面記載了諸多與該程序相關的資訊,其中有一個maps檔案,裡面記錄了各個動态連結庫的加載位址。我們隻需要根據所得到的出錯位址,以及這個maps檔案,就可以得出具體是哪一個庫,相應的偏移位址是多少。本文用例産生的輸出為:

-------------------------- 程序挂掉時的MAPS檔案 --------------------------

08048000-08049000 r-xp 00000000 00:09 17256 /mnt/hgfs/share/net/tcpBreak/a.out

08049000-0804a000 rw-p 00001000 00:09 17256 /mnt/hgfs/share/net/tcpBreak/a.out

0804a000-0804b000 rwxp 00000000 00:00 0

40000000-40015000 r-xp 00000000 08:02 271023 /lib/ld-2.3.2.so

40015000-40016000 rw-p 00014000 08:02 271023 /lib/ld-2.3.2.so

40016000-40017000 rw-p 00000000 00:00 0

40017000-40018000 r-xp 00000000 00:09 17255 /mnt/hgfs/share/net/tcpBreak/libtest.so

40018000-40019000 rw-p 00000000 00:09 17255 /mnt/hgfs/share/net/tcpBreak/libtest.so

40019000-4001b000 rw-p 00000000 00:00 0

40026000-400cf000 r-xp 00000000 08:02 350892 /usr/lib/libstdc++.so.5.0.3

400cf000-400d4000 rw-p 000a9000 08:02 350892 /usr/lib/libstdc++.so.5.0.3

400d4000-400d9000 rw-p 00000000 00:00 0

400d9000-400fa000 r-xp 00000000 08:02 286922 /lib/tls/libm-2.3.2.so

400fa000-400fb000 rw-p 00020000 08:02 286922 /lib/tls/libm-2.3.2.so

400fb000-40102000 r-xp 00000000 08:02 271272 /lib/libgcc_s-3.2.2-20030225.so.1

40102000-40103000 rw-p 00007000 08:02 271272 /lib/libgcc_s-3.2.2-20030225.so.1

40103000-40104000 rw-p 00000000 00:00 0

42000000-4212e000 r-xp 00000000 08:02 286920 /lib/tls/libc-2.3.2.so

4212e000-42131000 rw-p 0012e000 08:02 286920 /lib/tls/libc-2.3.2.so

42131000-42133000 rw-p 00000000 00:00 0

bfffd000-c0000000 rwxp ffffe000 00:00 0

-------------------------------------------------------------------------

--------------------------- 程序挂掉時的棧幀 --------------------------

    顯然 4001771b 400176fd 對應的庫是 libtest.so,偏移位址分别為 71b 6fd。

三、臨門一腳

    知道了對應的動态連結庫和偏移位址後,我們進一步用 addr2line 将這個偏移位址翻譯一下就可以了。

[root@redhat tcpBreak]# addr2line 71b 6fd -s -C -f -e libtest.so

a()

lib.cpp:14

b()

lib.cpp:10

    至此,大功告成。

四、簡而言之

    不管是否有用到動态連結庫,我們将原方法得到的輸出,結合程序挂掉時maps檔案的内容,就可以得到代碼出錯時的執行路徑。根據代碼所在部分,指定相應的檔案給 addr2line 的 -e 參數即可。對于上面那個例子:

[root@redhat tcpBreak]# addr2line 8048ab3 -s -C -f -e a.out

    本文釋出的捕獲出錯執行路徑的方法:

        1 在含有main函數的那個源碼檔案裡,包含segvCatch_ext.h這個頭檔案

        2 具體如何解析出錯時代碼的執行路徑,閱讀segvCatch_ext.h頭部的說明

五、似有餘味

    一個程式啟動後,位址是如何進行映射的,MAPS檔案是怎麼生成的,庫又是怎麼加載的,自行編寫動态連結庫時,有什麼注意事項...

    這些問題我也不甚明了,因為我自己也沒深究過,以後有時間可能會陸續補到部落格裡面。

參考資料:

[4] 《程式員的自我修養—連結、裝載與庫》,俞甲子,石凡,潘愛民. (PS 此書甚好,推薦大家閱讀)

繼續閱讀