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 — отличный справочник.