sigaction и игнорирование сигнала с c в среде linux

Я новичок в этом типе программирования, поэтому извините, если мой вопрос тривиален. Я пытаюсь вызвать ошибку сегментации в моей программе, и вместо выхода из программы я хочу обработать сигнал и продолжить выполнение после ошибки сегментации. Я написал код, который, кажется, работает, я просто хочу убедиться, что это способ сделать это. Так вот мой код.

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

Может кто-нибудь объяснить мне, почему printf внутри myhandle запускается дважды? И этот код в порядке?

Спасибо.

5 ответов

Решение

В этом примере я изменил ваш код ниже и теперь он работает так, как вы хотели.

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

По дальнейшему вопросу форма ОП в комментариях. Для выполнения удалите этот обработчик для этого подписчика

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

}

Возможно, вам придется проверить разборку, чтобы найти компьютер (т.е. 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, для перехода к printf() должно быть установлено значение 0x4006a8. Или от +2 до 0x4006a5, что также верно.

Причина двойного сброса сообщений заключается в том, что при первом вызове

context->uc_mcontext.gregs[REG_RIP]++

, он устанавливает RIP в 0x4006a4, одно недопустимое местоположение, вызывает еще одно исключение.

Может кто-нибудь объяснить мне, почему printf внутри myhandle запускается дважды?

Поведение кажется OS-зависимым. Контроль от 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;
}

Такого рода сигналы совсем не тривиальны для продолжения исполнения. Причина в том, что инструкция, вызывающая сигнал, не была выполнена, и поэтому выполнение будет продолжено при попытке выполнить ошибочную инструкцию.

Причина, по которой обработчик сигналов выполняется дважды (или даже повторяется бесконечно), заключается в том, что возврат приведет к тому, что ЦП попытается выполнить то же самое, что раньше приводило к ошибке сегментации, и без каких-либо изменений это снова приведет к ошибке сегментации.

Для обработки такого сигнала (SIGSEGV, SIGFPE, SIGILL и т.д.) вам придется изменить контекст сигнала, чтобы решить проблему. Для этого вам нужно будет использовать код, специально созданный для используемого процессора, а также поведение, специфичное для компилятора, так как вам нужно будет изменить контекст.

Не уверен, что это было сказано, но НЕ БЕЗОПАСНО вызывать printf() из обработчика сигнала из-за использования malloc().

Пожалуйста, прочитайте больше, чтобы понять, какие функции безопасны в контексте обработки сигналов. В Linux, а также в BSD/MacOS альтернативой является организация доставки сигнала в специальном файловом дескрипторе — в Linux это делается с помощью семейства функций signalfd, после чего вы можете обрабатывать сигнал в обычном цикле обработки событий.

Книга Майкла Керриска по Linux — отличный справочник.

Другие вопросы по тегам