Was soll dieses Tutorial?                                                                         

Was ist ein Debugger?

Voraussetzungen für DirectX

Vor-und Nachteile beim DirectX-Debugging

Die wichtigsten Tasten

Starten im Debugmodus

Der Bildschirm des Debugmodus

Eigene Ausgaben auf dem Debugger

Wie man DirectX austrickst

Der Disassembler

 

 

Was soll dieses Tutorial?

 

Nun da ich merken musste, dass relativ wenige Leute, die mit DirectX programmieren einen Debugger einsetzen und folglich auch schnell an recht “simplen“ Problemen scheitern, die sich mit dem Einsatz eines Debuggers lösen lassen, will ich eine kurze Beschreibung liefern, wie man sich das Programmiererleben mit dem Einsatz eines Debuggers versüßen lässt. Darum auch der Name Debug – bug für Fehler und De dafür, daß man selbigen an den Kragen will. Für Einsteiger kann diese Beschreibung recht nützlich sein, für Profis natürlich eher belächelnswert, denn ich kratze bei meiner täglichen Programmiererei auch nur an der Oberflächer der Materie, aber meist reicht das schon aus.

 

Was ist ein Debugger?

 

Ein Debugger ist ein Werkzeug mit dem man zur Laufzeit eines Programmes dessen Umgebung anzeigen lassen kann. Mit Umgebung meine ich hier Variableninhalte, Prozessorregisterinhalte, Speicherauszüge, die Disassamblierung des Programmcodes und Debugmeldungen des Programmes.

Außerdem kann man mit einem Debugger ein Programm im Einzelschrittmodus ablaufen lassen und somit nach jeder Anweisung kontrollieren, ob diese das tut, was man von ihr erwartet.

Weiterhin ist zu erwähnen, dass man einen Debugger auch mit einem laufenden Prozess verbinden kann und diesen dann ebenfalls genauer unter die Lupe nehmen kann.

 

 

Voraussetzungen für DirectX

 

Beim Installieren des SDK müssen die Debug-LIB-Dateien mit installiert werden, so dass man später in der Systemsteuerung von DirectX zwischen der Retail- und der Debug-LIB-Version umschalten kann. Die Retailversion wird zum abschließenden Compilieren des Projektes aktiviert, weil sie etwas schneller und kleiner ist. Auch wen zur Entwicklungszeit der Performanceunterschied zur Debugversion interessiert, kann hier immer mal hin und her schalten.

 

 

 

Da ich mit Visual Studio 6 arbeite beziehen sich viele Erklärungen und Bilder auf diese Entwicklungsumgebung. Die Grundfunktionalität findet man aber bei den meisten Debuggern wieder.

Wichtig: um eine Debug-Version seines Projektes zu erstellen muß man im Menu Erstellen->Aktive Konfiguration festlegen WIN32 Debug wählen! Wird dies vergessen, landet man nach F10 / F11 im Disassambliermodus in dem das Programm als Maschinencode(CPU-Befehle) angezeigt wird, und das ist recht selten eine Hilfe.

 

 

Vor-und Nachteile beim DirectX-Debugging

 

Da ein Debugger sich in ziemlich viel Sachen einmischt, die recht kritisch sind und sowieso sehr nah am System arbeitet, man denke nur an die Speicher-und Prozessorregisterauszüge, kann man es ihm kaum übel nehmen, daß er manchmal bockt. Gerade mit DirectX habe ich die Erfahrung gemacht, daß normale Funktionen wie das Abarbeiten bis zum Cursor oder das Anhalten an Haltepunkten nicht gerade oft funktioniert. Auch kritische Bereiche eines Programmes sind nicht gerade die Wohlfühlumgebung eines Debuggers. Hierzu zählen besonders critical sections, also Programmteile, die auch beim Normalbetrieb eines Programms nicht schludern sollten wie z.B. die Stellen zwischen Lock und Unlock. Außerdem habe ich bei DirectX verstärkt den Effekt, daß sich eine selbstgeschriebene Funktion mit DirectX-Aufrufen im Einzelschrittmodus abarbeiten lässt, aber beim Komplettaufruf der Funktion der Debugger nicht wieder so richtig hochkommt.

Neben den Vorteilen, die ein Debugger naturgemäß mit sich bringt, gibt es noch einen anderen erfreulichen Nebeneffekt. Es soll ja “gelegentlich“ vorkomen, daß ein sorfältig und korrekt erstelltes Programm loopt, hängenbleibt, Grafikmüll auf den Screen wirft und andere Dinge tut, die auf eine nicht ganz den Erwartungen entsprechende Arbeitsweise schließen lassen. Wenn man dann noch mit Alt+TAB zum Visual Studio wechseln kann und energisch, d.h. oft, Shift+F5 drückt, hat man gute Chancen den Rechner vor dem Neubooten zu bewahren, weil eben unser Debugger noch über unserem Programm thront und den Programmprozess abschießen darf. Diese Chancen werden aber durch den Einsatz von WIN95/98 etwas gemindert. Ich benutze WIN2000 und habe bis jetzt meinen Rechner noch nicht einmal wegen eines loopenden Programmes von mir booten müssen und ich habe schon schöne Zeigerverbiegungen und Endlosschleifen zusammengetippt.

 

Starten im Debugmodus

 

Im Menu Erstellen findet man den Punkt Debugger starten, braucht man aber eigentlich nicht weil hier nicht mal alle Möglichkeiten zu finden sind. Am Besten man merkt sich gleich die wichtigsten Tasten:

            F10                 Im Einzelschritt abarbeiten, startet auch das Programm wie F5, bleibt aber an der 1. Anweisung stehen

                                   Bei Funktionen springt F10 nicht in den Funktionscode.

            F11                 Wie F10 mit dem Unterschied, daß hier bei einer Funktion auch in die Funktion gesprungen wird.

            Shift+F11        In einer Funktion bis zu ihrem Ende springen.

            Ctrl+F10         Programm bis zum Cursor abarbeiten. Klappt aber selten wenn DirectX ins Spiel kommt.

            Shift+F5          (Zur Laufzeit) Beenden des Programmes und Debugmodus (siehe Vor-und Nachteile bei Vorteile).

            F9                   Setzen von Haltepunkten.

 

Nach dem Starten mit F10 oder F11, der Pfeil zeigt die nächste zu verarbeitende Anweisung an.

 

Den Inhalt initialisierter Variablen kann man sich anzeigen lassen indem man mit dem Cursor auf den Variablennamen zeigt, bei Arrays wird noch die Adresse angezeigt. Zu bemerken sei hier noch, daß der Debugger nur die Zeilen anspringt, die auch beim Compilieren und Linken zu ausführbarem Code geworden sind. So wurde die Zeile mit der Initialisierung von chKlassenname beim 2. Druck auf F10 angesprungen und erst danach wurde mit dem Cursor über dieser Variablen ihr Inhalt angezeigt.

 

 

Der Bildschirm des Debugmodus

 

Leute mit schlechten Augen können sich auch den Screenshot mit 1024*768 (34kb) ansehen indem sie in das Bild klicken.

1    Quellcodebereich, hier können über ein Kontextmenu, das mit Rechtsklick auf eine Variable oder Zeile aufgerufen wird, Informationen zu Variablen abgerufen werden. Außerdem kann man hier die nächste Anweisung festlegen, so kann eine Zeile immer wieder abgearbeitet oder auch übersprungen werden. Netterweise lässt sich auch der Quellcode wärend der Laufzeit ändern, dazu wird vor dem Abarbeiten der nächsten Anweisung neu compiliert.


 

2        Automatische Variablenanzeige. Hier werden die Variablen mit Inhalt eingeblendet, die in der aktuellen Zeile im Quellcode vorkommen.

 

3        Schnellüberwachung (benutzerdefinierte Variablenanzeige). Hier kann man durch Drag&Drop von 2, durch Eintippen des Variablennamens oder durch das Kontextmenü oben die Variablen zusammenstellen, deren Inhalt ständig angezeigt werden soll.

 

4        Debugger-Output. Hier schreibt DirectX und auch das eigene Programm Meldungen hin. Diese Liste ist auch nach Programmende noch vorhanden und wird erst beim nächsten Programmstart zurückgesetzt.

 

5        Schaltflächen zum Steuern des Debuggers (auch über Tasten möglich) und zur Auswahl der Anzeigen z.B. Register-oder Speicherauszug.

 

 

Bei Arrays und Strukturen wird ein Kästchen mit einem Plus eingeblendet (siehe 2) mit dem man den Inhalt der Felder bzw. Strukturelemente im einzelnen (siehe 3) aufklappen kann. Hier kann man einfach kontrollieren, ob die ZeroMemory-Funktion den richtigen Speicherbereich plattmacht, die Variablen auch wirklich so verändert werden wie es gewollt ist, Parameter richtig zurückgegeben werden und vieles mehr.

 

 

Eigene Ausgaben auf dem Debugger

 

Normalerweise sorgt unsere Anwendung durch die Benutzung der Debug-Version von DirectX schon für reichlich Lesestoff auf der Ausgabe. Besonders wenn man den Debuglevel in der Systemsteuerung auf extra gesprächig stellt. Mit dem Befehl OutputDebugString(TCHAR *msg) kann auch eine eigene Meldung dort angezeigt werden. Z.B könnte man immer wenn ein 3D-Objekt mit einem anderen kollidiert durch OutputDebugString(“BANG“); Meldung ausgeben, was mit einer MessageBox ja schön umständlich wäre. Fortschrittlicher wäre dann noch die Ausgabe welche Objekte kollidierten:

char msg[200];

wsprintf(msg,“Objekt %s mit Objekt %s kollidiert\n”, CrashTest.ObjName1, CrashTest.ObjName2);

OutputDebugString(msg);

 

Könnte auf der Ausgabe des Debuggers zu folgender Meldung führen:

Objekt Smartbombe500kg mit Objekt Fußgänger237 kollidiert

Damit lässt sich schon mal eine Reaktion unserer Anwendung erzeugen die uns verrät ob sie bisher alles so macht wie wir wollen, das sieht doch erstmal einfacher aus als eine gigantische Explosion auf den Bildschirm zu bringen.

 

Wie man DirectX austrickst

 

Wie bereits erwähnt sind Haltepunkte oft ein Problem wenn man DirectX debuggt. Hier hilft das Setzen mehrerer Haltepunkte(HP) um kritische Bereiche zu umgehen. Z.B. ist das Setzen des Videomodus und Lock/Unlock ein Problem, hat man einen HP danach, bleibt die Anwendung meist hängen und geht nicht zum Debugger zurück. Hier kann man einen HP vor diesem Bereich setzen, diesen dann im Einzelschritt(F10/F11) abarbeiten und danach mit F5 zum eigentlichen HP fortsetzen.

Debuggen von Bereichen zwischen unseren Lieblingspärchen Lock und Unlock macht auch gern Schwierigkeiten, vor allem wenn man mit OutputDebugString arbeitet. Durch das Win16Lock, welches intern aufgerufen wird, funktioniert OutputDebugString hier nicht. Aber gerade solche Programmstellen sind meist besonders interessant. Man kann zum Debuggen gleich Unlock nach Lock aufrufen um sich nur eben den Zeiger auf den gewünschten Speicher zu holen und dann loslegen. Hier kann es aber passieren, daß plötzlich nicht mehr die gewünschten Daten an dieser Stelle stehen weil Windows mal wieder der Meinung war lustig den Speicher hin und herzuschieben. Aber wenn man beim Debugen nicht zu anderen Anwendungen wechselt wird das selten genug vorkommen. Und immer daran denken wieder das Unlock später an die richtige Stelle zu schreiben.

Um beiden Problemen weitestgehend aus dem Weg zu gehen, kann man die Anwendung auch in einem Fenster laufen lassen. Das bringt auch den Vorteil, das sich DirectX hier nicht exclusiv auf dem System breit macht und verhindert, daß man beim Wechseln zu anderen Anwendungen und zum Debugger auch was zu sehen bekommt. Hier muß aber die Farbeinstellung des Desktop mit der der Anwendung übereinstimmen. Außerdem empfielt es sich das Fenster der Anwendung mit CreateWindow auf die ürsprünglichem Vollbildmaße zu stellen. Dadurch hat die Anwendung trotzdem ihren vollen Bildschirm. Wenn die Dektopmaße größer sind kann man sogar noch auf das Debugfenster schauen.

 

Durch den geschickten Einsatz von OutputDebugString läßt sich in dieser Konstellation ganz gut das Verhalten des Programm bei bestimmten Ereignissen testen.

 

 

Der Disassembler

 

Der Disassembler ist eine feine Sache wenn man Assembler versteht, programmiert oder neugierig ist. Hier werden die Befehle die der Compiler aus unserem C-Quelltext gezaubert hat in Mnemnonik-Befehlen angezeigt. Das sind Synonyme für Befehle die der Prozessor versteht, z.B. mov ax, 1h steht für move 1 nach Register ax. Und was bringt uns das? Manchmal ist man auf der Suche nach dem schnellsten Code oder nach dem kompaktesten Code und hier kann man einfach prüfen was der Compiler aus unseren Befehlen macht. Dazu wird immer eine Zeile C-Quellcode mit anschließenden Assamblerbefehlen angezeigt, die aus dieser Zeile erzeugt wurden. Da wundert man sich manchmal wieviele Befehle aus einer kurzen Zeile C-Code kommen.

 

Da bekommt man doch mal wieder Lust zum Assemblerprogrammieren?

 

Seitenanfang