Segmentierungsfehler

Im Computer, a Segmentierungsfehler (oft verkürzt auf Segfault) oder Zugriffsverletzung ist ein Fehler, oder Ausfallbedingung, durch Hardware mit erhöhtem mit Gedächtnisschutz, benachrichtigen Betriebssystem (Betriebssystem) Die Software hat versucht, auf einen eingeschränkten Speicherbereich zuzugreifen (eine Verstöße gegen den Speicherzugriff). Auf Standard x86 Computer, dies ist eine Form von allgemeine Schutzverletzung. Das Betriebssystem Kernel wird als Antwort in der Regel eine Korrekturwirkung durchführen und den Fehler im Allgemeinen an die Straftat weitergeben Prozess durch Senden des Prozesses a Signal. Prozesse können in einigen Fällen einen benutzerdefinierten Signalhandler installieren, sodass sie sich selbst wiederherstellen können.[1] Ansonsten wird der OS -Standard -Signalhandler verwendet, der im Allgemeinen verursacht wird abnormale Beendigung des Prozesses (ein Programm Absturz) und manchmal a Core-Dump.

Segmentierungsfehler sind eine übliche Fehlerklasse in Programmen, die in Sprachen wie geschrieben wurden C Das bietet einen Speicherzugriff auf niedriger Ebene und nur wenige bis gar keine Sicherheitsüberprüfungen. Sie ergeben sich hauptsächlich aufgrund von Fehlern in der Verwendung von Zeiger zum virtueller Speicher Ansprache, insbesondere illegaler Zugang. Eine andere Art von Speicherzugriffsfehler ist a Busfehler, was auch verschiedene Ursachen hat, aber heute viel seltener ist; Diese treten hauptsächlich auf Falsche auf physisch Speicheradressierung oder aufgrund des falsch ausgerichteten Speicherzugriffs - dies sind Speicherreferenzen, die die Hardware kann nicht Adresse anstelle von Referenzen, dass ein Prozess nicht ist erlaubt zu adressieren.

Viele Programmiersprachen können Mechanismen verwenden, um Segmentierungsfehler zu vermeiden und die Speichersicherheit zu verbessern. Zum Beispiel die Rost -Programmiersprache beschäftigt eine Eigentümerbasis[2] Modell, um die Speichersicherheit zu gewährleisten.[3] Andere Sprachen, wie z. Lispeln und Java, beschäftigen Müllsammlung,[4] Dies vermeidet bestimmte Klassen von Speicherfehlern, die zu Segmentierungsfehlern führen könnten.[5]

Überblick

Beispiel für ein menschliches Signal erzeugter Signal
Segmentierungsfehler beeinflussen Krita in Kde Desktop -Umgebung

Ein Segmentierungsfehler tritt auf, wenn ein Programm versucht, auf a zuzugreifen Erinnerung Ort, an den es nicht zugreifen darf, oder versucht, auf einen Speicherort auf eine Weise zuzugreifen, die nicht zulässig ist (z. B. Versuch, an a zu schreiben schreibgeschützt Ort oder um einen Teil der zu überschreiben Betriebssystem).

Der Begriff "Segmentierung" hat verschiedene Verwendungszwecke beim Computer. Im Kontext von "Segmentierungsfehler", einem seit den 1950er Jahren verwendeten Begriff, bezieht er sich auf den Adressraum von a Programm.[6] Mit dem Speicherschutz ist nur der Adressraum des Programms lesbar, und davon nur die Stapel und der Lese-/Schreibabschnitt der Datensegment eines Programms sind beschreibbar, während schreibgeschützte Daten und die Codesegment sind nicht beschreibbar. Der Versuch, außerhalb des Adressraums des Programms zu lesen oder in ein schreibgeschütztes Segment des Adressraums zu schreiben, führt zu einem Segmentierungsfehler, daher der Name.

Auf Systemen mit Hardware Speichersegmentierung bereitstellen virtueller Speicher, ein Segmentierungsfehler tritt auf, wenn die Hardware einen Versuch erkennt, sich auf ein nicht existierendes Segment zu beziehen oder auf einen Ort außerhalb der Grenzen eines Segments zu verweisen oder auf einen Ort zu verweisen, der nicht von den dafür erteilten Berechtigungen zulässig ist Segment. Auf Systemen nur verwenden Paging, ein Ungültiger Seitenfehler führt im Allgemeinen zu einem Segmentierungsfehler, und Segmentierungsfehler und Seitenfehler sind beide Fehler, die von der angesprochen werden virtueller Speicher Management System. Segmentierungsfehler können auch unabhängig von Seitenfehlern erfolgen Pufferüberlauf Das bleibt auf einer Seite, überschreibt jedoch illegal das Gedächtnis.

Auf der Hardwareebene wird der Fehler zunächst von der erhöht Speicherverwaltungseinheit (MMU) auf den illegalen Zugriff (falls das referenzierte Speicher vorhanden ist) als Teil seines Speicherschutzmerkmals oder eines ungültigen Seitenfehlers (falls der referenzierte Speicher nicht vorhanden ist). Wenn das Problem keine ungültige logische Adresse ist, sondern stattdessen eine ungültige physische Adresse, a Busfehler wird stattdessen erhöht, obwohl diese nicht immer unterschieden werden.

Auf der Ebene des Betriebssystems wird dieser Fehler erfasst und ein Signal an den beleidigenden Prozess weitergegeben, wodurch der Handler des Prozesses für dieses Signal aktiviert wird. Unterschiedliche Betriebssysteme haben unterschiedliche Signalnamen, um anzuzeigen, dass ein Segmentierungsfehler aufgetreten ist. An Unix-artig Betriebssysteme, ein Signal namens SigsegV (abgekürzt von Segmentierungsverletzung) wird an den beleidigenden Prozess gesendet. An Microsoft WindowsDer Straftat erhält eine Status_access_violation Ausnahme.

Ursachen

Die Bedingungen, unter denen Verstöße gegen die Segmentierung auftreten und wie sie sich manifestieren, sind spezifisch für Hardware und das Betriebssystem: Verschiedene Hardware erhöhen unterschiedliche Fehler für bestimmte Bedingungen, und verschiedene Betriebssysteme wandeln diese in verschiedene Signale um, die an Prozesse weitergegeben werden. Die unmittelbare Ursache ist eine Verstöße gegen den Speicherzugriff, während die zugrunde liegende Ursache im Allgemeinen a ist Softwarefehler irgendeiner Art. Bestimmung der tiefere UrsacheDebuggen Der Fehler kann in einigen Fällen einfach sein, wenn das Programm konsequent einen Segmentierungsfehler verursacht (z. B. Dereferenzierung a Null Zeiger), während in anderen Fällen der Fehler schwer zu reproduzieren sein und von der Speicherzuweisung in jedem Lauf abhängen kann (z. B. Dereferenzierung a baumelnder Zeiger).

Das Folgende sind einige typische Ursachen für einen Segmentierungsfehler:

  • Versuch, auf eine nicht vorhandene Speicheradresse zuzugreifen (Adressraum des Außenprozesses)
  • Versuch, auf Speicher zuzugreifen, hat das Programm keine Rechte (z. B. Kernelstrukturen im Prozesskontext)
  • Versuch, schreibgeschützte Speicher zu schreiben (z. B. Codesegment)

Diese wiederum werden häufig durch Programmierfehler verursacht, die zu einem ungültigen Speicherzugriff führen:

  • Dereferencing a Null Zeiger, was normalerweise auf eine Adresse zeigt, die nicht Teil des Adressraums des Prozesses ist
  • Derference oder Zuweisen eines nicht initialisierten Zeigers (Wild Zeiger, die auf eine zufällige Speicheradresse verweist)
  • Derference oder Zuweisung eines befreiten Zeigers (baumelnder Zeiger, welches auf Speicher hinweist, das befreit/verhandelt/gelöscht wurde)
  • A Pufferüberlauf
  • A Paketüberfluss
  • Versuch, ein Programm auszuführen, das nicht korrekt zusammengestellt wird. (Einige Compiler[die?] wird ausgeben und ausführbare Datei Trotz der Anwesenheit von Kompilierungszeitfehlern.)

Im C -Code treten Segmentierungsfehler am häufigsten aufgrund von Fehlern des Zeigergebrauchs auf, insbesondere in C Dynamische Speicherzuweisung. Dereferenzieren ein Nullzeiger, was dazu führt undefiniertes Verhalten, verursacht normalerweise einen Segmentierungsfehler. Dies liegt daran, dass ein Nullzeiger keine gültige Speicheradresse sein kann. Andererseits weisen wilde Zeiger und baumelnde Zeiger auf das Gedächtnis hin, das möglicherweise existieren oder nicht und kann nicht lesbar oder beschreibbar sein und daher zu transienten Fehler führen. Zum Beispiel:

verkohlen *P1 = NULL;  // Null Zeiger verkohlen *p2;  // Wild Zeiger: überhaupt nicht initialisiert. verkohlen *p3  = Malloc(10 * Größe von(verkohlen));  // Initialisierter Zeiger auf zugewiesenen Speicher initialisiert   // (Annahme, dass Malloc nicht versagt hat) frei(p3);  // p3 ist jetzt ein baumelnder Zeiger, da das Gedächtnis befreit wurde 

Dieferenzieren einer dieser Variablen kann einen Segmentierungsfehler verursachen: Derference des Nullzeigers verursacht im Allgemeinen einen SEGFAULT, während das Lesen aus dem Wildzeiger stattdessen zu zufälligen Daten führen kann, aber kein SEGFAULT, und das Lesen vom baumelnden Zeiger kann zu gültigen Daten für eine führen während und dann zufällige Daten, wie sie überschrieben werden.

Handhabung

Die Standardaktion für einen Segmentierungsfehler oder Busfehler ist abnormale Beendigung des Prozesses, der es auslöste. EIN Kerndatei kann generiert werden, um das Debuggen zu unterstützen, und andere plattformabhängige Aktionen können ebenfalls durchgeführt werden. Zum Beispiel, Linux Systeme, die das Grsecurity -Patch verwenden Pufferüberläufe.

Auf einigen Systemen wie Linux und Windows ist es dem Programm selbst möglich, einen Segmentierungsfehler zu bewältigen.[7] Abhängig von der Architektur und dem Betriebssystem kann das laufende Programm nicht nur das Ereignis verarbeiten, sondern auch einige Informationen über den Zustand wie das Geting A extrahieren Stapelspur, Prozessorregister Werte, die Zeile des Quellcode, wenn er ausgelöst wurde, Speicheradresse, auf die ungültig zugegriffen wurde[8] und ob die Aktion eine Lektüre oder ein Schreiben war.[9]

Obwohl ein Segmentierungsfehler im Allgemeinen bedeutet, dass das Programm einen Fehler enthält, der behoben werden muss, ist es auch möglich, einen solchen Fehler für Tests, Debugging und auch nachzuahmen, bei denen direkter Zugriff auf Speicher erforderlich ist. Im letzteren Fall muss das System in der Lage sein, das Programm auch nach dem Fehler auszuführen. In diesem Fall ist es möglich, das Ereignis zu verarbeiten und den Prozessorprogramm -Zähler zu erhöhen, um über die fehlgeschlagene Anweisung zu "springen", um die Ausführung fortzusetzen.[10]

Beispiele

Segmentierungsfehler an einem EMV Tastenfeld

Schreiben auf schreibgeschützte Erinnerung

Das Schreiben in schreibgeschützte Speicher erhöht einen Segmentierungsfehler. Auf der Ebene der Codefehler tritt dies auf, wenn das Programm auf einen eigenen Teil schreibt Codesegment oder der schreibgeschützte Teil der Datensegment, wie diese vom Betriebssystem in schreibgeschützte Speicher geladen werden.

Hier ist ein Beispiel von Ansi c Code, der im Allgemeinen einen Segmentierungsfehler auf Plattformen mit Speicherschutz verursacht. Es versucht a zu ändern a Saitenliteral, was ein undefiniertes Verhalten gemäß dem ANSI C -Standard ist. Die meisten Compiler Wird dies nicht zur Kompilierung des Zeitpunkts erfassen und stattdessen für ausführbare Code kompilieren, die zum Absturz kommen:

int hauptsächlich(Leere) {   verkohlen *s = "Hallo Welt";   *s = 'H'; } 

Wenn das Programm, das diesen Code enthält Rodata Abschnitt des Programms ausführbare Datei: der schreibgeschützte Abschnitt der Datensegment. Beim Laden legt das Betriebssystem es mit anderen Saiten und mit anderen Zeichenfolgen Konstante Daten in einem schreibgeschützten Speichersegment. Bei der Ausführung eine Variable, s, wird auf den Standort der Zeichenfolge verweisen, und es wird versucht, eine zu schreiben H Zeichen durch die Variable in das Gedächtnis und verursacht einen Segmentierungsfehler. Kompilien eines solchen Programms mit einem Compiler, der nicht auf die Zuordnung von schreibgeschützten Standorten zum Kompilierungszeit überprüft wird, und das Ausführen eines Unix-ähnlichen Betriebssystems erzeugt die folgenden Laufzeit Fehler:

$ GCC SEGFAULT.C -G -O SEGFAULT$ ./segfaultSegmentierungsfehler 

Backtrace der Kerndatei von GDB:

Programm erhalten Signal Sigsegv, Segmentierung Fehler. 0x1c0005c2 in hauptsächlich () bei Segfault.c:6 6  *s = 'H'; 

Dieser Code kann durch Verwendung eines Arrays anstelle eines Zeichenzeigers korrigiert werden, da dieser Speicher auf Stapel zugibt und es zum Wert des String -Literales initialisiert:

verkohlen s[] = "Hallo Welt"; s[0] = 'H';  // äquivalent, *s = 'H'; 

Auch wenn String -Literale nicht modifiziert werden sollten (dies hat ein undefiniertes Verhalten im C -Standard), sind sie in C von von statischer Char [] Typ,[11][12][13] Es gibt also keine implizite Konvertierung im ursprünglichen Code (der a zeigt a char * in diesem Array), während sie in C ++ von sind statische const char [] Typ, und daher gibt es eine implizite Konvertierung, sodass Compiler diesen speziellen Fehler im Allgemeinen fangen.

Nullzeiger Dereference

In C und c-ähnlichen Sprachen, Nullzeiger werden verwendet, um "Zeiger auf kein Objekt" und als Fehleranzeige zu bedeuten, und Derference Ein Nullzeiger (ein Lese- oder Schreiben durch einen Nullzeiger) ist ein sehr häufiger Programmfehler. Der C -Standard sagt nicht, dass der Nullzeiger der gleiche ist wie der Zeiger Speicheradresse0, obwohl dies in der Praxis der Fall sein kann. Die meisten Betriebssysteme karten die Adresse des Nullzeigers so, dass der Zugriff auf sie zu einem Segmentierungsfehler führt. Dieses Verhalten wird durch den C -Standard nicht garantiert. Dereferenzieren ein Nullzeiger ist undefiniertes Verhalten In C und eine konforme Implementierung dürfen davon ausgehen, dass jeder Zeiger, der Derferenzierung ist, nicht null ist.

int *ptr = NULL; printf("%d", *ptr); 

Dieser Beispielcode erstellt a Null Zeigerund versucht dann, auf seinen Wert zuzugreifen (lesen Sie den Wert). Dies führt zu einem Segmentierungsfehler zur Laufzeit auf vielen Betriebssystemen.

Dereferenzieren Sie einen Nullzeiger und zuweisen ihn dann (schreiben Sie einen Wert an ein nicht existierendes Ziel), verursacht normalerweise auch einen Segmentierungsfehler:

int *ptr = NULL; *ptr = 1; 

Der folgende Code enthält einen Nullzeiger -Dereferenz, aber wenn er kompiliert wird Dead Code Elimination:

int *ptr = NULL; *ptr; 

Pufferüberlauf

Der folgende Code greift auf das Zeichenarray zu s jenseits seiner Obergrenze. Je nach Compiler und Prozessor kann dies zu einem Segmentierungsfehler führen.

verkohlen s[] = "Hallo Welt"; verkohlen c = s[20]; 

Paketüberfluss

Ein anderes Beispiel ist Rekursion ohne Basisfall:

int hauptsächlich(Leere) {   Rückkehr hauptsächlich(); } 

das verursacht das Stapel zum Überlauf was zu einem Segmentierungsfehler führt.[14] Die unendliche Rekursion kann nicht unbedingt zu einem Stapelüberlauf führen, abhängig von der Sprache, Optimierungen des Compilers und der genauen Struktur eines Codes. In diesem Fall ist das Verhalten von nicht erreichbarem Code (der Rückgabeerklärung) undefiniert, sodass der Compiler ihn beseitigen und a verwenden kann Schwanzanruf Optimierung, die zu keinem Stack -Gebrauch führen kann. Andere Optimierungen könnten das Übersetzen der Rekursion in die Iteration umfassen, die angesichts der Struktur der Beispielfunktion dazu führen würde, dass das Programm für immer läuft, während sie wahrscheinlich nicht über den Stapel überfließen.

Siehe auch

Verweise

  1. ^ Experte C -Programmierung: Deep C -Geheimnisse Von Peter van der Linden, Seite 188
  2. ^ "Die Rost -Programmiersprache - Eigentum".
  3. ^ "Fearless Parallelance mit Rost - The Rust Programming Language Blog".
  4. ^ McCarthy, John (April 1960). "Rekursive Funktionen symbolischer Ausdrücke und ihre Berechnung nach Maschine, Teil I". Kommunikation der ACM. 4 (3): 184–195. doi:10.1145/367177.367199. S2CID 1489409. Abgerufen 2018-09-22.
  5. ^ Dhurjati, Dinakar; Kowshik, Sumant; Adve, Vikram; Lattner, Chris (1. Januar 2003). "Speichersicherheit ohne Laufzeitüberprüfungen oder Müllsammlung" (PDF). Verfahren der ACM -Sigplan -Konferenz von 2003 über Sprache, Compiler und Tool für eingebettete Systeme. ACM. 38 (7): 69–80. doi:10.1145/780732.780743. ISBN 1581136471. S2CID 1459540. Abgerufen 2018-09-22.
  6. ^ "Debugging -Segmentierungsfehler und Zeigerprobleme - cprogramming.com". www.cprogramming.com. Abgerufen 2021-02-03.
  7. ^ "Sehr von SEGFAULTS unter Windows und Linux (32-Bit, x86) wiederherstellen".. Abgerufen 2020-08-23.
  8. ^ "Implementierung des SIGSEGV/SIGABRT -Handlers, der die Debug -Stapelspur druckt". GitHub. Abgerufen 2020-08-23.
  9. ^ "Wie identifiziere ich Lesen oder Schreiben von Seitungsvorgängen bei der Verwendung von Sigaction -Handler auf Sigsegv? (Linux)". Abgerufen 2020-08-23.
  10. ^ "Linux - Schreibfehlerhandler". Abgerufen 2020-08-23.
  11. ^ "6.1.4 String -Literale". ISO/IEC 9899: 1990 - Programmiersprachen - C..
  12. ^ "6.4.5 String -Literale". ISO/IEC 9899: 1999 - Programmiersprachen - C..
  13. ^ "6.4.5 String -Literale". ISO/IEC 9899: 2011 - Programmiersprachen - C..
  14. ^ Was ist der Unterschied zwischen einem Segmentierungsfehler und einem Stapelüberlauf? bei Paketüberfluss

Externe Links