段錯誤信息的獲取和調試

1、段錯誤信息的獲取

程序發生段錯誤時,提示信息不多,下面有幾種查看段錯誤的發生信息的途徑。html

一、dmesg

dmesg 能夠在應用程序崩潰時,顯示內存中保存的相關信息。linux

以下所示,經過 dmesg 命令能夠查看發生段錯誤的程序名稱、引發段錯誤發生的內存地址、指令指針地址、堆棧指針地址、錯誤代碼、錯誤緣由等。程序員

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

二、-g

使用gcc編譯程序的源碼時,加上 -g 參數,這樣可使得生成的二進制文件中加入能夠用於 gdb 調試的有用信息。redis

可產生供gdb調試用的可執行文件,大小明顯比只用-o選項編譯彙編鏈接後的文件大。shell

gdb的簡單使用:編程

(gdb)l  列表(list)ubuntu

(gdb)r  執行(run)session

(gdb)n  下一個(next)函數

(gdb)q  退出(quit)工具

(gdb)p  輸出(print)

(gdb)c  繼續(continue)

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

(gdb)d   刪除斷點(delete)

三、nm

使用 nm 命令列出二進制文件中符號表,包括符號地址、符號類型、符號名等。這樣能夠幫助定位在哪裏發生了段錯誤。

root@# nm a.out 


四、ldd

使用 ldd 命令查看二進制程序的共享連接庫依賴,包括庫的名稱、起始地址,這樣能夠肯定段錯誤究竟是發生在了本身的程序中仍是依賴的共享庫中。

root@t# ldd a.out 

五、dbx

能夠在dbx命令下運行程序,這樣能夠查看程序是否用完了堆棧

% dbx a.out
(dbx) catch SIGSEGV
(dbx) run
...
signal SEGV (segmentation violation in <some_routine> at 0xeff57708)
(dbx) where

若是如今能夠看到調用鏈,那說明堆棧空間還未用完。可是若是是:

fetch at 0xeffe7a60 failed -- I/O error
(dbx)

那麼堆棧可能已經用完。上面這個十六進制的數就是能夠提取或映射的堆棧地址。
能夠嘗試在C-shell中調整堆棧段的大小限制。如下調整爲10KB

limit stacksize 10

 


2、段錯誤信息的調試

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

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

一、使用 printf 輸出信息

這個是看似最簡單,但每每不少狀況下十分有效的調試方式,也許能夠說是程序員用的最多的調試方式。

簡單來講,就是在程序的重要代碼附近加上像 printf 這類輸出信息,這樣能夠跟蹤並打印出段錯誤在代碼中可能出現的位置。

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

這樣在程序編譯時,若是加上 -DDEBUG 參數就能夠查看調試信息;不然不加上參數就不會顯示調試信息。

二、使用 gcc 和 gdb

1)調試步驟

A、爲了可以使用 gdb 調試程序,在編譯階段加上 -g 參數。

root@# gcc -g test.c

B、使用 gdb 命令調試程序

root@# 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 信號,觸發段錯誤,並提示地址 0x080483c四、建立了一個空指針,而後試圖訪問它的值(讀值)。

能夠經過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 也不能處理。

三、使用 core 文件和 gdb

上面有提到段錯誤觸發SIGSEGV信號,經過man 7 signal,能夠看到SIGSEGV默認的處理程序(handler)會打印段錯誤信息,併產生 core 文件,由此咱們能夠藉助於程序異常退出生成的 core 文件中的調試信息,使用 gdb 工具來調試程序中的段錯誤。

1)調試步驟

A、在一些Linux版本下,默認是不產生 core 文件的,首先能夠查看一下系統 core 文件的大小限制:

root@# ulimit -c
0

B、能夠看到默認設置狀況下,本機Linux環境下發生段錯誤不會自動生成 core 文件,下面設置下 core 文件的大小限制(單位爲KB)

root@# ulimit -c 1024
root@# ulimit -c 
1024

C、運行程序,發生段錯誤生成的 core 文件

root@# ./a.out 
段錯誤 (核心已轉儲)

D、加載 core 文件,使用 gdb 工具進行調試

root@ubuntu:/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 文件至關大時,該方法不可用

四、使用 objdump

1)調試步驟

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

root@# 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 生成二進制的相關信息,重定向到文件中

root@# objdump -d a.out > a.outDump 
root@# ls
a.out  a.outDump  core  test.c  

其中,生成的 a.outDump 文件中包含了二進制文件的 a.out 的彙編代碼

C、在 a.outDump 文件中查找發生段錯誤的地址

root@ubuntu:/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)的話,生成的彙編指令將會被優化,使得調試過程有些難度。

五、使用catchsegv

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

root@t# 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

(C專家編程 7.7節)

相關文章
相關標籤/搜索