4

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

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

5 回答 5

4

通过这个例子,我以下面的方式修改了你的代码,现在它可以按照你的意愿工作。

#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。要运行时删除此信号的此处理程序

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

}
于 2016-01-25T06:14:11.183 回答
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,一个无效位置,触发另一个异常。

于 2016-01-25T06:11:52.167 回答
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;
}
于 2016-01-25T06:11:28.003 回答
0

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

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

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

于 2016-01-25T06:13:42.660 回答
0

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

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

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

于 2022-02-11T16:29:21.770 回答