GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Wie schreibe ich einen Signalhandler, um SIGSEGV abzufangen?

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 Wiederherstellung, daher wird Ihr Code sehr nicht portierbar sein.

Werfen Sie einen Blick auf libsigsegv.


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 Signal-Handler.

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 ein ucontext 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. Das ist sehr schwierig, richtig zu machen (und auch nicht portierbar).

bearbeiten

Der ucontext Struktur ist in <ucontext.h> definiert . Innerhalb der ucontext das Feld uc_mcontext enthält den Maschinenkontext und innerhalb dass , das 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.

Was die Portabilität des Aufrufs von mprotect im Signalhandler betrifft, 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.


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 Nützliche tun kann mit Signalhandlern. Von einem Signal-Handler dürfen Sie nur asynchrone und reentrant-sichere Bibliotheksaufrufe aufrufen.

Sehen Sie sich diesen CERT-Hinweis an, 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 vielleicht 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, möchte ich darauf hinweisen, dass Sie auch nicht in gemeinsam genutzte (d.h. globale) Variablen schreiben sollten, ohne die entsprechenden Vorsichtsmaßnahmen zu treffen.


Linux
  1. So schreiben Sie mit dem Linux-Befehl Text auf ein Bild

  2. Wie groß ist der Pipe-Puffer?

  3. Wie schreibe ich ein Startskript für Systemd?

  4. Wie signalisiert man das Ende der Stdin-Eingabe?

  5. Wie kann man einem Thread beitreten, der am Blockieren von IO hängt?

So installieren Sie Signal Messenger auf Ubuntu 20.04

So schreiben Sie ein Shell-Skript in Ubuntu

So installieren Sie die Signal Messaging App unter Ubuntu 20.04

Wie man ein C-Programm unter Linux schreibt und ausführt

So installieren Sie Signal Messaging unter Ubuntu 20.04

Wie schreibe ich auf eine virtuelle Webcam unter Linux?