• linux SIGSEGV 信号捕捉,保证发生段错误后程序不崩溃


    Linux中编程的时候 有时候 try catch 可能满足不了我们的需求。因为碰到类似数组越界 ,非法内存访问之类的 ,这样的错误无法捕获。下面我们介绍一种使用捕获信号实现的异常 用来保证诸如段错误之类的错误发生时程序不会崩溃,而是跳过代码继续执行。首先我们来看看发生段错误之后系统的处理。

    发生段错误后系统会抛出 SIGSEGV 信号 ,之后 调用默认的信号处理函数 ,产生core文件 ,然后关闭程序 。

    那有没有一种办法可以保证程序不会死掉呢,当然是有的 。首先我们想到的是 截获改信号,调用自己的信号处理函数 。

    让我们来看看signal 这个函数 。

     #include <signal.h>
           typedef void (*sighandler_t)(int);
           sighandler_t signal(int signum, sighandler_t handler);
            第一个参数 的意思表示你要绑定的信号 (可以使用在控制台使用 kill -l 查看都有哪些信号 ,这些就不讲了,有兴趣的可以上网查)
            第二个参数 是表示信号处理的函数 指针 ,返回值为void* 参数为int ,如上 ,另外 系统也定义了一些宏 
                               (SIG_IGN,和 SIG_DFL) 第一个表示忽略这个信号 ,第二个表示 使用默认的信号处理函数 如果我们处理的       是SIGSEGV信号 ,那么它就会产生core文件 等等操作  
            返回值是一个信号处理函数的指针 ,如果发生错误 返回 SIG_ERR 这个宏 ,事实上 也是定义的一个函数 产生错误的原因 主要是因为给定的信号不正确 
    另外这个使用函数 有两点要注意 
       1. 进入到信号处理函数之后 这个信号会被 阻塞(block) 直到信号处理函数 返回 这点非常重要 ,后面会讲到。
       2. 信号函数处理完之后,会将该信号恢复为默认处理状态 ,即重新与产生core文件...函数绑定,所以在下一次用到的时候要重新调用signal这个函数绑定
           自定义的信号处理函数
    那么我们就可以开始尝试使用它了
     1     #include <signal.h>  
     2     #include <setjmp.h>  
     3     #include <stdarg.h>  
     4     #include <stdlib.h>  
     5     #include <stdio.h>  
     6     //信号处理函数  
     7     void recvSignal(int sig)  
     8     {  
     9         printf("received signal %d !!!
    ",sig);  
    10     }  
    11     int main(int argc,char** argv)  
    12     {  
    13       //给信号注册一个处理函数   
    14       signal(SIGSEGV, recvSignal);  
    15       int* s = 0;  
    16       (*s) = 1;  
    17      //以上两句用来产生 一个 传说中的段错误  
    18       while(1)  
    19       {  
    20         sleep(1);  
    21         printf("sleep 1 
    ");  
    22       }  
    23       return 0;  
    24     }  
    编译运行  一直打印收到信号 11 (SIGSEGV),为什么呢 ,
    上面代码给SIGSEGV 这个信号注册了一个处理函数 ,替代了系统默认的产生core文件的处理函数 ,当错误发生后 ,系统 发送 SIGSEGV ,然后 中断了程序 跳到 recvSignal 处理函数中去 ,处理完成后 ,再跳回来错误发生的地方 ,然后继续产生错误 ,继续发送 SIGSEGV  信号 ... 
    使用 setjmp 和longjmp 尝试跳过错误堆栈  
    #include <setjmp.h>
     int setjmp(jmp_buf env);   void longjmp(jmp_buf env, int val);
    系统跳转函数 ,可以直接在函数之间跳转 (比goto 强大多了) 

    int setjmp(jmp_buf env);  这个函数 将上下文 ,就是cpu和内存的信息保存到env中 (不用去理解 jmp_buf,就当我们平时用的buff好了),然后调用 void longjmp(jmp_buf env, int val); 的时候 跳转到使用env中的信息 ,恢复上下文 。如果是第一回调用setjmp 它会返回 0,如果是在 从longjmp 跳转过来的 ,那就返回 longjmp的参数 val,根据setjmp的返回值 我们就可以决定执行可能发生错误的代码还是直接跳过这段代码 。知道了原理之后 我们可能就会这样写

     1 #include <signal.h>
     2 #include <setjmp.h>
     3 #include <stdarg.h>
     4 #include <stdlib.h>
     5 #include <stdio.h>
     6 jmp_buf env;
     7 //信号处理函数
     8 void recvSignal(int sig)
     9 {
    10 printf("received signal %d !!!
    ",sig);
    11         longjmp(env,1);
    12 }
    13 int main(int argc,char** argv)
    14 {
    15 
    16     //保存一下上下文 
    17     int r = setjmp(env);
    18     if(  r  == 0)
    19     {
    20         //初次执行 ,那么可以执行 可能会发生错误的代码
    21         //给信号注册一个处理函数  
    22         signal(SIGSEGV, recvSignal);
    23         printf("excute this code!!");
    24            int* s = 0;
    25             (*s) = 1;
    26     }
    27     else
    28     {
    29         //是由longjmp 跳转回来的
    30             printf("jump this code !!"); 
    31     }
    32     while(1)
    33     {
    34         sleep(1);
    35         printf("sleep 1 
    ");
    36     }
    37     return 0;
    38 }

    编译 ,执行  产生 SIGSEGV 信号 ,然后在信号函数 里边跳转 到  int r = setjmp(env); 这一行 ,之后 直接略过了 可能发生错误的这段代码 ,跳转生效,可是这种方式还有一个bug,我们看看下面的代码

     1 #include <signal.h>
     2 #include <setjmp.h>
     3 #include <stdarg.h>
     4 #include <stdlib.h>
     5 #include <stdio.h>
     6 jmp_buf env;
     7 //信号处理函数
     8 void recvSignal(int sig)
     9 {
    10 printf("received signal %d !!!
    ",sig);
    11         longjmp(env,1);
    12 }
    13 int main(int argc,char** argv)
    14 {
    15 
    16     for(int i = 0; i < 2; i++)
    17     {
    18             //保存一下上下文 
    19         int r = setjmp(env);
    20         if(  r  == 0)
    21         {
    22             //初次执行 ,那么可以执行 可能会发生错误的代码
    23             //给信号注册一个处理函数  
    24             signal(SIGSEGV, recvSignal);
    25             printf("excute this code!!");
    26                int* s = 0;
    27                 (*s) = 1;
    28         }
    29         else
    30         {
    31                 //是由longjmp 跳转回来的
    32                 printf("jump this code !!"); 
    33         }
    34         sleep(5);
    35     }
    36 
    37     while(1)
    38     {
    39         sleep(1);
    40         printf("sleep 1 
    ");
    41     }
    42     return 0;
    43 }
    当for循环第二次执行的时候 ,程序依然产生了 SIGSEGV,系统仍然调用了默认的处理函数产生了core文件 ,分析下原因 上面我们说过“进入到信号处理函数之后 这个信号会被 阻塞(block) 直到信号处理函数返回”,在进入到信号处理函数之后 ,这个时候 系统阻塞了 SIGSEGV 这个信号 ,当跳回到 int r = setjmp(env); 这行代码的时候  SIGSEGV 信号依然是阻塞的 ,那以后 再给他绑定信号处理函数 自然没有作用 。
    好在系统给我们提供了int sigsetjmp(sigjmp_buf env, int savesigs);和  void siglongjmp(sigjmp_buf env, int val);这两个函数 ,这两个函数 和上面的 int setjmp(jmp_buf env);   void longjmp(jmp_buf env, int val); 大同小异 ,唯一的不同 是sigsetjmp 函数 多了 一个参数 ,savesigs,查看这函数的说明可以知道 ,当 savesigs 不为 0时,会保存当前的信号屏蔽表 (signal mask),然后在使用siglongjmp 跳转的时候 会恢复 线程的 屏蔽表。
    于是我们把上面的代码修改 后如下:
     1 #include <signal.h>
     2 #include <setjmp.h>
     3 #include <stdarg.h>
     4 #include <stdlib.h>
     5 #include <stdio.h>
     6 // jmp_buf env;
     7 //信号处理函数
     8 void recvSignal(int sig)
     9 {
    10 printf("received signal %d !!!
    ",sig);
    11         siglongjmp(env,1);
    12 }
    13 int main(int argc,char** argv)
    14 {
    15 
    16     for(int i = 0; i < 2; i++)
    17     {
    18             //保存一下上下文 
    19         int r = sigsetjmp(env,1);
    20         if(  r  == 0)
    21         {
    22             //初次执行 ,那么可以执行 可能会发生错误的代码
    23             //给信号注册一个处理函数  
    24             signal(SIGSEGV, recvSignal);
    25             printf("excute this code!!");
    26                int* s = 0;
    27                 (*s) = 1;
    28         }
    29         else
    30         {
    31                 //是由longjmp 跳转回来的
    32                 printf("jump this code !!"); 
    33         }
    34         sleep(5);
    35     }
    36 
    37     while(1)
    38     {
    39         sleep(1);
    40         printf("sleep 1 
    ");
    41     }
    42     return 0;
    43 }

    编译后 运行 。按照我们的需求 第二次进入for循环时, 发生段错误后程序不会死掉 ,而是会跳过这段代码了继续往下走 。下面我做了一个简单的封装 ,在错误发生时,我打印出了 错误信息 ,然后跳过错误的代码

     1 /*
     2 ** file name CException.h
     3 */
     4 #ifndef _CEXCEPTION_H_
     5 #define _CEXCEPTION_H_
     6 #include <setjmp.h>
     7 #include <stdlib.h>
     8 #include <stdarg.h>
     9 #include <execinfo.h>
    10 #include <stdio.h>
    11 #include <signal.h>
    12 #include <iostream>
    13 #include <string.h>
    14 typedef struct Except_frame
    15 {
    16     jmp_buf env;
    17     int flag;
    18     void clear()
    19     {
    20        flag = 0;
    21        bzero(env,sizeof(env));
    22     }
    23     bool isDef()
    24     {
    25        return flag;
    26     }
    27     Except_frame()
    28     {
    29       clear();
    30     }
    31 }Except_frame;
    32 extern Except_frame* except_stack;
    33 extern void errorDump();
    34 extern void recvSignal(int sig);
    35 Except_frame* except_stack = new Except_frame;
    36 void errorDump()
    37 {
    38     const int maxLevel = 200;
    39     void* buffer[maxLevel];
    40     int level = backtrace(buffer, maxLevel);
    41     const int SIZE_T = 1024;
    42     char cmd[SIZE_T] = "addr2line -C -f -e ";
    43     char* prog = cmd + strlen(cmd);
    44     readlink("/proc/self/exe", prog, sizeof(cmd) - (prog-cmd)-1);
    45     FILE* fp = popen(cmd, "w");
    46     if (!fp)
    47     {
    48         perror("popen");
    49         return;
    50     }
    51     for (int i = 0; i < level; ++i)
    52     {
    53         fprintf(fp, "%p
    ", buffer[i]);
    54     }
    55     fclose(fp);
    56 }
    57 
    58 void recvSignal(int sig)
    59 {
    60     printf("received signal %d !!!
    ",sig);
    61     errorDump();
    62     siglongjmp(except_stack->env,1);
    63 }
    64 #define TRY 
    65     except_stack->flag = sigsetjmp(except_stack->env,1);
    66     if(!except_stack->isDef()) 
    67     { 
    68       signal(SIGSEGV,recvSignal); 
    69       printf("start use TRY
    ");
    70 #define END_TRY 
    71     }
    72     else
    73     {
    74       except_stack->clear();
    75     }
    76     printf("stop use TRY
    ");
    77 #define RETURN_NULL 
    78     } 
    79     else 
    80     { 
    81       except_stack->clear();
    82     }
    83     return NULL;
    84 #define RETURN_PARAM  { 
    85       except_stack->clear();
    86     }
    87     return x;
    88 #define EXIT_ZERO 
    89     }
    90     else 
    91     { 
    92       except_stack->clear();
    93     }
    94     exit(0);
    95 #endif

    另外建一个文件 ,

     1     #include "CException.h"  
     2     int main(int argc,char** argv)  
     3     {  
     4         //可以如下使用   
     5         TRY  
     6             int*s = 0;  
     7             (int*s) = 1;  
     8         END_TRY  
     9         //使用这两个宏包含可能发生的错误代码 ,当然可以根据需求 使用   
    10         //RETURN_NULL   
    11         //RETURN_PARAM(0)  
    12         //EXIT_ZERO  这三个宏  
    13         return 0;  
    14     }  

    这个时候我们就能使用TRY 和 END_TRY,RETURM_NULL,RETURN_PARAM(param) 来实现程序发生段错误后跳过错误代码继续运行了 ,不过此代码仅限于单线程使用

    from:http://blog.csdn.net/work_msh/article/details/8470277

  • 相关阅读:
    fullPage最后一屏自适应
    刷新页面回到顶部
    Angular1和Aangular4剖析
    ts转化为js
    Element-ui的表单中怎么添加正则校验
    npm下载指定版本的插件
    c语言相关题目10
    合并两个有序链表,将两个升序链表合并为一个新的升序链表并返回。 新链表是通过拼接给定的两个链表的所有节点组成的。 例如:输入 1->2->4,1->3->4->5,输出:1->1->2->3->4->4->5
    编写一程序删除一个字符串中无效的空格,无效的空格是指句首和句尾的所有空格和中间重复的空格。
    某饮料厂举办一次促销活动,某种饮料凭3个瓶盖可以再换一瓶同种饮料一瓶, 并且可以一直循环下去(但不允许暂借或赊账)。如果不浪费瓶盖,对于初始买入 的n瓶饮料,最后一共最多可以喝到多少瓶这种饮料。
  • 原文地址:https://www.cnblogs.com/jiu0821/p/7207082.html
Copyright © 2020-2023  润新知