backtracelinux
Ⅰ memorymappingsegment發生段錯誤,如何回溯
linux應用中用backtrace和memory map信息定位段錯誤代碼的方法 原創
2020-03-27 14:33:21
川渝小神丟
碼齡11年
關注
前言:
在Linux應用軟體中,段錯誤(segmentation fault)是開發過程中比較棘手的問題,在博文(Linux環境下段錯誤的產生原因及調試方法小結)中簡要介紹了段錯誤的產生原因和簡單的調試方法。
本文主要描述面對大型程序,比如多線程,調用多個動態庫的情況下發生段錯誤分析方法。這個時候如果使用gdb和gcc,由於程序過於復雜,gdb將很難處理,並且對於那些偶爾出現段錯誤的情況gdb基本上無法定位。
另外,還可以使用dmesg,nm,ldd,objmp等工具結合分析匯編代碼調試段錯誤,但是對於大型程序一般都有編譯優化,分析匯編地址將不是那麼容易,另外需要一定匯編基礎,對於龐大程序分析匯編代碼也是噩夢。
最後,使用printf。printf是個最方便方式,如果對於必現的段錯誤,使用printf再結合二分法,一般都能定位到。但是就算段錯誤必現,對於那些多線程大型程序,使用printf就需要對代碼有較深入的了解,憑經驗找到可能觸發異常的地方,並且每次加了調試語句後需要重新編譯燒寫,效率非常低下。對於那些很難復現的段錯誤,printf更難入手。
綜上所述,單單使用上述某一種方法都不能很好地分析滿足如下條件的段錯誤:
1) 大型項目,代碼量幾十萬行以上。
2) 多線程,多動態庫。
3) 出現段錯誤不一定必現(偶爾出現)。
本文將使用backtrace和memory map信息定位出發時異常的大致位置(往往是某個函數名,如果此函數層數非常多,那麼還需要printf定位最終位置),然後通過printf二分法精確到具體位置,針對偶爾出現的段錯誤,使用applog日誌記錄的方式。下面將詳細描述具體方法。
一、段錯誤信號
在某些大型程序中,程序初始化時首先會安裝SIGSEGV信號及指定處理函數,在處理函數中完成特殊段仿冊指定的處理後,最後在處理函數中調用longjmp函數回到主進程報錯並退出主進程。backtrace和memory map信息就是在上述信號處理函數中寫入系統日誌中的,前面的主進程退出後,系統日誌仍然保留在flash上供後續分析。在系統日誌中,backtrace提供了觸發異常的堆棧調用點地址序列,memory map信息提供各目標文件在內存中的地址分配。先通過backtarce中記錄的目標文件和地址數據,結合memory map中目標文件代碼段起始地址,計算偏移量。
使用此偏移量和目標文件用addr2line命令可查得對應此地址的代碼信息。下面先描述linux下段錯誤信號描述(注意Unix系統和linux系統中的signal函數有差異)。
1. 如果沒有安裝SIGSEGV信握宏號和並指定處理函數,系統將對這個信號進行默認處理。通常是殺掉當前進程,並在終端輸出Segmentation fault信息。
2. 如果已經安裝SIGSEGV信號和並指定處理函數,產生這個信號後,系統先運行信號處理函數,信號處理函數運行完後當前進程繼續運行。通過實驗,在發生了這個信號後,大核並且當前進程沒有退出,那麼系統會多次進入信號處理函數。
3. 當安裝了SIGSEGV信號和並指定處理函數後,發生段錯誤的線程將調用處理函數,因此在處理函數中可以獲取哪個線程發生了段錯誤(一般通過線程ID)。即發生段錯誤線程的線程ID和段錯誤處理函數中獲取的線程ID是一樣的。 (也就是說,當線程A發生段錯誤,CPU的PC指針指向Linux內核,運行內核程序,Linux內核捕捉到段錯誤信號,這個時候要運行用戶空間中的信號處理函數,因此記錄下內核空間相關地址後切換到用戶空間,把PC指針指向段錯誤處理函數並運行。PC指針在進入內核空間之前運行的線程A,因此當時PC處於線程A的地址空間內,因此當段錯誤發生後,PC指針指向信號處理函數也應該處於線程A的地址空間內,因此在信號處理函數內獲取的線程ID就是出現問題的線程的線程ID,在信號處理函數中backtrace信息就是出現問題線程問題點相關地址信息)通過上述分析,已經知道段錯誤發生線程,為了進一步縮小范圍,確定發生段錯誤線程中到底哪個函數有問題,就是下面即將描述backtrace和memory map信息。
4. 同中斷類似,內核也為每個進程准備了一個信號向量表,信號向量表中記錄著每個信號所對應的處理機制,默認情況下是調用默認處理機制。當進程為某個信號注冊了信號處理程序後,發生該信號時,內核就會調用注冊的函數。
5. 詳細參考http://www.spongeliu.com/165.html
二、backtrace()
1. int backtrace(void **buffer,int size)
該函數用於獲取當前線程的調用堆棧(即獲取程序中當前函數的棧回溯信息,即一系列的函數調用關系),獲取的信息將會被存放在buffer中,它是 一個指針列表。參數 size 用來指定buffer中可以保存多少個void* 元素。函數返回值是實際獲取的指針個數,最大不超過size大小。在buffer中的指針實際是從堆棧中獲取的返回地址,每一個堆棧框架有一個返回地址。
注意:某些編譯器的優化選項對獲取正確的調用堆棧有干擾,另外內聯函數沒有堆棧框架;刪除框架指針也會導致無法正確解析堆棧內容.
2. char ** backtrace_symbols (void *const *buffer, int size)
backtrace_symbols將從backtrace函數獲取的信息轉化為一個字元串數組. 參數buffer應該是從backtrace函數獲取的指針數組,size是該數組中的元素個數(backtrace的返回值)。
函數返回值是一個指向字元串數組的指針,它的大小同buffer相同.每個字元串包含了一個相對於buffer中對應元素的可列印信息.它包括函數名,函數的偏移地址,和函數的實際返回地址。比如backtrace信息中,./main-test(SEGV_Test+0x10)[0x8bc8],main-test為程序名稱,SEGV_Test函數名,0x10為函數的偏移地址,0x8bc8為此函數實際返回地址。經過翻譯後的函數回溯信息放到backtrace_symbols()的返回值中,如果失敗則返回NULL。
現在,只有使用ELF二進制格式的程序才能獲取函數名稱和偏移地址.在其他系統,只有16進制的返回地址能被獲取.另外,你可能需要傳遞相應的符號給鏈接器,以能支持函數名功能(比如,在使用GNU ld鏈接器的系統中,需要傳遞(-rdynamic), -rdynamic可用來通知鏈接器將所有符號添加到動態符號表中,如果鏈接器支持-rdynamic,建議將其加上)。
該函數的返回值是通過malloc函數申請的空間,因此調用者必須使用free函數來釋放指針。注意:如果不能為字元串獲取足夠的空間函數的返回值將會為NULL。
3. void backtrace_symbols_fd (void *const *buffer, int size, int fd)
backtrace_symbols_fd()的buffer和size參數和backtrace_symbols()函數相同,只是它翻譯後的函數回溯信息不是放到返回值中,而是一行一行的放到文件描述符fd對應的文件中。
4. 在段錯誤信號處理函數中調用backtrace()和backtrace_symbols()函數用於確定出現問題線程的調用堆棧。
由於發生段錯誤後,內核才會把pc寄存器值設為信號處理函數地址,繼而執行應用空間的信號處理函數,因此在信號處理函數中調用backtrace棧回溯函數即可知道段錯誤發生的地方附近相關函數調用關系,間接找到段錯誤發生地點。雖然在信號處理函數中調用backtrace等函數並不能保證是一個安全的信號處理函數,但是用於調試完全可以忽略這一點。
5. 使用上述幾個函數需要注意:
1)backtrace的實現依賴於棧指針(fp寄存器),在gcc編譯過程中任何非零的優化等級(-On參數)或加入了棧指針優化參數- fomit-frame-pointer後多將不能正確得到程序棧信息。
2)backtrace_symbols的實現需要符號名稱的支持,在gcc編譯過程中需要加入-rdynamic參數。
3)內聯函數沒有棧幀,它在編譯過程中被展開在調用的位置。
4)未調用優化(Tail-call Optimization)將復用當前函數棧,而不再生成新的函數棧,這將導致棧信息不能正確被獲取。
三、memory map信息
1. /proc/PID/maps
Proc/pid/maps顯示進程映射了的內存區域和訪問許可權。
[root@ES_Controller:/var]#cat /proc/1/maps
00008000-0006a000 r-xp 00000000 1f:03 27 /bin/busybox
00071000-00072000 rw-p 00061000 1f:03 27 /bin/busybox
00072000-00095000 rwxp 00072000 00:00 0 [heap]
40000000-40002000 rw-p 40000000 00:00 0
412d8000-412f4000 r-xp 00000000 1f:03 396 /lib/ld-2.5.so
412fb000-412fd000 rw-p 0001b000 1f:03 396 /lib/ld-2.5.so
41300000-41416000 r-xp 00000000 1f:03 405 /lib/libc-2.5.so
41416000-4141d000 ---p 00116000 1f:03 405 /lib/libc-2.5.so
4141d000-41420000 rw-p 00115000 1f:03 405 /lib/libc-2.5.so
41420000-41423000 rw-p 41420000 00:00 0
beb71000-beb86000 rw-p befeb000 00:00 0 [stack]
內核每進程的vm_area_struct項/proc/pid/maps中的項
在其他進程(如控制台)執行/proc/pid/maps,會顯示進程號為pid對應的memory map信息;如果在本進程中想獲取本身的memory map信息,需要執行/proc/self/maps。
2. /proc/self/maps
我對/proc/self/maps的理解如下:
如果一個進程正在運行,那麼可以在終端執行/proc/進程PID/maps查看到memory map,但在當前進程代碼運行時需要memory map信息時,可以在代碼中執行/proc/self/maps查看memory map信息,也就是說在哪個程序中執行/proc/self/maps,那麼memory map信息就是對應進程的內存布局信息。
3. memory map
(1) 首先是映像文件(靜態編譯)
00008000-0000a000 r-xp 00000000 00:0d 16281360 /mnt/nfs/main-test/main-test
00011000-00012000 rw-p 00001000 00:0d 16281360 /mnt/nfs/main-test/main-test
映像文件名為main-test,第一行只讀可執行,說明為代碼段,00008000為代碼段的起始地址,如果用backtrace信息中出現問題點棧回溯中的某個函數的實際返回地址減去這個程序代碼段起始地址,那麼就剛好得到了出現問題點相對於整個程序代碼中的位置,再借用addr2line工具就可 以得到出現問題點所在源代碼對應行。
上述信息中第二行為可讀寫,那麼一般為程序的數據段。數據段對應信息的下一行如下,即堆(heap),當且僅當malloc調用時存在,是由kernel把匿名內存map到虛存空間,堆則在程序中沒有調用malloc的情況下不存在;
00012000-00033000 rwxp 00012000 00:00 0 [heap]
另外就是靜態編譯下的動態鏈接庫,最後是堆棧[stack]。
(2) 然後是共享庫(.SO,動態編譯)
40b58000-40bb7000 r-xp 00000000 1f:04 22773 /application/example.so
40bb7000-40bbe000 ---p 0005f000 1f:04 22773 /application/example.so
40bbe000-40bbf000 rw-p 0005e000 1f:04 22773 /application/example.so
同樣,第一行對應共享庫的代碼段;第三行(rw-p)為共享庫的數據段,第二行即不讀;也不可寫,且不可執行,不知道是什麼。共享庫在一個大型程序中,一般都有個入口函數,如果創建一個線程時,把這個入口函數作為線程函數,那麼這個共享庫實際上被載入到內存後,在一個線程中運行。
四、addr2line工具定位出錯點
1. addr2line定義
Addr2line (它是標準的 GNU Binutils 中的一部分)是一個可以將指令的地址和可執行映像轉換成文件名、函數名和源代碼行數的工具。
命令參數如下:
Usage: addr2line [option(s)] [addr(s)]
Convert addresses into line number/file name pairs.
If no addresses are specified on the command line, they will be read from stdin
The options are:
@<file> Read options from <file>
-a --addresses Show addresses
-b --target=<bfdname> Set the binary file format
-e --exe=<executable> Set the input file name (default is a.out)
-i --inlines Unwind inlined functions
-j --section=<name> Read section-relative offsets instead of addresses
-p --pretty-print Make the output easier to read for humans
-s --basenames Strip directory names
-f --functions Show function names
-C --demangle[=style] Demangle function names
-h --help Display this information
-v --version Display the program's version
2. 用addr2line定位
(1) 靜態鏈接情況
執行編譯命令:arm-linux-gcc -rdynamic -lpthread -g main.c -o main-test
即把所有源程序和庫全部連接成一個可執行文件main-test。
backtrace信息如下:
./main-test(pthread_create+0xac4)[0x95ec]
/lib/libc.so.6(__default_rt_sa_restorer_v2+0x0)[0x4132b240]
./main-test(test1+0x44)[0x8cdc]
./main-test(SEGV_Test+0x10)[0x8d5c]
./main-test(thread3+0x40)[0x92ec]
/lib/libpthread.so.0[0x41444858]
memory map信息如下:
00008000-0000a000 r-xp 00000000 00:0d 16281360 /mnt/nfs/main-test/main-test
00011000-00012000 rw-p 00001000 00:0d 16281360 /mnt/nfs/main-test/main-test
00012000-00033000 rwxp 00012000 00:00 0 [heap]
40000000-40001000 rw-p 40000000 00:00 0
40001000-400df000 r-xp 00000000 1f:03 446 /lib/preloadable_libiconv.so
400df000-400e7000 ---p 000de000 1f:03 446 /lib/preloadable_libiconv.so
400e7000-400e8000 rw-p 000de000 1f:03 446 /lib/preloadable_libiconv.so
400e8000-400ea000 rw-p 400e8000 00:00 0
從Memory map信息第一行可以知道靜態編譯的程序main-test代碼段地址空間為0x8000-0xa000,而backtrace信息中0x8cdc等地址也在這個地址空間范圍內(這種實際地址不用backtrace地址減去memory map地址計算偏移,然後再用addr2line命令定位)。因此執行如下命令:
addr2line 0x8cdc -e main-test -f
在使用時,用 -e 選項來指定可執行映像是main-test。通過使用 -f 選項,可以告訴工具輸出函數名。上述命令將會輸出在程序main-test中,指令地址為0x8cdc對應源代碼文件、源代碼文件中的函數名、地址對應行號。執行輸出信息如下:
[root@test main-test]$ addr2line 0x8cdc -e main-test -f
test1
/home/mytest/main-test/main.c:355
上述信息中,說明問題出現在在main.c中的355行附近(test1函數中)。
(2) 動態鏈接情況
然而,調試的程序往往沒有這么簡單,通常會載入用到各種各樣的動態鏈接庫。如果錯誤是發生在動態鏈接庫中那麼處理將變得困 難一些。下面另外一個例子:
backtrace信息如下:
./application(InitialConfigTest+0x6ec) [0xac00].
/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x4132b240].
/application/example.so [0x41c68b48].
/application/example.so(testquery+0x28) [0x41c68f6c].
./application [0x19f58].
./application [0x1a660].
/application/lib/libpub.so [0x401d6a68].
/lib/libpthread.so.0 [0x41444858].
部分memory map信息如下:
41c5f000-41cbe000 r-xp 00000000 1f:04 22773 /application/example.so
41cbe000-41cc5000 ---p 0005f000 1f:04 22773 /application/example.so
41cc5000-41cc6000 rw-p 0005e000 1f:04 22773 /application/example.so
用backtrace中的地址0x41c68b48做實驗,即執行命令 addr2line 0x41c68b48 -e example.so -f 後,addr2line輸出信息如下:
[root@test main-test]$ addr2line 0x41c68b48 -e example.so -f
??
??:0
出現這種情況是由於動態鏈接庫是程序運行時動態載入的,因此其載入地址每次可能不一樣(關於與位置無關映像,用file命令查看elf格式文件,如果是shared object,那麼表示此程序支持PIE與位置無關;如果是executable表示編譯和連接分別沒有加-fPIE和-pie選項。與位置無關編譯和鏈接增加參數:-fPIE for compiler,-pie for linker),可見0x41c68b48是一個非常大的地址, 和能得到正常信息的地址如0xac00相差甚遠,0xac00其也不是一個實際的物理地址(用戶空間的程序無法直接訪問物理地址),而是經過MMU(內存管理單元)映射過的。
因此,只需要得到此次example.so的載入地址後(動態庫代碼段起始地址,在memory map信息中第一行的最左面那個地址),再用backtrace中的實際地址0x41c68b48減去example.so的載入地址得到的結果再利用addr2line命令就可以正確的得到出錯的地方。
另外在backtrace信息中,(testquery+0x28)其實也是在描述出錯的地方,這里表示的是發生在符號testquery(函數名)偏移0x28處的地方,也就是說如果我們能得到符號testquery也就是函數testquery在程序中的入口地址再加上偏移量0x28也能得到正常的出錯地址。
0x41c68b48-0x41c5f000 = 0x9b48
addr2line 0x9b48 -e example.so -f
執行上述命令後,addr2line輸出信息如下,從而能定位到問題所在行:
[root@test main-test]$ addr2line 0x9b48 -e example.so -f
example_delay
/home/example/example.c:1883
注意:就算是動態鏈接,如果段錯誤出現在非動態庫中,即應用程序中,這個時候執行addr2line命令時,可執行程序backtrace地址不需要 減去memory map信息中的起始地址。
3. 使用addr2line注意
為了在使用addr2line工具後能得到准確的結果,在編譯程序時應該加上-g選項通知編譯器生成調試符號。
五、總結
1. 為了正確地獲取backtrace信息,編譯時需要加上-rdynamic選項,為了正確地利用addr2line輸出信息,編譯時需要加上-g選項。完整命令如下:
arm-linux-gcc -rdynamic -lpthread -g main.c -o main-test
2. 如果是靜態編譯程序,只需要backtrace信息和addr2line工具即獲取出錯點。
3. 如果是動態編譯程序,且問題出現在動態鏈接庫時(因為動態編譯時,動態鏈接庫是運行時才載入的,載入地址就是在memory map信息中第一行的最左面那個地址),那麼就需要使用backtrace信息中問題點實際地址減去memory map信息中代碼段其實地址,得出地址偏移,然後使用addr2line工具定位到行。
4. 在大型程序中,backtrace信息往往會出現多個目標地址的情況,如下:
./application(InitialConfigTest+0x6ec) [0xac00].
/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x4132b240].
/application/example.so [0x41c68b48].
/application/example.so(testquery+0x28) [0x41c68f6c].
./application [0x19f58].
./application [0x1a660].
/application/lib/libpub.so [0x401d6a68].
/lib/libpthread.so.0 [0x41444858].
上述例子屬於動態編譯,application為應用程序,example.so為動態鏈接庫。不管是應用程序還是動態鏈接庫,都出現了多個目標地址的情況。
如:
/application/example.so [0x40b61b48].
/application/example.so(testquery+0x28) [0x41c68f6c].
一般選擇第一個比較准確(沒有地址偏移的那個)
六、偶爾出現段錯誤分析經驗
未完待續...
七、其他相關資料:
1. http://blog.csdn.net/jxgz_leo/article/details/53458366
2. http://www.cnblogs.com/panfeng412/archive/2011/11/06/segmentation-fault-in-linux.html
3. http://blog.csdn.net/guoping16/article/details/6583957
4. Linux內核出現段錯誤,會列印出棧信息(dmesg命令可以看到這些信息)。
5. linux中Oops信息的調試及棧回溯(sù):http://blog.csdn.net/u012839187/article/details/78963443。
6. Linux core 文件介紹:https://www.cnblogs.com/dongquan/archive/2012/01/20/2328355.html
Ⅱ 安裝linux提示「loader exited unexpectedly!backtrace……」
安裝盤壞了(ISO文件有問題了),重新下載一個再試
Ⅲ linux core 怎麼打開
core文件是由應用程序收到系統信號後崩潰產生的,該文件中記錄了程序崩潰的原因(例如收到那種信號),調用堆棧和崩潰時的內存及變數值等等的信息。
打開core文件與編譯時使用的編譯器有關,但絕大多數linux程序是使用gcc編譯器編譯的,因此可使用對應gdb調試器打開,命令格式如下:
$ gdb 應用程序文件名 core文件名
舉例:
$ gdb /usr/bin/gedit ~/core ------ 查看由gedit崩潰產生的core文件
(gdb) bt ------ 或者backtrace, 查看程序運行到當前位置之前所有的堆棧幀情況)
(gdb) quit ------ 退出
如果不知道core文件由哪個文件產生的,可使用file命令顯示
$ file core
Ⅳ linux gdb backtrace 怎麼實現的
一般察看函數運行時堆棧的方法是使用GDB(bt命令)之類的外部調試器,但是,有些時候為了分析程序的BUG,(主要針對長時間運行程序的分析),在程序出錯時列印出函數的調用堆棧是非常有用的。
在glibc頭文件"execinfo.h"中聲明了三個函數用於獲取當前線程的函數調用堆棧。
[cpp] view plain print?
int backtrace(void **buffer,int size)
該函數用於獲取當前線程的調用堆棧,獲取的信息將會被存放在buffer中,它是一個指針列表。參數 size 用來指定buffer中可以保存多少個void* 元素。函數返回值是實際獲取的指針個數,最大不超過size大小
在buffer中的指針實際是從堆棧中獲取的返回地址,每一個堆棧框架有一個返回地址
注意:某些編譯器的優化選項對獲取正確的調用堆棧有干擾,另外內聯函數沒有堆棧框架;刪除框架指針也會導致無法正確解析堆棧內容
[cpp] view plain print?
char ** backtrace_symbols (void *const *buffer, int size)
backtrace_symbols將從backtrace函數獲取的信息轉化為一個字元串數組. 參數buffer應該是從backtrace函數獲取的指針數組,size是該數組中的元素個數(backtrace的返回值)
函數返回值是一個指向字元串數組的指針,它的大小同buffer相同.每個字元串包含了一個相對於buffer中對應元素的可列印信息.它包括函數名,函數的偏移地址,和實際的返回地址
現在,只有使用ELF二進制格式的程序才能獲取函數名稱和偏移地址.在其他系統,只有16進制的返回地址能被獲取.另外,你可能需要傳遞相應的符號給鏈接器,以能支持函數名功能(比如,在使用GNU ld鏈接器的系統中,你需要傳遞(-rdynamic), -rdynamic可用來通知鏈接器將所有符號添加到動態符號表中,如果你的鏈接器支持-rdynamic的話,建議將其加上!)
該函數的返回值是通過malloc函數申請的空間,因此調用者必須使用free函數來釋放指針.
注意:如果不能為字元串獲取足夠的空間函數的返回值將會為NULL
[cpp] view plain print?
void backtrace_symbols_fd (void *const *buffer, int size, int fd)
backtrace_symbols_fd與backtrace_symbols 函數具有相同的功能,不同的是它不會給調用者返回字元串數組,而是將結果寫入文件描述符為fd的文件中,每個函數對應一行.它不需要調用malloc函數,因此適用於有可能調用該函數會失敗的情況
下面是glibc中的實例(稍有修改):
[cpp] view plain print?
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
/* Obtain a backtrace and print it to @code{stdout}. */
void print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;
size = backtrace (array, 10);
strings = backtrace_symbols (array, size);
if (NULL == strings)
{
perror("backtrace_synbols");
Exit(EXIT_FAILURE);
}
printf ("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);
free (strings);
strings = NULL;
}
/* A mmy function to make the backtrace more interesting. */
void mmy_function (void)
{
print_trace ();
}
int main (int argc, char *argv[])
{
mmy_function ();
return 0;
}
輸出如下:
[cpp] view plain print?
Obtained 4 stack frames.
./execinfo() [0x80484dd]
./execinfo() [0x8048549]
./execinfo() [0x8048556]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x70a113]
我們還可以利用這backtrace來定位段錯誤位置。
通常情況系,程序發生段錯誤時系統會發送SIGSEGV信號給程序,預設處理是退出函數。我們可以使用 signal(SIGSEGV, &your_function);函數來接管SIGSEGV信號的處理,程序在發生段錯誤後,自動調用我們准備好的函數,從而在那個函數里來獲取當前函數調用棧。
舉例如下:
[cpp] view plain print?
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <execinfo.h>
#include <signal.h>
void mp(int signo)
{
void *buffer[30] = {0};
size_t size;
char **strings = NULL;
size_t i = 0;
size = backtrace(buffer, 30);
fprintf(stdout, "Obtained %zd stack frames.nm\n", size);
strings = backtrace_symbols(buffer, size);
if (strings == NULL)
{
perror("backtrace_symbols.");
exit(EXIT_FAILURE);
}
for (i = 0; i < size; i++)
{
fprintf(stdout, "%s\n", strings[i]);
}
free(strings);
strings = NULL;
exit(0);
}
void func_c()
{
*((volatile char *)0x0) = 0x9999;
}
void func_b()
{
func_c();
}
void func_a()
{
func_b();
}
int main(int argc, const char *argv[])
{
if (signal(SIGSEGV, mp) == SIG_ERR)
perror("can't catch SIGSEGV");
func_a();
return 0;
}
編譯程序:
gcc -g -rdynamic test.c -o test; ./test
輸出如下:
[cpp] view plain print?
Obtained6stackframes.nm
./backstrace_debug(mp+0x45)[0x80487c9]
[0x468400]
./backstrace_debug(func_b+0x8)[0x804888c]
./backstrace_debug(func_a+0x8)[0x8048896]
./backstrace_debug(main+0x33)[0x80488cb]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]
(這里有個疑問: 多次運行的結果是/lib/i368-Linux-gnu/libc.so.6和[0x468400]的返回地址是變化的,但不變的是後三位, 不知道為什麼)
接著:
objmp -d test > test.s
在test.s中搜索804888c如下:
[cpp] view plain print?
8048884 <func_b>:
8048884: 55 push %ebp
8048885: 89 e5 mov %esp, %ebp
8048887: e8 eb ff ff ff call 8048877 <func_c>
804888c: 5d pop %ebp
804888d: c3 ret
其中80488c時調用(call 8048877)C函數後的地址,雖然並沒有直接定位到C函數,通過匯編代碼, 基本可以推出是C函數出問題了(pop指令不會導致段錯誤的)。
我們也可以通過addr2line來查看
[cpp] view plain print?
addr2line 0x804888c -e backstrace_debug -f
輸出:
[cpp] view plain print?
func_b
/home/astrol/c/backstrace_debug.c:57
以下是簡單的backtrace原理實現:
Ⅳ Linux mutex為什麼不能用在中斷函數
Linux mutex不能用在中斷函數原因:Backtrace來看,應該是i2c_transfer中調用mutex_lock導致schele調用。
pthread_mutex_lock(&qlock);表示嘗試去把qlock上鎖,它會先判斷qlock是否已經上鎖,如果已經上鎖這個線程就會停在這一步直到其他線程把鎖解開。它才繼續運行。所以代碼中要麼是線程1先執行完後執行線程2,要麼就是線程2先執行,再執行線程1.而線程3一開始就執行了。
中斷函數防止方法:
要防止中斷沖突,其實就是要知道什麼設備容易產生中斷沖突,只要知道了這點,在使用這些設備時稍微注意一下就可以了。下面我列出一些容易沖突的設備,希望對讀者有用。
1、音效卡:一些早期的ISA型音效卡,系統很有可能不認,就需要用戶手動設置(一般為5)。
2、內置數據機和滑鼠:一般滑鼠用COM1,內置數據機使用COM2的中斷(一般為3),這時要注意此時COM2上不應有其它設備。
Ⅵ 如何快速定位Linux Panic出錯的代碼行
內核Panic時,一般會列印回調,並列印出當前出錯的地址:
kernel/panic.c:panic():
#ifdef CONFIG_DEBUG_BUGVERBOSE
/*
* Avoid nested stack-mping if a panic occurs ring oops processing
*/
if (!test_taint(TAINT_DIE) && oops_in_progress <= 1)
mp_stack();
#endif
而mp_stack()調用關系如下:
mp_stack() --> __mp_stack() --> show_stack() --> mp_backtrace()
mp_backtrace()會列印整個回調,例如:
[<001360ac>] (unwind_backtrace+0x0/0xf8) from [<00147b7c>] (warn_slowpath_common+0x50/0x60)
[<00147b7c>] (warn_slowpath_common+0x50/0x60) from [<00147c40>] (warn_slowpath_null+0x1c/0x24)
[<00147c40>] (warn_slowpath_null+0x1c/0x24) from [<0014de44>] (local_bh_enable_ip+0xa0/0xac)
[<0014de44>] (local_bh_enable_ip+0xa0/0xac) from [<0019594c>] (bdi_register+0xec/0x150)
通常,上面的回調會列印出出錯的地址。
解決方案
通過分析,要快速定位出錯的代碼行,其實就是快速查找到出錯的地址對應的代碼?
相應的工具有addr2line, gdb, objmp等,這幾個工具在How to read a Linux kernel panic?都有介紹,我們將針對上面的實例做更具體的分析。
需要提到的是,代碼的實際運行是不需要符號的,只需要地址就行。所以如果要調試代碼扮悄,必須確保調試符號已經編譯到內核中,不然,回調里頭列印的是一堆地址,根本看不到符號,那麼對於上面提到的情況二緩譽而言,將無法准確定位問題。
情況一
在代碼編譯連接時,每個函數都有起始地址和長度,這個地址是程序運行時的地址,而函數內部,每條指令相對於函數開始地址會有偏移。那麼有了地址以後,就可以定位到該地址落在哪個函數的區間內,然後找到該函數,進而通過計算偏移,定位到代碼行。
情況二
但是,如果拿到的日誌文件所在的系統版本跟當前的代碼版本不一致,那麼編譯後的地址就會有差異。那麼簡單地直接通過地廳哪渣址就可能找不到原來的位置,這個就可能需要回調里頭的函數名信息。先通過函數名定位到所在函數,然後通過偏移定位到代碼行。
Ⅶ 1 linux下調試core的命令,察看堆棧狀態命令
比方說,你要調試的core文件是 core.xxx,原始可執行文件是 a.exe
先用 gdb a.exe 進入 gdb,在gdb命令行下 執行
core-file /path/to/core.xxx
然後即可調試core mp文件了,比如用 bt 等