什么是core dump?
分析core dump是Linux應用程序調試的一種有效方式,像內核調試抓取ram dump一樣,core dump主要是獲取應用程序崩潰時的現場信息,如程序運行時的內存、寄存器狀態、堆棧指針、內存管理信息、函數調用堆棧信息等。
Core dump又稱為“核心轉儲”,是Linux基於信號實現的。Linux中信號是一種異步事件處理機制,每種信號都對應有默認的異常處理操作,默認操作包括忽略該信號(Ignore)、暫停進程(Stop)、終止進程(Terminate)、終止並產生core dump(Core)等。
| 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 |
| SIGTRAP | 5 | Core | Trace/breakpoint trap |
| SIGABRT | 6 | Core | Abort signal from abort(3) |
| SIGIOT | 6 | Core | IOT trap. A synonym for SIGABRT |
| SIGEMT | 7 | Term | |
| SIGFPE | 8 | Core | Floating point exception |
| SIGKILL | 9 | Term | Kill signal, cannot be caught, blocked or ignored. |
| SIGBUS | 10,7,10 | Core | Bus error (bad memory access) |
| 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, cannot be caught, blocked or ignored. |
| SIGTSTP | 18,20,24 | Stop | Stop typed at terminal |
| SIGTTIN | 21,21,26 | Stop | Terminal input for background process |
| SIGTTOU | 22,22,27 | Stop | Terminal output for background process |
| SIGIO | 23,29,22 | Term | I/O now possible (4.2BSD) |
| SIGPOLL | Term | Pollable event (Sys V). Synonym for SIGIO | |
| SIGPROF | 27,27,29 | Term | Profiling timer expired |
| SIGSYS | 12,31,12 | Core | Bad argument to routine (SVr4) |
| SIGURG | 16,23,21 | Ign | Urgent condition on socket (4.2BSD) |
| SIGVTALRM | 26,26,28 | Term | Virtual alarm clock (4.2BSD) |
| SIGXCPU | 24,24,30 | Core | CPU time limit exceeded (4.2BSD) |
| SIGXFSZ | 25,25,31 | Core | File size limit exceeded (4.2BSD) |
| SIGSTKFLT | 16 | Term | Stack fault on coprocessor (unused) |
| SIGCLD | 18 | Ign | A synonym for SIGCHLD |
| SIGPWR | 29,30,19 | Term | Power failure (System V) |
| SIGINFO | 29 | A synonym for SIGPWR, on an alpha | |
| SIGLOST | 29 | Term | File lock lost (unused), on a sparc |
| SIGWINCH | 28,28,20 | Ign | Window resize signal (4.3BSD, Sun) |
| SIGUNUSED | 31 | Core | Synonymous with SIGSYS |
什么情況下會產生core dump呢?
以下情況會出現應用程序崩潰導致產生core dump:
- 內存訪問越界 (數組越界、字符串無\n結束符、字符串讀寫越界)
- 多線程程序中使用了線程不安全的函數,如不可重入函數
- 多線程讀寫的數據未加鎖保護(臨界區資源需要互斥訪問)
- 非法指針(如空指針異常或者非法地址訪問)
- 堆棧溢出
怎么獲取core dump呢?
Linux提供了一組命令來配置core dump行為:
1. ulimit –c 查看core dump機制是否使能,若為0則默認不產生core dump,可以使用ulimit –c unlimited使能core dump

2. cat /proc/sys/kernel/core_pattern 查看core文件默認保存路徑,默認情況下是保存在應用程序當前目錄下,但是如果應用程序中調用chdir()函數切換了當前工作目錄,則會保存在對應的工作目錄
3. echo “/data/xxx/<core_file>” > /proc/sys/kernel/core_pattern 指定core文件保存路徑和文件名,其中core_file可以使用以下通配符:
%% 單個%字符
%p 所dump進程的進程ID
%u 所dump進程的實際用戶ID
%g 所dump進程的實際組ID
%s 導致本次core dump的信號
%t core dump的時間 (由1970年1月1日計起的秒數)
%h 主機名
%e 程序文件名
4. ulimit –c [size] 指定core文件大小,默認是不限制大小的,如果自定義的話,size值必須大於4,單位是block(1block = 512bytes)
怎么分析core dump?
我們首先編寫一個程序,人為地產生core dump並獲取core dump文件。

程序如上圖,我們通過除零操作產生core dump

編譯運行產生了浮點數異常,從而引發core dump (注:編譯時必須添加-g參數,表示添加調試信息,這樣才可以使用gdb進行調試)

當前目錄下產生了core文件,使用file命令查看core文件類型

發現core文件類型為ELF格式,使用readelf查看ELF文件頭部信息如下

通過Type字段可以看到,該文件為core文件
前面我們講到core dump可以查看應用程序崩潰時的現場信息,這里,我們需要gdb命令輔助實現,使用gdb test core(即test可執行文件和core文件)

“Program terminated with signal 8, Arithmetic exception”表示應用程序是因為接收到Linux內核發出的Signal 8信號量而終止執行,Signal 8是SIGFPE,即浮點數異常。同時打印出了出問題的代碼行result = a/b。

通過bt –n (backtrace)命令可以顯示函數調用棧信息,n表示顯示的調用棧層數,不指定則打印完整調用棧。因為test.c調試程序不涉及函數調用,所以我們只能看到main函數的棧信息,如果程序是在main函數的字函數中出錯,則可以打印更多的調用棧信息。

通過disassemble命令可以打印出錯時的匯編代碼片段,其中箭頭指向的是出錯的指令,即PC寄存器指向的地址,PC寄存器存放的是下一條執行指令。很多人會很困惑,因為通常程序執行的時候,PC寄存器指向的指令是待執行指令,就會懷疑gdb定位到的出錯指令的准確性。其實,CPU確實是執行過這一條指令,但是CPU發現這條指令發生的異常,這個時候就會進入異常處理流程,gdb通過回溯調用棧准確地回到這一條指令執行前的狀態,所以PC寄存器的值是完全可信的。

可以看到調用了div指令做除法操作,被除數是-0x8(%ebp),指當前棧基址向下偏移8個字節所在內存單元的數值,EBP是棧基址寄存器。同時我們可以看到前面通過movl $0x0, -0x8(%ebp)將0保存到該內存單元,證明被除數為0。
如上所示,gdb默認使用AT&T匯編語言格式打印匯編語句,可以通過set disassembly-flavor intel設置為intel匯編語言格式。

通過list命令可以查看當前指令附近的代碼,前提是gdb工具可以找到源代碼

