Debugging
Debugging
Basics
- "entwanzen"
- kommt daher, dass die ersten Computer Relais verwendeten. Als eine Wanze ein Relais blockierte, musste man den Computer entwanzen. Deshalb der Name
- Heute spricht man von einem Bug, wenn ein Programm-Fehler vorliegt
- Debugging = Fehlersuche- und Behebung
Kosequenzen von Bugs
- Compiler gibt Hinweise auf syntaktische/semantische Fehler
- Programm hält mit Laufzeit Fehler (Run-time Error)
- Programm hält nie an
- Programm läuft vollständig, aber gibt inkorrekte Resultate
- Programm läuft vollständig, aber gibt manchmal(?) inkorrekte Resultate
- Debugging ist Notwendig
- , wenn der Code nicht Compiliert
- , wenn Software "sich anders verhält als erwartet"
- während der Entwicklung
- Code sollte Stück für Stück entwickelt, getestet und debuggt werden
1. Fehlerlokalisierung
- Bug: Codesegment mit nicht beabsichtigten Aktionen.
- Man muss nachvollziehen:
- Was das Programm machen sollte
- Was das Programm wirklich macht
- Problem: Zu viel Informationen!
- Lösung: Einkreisen des Problems!
mögliche falsche annahmen
- Binärcode entspricht Quellcode
- Code, der eine Funktion aufruft, bekommt niemals unerwartete Argumente
- Library-Funktionen funktionieren immer ohne Fehler
- malloc wird immer den angeforderten Speicher geben
- Systembibliotheken und -tools sind fehlerfrei
Suchstrategie
- man sollte zuerst bei besonders verdächtigen Datensturkturen nachschauen
- nach initialisierung
- Nach der ersten, zweiten, mittleren Iteration einer Schleife
- Nach einem Tastendruck, der ein interaktives Programm zum Absturz bringt
- Nach dem Punkt, an dem das Programm die letzte korrekte Ausgabe geliefert hat
- Am Ende von Programm
2. Verstehen des Fehlers
- Was ist der Zustand des Programms vor Ausführung von fehlerhaften Code?
- Was ist der Zustand nach Ausführung des Codes?
- Was ist der erwartete Zustand?
Was
Namen und Werte aller aktiven Variablen
- mit
printf()kann man sich alle "suspekten" Variablen ausgeben lassen
Debugger
Kernkonzept
- ein Debugger kann Programm an einem bestimmten Punkt unterbrechen
- kann bei Unterbrechung den Zustand des Programms anzeigen
- ein Debugger ermöglicht, den Zustand von Programmen, die durch Laufzeitfehler abgestürzt sind, anzuzeigen
- Führt oft leichter zu dem Punkt, wo der Fehler aufgetreten ist
Gesetze des Debuggen
(Zoltan Somogyi, Melbourne University)
- Before you can fix it, you must be able to break it (consistently)
- Nicht reproduzierbare Bugs ... Heisenbugs ... sind schwierig
- If you can‘t find a bug where you‘re looking, you‘re looking in the wrong place
- Eine Pause machen und später weitermachen, ist im Allgemeinen eine gute Idee
- It takes two people to find a subtle bug, but only one of them needs to know the program
- Die zweite Person stellt Fragen, um die Annahmen des Debuggers in Frage zu stellen
Workflow
- wenn man Idee hat, wo der Bug ist
- breakpoint kurz vorher setzen
- Ab dann in Einzelschritten fortfahren
- Nun sollte man sehen, welche Variablen einen unerwarteten Wert annehmen
- wenn man weiß, welche Variable den falschen Wert hat, muss man nach dem Warum fragen
- zwei Möglichkeiten
- Bei der Zuweisung von der Variable ist das Statement falsch
- Werte anderer Variablen in diesem statement sind falsch
- zwei Möglichkeiten
- man kann auch eine Hypothese aufstellen:
Kommandozeilen-basierte Debugger
- erlauben kontrollierte Programmausführung
- ermögliche Anzeige des Zustands des Programmes
- ermöglichen Änderung von Variablen
gdbundlldbsind Debugger für C
gdb
- kompilieren mit
-g - Aufruf ohne core files:
-gdb prog
- Aufruf mit corefiles
-gdb prog core
Befehle
quit– verlässt gdbhelp [CMD]– on-line Hilfe für Kommando CMDrun ARGS– Ausführen des Programms mit Argumenten ARGS- z.B. aus dem Befehl ./prog arg1 arg2 wird im Debugger run arg1 arg2
break [PROC|LINE]– Setzen eines Haltepunks (breakpoint). Wenn das Programm die Funktion PROC (oder die Linie LINE) erhält, wird die Ausführung des Programms unterbrochen und die Kontrolle an gdb übergebennext– single step (over procedures): Ausführen des nächsten Statements. Falls das Statement ein Funktionsaufruf ist, ausführen des gesamten Funktionskörpersstep– single step (into procedures): Ausführen des nächsten Statements. Falls das Statement ein Funktionsaufruf ist, halte beim ersten Statement in der Funktionbt/backtrace– gibt Aufrufkette (Stack-trace) aus- Mit core dump: Finden, welche Funktion das Programm ausgeführt hat, als es abgestürzt ist
- Bei Unterbrechung: Ausgabe der Aufrufkette
up [N]– Wechseln des Kontexts eine Ebene höher im Stack; ändert den Rahmen (Scope) einer bestimmten Funktion im Stackdown [N]– Wechseln des Kontexts eine Ebene niedriger im Stacklist [LINE/PROC]– Anzeigen des Programmcodes; zeigt 5 Zeilen Code vor und nach dem momentanen Statementprint EXPR– Zeigt die Werte der Expression EXPR
Fehler Speicherverwaltung
Typische Speicherfehler
- Memory Leaks
- Uninitialisierter Speicher
- Use after free
- Das tool valgrind kann Speicherfehler auffinden
Memory Leaks
- Speicher wird angefordert, aber nicht freigegeben
- Fatal bei lang laufenden Prozessen
- valgrind:
--leak-check=full
Uninitialisierter Speicher
- Speicher wird gelesen, ohne dass vorher geschrieben wurde
- Fehler treten oft unerwartet auf, unabhängig vom Speicherinhalt
- Kann Informationen preisgeben (z.B. Crypto-Schlüssel)
- valgrind:
--undef-value-errores=yes--malloc-fill=0xfe
Use after free
- Speicher wird gelesen, nachdem er freigegeben wurde
- Fehler treten dann auf, wenn der Speicher neu vergeben wird
- valgrind:
track-origins=yes