程序發生段錯誤時,提示信息不多,下面有幾種查看段錯誤的發生信息的途徑。html
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]
使用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 命令列出二進制文件中符號表,包括符號地址、符號類型、符號名等。這樣能夠幫助定位在哪裏發生了段錯誤。
root@# nm a.out
使用 ldd 命令查看二進制程序的共享連接庫依賴,包括庫的名稱、起始地址,這樣能夠肯定段錯誤究竟是發生在了本身的程序中仍是依賴的共享庫中。
root@t# ldd a.out
% 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; } 輸出結果: 段錯誤(核心已轉儲)
這個是看似最簡單,但每每不少狀況下十分有效的調試方式,也許能夠說是程序員用的最多的調試方式。
簡單來講,就是在程序的重要代碼附近加上像 printf 這類輸出信息,這樣能夠跟蹤並打印出段錯誤在代碼中可能出現的位置。
爲了方便使用這種方法,可使用條件編譯指令 #define DEBUG 和 #endif 把 printf 函數包起來。
這樣在程序編譯時,若是加上 -DDEBUG 參數就能夠查看調試信息;不然不加上參數就不會顯示調試信息。
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
A、僅當能肯定程序必定會發生段錯誤的狀況下適用。
B、當程序的源碼能夠得到的狀況下,使用 -g 參數編譯程序
C、通常用於測試階段,生產環境下 gdb 會有反作用:使程序運行減慢,運行不夠穩定,等等。
D、即便在測試階段,若是程序過於複雜,gdb 也不能處理。
上面有提到段錯誤觸發SIGSEGV信號,經過man 7 signal,能夠看到SIGSEGV默認的處理程序(handler)會打印段錯誤信息,併產生 core 文件,由此咱們能夠藉助於程序異常退出生成的 core 文件中的調試信息,使用 gdb 工具來調試程序中的段錯誤。
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
A、適合於在實際生成環境下調試程度的段錯誤(即在不用從新發生段錯誤的狀況下重現段錯誤)
B、當程序很複雜,core 文件至關大時,該方法不可用
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),接下來打開程序的源碼,找到彙編指令對應的源碼,也就定位到段錯誤了。
A、不須要 -g 參數編譯,不須要藉助於core文件,但須要有必定的彙編語言基礎。
B、若是使用 gcc 編譯優化參數(-O1,-O2,-O3)的話,生成的彙編指令將會被優化,使得調試過程有些難度。
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]
(C專家編程 7.7節)