Как написать обработчик сигнала, чтобы поймать 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%lxn",(long) si->si_addr);
printf("Implements the handler onlyn");
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%lxn", (long) buffer);
printf("Start of region: 0x%lxn", (long) buffer+pagesize);
printf("Start of region: 0x%lxn", (long) buffer+2*pagesize);
printf("Start of region: 0x%lxn", (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 memoryn");
a = *p; //trying to read the memory
printf("It comes here after reading memoryn");
}
else
{
if (mprotect(buffer + pagesize * 0, pagesize,PROT_READ) == -1)
handle_error("mprotect");
a = *p;
printf("Now i can read the memoryn");
}
/* for (p = buffer;p<=buffer+4*pagesize ;p++ )
{
//a = *(p);
*(p) = 'a';
printf("Writing at address %pn",p);
}*/
printf("Loop completedn"); /* Should never happen */
exit(EXIT_SUCCESS);
}
проблема в том, что работает только обработчик сигнала, и я не могу вернуться к основной функции после улавливания сигнала.
5 ответов:
когда ваш обработчик сигнала возвращается (предполагая, что он не вызывает exit или longjmp или что-то, что мешает ему фактически вернуться), код будет продолжаться в том месте, где произошел сигнал, повторяя ту же инструкцию. Поскольку на данный момент защита памяти не была изменена, она просто снова бросит сигнал, и вы вернетесь в свой обработчик сигналов в бесконечном цикле.
поэтому, чтобы заставить его работать, вы должны вызвать mprotect в обработчике сигнала. К сожалению, как отмечает Стивен Шанскер, mprotect не является асинхронным, поэтому вы не можете безопасно вызвать его из обработчика сигнала. Итак, что касается POSIX, вы облажались.
К счастью, в большинстве реализаций (все современные варианты UNIX и Linux, насколько я знаю), mprotect является системным вызовом, так что безопасно звонить из обработчика сигнала, так что вы можете сделать большую часть того, что вы хотите. Проблема в том, что если вы хотите изменить защиту после чтения, вы должны будете сделать это в основной программе после чтения.
другая возможность состоит в том, чтобы сделать что-то с третьим аргументом обработчику сигнала, который указывает на конкретную структуру OS и arch, содержащую информацию о том, где произошел сигнал. В Linux это ucontext структура, которая содержит машинную информацию об адресе $PC и другом содержимом регистра, где произошел сигнал. Если вы измените это, вы измените, где обработчик сигнала будет вернитесь, чтобы вы могли изменить $PC, чтобы быть сразу после инструкции сбоя, поэтому он не будет повторно выполняться после возврата обработчика. Это очень сложно получить право (и не портативный тоже).
edit
The
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 руководства) в обработчике сигнала.
вы попали в ловушку, что все люди делают, когда они впервые пытаются обработать сигналы. Ловушка? Думая, что вы действительно можете сделать что-нибудь полезное с обработчиками сигналов. Из обработчика сигналов можно вызывать только асинхронные и реентерабельные вызовы библиотеки.
посмотреть в этом бюллетене CERT о том, почему и список функций POSIX, которые являются безопасными.
обратите внимание, что printf (), который вы уже вызываете, не находится на этом список.
Не mprotect. Вы не можете вызвать его из обработчика сигналов. Это может работа, но я могу обещать, что вы столкнетесь с проблемами в будущем. Будьте очень осторожны с обработчиками сигналов, они сложно получить право!
EDIT
поскольку я уже сейчас являюсь переносным придурком, я укажу, что вы также не следует писать в общие (т. е. глобальные) переменные без надлежащего меры предосторожности.
вы можете восстановить из SIGSEGV на linux. Также вы можете восстановить ошибки сегментации в Windows (вы увидите структурированное исключение вместо сигнала). Но стандарт POSIX не гарантирует восстановление, Так что ваш код будет очень непереносимую.
посмотри libsigsegv.
вы не должны возвращаться из обработчика сигнала, так как тогда поведение не определено. Скорее, выпрыгните из него с longjmp.
Это нормально только в том случае, если сигнал генерируется в функции асинхронного сигнала. В противном случае поведение не определено, если программа когда-либо вызывает другую функцию async-signal-unsafe. Следовательно, обработчик сигнала должен быть установлен только непосредственно перед тем, как это необходимо, и отключен как можно скорее.
на самом деле, я знаю очень мало использует обработчика SIGSEGV:
- использовать асинхронно-сигнал-безопасный библиотеки, след в след, а затем умереть.
- в виртуальной машине, такой как JVM или CLR: проверьте, произошел ли SIGSEGV в JIT-скомпилированном коде. Если нет, умрите; если да, то бросьте исключение для конкретного языка (не исключение C++), который работает, потому что JIT-компилятор знал, что ловушка может произойти, и сгенерировал соответствующие данные размотки фрейма.
- clone () и exec () отладчик (do не используйте fork () - который вызывает обратные вызовы, зарегистрированные pthread_atfork ()).
наконец, обратите внимание, что любое действие, которое запускает SIGSEGV, вероятно, UB, так как это доступ к недопустимой памяти. Однако это было бы не так, если бы сигнал был, скажем, SIGFPE.
есть проблема компиляции с помощью
ucontext_tили structucontext(в настоящее время в/usr/include/sys/ucontext.h)
Comments