Как написать обработчик сигнала, чтобы поймать SIGSEGV?
Я хочу написать обработчик сигнала, чтобы поймать SIGSEGV.
Я защищаю блок памяти для чтения или записи с помощью
char *buffer;
char *p;
char a;
int pagesize = 4096;
mprotect(buffer,pagesize,PROT_NONE)
Это защищает байты с размером страниц, начиная с буфера от любых чтений или записей.
Во-вторых, я пытаюсь прочитать память:
p = buffer;
a = *p
Это создаст SIGSEGV, и мой обработчик будет вызван.
Все идет нормально. Моя проблема заключается в том, что после вызова обработчика я хочу изменить запись доступа в память, выполнив
mprotect(buffer,pagesize,PROT_READ);
и продолжить нормальное функционирование моего кода. Я не хочу выходить из функции.
В будущем записи в одну и ту же память, я хочу снова поймать сигнал и изменить права записи, а затем записать это событие.
Вот код:
#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
char *buffer;
int flag=0;
static void handler(int sig, siginfo_t *si, void *unused)
{
printf("Got SIGSEGV at address: 0x%lx\n",(long) si->si_addr);
printf("Implements the handler only\n");
flag=1;
//exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
char *p; char a;
int pagesize;
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = handler;
if (sigaction(SIGSEGV, &sa, NULL) == -1)
handle_error("sigaction");
pagesize=4096;
/* Allocate a buffer aligned on a page boundary;
initial protection is PROT_READ | PROT_WRITE */
buffer = memalign(pagesize, 4 * pagesize);
if (buffer == NULL)
handle_error("memalign");
printf("Start of region: 0x%lx\n", (long) buffer);
printf("Start of region: 0x%lx\n", (long) buffer+pagesize);
printf("Start of region: 0x%lx\n", (long) buffer+2*pagesize);
printf("Start of region: 0x%lx\n", (long) buffer+3*pagesize);
//if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1)
if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1)
handle_error("mprotect");
//for (p = buffer ; ; )
if(flag==0)
{
p = buffer+pagesize/2;
printf("It comes here before reading memory\n");
a = *p; //trying to read the memory
printf("It comes here after reading memory\n");
}
else
{
if (mprotect(buffer + pagesize * 0, pagesize,PROT_READ) == -1)
handle_error("mprotect");
a = *p;
printf("Now i can read the memory\n");
}
/* for (p = buffer;p<=buffer+4*pagesize ;p++ )
{
//a = *(p);
*(p) = 'a';
printf("Writing at address %p\n",p);
}*/
printf("Loop completed\n"); /* Should never happen */
exit(EXIT_SUCCESS);
}
Проблема заключается в том, что работает только обработчик сигнала, и я не могу вернуться к основной функции после захвата сигнала.
Ответы
Ответ 1
Когда ваш обработчик сигнала возвращается (при условии, что он не вызывает exit или longjmp или что-то, что мешает ему фактически возвращаться), код будет продолжаться в точке, в которой произошел сигнал, повторно выполнив ту же инструкцию. Поскольку на этом этапе защита памяти не была изменена, она просто снова подаст сигнал, и вы вернетесь в обработчик сигнала в бесконечном цикле.
Чтобы заставить его работать, вы должны вызвать mprotect в обработчике сигналов. К сожалению, как отмечает Стивен Шанскер, mprotect не является безопасным для асинхронизации, поэтому вы не можете безопасно называть его обработчиком сигнала. Итак, что касается POSIX, вы ввернуты.
К счастью для большинства реализаций (все современные версии UNIX и Linux, насколько мне известно), mprotect - это системный вызов, поэтому безопасный вызов из обработчика сигнала, поэтому вы можете делать большую часть того, что хотите. Проблема в том, что если вы хотите изменить защиту после чтения, вам придется сделать это в основной программе после чтения.
Еще одна возможность - сделать что-то с третьим аргументом обработчику сигнала, который указывает на структуру ОС и своду, которая содержит информацию о том, где произошел сигнал. В Linux это структура ucontext, которая содержит информацию об адресе $PC и других регистрах, в которых произошел сигнал. Если вы измените это значение, вы измените место, где обработчик сигнала вернется, поэтому вы можете изменить $PC, чтобы сразу после инструкции по сбою, чтобы он не перезапустился после возврата обработчика. Это очень сложно сделать правильным (и не переносным).
изменить
Структура ucontext
определена в <ucontext.h>
. Внутри ucontext
поле uc_mcontext
содержит машинный контекст, и внутри него массив gregs
содержит общий регистр. Поэтому в вашем обработчике сигналов:
ucontext *u = (ucontext *)unused;
unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP];
предоставит вам компьютер, где произошло исключение. Вы можете прочитать его, чтобы выяснить, какая инструкция
это было ошибкой и сделать что-то другое.
Что касается мобильности вызова функции mprotect в обработчике сигналов, любая система, которая следует либо спецификации SVID, либо спецификации BSD4 должна быть безопасной - они позволяют вызывать любой системный вызов (что-либо в разделе 2 руководства) в обработчике сигналов.
Ответ 2
Вы попали в ловушку, которую делают все люди, когда они впервые пытаются обработать сигналы. Ловушка? Думая, что вы действительно можете сделать что-нибудь полезное с обработчиками сигналов. Из обработчика сигнала вам разрешено вызывать только асинхронные и безопасные для повторного входа вызовы библиотеки.
См. этот совет CERT о причинах и список безопасных функций POSIX.
Обратите внимание, что printf(), который вы уже вызываете, отсутствует в этом списке.
Также не является mprotect. Вы не можете вызывать его из обработчика сигнала. Это может сработать, но я могу обещать, что в будущем у вас возникнут проблемы. Будьте очень осторожны с обработчиками сигналов, их сложно понять правильно!
EDIT
Поскольку в данный момент я уже работаю над переносимостью, я укажу, что вам также не следует писать в общие (т.е. глобальные) переменные без принятия надлежащих мер предосторожности.
Ответ 3
Вы можете восстановить SIGSEGV на linux. Также вы можете восстановить из-за ошибок сегментации в Windows (вы увидите структурированное исключение вместо сигнала). Но стандарт POSIX не гарантирует восстановление, поэтому ваш код будет очень не переносимым.
Взгляните на libsigsegv.
Ответ 4
Вы не должны возвращаться из обработчика сигнала, так как поведение undefined. Скорее, выпрыгните из него с длинной ногой.
Это нормально, только если сигнал генерируется в безопасной системе с асинхронным сигналом. В противном случае поведение undefined, если программа когда-либо называет другую функцию несинхронизирующего сигнала. Следовательно, обработчик сигнала должен устанавливаться только перед тем, как он понадобится, и будет устранен как можно скорее.
На самом деле, я знаю очень мало использования обработчика SIGSEGV:
- используйте безопасную для асинхронного сигнала библиотеку backtrace для регистрации обратного хода, а затем умрите.
- в виртуальной машине, такой как JVM или CLR: проверьте, произошел ли SIGSEGV в JIT-компилированном коде. Если нет, умрете; если это так, то бросайте исключение, специфичное для языка (не исключение С++), которое работает, потому что компилятор JIT знал, что ловушка может произойти, и создала соответствующие данные для размотки кадров.
- clone() и exec() отладчик (не используйте fork() - который вызывает обратные вызовы, зарегистрированные pthread_atfork()).
Наконец, обратите внимание, что любое действие, запускающее SIGSEGV, вероятно, является UB, поскольку это обращение к недопустимой памяти. Однако это не так, если бы сигнал был, скажем, SIGFPE.
Ответ 5
Существует проблема компиляции с использованием ucontext_t
или struct ucontext
(присутствует в /usr/include/sys/ucontext.h
)
http://www.mail-archive.com/[email protected]/msg13853.html