天天看點

linux系統挂掉問題的分析

玩linux系統,經常遇到的一件事就是做了某個操作之後系統會突然挂掉,這要怎麼辦?

1. 首先我們要看log,看看是否會留下一些蛛絲馬迹,比如PC/LR是否有留下來。

PC是ARM的一個寄存器,即程式計數器,他記下的是目前程式執行的位置;

LR是link register,它儲存的是目前函數的傳回位址,

是以我們可以善用PC/LR來幫助我們查找問題的根源。

2. 假設我們知道系統挂掉時的PC值,同時我們要知道你的系統中挂掉的process是哪一個,

這樣再使用ps aux | grep my_process擷取這個process的pid。

獲得了process的pid,我們可以使用cat /proc/pid/maps > ./pid_maps擷取該procss的虛拟位址空間。

注意每一個使用者process的虛拟位址空間都可能不一樣,因為虛拟位址空間的關系,

在系統中每一個使用者process都認為自己是系統中唯一的一個process。

3. 因為我們有了PC值,是以接下來在pid_maps中找到PC值位于哪一個shared library中,

也就是說系統挂掉的點是在哪個.so中挂掉。此時我們根據PC值結合這隻挂掉的libtest.so

計算出在libtest.so中的偏移量。

4. readelf -a ./libtest.so | grep offset

或者nm ./libtest.so | grep offset

或者objdump -d libtest.so > libtest_disassemble.txt(建議使用objdump反彙編)

來檢視offset對應的代碼中的位置。

5. 結合源代碼進行分析,找到系統挂掉的具體位置。

使用這種方法的缺點是:

1. 如果系統挂掉時已經破壞了線程棧,那利用PC值分析的意義不大;

2. 如果系統是挂在核心空間,那也無法确認問題點,除非能夠恢複出使用者空間的線程棧。

nm指令會列出symbol value(symbol offset)、symbol type以及symbol name。

我們常見的symbol type有"T"和"t",這兩種類型的symbol都位于text section(即代碼段),

其中symbol type為"T"的symbol對應的是extern類型的函數,為"t"的則對應的是static類型的函數。

因為symbol value和symbol name是對應的,是以我們可以根據PC确定offset後找到

相應的symbol name進而确定問題點。

nm libtest.so > nm_libtest.txt

readelf指令用來顯示elf格式檔案的資訊,以本文讨論的問題範疇來講,感興趣的是symbols。

是以我們可以使用readelf -s libtest.so > readelf_libtest.txt将libtest.so中的symbol

dump出來,以便于我們排查問題。

objdump用來dump obj類型檔案的資訊,有了這條指令我們可以對編譯好的obj檔案進行反彙編,

這樣可以去檢視代碼執行的流程。很多時候,對我們解決問題會非常有幫助。

objdump -d libtest.so > objdump_libtest.txt

為什麼要計算PC對應在.so(shared library)中的偏移量?

shared library不同于靜态庫:靜态庫是在編譯時即被內建到可執行檔案中;

而動态庫是在你的程式運作時才被加載到RAM中。

在被映射到虛拟位址空間之前,動态連接配接庫中的各個符号之間的相對位址是确定的;

但是程式運作之前,無法确定其在虛拟位址空間中的位置,是以才需要計算偏移量。

在可執行程式中,僅僅有一個指向動态連接配接庫的指針。/etc/ld.so.conf是動态連接配接庫的配置檔案,

在運作程式時當需要某個動态庫時,ldconfig根據/etc/ld.so.conf中的配置選擇加載特定的動态庫到RAM中。

從以上描述,我們知道使用動态庫的優點是:(1)可執行程式的代碼量會變小;(2)動态庫

獨立于可執行程式,是以當我們要修改動态庫時我們僅僅需要重新編譯這個動态庫而不需要

将整個可執行程式都重新編譯一次;(3)另外可以在程式運作時去替換新的動态庫,給調試

程式帶來了友善。

正因為動态庫的獨立性,是以也決定了其一些缺點的存在:

(1)如果你的系統中缺少某個你需要的動态庫,那隻能在你運作時才會發現問題;

(2)如果你調用的動态庫中缺少你需要的API,那也隻能在運作程式時才能發現問題。

在之前寫過的文章裡面,有做過類似的試驗,這裡我們仍然可以做幾個試驗:

(1)将libtest.so所在的path從LD_LIBRARY_PATH中移除,再執行你的程式看一下:

sh#error while loading shared libraries:

(2)在系統總暫時性的将你的libtest.so删除,再執行你的程式看一下:

sh#error while loading shared libraries:

(3)将某個API從libtest.so中拿掉,再執行你的程式看一下:

sh#./test_main: symbol lookup error: ./libtest.so: undefined symbol:

如果是加載某個動态庫出錯,那麼先用ldd指令(ldd實際上隻是一個script)來檢視

可執行檔案需要用到哪些共享庫,然後依次檢查這些共享庫是否都存在;

接下來檢查共享庫所在的path是否都有添加在LD_LIBRARY_PATH中。

如果是動态庫中缺少某個API進而導緻執行程式時失敗,那首先要确認缺少的API是哪一個(如何确認?),

接下來使用nm/readelf或者是objdump來擷取libtest.so中的symbol了,然後再确認需要的symbol是否

包含在libtest.so了。

nm libtest.so | grep your_api

readelf -s libtest.so | grep your_api

objdump -d libtest.so | grep your api

因為我們故意移除了your_api,是以在結果中應該搜尋不到;

另外我們可以搜尋其它的symbol看一下,應該會搜尋到的,這樣可以确認我們用的工具指令沒有問題。

待解決問題:

可執行程式是如何調用動态庫中提供的API?

為什麼有時候找不到符号,是因為在編譯代碼時被stripped掉了嗎?

繼續閱讀