C调试时段异常后不复位方法

C调试时段异常后不复位方法

在C语言调试时,经常会遇到非法访问导致段异常复位,为方便调试,可以通过sigsetjmp结合信号处理挂接绕过复位机制。

段异常复位

我们都知道访问非法地址会导致程序跑死,那程序为什么会挂呢?
这是因为访问非法地址后,会触发异常信号(SIGSEGV 信号11),默认的信号处理函数会杀死本进程,进而表现出来就是程序跑挂了。下面是用简单的程序示例:

#include <stdio.h>

#define DEBUG do {printf("%s<%d> enter!\n", __FUNCTION__, __LINE__); fflush(stdout);} while (0)

static void ErrFunc(void) {
    int *addr = (int *) 0x1234;
    int temp;

    DEBUG;
    temp = *addr;
    DEBUG;

    return ;
}

int main() {
    ErrFunc();
    return 0;
}

程序输出:

ErrFunc<9> enter!
Segmentation fault (core dumped)

siglongjmp && sigsetjmp

DESCRIPTION
       The  functions  described  on this page are used for performing "nonlocal gotos": transferring execution from one function to a predetermined
       location in another function.  The setjmp() function dynamically establishes the target to which  control  will  later  be  transferred,  and
       longjmp() performs the transfer of execution.

       The  setjmp() function saves various information about the calling environment (typically, the stack pointer, the instruction pointer, possi‐
       bly the values of other registers and the signal mask) in the buffer env for later use by longjmp().  In this case, setjmp() returns 0.

       The longjmp() function uses the information saved in env to transfer control back to the point where  setjmp()  was  called  and  to  restore
       ("rewind") the stack to its state at the time of the setjmp() call.  In addition, and depending on the implementation (see NOTES), the values
       of some other registers and the process signal mask may be restored to their state at the time of the setjmp() call.

       Following a successful longjmp(), execution continues as if setjmp() had returned for a second time.  This "fake" return can be distinguished
       from  a  true  setjmp() call because the "fake" return returns the value provided in val.  If the programmer mistakenly passes the value 0 in
       val, the "fake" return will instead return 1.

   sigsetjmp() and siglongjmp()
       sigsetjmp() and siglongjmp() also perform nonlocal gotos, but provide predictable handling of the process signal mask.

       If, and only if, the savesigs argument provided to sigsetjmp() is nonzero, the process's current signal mask is saved  in  env  and  will  be
       restored if a siglongjmp() is later performed with this env.

简而言之,sigsetjmp的作用就类似于goto,但作用不局限于一个函数内。实际作用时就是如果函数直接往下走,遇到sigsetjmp接口了,那么该接口返回0,此时将环境信息保存在一个变量里,然后程序继续执行;如果函数是走到setlongjmp,则此时程序会跳转到之前保存的环境信息处,而且sigsetjmp接口返回非0值。

信号处理函数

Linux下提供sigaction函数用于修改信号处理函数,可以自定义信号处理函数,通过sigaction挂接,这样产生信号时就不是进入系统默认的处理函数,而是进入自定义函数。结合sigsetjmp和siglongjmp即可实现我们的目标----段异常后不复位。
用简单的代码示例:用数字标明进入顺序

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>
#include <ucontext.h>


#define DEBUG do {printf("%s<%d> enter!\n", __FUNCTION__, __LINE__); fflush(stdout);} while (0)

static sigjmp_buf s_sigEnv; /* 保存环境信息 */

static void SignalHandle_Sigsegv(int signal, siginfo_t *info, void *ucontext) { /* 自定义的信号处理函数,只做简单打印和跳转 */
    DEBUG; /* 2 */

    printf("sig:%d, error addr:%p\n", signal, info->si_addr);
    siglongjmp(s_sigEnv, 0);

    return ;
}

static void SignalHandleCallBackInit(void) {
    struct sigaction actInfo;

    actInfo.sa_sigaction = SignalHandle_Sigsegv; /* 挂接自定义的信号处理函数 */
    actInfo.sa_flags = SA_SIGINFO; /* 传递参数到此函数中 */
    sigaction(SIGSEGV, &actInfo, NULL);

    return ;
}

static void ErrFunc(void) {
    int *addr = (int *) 0x1234;
    int temp;

    if (sigsetjmp(s_sigEnv, 0) == 0) {
        DEBUG; /* 1 */
        temp = *addr; /* 发生异常 */
        DEBUG;
    } else {
        DEBUG; /* 3 */
    }

    return ;
}

int main() {
    SignalHandleCallBackInit();
    ErrFunc();
    return 0;
}

运行结果如下:可见确实不会因为段异常而复位,而且函数执行顺序如我们期待。

ErrFunc<36> enter!
SignalHandle_Sigsegv<13> enter!
sig:11, error addr:0x1234
ErrFunc<40> enter!

至此,即可实现段异常后不复位,当然,还是应该完善后处理函数来寻找及修复段异常原因,此文不表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值