1. 程式人生 > >段錯誤資訊的獲取和除錯

段錯誤資訊的獲取和除錯

一、段錯誤資訊的獲取

程式發生段錯誤時,提示資訊很少,下面有幾種檢視段錯誤的發生資訊的途徑。

1、dmesg

dmesg 可以在應用程式崩潰時,顯示記憶體中儲存的相關資訊。

如下所示,通過 dmesg 命令可以檢視發生段錯誤的程式名稱、引起段錯誤發生的記憶體地址、指令指標地址、堆疊指標地址、錯誤程式碼、錯誤原因等。

[email protected]#dmesg
[ 6357.422282] a.out[3044]: segfault at 806851c ip b75cd668 sp bf8b2100 error 4 in libc-2.15.so[b7559000+19f000]

2、-g

使用gcc編譯程式的原始碼時,加上 -g 引數,這樣可以使得生成的二進位制檔案中加入可以用於 gdb 除錯的有用資訊。

可產生供gdb除錯用的可執行檔案,大小明顯比只用-o選項編譯彙編連線後的檔案大。

gdb的簡單使用:

(gdb)l  列表(list)

(gdb)r  執行(run)

(gdb)n  下一個(next)

(gdb)q  退出(quit)

(gdb)p  輸出(print)

(gdb)c  繼續(continue)

(gdb)b 4 設定斷點(break)

(gdb)d   刪除斷點(delete)

3、nm

使用 nm 命令列出二進位制檔案中符號表,包括符號地址、符號型別、符號名等。這樣可以幫助定位在哪裡發生了段錯誤。

[email protected]# nm a.out 


4、ldd

使用 ldd 命令檢視二進位制程式的共享連結庫依賴,包括庫的名稱、起始地址,這樣可以確定段錯誤到底是發生在了自己的程式中還是依賴的共享庫中。

[email protected]# ldd a.out 


二、段錯誤資訊的除錯

接下來的講解是圍繞下面的程式碼進行的:

#include <stdio.h>
 
int main (void
) { int *ptr = NULL; *ptr = 10; return 0; } 輸出結果: 段錯誤(核心已轉儲)

1、使用 printf 輸出資訊

這個是看似最簡單,但往往很多情況下十分有效的除錯方式,也許可以說是程式設計師用的最多的除錯方式。

簡單來說,就是在程式的重要程式碼附近加上像 printf 這類輸出資訊,這樣可以跟蹤並打印出段錯誤在程式碼中可能出現的位置。

為了方便使用這種方法,可以使用條件編譯指令 #define DEBUG 和 #endif 把 printf 函式包起來。

這樣在程式編譯時,如果加上 -DDEBUG 引數就可以檢視除錯資訊;否則不加上引數就不會顯示除錯資訊。

2、使用 gcc 和 gdb

1)除錯步驟

A、為了能夠使用 gdb 除錯程式,在編譯階段加上 -g 引數。

[email protected]# gcc -g test.c

B、使用 gdb 命令除錯程式

[email protected]# gdb a.out 
GNU gdb (Ubuntu/Linaro 7.4-2012.02-0ubuntu2) 7.4-2012.02
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/tarena/project/c_test/a.out...done.
(gdb) 

C、進入 gdb 後,執行程式

(gdb) r
Starting program: /home/tarena/project/c_test/a.out 
 
Program received signal SIGSEGV, Segmentation fault.
0x080483c4 in main () at test.c:6
6        *ptr = 10;
(gdb) 

從輸出看出,程式收到 SIGSEGV 訊號,觸發段錯誤,並提示地址 0x080483c4、建立了一個空指標,然後試圖訪問它的值(讀值)。

可以通過man 7 signal檢視SIGSEGV的資訊

 Signal     Value     Action   Comment
       ──────────────────────────────────────────────────────────────────────
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating point exception
       SIGKILL       9       Term    Kill signal
       SIGSEGV      11       Core    Invalid memory reference
       SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                     readers
       SIGALRM      14       Term    Timer signal from alarm(2)
       SIGTERM      15       Term    Termination signal
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
       SIGCHLD   20,17,18    Ign     Child stopped or terminated
       SIGCONT   19,18,25    Cont    Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process
       SIGTSTP   18,20,24    Stop    Stop typed at tty
       SIGTTIN   21,21,26    Stop    tty input for background process
       SIGTTOU   22,22,27    Stop    tty output for background process
       The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

D、完成除錯後,輸入 q 命令退出 gdb

(gdb) q
A debugging session is active.
 
    Inferior 1 [process 3483] will be killed.
 
Quit anyway? (y or n) y

2)適用場景

A、僅當能確定程式一定會發生段錯誤的情況下適用。

B、當程式的原始碼可以獲得的情況下,使用 -g 引數編譯程式

C、一般用於測試階段,生產環境下 gdb 會有副作用:使程式執行減慢,執行不夠穩定,等等。

D、即使在測試階段,如果程式過於複雜,gdb 也不能處理。

3、使用 core 檔案和 gdb

上面有提到段錯誤觸發SIGSEGV訊號,通過man 7 signal,可以看到SIGSEGV預設的處理程式(handler)會列印段錯誤資訊,併產生 core 檔案,由此我們可以藉助於程式異常退出生成的 core 檔案中的除錯資訊,使用 gdb 工具來除錯程式中的段錯誤。

1)除錯步驟

A、在一些Linux版本下,預設是不產生 core 檔案的,首先可以檢視一下系統 core 檔案的大小限制:

[email protected]# ulimit -c
0

B、可以看到預設設定情況下,本機Linux環境下發生段錯誤不會自動生成 core 檔案,下面設定下 core 檔案的大小限制(單位為KB)

[email protected]# ulimit -c 1024
[email protected]# ulimit -c 
1024

C、執行程式,發生段錯誤生成的 core 檔案

[email protected]# ./a.out 
段錯誤 (核心已轉儲)

D、載入 core 檔案,使用 gdb 工具進行除錯

[email protected]:/home/tarena/project/c_test# gdb a.out core 
GNU gdb (Ubuntu/Linaro 7.4-2012.02-0ubuntu2) 7.4-2012.02
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/tarena/project/c_test/a.out...done.
[New LWP 3491]
warning: Can't read pathname for load map: 輸入/輸出錯誤.
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0  0x080483c4 in main () at test.c:6
6        *ptr = 10;
(gdb) 

從輸出看出,可以顯示出異樣的段錯誤資訊

E、完成除錯後,輸入 q 命令退出 gdb

(gdb) q

2)適用場景

A、適合於在實際生成環境下除錯程度的段錯誤(即在不用重新發生段錯誤的情況下重現段錯誤)

B、當程式很複雜,core 檔案相當大時,該方法不可用

4、使用 objdump

1)除錯步驟

A、使用 dmesg 命令,找到最近發生的段錯誤輸入資訊

[email protected]# dmesg
[  372.350652] a.out[2712]: segfault at 0 ip 080483c4 sp bfd1f7b8 error 6 in a.out[8048000+1000]

其中,對我們接下來的除錯過程有用的是發生段錯誤的地址 0 和指令指標地址 080483c4。

有時候,“地址引起的錯”可以告訴你問題的根源。看到上面的例子,我們可以說,int *ptr = NULL; *ptr = 10;,建立了一個空指標,然後試圖訪問它的值(讀值)。

B、使用 objdump 生成二進位制的相關資訊,重定向到檔案中

[email protected]# objdump -d a.out > a.outDump 
[email protected]# ls
a.out  a.outDump  core  test.c  

其中,生成的 a.outDump 檔案中包含了二進位制檔案的 a.out 的彙編程式碼

C、在 a.outDump 檔案中查詢發生段錯誤的地址

[email protected]:/home/tarena/project/c_test# grep -n -A 10 -B 10 "0" a.outDump
1-
2-a.out:     file format elf32-i386
 
118:080483b4 <main>:
119: 80483b4:    55                       push   %ebp
120: 80483b5:    89 e5                    mov    %esp,%ebp
121: 80483b7:    83 ec 10                 sub    $0x10,%esp
122: 80483ba:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%ebp)
123: 80483c1:    8b 45 fc                 mov    -0x4(%ebp),%eax
124: 80483c4:    c7 00 0a 00 00 00        movl   $0xa,(%eax)
125: 80483ca:    b8 00 00 00 00           mov    $0x0,%eax
126: 80483cf:    c9                       leave  
127: 80483d0:    c3                       ret    
128: 80483d1:    90                       nop
129: 80483d2:    90                       nop
130: 80483d3:    90                       nop
131: 80483d4:    90                       nop
132: 80483d5:    90                       nop
133: 80483d6:    90                       nop
134: 80483d7:    90                       nop
135: 80483d8:    90                       nop
136: 80483d9:    90                       nop
137: 80483da:    90                       nop
138: 80483db:    90                       nop
139: 80483dc:    90                       nop
140: 80483dd:    90                       nop
141: 80483de:    90                       nop
142: 80483df:    90                       nop

通過對以上彙編程式碼分析,得知段錯誤發生main函式,對應的彙編指令是movl   $0xa,(%eax),接下來開啟程式的原始碼,找到彙編指令對應的原始碼,也就定位到段錯誤了。 

2)適用場景

A、不需要 -g 引數編譯,不需要藉助於core檔案,但需要有一定的組合語言基礎。

B、如果使用 gcc 編譯優化引數(-O1,-O2,-O3)的話,生成的彙編指令將會被優化,使得除錯過程有些難度。

5、使用catchsegv

catchsegv 命令專門用來補貨段錯誤,它通過動態載入器(ld-linux.so)的預載入機制(PRELOAD)把一個事先寫好的 庫(/lib/libSegFault.so)載入上,用於捕捉段錯誤的出錯資訊。

[email protected]# catchsegv ./a.out
Segmentation fault (core dumped)
*** Segmentation fault
Register dump:
 EAX: 00000000   EBX: b77a1ff4   ECX: bfd8a0e4   EDX: bfd8a074
 ESI: 00000000   EDI: 00000000   EBP: bfd8a048   ESP: bfd8a038
 EIP: 080483c4   EFLAGS: 00010282
 CS: 0073   DS: 007b   ES: 007b   FS: 0000   GS: 0033   SS: 007b
 Trap: 0000000e   Error: 00000006   OldMask: 00000000
 ESP/signal: bfd8a038   CR2: 00000000
Backtrace:
??:0(main)[0x80483c4]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xb761a4d3]
??:0(_start)[0x8048321]
Memory map:
08048000-08049000 r-xp 00000000 08:01 2102158 /home/tarena/project/c_test/a.out
08049000-0804a000 r--p 00000000 08:01 2102158 /home/tarena/project/c_test/a.out
0804a000-0804b000 rw-p 00001000 08:01 2102158 /home/tarena/project/c_test/a.out
09467000-0948c000 rw-p 00000000 00:00 0 [heap]
b75e1000-b75fd000 r-xp 00000000 08:01 1704884 /lib/i386-linux-gnu/libgcc_s.so.1
b75fd000-b75fe000 r--p 0001b000 08:01 1704884 /lib/i386-linux-gnu/libgcc_s.so.1
b75fe000-b75ff000 rw-p 0001c000 08:01 1704884 /lib/i386-linux-gnu/libgcc_s.so.1
b75ff000-b7601000 rw-p 00000000 00:00 0
b7601000-b77a0000 r-xp 00000000 08:01 1704863 /lib/i386-linux-gnu/libc-2.15.so
b77a0000-b77a2000 r--p 0019f000 08:01 1704863 /lib/i386-linux-gnu/libc-2.15.so
b77a2000-b77a3000 rw-p 001a1000 08:01 1704863 /lib/i386-linux-gnu/libc-2.15.so
b77a3000-b77a6000 rw-p 00000000 00:00 0
b77b8000-b77bb000 r-xp 00000000 08:01 1704847 /lib/i386-linux-gnu/libSegFault.so
b77bb000-b77bc000 r--p 00002000 08:01 1704847 /lib/i386-linux-gnu/libSegFault.so
b77bc000-b77bd000 rw-p 00003000 08:01 1704847 /lib/i386-linux-gnu/libSegFault.so
b77bd000-b77bf000 rw-p 00000000 00:00 0
b77bf000-b77c0000 r-xp 00000000 00:00 0 [vdso]
b77c0000-b77e0000 r-xp 00000000 08:01 1704843 /lib/i386-linux-gnu/ld-2.15.so
b77e0000-b77e1000 r--p 0001f000 08:01 1704843 /lib/i386-linux-gnu/ld-2.15.so
b77e1000-b77e2000 rw-p 00020000 08:01 1704843 /lib/i386-linux-gnu/ld-2.15.so
bfd6b000-bfd8c000 rw-p 00000000 00:00 0 [stack]

 

 轉自: https://blog.csdn.net/qq_29350001/article/details/53780697