linux 下 C 編程 C版的try catch 捕捉段錯誤和異常處理

哇塞,C語言有try catch嗎?固然沒有。倒。。可能有人說了,那你野鬼說沒有的東西作什麼。 

    這裏須要重申一下,所謂正向設計下問題檢測的開發方法。正向設計時,在錯誤檢測和問題修復的方法是指:linux

      根據源碼分析,在源碼中加插檢測代碼的方式,驗證對代碼的理解和預判是否正確。bash

     而反向跟蹤是根據機器執行動做,反向理解邏輯的運行狀態,例如GDB。二者不少方面都很像,但存在一個最主要的區別在於,你是在先驗的讓程序運行判斷是否符合先驗,仍是在程序的運行中理解代碼的邏輯。socket

    例如正向設計的問題判斷,若是經過try catch來處理,則表示你已經估計到這裏是個潛在可能的錯誤,而經過運行來驗證你的判斷是否成立。錯誤的理解和判斷是在對源碼的分析上,反之,經過 GDB或者其餘IDE加斷點的跟蹤,屬於反向檢測運行狀態來判斷邏輯可能哪裏出錯。 
    惋惜try catch在C標準裏面並無。這裏,就經過指針訪問的段錯誤,來設計一個相似try catch的小玩意。但願新手能經過這個寫出其餘的try catch。 
    首先咱們要動點C標準裏,signal和setjmp的東西,以及GNU C的 glibc函數sigsetjmp,siglongjmp。記得要注意哪些是標準裏的,哪些不是,這對之後的平臺移植很用做用,哪怕你不移植,但萬一別人 要移植,由於你沒有區分好,致使別人處處找和平臺相關的代碼,最後這個代碼別人用不了,你的代碼又有何價值。 
    先說一下signal這個函數。這函數的做用就是相似對中斷向量表的重載,實際上是個重定位工做。中斷向量表是什麼意思,很簡單,就是有一個表,表裏面有一 堆函數地址,若是對應的中斷髮生了,就跳轉到該中斷對應的中斷向量表的對應存儲區域的函數裏(但願看這句話你別跟着念,眼睛也別花,哈)。若是不重定位這 些函數,則會啓用默認的函數。例如給打印個消息,而後讓進程中止。若是經過signal 對這個中斷向量表進行」重載「,則能夠進入你指定的函數。而不會進入默認的方式。 
    簡單舉例吧,正常的程序,你在bash上執行 ctrl+C,會讓程序中止。實際是怎麼發生的呢? 
    一、鍵盤發現你按鍵了,則會發出一個信號,除了按鍵值自己。該信號是告訴CPU,我有事了,CPU的硬件若是不對這個信號視而不見的話,就會告訴OS,哦,有個硬中斷髮生。 
    二、此時,OS就對應的發出個軟中斷,linux下就是 SIGINT。 
    三、若是對應你當前進程的本身的」中斷向量表「是默認狀況,則會OS啓動一個函數,這個函數會發出一個kill的工做,要求將你這個進程結束。 
    四、OS此時發現這個動做。就把你的進程給卡擦了。 
    而你若是用signal「重載」這個中斷,則此時原先默認函數不會執行,你會發現,你的程序該作什麼仍是作什麼,除非你的新函數仍然要求kill掉你這個進程。 
    那麼對於段錯誤,也存在一箇中斷,是由誰產生的? MMU,硬件產生的。OS獲取這個錯誤以後,就會到對應進程的「中斷向量表」找你是否重載過這個對應的中斷響應函數,固然這中間還有些其餘工做好比中斷屏 蔽方面的檢查工做等,我就不展開了。由此,若是你寫了一個新的函數,則能夠不用退出,能夠處理些本身的想作的事情。 
    可是很討厭的一個問題,若是是你的進程出錯,並且出錯的理由是對一個不正確的內存空間進行讀寫操做,不管你執行多少次,這個錯誤仍然存在。由於你的代碼產 生個由MMU發出的段錯誤的中斷,此時你的代碼被強行掛起來,就是說如今輪不到你玩了,而後OS處理對應的中斷響應,就是你寫的代碼,而寫的那個函數執行 完畢後,若是不kill或者其餘動做,你的代碼還會被再次執行。那麼你的進程會怎麼執行呢?在你上次錯的地方繼續執行。。。。這就鬱悶大發了。由於再次產 生個錯誤,此時又會進入你的中斷響應函數。 
    因而乎,你會發現,你的屏幕在不停的打印東西,若是你的那個函數內有條printf想提醒你,進入了這個函數了。 
    此時,咱們就須要動點手段了。這裏要談一下setjmp ,和longjmp,最終會用到sigsetjmp,siglongjmp,注意,這兩玩意不是C標準的,glibc支持,其餘的一些C環境也支持,我不一一列出來了。能夠查資料。 
    先說setjmp, longjmp。上下文這個詞在OS裏,特別進程管理部分常常提到,什麼意思呢?簡單說就是現場環境,不過只是CPU裏面的,包括指令的位置(其實也是在 寄存器裏),常規寄存器裏的內容(也包括堆棧指針寄存器),和外部存儲器就是內存沒有關係。 
    繼續舉例吧。 
    假設,劇場正在上演一個話劇,還沒結束呢。結果通知,馬上清場,爲何,領導要來開會,你別問爲何,領導就是領導,優先級高,此時你怎麼辦,那就和觀衆 商量一下,咱們把現場記錄下來,臺詞說哪也記錄下來,等領導開完會我們接着看,此時劇場的狀況清點記錄完畢,並清理乾淨,等領導開會,會開完了,再根據被 打斷時的場景進行恢復,則此時觀衆能夠連貫的繼續看下去。 
    另外一個例子就是香港的賭王片,賭到高潮的時候,不管是正方的叛徒仍是反方的臥底,不管是用刀仍是用槍,反正把男一號給搞傷了,怎麼辦?封牌局,拿個罩子, 把桌子罩起來,誰也別想改動這些牌的內容。等男二號上時在繼續賭,牌是什麼狀況,確定沒有變過,不管中間穿插了多少其餘鏡頭。假設恰好這個時候有跑龍套的 要用桌子吃午餐,劇組贊成了,把桌子給他用,可是桌子上面的東西則原封不動的挪到別的地方。等男二號來,再把跑龍套的趕走,恢復成前面的牌局。 
    這裏劇場場景的記錄並切換成領導的會議桌,以及桌子上的牌局挪動,讓給跑龍套的吃午餐,都是叫作上下文切換。setjmp的做用就是保存當前進入 setjmp函數時的環境。同時setjmp返回個值爲0。以區別longjmp跳轉到當前位置。相似函數調用函數,父函數須要保存的現場工做,不然子函 數退出時,父函數也沒辦法正常繼續工做啊。是否是。固然 setjmp所保存的東西必函數調用時保存的要多一些。其實setjmp沒什麼特點。我本身都寫過對應彙編以實現特定硬件上的需求。就是一堆mov,把寄 存器的值存到指定的位置再返回0。 
    而longjmp的意思是,能夠在你的代碼任意的位置,只要setjmp執行過之後的地方,直接跳,跳哪呢?就是跳到setjmp調用時的位置,這個跳哪 的信息從哪得來的,就是setjmp的參數指向的一個BUF,你在這個BUF裏面保存了當前地址。所以,若是屢次setjmp同一個buf,則在跳到最後 一次,若是每次setjmp了不一樣的BUF,那麼哪一個BUF做爲longjmp的參數,就是跳到對應setjmp的位置。此時等同於setjmp被返回, 只不過返回值不爲0,由此判斷是longjmp過來的。繼續舉例子。 
    導演說,第N個鏡頭。。。而後就開始演,演了一半,穿幫了,導演說,停!此時就是longjmp,longjmp去哪?和你演的這段都不要緊。直接到當前這個鏡頭的開始位置,爲何當前鏡頭能夠拍N屢次,就是由於你在鏡頭開始位置作了一個setjmp。 

    如今說下sigsetjmp siglongjmp。 
    sigsetjmp ,siglongjmp比setjmp ,longjmp的組合多了箇中斷屏蔽信息的存儲。此時siglongjmp能夠恢復到sigsetjmp出現時的中斷屏蔽狀況。在後面給出的代碼的 test10函數中,特意作了一個setjmp ,longjmp的方式,你會發現,第二次出錯,並不會進入normal_longjmp函數。所以此時的中斷配置等信息並無對應記錄下來,屬於出錯後 的狀況。 
     OK。如今說說setjmp longjmp有什麼好處。 
    前面說了。signal對SIGSEGV這個中斷的響應函數修改後,函數退出,會再次執行MMU發生錯誤的代碼位置,所以咱們要確保函數跳過當前出錯的位置,執行到咱們但願跳過的代碼。相似C++的try catch那樣,咱們但願有try catch。以下 
    char *p = "1234"; 
    TRY 
    p[2] = '1';//這顯然是錯的嘛 
    CATCH ;// 
    則咱們能夠 
1 if (setjmp(buf) == 0){
2     p[2] = '1';
3 }


    OK了,爲何這樣就能夠了呢?由於若是不是longjmp過來的,setjmp始終返回0,則此時必然會執行p[2],若是p[2]不會產生 SIGSEGV的錯誤,就不會執行longjmp,由此一切照舊,該作什麼作什麼。若是p[2] = '1'錯誤,則會發出中斷信號 SIGSEGV,而假設咱們把 下面SIGNAL_SEGV_DONE這個函數先前用 signal重載過,則此時發生的錯誤,會致使進入 SIGNAL_SEGV_DONE。以下 
1 void SIGNAL_SEGV_DONE(int signum){
2     printf("SEG ERROR !\n");
3     longjmp(buf,1);
4 }


    注意這裏第2個參數是返回的值,就是跳轉到setjmp的位置,等同於setjmp返回的值,此時等於上面if的條件不成立,則等於跳出了{}。 
    而若是新手仍是想不一樣,這個1怎麼就被返回到setjmp的地方,並且像函數返回同樣呢?我就說兩個事情。 
    一、函數的返回值是放在指定寄存器裏的,好比ARM是放在r0裏的,子函數把要return的值放在r0 裏,返回父函數,則父函數對子函數的返回值直接能夠從r0裏取得(或者不取,若是不存在返回,或者暫時不須要利用這個函數的返回值) 
    二、一個函數調用另外一個函數,沒什麼深奧的技巧。就是在返回時把寄存器,包括指令寄存器等等恢復成調用前的狀況。惟一是指令寄存器還要再加一下,跳過函數調用的那條指令,而後一個跳轉,就回到父函數了。 
    下面給出代碼,我是基於malloc_free上面進行的添加,你能夠對比第九部分的代碼差別。須要很是明確注意的如下幾點 
    一、這裏使用的方式,並不徹底等同C++的TRY CATCH.可是機理是同樣的。和GDB裏面的斷點中斷也是同樣的,只不事後者使用了SEGTRAP 這個中斷信號 
    二、signal在註冊函數時只須要一次,你別傻傻的如個人DEMO同樣,放在檢測的函數裏。 
    三、我這裏是爲了儘量只用頭文件,因此用了static jmp_buf SIGSEGV_buf;作成全局變量也沒有關係。 
    四、以下,對SIGSEGV_BEGIN 和 SIGSEGV_END的使用必定要加宏判斷。我暫時沒有想到比較好的解決方案,能把兩個宏之間的代碼描述能自動預編譯剔除掉。 
1 #ifdef __MMDB_FLAG__
2 SIGSEGV_BEGIN(test) //there will be one func ,static void sigsegv_done_test(int signum){
3     printf("you have a seciton error !\n");
4     
5 SIGSEGV_END()
6 #endif

以上代碼,等同於 
1 static void sigsegv_done_test(int signum){
2     if (signum != SIGSEGV){
3         return;
4     }
5     //you todo ....
6     printf("you have a section error !\n");
7     siglongjmp(SIGSEGV_buf,1);
8 }
    test8是一個不進行中斷函數註冊的例子,你會發現什麼事情都沒有發生,和之前同樣,由於沒註冊嘛。 
    test9是個標準的處理方案。 
    test10,說過了,讓你區分longjmp,setjmp 和siglongjmp sigsetjmp的區別。 
    你能夠經過執行 
    bin/test_malloc_free_main 10 

    的方式調用test10,其餘雷同,以下是代碼清單。函數

   malloc_free.h源碼分析

01 #ifndef _malloc_free_H_
02 #define _malloc_free_H_
03 #include <stdlib.h>
04 #define ALLOC_PAGE_SIZE 4096 //mininum malloc unit sizes ,not change
05 #define ALLOC_PAGE_MASK (ALLOC_PAGE_SIZE-1)
06 #define PAGE_SIZE_ALIGN(n) ((n) + ALLOC_PAGE_MASK) & (~(ALLOC_PAGE_MASK))
07 #define MALLOC_NUM_UNIT_SIZE (ALLOC_PAGE_SIZE / sizeof(void*))
08 #define MAX_MALLOC_UNIT_NUM 64 //not more than 4096*8
09 #define MAX_MALLOC_NUM  (MALLOC_NUM_UNIT_SIZE * MAX_MALLOC_UNIT_NUM) // max malloc times ,you can change
10 #define __MMDB_FLAG__
11 #ifndef __MMDB_FLAG__
12 //c_malloc c_free means malloc by check,not calloc,not type cmalloc!!!!!
13 #define c_malloc(a) malloc(a)
14 #define c_free(a) free(a)
15 #define MALLOC_FREE_INIT(...) do{}while(0)
16 #define _TYPE_INDEX_MALLOC_FREE(...)
17 #define _TYPE_COUNT_MALLOC_FREE(...)
18 #define CHECK_PTR_RANGE(...) (1)
19 #define GET_MALLOC_INDEX(...) do{}while (0)
20 #define CHECK_PTR_RANGE_ER(...) do{}while(0)
21 #if 0
22 #error "if not define __MMDB_FLAG__ ,this define how to done "
23 #define SIGSEGV_BEGIN(...) 
24 #define SIGSEGV_END(...) 
25  
26 #endif
27  
28 #define SIGNAL_SEGV(...) 
29 #define TRY_SEGV(...)
30 #define CATCH_SEGV(...)
31  
32 #else
33 #include <setjmp.h>
34 #include <signal.h>
35 static jmp_buf SIGSEGV_buf;//every C file have one
36 #define SIGSEGV_FUNC(name) sigsegv_done_##name
37 #define SIGSEGV_BEGIN(name) static void SIGSEGV_FUNC(name)(int signum){ if (signum != SIGSEGV) {return;}
38 #define SIGSEGV_END() siglongjmp(SIGSEGV_buf,1);}
39 #define SIGNAL_SEGV(name) do {signal(SIGSEGV,SIGSEGV_FUNC(name));}while (0)  //no return no need check
40 #define TRY_SEGV() if (sigsetjmp(SIGSEGV_buf,1) == 0){
41 #define CATCH_SEGV(...) }
42 #define MALLOC_FREE_INIT malloc_free_init
43 #define _TYPE_COUNT_MALLOC_FREE(name) unsigned long name  = 0;
44 #define _TYPE_INDEX_MALLOC_FREE(name) void ** name;
45 #define CHECK_PTR_RANGE(p,indexP) ((indexP[0] <= (void *)(p)) && (indexP[1] > (void*)(p)))
46 #define GET_MALLOC_INDEX(p,indexP) do{indexP = get_malloc_index(p);}while (0)
47 #define CHECK_PTR_RANGE_ER(p,indexP,n,NAME) do {if (CHECK_PTR_RANGE(p,indexP)){n++;}else{set_check_ptr_range_error_exit(p,indexP,n,NAME);}} while (0)
48  
49 //ins_inc_file
50  
51 //ins_typedef_def
52  
53 //ins_def
54  
55 //ins_func_declare
56 void memory_free_init(void*);
57 void **get_malloc_index(void *ptr); //not used in code ,please used GET_MALLOC_INDEX define
58 void set_check_ptr_range_error_exit(void *p,void **index,unsigned long n,const char *str);//not used in code ,please used CHECK_PTR_RANGE_ER
59 void *c_malloc(size_t size);
60 void c_free(void *ptr);
61  
62 #endif
63  
64  
65 #endif //_malloc_free_H_

malloc_free.c沒有任何變化學習

一下是test_malloc_free_main.c的清單this

001 #include "malloc_free.h"
002 #include <stdio.h>
003  
004 typedef void (* TEST_FUNC)(void);
005 #ifdef __MMDB_FLAG__
006 SIGSEGV_BEGIN(test) //there will be one func ,static void sigsegv_done_test(int signum){
007     printf("you have a seciton error !\n");
008      
009 SIGSEGV_END()
010 #endif
011 static void test8(void){
012     //test write to zero point
013     int *p = 0;
014 //  SIGNAL_SEGV(test);
015     TRY_SEGV();
016     p[0] = 0;
017     CATCH_SEGV();
018 }
019 static void test9(void){
020     //test write to zero point
021     int *p = 0;
022     SIGNAL_SEGV(test);
023     TRY_SEGV();
024     p[0] = 0;
025     CATCH_SEGV();
026 }
027  
028 #ifdef __MMDB_FLAG__
029 jmp_buf buf1;
030 #endif
031 void normal_longjmp(int signum){
032     printf("there is normal_longjmp func!\n");
033     longjmp(buf1,1);
034 }
035 static void test10(void){
036 #ifdef __MMDB_FLAG__
037  
038     int *p = 0;
039     signal(SIGSEGV,normal_longjmp);
040     printf("test10!\n");
041     if (setjmp(buf1) == 0){
042         p[0] = 0;
043     }else{// section error ,jmp from normal_longjmp func
044         printf("we come back from normal_longjmp func!\n");
045         p[0] = 0;
046     }
047 #endif 
048 }
049 static void test0(void){
050     void *p1 = 0;
051     char *p2 = 0;
052     printf("test0\n");
053     p1 =(char*)c_malloc(4);//*4);
054     p2 = (char*)c_malloc(6);
055     c_free(p1);
056     c_free(p2);
057     return; //normal check
058 }
059 static void test1(void){
060     char *p1,*p2;
061     p1 = (char*)c_malloc(5);
062     p2 = (char*)c_malloc(6);
063     c_free(p1);
064     //cfree(p2);
065     return; //free lack check
066 }
067 static void test2(void){
068     char *p1,*p2,*p3;
069     p1 = (char*)c_malloc(5);
070     p3 = p2 = (char*)c_malloc(6);
071     c_free(p1);
072     c_free(p2);
073     c_free(p3);
074     return; //free more check
075 }
076 static void test3(void){
077     char **pp = (char**)c_malloc(sizeof(char*)*MAX_MALLOC_NUM + 1);
078     int i;
079     for (i = 0 ; i <= MAX_MALLOC_NUM ; i++){
080         pp[i] = (char*)c_malloc(2);
081     }
082     return; //alloc more check
083 }
084 static void test4(void){
085     char *p1,*p2,*p3;
086     p1 = (char*)c_malloc(5);
087     p2 = (char*)c_malloc(6);
088     p3 = (char*)c_malloc(6);
089     c_free(p1);
090     c_free(p2);
091     c_free(p2);
092     return; //free twin check  
093 }
094 static void test5(void){
095     char *p1,*p2;
096     p1 = (char*)c_malloc(5);
097     p2 = (char*)c_malloc(6);
098     c_free(p1);
099     c_free(p2+3);
100     return; //free shift check 
101 }
102 static void test6(void){
103     char *p1,*p2;
104     p1 = (char*)c_malloc(5);
105     p2 = (char*)c_malloc(6);
106     c_free(p1);
107     c_free(0);
108     return; //free zero check  
109 }
110  
111 static void test7(void){
112     char *p1,*p2;
113     _TYPE_COUNT_MALLOC_FREE(ptr_count);
114     _TYPE_INDEX_MALLOC_FREE(pindex);   
115     int i;
116  
117     p1 = (char*)c_malloc(5);
118     GET_MALLOC_INDEX(p1,pindex);
119     p2 = p1+ 3;
相關文章
相關標籤/搜索