|
|
Reading Class Files
A Java .class file has 16 parts. Eleven of the parts always occupy the same number of bytes. For example, the magic number 0xCAFEBABE is always four bytes, never two bytes and never eight bytes. Five of the parts are of varying length. For example, longer methods must have more byte codes than shorter methods. Table 4-1 lists the 16 parts of every Java .class file in order. These parts always occur in exactly this order. The first step to disassembling a Java .class file is to break it up into these parts.
Table 4-1 The 16 parts of a .class file
|
Field
| Width (bytes)
| Meaning
|
|
magic
| 4
| This identifies the .class file format. It should be 0xCAFEBABE. If its anything else, youre dealing with a format more recent than this book.
|
minor version
| 2
| The minor version of the compiler
|
major version
| 2
| The major version of the compiler
|
constant pool
| variable
| The first two bytes give the number of entries in the constant pool. Then, as many bytes as are necessary to fill that many entries are read. The constant pool is a table of constant values used by this class.
|
access flags
| 2
| These bit flags tell you whether the class is public, final, abstract, an interface, and a few other things.
|
this class
| 2
| This tells you which entry in the constant pool holds this classs class info.
|
superclass
| 2
| If this is zero, then this classs only superclass is java.lang.Object. Otherwise, this is an index into the constant pool for the superclass class info.
|
interfaces
| variable
| The interface table holds two byte indices into the constant pool table, one for each interface that this class implements. The first two bytes give the number of entries in the interface table. Therefore, after reading the first two bytes, you have to read twice as many bytes as the number stored in the first two bytes.
|
fields
| variable
| The fields table includes one fields info structure for each field in the class.
|
methods
| variable
| The method table contains the byte codes for each method in the class, the return type of the method, and the types of each argument to the method.
|
attributes
| 2
| The attributes of the class
|
|
Listing 4-6 is a skeleton of a program that will disassemble byte code files. It reads the name of a file from the command line, opens a FileInputStream to the file, chains a DataInputStream to that FileInputStream, and then proceeds to read bytes out of the file. Or at least it will as soon as the skeleton is filled out. It would be simple enough to add a graphical interface to this program, as I did with Listing 4-5, but lets leave that as an exercise for you to explore.
Listing 4-6 Disassembler skeleton
import java.io.*;
import java.awt.FileDialog;
import java.awt.Frame;
public class Disassembler {
DataInputStream theInput;
PrintStream theOutput;
public static void main (String[] args) {
try {
Disassembler d = new Disassembler();
d.disassemble();
}
catch (Exception e) {
System.err.println(e);
e.printStackTrace();
}
}
public Disassembler (String theFile, OutputStream os) throws IOException {
this(new File(theFile), os);
}
public Disassembler (File theFile, OutputStream os) throws IOException {
FileInputStream fis = new FileInputStream(theFile);
theInput = new DataInputStream(fis);
theOutput = new PrintStream(os);
}
public Disassembler (OutputStream os) throws IOException {
this(chooseFile(), os);
}
public Disassembler () throws IOException {
this(chooseFile(), System.out);
}
public static File chooseFile() {
FileDialog fd = new FileDialog(new Frame(),
Please choose a file:, FileDialog.LOAD);
fd.show();
return new File(fd.getDirectory(), fd.getFile());
}
public void disassemble() throws IOException {
try {
readMagic();
readMinorVersion();
readMajorVersion();
readConstantPool();
readAccessFlags();
readClass();
readSuperclass();
readInterfaces();
readFields();
readMethods();
readAttributes();
// Output the file
writeImports();
writeAccess();
writeClassName();
writeSuperclass();
writeInterfaces();
writeFields();
writeMethods();
theOutput.println(});
theOutput.println(\n/*\n + thePool + \n*/);
}
catch (ClassFormatError e) {
System.err.println(e);
return;
}
}
int magic;
void readMagic() throws IOException {
}
void readMinorVersion() throws IOException {
}
void readMajorVersion() throws IOException {
}
void readConstantPool() throws IOException {
}
void readAccessFlags() throws IOException {
}
void readClass() throws IOException {
}
void readSuperclass() throws IOException {
}
void readInterfaces() throws IOException {
}
void readFields() throws IOException {
}
void readMethods() throws IOException {
}
void readAttributes() throws IOException {
}
public void writeAccess() {
}
public void writeClassName() {
}
public void writeSuperclass() {
}
public void writeImports() {
}
public void writeInterfaces() {
}
public void writeFields() {
}
public void writeMethods() {
}
public String getExceptions(MethodInfo mi) {
}
public String getCode(MethodInfo mi) {
}
public String getReturnType(MethodInfo mi) {
}
public String getArguments(MethodInfo mi) {
}
public String decodeDescriptor(String d) {
}
}
|