sigaction 并在 linux 环境中忽略带有 c 的信号

sigaction and ignoring a signal with c in linux environment

我是这类编程的新手,如果我的问题很琐碎,我深表歉意。 我想做的是在我的程序中引起分段错误,而不是退出程序,我想处理信号并在分段错误后继续执行。我写了一个似乎有效的代码,我只是想确保这是执行此操作的方法。所以这是我的代码。

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 运行两次吗? 这段代码也可以吗?

谢谢。

Can someone explain to me why the printf inside myhandle runs twice ?

行为出现OS-dependent。 myhandle 的控件可能根本就不是 return 到 main

捕获信号 11 是不正常的,通常由 OS 处理以终止程序。

然而,可以为它编写一个信号处理程序并让该函数在 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;
}

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

    int *a=NULL;
  400697:       48 c7 45 f8 00 00 00    movq   [=10=]x0,-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    [=10=]x40077c,%edi
  4006ad:       e8 de fd ff ff          callq  400490 <puts@plt>

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

消息转储两次是因为第一次调用时,

context->uc_mcontext.gregs[REG_RIP]++

,设置RIP为0x4006a4,一处无效位置,再触发一次异常

处理这些信号对于继续执行来说一点都不简单。原因是导致信号的指令尚未执行,因此将通过尝试执行失败的指令继续执行。

信号处理程序执行两次(甚至无限期重复)的原因是返回会导致 CPU 重试执行之前导致分段错误的相同操作,如果不做任何更改,它将导致再次出现分段错误。

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

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:~/Whosebug$ gcc test1.c
jeegar@jeegar:~/Whosebug$ ./a.out 
Before segfault
Signal is 11
I am still alive

关于在评论中形成 OP 的进一步问题。 要在运行时删除此信号的此处理程序

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 ;
}

}

不确定是否有人说过,但是从信号处理程序调用 printf() 不安全,因为它使用了 malloc()。

请阅读更多内容以了解哪些函数对于信号处理上下文是安全的。在 Linux 和 BSD/MacOS 上,另一种方法是在一个特殊的文件描述符上安排信号传递——在 Linux 上,这是通过 signalfd 函数族完成的,然后你可以在正常的事件循环中处理信号。

Michael Kerrisk 关于 Linux 的书在这里是一个很好的参考。