javaseiten.de   |   Version 0.6
 

4.4. Bytecode Engineering Library (BCEL)

4.4.1. Einführung

Mit Listing 4.3 wurde ein kurzes Java-Programm vorgestellt, mit dem der Konstantenpool einer .class-Datei ausgelesen und analysiert werden kann. Mit der Bytecode Engineering Library (BCEL) steht eine API zur Verfügung, mit der u.a. alle Bereiche einer Klassendatei analysiert werden können. BCEL wird von der Apache Software Foundation herausgegeben und kann unter der Adresse

http://jakarta.apache.org/bcel/ linkextern.gif

bezogen werden. BCEL ist aber weit mehr als ein Analyse-API für Klassendateien. Mit Hilfe der Bibliothek kann eine Klassendatei von Grund auf zusammengesetzt werden. So können z.B. mit Hilfe eines Kostruktoraufrufs der Klasse ClassGen benötigte Informationen (Name der Klasse, Name der Superklasse, Zugangsinformationen, implementierte Interfaces) über eine neu zu erzeugende Klasse übergeben werden. Die anschließenden Zeilen zeigen dazu einen Ausschnitt aus Listing 4.6:

ClassGen cg = new ClassGen("BCELSynthesizeExample",
                           "java.lang.Object",
                           "BCELSynthesizeExample.java",
                           ACC_PUBLIC | ACC_SUPER,
                           null);

Die folgende Abbildung beinhaltet ein Klassendiagramm wichtiger BCEL-Klassen, die zum Analaysieren und Synthetisieren von Klassendateien genutzt werden können.

Abbildung 4.6. Klassendiagramm wichtiger BCEL-Klassen, die beim Analysieren (linker Teil der Klassenhierarchie) und Synthetisieren von Klassendateien (rechter Teil) zur Anwendung kommen.

classdiagrambcel.gif

Nachdem BCEL heruntergeladen wurde, wird das Archiv bcel-x.x.jar benötigt, um die Java-Beispielprogramme in diesem Abschnitt compilieren und ausführen zu können. Damit die Tools javac uns java die benötigten Klassen im Archiv finden können, besteht die Möglichkeit das BCEL-Archiv in entsprechende Ordner innerhalb des JDK und der JRE zu kopieren, um dieses nicht explizit in einen Klassenpfad (classpath) aufnehmen zu müssen:

lib/ext       [innerhalb der JRE]
jre/lib/ext   [innerhalb des JDK]

In Abschnitt 4.2 wurde die Unterteilung einer Klassendatei in zusammenhängende Byte-Blöcke geschildert. Die folgenden Unterabschnitte bauen auf diesen Erläuterungen auf.

Versionen (BCEL): Die Homepage von BCEL war bis zur Version 4.x http://bcel.sourceforge.net linkextern.gif. Ab der Version 5.0 wird BCEL im Rahmen eines Apache-Jakarta-Projekts weiterentwickelt.

4.4.2. Analysieren von Bytecode

Unter Verwendung der BCEL soll ein kurzes Java-Programm erstellt werden, das die einzelnen Bereiche einer Klassendatei analysiert und das Ergebnis auf dem Bildschirm ausgibt. Dazu muss zunächst eine .class-Datei eingelesen werden und ein Objekt vom Typ JavaClass erzeugt werden.

 

Einlesen von Bytecode

Die Klasse ClassParser stellt zwei Konstruktoren zur Verfügung. Es können als Argumente eine Zeichenkette mit dem Namen der zu untersuchenden Klassendatei und der Namen eines Archivs, in dem sich die Klassendatei befindet, übergeben werden.

org.apache.bcel.classfile
Class ClassParser
EXT API: BCEL 5.2

Konstruktor:
  ClassParser(String file_name)
  ClassParser(String zip_file, String file_name) 

Methode:
  JavaClass parse()

Die Methode parse analysiert (parst) die angegebne existierende .class-Datei und gibt ein Objekt vom Typ JavaClass zurück, das die Informationen der Klassendatei repräsentiert (z.B. Konstantenpool, JVM-Befehle). Der im folgenden Code-Ausschnitt aufgerufene Konstruktor kann eine IOException auslösen, falls auf die zu analysierende Klassendatei Test.class nicht zugegriffen werden kann. Die Methode parse kann zusätzlich eine Ausnahme des Typs ClassFormatException auslösen, falls die Klassendatei nicht gültig ist.

try {
  ClassParser parser = new ClassParser("Test.class");
  JavaClass clazz = parser.parse();
} catch (Exception e) {
  System.out.println(e.toString());
}

 

Auslesen einzelner Teilbereiche des Bytecodes

Nachdem nun die Informationen der Klassendatei auf ein Objekt vom Typ JavaClass abgebildet wurden, können eine Reihe von Methoden genutzt werden, um die einzelnen Bereich einer Klassendatei wie z.B. Konstantenpool, Feldbereich, Methodenbereich und Attribute analysieren zu können. Für jeden dieser Bereich wird eine eigene Methode zur Verfügung gestellt:

org.apache.bcel.classfile
Class JavaClass
EXT API: BCEL 5.2

Methode:
  byte[] getBytes()
  String getFileName()
  
  int getMinor()
  int getMajor()
  
  ConstantPool getConstantPool()
  
  int getClassNameIndex()
  int getSuperclassNameIndex()
  int[] getInterfaceIndices()
  
  Field[] getFields()
  
  Method[] getMethods()  
  
  Attribute[] getAttributes()

Auch ein Hexdump der zu untersuchenden Klassendatei kann mit der Methode getBytes sehr einfach generiert werden, indem diese ein byte-Array aus der Klassendatei erzeugt. JavaClass erweitert die abstrakte Klasse AccessFlags und erbt damit die Methode getAccessFlags, mit der der Zugangscode (Modifikatoren) der Klasse abgefragt werden kann.

org.apache.bcel.classfile
Class AccessFlags
EXT API: BCEL 5.2

Methode:
  int getAccessFlags()

 

Einzelne Konstanten aus dem Konstantenpool auslesen

Durch Aufruf von der Methode getConstantPool kann ein Objekt vom Typ ConstantPool erzeugt werden. Danach kann mit Hilfe von getLength die Anzahl der Konstanten im Konstantenpool ermittelt werden. Jede einzelnen Konstante kann dann, durch die innerhalb der Klasse ConstantPool definierte Methode getConstant, abgefragt werden, indem dieser ein Zahlenwert (Index im Pool) übergeben wird.

org.apache.bcel.classfile
Class ConstantPool
EXT API: BCEL 5.2

Methode:
  int getLength()
  Constant getConstant(int index)

Die Klasse Constant definiert eine Methode toString, mit der eine String-Repräsentation einer Konstanten auf dem Bildschirm ausgegeben werden kann. Mit dem folgeden Code-Ausschnitt können z.B. die einzelnen Konstanten im Pool auf dem Bildschirm ausgegeben werden, wobei zuvor ein Objekt clazz vom Typ JavaClass beschafft wurde:

ConstantPool cp = clazz.getConstantPool();
int cpl = cp.getLength() - 1;
System.out.println("Anzahl der Konstanten im Konstantenpool: " + cpl); 
for (int i = 1; i <= cpl; i++) {
  Constant c = cp.getConstant(i); 
  System.out.println(i + ". Konstante: " + c.toString());
}

 

Auslesen von Feld- und Methodeninformationen

Die abstrakte Klasse FieldOrMethod definiert Methoden, die sowohl für Felder als auch für Methoden genutzt werden können.

org.apache.bcel.classfile
Class FieldOrMethod
EXT API: BCEL 5.2

Methode:
  int	getNameIndex()
  String getName()
   
  int	getSignatureIndex()
  String getSignature()
  
  Attribute[] getAttributes()

Mit getNameIndex wird der Index der Namensbezeichnung des Feldes bzw. der Methode im Konstantenpool abgefragt. Die Konstante im Pool bei diesem Index ist vom Typ Utf8 und speichert den Namen als Zeichenkette, der mit getName ausgelesen werden kann. Die Methode getSignatureIndex liefert ebenfalls einen Index im Konstantenpool. Die adressierte Konstante im Pool ist ebenfalls vom Typ Utf8 und speichert einen Felddeskriptor bzw. einen Methodendeskriptor, der von getSignature zurückgegeben wird. Sowohl Felder als auch Methoden können mehrere Attribute enthalten. Der Aufruf von getAttributes gibt ein Attribute-Array zurück, mit dem die Informationen der Attribute extrahiert werden können. Die abstrakte Klasse FieldOrMethod ist die Superklasse von Field bzw. Method, die diese Methoden erben. Ist ein Objekt clazz vom Typ JavaClass verfügbar, können die genannten Methoden zur Analyse des Feld- und Methodenbereiches angewendet werden.

Field[] f = clazz.getFields();
System.out.println("Anzahl der Felder der Klasse: " + f.length);
for (int i = 0; i < f.length; i++) {
  System.out.println((i+1) + ". Feld:");
  System.out.println("  Zugangscode: " + f[i].getAccessFlags()); 
  System.out.println("  Name und Index im Konstantenpool: " + 
      f[i].getName() + ", " + f[i].getNameIndex());
  System.out.println("  Deskriptor und Index im Konstantenpool: " + 
      f[i].getSignature() + ", " + f[i].getSignatureIndex());
  Attribute[] fieldAttr = f[i].getAttributes();
  System.out.println("  Anzahl der Feld-Attribute: " + fieldAttr.length);
}

Method[] m = clazz.getMethods();
System.out.println("Anzahl der Methoden der Klasse: " + m.length);
for (int i = 0; i < m.length; i++) {
  System.out.println(i+1) + ". Methode:");
  System.out.println("  Zugangscode: " + m[i].getAccessFlags());
  System.out.println("  Name und Index im Konstantenpool: " + 
      m[i].getName() + ", " + m[i].getNameIndex());
  System.out.println("  Deskriptor und Index im Konstantenpool: " + 
      m[i].getSignature() + ", " + m[i].getSignatureIndex());
  Attribute[] methodAttr = m[i].getAttributes();
  System.out.println("  Anzahl der Methoden-Attribute: " + methodAttr.length);
}

 

Auslesen von Attributinformationen

Für Attribute, wie sie in der Java Virtual Machine Specification definiert werden, wird innerhalb der BCEL eine eigene Klasse definiert. Für Attribute, die nicht standardmäßig durch die Spezifikation definiert sind, wird die Klasse Unknown zur Verfügung gestellt.

Abbildung 4.7. Klassendiagramm: Wichtige Spezialisierungen der abstrakten Superklasse Attribute.

classdiagramattribute.gif

Die abstrakte Superklasse Attribute definiert die folgenden drei Methoden, die durch Vererbung auch den abgeleiteten Klassen zur Verfügung stehen.

org.apache.bcel.classfile
Class Attribute
EXT API: BCEL 5.2

Methode:
  int getNameIndex()
  byte getTag()
  String toString()

Durch Aufruf von getNameIndex wird ein Index im Konstantenpool ermittelt. Die adressierte Konstante im Pool speichert den Namen des Attributs (z.B. "ConstantValue" oder "Code"). Die Methode toString wird innerhalb der Ableitungen der abstrakten Klasse Attribute überschreiben (überlagert) und gibt eine String-Repräsentation der Informationen im Attribut wieder. Attribute wie z.B. ConsantValue oder Code sind unterschiedlich strukturiert und speichern unterschiedliche Daten; toString wird daher in den entsprechenden Klassen ConstantValue bzw. Code ensprechend angepasst. Jedem Attribut wird ein Zahlenwert zugeordnet, der als "Tag des Attributs" bezeichnet wird und mit getTag abgerufen werden kann.

Attribut Tag
SourceFile 0
ConstantValue 1
Code 2
Exceptions 3
LineNumberTable 4
LocalVariableTable 5
InnerClasses 6
Synthetic 7
Deprecated 8
Signature 10
Unknown -1

Diese Tags sind innerhalb des Interfaces Constants gespeichert und können mit ihren zugeordneten Namensbezeichnungen angesprochen werden.

org.apache.bcel
Interface Constants
EXT API: BCEL 5.2

Feld:
  static byte ATTR_SOURCE_FILE
  static byte ATTR_CONSTANT_VALUE
  static byte ATTR_CODE
  static byte ATTR_EXCEPTIONS
  static byte ATTR_LINE_NUMBER_TABLE
  static byte ATTR_LOCAL_VARIABLE_TABLE
  static byte ATTR_INNER_CLASSES
  static byte ATTR_SYNTHETIC
  static byte ATTR_DEPRECATED
  static byte ATTR_SIGNATURE
  static byte ATTR_UNKNOWN

Attribute können in verschiedenen Bereichen einer Klassendatei auftreten: Feldbereich, Methodenbereich oder Klassenbereich (Attributabschnitt direkt im Anschluss an den Methodenbereich). Attribute können auch innerhalb von Attributen selbst auftreten, so ist z.B. ein LineNumberTable-Attribut innerhalb eines Code-Attributs zulässig. BCEL definiert für jeden dieser Bereich eine Klasse, in der jeweils eine Methode getAttributes definiert wird, die vorhandene Attribute ausliest. Ausgangspunkt ist wiederum ein Objekt clazz vom Typ JavaClass.

Attribute[] classAttr = clazz.getAttributes();

Field[] f = clazz.getFields();
Attribute[] fieldAttr = f[0].getAttributes();
Attribute[] fieldAttr = f[1].getAttributes();
...

Method[] m = clazz.getMethods();
Attribute[] methodAttr = m[0].getAttributes();
Attribute[] methodAttr = m[1].getAttributes();
...

Code codeAttr = m[0].getCode();
Attribute[] codeAttrAttr = codeAttr.getAttributes();
Code codeAttr = m[1].getCode();
Attribute[] codeAttrAttr = codeAttr.getAttributes();
...

Die Elemente (Objekte) des Attribute-Arrays sind definitionsgemäß vom Typ Attribute. Um festzustellen, um welches spezielle Attribut (z.B. Code- oder Exceptions-Attribut) es sich handelt kann der instanceof-Operator oder die Methode getTag verwendet werden.

Method[] m = clazz.getMethods();
Attribute[] methodAttr = m[0].getAttributes();
...

if (methodAttr[0] instanceof Code) { ... }
if (methodAttr[0] instanceof Exceptions { ... }
...

byte tag = methodAttr[0].getTag();
if (tag == Constants.ATTR_CODE) { ... }
if (tag == Constants.ATTR_EXCEPTIONS) { ... }
...

Der instanceof-Operator ermittelt zu welcher Klasse ein Objekt gehört. In einem Attribute-Array können alle Objekte enthalten sein, die von einer Unterklasse der abstrakten Klasse Attribute erzeugt wurden. Für die Konstante Constants.ATTR_CODE könnte auch der entsprechende Zahlenwert 2 eingesetzt werden. Als Beispiel für das Auslesen von Attributinformationen soll das folgende kurze Listing dienen, das aus einer bestehenden Klassendatei die Code-Bytes (JVM-Befehle) ausliest. Die Code-Bytes sind wiederum Bestandteil der Code-Attribute, die sich innerhalb des Methodenbereichs der Klassendatei befinden.

Listing 4.4. BCELAnalyzeExample.java. Liest die Code-Bytes (JVM-Befehle) aus den Code-Attributen der Beispielklasse BytecodeExample.class aus (siehe Listing 4.2 bzw. Abbildung 4.3).

/* BCELAnalyzeExample.java */

import org.apache.bcel.*;
import org.apache.bcel.classfile.*;

public class BCELAnalyzeExample {

  public static void main(String[] args) {

    JavaClass clazz = null;
    try {
      ClassParser parser = new ClassParser("BytecodeExample.class");
      clazz = parser.parse();
    } catch (Exception e) {
      System.out.println(e.toString());
    }
    
    Method[] m = clazz.getMethods();
    
    for (int i = 0; i < m.length; i++) {
      Attribute[] methodAttr = m[i].getAttributes();
      for (int j = 0; j < methodAttr.length; j++) {
        byte tag = methodAttr[j].getTag();
        if (tag == Constants.ATTR_CODE) {
          Code c = (Code)methodAttr[j];
          byte[] ca = c.getCode();
          System.out.print((i+1) + ". Methode, Code-Bytes: ");
          for (int k = 0; k < ca.length; k++) {
            System.out.print(ca[k] + " ");
          }
          System.out.println();
        }  
      }
    }

  }
}

Das Beispiel erzeugt die Ausgabe:

1. Methode, Code-Bytes: 42 -73 0 1 -79 
2. Methode, Code-Bytes: 3 59 3 60 27 16 10 -94 0 13 26 5 96 59 -124 1 
                        1 -89 -1 -13 -78 0 2 26 -74 0 3 -79 
3. Methode, Code-Bytes: -72 0 4 -79

Die ausgegebenen Zahlenwerte liegen im Bereich von -128 bis 127. In hexadezimaler Schreibweise lauten sie (siehe dazu auch Abschnitt 4.2.2):

2a b7 00 01 b1

03 3b 03 3c 1b 10 0a a2 00 0d 1a 05 60 3b 84 01
01 a7 ff f3 b2 00 02 1a b6 00 03 b1

b8 00 04 b1

Innerhalb des Listings wird die Methode getCode verwendet, die innerhalb der Klasse Code definiert ist. Die Methode gibt als Rückgabewert ein byte-Array zurück, welches die gesuchten Code-Bytes enthalten. Da die Klasse Code auf das Code-Attribut "zugeschnitten" ist, stehen auch weitere Methoden zum Auslesen von Informationen speziell für dieses Attribut zur Verfügung.

org.apache.bcel.classfile
Class Code
EXT API: BCEL 5.2

Methode:
  Attribute[] getAttributes()
  byte[] getCode()
  CodeException[] getExceptionTable()
  LineNumberTable getLineNumberTable()
  LocalVariableTable getLocalVariableTable()
  
  int getMaxLocals()
  int getMaxStack()
  
  String toString()

Der Aufruf von getExceptionTable gibt eine Exception-Tabelle (ist kein Attribut) zurück, die durch eine try-catch-Anweisung im Java-Quelltext hervorgerufen wird. Der Rückgabewert ist ein CodeException-Array, wobei jedes Element dieses Arrays einen Eintrag in der Exception-Tabelle repräsentiert. Die Klasse CodeException definiert wiederum nützliche Methoden mit denen ein Eintrag der Tabelle ausgelesen werden kann: getStartPC, getEndPC, getHandlerPC und getCatchType. Die Inhalte der beiden Attribute LineNumberTable und LocalVariableTable, die innerhalb eines Code-Attributs stehen, können direkt mit getLineNumberTable und getLocalVariableTable ermittelt werden, indem die entsprechenden Rückgabewerte ausgewertet werden. Dazu stellen die Klassen LineNumberTable und LocalVariableTable passende Methoden zur Verfügung. Der Rückgabewert von getMaxLocals ist die maximale Anzahl der lokalen Variablen im Array der lokalen Variablen und getMaxStack gibt die maximale Größe des Operandenstapels zurück. Die Methode toString überlagert die gleichnamige Methode aus der abstrakten Superklasse Attribute und gibt eine komplette String-Repräsentation des Code-Attributs wieder. Wird z.B. in Listing 4.4 die Modifikation

Code c = (Code)methodAttr[j];
System.out.println(c.toString()); // neu

vorgenommen, kann die folgende Ausgabe erzeugt werden:

Code(max_stack = 2, max_locals = 2, code_length = 28)
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; (2)
23: iload_0
24: invokevirtual java.io.PrintStream.println (I)V (3)
27: return

Attribute(s) =
LineNumber(0, 6), LineNumber(2, 7), LineNumber(10, 8), LineNumber(14, 7),
LineNumber(20, 10), LineNumber(27, 11)

Eine komplette Beschreibung der Methoden, die zum Auslesen von Attributinformationen zur Verfügung stehen, kann innerhalb der API-Dokumentation zu BCEL nachgelesen werden. Die Dokumentation ist ebenfalls auf den BCEL-Seiten im Internet vorhanden.

 

Beispielprogramm zur Analyse einer Klassendatei

Das kurze Beispielprgramm aus Listing 4.4 nutzt bereits die Bytecode Engineering Library, um die Bytecode-Repräsentation von JVM-Befehlen aus einer Klassendatei auszulesen. Das folgenden Listing 4.5 ist ein Analyse-Tool, mit dem eine vorliegende .class-Datei umfangreich analysiert werden kann. Es können drei Optionen beim Start des Programms angegeben werden. Zusätzlich kann auch ein JAR-Archiv notiert werden, falls sich die zu analysierende Klassendatei in diesem befindet.

java ClassFileAnalyzer [options] [jarfile] class

  options
    -hex
      Die Ausgabe enthält einen Hexdump der Klassendatei.
    -ai
      Die Ausgabe enthält Attribut-Informationen.
    -f
      Die Ausgabe erfolgt nicht auf dem Bildschirm, sondern wird in eine 
      Textdatei geschrieben (bei längeren Analyseausgaben).
  
  jarfile
    Ein JAR-Archiv, in dem sich die zu analysierende 
    Klassendatei class befindet.
  
  class
    Die zu analysierende Klassendatei.

Das Analyse-Tool ClassFileAnalyzer nutzt BCEL. Die Klassen innerhalb des Archivs bcel-x.x.jar müssen deshalb vom Java-Compiler javac und dem Java-Launcher java gefunden werden können.

Listing 4.5. ClassFileAnalyzer.java. Mit Hilfe des Listings wird der Bytecode innerhalb einer Klassendatei analysiert. Dabei wird die BCEL-Klassenbibliothek von apache.org verwendet.

/* ClassFileAnalyzer.java */

import org.apache.bcel.*;
import org.apache.bcel.classfile.*;
import java.util.*;
import java.io.*;

public class ClassFileAnalyzer {
  
  JavaClass clazz;
  ConstantPool cp;
  StringBuffer out = new StringBuffer();
  
  public void getClazz(String s1, String s2) {
    ClassParser parser;
    try {
      if (s2 == null) {
        parser = new ClassParser(s1);
      } else {
        parser = new ClassParser(s1, s2);
      }  
      clazz = parser.parse();
      out.append("Analyze " + clazz.getFileName() + "\n\n");
    } catch (Exception e) {
      System.out.println(e.toString());
      System.out.println("Die angegebene Datei kann nicht eingelesen werden!");
      System.exit(2);
    }  
  }
  
  public void hexDump(String str) {
    int bi;
    byte[] b = clazz.getBytes();  
    out.append("Hexdump (" + b.length + " Bytes):\n");
    for (int i = 0; i < b.length; i++) {
      bi = (new Byte(b[i])).intValue();
      bi = bi & 0x000000ff;
      String s = Integer.toHexString(bi);
      if (s.length() == 1) { s = "0" + s; }
      out.append(s + " ");
      if (((i+1) % 16) == 0) {
        out.append("     " + (i-15) + "\n");
      }  
    }
    out.append("\n\n");
  }
  
  public void getMinorMajor() {
    int minor = clazz.getMinor();
    int major = clazz.getMajor();
    out.append("Version des Klassendatei-Formats: " + 
        major + "." + minor + "\n\n");
  }  
  
  public void getConstants() {
    cp = clazz.getConstantPool();
    int cpl = cp.getLength() - 1;
    out.append("Anzahl der Konstanten im Konstantenpool: " + cpl + "\n"); 
    for (int i = 1; i <= cpl; i++) {
      Constant c = cp.getConstant(i); 
      if (c != null) {  // Long- und Double-Konstanten beachten!
        out.append(i + ". Konstante: " + c.toString() + "\n");
      }  
    }
    out.append("\n");
  }  
  
  public void getAccessThisSuper() {
    int caf = clazz.getAccessFlags();
    out.append("Klassen-Zugangscode: " + caf + "\n");
    int cni = clazz.getClassNameIndex();
    out.append("Klasse (Index im Konstantenpool): " + cni + "\n");
    int sni = clazz.getSuperclassNameIndex();
    out.append("Superklasse (Index im Konstantenpool): " + sni + "\n\n");
  }  
  
  public void getInterfaces() {
    int[] ii = clazz.getInterfaceIndices();
    out.append("Anzahl der implementierten Interfaces: " + ii.length + "\n");
    if (ii.length > 0) {
      out.append("Implementierte Interfaces (Index im Konstantenpool): ");
      for (int i = 0; i < ii.length; i++) {
        out.append(ii[i] + " ");
      }
      out.append("\n\n");
    }  
  }  
  
  public void getFields(boolean b) {
    Field[] f = clazz.getFields();
    out.append("Anzahl der Felder der Klasse: " + f.length + "\n");
    for (int i = 0; i < f.length; i++) {
      out.append((i+1) + ". Feld:\n");
      out.append("  Zugangscode: " + f[i].getAccessFlags() + "\n"); 
      out.append("  Name und Index im Konstantenpool: " + 
          f[i].getName() + ", " + f[i].getNameIndex() + "\n");
      out.append("  Deskriptor und Index im Konstantenpool: " + 
          f[i].getSignature() + ", " + f[i].getSignatureIndex() + "\n");
      Attribute[] fieldAttr = f[i].getAttributes();
      out.append("  Anzahl der Feld-Attribute: " + fieldAttr.length + "\n");
      for (int j = 0; j < fieldAttr.length; j++) {
        getAttributeInfo(fieldAttr[j], j, "    ", b);
      }  
    }  
    out.append("\n");
  }

  public void getMethods(boolean b) {
    Method[] m = clazz.getMethods();
    out.append("Anzahl der Methoden der Klasse: " + m.length + "\n");
    for (int i = 0; i < m.length; i++) {
      out.append((i+1) + ". Methode:\n");
      out.append("  Zugangscode: " + m[i].getAccessFlags() + "\n");
      out.append("  Name und Index im Konstantenpool: " + 
          m[i].getName() + ", " + m[i].getNameIndex() + "\n");
      out.append("  Deskriptor und Index im Konstantenpool: " + 
          m[i].getSignature() + ", " + m[i].getSignatureIndex() + "\n");
      Attribute[] methodAttr = m[i].getAttributes();
      out.append("  Anzahl der Methoden-Attribute: " + methodAttr.length + "\n");
      for (int j = 0; j < methodAttr.length; j ++) {
        getAttributeInfo(methodAttr[j], j, "    ", b);
      }  
    }  
    out.append("\n");
  }  
  
  public void getClassAttributes(boolean b) {
    Attribute[] classAttr = clazz.getAttributes();
    out.append("Anzahl der Attribute der Klasse: " + classAttr.length + "\n");
    for (int j = 0; j < classAttr.length; j++) {
      getAttributeInfo(classAttr[j], j, "", b);
    }  
  }  
  
  public void getAttributeInfo(Attribute attr, int index, String spaces, 
      boolean info) {
    int fni = attr.getNameIndex();
    out.append(spaces  + (index+1) + ". Attribut:\n");
    ConstantUtf8 cutf8 = (ConstantUtf8)cp.getConstant(fni, 
        Constants.CONSTANT_Utf8);
    out.append(spaces + "  Typ und Index im Konstantenpool: " + 
        cutf8.getBytes() + ", " + fni + "\n");
    if (info) {
      byte tag = attr.getTag();
      String attrStr = attr.toString();
      // Texteinrueckung
      String[] asa = attrStr.split("\n");
      out.append(spaces + "  Attribut-Beschreibung (tag=" + tag + "):\n");
      for (int i = 0; i < asa.length; i++) {
        asa[i] = asa[i].trim();
        if (!asa[i].equals("")) out.append(spaces + "    " + asa[i] + "\n");
      }
    }  
  }  
  
  public void output(boolean b, String s) {
    if (b) {
      int i = s.lastIndexOf("/");
      s = s.substring(i+1); String str = s + ".txt";
      try {
        FileWriter fw = new FileWriter(str);
        fw.write(out.toString());
        fw.flush(); fw.close();
      } catch (Exception e) {
        e.printStackTrace();
      }  
      System.out.println("Datei " + str + " im aktuellen Verzeichnis erstellt.");
    } else {
      System.out.println(out.toString());
    }  
  }  
  
  public static void main(String[] args) {
    boolean fileOutput = false; boolean attrInfo = false;
    boolean hexDump = false; boolean option = true;
    String fileName = "";
    Vector<String> options = new Vector<String>();
    Vector<String> sources = new Vector<String>();
    ClassFileAnalyzer analyzer = new ClassFileAnalyzer();
    
    for (int i = 0; i < args.length; i++) {
      if ((args[i].startsWith("-")) && (option == true)) {
        options.add(args[i]);
      } else {
        option = false;
        sources.add(args[i]);
      }  
    }
    for (int j = 0; j < options.size(); j++) {
      String str = options.get(j);
      if (str.equals("-f")) fileOutput = true;
      if (str.equals("-ai")) attrInfo = true;
      if (str.equals("-hex")) hexDump = true;
    }  
    
    if (sources.size() == 1) {
      analyzer.getClazz(sources.get(0), null);
      fileName = sources.get(0);
    } else if (sources.size() == 2) {
      analyzer.getClazz(sources.get(0), sources.get(1));
      fileName = sources.get(1);
    }  else {     
      System.out.println("usage: java ClassFileAnalyzer " + 
          "[options] [jarfile] class");
      System.exit(1);
    }  
    if (hexDump) { analyzer.hexDump(fileName); }
    analyzer.getMinorMajor();
    analyzer.getConstants();
    analyzer.getAccessThisSuper();          
    analyzer.getInterfaces();
    analyzer.getFields(attrInfo);
    analyzer.getMethods(attrInfo);       
    analyzer.getClassAttributes(attrInfo);
    analyzer.output(fileOutput, fileName);
  }  
}

Mit dem Beispielaufruf

> java ClassFileAnalyzer -f -ai -hex c:/jdk/jre/lib/rt.jar java/lang/String.class

kann die Klassendatei String.class, die standardmäßig im Archiv rt.jar des JDK enthalten ist analysiert werden (es ist vermutlich erforderlich den Pfad zum JAR-Archiv anzupassen). Die Ausgabe wird durch die Verwendung der Option -f in eine Datei mit dem Namen String.class.txt in das aktuelle Verzeichnis gespeichert (die vollständige Datei enthält ca. 4800 Zeilen, abhängig von der Version des JDK):

Analyze java/lang/String.class

Hexdump (14783 Bytes):
ca fe ba be 00 00 00 31 01 a4 03 00 00 ff ff 03      0
00 01 00 00 03 00 10 ff ff 08 00 42 08 00 45 08      16
00 5b 08 00 87 08 00 8b 08 00 a4 08 00 a6 01 00      32
...

Version des Klassendatei-Formats: 49.0

Anzahl der Konstanten im Konstantenpool: 419
1. Konstante: CONSTANT_Integer[3](bytes = 65535)
2. Konstante: CONSTANT_Integer[3](bytes = 65536)
...

Klassen-Zugangscode: 49
Klasse (Index im Konstantenpool): 188
Superklasse (Index im Konstantenpool): 187

Anzahl der implementierten Interfaces: 3
Implementierte Interfaces (Index im Konstantenpool): 172 177 175 

Anzahl der Felder der Klasse: 7
1. Feld:
  Zugangscode: 18
  Name und Index im Konstantenpool: value, 167
  Deskriptor und Index im Konstantenpool: [C, 63
  Anzahl der Feld-Attribute: 0
...

Anzahl der Methoden der Klasse: 83
1. Methode:
  Zugangscode: 1
  Name und Index im Konstantenpool: <init>, 50
  Deskriptor und Index im Konstantenpool: ()V, 13
  Anzahl der Methoden-Attribute: 1
    1. Attribut:
      Typ und Index im Konstantenpool: Code, 52
      Attribut-Beschreibung (tag=2):
        Code(max_stack = 2, max_locals = 1, code_length = 22)
        0:    aload_0
        1:    invokespecial	java.lang.Object.<init> ()V (362)
        4:    aload_0
...

Anzahl der Attribute der Klasse: 3
1. Attribut:
  Typ und Index im Konstantenpool: SourceFile, 62
  Attribut-Beschreibung (tag=0):
    SourceFile(String.java)
...

 

Die Utility-Klasse Class2HTML

Mit Listing 4.5 kann eine Klassendatei analysiert werden, wobei die Ausgabe auf dem Bildschirm oder in eine Textdatei erfolgen kann. BCEL enthält innerhalb des Pakets org.apache.bcel.util die Klasse Class2HTML, die eine bestehende Klassendatei einliest, analysiert und das Ergebnis in html-Dateien einbettet. Die Ausgabedateien können dann mit einem Browser angezeigt werden. Class2HTML kann z.B. genutzt werden, um die Klassendatei BytecodeExample.class zu analysieren (siehe Listing 4.2 bzw. Abbildung 4.3):

> java org.apache.bcel.util.Class2HTML BytecodeExample.class

Durch die Anweisung werden automatisch die folgenden fünf html-Datein ins aktuellen Verzeichnis geschrieben:

BytecodeExample.html
BytecodeExample_attributes.html
BytecodeExample_code.html
BytecodeExample_cp.html
BytecodeExample_methods.html

Versionen (JDK 6): Es kann zu einer Fehlermeldung bei der Erzeugung der html-Dateien kommen, falls Klassendateien mit Class2HTML (BCEL 5.2) analysiert werden, die durch den Java-Compiler javac in der Version 1.6.0 erzeugt wurden. Abhilfe: Klassendatei mit javac -target 5 Test.java erzeugen.

Die html-Datei BytecodeExample.html kann nun mit einem Browser aufgerufen werden. Da diese Datei Framesets enthält erfolgt die Anzeige aller weiteren html-Dateien in unabhängigen Bereichen des Anzeigefensters der Browsers.

Abbildung 4.8. Browseranzeige von BytecodeExample.html. Es werden Bytecode-Informationen zur Klassendatei BytecodeExample.class angezeigt.

bcelclass2html.jpg

4.4.3. Synthetisieren von Bytecode

Die im "Gen-Teil" der BCEL API bereitgestellten Klassen wie ClassGen, MethodGen oder ContantPoolGen können z.B. ber der Erstellung des Backends (Synthesephase) eines Compilers verwendet werden. Dieser Abschnitt der API ist im Paket org.apache.bcel.generic zusammengefasst. Es bestehen starke statische Abhängigkeiten zwischen dem Konstantenpool und dem Methodenbereich einer Klassendatei, da vom Methodenbereich aus häufig auf Konstanten mit einem festen Index im Pool zugegriffen wird. Bei der Erstellung einer Methode müssten gleichzeitig auch abhängige Einträge im Konstantenpool getätigt werden. Diese Einträge und die Adressierung kann automatisch durch die Generic-API gelöst werden bzw. die statische Adressierung wird bei der Nutzung der API "generisch". Einige wichtige Klassen dieser API sollen erläutert werden, indem die zur folgenden kurzen Klassendefinition zugehörige Klassendatei "nachgebaut" wird.

public class BCELSynthesizeExample0 {
  public static void main(String[] args) {
    System.out.println("Hallo Apache BCEL!");
  }
}

Die Klasse ClassGen stellt dazu Konstruktoren und Methoden bereit.

org.apache.bcel.generic
Class ClassGen
EXT API: BCEL 5.2

Konstruktor:
  ClassGen(String class_name, 
           String super_class_name, 
           String file_name, 
           int access_flags, 
           String[] interfaces)

Methode:
  ConstantPoolGen getConstantPool()
  void addEmptyConstructor(int access_flags)
  void addMethod(Method m)
  JavaClass getJavaClass()

Die zu erstellende Klasse soll den Namen BCELSynthesizeExample haben und deren Superklasse ist Object. Der Name der Quelldatei lautet dann entsprechend BCELSynthesizeExample.java und wird später in der generierten Klassendatei im Source-Attribut gespeichert. Die Klassendeklaration soll den Modifikator public enthalten. Dazu kann die Konstante ACC_PUBLIC genutzt werden, die im Interface Constants definiert wird.

org.apache.bcel
Interface Constants
EXT API: BCEL 5.2

Feld:
  static short ACC_PUBLIC
  static short ACC_SUPER
  static short GETSTATIC
  static short INVOKEVIRTUAL

Im Beispiel aus Listing 4.6 wird dieses Interface, das nur Konstanten enthält, durch die Beispielklasse implementiert und erbt damit alle Konstanten des Interfaces. Das ist sinnvoll, da viele Konstanten im Interface zusammengefasst sind. Jeder Klasse, die dieses Interface implementiert, stehen damit benötigte Konstanten zur Verfügung. Die Konstante ACC_SUPER wird aus Gründen der Abwärtskompatibilität verwendet. Da die Klasse BCELSynthesizeExample kein Interface implementieren soll, könnte der Kontruktoraufruf von ClassGen wie folgt aussehen:

ClassGen cg = new ClassGen("BCELSynthesizeExample",
                           "java.lang.Object",
                           "BCELSynthesizeExample.java",
                           ACC_PUBLIC | ACC_SUPER,
                           null);

Mit addEmptyConstructor kann der zu erzeugenden Klasse ein Default-Konstruktor hinzugefügt werden, der normalerweise durch den Compiler javac automatisch generiert wird. Dessen Aufgabe ist es den paramterlosen Konstruktor der Superklasse aufzurufen (Initialisierung eines Objekts bzw. vorgeschriebene Initialisierungsreihenfolge). Alle weiteren Methode, im Beispiel wird nur die main-Methode benötigt, können mit addMethod hinzugefügt werden, wobei als Argument ein Objekt vom Typ Method verlangt wird. Wie ein solches Objekt beschafft werden kann ist Teil der weiteren Erläuterungen. Die Realisierung der main-Methode erfolgt durch die vier JVM-Befehle

getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
ldc #3; //String Hallo Apache BCEL!
invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
return

wie die Disassemblierung der Klassendatei BCELSynthesizeExample0.class mit javap -c deutlich macht. Um die drei Anweisungen getstatic, invokevirtual und return "nachzubilden", stellt die BCEL API die Klasse InstructionFactory zur Verfügung:

org.apache.bcel.generic
Class InstructionFactory
EXT API: BCEL 5.2

Konstruktor:
  InstructionFactory(ConstantPoolGen cp)
  
Methode:
  FieldInstruction createFieldAccess(String class_name, 
                                     String name, 
                                     Type type, 
                                     short kind)
  InvokeInstruction createInvoke(String class_name, 
                                 String name, 
                                 Type ret_type, 
                                 Type[] arg_types, 
                                 short kind)
  static ReturnInstruction createReturn(Type type)

Mit dem angegebenen Konstruktor kann ein Objekt der Factory erhalten werden. Dazu wird ein Objekte vom Typ ConstantPoolGen als Argumente übergeben, wobei dieses durch den Aufruf von getConstantPool der Klasse ClassGen erzeugt werden kann. Die drei angegebenen Methoden können für das Anwendungsbeispiel entsprechend genutzt werden:

ConstantPoolGen cp = cg.getConstantPool();
InstructionFactory ifactory = new InstructionFactory(cp);

// getstatic
FieldInstruction inst1 = ifactory.createFieldAccess(
                         "java.lang.System",
                         "out",
                         new ObjectType("java.io.PrintStream"),
                         GETSTATIC);

// invokevirtual
InvokeInstruction inst3 = ifactory.createInvoke(
                          "java.io.PrintStream",
                          "println",
                          Type.VOID,
                          new Type[] { Type.STRING },
                          INVOKEVIRTUAL);

// return
ReturnInstruction inst4 = InstructionFactory.createReturn(Type.VOID);

Die ersten beiden Argumente, die den Methoden createFieldAccess und createInvoke übergeben werden sind der Name der Klasse, in der das Feld bzw. die Methode definiert ist und der Name des Feldes bzw. der Methode. Diese Angaben erscheinen später in der fertigen Klassendatei BCELSynthesizeExample.class innerhalb des Konstantenpools (die Zeichenketten werden durch Utf8-Konstanten gespeichert). Weiterhin muss bei der Methode zur Erzeugung einer Feld-Instruktion ein Objekt vom Typ Type übergeben werden. Type ist eine abstrakte Superklasse für mögliche Java-Typen (primitive Datentypen und Referenztypen). ObjectType ist eine konkrete Unterklasse von Type. Das Feld out ist vom Typ PrintStream (wie in der JavaSE API angegeben) und der Felddeskriptor (Signatur des Feldes), der später ebenfalls in den Konstantenpool der Klassendatei aufgenommen wird, kann z.B. mit

System.out.println(new ObjectType("java.io.PrintStream").getSignature());

ausgebenen werden und lautet: Ljava/io/PrintStream;. Die Methode createInvoke verlangt ebenfalls Typangaben zum Rückgabewert und den übergebenen Argumenten. Dazu definiert die Klasse Type u.a. die verwendeten Konstanten VOID und STRING, die mit Hilfe der Punktnotation angesprochen werden können. Aus diesen Typangaben kann ebenfalls ein Methodendeskriptor (Signatur der Methode) abgeleitet werden, der auch an entsprechender Stelle innerhalb der erzugten Klassendatei steht. Als letzten Argument erwarten die beiden Methoden createFieldAccess und crateInvoke eine nähere Spezifizierung, da ein Feld-Zugang mit den JVM-Befehlen getfield, getstatic, putfield, putstatic und ein Methodenaufruf mit JVM-Befehlen invokeinterface, invokestatic, invokevirtual, invokespecial erfolgen kann. Die im Quelltext verwendeten Konstanten GETSTATIC und INVOKEVIRTUAL sind im Interface Constants definiert. GETSTATIC hat den dezimalen Wert 178 bzw. hexadezimal b2 und INVOKEVIRTUAL speichert den dezimalen Wert 182 (0xb6). Diese beiden Werte entsprechen den Operationcodes des zugehörigen JVM-Befehle. FieldInstruction, InvokeInstruction und ReturnInstruction sind Unterklassen der abstrakten Superklasse Instruction.

Die BCEL API stellt auch Wrapper-Klassen für virtuelle Instruktionen zur Verfügung (Compound Instruction). So kann z.B. die Wrapper-Klasse PUSH dazu verwendet werden, um die JVM-Befehle ldc oder bipush zu erzeugen, wie sie später entsprechend codiert in der fertigen Klassendatei stehen. PUSH implementiert das Interface CompoundInstruction.

org.apache.bcel.generic
Class PUSH
EXT API: BCEL 5.2

Konstruktor:
  PUSH(ConstantPoolGen cp, String value)
  PUSH(ConstantPoolGen cp, int value)
  
Methode:
  Instruction getInstruction()

Die folgenden Zeilen zeigen die flexible Anwendung der Wrapper-Klasse PUSH bei einem vorhandenen Objekt cp vom Typ ConstantPoolGen.

PUSH p1 = new PUSH(cp, "Hallo!");
PUSH p2 = new PUSH(cp, 127);
PUSH p3 = new PUSH(cp, 3);
Instruction i1 = p1.getInstruction();
Instruction i2 = p2.getInstruction();
Instruction i3 = p3.getInstruction();
System.out.println(i1.toString());
System.out.println(i2.toString());
System.out.println(i3.toString());

Ausgabe (ohne Operandenbytes):
ldc[18](2)
bipush[16](2)
iconst_3[6](1)

Die Ausgabe enthält in eckigen Klammern den Operationscode des JVM-Befehls und anchließend in runden Klammern die Länge des gesamten JVM-Befehls, wobei die Längenangabe mögliche Operandenbytes berücksichtigt. Zusammen mit der Klasse PUSH kann nun die main-Methode im Beispiel synthetisiert werden, um die gewünschte Zeichenkette "Hallo Apache BCEL!" auf dem Bildschirm ausgeben zu können.

CompoundInstruction inst2 = new PUSH(cp, "Hallo Apache BCEL!");

Die im Laufe der Beschreibung erzeugten Objekte inst1 bis inst4 können nun zu einer Liste zusammengefasst werden. Dazu stellt die Klasse InstructionList die Methde append bereit.

org.apache.bcel.generic
Class InstructionList
EXT API: BCEL 5.2

Konstruktor:
  InstructionList()
  
Methode:
  InstructionHandle append(Instruction i)
  InstructionHandle append(CompoundInstruction c)
  void dispose()

Ein Objekt der Instruktions-Liste il kann nun als Argument dem Konstruktor der Klasse MethodGen übergeben werden. Der Konstruktor erwartet weiterhin näher Spezifkationen zur erzeugenden Methode main: Modifkatoren, Anagabe zum Rückgabtyp, Typ und Name des Methoden-Parameters, den Namen der Methode, den Namen der Klasse, in der die Methode definiert ist und einen Bezug zum Konstantenpool, welcher mit der Übergabe eines Objekts vom Typ ConstantPoolGen hergestellt wird.

org.apache.bcel.generic
Class MethodGen
EXT API: BCEL 5.2

Konstruktor:
  MethodGen(int access_flags,
            Type return_type,
            Type[] arg_types,
            String[] arg_names,
            String method_name,
            String class_name,
            InstructionList il,
            ConstantPoolGen cp)
  
Methode:
  void setMaxStack()
  void setMaxLocals()
  
  Method getMethod()

Im Beispiel könnte die Erzeugung eines Objekts für die main-Methode wie folgt aussehen:

MethodGen mainMethod = new MethodGen(ACC_PUBLIC | ACC_STATIC,
                           Type.VOID,
                           new Type[] { new ArrayType(Type.STRING, 1) },
                           new String[] { "args" },
                           "main",
                           "BCELSynthesizeExample",
                           il,
                           cp);

Das zweite Argument, des verwendeten Konstruktors der Klasse ArrayType, gibt die Dimension des Arrays an. Mit den beiden Methoden setMaxStack und setMaxLocals werden die maximale Größe des Operandenstapels und die maximale Anzahl der Elemente im Array der lokalen Variablen ermittelt und gesetzt. Schließlich kann mit getMethod das gewünscht Objekt zur main-Methode generiert werden. Das anschließende Listing synthetisiert die Java-Klassendatei BCELSynthesizeExample.class mit Hilfe der BCEL API. Die erstellte Datei soll durch die Anweisung java BCELSynthesizeExample den Text "Hallo Apache BCEL!" auf die Konsole ausgeben.

Listing 4.6. BCELSynthesizeExampleGenerator.java. Zusammenstellen des erforderlichen Bytecodes einer gültigen Klassendatei BCELSynthesizeExample.class mit Hilfe der BCEL API.

/* BCELSynthesizeExampleGenerator.java */

import org.apache.bcel.*;
import org.apache.bcel.generic.*;

public class BCELSynthesizeExampleGenerator implements Constants {
  
  public static void main(String[] args) {
    
    ClassGen cg = new ClassGen("BCELSynthesizeExample",
                               "java.lang.Object",
                               "BCELSynthesizeExample.java",
                               ACC_PUBLIC | ACC_SUPER,
                               null);
    ConstantPoolGen cp = cg.getConstantPool();
            
    // leeren Konstruktor hinzufuegen
    cg.addEmptyConstructor(ACC_PUBLIC);
    
    // main-Methode erstellen
    InstructionFactory ifactory = new InstructionFactory(cp);
        
    FieldInstruction inst1 = ifactory.createFieldAccess(
                             "java.lang.System",
                             "out",
                             new ObjectType("java.io.PrintStream"),
                             GETSTATIC);
    CompoundInstruction inst2 = new PUSH(cp, "Hallo Apache BCEL!");
    InvokeInstruction inst3 = ifactory.createInvoke(
                              "java.io.PrintStream",
                              "println",
                              Type.VOID,
                              new Type[] { Type.STRING },
                              INVOKEVIRTUAL);
    ReturnInstruction inst4 = InstructionFactory.createReturn(Type.VOID);
    
    InstructionList il = new InstructionList();
    il.append(inst1);
    il.append(inst2);
    il.append(inst3);
    il.append(inst4);
        
    MethodGen mainMethod = new MethodGen(ACC_PUBLIC | ACC_STATIC,
                               Type.VOID,
                               new Type[] { new ArrayType(Type.STRING, 1) },
                               new String[] { "args" },
                               "main",
                               "BCELSynthesizeExample",
                               il,
                               cp);
    
    mainMethod.setMaxStack();
    mainMethod.setMaxLocals();
    cg.addMethod(mainMethod.getMethod());
    il.dispose();
    
    // erstellten Bytecode in Klassendatei abspeichern
    try {
      cg.getJavaClass().dump("BCELSynthesizeExample.class");
    } catch (IOException e) {
      System.out.println(e.toString());
    }
    System.out.println("Datei BCELSynthesizeExample.class erstellt."); 
  }
}

Die mit Hilfe der BCEL-API erstellte Datei BCELSynthesizeExample.class kann durch

> java BCELSynthesizeExample

aufgerufen werden. Die Ausgabe auf die Konsole sollte dann wie gewünscht lauten:

Hallo Apache BCEL!

Innerhalb des Listings wurde u.a. InstructionFactory verwendet, um die println-Anweisung im Java-Quelltext realisieren zu könnnen. Für diesen Spezialfall stellt InstructionFactory auch die "Convenience-Methode" createPrintln zur Verfügung, die eine InstructionList als Rückgabewert besitzt.

InstructionFactory ifactory = new InstructionFactory(cp);
InstructionList il = ifactory.createPrintln("Hallo Apache BCEL!"); 
Instruction[] i = il.getInstructions();
for (int j = 0; j < i.length; j++) {
  System.out.println(i[j].toString());
}

Ausgabe (ohne Operandenbytes):
getstatic[178](3)
ldc[18](2)
invokevirtual[182](3)

Das Paket org.apache.bcel.generic stellt eine Vielzahl weiterer Methoden zur Verfügung, die innerhalb der BCEL-API-Dokumentation im Einzelnen beschrieben sind.

 

Die Utility-Klasse BCELifier

Mit der Klasse BCELifier wird ein Utility zur Verfügung gestellt, mit dem eine vorhandene .class-Dateien zurück auf einen Java-Quelltext abgebildet werden kann, der Klassen aus dem Generic-Teil der BCEL API nutzt. BECLifier ist hifreich, um sich mit der Synthetisierung von Bytecode bzw. von Klassendateien vertraut zu machen. Wird z.B. BCELifier in Verbindung mit der im Verzeichnis vorhandene Klassendatei BCELSynthesizeExample.class genutzt kann ein entsprechender Quelltext erzeugt werden.

> java org.apache.bcel.util.BCELifier BCELSynthesizeExample

Die Anweisung hat die folgende Konsolenausgabe zur Folge:

/* BCELSynthesizeExampleCreator.java */

import org.apache.bcel.generic.*;
import org.apache.bcel.classfile.*;
import org.apache.bcel.*;
import java.io.*;

public class BCELSynthesizeExampleCreator implements Constants {
  private InstructionFactory _factory;
  private ConstantPoolGen    _cp;
  private ClassGen           _cg;

  public BCELSynthesizeExampleCreator() {
    _cg = new ClassGen("BCELSynthesizeExample", 
                       "java.lang.Object", 
                       "BCELSynthesizeExample.java", 
                       ACC_PUBLIC | ACC_SUPER, 
                       new String[] {  });
    _cp = _cg.getConstantPool();
    _factory = new InstructionFactory(_cg, _cp);
  }

  public void create(OutputStream out) throws IOException {
    createMethod_0();
    createMethod_1();
    _cg.getJavaClass().dump(out);
  }

  private void createMethod_0() {
    InstructionList il = new InstructionList();
    MethodGen method = new MethodGen(ACC_PUBLIC, 
                                     Type.VOID, 
                                     Type.NO_ARGS, 
                                     new String[] {  }, 
                                     "<init>", 
                                     "BCELSynthesizeExample", 
                                     il,
                                     _cp);
    InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 0));
    il.append(_factory.createInvoke("java.lang.Object", 
                                    "<init>", 
                                    Type.VOID, 
                                    Type.NO_ARGS, 
                                    Constants.INVOKESPECIAL));
    InstructionHandle ih_4 = il.append(_factory.createReturn(Type.VOID));
    method.setMaxStack();
    method.setMaxLocals();
    _cg.addMethod(method.getMethod());
    il.dispose();
  }

  private void createMethod_1() {
    InstructionList il = new InstructionList();
    MethodGen method = new MethodGen(
                           ACC_PUBLIC | ACC_STATIC,
                           Type.VOID,
                           new Type[] { new ArrayType(Type.STRING, 1) },
                           new String[] { "arg0" },
                           "main",
                           "BCELSynthesizeExample",
                           il,
                           _cp);
    InstructionHandle ih_0 = il.append(_factory.createFieldAccess(
                             "java.lang.System",
                             "out",
                             new ObjectType("java.io.PrintStream"),
                             Constants.GETSTATIC));
    il.append(new PUSH(_cp, "Hallo Apache BCEL!"));
    il.append(_factory.createInvoke("java.io.PrintStream",
                                    "println",
                                    Type.VOID,
                                    new Type[] { Type.STRING },
                                    Constants.INVOKEVIRTUAL));
    InstructionHandle ih_8 = il.append(_factory.createReturn(Type.VOID));
    method.setMaxStack();
    method.setMaxLocals();
    _cg.addMethod(method.getMethod());
    il.dispose();
  }

  public static void main(String[] args) throws Exception {
    BCELSynthesizeExampleCreator creator = new BCELSynthesizeExampleCreator();
    creator.create(new FileOutputStream("BCELSynthesizeExample.class"));
  }
}

Der entstandene Quelltext könnte dann wiederum compiliert und ausgeführt werden, was zu einer Abspeicherung einer Klassendatei in das aktuelle Verzeichnis führt.

BCELSynthesizeExample.class (1) -->  BCELSynthesizeExampleCreator.java  ----+
                                                                            |
BCELSynthesizeExample.class (2) <-------------------------------------------+

Die neu abgespeicherte Klassendatei BCELSynthesizeExample.class (2) hat die gleiche Funktion wie die ursprüngliche Klassendatei (1). Der mit BCELifier erzeugte Quelltext BCELSynthesizeExampleCreator.java und der Quelltext aus Listing 4.6 sind daher vergleichbar.

4.4.4. Verifizieren von Bytecode

Bevor eine Klassendatei ausgeführt wird, überprüft die virtuelle Java-Maschine (JVM), ob es sich bei der Datei auch wirklich um eine Klassendatei nach der JVM-Spezifikation handelt. BCEL enthält einen eigenen Verfifier mit dem Namen JustIce, der eine .class-Datei überprüft. Im Falle eines Fehlers innerhalb der Datei werden Informationen bezüglich des Fehlers bereitgestellt. JustIce unterstützt die Erstellung von gültigen Klassendateien, die mit BCEL erzeugt oder verändert wurden. Eine erzeugte Klassendatei wird dabei mehreren abgestuften Verifikationsschritten unterzogen. Der Verifier JustIce unterteilt den Verifikationsprozess in Pass 1 bis Pass 3:

  • Pass 1: Mit dem ersten Verifikationsschritt (Pass1) wird z.B. überprüft, ob die Datei mit den vier hex. Bytewerten ca fe ba be beginnt und ob vorhandene Utf8-Konstanten im Konstantenpool die zu Beginn dieser Byte-Blöcke angegebene Länge besitzen.

  • Pass 2: Durch Pass2 wird z.B. erkannt, wenn die zu überprüfende Klassendatei bzw. Klasse eine Unterklasse einer final-Klasse sein soll. Feld- und Methodendeskriptoren werden als Zeichenkette durch Utf8-Konstanten gespeichert und die einzelnen Zeichen die innerhalb der Deskriptoren auftreten können sind durch eine Grammatik festgelgt. Pass2 überprüft die Gültigkeit des Zeichenkombinationen.

  • Pass 3 (a/b): Der Verfikationsschritt Pass 3 (a) erkennt z.B. wenn ein Feld mit einem entsprechenden Namen nicht innerhalb der angegebenen Klasse definiert ist.

Damit der Verifier JustIce eine Klassendatei überprüfen kann, muss zunächst ein Objekt der Klasse Verifier erzeugt werden. Dazu wird innerhalb der Klasse VerifierFactory die Methode getVerifier zur Verfügung gestellt, wobei der Name der zu überprüfenden Klasse bzw. Klassendatei als Argument übergeben werden kann.

org.apache.bcel.verifier
Class VerifierFactory
EXT API: BCEL 5.2

Methode:
  static Verifier getVerifier(String fully_qualified_classname)

Nachdem das Objekt beschafft wurde, können nun die einzelnen Verifikationgsschritte mit den Methoden doPass1, doPass2, doPass3a und doPass3b durchgeführt werden.

org.apache.bcel.verifier
Class Verifier
EXT API: BCEL 5.2

Methode:
  VerificationResult doPass1()
  VerificationResult doPass2()
  VerificationResult doPass3a(int method_no)
  VerificationResult doPass3b(int method_no)
  String[] getMessages()

Mit getMessages können alle Warnmeldungen, die während der Überprüfung auflaufen, abgefragt werden. Die Klasse VerificationResult stellt Methoden bereit, mit denen die Ergebnisse der einzelnen Überprüfungsschritte ausgewertet werden können.

org.apache.bcel.verifier
Class VerificationResult
EXT API: BCEL 5.2

Feld:
  static int VERIFIED_OK
  static int VERIFIED_REJECTED
  static int VERIFIED_NOTYET

Methode:
  int getStatus()
  String toString()

Die Methode getStatus gibt die Konstanten VERIFIED_OK bzw. VERIFIED_REJECTED zurück, je nachdem ob Fehler innerhalb der Klassendatei aufgetreten sind oder nicht. Die Konstante VERIFIED_NOTYET zeigt an, dass eine spezielle Pass-Überprüfung der Klassendatei nicht stattgefunden hat, da der Verifikationsprozess vorher durch eine Fehlermeldung unterbrochen wurde. Mit toString kann eine String-Repräsentation der Verifikationsergebnisse ausgegeben werden. Das folgende Listing überprüft die mit Listing 4.6 erstellte Klassendatei auf Korrektheit.

Listing 4.7. BCELVerifyExample.java. Überprüfen des erstellten Bytecodes in Datei BCELSynthesizeExample.class.

/* BCELVerifyExample.java */

import org.apache.bcel.classfile.*;
import org.apache.bcel.verifier.*;

public class BCELVerifyExample {
  
  public static void main(String[] args) {
    try {
      System.out.println("Klassendatei BCELSynthesizeExample.class " + 
          "wird verifiziert:\n");
      Verifier v = VerifierFactory.getVerifier("BCELSynthesizeExample");
      VerificationResult vr;
      // Pass 1
      vr = v.doPass1();
      System.out.println("Pass 1:\n" + vr.toString());
      // Pass 2
      vr = v.doPass2();
      System.out.println("Pass 2:\n" + vr.toString());
      // Pass 3
      if (vr.getStatus() == VerificationResult.VERIFIED_OK) {
        ClassParser parser = new ClassParser("BCELSynthesizeExample.class");
        JavaClass clazz = parser.parse();
        Method[] m = clazz.getMethods();
        for (int i = 0; i < m.length; i++) {
          vr = v.doPass3a(i);
          System.out.println("Pass 3a, method " + i + " (" + m[i].toString() + 
              "):\n" + vr.toString());
          vr = v.doPass3b(i);
          System.out.println("Pass 3b, method " + i + " (" + m[i].toString() + 
              "):\n" + vr.toString());
        }
      }
      String[] w = v.getMessages();
      if (w.length != 0) {
        System.out.println("Warnings:");
        for (int j = 0; j < w.length; j++) {
          System.out.println(w[j]);
        }
      }  
      v.flush();
    } catch (Exception e) {
      System.out.println(e.toString());
    }  
     
  }  
}

Die Ausgabe der Überprüfung lautet:

Klassendatei BCELSynthesizeExample.class wird verifiziert:

Pass 1:
VERIFIED_OK
Passed verification.

Pass 2:
VERIFIED_OK
Passed verification.

Pass 3a, method 0 (public void <init>()):
VERIFIED_OK
Passed verification.

Pass 3b, method 0 (public void <init>()):
VERIFIED_OK
Passed verification.

Pass 3a, method 1 (public static void main(String[] args)):
VERIFIED_OK
Passed verification.

Pass 3b, method 1 (public static void main(String[] args)):
VERIFIED_OK
Passed verification.

Warnings:
Pass 2: Attribute '<LocalVariableTable: LocalVariable(start_pc = 0, length = 5, 
        index = 0:BCELSynthesizeExample this)>' as an attribute of Code attribute 
        '<CODE>' (method 'public void <init>()') will effectively be ignored and 
        is only useful for debuggers and such.
Pass 2: Attribute '<LocalVariableTable: LocalVariable(start_pc = 0, length = 9, 
        index = 0:String[] args)>' as an attribute of Code attribute '<CODE>' 
        (method 'public static void main(String[] args)') will effectively be 
        ignored and is only useful for debuggers and such.

Die Überprüfung des Bytecodes innerhalb einer Klassendatei auf Gültigkeit kann auch mit einer einzigen Kommandozeilenanweisung geschehen:

> java org.apache.bcel.verifier.Verifier BCELSynthesizeExample.class

 

Fallbeispiele zu Pass 1, Pass 2 und Pass 3

Pass 1: Wird das vierte Byte innerhalb einer gültigen Klassendatei von z.B. be zu ba verändert wird der Verifikationsprozess nach Pass 1 mit einer Fehlermeldung abgebrochen.

ca fe ba be --> ca fe ba ba

Pass 1:
VERIFIED_REJECTED
BCELSynthesizeExample is not a Java .class file

Pass 2: Der gültige Methodendeskriptor ([Ljava/lang/String;)V einer main-Methode wird innerhalb der Klassendatei durch eine Utf8-Konstante als Zeichenkette gespeichert. Dabei steht V dafür, dass kein Wert zurückgegeben werden soll. Wird das V innerhalb der Zeichenkette, durch ein geeignetes Programm zu Testzwecken, durch X ersetzt entsteht ein ungültiger Methodendeskriptor, der durch die Pass-2-Überprüfung erkannt wird.

([Ljava/lang/String;)V --> ([Ljava/lang/String;)X

Pass 2:
VERIFIED_REJECTED
Illegal descriptor (==signature) '([Ljava/lang/String;)X' used by 
    Method '<<Method>>'.

Pass 3: Auch der Name eines Feldes wird durch eine Utf8-Konstante gespeichert. Ist z.B. der Name des Feldes out, das innerhalb der Klasse System definiert ist fehlerhaft, erfolgt durch dir Verifikation nach Pass 3 (a) eine Fehlermeldung.

out --> ous

Pass 3a, method 1 (public static void main(String[] args)):
VERIFIED_REJECTED
Instruction getstatic[178](3) 20 constraint violated: Referenced field 'ous' 
    does not exist in class 'java.lang.System'.

Pass 3b, method 1 (public static void main(String[] args)):
VERIFIED_NOTYET
Not yet verified.

 

 

 

Diese Seite nutzt Google-Dienste - siehe dazu Datenschutz.

Copyright © 2006, 2007 Harald Roeder