利用linux信號機制調試段錯誤(Segment fault)
在實際開發過程中,大家可能會遇到段錯誤的問題,雖然是個老問題,但是其帶來的隱患是極大的,只要出現一次,程序立即崩潰中止。如果程序運行在PC中,segment fault的調試相對比較方便,因為可以通過串口、顯示器可以查看消息,只要程序運行,通過GDB調試工具即可捕捉產生segment fault的具體原因。但是不知大家有沒有想法,當程序運行在嵌入式設備上時,你所面臨資源的缺乏,你沒有串口打印信息,沒有顯示器可查看,你不知道程序運行的狀態,如果程序的產生segment falut這種bug發生的周期1年之內只發生過三四次,時間又不確定,你又如何調試知道程序發生錯誤的具體位置呢?等等問題實在令人糾結。
解決此問題的方式方法有如下幾種:
1 在產品發布之前,盡量將所有segment fault產生原因找出,全部消除(最理想情況下)
2 在程序中的關鍵位置增加打印信息,設定打印級別,通過打信的信息縮小查bug的范圍,如果是嵌入式設備,需要將這些信息寫入文件,保存在flash中。
3 利用看門狗喂狗,如果程序中止或長期陷入死循環,將重新加載程序
4 利用linux信號機制來解決段錯誤問題(有點類似于軟看門狗)
下面我著重講解第4要點的解決方式:
首先,我先敘述下我的總體思路,假設我的程序在某處調用一函數dummy_func(),這個函數有個segment fault段錯誤,如果你程序沒對其進行處理,不用懷疑,你的程序馬上掛掉,如果是嵌入式設備里的程序,你可能不知道產生segment fault的具體原因和具體位置,只能看log日志慢慢分析。我們知道,在linux的信號機制中,當產生segment fault錯誤時,程序會產生SIGSEGV信號,于是我們試想,如果我們在程序中能夠及時捕捉到此信號,然后在此信號處理函數中重新加載此應用程序,就可以實現類似看門狗的功能,暫且將這種方法稱之為軟件看門狗吧。但是此方法只是權宜之計,它可以讓你的產品在客戶面前保持良好印象(因為如果程序掛掉后又重新加載了,而客戶不知道),而不至于長期很頻繁的去現場解決此問題,這種方式適用于產生段錯誤的周期具有不確定性,其實我們都知道遇到segment fault我們還得解決此問題產生的具體原因,這才是正確的解決方式。
下面我列出完整的程序源碼,雖然簡單,但是非常有用:
/******************************程序開始 **********************************/
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
char main_status = 0;
/***********************
* 此函數產生一個段錯誤
* *********************/
void dummy_func(void)
{
printf("hello world/n");
char *p = NULL; //0地址
*p = 0x1a; //對0地址寫入數據,出現段錯誤
return;
}
/************************
* 此函數用于重新加載程序
************************/
void deal(void)
{
char buffer[255];
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "cd ~/test");
system(buffer);
/*
*此延時很重要,如果不加延時,ctrl+c的信號無法及時處理(即ctrl+c失效),程序將循環加載
* 如果不加延時,程序又重新執行后面./test1語句將重新加載應用程序了
*/
sleep(5);
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "./test1");
system(buffer);
printf ("xxxxxxxxxxxxxxx/n");
if( 1 == main_status )
exit(0);
}
/**************************
* 捕捉到ctrl+c信號的處理函數
**************************/
void ctrl_c_func(int signo)
{
printf("stop the demo/n");
main_status = 1; //置標志位
exit(0);
}
int ctrl_c_func_init(void)
{
int ret = 0;
struct sigaction act;
act.sa_handler = ctrl_c_func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGINT, &act, NULL);
}
/****************************
* main主程序
****************************/
int main(int argc, char **argv)
{
signal(SIGSEGV, &deal); //捕捉SIGSEGV信號
signal(SIGINT, &ctrl_c_func); //捕捉SIGINT信號
// ctrl_c_func_init(); //如果不用上面捕捉,調用這個函數也行
while(1)
{
if( 1 == main_status ) //如果接收到標志位,則退出
{
exit(0);
}
dummy_func(); //調用產生segment fault函數
}
return 0;
}
/******************************程序結束 **********************************/