linux SIGSEGV 信号捕捉,保证发生段错误后程序不崩溃
来源:互联网 发布:弱智吧 知乎 编辑:程序博客网 时间:2024/05/21 11:16
在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这个函数绑定
自定义的信号处理函数
那么我们就可以开始尝试使用它了
#include <signal.h>#include <setjmp.h>#include <stdarg.h>#include <stdlib.h>#include <stdio.h>//信号处理函数void recvSignal(int sig){printf("received signal %d !!!\n",sig);}int main(int argc,char** argv){ //给信号注册一个处理函数 signal(SIGSEGV, recvSignal); int* s = 0; (*s) = 1; //以上两句用来产生 一个 传说中的段错误 while(1) { sleep(1); printf("sleep 1 \n"); } return 0;}
编译运行 一直打印收到信号 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的返回值 我们就可以决定执行可能发生错误的代码还是直接跳过这段代码 。知道了原理之后 我们可能就会这样写
#include <signal.h>#include <setjmp.h>#include <stdarg.h>#include <stdlib.h>#include <stdio.h>jmp_buf env;//信号处理函数void recvSignal(int sig){printf("received signal %d !!!\n",sig); longjmp(env,1);}int main(int argc,char** argv){ //保存一下上下文 int r = setjmp(env); if( r == 0) { //初次执行 ,那么可以执行 可能会发生错误的代码 //给信号注册一个处理函数 signal(SIGSEGV, recvSignal); printf("excute this code!!"); int* s = 0; (*s) = 1; } else { //是由longjmp 跳转回来的 printf("jump this code !!"); } while(1) { sleep(1); printf("sleep 1 \n"); } return 0;}
编译 ,执行 产生 SIGSEGV 信号 ,然后在信号函数 里边跳转 到 int r = setjmp(env); 这一行 ,之后 直接略过了 可能发生错误的这段代码 ,跳转生效,可是这种方式还有一个bug,我们看看下面的代码
#include <signal.h>#include <setjmp.h>#include <stdarg.h>#include <stdlib.h>#include <stdio.h>jmp_buf env;//信号处理函数void recvSignal(int sig){printf("received signal %d !!!\n",sig); longjmp(env,1);}int main(int argc,char** argv){ for(int i = 0; i < 2; i++) { //保存一下上下文 int r = setjmp(env); if( r == 0) { //初次执行 ,那么可以执行 可能会发生错误的代码 //给信号注册一个处理函数 signal(SIGSEGV, recvSignal); printf("excute this code!!"); int* s = 0; (*s) = 1; } else { //是由longjmp 跳转回来的 printf("jump this code !!"); } sleep(5); } while(1) { sleep(1); printf("sleep 1 \n"); } return 0;}
当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 跳转的时候 会恢复 线程的 屏蔽表。
于是我们把上面的代码修改 后如下:
#include <signal.h>#include <setjmp.h>#include <stdarg.h>#include <stdlib.h>#include <stdio.h>// jmp_buf env;//信号处理函数void recvSignal(int sig){printf("received signal %d !!!\n",sig); siglongjmp(env,1);}int main(int argc,char** argv){ for(int i = 0; i < 2; i++) { //保存一下上下文 int r = sigsetjmp(env,1); if( r == 0) { //初次执行 ,那么可以执行 可能会发生错误的代码 //给信号注册一个处理函数 signal(SIGSEGV, recvSignal); printf("excute this code!!"); int* s = 0; (*s) = 1; } else { //是由longjmp 跳转回来的 printf("jump this code !!"); } sleep(5); } while(1) { sleep(1); printf("sleep 1 \n"); } return 0;}
编译后 运行 。按照我们的需求 第二次进入for循环时, 发生段错误后程序不会死掉 ,而是会跳过这段代码了继续往下走 。下面我做了一个简单的封装 ,在错误发生时,我打印出了 错误信息 ,然后跳过错误的代码
/*** file name CException.h*/#ifndef _CEXCEPTION_H_#define _CEXCEPTION_H_#include <setjmp.h>#include <stdlib.h>#include <stdarg.h>#include <execinfo.h>#include <stdio.h>#include <signal.h>#include <iostream>#include <string.h>typedef struct Except_frame{ jmp_buf env; int flag; void clear() { flag = 0; bzero(env,sizeof(env)); } bool isDef() { return flag; } Except_frame() { clear(); }}Except_frame;extern Except_frame* except_stack;extern void errorDump();extern void recvSignal(int sig);Except_frame* except_stack = new Except_frame;void errorDump(){ const int maxLevel = 200; void* buffer[maxLevel]; int level = backtrace(buffer, maxLevel); const int SIZE_T = 1024; char cmd[SIZE_T] = "addr2line -C -f -e "; char* prog = cmd + strlen(cmd); readlink("/proc/self/exe", prog, sizeof(cmd) - (prog-cmd)-1); FILE* fp = popen(cmd, "w"); if (!fp) { perror("popen"); return; } for (int i = 0; i < level; ++i) { fprintf(fp, "%p\n", buffer[i]); } fclose(fp);}void recvSignal(int sig){ printf("received signal %d !!!\n",sig); errorDump(); siglongjmp(except_stack->env,1);}#define TRY \ except_stack->flag = sigsetjmp(except_stack->env,1);\ if(!except_stack->isDef()) \ { \ signal(SIGSEGV,recvSignal); \ printf("start use TRY\n");#define END_TRY \ }\ else\ {\ except_stack->clear();\ }\ printf("stop use TRY\n");#define RETURN_NULL \ } \ else \ { \ except_stack->clear();\ }\ return NULL;#define RETURN_PARAM { \ except_stack->clear();\ }\ return x;#define EXIT_ZERO \ }\ else \ { \ except_stack->clear();\ }\ exit(0);#endif
#include "CException.h"int main(int argc,char** argv){ //可以如下使用 TRY int*s = 0; (int*s) = 1; END_TRY //使用这两个宏包含可能发生的错误代码 ,当然可以根据需求 使用 //RETURN_NULL //RETURN_PARAM(0) //EXIT_ZERO 这三个宏 return 0;}
这个时候我们就能使用TRY 和 END_TRY,RETURM_NULL,RETURN_PARAM(param) 来实现程序发生段错误后跳过错误代码继续运行了 ,不过此代码仅限于单线程使用
- linux SIGSEGV 信号捕捉,保证发生段错误后程序不崩溃
- 捕捉段错误信号信号处理程序
- //捕捉段错误信号的信号处理程序
- 捕捉信号SIGSEGV并回溯栈帧
- 捕捉信号SIGSEGV并回溯栈帧
- linux下调用mysql_query产生SIGSEGV段错误
- SIGSEGV 信号捕捉,setjmp/longjmp记录上下文跳转
- 捕捉信号SIGSEGV并回溯栈帧backtrace
- Linux 信号捕捉trap
- linux trap 捕捉信号
- Linux捕捉信号相关
- linux之信号捕捉
- Linux信号捕捉
- 在qt程序中捕捉linux信号,sigint sighup sigterm
- SIGSEGV信号
- 【linux信号】信号处理函数执行后返回到信号发生处
- Linux 程序崩溃后的源码定位
- Linux下发生段错误时如何产生core文件
- 径向基函数(RBF)神经网络
- 无约束最优化方法
- C#技术漫谈之垃圾回收机制(GC)
- ubuntu11.10上安装网卡驱动
- Linux上安装Perl模块的两种方法
- linux SIGSEGV 信号捕捉,保证发生段错误后程序不崩溃
- 同步和异步、阻塞和非阻塞
- EM算法
- MapReduce算法设计--Think in Hadoop
- MapReduce在搜索引擎中一些应用
- 高斯混合模型
- 采用分离链接法的HashTable的实现
- 相关性检验
- 浅议tomcat与classloader
原创粉丝点击
热门IT博客
热门问题
老师的惩罚
人脸识别
我在镇武司摸鱼那些年
重生之率土为王
我在大康的咸鱼生活
盘龙之生命进化
天生仙种
凡人之先天五行
春回大明朝
姑娘不必设防,我是瞎子
台达伺服驱动器报警代码大全
伺服控制系统
步进电机和伺服电机
伺服电机接线图
三菱伺服报警代码
伺服驱动器报警代码
松下伺服驱动器报警代码大全
安川伺服驱动器说明书
松下伺服器参数说明一览表
伺服驱动器接线图
yaskawa伺服驱动器报警代码
汇川伺服报警故障代码大全
水量伺服器有什么用处
松下伺服驱动器说明书
松下伺服电机
伺服驱动故障代码大全图
三菱伺服驱动器
防爆伺服电机
moog伺服阀价格
台达伺服电机价格
二手伺服电机回收
伺服a智能增压器
伺服驱动器维修
西门子伺服电机选型手册
伺服式液位计
三菱伺服驱动器价格
伺服马达维修
直流无刷伺服电机
伺服是什么意思
伺服电机驱动器
伺机的意思
伺机
伺机而动
似
似瘾
前程似锦意思
爱似尘埃心向水
祝你前程似锦
祝前程似锦
暖婚似火顾少轻轻
侯门嫡女珠似宝