【代碼調試】Linux coredump分析



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

【2】Linux上Core Dump文件的形成和分析

【3】由coreDump引發的一次探討

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章