Jak napisać obsługę sygnału, aby złapać SIGSEGV?

Chcę napisać obsługę sygnału, aby złapać SIGSEGV. I protect a block of memory for read or write using

char *buffer;
char *p;
char a;
int pagesize = 4096;

mprotect(buffer,pagesize,PROT_NONE)

Zabezpiecza to bajty rozmiaru strony pamięci zaczynające się w buforze przed jakimkolwiek odczytem lub zapisem.

Po Drugie, staram się odczytać pamięć:

p = buffer;
a = *p 

To wygeneruje SIGSEGV, a mój handler zostanie wywołany. Jak na razie dobrze. Mój problem polega na tym, że po wywołaniu obsługi chcę zmienić zapis dostępu do pamięci wykonując

mprotect(buffer,pagesize,PROT_READ);

I dalej normalne funkcjonowanie mojego kodu. Nie chcę wychodzić z funkcji. Przy przyszłych zapisach do tej samej pamięci, chcę ponownie złapać sygnał i zmodyfikować prawa zapisu, a następnie nagrać to zdarzenie.

Oto kod:

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

Problem polega na tym, że działa tylko obsługa sygnału i nie mogę wrócić do głównej funkcji po przechwyceniu sygnału.

Author: Jeff, 2010-04-18

5 answers

Gdy funkcja obsługi sygnału powróci (zakładając, że nie wywoła exit lub longjmp lub czegoś, co uniemożliwia jego rzeczywiste zwrócenie), kod będzie kontynuowany w momencie wystąpienia sygnału, ponownie wykonując tę samą instrukcję. Ponieważ w tym momencie Ochrona pamięci nie została zmieniona, po prostu ponownie rzuci sygnał i będziesz z powrotem w obsłudze sygnału w nieskończonej pętli.

Aby to zadziałało, musisz wywołać mprotect w programie obsługi sygnału. Niestety, jako Steven Schansker zauważa, że mprotect nie jest asynchroniczny, więc nie można go bezpiecznie wywołać z obsługi sygnału. Więc jeśli chodzi o POSIX, masz przesrane.

Na szczęście w większości implementacji (wszystkie współczesne warianty Uniksa i Linuksa, o ile mi wiadomo), mprotect jest wywołaniem systemowym, więc jest bezpieczne do wywołania z poziomu obsługi sygnału , więc możesz zrobić większość tego, co chcesz. Problem polega na tym, że jeśli chcesz zmienić zabezpieczenia z powrotem po odczycie, będziesz musiał to zrobić w głównym program po przeczytaniu.

Inną możliwością jest zrobienie czegoś z trzecim argumentem do obsługi sygnału, który wskazuje na specyficzną strukturę systemu operacyjnego i arch, która zawiera informacje o tym, gdzie wystąpił sygnał. W Linuksie jest to struktura ucontext, która zawiera specyficzne dla maszyny informacje o adresie $PC i innej zawartości rejestru, w której wystąpił sygnał. Jeśli zmodyfikujesz to, zmienisz miejsce, do którego powróci obsługa sygnału, więc możesz zmienić $PC NA be tuż po instrukcji błędu, więc nie będzie ponownie wykonywana po powrocie obsługi. Jest to bardzo trudne do uzyskania (i nie Przenośne zbyt).

Edit

Struktura ucontext jest zdefiniowana w <ucontext.h>. W ucontext pole uc_mcontext zawiera kontekst maszyny, a w to Tablica gregs zawiera ogólny kontekst rejestru. Więc w twoim programie obsługi sygnału:

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

Wyświetli komputer, na którym wystąpił wyjątek. Możesz przeczytać, aby dowiedzieć się out what instruction it czy to był błąd, i zrobić coś innego.

Jeśli chodzi o przenośność wywołania mprotect w funkcji obsługi sygnału, każdy system, który podąża za specyfikacją SVID lub specyfikacją BSD4 powinien być bezpieczny-pozwalają one na wywołanie dowolnego wywołania systemowego (cokolwiek w sekcji 2 podręcznika) w funkcji obsługi sygnału.

 62
Author: Chris Dodd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-20 07:33:41

Wpadłeś w pułapkę, którą wszyscy ludzie robią, gdy po raz pierwszy próbują obsłużyć sygnały. Pułapka? Myślenie, że możesz zrobić wszystko użyteczne za pomocą programów obsługi sygnałów. Z funkcji obsługi sygnału można wywoływać tylko asynchroniczne i bezpieczne wywołania biblioteki reentrant.

Zobacz Ten poradnik CERT , Dlaczego I listę bezpiecznych funkcji POSIX.

Zauważ, że printf (), którą już wywołujesz, nie znajduje się na tej liście.

Nor is mprotect. Nie wolno Ci dzwonić od operatora sygnału. To może zadziałać, ale mogę obiecać, że napotkasz problemy. Bądź bardzo ostrożny z obsługą sygnału, są trudne do uzyskania prawo!

EDIT

Ponieważ już teraz jestem przenośnym dupkiem, zwrócę uwagę, że ty również nie powinieneś pisać do współdzielonych (tj. globalnych) zmiennych bez podjęcia odpowiednich środków ostrożności.

 21
Author: Steven Schlansker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-19 02:04:43

Można odzyskać z SIGSEGV na Linuksie. Możesz również odzyskać po błędach segmentacji w systemie Windows (zobaczysz ustrukturyzowany wyjątek zamiast sygnału). Ale standard POSIX nie gwarantuje odzyskiwania , więc Twój kod będzie bardzo przenośny.

Spójrz na libsigsegv .

 9
Author: Ben Voigt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-24 23:21:08

Nie powinieneś wracać z obsługi sygnału, ponieważ wtedy zachowanie jest niezdefiniowane. Raczej wyskoczyć z niego z longjmp.

Jest to w porządku tylko wtedy, gdy sygnał jest generowany w funkcji async-signal-safe. W przeciwnym razie zachowanie jest niezdefiniowane, jeśli program kiedykolwiek wywoła inną funkcję async-signal-unsafe. W związku z tym obsługa sygnału powinna być ustawiona bezpośrednio przed koniecznością i wyłączona tak szybko, jak to możliwe.

W zasadzie znam niewiele zastosowań SIGSEGV "handler": {]}

  • użyj biblioteki backtrace async-signal-safe, aby zarejestrować backtrace, a następnie umrzeć.
  • w maszynie wirtualnej, takiej jak JVM lub CLR: sprawdź, czy SIGSEGV wystąpił w skompilowanym kodzie JIT. Jeśli nie, giń; jeśli tak, wyrzuć wyjątek specyficzny dla języka (, a nie wyjątek C++), który działa, ponieważ kompilator JIT wiedział, że pułapka może się wydarzyć i wygenerował odpowiednie dane odwijające ramkę.
  • clone() i exec () a debugger (do Nie używa fork–) - który wywołuje wywołania zwrotne zarejestrowany przez pthread_atfork ()).

Na koniec zauważ, że każda akcja uruchamiająca SIGSEGV to prawdopodobnie UB, ponieważ jest to dostęp do nieprawidłowej pamięci. Nie byłoby tak jednak, gdyby sygnałem był, powiedzmy, SIGFPE.

 4
Author: Demi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-04 16:05:28

Istnieje problem kompilacji przy użyciu ucontext_t lub struct ucontext (obecny w /usr/include/sys/ucontext.h)

Http://www.mail-archive.com/[email protected]/msg13853.html

 0
Author: shreshtha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-15 05:39:52