Wie schreibe ich einen Signalhandler, um SIGSEGV abzufangen?

Lesezeit: 9 Minuten

Adis Benutzeravatar
Adi

Ich möchte einen Signalhandler schreiben, um SIGSEGV abzufangen. Ich schütze einen Speicherblock zum Lesen oder Schreiben

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

mprotect(buffer,pagesize,PROT_NONE)

Dies schützt Speicherbytes in Seitengröße, beginnend bei Puffer, vor Lese- oder Schreibvorgängen.

Zweitens versuche ich, die Erinnerung zu lesen:

p = buffer;
a = *p 

Dadurch wird ein SIGSEGV generiert und mein Handler wird aufgerufen. So weit, ist es gut. Mein Problem ist, dass ich, sobald der Handler aufgerufen wird, den Schreibzugriff des Speichers ändern möchte, indem ich es tue

mprotect(buffer,pagesize,PROT_READ);

und das normale Funktionieren meines Codes fortsetzen. Ich möchte die Funktion nicht verlassen. Bei zukünftigen Schreibvorgängen in denselben Speicher möchte ich das Signal erneut abfangen und die Schreibrechte ändern und dann dieses Ereignis aufzeichnen.

Hier ist der Code:

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

Das Problem ist, dass nur der Signal-Handler ausgeführt wird und ich nach dem Abfangen des Signals nicht zur Hauptfunktion zurückkehren kann.

  • danke nos für die Bearbeitung. Ich weiß das zu schätzen. Ich muss etwas Zeit aufwenden, um zu lernen, meine Fragen zu bearbeiten.

    – Adi

    18. April 2010 um 18:59 Uhr

  • Aktivieren Sie beim Kompilieren immer alle Warnungen und beheben Sie diese Warnungen. (zum gccbei minimaler Verwendung: -Wall -Wextra -pedantic Ich benutze auch: -Wconversion -std=gnu99 ) Der Compiler wird Ihnen sagen: 1) Parameter argc unbenutzter 2) Parameter argv unbenutzt (schlagen Sie vor, die Signatur main () zu verwenden von: int main( void ) 3) lokale Variable p verwendet in der else Codeblock ohne Initialisierung. 4) Parameter unused unbenutzt, vorschlagen: Anweisung hinzufügen: (void)unused; als erste Zeile in dieser Funktion. 5) lokale Variable a eingestellt, aber nicht verwendet.

    – Benutzer3629249

    3. September 2016 um 20:51 Uhr

  • Benutze niemals printf() in einem Signalhandler! Die Funktion write() wäre in Ordnung zu verwenden, aber am besten keine E / A in einem Signalhandler auszuführen, sondern nur ein Flag zu setzen und die Hauptcodezeile dieses Flag überprüfen zu lassen

    – Benutzer3629249

    3. September 2016 um 20:52 Uhr

  • Die Variable pagesize wird als deklariert intaber es sollte als deklariert werden size_t

    – Benutzer3629249

    3. September 2016 um 20:54 Uhr

  • das sig Parameter sollte mit SIGSEGV verglichen werden, da es andere Signale gibt und ein solcher Vergleich die Compiler-Meldung über ein unbenutztes entfernen würde sig Parameter

    – Benutzer3629249

    3. September 2016 um 20:59 Uhr

Benutzeravatar von Chris Dodd
Chris Dodd

Wenn Ihr Signal-Handler zurückkehrt (vorausgesetzt, er ruft nicht exit oder longjmp oder etwas auf, das die tatsächliche Rückkehr verhindert), wird der Code an dem Punkt fortgesetzt, an dem das Signal aufgetreten ist, und dieselbe Anweisung erneut ausführen. Da zu diesem Zeitpunkt der Speicherschutz nicht geändert wurde, wird das Signal einfach erneut ausgegeben, und Sie befinden sich in einer Endlosschleife wieder in Ihrem Signalhandler.

Damit es funktioniert, müssen Sie mprotect im Signal-Handler aufrufen. Wie Steven Schansker feststellt, ist mprotect leider nicht async-sicher, sodass Sie es nicht sicher vom Signal-Handler aufrufen können. Was POSIX betrifft, sind Sie also aufgeschmissen.

Glücklicherweise ist mprotect bei den meisten Implementierungen (soweit ich weiß alle modernen UNIX- und Linux-Varianten) ein Systemaufruf, sodass Sie ihn sicher von einem Signal-Handler aus aufrufen können, sodass Sie das meiste tun können, was Sie wollen. Das Problem ist, dass Sie, wenn Sie den Schutz nach dem Lesen zurücksetzen möchten, dies im Hauptprogramm nach dem Lesen tun müssen.

Eine andere Möglichkeit besteht darin, etwas mit dem dritten Argument für den Signal-Handler zu tun, das auf eine Betriebssystem- und Arch-spezifische Struktur zeigt, die Informationen darüber enthält, wo das Signal aufgetreten ist. Unter Linux ist dies eine Kontext Struktur, die maschinenspezifische Informationen über die $PC-Adresse und andere Registerinhalte enthält, an denen das Signal aufgetreten ist. Wenn Sie dies ändern, ändern Sie, wohin der Signal-Handler zurückkehrt, sodass Sie $PC so ändern können, dass er direkt nach der fehlerhaften Anweisung steht, damit er nicht erneut ausgeführt wird, nachdem der Handler zurückkehrt. Dies ist sehr schwierig, richtig zu machen (und auch nicht tragbar).

bearbeiten

Das ucontext Struktur ist definiert in <ucontext.h>. Innerhalb der ucontext das Feld uc_mcontext enthält den Maschinenkontext und darin dasdas Array gregs enthält den allgemeinen Registerkontext. Also in Ihrem Signalhandler:

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

wird Ihnen den PC nennen, bei dem die Ausnahme aufgetreten ist. Sie können es lesen, um herauszufinden, welche Anweisung so fehlerhaft war, und etwas anderes tun.

In Bezug auf die Portabilität des Aufrufs von mprotect im Signalhandler sollte jedes System, das entweder der SVID-Spezifikation oder der BSD4-Spezifikation folgt, sicher sein – sie erlauben das Aufrufen jedes Systemaufrufs (alles in Abschnitt 2 des Handbuchs) in einem Signal Handler.

  • Richtig, Sie können den Speicherzugriff für das Programm (wie eine VM) durchführen und dann den Befehlszeiger aktualisieren. Berufung mprotect ist auf jeden Fall einfacher.

    – Ben Voigt

    18. April 2010 um 19:22 Uhr

  • Hallo Chris, Sie haben mir einige nützliche Informationen gegeben. Danke dafür. Können Sie mir sagen, wie ich die Informationen in der ucontext-Struktur lesen kann (3. Argument und $PC ändern). Ich bin neugierig, darüber zu erfahren.

    – Adi

    18. April 2010 um 19:25 Uhr

  • @ Ben Voigt, ich habe nicht ganz verstanden, was du meinst, bitte etwas ausführlicher.

    – Adi

    18. April 2010 um 19:26 Uhr

  • @chris, sieht so aus, als könnte ich mprotect im Signalhandler ausführen und dann sicher zurückkehren, um meine normale Ausführung durchzuführen. Ich bin mir bei der Portabilität nicht sicher, wie Sie erwähnt haben, aber ich hoffe, dass es in meinem Fall in Ordnung ist. Danke allen für die Hilfe..

    – Adi

    18. April 2010 um 19:43 Uhr

  • @chris danke für die Erklärung, ich werde den PC mit deiner Technik sehen.

    – Adi

    19. April 2010 um 6:34 Uhr

Benutzeravatar von Steven Schlansker
Stefan Schlansker

Sie sind in die Falle getappt, die alle Leute machen, wenn sie zum ersten Mal versuchen, mit Signalen umzugehen. Die Falle? Zu denken, dass man eigentlich alles schaffen kann nützlich mit Signalhandlern. Von einem Signal-Handler aus dürfen Sie nur asynchrone und reentrant-sichere Bibliotheksaufrufe aufrufen.

Sehen dieses CERT-Advisory warum und eine Liste der POSIX-Funktionen, die sicher sind.

Beachten Sie, dass printf(), das Sie bereits aufrufen, nicht auf dieser Liste steht.

mprotect auch nicht. Sie dürfen es nicht von einem Signal-Handler aufrufen. Es könnte Arbeit, aber ich kann versprechen, dass Sie später auf Probleme stoßen werden. Seien Sie wirklich vorsichtig mit Signalhandlern, sie sind schwierig richtig zu machen!

BEARBEITEN

Da ich im Moment schon ein Portabilitätsidiot bin, weise ich euch darauf hin sollte auch nicht in gemeinsam genutzte (dh globale) Variablen schreiben ohne die richtigen Vorkehrungen zu treffen.

  • Hallo Steven, wenn ich im Signal-Handler nichts Nützliches tun kann, bin ich in Ordnung, wenn ich einige Zähler darin aktualisieren und zum Hauptbildschirm zurückkehren und meinen Code normal ausführen kann, ist das möglich?

    – Adi

    18. April 2010 um 19:03 Uhr

  • Aus dem CERT-Hinweis zitierend, “können sie andere Funktionen aufrufen, vorausgesetzt, dass alle Implementierungen, auf die der Code portiert wird, garantieren, dass diese Funktionen asynchron und sicher sind”. Unter Linux beinhaltet das viel mehr Funktionen.

    – Ben Voigt

    18. April 2010 um 19:29 Uhr

  • Sicher, aber man muss sich nur des Problems bewusst sein! Ich kann nicht aus dem Stegreif benennen, welche Funktionen signalsicher sind und welche nicht, und ich bezweifle, dass viele das könnten!

    – Steven Schlansker

    19. April 2010 um 1:59 Uhr

  • Das CERT Secure Coding ist eine großartige Seite, ich kannte sie nicht. Es scheint, als hätte ich für eine Weile etwas Neues gelesen 🙂

    – alecov

    12. August 2010 um 5:47 Uhr

  • „Du darfst nicht anrufen [mprotect] von einem Signal-Handler.” Nur wenn Sie sich strikt an POSIX halten müssen. Glibcs ​​mprotect ist heute async-signalsicher: gnu.org/software/libc/manual/html_node/…

    – Joseph Sible – Wiedereinsetzung von Monica

    26. September 2018 um 19:59 Uhr

Benutzeravatar von Ben Voigt
Ben Voigt

Sie können SIGSEGV unter Linux wiederherstellen. Sie können sich auch von Segmentierungsfehlern unter Windows erholen (Sie sehen eine strukturierte Ausnahme anstelle eines Signals). Aber der POSIX-Standard garantiert keine Wiederherstellungdaher wird Ihr Code sehr nicht portabel sein.

Schauen Sie sich an libsigsegv.

Benutzeravatar von Demi
Demi

Sie sollten nicht vom Signalhandler zurückkehren, da das Verhalten dann undefiniert ist. Springen Sie lieber mit longjmp heraus.

Dies ist nur in Ordnung, wenn das Signal in einer async-signalsicheren Funktion generiert wird. Andernfalls ist das Verhalten undefiniert, wenn das Programm jemals eine andere Async-Signal-unsafe-Funktion aufruft. Daher sollte der Signal-Handler nur unmittelbar vor der Notwendigkeit eingerichtet und so schnell wie möglich wieder abgebaut werden.

Tatsächlich kenne ich nur sehr wenige Verwendungen eines SIGSEGV-Handlers:

  • Verwenden Sie eine asynchronsignalsichere Backtrace-Bibliothek, um einen Backtrace zu protokollieren, und sterben Sie dann.
  • in einer VM wie JVM oder CLR: Überprüfen Sie, ob SIGSEGV in JIT-kompiliertem Code aufgetreten ist. Wenn nicht, stirb; Wenn ja, löse eine sprachspezifische Ausnahme aus (nicht eine C++-Ausnahme), die funktioniert, weil der JIT-Compiler wusste, dass der Trap auftreten könnte, und entsprechende Frame-Unwind-Daten generiert hat.
  • clone() und exec() einen Debugger (do nicht use fork() – das Callbacks aufruft, die von pthread_atfork() registriert wurden).

Beachten Sie schließlich, dass jede Aktion, die SIGSEGV auslöst, wahrscheinlich UB ist, da dies auf ungültigen Speicher zugreift. Dies wäre jedoch nicht der Fall, wenn das Signal beispielsweise SIGFPE wäre.

Benutzeravatar von shreshtha
schreschtha

Es gibt ein Kompilierungsproblem bei der Verwendung von ucontext_t oder Struktur ucontext (anwesend in /usr/include/sys/ucontext.h)

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

1417850cookie-checkWie schreibe ich einen Signalhandler, um SIGSEGV abzufangen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy