GDB beschädigter Stapelrahmen – Wie debuggt man?

Lesezeit: 7 Minuten

Ich habe folgenden Stacktrace. Kann man daraus irgendetwas Nützliches zum Debuggen ableiten?

Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0  0x00000002 in ?? ()
#1  0x00000001 in ?? ()
#2  0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

Wo wir anfangen sollen, uns den Code anzusehen, wenn wir a bekommen Segmentation faultund der Stack-Trace ist nicht so nützlich?

HINWEIS: Wenn ich den Code poste, geben mir die SO-Experten die Antwort. Ich möchte die Anleitung von SO nehmen und die Antwort selbst finden, also poste ich den Code hier nicht. Entschuldigung.

  • Wahrscheinlich ist Ihr Programm ins Unkraut gesprungen – können Sie etwas aus dem Stapelzeiger wiederherstellen?

    – Karl Norum

    21. März 2012 um 17:36 Uhr

  • Eine andere zu beachtende Sache ist, ob der Frame-Zeiger richtig gesetzt ist. Bauen Sie ohne Optimierungen oder übergeben ein Flag wie -fno-omit-frame-pointer? Auch für Speicherbeschädigung, valgrind könnte ein geeigneteres Werkzeug sein, wenn es eine Option für Sie ist.

    – Fataler Fehler

    21. März 2012 um 17:36 Uhr

Benutzeravatar von Chris Dodd
Chris Dodd

Diese falschen Adressen (0x00000002 und dergleichen) sind eigentlich PC-Werte, keine SP-Werte. Wenn Sie nun diese Art von SEGV mit einer falschen (sehr kleinen) PC-Adresse erhalten, liegt das in 99 % der Fälle daran, dass ein falscher Funktionszeiger aufgerufen wird. Beachten Sie, dass virtuelle Aufrufe in C++ über Funktionszeiger implementiert werden, sodass sich jedes Problem mit einem virtuellen Aufruf auf die gleiche Weise manifestieren kann.

Ein indirekter Aufrufbefehl schiebt den PC nach dem Aufruf einfach auf den Stapel und setzt dann den PC auf den Zielwert (in diesem Fall falsch), also wenn dieser ist Was passiert ist, können Sie leicht rückgängig machen, indem Sie den PC manuell vom Stapel entfernen. In 32-Bit-x86-Code tun Sie einfach:

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4

Mit 64-Bit-x86-Code benötigen Sie

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8

Dann solltest du in der Lage sein, a bt und finde heraus, wo der Code wirklich ist.

In den anderen 1 % der Fälle ist der Fehler auf das Überschreiben des Stacks zurückzuführen, normalerweise durch Überlaufen eines auf dem Stack gespeicherten Arrays. In diesem Fall können Sie möglicherweise mehr Klarheit über die Situation erhalten, indem Sie ein Tool wie verwenden Valgrind

  • @George: gdb executable corefile öffnet gdb mit der ausführbaren Datei und der Kerndatei, an welcher Stelle Sie dies tun können bt (oder die obigen Befehle gefolgt von bt)…

    – Chris Dodd

    27. März 2014 um 18:58 Uhr

  • @mk .. ARM verwendet den Stack nicht für Rückgabeadressen – es verwendet stattdessen das Link-Register. Daher tritt dieses Problem im Allgemeinen nicht auf, oder wenn dies der Fall ist, liegt es normalerweise an einer anderen Stapelbeschädigung.

    – Chris Dodd

    17. April 2015 um 22:10 Uhr


  • Selbst in ARM werden meiner Meinung nach alle Allzweckregister und LR im Stapel gespeichert, bevor die aufgerufene Funktion ausgeführt wird. Sobald die Funktion beendet ist, wird der Wert von LR in PC eingefügt und daher kehrt die Funktion zurück. Wenn also der Stack beschädigt ist, können wir einen falschen Wert sehen, ist PC richtig? In diesem Fall kann das Anpassen des Stack-Zeigers zu einem geeigneten Stack führen und beim Debuggen des Problems helfen. Was denkst du? Bitte teilen Sie mir Ihre Gedanken mit. Vielen Dank.

    – Sandeep

    20. April 2015 um 2:37 Uhr

  • Was bedeutet falsch?

    – Danny Lo

    17. Mai 2017 um 11:12 Uhr

  • ARM ist nicht x86 – sein Stapelzeiger wird aufgerufen spnicht esp oder rspund seine Aufrufanweisung speichert die Rücksendeadresse in der lr registrieren, nicht auf dem Stack. Für ARM ist also alles, was Sie wirklich brauchen, um den Anruf rückgängig zu machen set $pc = $lr. Wenn $lr ungültig ist, haben Sie ein viel schwierigeres Problem, sich zu entspannen.

    – Chris Dodd

    2. Oktober 2018 um 4:38 Uhr


wallyks Benutzeravatar
Wallyk

Wenn die Situation ziemlich einfach ist, ist die Antwort von Chris Dodd die beste. Es sieht so aus, als ob es durch einen NULL-Zeiger gesprungen ist.

Es ist jedoch möglich, dass sich das Programm vor dem Absturz in den Fuß, das Knie, den Hals und das Auge geschossen hat – den Stapel überschrieben, den Frame-Zeiger durcheinander gebracht und andere Übel. Wenn dem so ist, dann wird Ihnen das Enträtseln des Haschischs wahrscheinlich keine Kartoffeln und Fleisch zeigen.

Die effizientere Lösung besteht darin, das Programm unter dem Debugger auszuführen und Funktionen zu überspringen, bis das Programm abstürzt. Sobald eine abstürzende Funktion identifiziert wurde, starten Sie erneut und treten Sie in diese Funktion ein, und ermitteln Sie, welche aufgerufene Funktion den Absturz verursacht. Wiederholen Sie diesen Vorgang, bis Sie die einzelne fehlerhafte Codezeile gefunden haben. In 75 % der Fälle liegt die Lösung dann auf der Hand.

In den anderen 25 % der Situationen ist die sogenannte beleidigende Codezeile ein Ablenkungsmanöver. Es wird auf (ungültige) Bedingungen reagieren, die viele Zeilen zuvor eingerichtet wurden – vielleicht Tausende von Zeilen zuvor. Wenn das der Fall ist, hängt der beste gewählte Kurs von vielen Faktoren ab: vor allem von Ihrem Verständnis des Codes und Ihrer Erfahrung damit:

  • Vielleicht einen Debugger-Watchpoint setzen oder eine Diagnose einfügen printf‘s auf kritische Variablen wird zum Notwendigen führen A ha!
  • Vielleicht liefert das Ändern der Testbedingungen mit unterschiedlichen Eingaben mehr Einblick als das Debuggen.
  • Vielleicht zwingt Sie ein zweites Augenpaar, Ihre Annahmen zu überprüfen oder übersehene Beweise zu sammeln.
  • Manchmal reicht es aus, zum Abendessen zu gehen und über die gesammelten Beweise nachzudenken.

Viel Glück!

  • Steht ein zweites Augenpaar nicht zur Verfügung, haben sich Quietscheenten als Alternative bewährt.

    – Matt

    21. März 2012 um 18:52 Uhr

  • Auch das Abschreiben des Endes eines Puffers ist möglich. Es stürzt möglicherweise nicht ab, wenn Sie das Ende des Puffers abschreiben, aber wenn Sie die Funktion verlassen, stirbt es.

    – phyatt

    23. September 2016 um 20:15 Uhr

  • Kann nützlich sein: GDB: Automatisches Weiterschalten

    – Benutzer202729

    12. Oktober 2018 um 4:22 Uhr

Benutzeravatar von manabear
manaber

Angenommen, der Stapelzeiger ist gültig …

Es kann unmöglich sein, genau zu wissen, wo das SEGV aus dem Backtrace auftritt – ich denke, die ersten beiden Stack-Frames werden vollständig überschrieben. 0xbffff284 scheint eine gültige Adresse zu sein, aber die nächsten beiden sind es nicht. Um sich den Stack genauer anzusehen, können Sie Folgendes versuchen:

gdb$ x/32ga $rsp

oder eine Variante (ersetzen Sie die 32 durch eine andere Zahl). Das druckt eine Anzahl von Wörtern (32), beginnend mit dem Stapelzeiger der Größe Giant (g), formatiert als Adressen (a). Geben Sie „help x“ ein, um weitere Informationen zum Format zu erhalten.

In diesem Fall ist es vielleicht keine schlechte Idee, Ihren Code mit einigen Sentinel-‘printf’s zu instrumentieren.

  • Unglaublich hilfreich, danke – ich hatte einen Stapel, der nur drei Frames zurückging und dann auf „Backtrace gestoppt: vorheriger Frame identisch mit diesem Frame (beschädigter Stapel?)“ klickte; Ich habe so etwas schon einmal im Code in einem CPU-Ausnahmehandler gemacht, konnte mich aber nicht an etwas anderes erinnern als info symbol wie macht man das in gdb.

    – schlanker

    8. März 2013 um 19:05 Uhr

  • FWIW auf 32-Bit-ARM-Geräten: x/256wa $sp =)

    – schlanker

    8. März 2013 um 19:05 Uhr

  • @leander Können Sie mir sagen, was X/256wa ist? Ich brauche es für 64-Bit-ARM. Im Allgemeinen ist es hilfreich, wenn Sie erklären können, was es ist.

    – Sandeep

    17. April 2015 um 12:15 Uhr

  • Laut Antwort ‘x’ = Speicherort untersuchen; es gibt eine Anzahl von ‘w’=Wörtern (in diesem Fall 256) aus und interpretiert sie als ‘a’=Adressen. Weitere Informationen finden Sie im GDB-Handbuch unter sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory .

    – schlanker

    17. April 2015 um 23:18 Uhr

Sehen Sie sich einige Ihrer anderen Register an, um festzustellen, ob in einem der Stapelzeiger zwischengespeichert ist. Von dort aus können Sie möglicherweise einen Stapel abrufen. Wenn dies eingebettet ist, wird der Stapel außerdem häufig an einer ganz bestimmten Adresse definiert. Damit kann man auch mal einen ordentlichen Stack bekommen. Dies alles setzt voraus, dass Ihr Programm, als Sie in den Hyperraum gesprungen sind, unterwegs nicht den ganzen Speicher ausgekotzt hat …

Wenn es sich um eine Stapelüberschreibung handelt, können die Werte durchaus etwas entsprechen, das aus dem Programm erkennbar ist.

Ich habe zum Beispiel gerade den Stack betrachtet

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

und 0x342d ist 13357, was sich als Knoten-ID herausstellte, als ich die Anwendungsprotokolle danach durchsuchte. Das half sofort dabei, Kandidatenseiten einzugrenzen, an denen das Überschreiben des Stapels aufgetreten sein könnte.

Benutzeravatar von user3053087
Benutzer3053087

lustig … wir hatten die genau Dasselbe passiert mit einem Treiber in einer alten C-App hier. Die oberen 2 Stack-Trace-Wertzeiger in Hex waren Datenbytes, die vom Port eingelesen wurden. Ich habe zufällig einen bemerkt, weil er mir bekannt vorkam.

1423050cookie-checkGDB beschädigter Stapelrahmen – Wie debuggt man?

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

Privacy policy