Как написать обработчик сигнала для отлова SIGSEGV? - PullRequest
65 голосов
/ 18 апреля 2010

Я хочу написать обработчик сигнала для перехвата 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);
}

Проблема в том, что работает только обработчик сигнала, и я не могу вернуться к основной функции после перехвата сигнала.

Ответы [ 5 ]

65 голосов
/ 18 апреля 2010

Когда ваш обработчик сигнала возвращается (при условии, что он не вызывает 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 руководства). в обработчике сигнала.

24 голосов
/ 18 апреля 2010

Вы попали в ловушку, которую делают все люди, когда они впервые пытаются обработать сигналы. Ловушка? Думая, что вы действительно можете сделать что-нибудь полезное с обработчиками сигналов. Из обработчика сигнала вам разрешено вызывать только асинхронные и безопасные для повторного входа вызовы библиотеки.

См. эту рекомендацию CERT относительно причин и список безопасных функций POSIX.

Обратите внимание, что printf (), который вы уже вызываете, отсутствует в этом списке.

Также не защищен. Вы не можете вызывать его из обработчика сигнала. Это может работать, но я могу обещать, что в будущем у вас возникнут проблемы. Будьте очень осторожны с обработчиками сигналов, их сложно понять правильно!

EDIT

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

12 голосов
/ 18 апреля 2010

Вы можете восстановить с SIGSEGV на Linux. Также вы можете исправить ошибки сегментации в Windows (вместо сигнала вы увидите структурированное исключение). Но стандарт POSIX не гарантирует восстановления , поэтому ваш код будет очень непереносимым.

Взгляните на libsigsegv .

4 голосов
/ 19 июня 2013

Вы не должны возвращаться из обработчика сигнала, так как тогда поведение не определено. Скорее выпрыгните из него с помощью longjmp.

Это нормально, только если сигнал генерируется в функции, безопасной для асинхронного сигнала. В противном случае поведение не определено, если программа когда-либо вызывает другую небезопасную асинхронную функцию. Следовательно, обработчик сигнала должен быть установлен только непосредственно перед тем, как это необходимо, и отключен как можно скорее.

На самом деле, я знаю очень мало случаев использования обработчика SIGSEGV:

  • используйте безопасную для асинхронного сигнала библиотеку обратной трассировки, чтобы зарегистрировать обратную трассировку, а затем умереть.
  • в виртуальной машине, такой как JVM или CLR: проверьте, произошел ли SIGSEGV в JIT-скомпилированном коде. Если нет, умри; если это так, то генерируйте специфическое для языка исключение (, а не исключение C ++), которое работает, потому что JIT-компилятор знал, что может произойти перехват, и генерировал соответствующие данные для разворачивания кадра.
  • clone () и exec () отладчик (не не используйте fork () - который вызывает обратные вызовы, зарегистрированные pthread_atfork ()).

Наконец, обратите внимание, что любое действие, которое запускает SIGSEGV, вероятно, является UB, так как оно обращается к недопустимой памяти. Однако это было бы не так, если бы сигнал был, скажем, SIGFPE.

0 голосов
/ 12 августа 2010

Существует проблема компиляции с использованием ucontext_t или struct ucontext (присутствует в /usr/include/sys/ucontext.h)

http://www.mail-archive.com/arch-general@archlinux.org/msg13853.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...