Was soll ich mehr über das Debuggen erfahren? [geschlossen]

8

Zum Debuggen meiner Programme verwende ich hauptsächlich die folgenden Methoden:

  1. Verwenden Sie printf (oder ein Äquivalent in anderen Sprachen), um den Wert einer Variablen nach einer bestimmten Anweisung zu überprüfen oder um zu überprüfen, ob das Programm eine bedingte Anweisung oder eine Schleife eingibt.

  2. Verwenden Sie Watch / Breakpoints, wenn Sie IDEs verwenden.

Ich bin in der Lage, die Probleme mit den oben genannten Methoden zu lösen. Ich habe jedoch festgestellt, dass es zwei Arten von Builds gibt - Debug und Release. Aus dem Namen kann ich verstehen, dass der Debug-Build beim Debuggen hilfreich wäre. Ich habe auch gelesen, dass der Debug-Build so etwas wie Symboltabelleninformationen und so weiter speichert.

Wie kann ich den Debug-Build zum Debuggen verwenden? Gibt es andere Debugging-Techniken, die ich lernen sollte?

Cracker
quelle
Der Unterschied hängt von der Plattform ab. Für Java ist der Unterschied gering. Für C kann es sehr unterschiedlich
2
Auf welchen Sprachen / Plattformen codieren Sie?
DXM

Antworten:

7

Lernen Sie Ihren Debugger

Es ist sehr hilfreich, sich mit dem Debugger vertraut zu machen, egal ob es sich um eine textbasierte, vollständige IDE oder eine Mischung daraus handelt. Sie geben nicht viele Details an, deshalb beschreibe ich den allgemeinen Fall:

1) Haltepunkte

Bei vielen Debuggern können Sie nicht nur an einer Codezeile anhalten, sondern auch festlegen, dass eine Unterbrechung auftreten soll, wenn eine Bedingung auftritt (z. B. "x> 5"), nachdem der Code mehrere Male durchlaufen wurde oder wenn sich der Wert eines Speichers ändert. Dies ist sehr nützlich, um zu verstehen, wie Ihr Code in einen schlechten Zustand gerät, z. B. um zu beobachten, wann ein Zeiger null wird, anstatt den Absturz zu fangen, wenn er dereferenziert wird.

2) Code durchgehen

Sie können zeilenweise entlang des Codes in Funktionen eintreten, über Zeilen springen ('nächste Anweisung setzen') und dann die Funktionen verlassen. Es ist eine wirklich leistungsstarke Methode, um die Codeausführung zu verfolgen und zu überprüfen, ob sie das tut, was Sie denken :-)

3) Ausdrücke auswerten

Sie können also Variablen in eine Überwachungsliste / ein Überwachungsfenster einfügen und sehen, wie sich ihr Wert ändert, wenn Sie einen Haltepunkt erreichen oder Code durchlaufen. In der Regel können Sie aber auch komplexe Ausdrucksauswertungen durchführen, z. B. wird "x + y / 5" ausgewertet. Bei einigen Debuggern können Sie auch Funktionsaufrufe in die Überwachungslisten aufnehmen. Sie können Dinge wie "time ()", "MyFunction (...)", "time ()" ausführen und die Uhrzeit ermitteln, wie lange Ihre Funktion gedauert hat.

4) Ausnahmen und Signalbehandlung

Wenn Ihre Sprache Ausnahmen und / oder Signale unterstützt, können Sie den Debugger normalerweise so konfigurieren, dass er darauf reagiert. Bei einigen Debuggern können Sie an dem Punkt in den Code einbrechen, an dem die Ausnahme auftreten wird, und nicht, nachdem sie nicht abgefangen wurde. Dies ist nützlich, um seltsame Probleme wie "Datei nicht gefunden" -Fehler aufzuspüren, da das Programm als anderes Benutzerkonto ausgeführt wird.

5) Anhängen an einen Prozess / Kern

Manchmal müssen Sie den Debugger verwenden, um auf einen vorhandenen Prozess zu springen, der schief läuft. Wenn Sie Quellcode in der Nähe haben und die Debug-Symbole intakt sind, können Sie eintauchen, als hätten Sie überhaupt erst im Debugger begonnen. Dies ist auch für Core-Dumps ähnlich, außer dass Sie das Debuggen in diesen normalerweise nicht fortsetzen können (der Prozess ist bereits beendet).

Konfiguration erstellen

Es gibt eine Reihe von Build-Variationen, die Sie durch Ein- oder Ausschalten von Funktionen wie Debug-Symbolen, Optimierungen und anderen Compiler-Flags vornehmen können:

1) Debuggen

Traditionell ist dies ein einfacher Build ohne besondere Eigenschaften, der das Debuggen einfach und vorhersehbar macht. Es variiert ein wenig je nach Plattform, es kann jedoch zusätzlichen Spielraum geben, z. B. Zuweisungen und Puffergrößen, um die Zuverlässigkeit zu gewährleisten. Normalerweise ist ein Compilersymbol wie DEBUG oder Conditional ("Debug") vorhanden, sodass debugspezifischer Code eingezogen wird. Dies ist häufig der Build, der mit intakten Symbolen auf Funktionsebene ausgeliefert wird, insbesondere wenn Zuverlässigkeit und / oder Wiederholbarkeit a sind Besorgnis, Sorge.

2) Release / Optimierter Build

Durch das Aktivieren von Compiler-Optimierungen können einige Funktionen zur Codegenerierung auf niedriger Ebene im Compiler basierend auf Annahmen über Ihren Code schnelleren oder kleineren Code erstellen. Die möglichen Geschwindigkeitssteigerungen sind irrelevant, wenn Ihre Algorithmusauswahl schlecht ist. Bei intensiven Berechnungen kann dies jedoch durch Common Subexpression Elimination und Loop Unrolling usw. einen ausreichenden Unterschied bewirken. Manchmal sind die vom Optimierer getroffenen Annahmen nicht mit Ihrem Code und damit dem Optimierer kompatibel muss eine Kerbe runtergekurbelt werden. Compiler-Fehler in optimiertem Code waren in der Vergangenheit ebenfalls ein Problem.

3) Instrumentierter / profilierter Build

Ihr Code wird mit einem bestimmten Instrumentierungscode erstellt, um zu messen, wie oft eine Funktion aufgerufen wird und wie lange sie in dieser Funktion verbracht wird. Dieser vom Compiler generierte Code wird am Ende des Analyseprozesses ausgeschrieben. Manchmal ist es einfacher, dafür ein spezielles Software-Tool zu verwenden - siehe unten. Diese Art von Build wird niemals ausgeliefert.

4) Sicherer / geprüfter Build

Alle 'Sicherheitsventile' werden über Präprozessorsymbole oder Compilereinstellungen aktiviert. Beispielsweise überprüfen ASSERT-Makros Funktionsparameter, Iteratoren prüfen auf nicht geänderte Sammlungen, Kanarienvögel werden in den Stapel gelegt, um Beschädigungen zu erkennen, Heap-Zuordnungen werden mit Sentinel-Werten gefüllt (0xdeadbeef ist ein denkwürdiger Wert), um Heap-Beschädigungen zu erkennen. Für Kunden mit anhaltenden Problemen, die nur auf ihrer Website reproduziert werden können, ist dies eine praktische Sache.

5) Feature-Build

Wenn Sie unterschiedliche Kunden haben, die unterschiedliche Anforderungen an Ihr Softwareprodukt haben, ist es üblich, für jeden Kunden einen Build zu erstellen, der die verschiedenen Teile beim Testen ausführt. Beispielsweise möchte ein Kunde Offline-Funktionen und ein anderer nur Online. Es ist wichtig, beide Möglichkeiten zu testen, wenn der Code unterschiedlich aufgebaut ist.

Protokollierung und Ablaufverfolgung

Es gibt also einige hilfreiche Anweisungen für printf () und anschließend umfassende, strukturierte Trace-Informationen für Datendateien. Diese Informationen können dann abgebaut werden, um das Laufzeitverhalten / die Laufzeitmerkmale Ihrer Software zu verstehen. Wenn Ihr Code nicht abstürzt oder die Reproduktion einige Zeit in Anspruch nimmt, ist es hilfreich, ein Bild von z. B. einer Liste von Threads, deren Statusübergängen, Speicherzuordnungen, Poolgrößen, freiem Speicher, Anzahl der Dateihandles usw. zu haben hängt wirklich von der Größe, Komplexität und Leistungsanforderungen Ihrer Anwendung ab. Als Beispiel möchten Spieleentwickler jedoch sicherstellen, dass die CPU- oder Speicherauslastung während eines laufenden Spiels keine "Spitzen" aufweist, da dies wahrscheinlich die Framerate beeinflusst. Einige dieser Informationen werden vom System verwaltet, andere von Bibliotheken und der Rest vom Code.

Andere Werkzeuge

Es ist nicht immer so, dass Sie einen anderen Build erstellen müssen, um diese Szenarien abzudecken: Einige Aspekte können zur Laufzeit durch Prozesskonfiguration (Windows-Registrierungstricks) ausgewählt werden, wodurch alternative Bibliotheken mit höherer Priorität für die Standardbibliotheken verfügbar gemacht werden, z. B. efence on your Loader-Pfad oder Verwendung eines Software-ICE oder eines speziellen Debuggers, um Ihre Software auf Laufzeitmerkmale zu prüfen (z. B. Intel v-Tune). Einige davon kosten viel Geld, andere sind kostenlose Xcode-Tools.

JBRWilkinson
quelle
6

Ein Debug-Build wird mit Debugging-Symbolen in der ausführbaren Datei erstellt. Grundsätzlich bedeutet dies, dass jede Zeile des Quellcodes mit dem daraus generierten Maschinencode verknüpft ist und alle Namen von Variablen und Funktionen beibehalten werden. In GCC erfolgt dies mit dem -gFlag, wenn Sie Quelldateien kompilieren. Eine andere Sache, die häufig gemacht wird, ist das Deaktivieren der Optimierung. Dies liegt daran, dass der Compiler einige nette Tricks ausführt, die Ihr Programm schneller machen, aber das Debuggen unmöglich machen. In GCC ist dies erledigt mit -O0.

Das nützlichste Tool, das ich jemals zum Debuggen verwendet habe, ist gdb . Es ist eine Textversion der von Ihnen erwähnten IDE-Haltepunkte. Mit gdb können Sie jedoch viel mehr als mit einer IDE. In der Tat sind einige IDEs nur ein Wrapper um gdb, aber einige Funktionen gehen verloren. Sie können Speicherplätze überwachen, Baugruppen drucken, Stapelrahmen drucken, die Signalverarbeitung ändern und vieles mehr. Wenn Sie das Debuggen ernst nehmen, würde ich gdb oder ein gleichwertiges textbasiertes Debugging-Programm lernen.

Eine andere Sache, die ich oft nützlich finde, ist Valgrind . Es findet Speicherlecks und verfolgt, ob der Speicher initialisiert ist oder nicht. Sie verwenden es mit Ihrem Debug-Build, weil Sie dann Zeilennummern erhalten, in denen interessante Dinge passieren.

Jarryd
quelle
1
"Ja wirklich?" Jedes Mal , das ich je hatte verwenden GDB ich es ein wichtiger Schritt finden rückwärts von der Leistung und Benutzerfreundlichkeit ich mit einem guten IDE-basierten Debugger an den Fingerspitzen haben. Zumal GDB ein sehr allgemeines Tool ist, während ein integrierter Debugger die Regeln der Sprache kennen kann, für die er entwickelt wurde, um Ihnen spezifischeres und relevanteres Feedback zu geben.
Mason Wheeler
1
GDB ist sehr stark für C und C ++ konzipiert. Ich habe IDE-Debugger eine ganze Weile benutzt und sie haben mich ohne Ende frustriert. Ich finde, dass ich so viel mehr Kontrolle darüber habe, was ich mache, wenn ich GDB benutze.
Jarryd
2
@MasonWheeler, gdbist skriptfähig, IDEs normalerweise nicht. Ich brauche keine "Kraft" an meinen Fingerspitzen, wenn ein Computer die ganze Arbeit für mich erledigen kann, während ich an meinem Tee nippe.
SK-Logik
3
@MasonWheeler, nein, du machst etwas falsch, wenn du "step-inspect-step-inspect" wie verrückt drückst . Es ist so viel einfacher, ein winziges Skript zu schreiben, um Ihre aktuelle Hypothese über eine Ursache eines Problems zu überprüfen, es auszuführen (fast so, wie Sie die Funktionstests ausführen) und die Ergebnisse zu analysieren und gegebenenfalls zu wiederholen. Alle Formen eines interaktiven Debuggens sind fast immer nahezu nutzlos, selbst wenn es sich um ein Post-Mortem eines Core-Dumps handelt. Ein typisches Debugging-Skript würde mehrere Haltepunkte einrichten, die Werte an den Haltepunkten ausführen, überprüfen und gut formatieren.
SK-Logik
1
@Mason Wheeler, eine einfache Stapelverfolgung ( btin gdb) würde Ihnen in den meisten Fällen mehr als genug Informationen für eine 0-Hypothese liefern (wenn dies nicht der Fall ist, liegt Ihr Code weit unter dem unter dem Standard liegenden Qualitätsschwellenwert). . Ich benutze Debugger seit VMS, habe alle möglichen Geschmacksrichtungen ausprobiert, einschließlich einiger extrem exotischer Bestien wie zeitaufwändige Debugger, konnte aber bisher keine wirklich nützliche finden. Sie testen Ihre Annahmen interaktiv - dann verschwenden Sie Ihre Zeit. Ich teste meine Annahmen im Batch (könnte viele parallel sein), schnell und mit sehr geringem Aufwand.
SK-Logik
3

Es gibt eine äußerst leistungsfähige Debugging-Technik, die in den anderen Antworten noch nicht erwähnt wurde. Es ist für einige Entwicklungsumgebungen kostenlos erhältlich oder kann mit relativ geringem Aufwand zu fast allem anderen hinzugefügt werden.

Dieses Ding ist eine eingebettete REPL, die es vorzugsweise ermöglicht, jederzeit eine Verbindung über einen Socket herzustellen, in einem dedizierten Thread zu laufen und alle möglichen Formen der Reflexion für den laufenden Code zu verwenden, den laufenden Code zu ändern oder vollständig zu ersetzen, neue Dinge hinzuzufügen und auszuführen Funktionen usw.

Sie haben es sofort einsatzbereit, wenn Sie beispielsweise Common Lisp, Smalltalk, Python, Ruby usw. codieren. Es ist möglich, einen leichtgewichtigen Interpreter (z. B. Lua, Guile, JavaScript oder Python) in eine native Anwendung einzubetten . Für JVM- oder .NET-basierte Umgebungen stehen viele einbettbare Compiler und Interpreter zur Verfügung, und eine ziemlich leistungsstarke Reflexion ist kostenlos verfügbar.

Dieser Ansatz ist viel effizienter als die interaktiven / iterativen Debugger (wie GDB, Visual Studio-Debugger usw.) und sollte für die besten Ergebnisse in Verbindung mit einer geeigneten Protokollierungsfunktion und ordnungsgemäß platzierten Zusicherungen verwendet werden.

SK-Logik
quelle
Wird jedoch nicht in Verbindung mit einem Hacker verwendet, der auf Ihr Netzwerk zugreift.
Stellen
@gbjbaanb, dafür wird SSL verwendet. Lesen Sie die ViawebGeschichte - sie haben viel von einem solchen Ansatz profitiert und das laufende Produktionssystem debuggen können.
SK-Logik
2

Das wichtigste Debug-Tool, das ich jemals verwendet habe, ist der Post-Mortem-Crash-Dump (Core-Dump unter Linux, Benutzer-dmp unter Windows).

Es ist ein sehr komplexes Thema, daher hier ein Link :

Grundsätzlich (unter Windows, der Plattform, mit der ich am meisten Erfahrung mit Post-Mortem-Debugging habe) erstellen Sie Ihre Apps mit Symbolen, die in einer separaten Datei (einer PDF-Datei) gespeichert sind. Sie bewahren diese sicher auf (damit niemand Ihren Code problemlos zurückentwickeln kann) und warten auf einen Absturz. Wenn dies der Fall ist (und DrWatson oder ähnliches ausgeführt wird, um den Absturz zu erfassen und die Speicherauszugsdatei zu generieren ), laden Sie die .dmp-Datei zusammen mit den Symbolen (und optional einem Pfad zum Quellcode) und in WinDbg (Windows-Debugger) Es zeigt Ihnen viele Informationen wie Aufrufstapel, Register, Variablen, Speicherwerte usw. Es ist schön, wenn es funktioniert.

Bei einem Debug-Build wird dies alles so eingerichtet, dass es automatisch erfolgt. Sie müssen Symbole aktivieren, wenn Sie eine Version erstellen. Debug-Builds fügen auch andere Dinge wie Speicherwächter hinzu (diese Ausnahmen lösen aus, wenn Sie versuchen, in sie zu schreiben. Dies zeigt sehr leicht Pufferüberläufe oder Speicherbeschädigungen an oder behauptet, dass Dinge nicht ganz richtig sind). Im Allgemeinen müssen Entwickler bei Debug-Builds ihren Code ausführen und testen, bevor sie ihn weitergeben.

Dies ist für das Debuggen von nativem Code vorgesehen. .NET-Code ist ein PiTA für Post-Mortem-Inhalte wie diese, aber manchmal können Sie .NET-Ausnahmebedingungen durch Laden von sos entfernen .

Alles komplexe Sachen, aber es ist nicht so schlimm. Erwarten Sie jedoch keine nette, spitzklickende Benutzeroberfläche, dies ist die Lieblichkeit der Befehlszeilenleistung.

gbjbaanb
quelle
2

Verwenden Sie printf (oder ein Äquivalent in anderen Sprachen), um den Wert einer Variablen nach einer bestimmten Anweisung zu überprüfen oder um zu überprüfen, ob das Programm eine bedingte Anweisung oder eine Schleife eingibt.

Sie sollten auch jede Art von Assert-Anweisung verwenden, die angeboten wird.

Sie sollten auch Unit-Tests schreiben.

Ich bin in der Lage, die Probleme mit den oben genannten Methoden zu lösen.

Perfekt. Sie wissen alles, was Sie wissen müssen.

Gibt es andere Debugging-Techniken, die ich lernen sollte?

Nein, nicht dass du lernen solltest . Sie können mehr lernen, wenn Sie denken, dass es helfen wird. Aber du brauchst nicht mehr als das, was du hast.

In den letzten mehr als 30 Jahren habe ich nur einige Male (vielleicht drei oder vier) einen Debugger verwendet. Und dann habe ich es nur verwendet, um Post-Mortem-Crash-Dumps zu lesen, um den fehlgeschlagenen Funktionsaufruf zu finden.

Die Verwendung des Debuggers ist keine wesentliche Fähigkeit. Die print-Anweisung ist ausreichend.

S.Lott
quelle
0

Hier ist eine kurze Liste der Techniken:

  • Statische Anrufdiagramme
  • Dynamische Anrufdiagramme
  • Hotspot-Profilerstellung
  • Thread Messaging beobachten
  • Debugging / umgekehrte Ausführung wiederholen
  • Überwachungspunkte
  • Tracepoints

Sie können auch benutzerdefinierte Verhaltensweisen mit Tools im AOP-Stil implementieren und mit einem guten statischen Analysetool einen langen Weg gehen.

Paul Nathan
quelle
0

Erfahren Sie alles über Ihr Debugging-Tool.

Sie verbergen oft wirklich leistungsstarke Funktionen, die Ihnen helfen können, besser zu verstehen, was passiert. (insbesondere der C ++ - Debugger von Visual Studio)

Klaim
quelle