在linux环境中使用c进行sigaction和忽略信号

时间:2016-01-25 05:10:25

标签: c linux sigaction

我是这类编程的新手,很抱歉,如果我的问题很简单。 我想要做的是在我的程序中导致分段错误而不是退出程序,我想处理信号并在分段错误后继续执行。我编写了一个似乎正常工作的代码,我只是想确保这是实现此目的的方法。所以这是我的代码。

void myhandle(int mysignal, siginfo_t *si, void* arg)
{
  printf("Signal is %d\n",mysignal);

  ucontext_t *context = (ucontext_t *)arg;
  context->uc_mcontext.gregs[REG_RIP]++;
}


int main(int argc, char *argv[])
{
   struct sigaction action;

  action.sa_handler=myhandle;
  sigaction(11,&action,NULL);

  printf("Before segfault\n");

  int *a=NULL;
  int b=*a;

  printf("I am still alive\n");

  return 0;
}

有人可以向我解释为什么myhandle里面的printf运行两次? 这段代码也可以吗?

谢谢。

4 个答案:

答案 0 :(得分:2)

您可能需要检查dissamembly以找到要跳转的PC(即RIP)。对于你的情况,它应该看起来像,

    int *a=NULL;
  400697:       48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
  40069e:       00
    int b=*a;
  40069f:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  4006a3:       8b 00                   mov    (%rax),%eax
  4006a5:       89 45 f4                mov    %eax,-0xc(%rbp)

    printf("I am still alive\n");
  4006a8:       bf 7c 07 40 00          mov    $0x40077c,%edi
  4006ad:       e8 de fd ff ff          callq  400490 <puts@plt>

,异常在0x4006a3上,应该设置为0x4006a8以跳转到printf()。或者+2到0x4006a5,这也是有效的。

邮件转储两次的原因是,在第一次调用时,

context->uc_mcontext.gregs[REG_RIP]++

,它将RIP设置为0x4006a4,一个无效位置,再触发一个例外。

答案 1 :(得分:2)

通过this example我已经以下面的方式修改了您的代码,现在它可以按照您的意愿运行。

#include<stdio.h>
#define __USE_GNU
#include<signal.h>
#include<ucontext.h>

void myhandle(int mysignal, siginfo_t *si, void* arg)
{
  printf("Signal is %d\n",mysignal);

  ucontext_t *context = (ucontext_t *)arg;
  context->uc_mcontext.gregs[REG_RIP] = context->uc_mcontext.gregs[REG_RIP] + 0x04 ;
}


int main(int argc, char *argv[])
{

  struct sigaction action;
  action.sa_sigaction = &myhandle;
  action.sa_flags = SA_SIGINFO;

  sigaction(11,&action,NULL);


  printf("Before segfault\n");

  int *a=NULL;
  int b;
  b =*a;

  printf("I am still alive\n");

  return 0;
}

<强>输出:

jeegar@jeegar:~/stackoverflow$ gcc test1.c
jeegar@jeegar:~/stackoverflow$ ./a.out 
Before segfault
Signal is 11
I am still alive

在进一步的问题表格OP中。 运行时为此signel删除此处理程序

void myhandle(int mysignal, siginfo_t *si, void* arg)
{
  printf("Signal is %d\n",mysignal);

if(flag == 0) {
  // Disable the handler
  action.sa_sigaction = SIG_DFL;
  sigaction(11,&action,NULL);
}

if(flag) {
  ucontext_t *context = (ucontext_t *)arg;
  context->uc_mcontext.gregs[REG_RIP] = context-      >uc_mcontext.gregs[REG_RIP] + 0x04 ;
}

}

答案 2 :(得分:1)

  

有人可以向我解释为什么myhandle里面的printf会运行两次吗?

该行为似乎取决于操作系统。来自myhandle的控件可能根本不会返回main

捕获信号11是很常见的,通常由操作系统处理以终止程序。

但是,可以为它编写一个信号处理程序,让该函数在exit之前打印出来。

struct sigaction action;
struct sigaction old_action;

void myhandle( int mysignal )
{
    if( 11 == mysignal )
    {
        printf( "Signal is %d\n", mysignal );   // <-- this should print OK.
        sigaction( 11, &old_action, NULL );     // restore OS signal handler, or just exit().
        return;
    }
}


int main(int argc, char *argv[])
{
    action.sa_handler = myhandle;
    sigaction( 11, &action, &old_action );

    printf("Before segfault\n");

    int *a=NULL;
    int b=*a;

    printf( "I am still alive\n" );   // <-- this won't happen
    return 0;
}

答案 3 :(得分:0)

这些信号对于继续执行来说并不是一件容易的事。原因是导致信号的指令尚未执行,因此尝试执行失败的指令将继续执行。

信号处理程序执行两次(或者甚至无限重复)的原因是返回会导致CPU重试执行导致分段错误的相同事情,并且如果没有任何改变,它将再次导致分段错误。 / p>

为了处理这样的信号(SIGSEGVSIGFPESIGILL等),您必须实际改变信号上下文以解决问题。为此,您需要使用专为正在使用的CPU编写的代码,并使用编译器特定的行为,因为您需要修改上下文。