Vergleich: Ausgabe von javap
mit Jasmin-Syntax
Innerhalb eines Java Development Kits (JDK) wird das Standard-Tool
javap
zur Verfügung gestellt und wird darin als "Java Class File
Disassembler" bezeichnet. Die Ausgabe von javap
bei Verwendung der
Option -c
ähnelt dabei einem gültigen Jasmin-Quelltext. In diesem
Zusammenhang wird javap
auch oftmals als unvollständiger
Disassembler für Java-Klassendateien bezeichnet. Die nachfolgende Tabelle
stellt die Ausgabe von javap -c
einem Jamin-Quelltext gegenüber.
Dabei wird als Beispiel Listing 4.2 verwendet. Durch
die Anweisung
javap -c BytecodeExample.class
kann eine entsprechende Ausgabe erhalten werden (linke Spalte der Tabelle).
Tabelle 5.3. Vergleich der Ausgabe des Klassendatei-Disassemblers javap -c
mit der Assembler-Syntax von Jasmin am Beispiel BytecodeExample.class
bzw. Listing 4.2.
Ausgabe javap -c |
Jasmin-Quelltext |
public class BytecodeExample
extends java.lang.Object
|
.class public BytecodeExample
.super java/lang/Object
|
public BytecodeExample();
Code:
0: aload_0
1: invokespecial #1; //Method java/
lang/Object."<init>":()V
4: return
|
.method public <init>()V
.limit stack 1
.limit locals 1
0: aload_0
1: invokespecial java/lang/Object/
<init>()V
4: return
.end method
|
public static void method1();
Code:
0: iconst_0
1: istore_0
2: iconst_0
3: istore_1
4: iload_1
5: bipush 10
7: if_icmpge 20
10: iload_0
11: iconst_2
12: iadd
13: istore_0
14: iinc 1, 1
17: goto 4
20: getstatic #2; //Field java/lang/
System.out:Ljava/io/PrintStream;
23: iload_0
24: invokevirtual #3; //Method java/
io/PrintStream.println:(I)V
27: return
|
.method public static method1()V
.limit stack 2
.limit locals 2
0: iconst_0
1: istore_0
2: iconst_0
3: istore_1
4: iload_1
5: bipush 10
7: if_icmpge 20
10: iload_0
11: iconst_2
12: iadd
13: istore_0
14: iinc 1 1
17: goto 4
20: getstatic java.lang.System.out
Ljava/io/PrintStream;
23: iload_0
24: invokevirtual java/io/PrintStream/
println(I)V
27: return
.end method
|
public static void main(java.lang.
String[]);
Code:
0: invokestatic #4;
//Method method1:()V
3: return
|
.method public static main([Ljava/lang/
String;)V
.limit stack 0
.limit locals 1
0: invokestatic BytecodeExample/
method1()V
3: return
.end method
|
Der in der Tabelle angegebene Jasmin-Quelltext wird vom Assembler
übersetzt. Da aber die Direktive .bytecode
nicht enthalten ist,
wird eine Versionsummer der zur erzeugenden Klassendatei von Jasmin
festgelegt. Die mit javac
(Version 1.6.x) erzeugte Klassendatei
enthält Attribute, die mittels javap -c
nicht ausgebenen werden.
Wird anstatt der Option -c
die Option -verbose
gewählt, enthält die Ausgabe von javap
diese Attribute
(SourceFile, LineNumberTable, StackMapTable) und weitere Informationen: Minor- und
Major-Versionsnummer der Klassendatei, Konstanten des Kontantenpools, Größe
des Operandenstapels (stack), Größe des Arrays der lokalen Variablen (locals) und
die Anzahl der Parameter der Methode.
> javap -verbose BytecodeExample
Compiled from "BytecodeExample.java"
public class BytecodeExample extends java.lang.Object
SourceFile: "BytecodeExample.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #6.#17; // java/lang/Object."<init>":()V
const #2 = Field #18.#19; // java/lang/System.out:Ljava/io/PrintStream;
const #3 = Method #20.#21; // java/io/PrintStream.println:(I)V
const #4 = Method #5.#22; // BytecodeExample.method1:()V
const #5 = class #23; // BytecodeExample
const #6 = class #24; // java/lang/Object
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Asciz LineNumberTable;
const #11 = Asciz method1;
const #12 = Asciz StackMapTable;
const #13 = Asciz main;
const #14 = Asciz ([Ljava/lang/String;)V;
const #15 = Asciz SourceFile;
const #16 = Asciz BytecodeExample.java;
const #17 = NameAndType #7:#8;// "<init>":()V
const #18 = class #25; // java/lang/System
const #19 = NameAndType #26:#27;// out:Ljava/io/PrintStream;
const #20 = class #28; // java/io/PrintStream
const #21 = NameAndType #29:#30;// println:(I)V
const #22 = NameAndType #11:#8;// method1:()V
const #23 = Asciz BytecodeExample;
const #24 = Asciz java/lang/Object;
const #25 = Asciz java/lang/System;
const #26 = Asciz out;
const #27 = Asciz Ljava/io/PrintStream;;
const #28 = Asciz java/io/PrintStream;
const #29 = Asciz println;
const #30 = Asciz (I)V;
{
public BytecodeExample();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static void method1();
Code:
Stack=2, Locals=2, Args_size=0
0: iconst_0
1: istore_0
2: iconst_0
3: istore_1
4: iload_1
5: bipush 10
7: if_icmpge 20
10: iload_0
11: iconst_2
12: iadd
13: istore_0
14: iinc 1, 1
17: goto 4
20: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
23: iload_0
24: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
27: return
LineNumberTable:
line 6: 0
line 7: 2
line 8: 10
line 7: 14
line 10: 20
line 11: 27
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 4
locals = [ int, int ]
frame_type = 250 /* chop */
offset_delta = 15
public static void main(java.lang.String[]);
Code:
Stack=0, Locals=1, Args_size=1
0: invokestatic #4; //Method method1:()V
3: return
LineNumberTable:
line 14: 0
line 15: 3
}
Die mit dem Jasmin-Quelltext aus Tabelle 5.3 erzeugte
Klassendatei enthält keine LineNumberTable-Attribute und kein StackMapTable-Attribut.
Diese können aber unter Verwendnung der Direktiven .line
und
.stack
ebenfalls erzeugt werden, wie das folgende Listing zeigt.
Listing 5.22. BytecodeExample.j
.
.bytecode 50.0
.source BytecodeExample.java
.class public BytecodeExample
.super java/lang/Object
.method public <init>()V
.limit stack 1
.limit locals 1
.line 3
0: aload_0
1: invokespecial java/lang/Object/<init>()V
4: return
.end method
.method public static method1()V
.limit stack 2
.limit locals 2
.line 6
0: iconst_0
1: istore_0
.line 7
2: iconst_0
3: istore_1
Label4:
4: iload_1
5: bipush 10
7: if_icmpge Label20
.line 8
10: iload_0
11: iconst_2
12: iadd
13: istore_0
.line 7
14: iinc 1 1
17: goto Label4
Label20:
.line 10
20: getstatic java/lang/System/out Ljava/io/PrintStream;
23: iload_0
24: invokevirtual java/io/PrintStream/println(I)V
.line 11
27: return
.stack
offset 4
locals Integer
locals Integer
.end stack
.stack
offset 20
locals Integer
.end stack
.end method
.method public static main([Ljava/lang/String;)V
.limit stack 0
.limit locals 1
.line 14
0: invokestatic BytecodeExample/method1()V
.line 15
3: return
.end method
Die Ausgabe zu javap -verbose
enthält eine Angabe zum
StackMapTable-Attribut der Klassendatei.
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 4
locals = [ int, int ]
frame_type = 250 /* chop */
offset_delta = 15
Der Zusammenhang dieser Angabe mit den .stack
-Direktiven des
Jasmin-Quelltextes soll näher erläutert werden.
Das Attribut enthält zwei Einträge, also zwei Frames. Die beiden Frames
sind vom Typ append_frame und chop_frame. Die beiden Frames innerhalb der Datei
BytecodeExample.class
können z.B. mittels eines Hexdumps lokalisiert
werden. Der erste Frame wird durch die dezimalen Zahlenwerte 253, 0, 4, 1, und 1
repräsentiert und der zweite Frame besteht aus 250, 0 und 15. Das erste Byte
gibt jeweils den Typ des Frames an. Wie der JVM-Spezifikation (Third Edition)
zu entnehmen ist, handelt es sich um einen append-Frame, falls das erste Byte
im Bereich von 252 bis 254 ist. Ein append-Frame hat die definierte Eigenschaft,
dass die Anzahl der stack
-Einträge null ist und dass für die Anzahl
der locals
gilt: Die Anzahl der locals
des append-Frames
ergibt sich aus der Anzahl der locals
des vorhergenden Frames, wobei
der append-Frame k zusätzliche locals
enthält. Für die Größe k gilt:
frame_type
- 251. Die erste .stack
-Direktive aus
Listing 5.22 enthält daher zweimal das Schlüsselwort
locals
. Die Ausgabe zu javap -verbose
enthält weiterhin
die Angaben offset_delta
. Für diese Werte gilt: Ein Wert
offset_delta
dient dazu, die tatsächliche Stelle im Bytecode
(Index im code-Array) zu berechnen, für die der Frame definiert ist. Die
tatsächliche Bytecodestelle ergibt sich zu: Addiere offset_delta + 1
zur tatsächlichen Bytecodestelle des vorhergenden Frames. Innerhalb einer
.stack
-Direktive steht hinter dem Schlüsselwort offset
die tatsächliche Bytecodestelle. Ein Ausnahme ergibt sich, falls der vorhergende
Frame der initiale Frame ist. Für diesen Fall gilt, dass die tatsächliche
Bytecodestelle dem Wert offset_delta
entspricht. Die Verwendung
des Wertes offset_delta
und eine Addition mit eins wird aus
Gründen der Praktikabilität verwendet (korrekteA Abfolge der Frames, ausschließen von
Frame-Duplikaten).
Der zweite Frame ist vom Typ chop_frame. Ein chop_frame beginnt mit einem Byte,
dessen dezimaler Wert im Bereich von 248 bis 250 liegt. Dieser Frametyp hat die
Eigenschaft, dass keine stack
-Einträge vorhanden sind und dass für
die Anzahl der locals
gilt: Die Anzahl der locals
des
chop-Frames ergibt sich aus der Anzahl der locals
des vorhergenden
Frames, wobei die letzten k locals
entfernt sind. Für die Größe k
gilt: 251 - frame_type
. Im Beispiel ergibt sich 251 - 205 = 1. Die
zweite .stack
-Direktive des betrachteten Jasmin-Listings enthält
daher nur einmal das Schlüsselwort locals
.
Bytecodierung eines Default-Konstruktors
Die Codierung des Default-Konstruktors aus Listing 5.1
innerhalb der Klassendatei HelloWorld.class
soll näher erläutert
werden, wodurch auch die Vorgehensweise des Assemblers bei der Übersetzung
verdeutlicht wird.
.method public <init>()V
.limit stack 1
.limit locals 1
aload_0
invokespecial java/lang/Object/<init>()V
return
.end method
Der Default-Konstruktor wird durch die JVM-Befehle
aload_0
,
invokespecial
und
return
realisiert.
aload_0
und return
werden innerhalb der durch Jasmin erzeugten
Klassendatei durch die Operationscodes 2a
und b1
codiert.
Ein JVM-Befehl invokespecial
besteht aber aus insgesamt drei Bytes, da
dem Operationcode b7
noch zwei Operandenbytes folgen, die eine Konstante
im Konstantenpool adressieren. Die Jasmin-Syntax erlaubt für den Aufruf des
paramterlosen Konstruktors der Klasse Object
:
invokespecial java/lang/Object/<init>()V
Klassenname: java/lang/Object
Methodenname: <init>
Methodendeskriptor: ()V
Auf die Mnemonik invokespecial
folgt die Angabe zur Klasse, in
der der aufzurufende Konstruktor definiert ist. Ein Default-Konstruktor hat nach
der JVM-Spezifiaktion definitionsgemäß den Namen <init>
. Das
leere Klammerpaar gibt an, dass der Konstruktor keine Argumente erwartet und die
Angabe V bedeutet, dass der Konstruktor der Klasse Object
keine
Werterückgabe besitzt. Aus diesen drei Angaben zum Klassennamen, zum Methodennamen
und zum Methodendeskriptor erzeugt der Java-Assembler insgesamt 6 Konstanten im
Konstantenpool der zu erzeugenden Klassendatei, im Beispiel
HelloWorld.class
:
(1). Konstante (Methodref): Methode von Klasse #(2), Name/Typ #(4)
(2). Konstante (Class): Klasse #(3)
(3). Konstante (Utf8): java/lang/Object
(4). Konstante (NameAndType): Name #(5), Typ #(6)
(5). Konstante (Utf8): <init>
(6). Konstante (Utf8): ()V
Die Nummerierung der Konstanten wurde in Klammern gesetzt, da die reale Position
der Konstanten im Konstantenpool der erzeugten Klassendatei von der Implementierung
des Assemblers abhängt. In der von HelloWorld.j
erzeugten Klassendatei
könnte dann an entsprechender Stelle die Byteabfolge b7 (00 01)
stehen.
Nach dem Opcode für invokespecial
codieren die beiden folgenden Bytes
die erste Konstante im Konstantenpool vom Typ Methodref. Mit Hilfe dieser Konstanten
werden alle weiteren fünf Konstanten adressiert, die alle Angaben zum Konstruktoraufruf
liefern. Der gesamt Default-Konstruktor im Beispiel wird auf die folgenden Bytes
innerhalb der korrespondierenden Klassendatei abgebildet:
Im Konstantenpool:
(1). Konstante: 0a 00 02 00 04
(2). Konstante: 07 00 03
(3). Konstante: 01 00 10 6a 61 76 61 2f 6c 61 32 6e 67 2f 4f 62 6a 65 63 74
(4). Konstante: 0c 00 05 00 06
(5). Konstante: 01 00 06 3c 69 6e 69 74 3e
(6). Konstante: 01 00 03 28 29 56
(7). Konstante: 01 00 04 43 6f 64 65
Im Methodenbereich:
00 01 00 05 00 06 00 01 00 07 00 00 00 11
00 01 00 01 00 00 00 05 2a b7 00 01 b1
00 00 00 00
Für den Konstantenpool wird zu den bereits genannten sechs Konstanten noch
eine weitere Konstante benötigt, die den Namen des Methodenattributs speichert.
Die erste notierte Zeile im Methodenbereich bedeutet:
00 01 - Konstruktor wurde mit public deklariert.
00 05 - Name des Konstruktors steht in der 5. Konstanten.
00 06 - Deskriptor des Konstruktors steht in der 6. Konstanten.
00 01 - Es existiert ein einziges Methodenattribut.
00 07 - Der Name des Methodenattributs steht in der 7. Konstanten.
(Typ: Utf8, repräsentiert die Zeichenkette "Code")
00 00 00 11 - Die Länge des Methodenattributs besteht aus 17 Bytes.
Die zweite notierte Zeile im Methodenbereich beginnt mit 00 01 00 01
.
Die ersten beiden Bytes resultieren aus .limit stack 1
und die
darauffolgenden Bytes werden durch .limit locals 1
erzeugt.
Die Bytes 00 00 00 05
geben die Länge des Code-Arrays an, das
aus den bereits erläuterten Bytes 2a b7 00 01 b1
besteht.
Die ersten beiden Bytes 00 00
der dritten notierten Zeile im
Methodenbereich geben an, dass keine Einträge in der Exception-Tabelle existieren
und die folgenden Bytes 00 00
besagen, dass das Methodenattribut
Code selber keine weiteren Attribute enthält.