Inhaltsverzeichnis

Debugging und Bugfixing - Tipps für die Fehlersuche

Allgemein

Um die Fehlerursachen zu finden, empfiehlt sich folgendes, generisches Vorgehen:

1. Verstehen
1.1 Definieren des “Gut-Falls”: Wie wäre zu erkennen, dass das Programm korrekt läuft?

1.2 Versuchen Sie genau zu verstehen, unter welchen Umständen der fehlerhafte Zustand auftritt.
Wie ist dieser zu reproduzieren? Ohne Reproduzierbarkeit ist ein Fehler nicht auffindbar!

  • Welche Eingaben, Spannungen waren vorhanden?
  • Gleicher Fehler bei geänderten Eingaben?
  • Prüfen Sie auch vermeintlich klare Dinge

Beim Verständnisaufbau hilft manchmal weniger Theorie und mehr ausprobieren.

Wichtig: Schreiben Sie auf, was funktioniert und was nicht (am besten mit Ablegen des verwendeten Codes). Dann lässt sich auch nach einigen Tests nachvollziehen, was womit getan wurde bzw. nicht funktionierte.

Und: Ein Fehler der auf mysteriöse Weise wieder verschwindet, kommt genauso wieder (i.d.R. dann, wenn keine Zeit mehr da ist). Ein Fehler der nicht korrigiert wurde, wurde nicht korrigiert..

1.3 War die Teilfunktion schon jemals lauffähig?
Falls die Funktion an anderer Stelle bereits lauffähig war, dann Ja.
1.3.1 Ja, Teilfunktion war schon lauffähig
Gehe zu 2.
1.3.2 Nein, Teilfunktion war noch nie lauffähig
1.3.2.1 Suchen
Gab es weltweit noch keinen, der diese Teilfunktion implementiert hat? Um das zu beantworten hilft ein Blick in Google.

Lernen Sie besser_suchen_mit_google

Falls es jemanden gab, so war die Funktion an anderer Stelle bereits lauffähig --> Gehe zu 2. .

1.3.2.2 Reduzieren
Ähnlich Punkt 2. und 3. ist es zielführend Das System soweit zu vereinfachen, dass es lauffähig ist, auch wenn die Funktion darunter leidet.

D.h. im Extremfall das Flashen mit einer leeren main Funktion (Geht das Flashen?) oder mit einer, welche nur einen PIN / LED aktiviert.

1.3.2.3 Kreativ sein
Falls die Teilfunktion noch nirgends implementiert wurde (wirklich?), dann muss etwas mehr Kreativität genutzt werden.
2. Vermuten
Erstelle eine Hypothese an was der Fehler liegen kann. Dann ist das (fehlerhafte) System leichter über „Teile und Herrsche“ zu bändigen.
3. Aufteilen
Breche das System (Hardware und Softwarekomponenten) und die Interaktionen (Schnittstellen, Funktionsaufrufe) auf ein Minimum herunter.

Alternativ: Breche das System in Teile und prüfe ob die Einzelteile fehlerfrei sind.

Falls es schon einen Teil des Programms gab, so sollte dieser wieder hergestellt werden. Die Änderungen sollten dann im folgenden Schritt Zeile für Zeile (bzw. Funktionsblock für Funktionsblock) eingefügt und auf der Hardware auf getestet werden.

4. Ersetzen
Ersetze Systeme (Hardware und Softwarekomponenten) und die Interaktionen (Schnittstellen, Funktionsaufrufe) durch einfachere.
  1. Vereinfache die Software
    1. Kompiliert der Beispiel-Code aus der Vorlesung?
      Kompiliert das gewünschte C-File, wenn alles (includes, variablen, functions), außer main() ausgeblendet wird?
    2. Kompiliert eine einfache Software, welche eine (wechselnde) Ausgabe beinhaltet (z.B. LED, Port)?
      Besser einfach beginnen, also nicht mit einem über SPI angeschlossendem Display.
  2. Vereinfache die Hardware
    1. Nutze Oszilloskop und Logic Analyzer statt ausgebende Komponenten.
    2. Nutze Funktionsgenerator statt eingebende Komponenten.
    3. Nutze bereits getestete Komponenten.
  3. Vereinfache die Interaktion
5. Beobachten und zurück zu Start
Tritt der Fehler exakt gleich auf?
Welche neuen Hypothesen lassen sich aufstellen?

Häufig ist es von Vorteil eine Ausgabemöglichkeit zu schaffen.Zur Ausgabe bietet sich z.B. ein unbenutzter PIN oder - falls schon vorhanden und in Software ansprechbar - ein Display an.

  1. Die Ausgabemöglichkeit kann als genutzt werden, um den Programmablauf zu überprüfen. z.B. kann die Ausgabe eines Buchstabens auf dem Display als Zeile eingefügt und so das Erreichen dieser Zeile überprüft werden. Beispiel
  2. Ähnliches geht auch beim Sprung in ein Unterprogramm. Wird dieses aber mehrmals in der Hauptschleife aufgerufen, kann es sich anbieten eine Hilfsvariable einzufügen. Damit ist es möglich nur den Sprung beim vermuteten Fehlverhalten zu betrachten: Beispiel
  3. Falls sich das Unterprogramm in einer weiteren Datei (z.B. eingebundene Library) befindet, so muss die Hilfsvariable übergeben werden. Temporär ist dafür die Definition/Deklaration einer externen Variable sinnvoll. Beispiel: Es wurde die Datei ADC.h inkludiert. Aus main() wird setADCgain() aufgerufen. In dieser Funktion wird ein Fehler erwartet. Sinnvoll ist es nun die Variable dummy in ADC.h zu deklarieren (extern int dummy;) und in main.c zu definieren (int dummy=0;). Dann kann die Variable in ADC.C auch ohne weitere Definition/Deklaration verwendet werden. Näheres dazu auch auf Wikipedia. Beispiel
6. Bugfixen
Sobald der Bug eingekreist wurde, geht es ans korrigieren. Hier hilft meist die Theorie weiter.

Wichtig: Prüfen Sie, ob der Fehler noch auftritt. Ist der Fehler auch bei Variation der Eingaben weg?

Software

Hardware

Häufige Fehler

I2C

Tipps für die Fehlerkorrektur (Bugfixing)