Deep Eclipse Debugger

FIXME Translate, upgrade to version 1.0

Der Debugger wurde als eigenständiges Plugin implementiert, welches das Deep Plugin verwendet. Es wurde darauf geachtet, keine Abhängigkeiten zur Hardware einzubauen.

Die Implementierungen der einzelnen Komponenten sind im Package org.deepjava.debug.eclipse untergebracht. Einzige Ausnahmen sind die Dateien MANIFEST.MF welche im Ordner META-INF ist, plugin.xml und build.properties. Diese müssen auf der obersten Ebene sein.

Meta-Dateien


Die oben genannten Ausnahmen sind Meta-Dateien die Eclipse benötigt, um mit dem Plugin umgehen zu können. Diese Dateien beschreiben, was das Plugin kann, wo der Einstiegspunkt ist, welche Abhängigkeiten es hat etc. Man kann diese Dateien von Hand editieren, es ist jedoch ratsamer den Manifest-Editor zu benutzen. Da dieser komfortabler ist und sich weniger Fehler einschleichen.

DeepDebugPlugin.java


DeepDebugPlugin.java ist der Einstiegspunkt für Eclipse. Beim Laden des Plugins wird der Konstruktor aufgerufen. In diesem wird dann die Konsole initialisiert. Die Klasse beinhaltet des weiteren Konstanten welche in diversen anderen Klassen benötigt werden und Methoden für das Handling von Dateien und Bildern welche das Plugin benützt und mit diesem ausgeliefert werden.

Platform debug model


Das Platform Debug Model definiert generische Interfaces welche für die konkrete Anwendung implementiert bzw. erweitert werden müssen. Das Model wird in der Eclipse Dokumentation unter Platform Plug-in Developer Guide → Programmer's Guide → Program debug and launch support → Debugging a Program → Platform debug model erläutert. Als visuelle Hilfe hier noch ein UML-Diagram des Models:

Invalid Link
Platform debug model ©IBM, 2004

IDebugElement

Wurde in der Klasse DebugElement implementiert, diese stellt Grundfunktionen wie das Generieren von DebugEvents zur Verfügung. Diese wird wiederum von DeepDebugElement um anwendungsspezifischen Inhalt erweitert.
DeepDebugElement wird für alle Model-spezifischen Elemente als Basisklasse verwendet.

IDebugTarget

Wird von DeepDebugTarget implementiert und erweitert gleichzeitig noch DeepDebugElement. Weitere Interfaces, die implementiert werden, sind IBreakpointManagerListener und IDeepEventListener.

Das DeepDebugTarget ist ein Ausführungskontext, welcher die Hardware repräsentiert und ist die Wurzel der Debug-Elemente-Hierarchie. Es erzeugt einen DeepThread und den EventDispatchJob. Des Weiteren meldet es sich als Listener beim BreakpointManager und dem EventDispatchJob an.

DeepDebugTarget stellt Methoden zum Behandeln von Events und Breakpoints zur Verfügung.

IThread

Wird von DeepDebugThread implementiert und stellt den sequenziellen Ablauf des Programms auf der Hardware dar. Es werden die Aktionen suspend, resume, terminate, step-into, step-over und step-return unterstützt. Nach einer ausgeführten Aktion wird der entsprechende DeepDebugEvent ausgelöst.

Stackframe Handling

Wurde die Hardware angehalten, so liefert der Thread den Stack, mit den einzelnen Stackframes in absteigender Reihenfolge. D.h. das aktuelle Stackframe ist zuoberst und folgt den Methodenaufrufen bis zur Methode vor dem Klassenkonstruktor des Kernels. Da das Abrufen der Stackframes x-mal pro Suspend-Event erfolgt, musste aus Performance-Gründen das Aktualisieren und das Ausliefern der Stackframes getrennt werden. Die Stackframes werden pro Suspend-Event nur noch einmal von der Hardware gelesen und dies auch nur, wenn sich der Stackpointer verändert hat.

Step Handling

Das Ziel ist mit möglichst wenigen Unterbrechungen des Programms auf die gewünschte Linie im Java-Programm zu kommen. Dabei ist zu beachten, dass ein Schritt in einem Java-Programm mehrere Schritte auf der Hardware bedeuten. Des Weiteren verhindern bedingte Sprünge wie if-else- oder switch-Konstrukte, deren Bedingungen erst zur Laufzeit ausgewertet werden, eine Vorhersage des Programmablaufs. Die Lösung ist für jede Step-Art etwas anders.

Am einfachsten ist es bei Step-Return. Aus dem Stackframe auf der Hardware wird die Rücksprungadresse ausgelesen und wir lassen das Target so viele Sprünge machen bis diese Adresse erreicht ist.

Komplizierter ist es bei Step-Over. Als erstes wird die aktuelle Liniennummer im Java-Programm und danach die folgende bestimmt. Gibt es keine folgende so ist die Methode am Ende und wir können einen Step-Return ausführen. Anderenfalls muss mithilfe der SSA überprüft werden ob die aktuelle Linie einen Methodenaufruf oder eine Return-Anweisung enthält. Bei einem Methodenaufruf springen wir solange bis die aktuelle Methode wieder erreicht ist. Danach gehen wir mit Einzelschritten vorwärts, bis eine Liniennummer zutrifft. Bei einer Return-Anweisung wird wiederum ein Step-Return ausgeführt.
Hat bis jetzt nichts zugetroffen so muss im Einzelschritt nach jeder Maschineninstruktion überprüft werden ob schon eine Linie im Java-Programm zu trifft.

Am kompliziertesten ist es bei Step-Into. Hier wird ebenfalls zuerst die aktuelle Liniennummer und die folgende bestimmt. Aber es muss auch bestimmt werden, wenn es auf dieser Linie mehrere Methodenaufrufe gibt, welcher bereits ausgeführt ist. Gibt es keine nächste Liniennummer so wird bis ans ende der Methode geprüft.
Mit Hilfe der SSA wird überprüft ob ein Methodenaufruf einer nicht synthetischen Methode statt findet. Ist dies der Fall, so springen wir solange bis die Methode erreicht ist,danach muss noch mit Einzelschritten der Prolog der Methode traversiert werden damit wir auf der ersten Zeile der Methode sind.
Ist die die aufgerufene Methode synthetisch, so muss überprüft werden, ob es sich um die Inline-Assembler-Anweisung handelt. In diesem Fall wissen wir nicht ob ein Sprung gemacht wird oder nicht. Daher wird ein einzelner Schritt ausgeführt und danach die Methode bestimmt in welcher wir uns nun befinden. Danach gehen wir mit Einzelschritten vorwärts, bis eine Liniennummer zutrifft.
Befindet sich auf der aktuellen Liniennummer eine Return-Anweisung so können wir einen Step-Return ausführen.
Hat nichts zugetroffen so muss im Einzelschritt nach jeder Maschineninstruktion überprüft werden, ob eine Linie im Java-Programm zutrifft.

IStackFrame

Wird von DeepStackFrame implementiert und stellt einen Ausführungskontext in einem angehaltenem Thread dar. Das Stackframe enthält zudem die statischen Variablen, welche am Ausführungsort sichtbar sind.
Bei der Initialisierung des Stackframes wird bestimmt, zu welcher Methode in welcher Klasse es gehört. Sobald dies bekannt ist, wird noch die Liniennummer bestimmt und die Objekte für statischen Variablen angelegt.
Das DeepStackFrame unterstützt zu den Step-, Suspend-, Resume- und Terminate-Events zusätzlich noch die Aktionen Ausliefern der Liniennummer, der zugehörigen Methode und des Source-Namens.

IVariable

Wird von den Klassen DeepVariable, DeepArrayEntry und DeepStringEntry implementiert. Eine Variable repräsentiert eine sichtbare Datenstruktur in einem Stackframe oder in einen IValue-Objekt. Eine Variable enthält selbst aber auch ein IValue-Objekt.

IValue

Wird von den Klassen DeepValue, DeepString, DeepArray und DeepObject implementiert. Ein IValue-Objekt repräsentiert den Wert einer Variable. Ist dies eine komplexe Datenstruktur, so enthält das IValue-Objekt wiederum Variablen.

Debug Events


Es werden zwei verschiedene Typen von Events verwendet.

Eclipse

Eclipse spezifische Events vom Typ DebugEvent welche über die Klasse DebugElement generiert werden. Diese werden dazu verwendet die Entwicklungsumgebung gewünschte Aktionen ausführen zu lassen.

Deep

Deep spezifische Events vom Typ String welche über die Klasse DeepDebugTarget, welche den EventDispatchJob enthält, generiert werden. Sämtliche Klassen welche das Interface IDeepEventListener implementieren können sich am DeepDebugTarget registrieren und werden somit über die Events benachrichtigt. Diese werden verwendet um DeepDebugger- und Target-Events zu signalisieren.

Breakpoint


Der DeepDebugger unterstürtzt nur Line Breakpoints. Diese sind in der Klasse DeepLineBreakpoint implementiert.

Der User kann im Editor einen Breakpoint setzten. Da wir keinen Einfluss auf den Editor haben, bekommen wir einen JavaLinebreakpoint. Zuerst wird dann überprüft, ob der Breakpoint zu einer Klasse gehört, welche auch auf das Target geladen wurde.
Nun wird für diesen JavaLineBreakpoint ein DeepLineBreakpoint-Objekt erzeugt. Bei der Erzeugung wird ermittelt wievele Hardware-Breakpoints dieser LineBreakpoint erzeugt und welches die Memoryadressen sind. (z.B. ein Breakpoint auf einer for-Schleife erzeugt 2 Hardware-Breakpoints)
Danach wird überprüft ob noch genügend Hardware-Breakpoints auf dem Target verfügbar sind. Ist dies der Fall, so wird der DeepLineBreakpoint auf dem Target installiert und als IDeepEventListener am DebugTarget registriert. Andererseits wird der JavaLineBreakpoint disabled und eine Warnung auf der Konsole ausgegeben.
Wird ein Breakpoint im Editor entfernt, so bekommen wir ebenfalls den JavaLineBreakpoint welcher entfernt wurde. Mit Hilfe dessen wird ein DeepDebugEvent mit der Angabe der Source-Datei und der Liniennummer generiert. Wurde der betreffende DeepBreakpoint installiert, so kann er mit den Angaben entscheiden das es ihn betrifft. Darauf hin entfernt er seine Hardware-Breakpoints auf dem Target und sich als Listerner vom DebugTarget.
Spricht ein Hardware-Breakpoint auf dem Target an, so wird vom EventDispatch Job ein DeepDebugEvent mit dem aktuellen PC generiert. Der betreffende DeepBreakpoint bestätigt auf dem Target den Match.(Nur so kann das Target später wieder gestartet werden) Danach wird noch ein DebugEvent an Eclipse gesendet, dass ein Breakpoint erreicht wurde und welcher.

Source lookup


Der Source lookup mechanimus ist 2-teilig, der eine Teil ist die Source für ein Stackframe zu finden, der Andere ist dafür einen Standard-Suchpfad zu definieren.

Source lookup für Stackframes

Damit die Source gefunden werden kann, muss der DeepSouceLookupDirector den DeepSourceLookupParticipant initialisieren bzw. erzeugen und registrieren. Die Aufgabe des DeepSourceLookupParticipant ist es dann aus dem DeepStackFrame den Source-Namen zu extrahieren und als String zurück zu geben. Anhand dessen sucht Eclipse dann auf dem Standard-Suchpfad die Source-Datei.

Standard-Suchpfad

Der User kann in den Run-Konfiguration einen Source-Lookup-Pfad definieren. Wird dies jedoch nicht gemacht, so braucht es einen Standard-Suchpfad. Dafür ist die Klasse DeepSourcePathComputerDelegate zuständig. Es wird das aktuelle Projekt und dessen Unterverzeichnisse, sowie alle im der Deep-Datei angegebenen Bibliotheken(die keine Jars sind) als Standard gesetzt.

Presentation

Wurde die Source-Datei gefunden, so muss sie in geeigneter Form dem richtigen Editor übergeben werden. Dafür ist die Klasse DeepModelPresentation zuständig. Diese bestimmt die Editor-ID für die gegebene Source und liefert den Editor-Input. Ebenso liefert die Klasse die Labels welche in der Debug-View für das Target, den Thread und die Stackframes verwendet werden.