段錯誤信息的獲取和調試


一、段錯誤信息的獲取

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

1、dmesg

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

如下所示,通過 dmesg 命令可以查看發生段錯誤的程序名稱、引起段錯誤發生的內存地址、指令指針地址、堆棧指針地址、錯誤代碼、錯誤原因等。

root@#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 命令列出二進制文件中符號表,包括符號地址、符號類型、符號名等。這樣可以幫助定位在哪里發生了段錯誤。

root@# nm a.out 


4、ldd

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

root@t# ldd a.out 

5、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

 


二、段錯誤信息的調試

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

#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 參數。

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 信號,觸發段錯誤,並提示地址 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 文件的大小限制:

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 文件相當大時,該方法不可用

4、使用 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)的話,生成的匯編指令將會被優化,使得調試過程有些難度。

5、使用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節)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM