Bestimmen Sie die Codezeile, die einen Segmentierungsfehler verursacht?

151

Wie kann man feststellen, wo sich der Fehler im Code befindet, der einen Segmentierungsfehler verursacht ?

Kann mein compiler ( gcc) den Ort des Fehlers im Programm anzeigen?

Trilarion
quelle
5
Kein gcc / gdb kann nicht. Sie können herausfinden, wo der Segfault aufgetreten ist, aber der tatsächliche Fehler kann sich an einer völlig anderen Stelle befinden.

Antworten:

218

GCC kann das nicht, aber GDB (ein Debugger ) kann es sicher. Kompilieren Sie Ihr Programm mit dem -gSchalter wie folgt:

gcc program.c -g

Dann benutze gdb:

$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>

Hier ist ein nettes Tutorial, um Ihnen den Einstieg in GDB zu erleichtern.

Wo der Segfault auftritt, ist im Allgemeinen nur ein Hinweis darauf, wo "der Fehler, der ihn verursacht", im Code enthalten ist. Der angegebene Ort ist nicht unbedingt der Ort, an dem das Problem liegt.

nc3b
quelle
28
Beachten Sie, dass der Ort, an dem der Segfault auftritt, im Allgemeinen nur ein Hinweis darauf ist, wo "der Fehler, der ihn verursacht", im Code enthalten ist. Ein wichtiger Hinweis, aber nicht unbedingt, wo das Problem liegt.
mpez0
9
Sie können auch (bt full) verwenden, um weitere Details zu erhalten.
Ant2009
1
Ich finde das nützlich: gnu.org/software/gcc/bugs/segfault.html
Loves Probability
2
Verwenden Sie btals Abkürzung für backtrace.
Rustyx
42

Sie können es auch valgrindversuchen: Wenn Sie installieren valgrindund ausführen

valgrind --leak-check=full <program>

Anschließend wird Ihr Programm ausgeführt und Stack-Traces für alle Segfaults sowie für ungültige Speicherlese- oder -schreibvorgänge und Speicherlecks angezeigt. Es ist wirklich sehr nützlich.

jwkpiano1
quelle
2
+1, Valgrind ist so viel schneller / einfacher zu verwenden, um Speicherfehler zu erkennen. Bei nicht optimierten Builds mit Debugging-Symbolen erfahren Sie genau, wo und warum ein Segfault aufgetreten ist.
Tim Post
1
Leider verschwindet mein Segfault beim Kompilieren mit -g -O0 und kombiniert mit valgrind.
JohnMudd
2
--leak-check=fullwird nicht helfen, Segfaults zu debuggen. Es ist nur zum Debuggen von Speicherlecks nützlich.
ks1322
@ JohnMudd Ich habe einen Segfault nur etwa 1% der getesteten Eingabedateien erscheinen, wenn Sie die fehlgeschlagene Eingabe wiederholen, wird es nicht fehlschlagen. Mein Problem wurde durch Multithreading verursacht. Bisher habe ich die Codezeile, die dieses Problem verursacht, nicht herausgefunden. Ich benutze Wiederholungsversuche, um dieses Problem vorerst zu vertuschen. Wenn Sie die Option -g verwenden, verschwindet der Fehler!
Kemin Zhou
18

Sie können auch einen Core-Dump verwenden und ihn dann mit gdb untersuchen. Um nützliche Informationen zu erhalten, müssen Sie auch mit dem -gFlag kompilieren .

Wann immer Sie die Nachricht erhalten:

 Segmentation fault (core dumped)

Eine Kerndatei wird in Ihr aktuelles Verzeichnis geschrieben. Und Sie können es mit dem Befehl überprüfen

 gdb your_program core_file

Die Datei enthält den Status des Speichers, als das Programm abstürzte. Ein Core-Dump kann während der Bereitstellung Ihrer Software hilfreich sein.

Stellen Sie sicher, dass Ihr System die Größe der Core-Dump-Datei nicht auf Null setzt. Sie können es auf unbegrenzt einstellen mit:

ulimit -c unlimited

Vorsicht! dass Core Dumps riesig werden können.

Lucas
quelle
Ich habe kürzlich zu Arch-Linux gewechselt. Mein aktuelles Verzeichnis enthält nicht die Core-Dump-Datei. Wie kann ich es generieren?
Abhinav
Sie generieren es nicht; Linux tut es. Core Dumps werden an verschiedenen Orten auf verschiedenen Linuces gespeichert - Google in der Nähe. Lesen Sie
Mawg sagt, Monica am
7

Es gibt eine Reihe von Tools, die beim Debuggen von Segmentierungsfehlern helfen, und ich möchte mein Lieblingswerkzeug zur Liste hinzufügen: Address Sanitizers (häufig als ASAN abgekürzt) .

Moderne¹ Compiler werden mit dem praktischen -fsanitize=addressFlag geliefert , das einige Kompilierungs- und Laufzeitkosten hinzufügt, wodurch mehr Fehler überprüft werden.

Gemäß der Dokumentation umfassen diese Überprüfungen standardmäßig das Abfangen von Segmentierungsfehlern. Der Vorteil hierbei ist, dass Sie einen Stack-Trace erhalten, der der Ausgabe von gdb ähnelt, ohne das Programm jedoch in einem Debugger auszuführen. Ein Beispiel:

int main() {
  volatile int *ptr = (int*)0;
  *ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING

Die Ausgabe ist etwas komplizierter als die Ausgabe von GDB, aber es gibt Vorteile:

  • Es ist nicht erforderlich, das Problem zu reproduzieren, um eine Stapelverfolgung zu erhalten. Es reicht aus, das Flag während der Entwicklung zu aktivieren.

  • ASANs erfassen weit mehr als nur Segmentierungsfehler. Viele Zugriffe außerhalb der Grenzen werden abgefangen, selbst wenn dieser Speicherbereich für den Prozess zugänglich war.


¹ Das sind Clang 3.1+ und GCC 4.8+ .

Asynts
quelle
Das ist sehr hilfreich für mich. Ich habe einen sehr subtilen Fehler, der zufällig mit einer Häufigkeit von etwa 1% auftritt. Ich verarbeite eine große Anzahl von Eingabedateien mit (16 Hauptschritte; jeder von einer anderen C- oder C ++ - Binärdatei). Ein späterer Schritt löst einen Segmentierungsfehler aufgrund von Multithreading nur zufällig aus. Es ist schwer zu debuggen. Diese Option löste die Ausgabe der Debug-Informationen aus, zumindest gab sie mir einen Startpunkt für die Codeüberprüfung, um den Ort des Fehlers zu finden.
Kemin Zhou
2

Lucas 'Antwort zu Core Dumps ist gut. In meiner .cshrc habe ich:

alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'

um die Rückverfolgung durch Eingabe von 'core' anzuzeigen. Und der Datumsstempel, um sicherzustellen, dass ich mir die richtige Datei ansehe :(.

Hinzugefügt : Liegt ein Stapel Korruption Fehler, dann wird der Backtrace auf den Core - Dump angewandt wird oft Müll. In diesem Fall kann das Ausführen des Programms innerhalb von gdb gemäß der akzeptierten Antwort zu besseren Ergebnissen führen (vorausgesetzt, der Fehler ist leicht reproduzierbar). Achten Sie auch darauf, dass mehrere Prozesse gleichzeitig den Kern entleeren. Einige Betriebssysteme fügen die PID zum Namen der Kerndatei hinzu.

Joseph Quinsey
quelle
4
und vergessen Sie nicht, zuerst ulimit -c unlimitedCore Dumps zu aktivieren.
James Morris
@ James: Richtig. Lucas hat das bereits erwähnt. Und für diejenigen von uns, die immer noch im csh stecken, verwenden Sie 'limit'. Und ich konnte die CYGWIN-Stackdumps noch nie lesen (aber ich habe es 2 oder 3 Jahre lang nicht versucht).
Joseph Quinsey
2

Alle oben genannten Antworten sind korrekt und werden empfohlen. Diese Antwort ist nur als letztes Mittel gedacht, wenn keiner der oben genannten Ansätze verwendet werden kann.

Wenn alles andere fehlschlägt, können Sie Ihr Programm jederzeit mit verschiedenen temporären Debug-Print-Anweisungen (z. B. fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);) neu kompilieren, die über die Ihrer Meinung nach relevanten Teile Ihres Codes verteilt sind. Führen Sie dann das Programm aus und beobachten Sie, was der letzte Debug-Druck war, der kurz vor dem Absturz gedruckt wurde. Sie wissen, dass Ihr Programm so weit gekommen ist. Der Absturz muss also nach diesem Zeitpunkt stattgefunden haben. Fügen Sie Debug-Ausdrucke hinzu oder entfernen Sie sie, kompilieren Sie sie erneut und führen Sie den Test erneut aus, bis Sie ihn auf eine einzelne Codezeile eingegrenzt haben. An diesem Punkt können Sie den Fehler beheben und alle temporären Debug-Ausdrucke entfernen.

Es ist ziemlich langweilig, aber es hat den Vorteil, dass es fast überall funktioniert - das einzige Mal, wenn Sie aus irgendeinem Grund keinen Zugriff auf stdout oder stderr haben oder wenn der Fehler, den Sie beheben möchten, ein Rennen ist -Bedingung, deren Verhalten sich ändert, wenn sich das Timing des Programms ändert (da die Debug-Drucke das Programm verlangsamen und sein Timing ändern)

Jeremy Friesner
quelle