Das vorgestellte Bild dieses Beitrags ist ein Comic aus xkcd.com.

Der obige xkcd-Comic mit dem Titel Debugger spielt auf das Problem an, dass man bei der Anwendung einer bestimmten Methode auf sich selbst möglicherweise nicht das bekommt, wonach man gefragt hat. Turings Halteproblem ist ein bekanntes Beispiel dafür: Man kann algorithmisch nicht entscheiden, ob ein Algorithmus auf einer gegebenen Eingabe terminiert. Haben wir dieses Problem auch bei Debuggern? Insbesondere habe ich mich gefragt, ob es sinnvoll ist, den Hardware-Debugger, den ich entwickle, mit sich selbst zu debuggen.

Generelle Einwände

Das Hauptargument gegen die Verwendung eines Debuggers zum Debuggen seiner selbst ist, dass unter Umständen der zu identifizierende Bug gerade verhindert, dass dieser Bug gefunden wird. Und tatsächlich könnte dies der Fall sein.

Auf der anderen Seite debuggen wir natürlich Programme mit GDB, obwohl wir wissen, dass der von uns verwendete Debugger nicht fehlerfrei ist. Wenn man sich beispielsweise die Liste der Fehler im GDB-Debugger anschaut, findet man ungefähr 3000 Einträge. Glücklicherweise sind fast alle dieser Einträge vermutlich irrelevant für den speziellen Fall, sodass man GDB trotzdem verwenden kann. Und natürlich kann man GDB auch mit sich selbst debuggen. Wenn man so etwas macht, sollte man sich jedoch der Grenzen bewusst sein.

Grenzen bei der Anwendung eines Hardwaredebuggers auf sich selbst

In meinem Fall muss man bei der Entwicklung des Hardware-Debuggers dw-link nur einen kleinen Teil des gesamten Debugging-Systems debuggen. Dennoch sollte man sich bei der Anwendung von dw-link auf sich selbst der Grenzen bewusst sein, die damit verbunden sind.

Zunächst muss man sich sicher sein, dass man der Grundfunktionalität des Hardware-Debuggers vertrauen kann. Dies bedeutet, dass man in den frühen Stadien der Entwicklung des Debuggers andere Techniken anwendet.

Insbesondere ist es nicht möglich, zeitkritische Teile des Programms mit dem Debugger zu debuggen. Das gilt beispielsweise für die unterste Ebene des debugWIRE-Protokolls, die asynchrone serielle Eindraht-Kommunikation über die RESET-Leitung. Debuggen auf dieser Ebene kann nur mit Methoden durchgeführt werden, die das Timing-Verhalten nicht (oder nur minimal) ändern. Ein Logikanalysator ist hier eine große Hilfe, wie ich in der Beschreibung der Entwicklung der SingleWireSerial-Bibliothek gezeigt habe. Ein ähnlicher Kommentar gilt für die nächsthöhere Ebene, den Austausch von Befehlen und Daten mit dem Ziel, um den Speicher und die Register des Ziels zu lesen und zu schreiben. Der Logikanalysator ist auch hier das Werkzeug der Wahl.

Sobald diese Teile zuverlässig genug funktionieren, kann man natürlich eine andere dw-link Instanz verwenden, um dw-link zu debuggen. Man kann sogar in zeitkritischen Teilen anhalten und den Maschinenzustand überprüfen. Man muss sich jedoch bewusst sein, dass in diesem Fall ein Neustart erforderlich sein kann. Es gibt nur noch ein Problem, das man beachten muss, nämlich wie man die beiden Instanzen von dw-link startet.

Starten einer Debugsitzung auf Objekt- und Metaebene

Wenn eine serielle Verbindung zum Hardware-Debugger dw-link hergestellt wird (wenn der GDB-Befehl target remote ausgeführt wird), dann werden dw-link oder der Target-Chip oder beide zurückgesetzt. Wenn wir nun zwei GDB-Instanzen mit zwei Instanzen des Hardwaredebuggers verbinden, müssen wir die richtige Reihenfolge auswählen, um das gesamte System zu starten.

Bevor wir uns mit diesem Problem befassen, will ich das allgemeine Setup visualisieren.

Da wir die dw-link auf der Objektebene debuggen möchten, müssen wir sicherstellen, dass die RESET-Leitung keine kapazitive Last hat. Falls es sich um ein Arduino-Board handelt, muss man die RESET EN Lötbrücke durchtrennen. Dies bedeutet, dass beim Anschließen des Boards kein RESET-Impuls ausgelöst wird.

Was passiert, wenn man zuerst GDB und dw-link auf Objektebene startet? Wenn dw-link auf der Metaebene versucht, eine debugWIRE-Verbindung zu dw-link auf Objektebene herzustellen, wird dw-link auf Objektebene zurückgesetzt. Aus diesem Grund verliert dw-link auf Objektebene die debugWIRE-Verbindung zum Target-Chip und die GDB-RSP-Verbindung zum Objektebenen-GDB. Diese Vorgehensweise funktioniert also überhaupt nicht.

Also beginnen wir mit dem Meta-Level-Debugger. Wenn dw-link auf der Metaebene gestartet wird, wird die debugWIRE-Verbindung zu Objektebenen-Uno-Board hergestellt und das Board wird zurückgesetzt. Wenn man nun dw-link auf Objektebene mit einem Debugger-Befehl auf der Metaebene startet, kann der Debugger auf Objektebene eine Verbindung zu ihm herstellen, und der dw-link auf Objektebene kann eine debugWIRE-Verbindung zum Target-Chip herstellen. Das heißt, wir sind im Geschäft.

Ich habe es ausprobiert, und es funktioniert in der Tat!

Die Interaktion mit den zwei GDB Instanzen ist jedoch etwas verwirrend. Und man muss aufpassen, dass man nicht an einem Haltepunkt setzt, der mitten in einer laufenden Kommunikation liegt. Ich bin noch nicht so weit gekommen, dass ich die gesamte Maschinerie starte, um einen Fehler in der Firmware zu suchen. Das Prozedere ist jedoch definitiv weniger umständlich als das Verteilen von Print-Anweisungen im gesamten Code.

Views: 8