文章目錄
1 前言
在上一篇文章中描述瞭如何使用Valgrind工具檢查內存相關問題,包括內存泄露、空指針使用、野指針使用、重複釋放等問題。對於大多數情況下,Valgrind的作用性體現更多在於“內存泄露”檢查,因爲空指針、野指針的訪問,會引發程序段錯誤(segment fault )而終止,此時可以藉助linux系統的coredump文件結合gdb工具可以快速定位到問題發生位置。此外,程序崩潰引發系統記錄coredump文件的原因是衆多的,野指針、空指針訪問只是其中一種,如堆棧溢出、內存越界等等都會引起coredump,利用好coredump文件,可以幫助我們解決實際項目中的異常問題。
2 coredump
2.1 什麼是coredump
coredump指的是應用程序因爲各種原因導致異常終止時,操作系統將應用程序的異常發生時的狀態信息記錄爲一個coredump的文件。一個coredump文件主要包含了應用程序的內存信息、寄存器狀態、堆棧地址、函數調用上下文,開發人員通過分析這些信息,確定程序異常發生時的調用位置,如果是堆棧溢出,還需分析多層函數的調用信息。
通俗來說,coredump是操作系統記錄應用程序非正常終止的信息,留給我們排查問題的依據。
2.2 coredump意義
coredump對於分析程序異常的作用是不言而喻的。以以前我們學習ARM 32位MCU爲例(STM32),由於初學過程,代碼質量參差不齊,經常引起硬件錯誤中斷(Hard Fault)。面對這種情況,我們是束手無策的,一方面是程序發生錯誤後沒有記錄到有參考意義的信息(當然,可以通過仿真器實時獲取堆棧信息,但對於實際產品不不現實);另一方面是問題復現概率比較低,復現條件不確定。linux系統是一個“考慮周全”的操作系統,應用程序發生異常,會記錄一些關鍵的信息,已便於我們分析。coredump的意義就在於此。
- 根據記錄信息分析程序異常的原因
- 根據記錄信息反推出現問題的條件,復現問題來驗證
2.3 coredump產生的場景
應用程序發生異常時,會產生coredump文件記錄,這些異常幾乎都與內存相關,總結起來包括幾點。
【1】內存訪問越界
- 數組下標越界
- 超出動態(malloc/new)內存申請範圍
- 字符串沒有結束符,一些函數依賴於字符串結束符,如 strcpy、strcmp、sprintf
【2】訪問非法指針
- 空指針(未申請內存)
- 野指針(已釋放內存)
- 重複釋放指針(內存)
- 指針強制轉換,指針強制轉換需特別謹慎,可能因爲對齊、起始地址等問題引起內存訪問錯誤
【3】堆棧溢出,分配大量局部變量、多重函數調用、較深的函數遞歸等可能導致堆棧溢出
【4】多線程訪問
- 調用不可重入函數
- 共享數據未互斥訪問
2.2 開啓coredump
系統默認不開啓coredump記錄功能,執行"ulimit -c"
查看是否開啓,返回0表示未開啓coredump記錄功能。
- 查看是否記錄coredump
acuity@ubuntu:~$ ulimit -c
1024
可以使用“ulimit -c [size]”
命令指定記錄coredump文件的大小,即是開啓coredump記錄。需要注意的是,單位爲block
,1block=512bytes。
- 開啓coredump
acuity@ubuntu:~$ ulimit -c 1024
萬一程序比較糟糕,指定的coredump文件大小限制,導致文件記錄不到或者缺失怎麼辦。此時,一勞永逸的辦法就是不限制coredump文件大小;執行“ulimit -c unlimited”
設定,設置時需要root權限。
- 不限制coredump文件大小
root@ubuntu:/home/acuity# ulimit -c unlimited
root@ubuntu:/home/acuity# ulimit -c
unlimited
以上方式都是在終端臨時設置開啓coredump記錄功能,系統重啓後失效,很顯然這不是理想的方法。理想的方法是修改配置文件,使得系統一直開啓coredump記錄功能,至少在項目開發測試階段是需要開啓的。原則上,軟件發佈後也應該記錄,出現問題後能夠有追溯和分析問題的依據。
- 通過配置文件使能
在"/etc/profile"
文件增加" ulimit -c unlimited "
。
注:
ulimit 命令是一個設置資源限制的命令,除了coredump外,還可以設定其他資源限制
- -a:查看當前資源限制信息
- -c <core最大值>:設定core文件的最大值,單位爲塊(block)
- -d <數據節段大小>:進程數據段最大值,單位爲KB
- -f <文件大小>:進程可創建最大文件值,單位爲塊(block)
- -H:設置資源的硬性限制,設置後不可更改
- -l <內存大小>: 可加鎖內存大小,單位 爲KB
- -m <內存大小>:指定可使用內存的上限,單位爲KB
- -n <文件數目>:進程最大可打開的文件數(文件描述符數目)
- -p <緩衝區大小>:管道緩衝區的大小,單位爲KB
- -s <堆棧大小>:線程最大堆棧大小,單位爲KB
- -S:設置資源的彈性限制,不可超過硬性資源限制
- -t <cpu時間>:cpu最大佔用時間,單位爲秒
- -u <進程數目>:用戶可創建的最大進程數
- -v <虛擬內存大小>:進程最大可用虛擬內存,單位爲KB
**除此之外,還有可以通過在代碼中設定開啓coredump。**然而一般不推薦該方式, 因爲如果代碼中沒有增加開啓功能,而應用程序又發生了異常,系統將無法記錄coredump。建議在系統配置文件設置開啓。
訪問接口:
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim); /* 獲取coredump 文件限制大
小 */
int setrlimit(int resource, const struct rlimit *rlim);/* 設置coredump 文件限制
大小 */
-
功能,獲取(設置)系統資源限制,coredump只是系統資源的一種,如虛擬內存大小、進程堆棧、最大進程數等等
-
resource
,系統資源標識,對於coredump,爲RLIMIT_CORE
-
rlim
,資源限制數據結構,即是限制值struct rlimit { rlim_t rlim_cur; rlim_t rlim_max; };
-
返回,成功返回0,失敗返回-1,錯誤碼存於
error
中
例子:
#include <sys/resource.h>
int main(int argc, char * argv [ ])
{
struct rlimit rlmt;
rlmt.rlim_cur = (rlim_t)1024;
rlmt.rlim_max = (rlim_t)1024;
if (-1 == setrlimit(RLIMIT_CORE, &rlmt))
{
perror("setrlimit error");
return -1;
}
}
2.3 coredump存儲位置與命名
coredump文件默認存儲於應用程序執行目錄下,文件名稱爲“core”。使用默認文件名稱顯然不是一個好的方式,如果有多個應用程序異常終止,將覆蓋core文件;或者同一個應用程序,在異常終止後被守護進程重新啓動運行,再次異常時導致core文件被覆蓋。
- 文件名稱帶進程id(PID)
修改"/proc/sys/kernel/core_uses_pid"
文件,可以將進程的id作爲作爲擴展名,文件內容爲1表示使用擴展名,默認爲0;使用進程id擴展名時,生成的core文件格式爲"core.xxx"
,xxx爲進程id。
- 更詳細的名稱以及存儲位置
修改"/proc/sys/kernel/core_pattern"
文件可以設置coredump文件的存儲位置和更詳細的文件名稱。默認位置和名稱信息如下:
root@ubuntu:/home/acuity# cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c %d %P %E
擴展字符含義:
%p - 擴展進程id(pid)
%P - 與%p作用相同
%u - 擴展用戶id(uid)
%g - 擴展組id(gid)
%s - 擴展產生信號
%t - 擴展當前時間,從1970-01-0100:00:00開始的秒數
%h - 擴展主機名
%e - 擴展應用程序文件名稱
%E - 擴展應用程序文件名稱,包括文件絕對路徑
coredump存儲目錄不變(存儲於當前應用程序目錄下),文件擴展名稱增加應用程序文件名稱、進程id、當前時間,這是實際場景常用的基本用法,能否適用絕對部分場合。可以用vi直接打開文件編輯,也可以使用echo
修改文件內容,前提都是必須以root權限修改。
- 在應用程序當前目錄生成
“core.name.pit.time”
文件
echo ./core.%e.%p.%t > /proc/sys/kernel/core_pattern
如需指定其他存儲路徑,可以修改路徑部分。
- 在
“/home”
目錄生成“core-name-pit-time”
文件
echo /home/core-%e-%p-%t > /proc/sys/kernel/core_pattern
注:
指定某些目錄,可以生成coredump文件,但文件內容爲空,可能是權限問題??
3 使用coredump
編寫一個“非法”程序,讓系統記錄coredump,結合gdb來分析過程;編譯時需加入"-g"
,保留調試信息。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
p = malloc(4);
if (p == NULL)
{
perror("malloc failed");
}
printf("address [0x%p]\r\n", p);
free(p);
free(p); /* 重複釋放*/
return 0;
}
編譯執行該程序,由於訪問野指針,程序異常退出,將產生一個coredump文件。
- 查看coredump文件
root@ubuntu:/usr# file core.coredump.2046.1591860958
core.coredump.2046.1591860958: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from './coredump'
注:
有時候coredump只生成一個空文件,可以通“file”命令查看
- 啓動gdb 調試命令
gdb exe-file core-file
- 查看coredump信息
gdb後,鍵入“bt”
- 執行結果
通過分析,出現異常的地方是第17行,翻閱源碼,17行執行了重複釋放動態申請內存的操作。
4 參考文章
【1】詳解coredump