Как записать обработчик сигналов для ловли 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 
#include 
#include 
#include 
#include 
#include 

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

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

68
задан Jeff 16 November 2016 в 20:17
поделиться

4 ответа

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

Итак, чтобы это работало, вы должны вызвать mprotect в обработчике сигнала. К сожалению, как отмечает Стивен Шанскер, mprotect не является асинхронным, поэтому вы не можете безопасно вызывать его из обработчика сигнала. Итак, что касается POSIX, вы облажались.

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

Другая возможность - сделать что-то с третьим аргументом обработчика сигнала, который указывает на ОС и структуру, специфичную для архитектуры, которая содержит информацию о том, где произошел сигнал. В Linux это структура ucontext , которая содержит машинно-зависимую информацию об адресе $ PC и другом содержимом регистров, из которых возник сигнал. Если вы измените это, вы измените место, куда будет возвращаться обработчик сигнала, поэтому вы можете изменить $ PC так, чтобы он находился сразу после инструкции, вызвавшей сбой, чтобы она не выполнялась повторно после возврата обработчика. Это очень сложно сделать правильно (и это тоже непереносимо).

править

Структура ucontext определена в . В ucontext поле uc_mcontext содержит контекст компьютера, а в , что , массив gregs содержит общий контекст регистра. Итак, в вашем обработчике сигналов:

ucontext *u = (ucontext *)unused;
unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP];

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

Что касается переносимости вызова mprotect в обработчике сигналов, любая система, которая следует либо спецификации SVID, либо спецификации BSD4, должна быть безопасной - они позволяют вызывать любой системный вызов (все, что описано в разделе 2 руководства) в обработчике сигналов.

68
ответ дан 24 November 2019 в 14:18
поделиться

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

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

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

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

РЕДАКТИРОВАТЬ

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

25
ответ дан 24 November 2019 в 14:18
поделиться

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

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

12
ответ дан 24 November 2019 в 14:18
поделиться

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

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

0
ответ дан 24 November 2019 в 14:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: