Bachelorarbeit A Java Class File Parser Wolfgang Estgfäller (0617349) [email protected] 1 März 2010 Betreuer: Friedrich Neurauter Zusammenfassung (Englisch) Java is one of the most widely spread and used programming languages. One reason for that fact is the portability of Java. Java source code is compiled into a platform-independent intermediate representation, the so-called bytecode. The aim of this project was to write a parser for the binary class les contain- ing the bytecode and to present the gained contents by a graphical user interface. Inhaltsverzeichnis 1 Einleitung 1 2 Kontextfreie Grammatiken und Parser 2 2.1 Formale Sprachen und kontextfreie Grammatiken . . . . . . . . . 2 2.2 Parser 4 2.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 Top-down Parser . . . . . . . . . . . . . . . . . . . . . . . 5 2.2.2 Bottom-up Parser 6 ANTLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.1 Überblick 2.3.2 LL(*) 2.3.3 ANTLR - Grammatik 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 . . . . . . . . . . . . . . . . . . . . 9 3 Class-File Format 11 4 Vergleichbare Tools 16 5 Implementierung 18 5.1 Programmstruktur 5.2 Funktionsweise 5.3 Grammatik 5.4 Pakete im Detail 5.5 18 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 5.4.1 Classle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 5.4.2 Gui . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 5.4.3 Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Benutzerhandbuch 6 Zusammenfassung iv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 27 1 Einleitung Java Class-Files sind binäre Dateien, die von einem Java-Compiler erzeugt wer- den. Sie enthalten beispielsweise Informationen über Methoden, Felder, Konstanten und natürlich auch den Java-Bytecode. Der Bytecode ist ein Zwischen- code, der vom Compiler generiert wird. Er enthält Instruktionen, die von der Java Virtual Machine ausgeführt werden können. Eine Eigenschaft vom Byte- code ist seine Plattformunabhängigkeit. Dadurch kann ein kompiliertes Java Programm auf mehreren Plattformen ausgeführt werden. Natürlich unter der Voraussetzung, dass auf der Zielplattform eine Java Virtual Machine vorhanden ist. Dadurch existiert eine Vielzahl von Java Programmen, welche unter anderem aus dem Internet bezogen werden können. Seien es kommerzielle oder freie Programme. Oft wird nur die ausführbare Variante eines Programms angeboten. Auÿerdem sind viele Programme schlecht dokumentiert und geben unzureichende Informationen über deren Inhalt oder Funktionsweise. Treten dann zum Beispiel bei der Verwendung einer Bibliothek Fehler auf, kann es mühsam werden diese zu beseitigen. Motivation für diese Arbeit war es, eine Lösung für Probleme solcher Art zu bieten. Man sollte die Möglichkeit haben Class-Files einzulesen und anzuzeigen. Dies kann hilfreich sein, wenn der Quellcode aus irgendwelchen Gründen verloren ging oder auch einfach nur um den Inhalt zu analysieren. Ziel dieser Arbeit war es ein Programm zu entwerfen, das die Inhalte der Java Class-Files präsentiert. Dabei sollte ein Parser mit dem bekannten Parser Generator ANTLR erzeugt und implementiert werden. Dem Benutzer sollte die Möglichkeit gegeben werden, die Inhalte der Class-Files benutzerfreundlich zu durchstöbern. Diese Arbeit gliedert sich wie folgt. Der erste Teil beschäftigt sich mit grundlegendem Wissen 2 über kontextfreie Grammatiken, Parser im Allgemeinen und dem ANTLR Framework, mit dessen Hilfe Parser erstellt werden können. Dann folgt eine kurze Einführung in das Format der Java Class-Files 3. Dabei wird auf deren Struktur und Inhalte eingegangen. Im Kapitel 5 wird die Implementation des Programms beschrieben. Am Ende wird noch eine Zusammenfassung über das Erarbeitete gegeben. 1 2 Kontextfreie Grammatiken und Parser 2.1 Formale Sprachen und kontextfreie Grammatiken Die nachfolgenden Denitionen sind eine Einführung in die Theorie der Alphabete, Wörter, Sprachen und kontextfreien Grammatiken. Grundlage dafür waren [6], [4], [7] und [9]. Ein Alphabet Σ ist eine endliche, nichtleere Menge von Symbolen (Zeichen), Wort (Zeichenkette) w über Σ ist eine endliche w = w0 , w1 , . . . , wn−1 mit wi ∈ Σ, n ∈ N, wobei |w| = n die Länge des ∗ Wortes w bezeichnet. Σ bezeichnet die Menge {w | w = w0 , w1 , . . . , wn−1 , wi ∈ Σ, n ∈ N}, also alle Wörter über dem Alphabet Σ. Σ+ steht für die Menge aller + = Σ∗ \ {ε}, wobei ε dem leeren Wort nichtleeren Wörter über Σ, genauer Σ ∗ entspricht, für welches |ε| = 0 und εw = wε = w gilt. Eine Teilmenge L ⊆ Σ heiÿt formale Sprache über Σ. z.b. Buchstaben oder Zahlen. Ein Folge Kontextfreie Grammatiken Denition 2.1. Eine kontextfreie Grammatik G (Abk. KFG) ist ein 4-Tupel G = (N, Σ, P, S) mit folgenden Eigenschaften: • N ist ein Alphabet, dessen Elemente als Nichtterminale (engl. nontermi- nals) bezeichnet werden. • Σ ist ein Alphabet, dessen Elemente als Terminale (engl. terminals) beze- ichnet werden. • Es gilt N ∩ Σ = ∅. • P ist eine endliche Teilmenge von N ×V, wobei das Vokabular V die Menge N ∪ Σ ist. • Ein Element (A, α) aus P , mit A ∈ N und α ∈ V ∗ , wird als Produktion bezeichnet und wird in der Form A → α geschrieben. • S ∈ N ist das Startsymbol. Beispiel 2.2. Ein Beispiel für eine kontextfreie Grammatik, die die Sprache einfacher arithmetischer Ausdrücke beschreibt: G = ({expr, term, f actor, number, digit}, {0, 1, . . . , 9, +, ∗, (, )}, P, expr), 2 2.1 Formale Sprachen und kontextfreie Grammatiken P mit bestehend aus den Produktionen expr → term + expr | term term → f actor ∗ term | f actor f actor → number | ( expr ) number → digit | number digit digit → 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Die Nichtterminal-Symbole sind expr, term, f actor, number 0 bis 9, +, ∗, ( und ). und digit, die Terminal-Symbole sind die Ziern Um eine kompaktere Schreibweise einer Menge von Produktionen zu erhalten wird allgemein die Konvention verwendet, dass Produktionen der Form A → α1 , A → α2 , . . . , A → αn A → α1 |α2 | . . . |αn . Denition 2.3. Seien abgekürzt dargestellt werden können als Gegeben sei eine kontextfreie Grammatik α, β, γ ∈ V ∗ . Dann kann β aus α in einem G = (N, Σ, P, S). direkten Schritt abgeleitet werden, geschrieben als α− → β, G ∗ wenn es zwei Worte v und w über V und eine Produktion A → γ in P gibt, sodass α = vAw und β = vγw. Falls v ∈ Σ∗ k, dann wird die Ableitung als Linksableitung bezeichnet Wenn ∗ − → G und ∗ {w ∈ Σ∗ | S − → w} G S∈V ∗ und w ∈ Σ∗ als Rechtsableitung. ist die reexive und transitive Hülle von w ∈ V∗ Beispiel 2.4. bezeichnet und falls ∗ S − → w G wird als gilt, dann ist w in Gegeben sei die kontextfreie Grammatik β = 3 + 4 ∗ 12 ∈ G Satzform. die von G erzeugte Sprache Weiters − →. Die Menge L(G) = bezeichnet. G aus Beispiel 2.2, α= Σ∗ . Dann gilt ∗ α− →β G ∗ ∗ expr − → term + expr − → 3 + expr − → 3 + term − → 3 + f actor ∗ term − → G G ∗ G G G ∗ 3 + 4 ∗ term − → 3 + 4 ∗ number − → 3 + 4 ∗ digitnumber − → 3 + 4 ∗ 12 G G Denition 2.5. w∈ Sei G = (N, Σ, P, S) ein Ableitungsbaum Σ∗ . Dann ist G eine kontextfreie Grammatik und sei (engl. parse tree) B von w ein markierter Baum, wenn gilt: (1) Die Wurzel ist mit S markiert. (2) Jedes Blatt ist mit einem Element aus Σ∗ markiert. (3) Für einen inneren Knoten mit der Markierung A muss gelten A ∈ N. k die Markierung K hat und seine Kinder mit den K0 , K1 , . . . , Kn−1 versehen sind (Ki ∈ V, 0 ≤ i ≤ n − 1), K → K0 , K1 , . . . , Kn−1 eine Produktion in P sein. (4) Wenn der Knoten Markierungen dann muss 3 2 Kontextfreie Grammatiken und Parser (5) Das Wort w w ergibt sich aus der Verkettung der Markierungen aller Blätter von links nach rechts. Die Grammatik G wird als mehrdeutig (engl. ambiguous) bezeichnet, wenn es für v aus L(G) mehrere unterschiedliche Ableitungsbäume mindestens ein Element gibt. Beispiel 2.6. Gegeben sei eine kontextfreie Grammatik G = (N, Σ, P, {S}), wobei • N = {S, E, O} • Σ = {0, 1, +, ·} • P = {S → E, E → EOE, E → 0, E → 1, O → +, O → ·} Für w =0·1+1·0 sieht Ableitungsbaum folgendermaÿen aus: S E E O E O E 0 · 1 + E E O E 1 · 0 2.2 Parser Unter dem Begri parsen (engl. to parse analysieren) versteht man die syn- taktisch Analyse einer Sequenz von Symbolen. Dabei wird die Eingabe in eine Sequenz von sogenannten Tokens, beispielsweise Wörter, umgewandelt. Diese Sequenz wird dann mit einer Grammatik abgearbeitet. Dabei wird durch die Anwendung der Produktionen der Grammatik auf die Eingabesequenz ein Ableitungsbaum erstellt. Wenn die Sequenz erfolgreich abgearbeitet wird, ist die Eingabe gültig. In der Informatik werden die dafür zuständigen Programme als Parser bezeich- net. Meistens sind Parser eine Komponente eines Compilers oder Interpreters, die eine Eingabe auf die Korrektheit der Syntax überprüft und eine Datenstruktur erstellt, um die ausgelesenen Informationen weiterverarbeiten zu können. In den meisten Fällen wird eine Art Baumstruktur erstellt, genannt der Syntaxbaum, was nichts anderes ist, als ein reduzierter Ableitungsbaum (siehe Denition 2.5). Diese abstrahierten Baumstrukturen enthalten nur mehr die notwendigen Informationen. Elemente die ausschlieÿlich zur Strukturierung dienen, wie zum Beispiel ; bei der Programmiersprache Java, werden dabei üblicherweise weggelassen. Oft werden auch zusätzliche Informationen eingefügt, um eine bessere/einfachere Struktur zu erhalten. 4 2.2 Parser Das Einlesen der Eingabesymbole und die Erstellung von Tokens wird in vielen Fällen von einem separaten lexikalischen Scanner übernommen. Die erstellten Tokens werden dann Stück für Stück an den eigentlichen Parser weitergegeben. Die folgende Abbildung veranschaulicht den Ablauf eines üblichen Parsers. Abbildung 2.1: Illustration der einzelnen Arbeitsschritte Parser Typen Generell unterscheidet man zwischen zwei Typen von Parsern: 1. Top-down 2. Bottom-up Parser Parser 2.2.1 Top-down Parser Ein Top-down Parser leitet die Eingabe nach dem Prinzip der Linksableitung (siehe Denition 2.3) ab. Bei der schrittweise durchgeführten Ableitung wird von links nach rechts abgeleitet, das heiÿt, es wird immer das Nichtterminal der aktuellen Satzform durch die entsprechende Produktionsregel ersetzt. Das ganze nennt sich Top-down parsen, weil die Expansion des Ableitungsbaumes von der Wurzel zu den Blättern erfolgt. Ein Beispiel dazu gibt es im Anschluss. Wenn zwei oder mehrere Alternativen mit dem selben Nichterminal oder dem selben Terminal beginnen, ist die Wahl der Alternative nichtdeterministisch. Da Nichtdeterminismus jedoch praktisch nicht realisierbar ist, müssen andere Lösungen verwendet werden. Eine Lösung für dieses Problem ist Backtracking. Dabei merkt sich der Parser die Entscheidungspunkte und wenn eine Ableitung fehlschlägt, springt er zum 5 2 Kontextfreie Grammatiken und Parser jeweiligen Entscheidungspunkt zurück. Der fehlgeschlagene Entscheidung wird markiert und es wird mit einer anderen Alternative fortgefahren. Das macht die Lösung mächtig, aber auch langsam aufgrund der resultierenden exponentiellen Komplexität. Eine andere Möglichkeit ist das prädiktive Parsen, bei der die Wahl der richtigen Produktionsregel mit einer Vorschau, genannt Lookahead, von einem oder mehreren Symbolen getroen wird. Parser dieser Variante werden häug verwendet, da die Erstellung sehr intuitiv ist. Die bekanntesten prädiktiven Parser sind LL(k) Parser, wobei k für die Anzahl der Lookahead Symbole steht. LL steht für das Abarbeiten der Eingabe von Links nach rechts und die Ableitung mithilfe der Linksableitung. 2.2.2 Bottom-up Parser Da bei dieser Bachelorarbeit kein Bottom-up Parser verwendet wurde, wird diese Thematik nur kurz angeschnitten. Anders als beim Top down Parser, erfolgt die Erstellung des Ableitungsbaumes nicht durch eine Expansion, sondern durch eine Reduktion von Satzformen (siehe Beispiel 2.7). Bottom up Parser schlieÿen von den kleinsten gefunden Einheiten auf den höheren Zusammenhang. Sie arbeiten sich also von unten nach oben. Grammatiken die Zykel oder ε-Produktionen enthalten terminieren mit einem Bottom up Parser nicht. Ein bekannter Bottom-up Parser ist der LR oder LR(k) Parser, der die Eingabe von Links nach rechts abarbeitet und die Rechtsableitung verwendet. Beispiel 2.7. Gegeben sei die KFG G = ({S, A, B, C}, {a, b, c}, P, {S}) mit den Produktionen S ⇒ AB A ⇒ aA|A B ⇒ bB|b C ⇒ cC|c Ableitung des Wortes aabb mit einer Sequenz von Linksableitungen: S− → ABC − → aABC − → aaBC − → aabBC − → aabbC − → aabbcC − → aabbcc G G G G G G G Und die Rechtsableitung: S− → ABC − → ABcC − → ABcc − → AbBcc − → Abbcc − → aAbbcc − → aabbcc G G G G G G G Der dazugehörige Ableitungsbaum ist bei beiden Ableitungen identisch, die Reihenfolge bei der Erzeugung unterscheidet sich jedoch: 6 2.3 ANTLR S 1 Expansions-Reihenfolge bei einer Linksableitung: A a 3 2 B 5 A b a C B 6 1, 2, 5, 3, 6, 4, 7 4 c b 7 C c Reduktions-Reihenfolge bei einer Rechtsableitung: 7, 4, 6, 3, 5, 2, 1 2.3 ANTLR Da das im Rahmen dieses Arbeit erstellte Programm ANTLR als Parser Generator verwendet, wird in diesem Abschnitt eine kurze Einführung in das von Terence Parr erstellte Framework gegeben. Jedoch wird nur ein allgemeiner Überblick gegeben, für mehr Informationen wird das das Buch [10] von Terence Parr wärmstens empfohlen. Alternativ kann auch die Internet-Präsenz [1] des Projektes besucht werden. 2.3.1 Überblick ANTLR, ANother Tool for Language Recognition, ist ein objektorientierter Parser Generator, der in Java geschrieben wurde. Das Tool erstellt lexikalische Scanner, Parser und Treeparser anhand von LL(*) Grammatiken. Auÿerdem unterstützt das Tool eine Reihe von Zielsprachen, wie zum Beispiel und Python. C, C#, Java Das Projekt wird seit 1989 von Terence Parr an der Universität von San Francisco entwickelt. Die aktuelle Version ist ANTLRv3 und ist als Freie Software unter der BSD Lizenz verfügbar. Arbeitsweise Abbildung 2.2 veranschaulicht die Arbeitsweise von ANTLR. Die Rechtecke repräsentieren die Übersetzungsphasen und die Kanten die Datenstrukturen die dabei erstellt werden. Am Anfang erhält der Lexer eine Sequenz von Eingabesymbolen, welche er in Tokens umwandelt. Der Parser erstellt anhand der denierten Grammatik gegebenenfalls einen abstrakten Syntaxbaum (engl. abstract syntax tree AST), den ein sogenannter Treewalker dann durchläuft. Oft sind mehrere Iterationen von Nöten bis das Endresultat erreicht wird, also übergibt der Treewalker seinen Erstellten AST an den nächsten und so weiter. Der Parser und die Treewalker können sich gemeinsame Strukturen teilen. Treewalker werden bei ANTLR nämlich auch mit einer Grammtik erstellt, die der Grammatik des Parser meist sehr ähnlich sieht. Das erleichtert deren Erstellung ungemein. 7 2 Kontextfreie Grammatiken und Parser Abbildung 2.2: Datenuss beim Parse-Vorgang Benutzung Die von ANTLR verwendete Sprache ist eine Mischung aus einer Grammatik, benutzerdenierten Einstellungen und Befehlen in der gewählten Zielsprache. Ein Beispiel für eine sehr einfache Grammatik, die helloworld gefolgt von einem oder mehreren ! generiert: grammar Simple; s : a b (c)+; a : 'hello'; b : 'world'; c : '!'; NL: ('\n')+ {$channel=HIDDEN;} ; // ignoriere newline 2.3.2 LL(*) Es wurde bereits erwähnt, dass ANTLR die Generierung von LL(*) Parsern, also Parsern die theoretisch einen beliebigen Lookahead erlauben, unterstützt. Der Unterschied dabei ist, dass LL(k) Parser, wenn sie zwischen verschiedenen Alternativen unterscheiden müssen, maximal k Symbole vorausschauen. LL(k) Parser werden durch deterministische endliche Automaten (DEA) realisiert, die zyklenfrei sind. Beim LL(*) Parsing hingegen wird bei der Ermittlung, welche Alternative verwendet werden soll, ein DEA erstellt, der Zyklen enthalten kann. Sobald die Alternative ermittelt ist, wird das LL Parsen wie sonst üblich fortgeführt. Die folgende Grammatik ist zum Beispiel nicht LL(k): grammar T; def : (modifier)+ 'int' ID '=' INT ';' //z.B. "public int x=5;" |(modifier)+ 'int' ID ';' //z.B. "public static int x;" modifier : 'public' |'static' ; 8 2.3 ANTLR Da hier beliebig viele Modier vorkommen könnten, kann diese Grammatik für ein xes k nicht verwendet werden. Für einen LL(k) Parser müsste die Regel def links-faktorisiert werden. Mit einem LL(*) Parser, wie ANTLR, kann ein solches Konstrukt aber gelesen werden. 2.3.3 ANTLR - Grammatik Wie schon angesprochen, ist die Sprache von ANTLR eine Kombination aus Einstellungen, Grammatik und eigenem Code. In diesem Abschnitt werden einige Funktionalitäten beschrieben, um sich ein besseres Bild machen zu können. Datei Kopf Im Datei Kopf hat man die Möglichkeit globale Einstellungen zu setzen: Stellt die Datei eine Grammatik oder eine Baumgrammatik dar? Welche Zielsprache wird verwendet? Welche Tokens werden verwendet? Soll ein AST erstellt werden? Code, der zum Datei Kopf hinzugefügt werden soll . . . Aktionen und Attribute Die in ANTLR verwendete Sprache erlaubt, wie bereits erwähnt, das Einbinden von eigenem Code in der gewählten Zielsprache. Diese Code-Blöcke werden als Aktionen bezeichnet und können in den Grammatik-Regeln, auch zwischen einzelnen Symbolen oder Regelaufrufen, beliebig platziert werden. Man kann für jede Regel auch einen init- oder after-Codeblock denieren, in dem Code formuliert werden kann, der beim Eintritt in die Regel beziehungsweise beim Verlassen der Regel ausgeführt wird. Regeln und Tokens stellen vordenierte Attribute zur Verfügung, die in Aktionen verwendet werden können. Damit kann beispielsweise auf den Text zugegrien werden der, von einer Regel bzw. einem Token geparst wurde. Variablen mit Gültigkeitsbereich ANTLR ermöglicht die Erstellung von Scopes. Darunter versteht man Variablen mit einem begrenzten Gültigkeitsbereich. Eine Regel kann angeben, ob sie ein Scope verwenden will. Dieses Scope ist dann in allen Regeln, die im Zuge ihrer Ausführung aufgerufen werden, sichtbar. Realisiert wird dieses Konzept mit Stacks. Beim Eintritt in eine Regel wird ein Element auf den Stack gelegt, beim Verlassen wieder heruntergenommen. Somit besteht die Möglichkeit Scopes zu verschachteln. Man hat einerseits die Möglichkeit sogenannte globale Scopes zu erstellen. Diese globalen Scopes werden mit einem einzigen Stack realisiert. So können sich beispielsweise mehrere Regeln ein gemeinsames Scope teilen. Andererseits gibt es auch die dynamischen Scopes, die für einzelne Regeln deniert werden können. Hierbei wird immer ein separater Stack verwendet. 9 2 Kontextfreie Grammatiken und Parser Backtracking und Memoization Wenn für die spezizierte Grammatik kein gültiger LL(*) Parser erstellt werden kann, ist es möglich die Option Backtracking zu aktivieren. Dabei probiert der Parser einfach alle Alternativen durch bis eine Alternative zutrit, oder eben alle Möglichkeiten durchprobiert wurden. Zusätzlich kann die Option Memoization aktiviert werden, dabei werden Ergebnisse, die beim Backtracking entstehen, zwischengespeichert und können wiederverwendet werden. Backtracking in Kombination mit Memoization soll eine (fast) lineare Laufzeit garantieren. Prädikate ANTLR erlaubt die Verwendung von syntaktischen und semantischen Prädikaten. LL(*) ist zwar schon viel besser als LL(k), jedoch hat LL(*) trotzdem noch einige Schwächen. So können mit dem Einsatz von Prädikaten auch Grammatiken, die kontextsensitive Teile, enthalten behandelt werden. Prädikate ermöglichen die Auswertung von mehrdeutigen Regeln, beispielsweise durch die Selektion einer Regel anhand des Kontextes, in dem sie aufgerufen wurde. Allgemein ausgedrückt stellen Prädikate Bedingungen dar, die zur Laufzeit ausgewertet werden. Baumerstellung Wird eingestellt, dass ANTLR einen Baum als Ausgabe verwenden soll, erstellt ANTLR automatisch einen Ableitungsbaum. Dieser Baum kann dann mit einer neuen Grammatik, die als tree grammar deniert wird, geparst werden. Auch für eine Baum-Grammatik kann als Ausgabe wiederum ein Baum erstellt werden. Somit können mehrere Iterationen leicht durchgeführt werden. Standardmäÿig wird die von ANTLR vorgegebene Datenstruktur verwendet, jedoch kann diese auch an eigene Wünsche angepasst werden. 10 3 Class-File Format Dieses Kapitel soll dem Leser Einblick in das Java Class-File Format geben. Als Class-Dateien bezeichnet man Dateien, die beim Kompilieren aus Java Quellcode erzeugt werden. Dabei wird eine Art Zwischencode, der sogenannte Bytecode, erzeugt. Dieser Zwischencode ist platformunabhängig und kann mit Hilfe einer Java Laufzeitumgebung ausgeführt werden. Das Prinzip dahinter ist Write Once, Run Everywhere , zu deutsch Einmal schreiben, überall ausführen . Die Java Laufzeitumgebung (Java Runtime Environment, JRE) enthält eine virtuelle Maschine (Java Virtual Machine, JVM) welche den Bytecode in Maschinencode übersetzt und ausführt. Im Folgenden wird der Aufbau dieser Datei erleutert, wobei diese Informationen hauptsächlich aus dem Buch [8] entnommen wurden, welches die Java Version Java Specication [2] entnommen, welcher im Java Community Process (JCP) 1.5 beschreibt. Details zur aktuellen Version 1.6 wurden dem Request JSR 202 eingetragen ist. Die Datei besteht aus einem Stream von Bytes. Normalerweise entsprechen entweder 8, 16, 32 oder 64 Bit (u1, u2, u4, oder u8) einem Datensatz, wobei Datensätze die aus mehreren Bytes zusammengesetzt sind, immer in Big-Endian 1 Ordnung vorliegen. Die Tabelle veranschaulicht die Class-File Struktur. Class File { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } Kopfdaten Konstanten-Pool Klassen-Deskriptor Felder Methoden Klassen-Attribute Tabelle 3.1: Class-File Struktur 1 Bezeichnet die Anordnung der Bytes im Speicher, wobei höherwertige Bytes zuerst vorkommen. 11 3 Class-File Format Inhalt Um einen besseren Überblick zu bieten wurde der Inhalt in verschiedene Bereiche (siehe Tabelle 3.1) unterteilt. Auf diese Bereiche wird im folgenden kurz eingegangen. Kopfdaten Jedes Class-File beginnt mit der sogenannten muss dem Wert 0xCAFEBABE Magic Number. Diese Signatur entsprechen. Weiters wird hier noch die Java Ver- sion speziziert, welche für die Ausführung dieser Datei erforderlich ist. Diese Nummer setzt sich aus der Major und Minor Versionsnummer zusammen. Die Versionen unterliegen einer lexikographischen Ordnung. Wenn zum Beispiel Mi- 2 ist und Major 1.1 < 1.2 < 2.1. nor gleich wobei gleich 1, dann ist die Versionsnummer gleich 1.2, Konstanten-Pool Der Konstanten-Pool enthält mehrere der folgenden Konstanten: 01 03 04 05 06 07 08 09 10 11 12 CONSTANT_Utf8 CONSTANT_Integer CONSTANT_Float CONSTANT_Long CONSTANT_Double CONSTANT_Class CONSTANT_String CONSTANT_Fieldref CONSTANT_Methodref CONSTANT_InterfaceMethodref CONSTANT_NameAndType Die Konstanten Utf8, Integer, Float, Long, Double und String repräsentieren ihren jeweiligen Datentyp. Fieldref, Methodref und InterfaceMethodref repräsentieren Felder, Methoden und Interface-Methoden. Der Typ Class repräsentiert ein Interface oder eine Klasse. NameAndType Konstanten enthalten zwei Verweise auf Utf8 Einträge im Konstanten Pool, die den Name und Typ eines Feldes oder einer Methode repräsentieren. Die Einträge Long und Double beanspruchen zwei Indexe im Pool. Der Tag 02 wird nicht verwendet. Abbildung 3.1: Beziehungen zwischen den Konstanten. 12 Klassen-Deskriptor Im Klassen-Deskriptor ist eine Maske von Access Flags deniert, welche die Zugrisrechte und Eigenschaften der Klasse bzw. des Interfaces bestimmt. Weiters enthält sie zwei Verweise auf CONSTANT_CLASS Einträge im Konstanten Pool, welche die this-Klasse und Superklasse repräsentieren. Abschlieÿend folgen noch alle Interfaces, die von der Klasse implementiert werden. Attribute Da die Felder, Methoden und die Klasse selbst Attribute enthalten kann, wird zuerst eine kurzer Überblick über die verschiedenen Attribute gegeben. Es werden nur die Attribute beschrieben, die im Programm zu dieser Arbeit verwendet wurden. Generell hat ein Attribut folgenden Aufbau: attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; } Der Name Index verweist auf einen CONSTANT_Utf8 Eintrag im Pool, der den Name des Attributes enthält. Die Attributlänge deniert die Anzahl der Bytes die folgen. Danach folgt der eigentliche Inhalt des Attributes, dieser ist von Attribut zu Attribut verschieden. ConstantValue: Ein statisches Feld wird mit dem Wert von ConstantValue initialisiert. Code: Der Code den eine Methode enthält. Exceptions: Exceptions die von einer Methode geworfen werden. InnerClasses: Eine innere Klasse. EnclosingMethod: Wenn die Klasse anonym oder eine Local Class ist. Synthetic: Nicht sichtbar im Quellcode. Signature: Eine Signatur die eine Klasse, Methode oder ein Feld beschreibt. SourceFile: 13 3 Class-File Format Name der Klasse bzw. des Interfaces. LineNumberTable: Eine Debug Erweiterung, mit der ermittelt werden kann, welcher Teil vom Code zu einer bestimmten Zeilennummer im Quellcode gehört. LocalVariableTable: Diese Tabelle kann von einem Debugger verwendet werden um den Wert einer lokalen Variable während der Ausführung einer Methode zu bestimmen. Deprecated: Wenn eine Klasse oder Methode auf deprecated gesetzt sein sollte. Felder Hier werden alle Felder der Klasse bzw. des Interfaces abgespeichert. Jedes Feld ist wie folgt aufgebaut: field_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } Access Flags beschreiben wieder eine Maske, die Informationen über die Zugrisrechte und Eigenschaften enthält. Der Name Index zeigt auf einen CONSTANT_Utf8 Eintrag im Pool, der den Namen des Feldes repräsentiert. Das Gleiche gilt für den Deskriptor Index. Danach folgen noch die Attribute des Feldes. Gültige Attribute für ein Feld sind ConstantValue, Synthetic, Signature und Deprecated. Methoden Hier werden alle Methoden der Klasse bzw. des Interfaces abgespeichert. Die Methoden haben den gleichen Aufbau wie die Felder vorhin: method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } Die Attribute, die für Methoden gültig sind, sind Signature 14 und Deprecated. Code, Exceptions, Synthetic, Klassen-Attribute Wie auch Methoden und Felder, kann die Klasse bzw. das Interface Attribute en- InnerClasses, EnclosingMethod, Synthetic, Deprecated. thalten. Dazu zählen die Attribute SourceFile, Signature und 15 4 Vergleichbare Tools In diesem Kapitel wird kurz auf zwei andere frei verfügbare Tools eingegangen. Anschlieÿend werden die Tools mit dem im Rahmen dieser Arbeit entwickelten Programm Java ByteCode Explorer (Jbce) verglichen. Jclazz Utils Jclazz Utils [5] ist ein Tool, das in Java geschrieben wurde. Es relativ aktuell und bietet deshalb eine fast vollständige Unterstützung für Java Class-Files mit Version 1.6. Das Programm bietet eine sehr einfache graphische Schnittstelle, die einem ermöglicht ein einzelnes Class-File zu parsen. Die Inhalte werden in einer Baumstruktur angezeigt und beim Klick auf ein Element, werden zusätzliche Informationen dargestellt. Das Programm bietet die Möglichkeit Class-Dateien zu dekompilieren. Der Decompiler dieses Programmes wurde in dieser Arbeit verwendet. Jdec 1 ist ein sehr umfangreiches Tool. Jdec unterstützt Class-Files bis zur Ver- Jdec sion 1.5. Die GUI von Jdec bietet eine Reihe von Funktionalitäten. Jdec besitzt einen Decompiler, kann Assemblercode anzeigen lassen, kann .jar Dateien önen und noch vieles mehr. Leider ist das Programm unübersichtlich und beim Einlesen gröÿerer Class-Files kann es zu Problemen kommen. Vergleich Im Rahmen der Arbeit sollte ein Programm geschrieben werden, dass ClassFiles einliest und deren Inhalt anzeigt. Diese Anforderung wird von Jbce am besten erfüllt. Der Inhalt des Class-Files wird in klare Bereiche unterteilt und die Navigation durch das Class-File gestaltet sich übersichtlich. Der Inhalt wird in einer ansprechenden Form dargestellt. Bei Jclazz wird das Clazzle nur sehr grob unterteilt, was bei gröÿeren Class-Files unübersichtlich werden kann, auÿerdem ist die Darstellung der Inhalte nicht so umfangreich, wie bei Jbce. Jdec bietet keine Möglichkeit, das Class-File genau zu betrachten, lediglich der KonstantenPool, lokale Variablen und ein paar Allgemeine Information können angezeigt werden. Jdec richtet sich eher an jemanden, für den hauptsächlich der Quellcode interessant ist. Jedoch erlaubt Jdec das einlesen von .jar Dateien, ein Feature, das bei Jbce und Jclazz Utils nicht vorhanden ist. Während Jbce und Jclazz Java 1.6 mehr oder weniger unterstützen, wird diese 1 Die Homepage von Jdec ist 16 http://jdec.sourceforge.net/. Version bei Jdec nicht unterstützt. Jclazz hat in diesem Punkt jedoch gegenüber Jbce auch einen Vorteil, da bei Jbce noch einzelne Elemente der Version 1.6 fehlen. Beim Parsen von gröÿeren Dateien kam es bei Jdec zu Probleme, währen Jbce und Jclazz keine Problemen mit komplexeren Class-Files haben. 17 5 Implementierung In diesem Kapitel wird die Funktionsweise des Programmes Java Byte Code Ex- plorer beschrieben, welches im Rahmen dieser Bachelorarbeit entwickelt wurde. Das Programm liest Java Class-Files ein und stellt den Inhalt anhand einer Baumstruktur dar. Der Benutzer kann durch die einzelnen Elemente der ClassFiles navigieren, dabei werden genauere Informationen über das selektierte Element angezeigt. Das Programm erlaubt das Önen mehrerer Class-Files und deren Darstellung. Das Programm selbst ist vollständig in Java [11] geschrieben. Als ParserGenerator wird ANTLR [1] verwendet. Für das Dekompilieren des Codes wird der Decompiler der Java Clazz Utils 1.2.2 [5] verwendet. Ein eigener Decompiler würde den Rahmen der Arbeit sprengen. Syntax Highlighting wird mit dem jEdit Syntax Paket [3] realisiert und wurde nicht selbst geschrieben, da es nur eine zusätzliche graphische Aufwertung darstellt. 5.1 Programmstruktur Das Programm ist in mehrere Pakete eingeteilt: • Classle Stellt Datenstrukturen zur Verfügung, welche den Inhalt eines Class-Files repräsentieren. Auÿerdem wird eine Vielzahl von Funktionen geboten die abgespeicherten Informationen auszuwerten. • Exceptions Das Paket enthält die verschiedenen Exceptions, die im Programm verwendet werden. • Gui Die graphische Schnittstelle ist in diesem Paket implementiert. Mit der graphischen Schnittstelle kann das Programm gesteuert werden. Über sie werden die Class-File Dateien, die eingelesen werden sollen ausgewählt und anschlieÿend dargestellt. • Parser Mit Hilfe dieses Paketes werden die Daten aus einem Class-File ausgelesen und in eine eigene Datenstruktur geschrieben. • Util Bietet einige Funktionalitäten, die im gesamten Programm verwendet werden. Hauptsächlich um Bytecode zu analysieren oder Daten umzuwandeln. 18 5.2 Funktionsweise 5.2 Funktionsweise Nachdem das Programm gestartet wurde, wird als erstes das Haupfenster dargestellt. Nun hat der Benutzer die Möglichkeit ein Class-File auszuwählen, welches eingelesen werden soll. Wenn eine Datei zum Parsen ausgewählt wurde, wird ein Parser Objekt wird erstellt, welches den Inhalt der Datei abarbeitet. Der Lexer erstellt Tokens aus den Bytes der Datei. Der Parser versucht die TokenSequenz vom Lexer anhand der Regeln der Grammatik, abzuarbeiten und dabei einen Parsetree zu erzeugen. Wenn dieser Vorgang erfolgreich war übergibt der Parser seinen erstellten Parsetree einem Tree Parser, der den Parsetree durchläuft und eine Datenstruktur erzeugt, die die gesammelten Informationen enthält. Anschlieÿend wird ein internes Fenster erzeugt, welches die vorhin erstellte Datenstruktur darstellen soll. Dazu wird eine Baumstruktur erstellt, die in die gleichen Bereiche aufgeteilt ist, wie das Class-File im vorigen Kapitel. Diese Baumstruktur enthält einerseits Daten aus dem Class-File und andererseits wird die Struktur verwendet, um durch das Class-File zu navigieren. Wenn das Class-File innere Klassen besitzt, wird auch deren Inhalt eingelesen und dargestellt. Wenn das Class-File samt innerer Klassen eingelesen wurde und die Struktur somit vollständig ist, wird sie im inneren Fenster dargestellt. Jetzt kann der Anwender durch den Baum navigieren und Elemente selektieren. Die ausgewählten Elemente werden auch im inneren Fenster, neben der Baumstruktur, dargestellt. 5.3 Grammatik Zentraler Bestandteil des Programmes ist die Grammatik Jbce.g, die erstellt wurde um den Inhalt von den Class-Files zu lesen. Die Grammatik ist eine Mischung aus der ANTLR - Grammatik Notation und Java Code. Die Tokens der Grammatik, also der Input für den Parser, sind die Zahlen 0-9 und Buchstaben A-F. Also die Werte 0 - 15 als hexadezimale Zeichen. Das Problem beim Parsen ist, dass die Struktur des Class-Files nicht allein mit der syntaktischen Analyse ermittelt werden kann. Anders als in einer Programmiersprache gibt es keine Schlüsselwörter oder andere Elemente wie den Strichpunkt, also Elemente, die zur Strukturierung verwendet werden. Für das Parsen eines Class-Files genügt keine kontextfreie Grammatik, da die Spezikation des Class-File Formates ein Sprache deniert, die sehr wohl kontext-sensitiv ist. Aus diesem Grund werden Prädikate verwendet. Als Beispiel dient die Regel attributes, welche die Attribute einer Klasse, Methode oder eines Feldes parst: attributes scope{int count; int index;} @init{$attributes::index=0;} : //attribute count u2 {$attributes::count = toInt($u2.text);} //attribute array 19 5 Implementierung ( {$attributes::index < $attributes::count}? attribute_info {$attributes::index++;} )* ; Die Regel deniert zuerst einmal zwei dynamische Variablen Scopes, und index, count vom Typ int. Der Wert von index, wird beim Eintreten in die Regel auf 0 gesetzt. Count erhält den Wert, der in repräsentiert u2 u2 gespeichert ist, in diesem Fall die Anzahl der Attribute. Dazu wird mit Regelname.text auf u2 liest zwei Bytes, also sind hier maximal 0xFFFF Attribute möglich. Anschlieÿend kommt ein geklammerter Ausdruck, der den Wert der Regel zugegrien. beliebig oft vorkommen kann, aufgrund des *, der hinter den beiden Klammern steht. Innerhalb dieses Audruckes bendet sich an erster Stelle das Prädikat {$attributes::index < $attributes::count}?, welches besagt, dass der Ausdruck abgebrochen wird, wenn index nicht mehr kleiner als count ist. Wenn die Bedingung jedoch gilt, wird attribute_info geparst und anschlieÿend index inkrementiert. Praktisch wird attribute_info genau count mal gelesen. Durch Konstrukte dieser Art wird eine Mehrdeutigkeit der erstellten Grammatik vermieden. Im Datei-Kopf wurde die Einstellung gesetzt, dass ANTLR als Ausgabe einen AST erstellen soll. ANTLR generiert also einen abstrakten Ableitungsbaum, der alles eingelesene enthält. Dieser AST wird dann an die Baumgrammatik JbceTree.g weitergegeben. Diese Baumgrammatik sieht der Grammatik Jbce.g sehr ähnlich. Rein von der Grammatik Notation ist sie etwas kürzer, da Elemente, wie zum Beispiel die Tokens der Ziern 0-9, ausgelassen werden können. Die Baumgrammatik durchläuft also das bereits geparste ein weiters mal. Der Grund dafür ist, dass das Problem durch die iterative Vorgehensweise vereinfacht wird. Würde nur eine Grammatik erstellt, wäre diese weitaus komplexer. Die Aufgabe der Baumgrammatik ist es anhand der vorhin erstellten Baumstruktur ein ClassFile Objekt zu erzeugen und es mit den Daten zu füllen. 5.4 Pakete im Detail Auf die wichtigsten drei Pakete, classfile, gui und parser wird im Folgenden näher eingegangen. 5.4.1 Classle Das Paket enthält alle Objekte, die verwendet werden um den Inhalt eines Class-Files zu repräsentieren. Die Datenstruktur ist hierarchisch aufgebaut. Das Wurzelelement bildet die Klasse ClassFile, in der man alle weiteren Objek- te des Paketes verschachtelt wieder ndet. Das Objekt enthält die Klassen FileHeader, ClassDescriptor, ConstantPool und Listen, welche die Method- en, Felder, Interfaces und inneren Klassen enthalten. Weiters werden Methoden 20 5.4 Pakete im Detail geboten, um auf die soeben angesprochenen Elemente, Eigenschaften und andere Attribute abzurufen. Das ClassFile Objekt wird beim Parsevorgang erzeugt, dabei werden die Inhalte nach und nach hinzugefügt. Wenn alle Daten abgespeichert wurden, muss das ClassFile Objekt noch aktualisiert werden, da Referenzen zu einigen der enthaltenen Daten erstellt werden müssen, die für die korrekte Darstellung in der GUI von Nöten sind. Classle.attribute In diesem Unterpaket ndet man die Objekte, die als Datenstrukturen für die Attribute der Klassen, Felder und Methoden dienen. Die Attribute, die Repräsentiert werden, werden im Kapitel 3 beschrieben. Jedes Attribut enthält eigene Daten, welche hier, aufgrund ihres Umfanges, nicht näher erläutert werden. Classle.constant Wie der Name schon vermuten lässt, handelt es sich hier um die Datenstrukturen für die Konstanten, also die Einträge im Konstanten-Pool. Jede Konstante die in Kapitel 3 beschrieben wurde, verfügt über ein eigenes Objekt. Einige der Konstanten verweisen auf andere Einträge im Pool (siehe Abbildung 3.1). Da diese Verweise zum Teil aufgelöst werden, also beispielsweise statt dem Index als Ganzzahl eine Referenz auf das entsprechende Objekt abgespeichert wird, muss der Konstanten-Pool nach seiner vollständigen Erstellung mehrmals durchlaufen werden um die entsprechenden Referenzen einzufügen. Classle.descriptor Bestimmte Konstanten verweisen auf Typinformationen, die in Utf8 Konstanten im Pool abgespeichert sind. Diese Typinformationen beschreiben die Signaturen von Methoden oder Feldern. Die dort enthaltenen Strings repräsentieren kodierte Typinformationen. Die Klassen in diesem Paket lesen diese Typinformationen aus und bieten Methoden, um auf die Inhalte zuzugreifen. Die Methode parseFile mit folgender Methoden-Signatur ClassFile parseFile(File file) hat den Deskriptor: (Ljava/io/File;)Lat/ac/uibk/jbce/classfile/ClassFile; Classle.signature Wenn Generics verwendet werden, erhalten Methoden, Klassen oder Felder ein Signatur-Attribut. Dieses Attribut enthält einen Verweis auf eine Utf8 Konstante im Pool. Der dort enthaltene String enthält die Informationen wiederum in einer kodierten Form, welche mit diesem Paket geparst werden kann. Zum Beispiel hätte die Methode <T extends ArrayList<? extends U>&Iterator> T methodname(V var){} 21 5 Implementierung die Signatur <T:Ljava/util/ArrayList<+TU;>;:Ljava/util/Iterator;>(TV;)TT; . 5.4.2 Gui Im gui Paket benden sich alle Klassen, die für die Darstellung der graphischen Schnittstelle benötigt werden. Wenn das Programm ausgeführt wird, erstellt der Controller, der ebenfalls im bildung gui Paket enthalten ist, das Hauptfenster (siehe Ab- 5.1) der Anwendung. Wenn ein Class-File ausgewählt wurde, startet der Controller den Parse Vorgang, indem er sich des Modules parser bedient. Da das Parsen ein zeitinten- sives Unterfangen ist, wird es vom Controller in einen eigenen Thread ausgelagert. Nach dem erfolgreichen Parsen der Datei, wird ein internes Fenster erstellt, dem die Class-File Datenstruktur übergeben wird. Diese internen Fenster werden auf dem Hauptfenster präsentiert. Vom Controller werden alle geparsten Class-Files zwischengespeichert. Sobald ein internes Fenster geschlossen wird, wird das Class-File und die dazugehörigen inneren Class-Files aus dem Cache entfernt. Interne Fenster werden von der Klasse JbceFrame repräsentiert. In- terne Fenster sind an das Hauptfenster gebunden. Jedes interne Fenster wird in zwei Teile geteilt um erstens die Baumstruktur des Class-Files und zweitens den ausgewählten Inhalt anzuzeigen. Die Baumstruktur wird von der Klasse Navigator implementiert View. und der gezeigte Inhalt ist eine Implementierung der Klasse Gui.navigator Die Klasse Navigator ist eine Erweiterung der Klasse JTree aus dem Swing Paket von Java. Bei der Erstellung eines Navigator-Objektes, wird die vom Parser erstellte ClassFile Instanz übergeben. Der Navigator durchläuft diese Datenstruktur und erstellt einen Baum, dessen Struktur an das Class-File angelehnt ist. Der Baum wird in die selben Bereiche eingeteilt, die auch in Kapitel 3 verwendet wurden. Jeder Knoten im Baum erhält ein Objekt zugewiesen, das die jeweiligen Daten enthält. Diese Objekte werden zur Visualisierung des selektierten Elementes verwendet. Die Knoten des Baumes sind Objekte vom Typ ClassFileTreeNode, eine Erweiterung des DefaultMutableTreeNode. Wenn der Navigator bei der Erstellung auf eine innere Klasse stöÿt, fügt er diese zu seinem ParseTaskScheduler hinzu. Die inneren Klassen werden erst im An- schluss, sobald die eigentliche Struktur erstellt wurde, geparst. Das Parsen der inneren Klassen erfolgt selbstverständlich auch in einem eigenen Thread. Enthält eine innere Klasse weitere innere Klassen werden diese wiederum in den ParseTaskScheduler eingefügt. Der ParseTaskScheduler stellt eine geordnete Fifo Warteschlange dar. Die Elemente werden nach ihrer Tiefe eingeordnet. Direkte innere Klassen des Wurzel Class-Files werden also vor den inneren Klassen der inneren Klassen geparst. Die Verschachtelung der Klassen kann nämlich etwas umständlich sein. Ein Beispiel: Klasse A hat zwei innere Klassen B und C. Es kommt vor, dass B wiederum C als innere Klasse deniert. Da JTree keine Duplikate zulässt, 22 5.5 Benutzerhandbuch müssten in so einem Fall zwei Knoten erstellt werden, die jedoch die gleichen Daten enthalten. Auf diese Redundanz wurde bewusst verzichtet. Innere Klassen werden immer nur ein einziges Mal zum Baum hinzugefügt. Wenn sie ein weiteres mal vorkommen, wird lediglich ein Knoten mit dem Namen der Klasse hinzugefügt. Der ParseTaskScheduler garantiert, dass die inneren Klassen immer auf der höchstmöglichen Ebene eingefügt werden. Gui.view Die Klasse ViewFactory erhält ein ClassFileTreeNode Objekt und entschei- det anhand des Knotens und dem dazugehörigen Objekt, welche View erstellt werden soll. Jede View erstellt eine Swing Komponente welche dann im internen Fenster dargestellt werden kann. Bei den meisten Views wird HTML Code generiert um die Darstellung graphisch aufzuwerten. Die ClassFileView und die MethodView generieren den Quellcode und Assemblercode für das gesamte Class-File bzw. für die Methode. Das Dekompilieren übernimmt, wie schon erwähnt, der Decompiler der Jclazz Utils. Assemblercode wird mit der BCEL Bibliothek, die Teil von Java ist, generiert. Die erstellten Views werden vom jeweiligen JbceFrame zwischengespeichert. Die Erzeugung der Views kann bei gröÿeren ClassFiles nämlich viel Zeit beanspruchen und so muss jede View nur einmal erzeugt werden. Vor allem das Syntaxhighlighting kann bei groÿen ClassFiles länger dauern. Deshalb wird die Erstellung einer View vom Controller in einen eigenen Thread ausgelagert. 5.4.3 Parser JbceParser, JbceTreeParser und der ByteLexer. Parser und TreeParser wurden mit ANTLR generiert, deshalb wird auch Im Parser Paket benden sich der nicht näher darauf eingegangen. Der ByteLexer ist eine eigene Lexer Implementation, für einen ANTLR-Parser. Der Lexer liest Byte für Byte aus einer Datei und erstellt sich pro Byte zwei Tokens. Jedes Token enthält den Wert von 4 Bit und entspricht somit einem Zeichen der hexadezimalen Darstellung. Somit lässt sich die Grammatik einfacher beschreiben und lesen. Der Lexer liest ein Byte, gibt eines der beiden Tokens zurück und puert das andere. Wenn das nächste Token angefordert wird, wird der Puer entleert. 5.5 Benutzerhandbuch Das Programm das im Rahmen dieser Arbeit geschrieben wurde, trägt den Namen Java ByteCode Explorer , kurz Jbce. Für die Ausführung des Programmes muss die Java Laufzeitumgebung Version 1.6 1 installiert sein. Das Programm muss nicht installiert werden, sondern ist als ausführbare jar Datei erhältlich. Die jar Datei lässt sich über die Kommandozeile mit java -jar Jbce.jar starten. Wenn das Programm ausgeführt wird, önet sich zuerst das Hauptfenster: 1 Die Java Laufzeitumgebung kann von http://www.java.com/de/ bezogen werden. 23 5 Implementierung Abbildung 5.1: Das Hauptfenster von Jbce. Das Hauptfenster enthält die Menüleiste (1), einen internen Desktop (2) auf dem mehrere interne Fenster (3) angeordnet werden können und eine Statusleiste (4). Um ein Class-File zu parsen muss auf das Menü Jbce und dann auf Open geklickt werden. Ein Dateiauswahl-Dialog önet sich mit dem ein Class-File ausgewählt werden kann: Abbildung 5.2: Datei-Auswahl Der Dateiauswahl-Dialog begrenzt die Auswahl durch einen Filter auf Dateien mit 24 .class-Endung. Nachdem ein Class-File ausgewählt wurde, startet der 5.5 Benutzerhandbuch Parse Vorgang. Wenn der Vorgang abgeschlossen ist önet sich ein inneres Fenster und zeigt den Inhalt der Class Datei an. Innere Fenster sind in zwei Bereiche eingteilt: Abbildung 5.3: Aufteilung eines Inneres Fenster Links (1) bendet sich der Navigator mit dessen Hilfe durch das Class-File navigiert werden kann. Wird ein Element selektiert, wird rechts (2) der Inhalt des gewählten Elementes dargestellt. Das ausgewählte Element wird rot umrandet, damit es gleich ins Auge sticht. Wenn ein Element ausgewählt oder ein Teilbaum expandiert wird, werden alle Subelemente inklusive dem gewählten Element mit einem blauen Hintergrund hervorgehoben. Die einzelnen Bereiche des ClassFiles wurden mit Icons versehen um einen besseren Überblick zu schaen. Bei den meisten Elementen besteht der Inhalt nur aus einer Seite. Bei manchen kann über Reiter zwischen Kategorien hin und her gewechselt werden, wie in der Abbildung oben. Jedes innere Fenster trägt den absoluten Pfad zur Klasse als Titel. Damit sind die Fenster leicht voneinander unterscheidbar. Zwischen den geöneten Fenstern kann auch über die Menüleiste hin und her gewechselt werden. Im Menü Windows sind alle geöneten Fenster aufgelistet. Wie schon erwähnt werden auch alle inneren Klassen geparst und dementsprechend verschachtelt dargestellt. Ein Beispiel dazu: 25 5 Implementierung Abbildung 5.4: Darstellung innerer Klassen Innere Klassen die beim Parsen nicht aundbar sind, werden übersprungen. Im Baum ndet sich dann ein Eintrag unable to parse . . . . Jedoch funktioniert das Dekompilieren nur, wenn alle inneren Klassen präsent sind. Wenn der Inhalt einer Datei nicht zur Grammatik passt wird der Parse Vorgang abgebrochen. Es erscheint ein Dialog der eine kurze Fehlermeldung enthält. Für genauere Informationen sollte auf die Konsolenausgabe geschaut werden. 26 6 Zusammenfassung Mögliche Verbesserungen Das Programm das im Rahmen dieser Arbeit entwickelt wurde, erfüllt die Anforderungen die gestellt wurden. Jedoch beschränkt sich die Funktionalität in einigen Fällen auf ein Minimum, da für eine umfangreiche Applikation mehr Zeit von Nöten gewesen wäre. Deshalb wird nun eine Liste von Möglichen zur Verbesserung und der Erweiterung des Programms aufgeführt. • Die Java Class-File Spezikation sieht weitere Attribute vor, die aus zeitlichen Gründen nicht implementiert wurden. Das StackMapTable -Attribut und die Annotations-Attribute. • Aktuell verwendet das Programm eine Bibliothek für die Generierung des Quellcode, da die Erstellung eines eigenen Decompilers den Rahmen der Arbeit überschritten hätte. • Erweiterte Funktionalitäten zur Erkennung von Fehlern in Java Class-Files sind in dieser Version nicht vorhanden. • Möglichkeiten zur Manipulation von Class-Files. • Unterstützung von .jar Dateien. Zusammenfassung Abschlieÿend kann man sagen, dass das Ziel der Arbeit, die Erstellung eines Parsers für Java Class-Files unter der Verwendung des ANTLR Frameworks, erreicht wurde. Es wurde eine Grammatik erstellt, die zum Parsen eines ClassFiles verwendet werden kann. Das Programm implementiert den aus dieser Grammatik erzeugten Parser. Die graphische Schnittstelle bietet die Möglichkeit mehrere Class-Files zur selben Zeit anzeigen zu lassen. Mit Hilfe des Navigators, der die Class-Files in verschiedene Bereiche gliedert, kann deren Inhalt bequem und verständlich angezeigt werden. Der Inhalt wird durch generierten HTMLCode in einer graphisch ansprechenden Form dargestellt. Zusätzlich werden die inneren Klassen der geparsten Klasse eingelesen und ebenfalls im Navigator dargestellt. 27 Literaturverzeichnis [1] ANother Tool for Language Recognition. http://www.antlr.org/. [2] Java Community Process, JSR-000202 JavaTM Class File Specication Update (Final Release). http://jcp.org/aboutJava/communityprocess/ final/jsr202/index.html. [3] jEdit 2.2.1 Syntax Paket. http://syntax.jedit.org/. [4] Alfred V. Aho and Jerey D. Ullman. and compiling. The theory of parsing, translation, Prentice-Hall, Inc., Upper Saddle River, NJ, USA, 1972. [5] Andrew Dmitriev. Java Clazz Utils 1.2.2. net/. http://jclazz.sourceforge. [6] John E. Hopcroft, Rajeev Motwani, and Jerey D. Ullman. to Automata Theory, Languages and Computability. Introduction Addison-Wesley Long- man Publishing Co., Inc., Boston, MA, USA, 2001. [7] Dexter C. Kozen. Automata and Computability. Springer-Verlag New York, Inc., Secaucus, NJ, USA, 1997. [8] Tim Lindholm and Frank Yellin. edition. Java Virtual Machine Specication, 2nd Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA, 1999. [9] Anton Nijholt. ing. Context-Free Grammars: Covers, Normal Forms, and Pars- Springer-Verlag New York, Inc., Secaucus, NJ, USA, 1980. [10] Terence Parr. Languages. The Denitive ANTLR Reference: Building Domain-Specic Pragmatic Bookshelf, 2007. [11] Sun/Oracle. Java - Programmiersprache. 28 http://java.sun.com/.