javaseiten.de   |   Version 0.6
 

5.9. Verschiedenes

 

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.

; 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
  ; append_frame (frameNumber = 0)
  ; frame_type = 253, offset_delta = 4
  ; frame bytes: 253 0 4 1 1 
  .stack 
    offset 4
    locals Integer
    locals Integer
    .end stack
  ; chop_frame (frameNumber = 1)
  ; frame_type = 250, offset_delta = 15
  ; frame bytes: 250 0 15 
  .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.

 

 

 

Diese Seite nutzt Google-Dienste - siehe dazu Datenschutz.

Copyright © 2006, 2007 Harald Roeder