Wie funktioniert ein Segmentierungsfehler unter der Haube?

266

Abgesehen von "Die MMU der CPU sendet ein Signal" und "Der Kernel leitet es an das fehlerhafte Programm weiter und beendet es", kann ich anscheinend keine Informationen dazu finden.

Ich nahm an, dass es wahrscheinlich das Signal an die Shell sendet und die Shell es durch Beenden des fehlerhaften Prozesses und Drucken verarbeitet "Segmentation fault". Also habe ich diese Annahme getestet, indem ich eine extrem minimale Shell geschrieben habe, die ich crsh (crap shell) nenne . Diese Shell nimmt nur Benutzereingaben entgegen und leitet sie an die system()Methode weiter.

#include <stdio.h>
#include <stdlib.h>

int main(){
    char cmdbuf[1000];
    while (1){
        printf("Crap Shell> ");
        fgets(cmdbuf, 1000, stdin);
        system(cmdbuf);
    }
}

Also habe ich diese Shell in einem leeren Terminal ausgeführt (ohne bashdarunter zu laufen). Dann fuhr ich fort, ein Programm auszuführen, das einen Segfault erzeugt. Wenn meine Annahmen korrekt wären, würde dies entweder a) abstürzen crsh, xterm schließen, b) nicht drucken "Segmentation fault"oder c) beides.

braden@system ~/code/crsh/ $ xterm -e ./crsh
Crap Shell> ./segfault
Segmentation fault
Crap Shell> [still running]

Zurück zu Punkt eins, denke ich. Ich habe gerade gezeigt, dass dies nicht von der Shell, sondern vom darunterliegenden System ausgeführt wird. Wie wird "Segmentierungsfehler" überhaupt gedruckt? "Wer" macht das? Der Kernel? Etwas anderes? Wie breitet sich das Signal und alle seine Nebenwirkungen von der Hardware aus, bis das Programm schließlich beendet wird?

Braden Best
quelle
43
crshist eine großartige Idee für diese Art des Experimentierens. Vielen Dank, dass Sie uns alle darüber und über die dahinter stehende Idee informiert haben.
Bruce Ediger
30
Als ich es zum ersten Mal sah crsh, dachte ich, es würde "Absturz" ausgesprochen. Ich bin mir nicht sicher, ob das ein ebenso passender Name ist.
jpmc26
56
Dies ist ein schönes Experiment ... aber Sie sollten wissen, was system()unter der Haube tut. Es stellt sich heraus, dass system()ein Shell-Prozess ausgelöst wird! Ihr Shell-Prozess erzeugt also einen anderen Shell-Prozess und dieser Shell-Prozess (wahrscheinlich /bin/shoder so ähnlich) ist derjenige, der das Programm ausführt. Die Art /bin/shund Weise oder Arbeitsweise bashist durch die Verwendung von fork()und exec()(oder einer anderen Funktion in der execve()Familie).
Dietrich Epp
4
@BradenBest: Genau. Lesen Sie die Handbuchseite man 2 wait, sie enthält die Makros WIFSIGNALED()und WTERMSIG().
Dietrich Epp
4
@DietrichEpp Genau wie du gesagt hast! Ich habe versucht, einen Scheck hinzuzufügen (WIFSIGNALED(status) && WTERMSIG(status) == 11), damit er etwas doofes druckt ( "YOU DUN GOOFED AND TRIGGERED A SEGFAULT"). Als ich das segfaultProgramm von innen heraus ausführte crsh, druckte es genau das. Währenddessen wird bei Befehlen, die normalerweise beendet werden, keine Fehlermeldung ausgegeben.
Braden Best

Antworten:

248

Alle modernen CPUs haben die Fähigkeit, den gerade ausgeführten Maschinenbefehl zu unterbrechen . Sie speichern genügend Status (normalerweise, aber nicht immer, auf dem Stapel), um die Ausführung später wieder aufzunehmen , als wäre nichts geschehen (normalerweise wird der unterbrochene Befehl von Grund auf neu gestartet). Dann starten sie die Ausführung eines Interrupt-Handlers , der nur aus Maschinencode besteht, aber an einer bestimmten Stelle platziert ist, damit die CPU weiß, wo er sich im Voraus befindet. Interrupt-Handler sind immer Teil des Kernels des Betriebssystems: Die Komponente, die mit den größten Berechtigungen ausgeführt wird und für die Überwachung der Ausführung aller anderen Komponenten verantwortlich ist. 1,2

Interrupts können synchron sein , was bedeutet, dass sie von der CPU selbst als direkte Antwort auf etwas ausgelöst werden, das der gerade ausgeführte Befehl ausgeführt hat, oder asynchron , was bedeutet, dass sie zu einem unvorhersehbaren Zeitpunkt aufgrund eines externen Ereignisses auftreten, wie z. B. Daten, die im Netzwerk ankommen Hafen. Einige Leute reservieren den Begriff "Interrupt" für asynchrone Interrupts und nennen synchrone Interrupts stattdessen "Traps", "Fehler" oder "Ausnahmen", aber diese Wörter haben alle andere Bedeutungen, also bleibe ich bei "synchronem Interrupt".

Heutzutage kennen die meisten modernen Betriebssysteme Prozesse . Im Grunde ist dies ein Mechanismus, mit dem der Computer mehr als ein Programm gleichzeitig ausführen kann, aber es ist auch ein wesentlicher Aspekt der Konfiguration des Speicherschutzes durch Betriebssysteme , der ein Merkmal der meisten (aber leider noch nicht alle ) modernen CPUs. Es geht zusammen mit dem virtuellen SpeicherDies ist die Möglichkeit, die Zuordnung zwischen Speicheradressen und tatsächlichen Speicherorten im RAM zu ändern. Der Speicherschutz ermöglicht es dem Betriebssystem, jedem Prozess einen eigenen privaten RAM-Block zuzuweisen, auf den nur er zugreifen kann. Außerdem kann das Betriebssystem (das im Auftrag eines bestimmten Prozesses handelt) RAM-Bereiche als schreibgeschützt, ausführbar, für eine Gruppe kooperierender Prozesse freigegeben usw. kennzeichnen. Außerdem wird ein Teil des Arbeitsspeichers vorhanden sein, auf den nur der Zugriff möglich ist Kernel. 3

Solange jeder Prozess nur so auf den Speicher zugreift, wie es die CPU zulässt, ist der Speicherschutz unsichtbar. Wenn ein Prozess gegen die Regeln verstößt, generiert die CPU einen synchronen Interrupt und fordert den Kernel auf, die Dinge zu klären. Es kommt regelmäßig vor, dass der Prozess nicht wirklich gegen die Regeln verstößt. Nur der Kernel muss etwas arbeiten, bevor der Prozess fortgesetzt werden kann. Wenn beispielsweise eine Seite des Arbeitsspeichers eines Prozesses in die Auslagerungsdatei "entfernt" werden muss, um Speicherplatz im RAM für etwas anderes freizugeben, markiert der Kernel diese Seite als unzugänglich. Wenn der Prozess das nächste Mal versucht, ihn zu verwenden, generiert die CPU einen Speicherschutz-Interrupt. Der Kernel ruft die Seite aus dem Auslagerungsmodus ab, legt sie wieder an ihrem ursprünglichen Ort ab, markiert sie als wieder zugänglich und setzt die Ausführung fort.

Angenommen, der Prozess hat wirklich gegen die Regeln verstoßen. Es wurde versucht, auf eine Seite zuzugreifen, der noch kein RAM zugeordnet war, oder es wurde versucht, eine Seite auszuführen, die als nicht mit Maschinencode gekennzeichnet ist, oder was auch immer. Die Betriebssystemfamilie, die allgemein als "Unix" bekannt ist, verwendet alle Signale , um mit dieser Situation umzugehen. 4 Signale ähneln Interrupts, werden jedoch vom Kernel generiert und von Prozessen abgefangen, anstatt von der Hardware generiert und vom Kernel abgefangen zu werden. Prozesse können Signalhandler definierenin ihrem eigenen Code, und teilen Sie dem Kernel mit, wo sie sich befinden. Diese Signalhandler werden dann ausgeführt und unterbrechen bei Bedarf den normalen Steuerungsfluss. Alle Signale haben eine Nummer und zwei Namen, von denen einer ein kryptisches Akronym und der andere eine etwas weniger kryptische Phrase ist. Das Signal, das generiert wird, wenn ein Prozess die Speicherschutzregeln verletzt, ist (gemäß Konvention) Nummer 11, und seine Namen sind SIGSEGVund "Segmentierungsfehler". 5,6

Ein wichtiger Unterschied zwischen Signalen und Interrupts besteht darin, dass es für jedes Signal ein Standardverhalten gibt . Wenn das Betriebssystem keine Handler für alle Interrupts definiert, ist dies ein Fehler im Betriebssystem, und der gesamte Computer stürzt ab, wenn die CPU versucht, einen fehlenden Handler aufzurufen. Prozesse sind jedoch nicht verpflichtet, Signalhandler für alle Signale zu definieren. Wenn der Kernel ein Signal für einen Prozess generiert und dieses Signal auf seinem Standardverhalten belassen wurde, wird der Kernel einfach weitermachen und alles tun, was der Standard ist, und den Prozess nicht stören. Das Standardverhalten der meisten Signale ist entweder "nichts tun" oder "diesen Prozess beenden und möglicherweise auch einen Core-Dump erzeugen". SIGSEGVist einer der letzteren.

Um es noch einmal zusammenzufassen, wir haben einen Prozess, der die Speicherschutzregeln gebrochen hat. Die CPU hat den Prozess angehalten und einen synchronen Interrupt generiert. Der Kernel hat das unterbrochen und ein SIGSEGVSignal für den Prozess generiert . Angenommen, der Prozess hat keinen Signal-Handler für eingerichtet SIGSEGV, sodass der Kernel das Standardverhalten ausführt, das darin besteht, den Prozess zu beenden. Dies hat die gleichen Auswirkungen wie der _exitSystemaufruf: Geöffnete Dateien werden geschlossen, Speicher wird freigegeben usw.

Bis zu diesem Zeitpunkt wurden keine Nachrichten ausgedruckt, die ein Mensch sehen kann, und die Shell (oder allgemein der übergeordnete Prozess des gerade abgebrochenen Prozesses) war überhaupt nicht beteiligt. SIGSEGVGeht zu dem Prozess, der die Regeln verletzt hat, nicht zu seinem übergeordneten Element. Der nächste Schritt in der Sequenz besteht jedoch darin, dem übergeordneten Prozess mitzuteilen, dass sein untergeordnetes Element beendet wurde. Dies kann auf verschiedene Weise geschehen, von denen die einfachste ist , wenn die Eltern bereits für diese Meldung warten, eines der Verwendung von waitSystemaufrufen ( wait, waitpid, wait4, usw.). In diesem Fall veranlasst der Kernel lediglich die Rückgabe dieses Systemaufrufs und versieht den übergeordneten Prozess mit einer Codenummer, die als Exit-Status bezeichnet wird. 7 Der Beendigungsstatus informiert den Elternteil darüber, warum der Kindprozess beendet wurde. In diesem Fall wird festgestellt, dass das Kind aufgrund des Standardverhaltens eines SIGSEGVSignals beendet wurde.

Der übergeordnete Prozess kann dann das Ereignis einem Menschen melden, indem er eine Nachricht druckt; Shell-Programme tun dies fast immer. Sie enthalten crshkeinen Code, um das zu tun, aber es passiert trotzdem, weil die C-Bibliotheksroutine systemeine voll funktionsfähige Shell /bin/sh"unter der Haube" ausführt. crshist der Großelternteil in diesem Szenario; Die Benachrichtigung über den übergeordneten Prozess wird durch gekennzeichnet /bin/sh, wodurch die übliche Nachricht gedruckt wird. Dann wird es /bin/shselbst beendet, da es nichts mehr zu tun hat, und die Implementierung der C-Bibliothek von systemempfängt diese Beendigungsbenachrichtigung. Sie können diese Beendigungsbenachrichtigung in Ihrem Code sehen, indem Sie den Rückgabewert von überprüfensystem; Es wird Ihnen jedoch nicht mitgeteilt, dass der Enkelprozess aufgrund eines Segfault-Vorgangs gestorben ist, da dieser durch den Zwischen-Shell-Prozess verbraucht wurde.


Fußnoten

  1. Einige Betriebssysteme implementieren keine Gerätetreiber als Teil des Kernels. Alle Interrupt-Handler müssen jedoch weiterhin Teil des Kernels sein, ebenso wie der Code, der den Speicherschutz konfiguriert, da die Hardware nur dem Kernel gestattet, diese Aufgaben auszuführen .

  2. Es kann ein Programm geben, das als "Hypervisor" oder "Virtual Machine Manager" bezeichnet wird und noch privilegierter ist als der Kernel. Für diese Antwort kann es jedoch als Teil der Hardware betrachtet werden .

  3. Der Kernel ist ein Programm , aber kein Prozess. es ist eher wie eine Bibliothek. Alle Prozesse führen von Zeit zu Zeit zusätzlich zu ihrem eigenen Code Teile des Kernel-Codes aus. Es kann eine Reihe von "Kernel-Threads" geben, die nur Kernel-Code ausführen, die uns hier jedoch nicht betreffen.

  4. Das einzige Betriebssystem, mit dem Sie wahrscheinlich mehr zu tun haben, das nicht als Implementierung von Unix angesehen werden kann, ist natürlich Windows. In dieser Situation werden keine Signale verwendet. ( In der Tat ist es nicht haben Signale, unter Windows die <signal.h>Schnittstelle vollständig durch die C - Bibliothek gefälscht ist.) Es nutzt etwas „genannt strukturierte Ausnahmebehandlung “ statt.

  5. Einige Speicherschutzverletzungen erzeugen SIGBUS("Busfehler") statt SIGSEGV. Die Linie zwischen den beiden ist unterbestimmt und variiert von System zu System. Wenn Sie ein Programm geschrieben haben, das einen Handler für definiert SIGSEGV, ist es wahrscheinlich eine gute Idee, denselben Handler für zu definieren SIGBUS.

  6. "Segmentierungsfehler" war der Name des Interrupts, der bei Verstößen gegen den Speicherschutz von einem der Computer generiert wurde, auf denen das ursprüngliche Unix ausgeführt wurde , wahrscheinlich der PDP-11 . „ Segmentierung “ ist ein Typ von Speicherschutz, aber heutzutage der Begriff „Segmentierung Fehler “ bezieht sich allgemein auf jede Art von Speicherschutzverletzung.

  7. Alle anderen Möglichkeiten, wie der übergeordnete Prozess benachrichtigt werden kann, wenn ein Kind beendet wurde, führen dazu, dass der übergeordnete Prozess anruft waitund einen Beendigungsstatus erhält. Es ist nur so, dass zuerst etwas anderes passiert.

zwol
quelle
@zvol: ad 2) Ich glaube nicht, dass die CPU etwas über Prozesse weiß. Sie sollten sagen, dass es einen Interrupt-Handler aufruft, der die Kontrolle übergibt.
User323094
9
@ user323094 Moderne Multicore-CPUs kennen sich mit Prozessen aus. Ausreichend, damit sie in dieser Situation nur den Ausführungsthread anhalten können, der die Speicherschutzverletzung ausgelöst hat. Außerdem habe ich versucht, mich nicht auf Details zu beschränken. Aus der Sicht des User-Space-Programmierers ist es am wichtigsten, Schritt 2 so zu verstehen, dass es die Hardware ist , die Verstöße gegen den Speicherschutz erkennt. weniger die genaue Arbeitsteilung zwischen Hardware, Firmware und Betriebssystem, wenn es darum geht, den "störenden Prozess" zu identifizieren.
zwol
Eine weitere Subtilität, die einen naiven Leser verwirren könnte, ist "Der Kernel sendet dem anstößigen Prozess ein SIGSEGV-Signal." Dies verwendet den üblichen Jargon, bedeutet aber tatsächlich, dass der Kernel sich selbst auffordert, sich mit signal foo auf der Prozessleiste zu befassen (dh der Userland-Code wird nur dann einbezogen, wenn ein Signalhandler installiert ist, eine Frage, die vom Kernel gelöst wird). Manchmal bevorzuge ich aus diesem Grund "ein SIGSEGV-Signal im Prozess auslösen" .
dmckee
2
Der wesentliche Unterschied zwischen SIGBUS (Busfehler) und SIGSEGV (Segmentierungsfehler) ist folgender: SIGSEGV tritt auf, wenn die CPU weiß, dass Sie nicht auf eine Adresse zugreifen sollten (und daher keine externe Speicherbusanforderung stellt). SIGBUS tritt auf, wenn die CPU erst dann von dem Adressierungsproblem erfährt, wenn sie Ihre Anfrage auf ihren externen Adressbus gelegt hat. Fragen Sie beispielsweise nach einer physischen Adresse, auf die nichts auf dem Bus antwortet, oder nach dem Lesen von Daten an einer falsch ausgerichteten Grenze (für die zwei physische Anforderungen anstelle einer erforderlich wären)
Stuart Caie,
2
@StuartCaie Sie beschreiben das Verhalten von Interrupts . In der Tat machen viele CPUs den Unterschied, den Sie skizzieren (obwohl einige dies nicht tun, und die Grenze zwischen den beiden variiert). Die Signale SIGSEGV und SIGBUS werden jedoch nicht zuverlässig auf diese beiden Zustände auf CPU-Ebene abgebildet. Die einzige Bedingung, bei der POSIX SIGBUS anstelle von SIGSEGV benötigt, besteht darin, dass Sie mmapeine Datei in einen Speicherbereich verschieben, der größer als die Datei ist, und dann über das Dateiende hinaus auf "ganze Seiten" zugreifen. (POSIX ist ansonsten ziemlich vage, wann SIGSEGV / SIGBUS / SIGILL / etc passieren.)
zwol
42

Die Shell hat in der Tat etwas mit dieser Nachricht zu tun und crshruft indirekt eine Shell auf, was wahrscheinlich auch so ist bash.

Ich habe ein kleines C-Programm geschrieben, das immer Fehler ausgibt:

#include <stdio.h>

int
main(int ac, char **av)
{
        int *i = NULL;

        *i = 12;

        return 0;
}

Wenn ich es von meiner Standard-Shell aus starte zsh, erhalte ich Folgendes:

4 % ./segv
zsh: 13512 segmentation fault  ./segv

Wenn ich es starte bash, bekomme ich das, was Sie in Ihrer Frage notiert haben:

bediger@flq123:csrc % ./segv
Segmentation fault

Ich wollte einen Signal-Handler in meinen Code schreiben, dann wurde mir klar, dass der system()von crshexecs verwendete Bibliotheksaufruf /bin/shlaut eine Shell ist man 3 system. Das /bin/shist mit ziemlicher Sicherheit der Ausdruck "Segmentierungsfehler", da dies mit ziemlicher Sicherheit nicht der Fall crshist.

Wenn Sie erneut schreiben crsh, um den execve()Systemaufruf zum Ausführen des Programms zu verwenden, wird die Zeichenfolge "Segmentierungsfehler" nicht angezeigt. Es kommt von der Shell, die von aufgerufen wird system().

Bruce Ediger
quelle
5
Ich habe das gerade mit Dietrich Epp besprochen. Ich habe eine Version von crsh gehackt , die verwendet wird, execvpund den Test erneut durchgeführt, um festzustellen, dass die Shell zwar immer noch nicht abstürzt (was bedeutet, dass SIGSEGV niemals an die Shell gesendet wird), aber keinen "Segmentierungsfehler" ausgibt. Es wird überhaupt nichts gedruckt. Dies scheint darauf hinzudeuten, dass die Shell erkennt, wann ihre untergeordneten Prozesse beendet werden, und für das Drucken von "Segmentierungsfehler" (oder einer Variante davon) verantwortlich ist.
Braden Best
2
@BradenBest - Ich habe das Gleiche getan, mein Code ist schlampiger als Ihr Code. Ich habe überhaupt keine Nachricht erhalten und meine noch schäbigere Shell druckt nichts. Ich habe waitpid()auf jedem Fork / Exec einen anderen Wert für Prozesse mit einem Segmentierungsfehler zurückgegeben als für Prozesse, die mit dem Status 0 beendet werden.
Bruce Ediger
21

Abgesehen von "Die MMU der CPU sendet ein Signal" und "Der Kernel leitet es an das fehlerhafte Programm weiter und beendet es", kann ich anscheinend keine Informationen dazu finden.

Dies ist eine verstümmelte Zusammenfassung. Der Unix-Signalmechanismus unterscheidet sich grundlegend von den CPU-spezifischen Ereignissen, die den Prozess starten.

Wenn auf eine ungültige Adresse zugegriffen wird (oder in einen schreibgeschützten Bereich geschrieben wird, versucht wird, einen nicht ausführbaren Abschnitt auszuführen usw.), generiert die CPU im Allgemeinen ein CPU-spezifisches Ereignis (auf herkömmlichen Nicht-VM-Architekturen war dies der Fall) Dies wird als Segmentierungsverletzung bezeichnet, da jedes "Segment" (normalerweise der schreibgeschützte ausführbare "Text", die beschreibbaren Daten mit variabler Länge und der Stapel, der sich normalerweise am anderen Ende des Speichers befindet) einen festen Adressbereich hat. In einer modernen Architektur ist es eher ein Seitenfehler (für nicht zugeordneten Speicher) oder eine Zugriffsverletzung (für Lese-, Schreib- und Ausführungsberechtigungsprobleme), und ich werde mich für den Rest der Antwort darauf konzentrieren.

Jetzt kann der Kernel verschiedene Dinge tun. Seitenfehler werden auch für den Speicher generiert, der gültig, aber nicht geladen ist (z. B. ausgelagert oder in einer Mmap-Datei usw.). In diesem Fall ordnet der Kernel den Speicher zu und startet das Benutzerprogramm von der Anweisung aus, die den Fehler verursacht hat Error. Andernfalls wird ein Signal gesendet. Dies "leitet [das ursprüngliche Ereignis] nicht direkt an das fehlerhafte Programm", da der Prozess zum Installieren eines Signal-Handlers anders und größtenteils architekturunabhängig ist, als wenn das Programm die Installation eines Interrupt-Handlers simulieren würde.

Wenn auf dem Benutzerprogramm ein Signalhandler installiert ist, bedeutet dies, dass ein Stapelrahmen erstellt und die Ausführungsposition des Benutzerprogramms auf den Signalhandler festgelegt wird. Das Gleiche gilt für alle Signale, aber im Fall einer Segmentierungsverletzung werden die Dinge im Allgemeinen so angeordnet, dass der Befehl, der den Fehler verursacht hat, neu gestartet wird, wenn der Signalhandler zurückkehrt. Möglicherweise hat das Anwenderprogramm den Fehler behoben, z. B. indem der Speicher der betreffenden Adresse zugeordnet wurde (es hängt von der Architektur ab, ob dies möglich ist). Der Signalhandler kann auch zu einer anderen Stelle im Programm springen (normalerweise über longjmp oder durch Auslösen einer Ausnahme), um die Operation abzubrechen, die den fehlerhaften Speicherzugriff verursacht hat.

Wenn im Anwenderprogramm kein Signalhandler installiert ist, wird es einfach beendet. Bei einigen Architekturen wird der Befehl möglicherweise immer wieder neu gestartet, wenn das Signal ignoriert wird, was zu einer Endlosschleife führt.

Random832
quelle
+1, nur eine Antwort, die etwas zu der akzeptierten hinzufügt. Schöne Beschreibung der "Segmentierungs" -Historie. Unterhaltsame Tatsache: In x86 sind Segmentbegrenzungen im 32-Bit-geschützten Modus (mit oder ohne aktiviertem Paging (virtueller Speicher)) noch vorhanden, sodass Anweisungen, die auf den Speicher zugreifen, generiert werden können #PF(fault-code)(Seitenfehler) oder #GP(0)("Wenn sich eine effektive Adresse eines Speicheroperanden außerhalb der CS befindet, DS-, ES-, FS- oder GS-Segmentlimit. "). Im 64-Bit-Modus werden Segmentlimitprüfungen gestrichen, da die Betriebssysteme stattdessen nur Paging und ein flaches Speichermodell für den Benutzerbereich verwendet haben.
Peter Cordes
Eigentlich glaube ich, dass die meisten Betriebssysteme auf x86 eine segmentierte Paginierung verwenden: eine Reihe großer Segmente in einem flachen, seitenweisen Adressraum. So schützen Sie den Kernel-Speicher und
ordnen ihn
Auch unter NT (aber ich würde gerne wissen, ob bei den meisten Unixen das Gleiche der Fall ist!) Kann ein "Segmentierungsfehler" ziemlich häufig auftreten: Am Anfang des Benutzerraums befindet sich ein 64k-geschütztes Segment. Wenn Sie also einen NULL-Zeiger dereferenzieren, wird a ausgelöst (
Richtig
1
@ LorenzoDematté Ja, alle oder fast alle modernen Unixe hinterlassen am Anfang des Adressraums einen Teil permanent nicht zugeordneter Adressen, um NULL-Dereferenzen abzufangen. Es kann ziemlich groß sein - auf 64-Bit-Systemen können es sogar vier Gigabyte sein , so dass das versehentliche Abschneiden von Zeigern auf 32 Bit sofort erkannt wird. Eine Segmentierung im strengen x86-Sinne wird jedoch kaum angewendet. Es gibt ein flaches Segment für den Benutzerbereich und ein Segment für den Kernel und vielleicht ein paar für spezielle Tricks, wie die Verwendung von FS und GS.
zwol
1
@ LorenzoDematté NT verwendet Ausnahmen anstelle von Signalen. in diesem Fall STATUS_ACCESS_VIOLATION.
Random832
18

Ein Segmentierungsfehler ist ein Zugriff auf eine Speicheradresse, die nicht zulässig ist (nicht Teil des Prozesses oder der Versuch, schreibgeschützte Daten zu schreiben oder nicht ausführbare Daten auszuführen, ...). Dies wird von der MMU (Memory Management Unit, heute Teil der CPU) abgefangen und verursacht einen Interrupt. Der Interrupt wird vom Kernel behandelt, der ein SIGSEGFAULTSignal (siehe signal(2)zum Beispiel) an den fehlerhaften Prozess sendet . Der Standardhandler für dieses Signal gibt einen Speicherauszug aus (siehe core(5)) und beendet den Prozess.

Die Muschel hat absolut keine Hand dabei.

vonbrand
quelle
3
So definiert Ihre C-Bibliothek, wie Glibc auf einem Desktop, die Zeichenfolge?
Drewbenn
7
Es ist auch erwähnenswert, dass SIGSEGV kann behandelt / ignoriert werden. Es ist also möglich, ein Programm zu schreiben, das dadurch nicht beendet wird. Die Java Virtual Machine ist ein bemerkenswertes Beispiel, das SIGSEGV intern für verschiedene Zwecke verwendet, wie hier erwähnt: stackoverflow.com/questions/3731784/…
Karol Nowak
2
Ebenso kümmert sich .NET unter Windows in den meisten Fällen nicht um das Hinzufügen von Nullzeigerprüfungen - es erkennt lediglich Zugriffsverletzungen (entspricht Segfaults).
immibis