在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 = ;
(*s) = ;
//以上两句用来产生 一个 传说中的段错误
while()
{
sleep();
printf("sleep 1 \n");
}
return ;
}
编译运行  一直打印收到信号 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,);
}
int main(int argc,char** argv)
{ //保存一下上下文
int r = setjmp(env);
if( r == )
{
//初次执行 ,那么可以执行 可能会发生错误的代码
//给信号注册一个处理函数
signal(SIGSEGV, recvSignal);
printf("excute this code!!");
int* s = ;
(*s) = ;
}
else
{
//是由longjmp 跳转回来的
printf("jump this code !!");
}
while()
{
sleep();
printf("sleep 1 \n");
}
return ;
}

编译 ,执行  产生 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,);
}
int main(int argc,char** argv)
{ for(int i = ; i < ; i++)
{
//保存一下上下文
int r = setjmp(env);
if( r == )
{
//初次执行 ,那么可以执行 可能会发生错误的代码
//给信号注册一个处理函数
signal(SIGSEGV, recvSignal);
printf("excute this code!!");
int* s = ;
(*s) = ;
}
else
{
//是由longjmp 跳转回来的
printf("jump this code !!");
}
sleep();
} while()
{
sleep();
printf("sleep 1 \n");
}
return ;
}
当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,);
}
int main(int argc,char** argv)
{ for(int i = ; i < ; i++)
{
//保存一下上下文
int r = sigsetjmp(env,);
if( r == )
{
//初次执行 ,那么可以执行 可能会发生错误的代码
//给信号注册一个处理函数
signal(SIGSEGV, recvSignal);
printf("excute this code!!");
int* s = ;
(*s) = ;
}
else
{
//是由longjmp 跳转回来的
printf("jump this code !!");
}
sleep();
} while()
{
sleep();
printf("sleep 1 \n");
}
return ;
}

编译后 运行 。按照我们的需求 第二次进入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 = ;
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 = ;
void* buffer[maxLevel];
int level = backtrace(buffer, maxLevel);
const int SIZE_T = ;
char cmd[SIZE_T] = "addr2line -C -f -e ";
char* prog = cmd + strlen(cmd);
readlink("/proc/self/exe", prog, sizeof(cmd) - (prog-cmd)-);
FILE* fp = popen(cmd, "w");
if (!fp)
{
perror("popen");
return;
}
for (int i = ; 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,);
}
#define TRY \
except_stack->flag = sigsetjmp(except_stack->env,);\
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();
#endif

另外建一个文件 ,

     #include "CException.h"
int main(int argc,char** argv)
{
//可以如下使用
TRY
int*s = ;
(int*s) = ;
END_TRY
//使用这两个宏包含可能发生的错误代码 ,当然可以根据需求 使用
//RETURN_NULL
//RETURN_PARAM(0)
//EXIT_ZERO 这三个宏
return ;
}

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

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

linux SIGSEGV 信号捕捉,保证发生段错误后程序不崩溃的更多相关文章

  1. VSCODE调试时在cygwin.S中发生段错误

    起因: C++实现矩阵类和向量类 当看了我实现的矩阵类后,一个同学问我: 然后我就试了试1000维,结果运行时在cygwin.S里引发了奇奇怪怪的Segmentation fault,而且这个文件还是 ...

  2. Linux之nohup命令:实现退出终端后程序继续后台运行

    转自:http://tech.ccidnet.com/art/302/20070618/1115599_1.html 简单而有用的nohup命令在UNIX/LINUX中,普通进程用&符号放到后 ...

  3. Linux环境下段错误的产生原因及调试方法小结(转)

    最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且 项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation F ...

  4. Linux环境下段错误的产生原因及调试方法小结

    转载自http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之 ...

  5. Linux下的段错误(Segmentation fault)

    Linux开发中常见段错误问题原因分析 1 使用非法的内存地址(指针),包括使用未经初始化及已经释放的指针.不存在的地址.受系统保护的地址,只读的地址等,这一类也是最常见和最好解决的段错误问题,使用G ...

  6. 【转】【调试技巧】Linux环境下段错误的产生原因及调试方法小结

    本文转自:http://www.cnblogs.com/panfeng412/archive/2011/11/06/segmentation-fault-in-linux.html 1. 段错误是什么 ...

  7. Linux环境下段错误的产生原因及调试方法小结【转】

    转自:http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之 ...

  8. Linux段错误及GDB Coredump调试方法

    最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation Fa ...

  9. 转:Linux环境下段错误的产生原因及调试方法小结

    源地址:http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 补充:http://baike.baidu.com/link ...

随机推荐

  1. 9.proc目录下的文件和目录详解

    1./proc目录下的文件和目录详解 /proc:虚拟目录.是内存的映射,内核和进程的虚拟文件系统目录,每个进程会生成1个pid,而每个进程都有1个目录. /proc/Version:内核版本 /pr ...

  2. Android使用Http协议访问网络——HttpConnection

    套路篇 使用HttpConnection访问网络一般有如下的套路: 1.获取到HttpConnection的实例,new出一个URL对象,并传入目标的网址,然后调用一下openConnection() ...

  3. 机器学习(八)—Apriori算法

    摘要:本文对Apriori算法进行了简单介绍,并通过Python进行实现,进而结合UCI数据库中的肋形蘑菇数据集对算法进行验证. “啤酒与尿布”的例子相信很多人都听说过吧,故事是这样的:在一家超市中, ...

  4. 【解题报告】2014ACM/ICPC上海赛区现场赛B

    唉  谷歌出的神题,差点爆零了...三小时终于A掉 B题 题目大概是说从左上角的点出发,经过某路线最后回到原点,求每个格子被路线包含的圈数的平方和. 首先可以知道,对于某个格子来说,从该格子的任意一个 ...

  5. java多线程:synchronized和lock比较浅析

    转载:http://www.toutiao.com/a6392135944652587266/?tt_from=weixin&utm_campaign=client_share&app ...

  6. maven依赖顺序原则

    使用maven的程序员都会遇到一个问题,那就是maven依赖冲突的问题,这会导致ClassNotFound或者MethodNotFound这样的异常.其实只要明白maven依赖的根本性的原则就不怕这样 ...

  7. bzoj 3456 城市规划——分治FFT / 多项式求逆 / 多项式求ln

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3456 分治FFT: 设 dp[ i ] 表示 i 个点时连通的方案数. 考虑算补集:连通的方 ...

  8. 安装nagios-plugins插件make时遇到的error

    安装nagios-plugins插件make时遇到的error error内容: check_http.c: In function ‘process_arguments’: check_http.c ...

  9. python urllib和urllib3包

    urllib.request urllib当中使用最多的模块,涉及请求,响应,浏览器模拟,代理,cookie等功能. 1. 快速请求 urlopen返回对象提供一些基本方法: read 返回文本数据 ...

  10. php设计模式之单例(多例),注册器,观察者模式

    单例(Singleton)模式和不常见的多例(Multiton)模式控制着应用程序中类的数量.如模式名称,单例只能实例化一次,只有一个对象,多例模式可以多次实例化. 基于Singleton的特性,我们 ...