为SIGSEGV设置handler有用吗?

背景

最近几天看到先辈们30年前留下了一块代码,为SIGSEGV设置了handler,所以心中有了两个疑问:

  1. 为SIGSEGV设置handler有没有用?
  2. 能否跳过引起崩溃的那一句指令?

答案是没有太多用处,信号处理函数返回后会再次执行出错的指令,从而再次引发调用信号处理函数,陷入无限循环。不过信号处理函数有能力改变RIP的值从而跳过出错的指令,但用处不大,因为跳过了出错指令会影响后面代码的执行结果。
以下试验只是为了玩玩,没有太多用处。

试验代码

#include<stdio.h>
#include <unistd.h>
#include <stdint.h>

#define __USE_GNU
#include <signal.h>

int handle_count=0;
void signal_seg_handler(int sig, siginfo_t* siginfo, void* context){
        char eye_chacher[16]="seg_handler";
        mcontext_t* mcontext = &((ucontext_t*)context)->uc_mcontext;
        write(STDERR_FILENO, "SIGSEGV caught\n", 15);
        if(++handle_count<5){
                uint8_t* code = (uint8_t*)mcontext->gregs[REG_RIP];
                printf("SIGSEGV instruction address:%p code:%02x%02x\n", code, code[0], code[1]);//此处不应该使用非重入函数printf,仅仅为了方便。
        }else{
                write(STDERR_FILENO, "SIGSEGV caught more than 5 times\n", 32);
                exit(-1);
        }
}

static void do_segv()
{
        char eye_chacher[16]="do_segv";
        int *segv;
        segv = 0; /* malloc(a_huge_amount); */
        *segv = 1;
        printf("Should not go here\n");
}

void main(){
        struct sigaction act;
        memset(&act, 0, sizeof(act));
        act.sa_sigaction = signal_seg_handler;
        act.sa_flags = SA_SIGINFO;
        sigaction(SIGSEGV, &act, NULL);
        do_segv();
        printf("exit\n");
}

给地址0处赋值,必然引起SEGV,从而触发信号处理函数signal_seg_handler。
运行结果如下:

SIGSEGV caught
SIGSEGV instruction address:0x400779 code:c700
SIGSEGV caught
SIGSEGV instruction address:0x400779 code:c700
SIGSEGV caught
SIGSEGV instruction address:0x400779 code:c700
SIGSEGV caught
SIGSEGV instruction address:0x400779 code:c700
SIGSEGV caught
SIGSEGV caught more than 5 times

正如预期, 不停地执行0x400779处的指令,使用GDB查看此处指令:

(gdb) x /3i 0x400779
   0x400779 <do_segv+42>:       movl   $0x1,(%rax)
   0x40077f <do_segv+48>:       mov    $0x4008e2,%edi
   0x400784 <do_segv+53>:       callq  0x400520 <puts@plt>
(gdb) x /6xb 0x400779
0x400779 <do_segv+42>:  0xc7    0x00    0x01    0x00    0x00    0x00
(gdb) f 3
#3  0x0000000000400779 in do_segv () at sigsegv.c:33
33              *segv = 1;
(gdb) disass /m
Dump of assembler code for function do_segv:
...
33              *segv = 1;
   0x0000000000400775 <+38>:    mov    -0x8(%rbp),%rax
=> 0x0000000000400779 <+42>:    movl   $0x1,(%rax)

所以说代码*segv = 1;一直没过去。

跳过出错指令

通过上面的分析,我们知道了出错指令的长度为6;信号处理函数signal_seg_handler中也能拿到一系列要恢复的寄存器的值,尤其是RIP。只要修改RIP=RIP+6即可。
代码修改如下:

void signal_seg_handler(int sig, siginfo_t* siginfo, void* context){
        char eye_chacher[16]="seg_handler";
        mcontext_t* mcontext = &((ucontext_t*)context)->uc_mcontext;
        write(STDERR_FILENO, "SIGSEGV caught\n", 15);
        mcontext->gregs[REG_RIP] += 6; // skip it!
        return;

编译运行:

[mzhai@dendevmvascen05 cprograms]$ ./a.out
SIGSEGV caught
Should not go here
exit

打印了不应该打印的Should not go here,搞定!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深山老宅

鸡蛋不错的话,要不要激励下母鸡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值