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 { magic = theInput.readInt(); if (magic != 0xCAFEBABE) { throw new ClassFormatError("Incorrect Magic Number: " + magic); } } int minor_version; void readMinorVersion() throws IOException { minor_version = theInput.readUnsignedShort(); if (minor_version != 3) { throw new ClassFormatError("Minor Version not 3"); } } int major_version; void readMajorVersion() throws IOException { major_version = theInput.readUnsignedShort(); if (major_version != 45) { throw new ClassFormatError("Major Version not 45"); } } ConstantPool thePool; void readConstantPool() throws IOException { thePool = new ConstantPool(theInput); } short access_flags; boolean isPublic; boolean isFinal; boolean isInterface; boolean isAbstract; boolean isSpecial; void readAccessFlags() throws IOException { access_flags = theInput.readShort(); isPublic = (access_flags & 0x0001) == 0 ? false : true; isFinal = (access_flags & 0x0010) == 0 ? false : true; isInterface = (access_flags & 0x0020) == 0 ? false : true; isAbstract = (access_flags & 0x0200) == 0 ? false : true; isSpecial = (access_flags & 0x0400) == 0 ? false : true; if (isAbstract && isFinal) { throw new ClassFormatError("This class is abstract and final!"); } if (isInterface && !isAbstract) { throw new ClassFormatError("This interface is not abstract!"); } if (isFinal && isInterface) { throw new ClassFormatError("This interface is final!"); } } ClassInfo thisClass; void readClass() throws IOException { int index = theInput.readUnsignedShort(); thisClass = thePool.readClassInfo(index); } ClassInfo superclass; void readSuperclass() throws IOException { int index = theInput.readUnsignedShort(); if (index == 0) { superclass = null; } else { superclass = thePool.readClassInfo(index); } } ClassInfo[] interfaces; void readInterfaces() throws IOException { interfaces = new ClassInfo[theInput.readUnsignedShort()]; for (int i=0; i < interfaces.length; i++) { interfaces[i] = thePool.readClassInfo(theInput.readUnsignedShort()); } } FieldInfo[] fields; void readFields() throws IOException { fields = new FieldInfo[theInput.readUnsignedShort()]; for (int i = 0; i < fields.length; i++) { fields[i] = new FieldInfo(theInput); } } MethodInfo[] methods; void readMethods() throws IOException { methods = new MethodInfo[theInput.readUnsignedShort()]; for (int i = 0; i < methods.length; i++) { methods[i] = new MethodInfo(theInput); } } AttributeInfo[] attributes; void readAttributes() throws IOException { attributes = new AttributeInfo[theInput.readUnsignedShort()]; for (int i = 0; i < attributes.length; i++) { attributes[i] = new AttributeInfo(theInput); } } public void writeAccess() { if (isPublic) theOutput.print("public "); if (isFinal) theOutput.print("final "); if (isAbstract) theOutput.print("abstract "); if (isInterface) theOutput.print("interface "); else theOutput.print("class "); } public void writeClassName() { String name = thePool.readUTF8(thisClass.nameIndex()); theOutput.print(name + " "); } // need to convert java/lang/Object to java.lang.Object and so forth public void writeSuperclass() { if (superclass.nameIndex() != 0) { String name = thePool.readUTF8(superclass.nameIndex()); name = name.replace('/', '.'); theOutput.print("extends " + name + " "); } } /* In essence this amounts to looping through every entry in the constant pool, and printing an import statement for each one which is a ClassInfo structure. However to make this more similar to actual Java source code */ public void writeImports() { PoolEntry pe = null; String thisname = thePool.readUTF8(thisClass.nameIndex()); // recall that there's nothing in the zeroth pool entry for (int i = 1; i < thePool.howMany(); i++) { pe = thePool.read(i); if (pe.tag() == PoolEntry.cClassInfo) { ClassInfo ci = pe.readClassInfo(); String name = thePool.readUTF8(ci.nameIndex()); name = name.replace('/','.'); if (!name.startsWith("java.lang.") && !name.equals(thisname)) { theOutput.println("import " + name + ";"); } } // Doubles and longs take two pool entries // see Java VM Spec., p. 98 else if (pe.tag == PoolEntry.cDouble || pe.tag == PoolEntry.cLong) { i++; } } theOutput.println(); } public void writeInterfaces() { if (interfaces.length > 0) { String name = thePool.readUTF8(interfaces[0].nameIndex()); theOutput.print("implements " + name + " "); for (int i=1; i < interfaces.length; i++) { name = thePool.readUTF8(interfaces[i].nameIndex()); theOutput.print(", " + name); } } theOutput.println(" {\n"); } public void writeFields() { for (int i = 0; i < fields.length; i++) { // indent two spaces theOutput.print(" "); // print the access specifiers if (fields[i].isPublic()) theOutput.print("public "); if (fields[i].isPrivate()) theOutput.print("private "); if (fields[i].isProtected()) theOutput.print("protected "); if (fields[i].isStatic()) theOutput.print("static "); if (fields[i].isVolatile()) theOutput.print("volatile "); if (fields[i].isTransient()) theOutput.print("transient "); if (fields[i].isFinal()) theOutput.print("final "); //print the type String descriptor = thePool.readUTF8(fields[i].descriptorIndex()); theOutput.print(decodeDescriptor(descriptor) + " "); //print the name theOutput.print(thePool.readUTF8(fields[i].nameIndex())); theOutput.println(";"); } } public void writeMethods() { for (int i = 0; i < methods.length; i++) { theOutput.println(); theOutput.print(" "); // access specifiers if (methods[i].isPublic()) theOutput.print("public "); if (methods[i].isPrivate()) theOutput.print("private "); if (methods[i].isProtected()) theOutput.print("protected "); if (methods[i].isStatic()) theOutput.print("static "); if (methods[i].isNative()) theOutput.print("native "); if (methods[i].isSynchronized()) theOutput.print("synchronized "); if (methods[i].isAbstract()) theOutput.print("abstract "); //print the type theOutput.print(getReturnType(methods[i]) + " "); //print the name theOutput.print(thePool.readUTF8(methods[i].nameIndex())); //argument list theOutput.print("("); theOutput.print(getArguments(methods[i])); theOutput.print(")"); //exceptions theOutput.print(getExceptions(methods[i])); theOutput.println(" {\n"); // method body theOutput.println(getCode(methods[i])); theOutput.println(" }\n"); } } public String getExceptions(MethodInfo mi) { ExceptionsAttribute theExceptions=null; String result = ""; // find the exceptions attribute AttributeInfo[] mAttributes = mi.getAttributes(); for (int i = 0; i < mAttributes.length; i++) { String name = thePool.readUTF8(mAttributes[i].nameIndex()); if (name.equals("Exceptions")) { try { theExceptions = new ExceptionsAttribute(mAttributes[i]); } catch (IOException e) { } break; } } if (theExceptions != null) { for (int i = 0; i < theExceptions.howMany(); i++) { if (i == 0) result += " throws "; else result += ", "; ClassInfo ci = thePool.readClassInfo(theExceptions.getIndex(i)); result += thePool.readUTF8(ci.nameIndex()).replace('/', '.'); } } return result; } public String getCode(MethodInfo mi) { CodeAttribute theCode = null; // find the exceptions attribute AttributeInfo[] mAttributes = mi.getAttributes(); for (int i = 0; i < mAttributes.length; i++) { String name = thePool.readUTF8(mAttributes[i].nameIndex()); if (name.equals("Code")) { try{ theCode = new CodeAttribute(mAttributes[i]); } catch (IOException e) { } break; } } if (theCode != null) { return theCode.toString(); } return ""; } public String getReturnType(MethodInfo mi) { String descriptor = thePool.readUTF8(mi.descriptorIndex()); String d = descriptor.substring(descriptor.indexOf(')') + 1); return decodeDescriptor(d); } public String getArguments(MethodInfo mi) { String descriptor = thePool.readUTF8(mi.descriptorIndex()); String params = descriptor.substring(1,descriptor.indexOf(")")); String result = ""; try { int i = 0; int a = 0; // number of arguments while (i < params.length()) { char c = params.charAt(i); switch (c) { case '[': if (a++ != 0) result += ", "; int dimensions = 0; while (params.charAt(i) == '[') { i++; dimensions++; } char t = params.charAt(i); String type; if (t == 'L') { type = decodeDescriptor(params.substring(i, params.indexOf(";", i) + 1)); i = params.indexOf(";", i) + 1; } else { type = decodeDescriptor(String.valueOf(t)); i++; } for (int j=0; j < dimensions; j++) { type += "[]"; } result += type; break; case 'L': if (a++ != 0) result += ", "; String o = params.substring(i+1, params.indexOf(';', i)); result += o.replace('/', '.'); i = params.indexOf(';', i) + 1; break; case 'B': case 'C': case 'D': case 'F': case 'I': case 'J': case 'S': case 'Z': if (a++ != 0) result += ", "; result += decodeDescriptor(String.valueOf(c)); i++; break; case 'V': i++; break; default: throw new ClassFormatError("Bad Parameter String: " + params + " " + c); } } } catch (StringIndexOutOfBoundsException e) { } return result; } public String decodeDescriptor(String d) { if (d.startsWith("B")) return "byte"; else if (d.startsWith("C")) return "char"; else if (d.startsWith("D")) return "double"; else if (d.startsWith("F")) return "float"; else if (d.startsWith("I")) return "int"; else if (d.startsWith("J")) return "long"; else if (d.startsWith("S")) return "short"; else if (d.startsWith("Z")) return "boolean"; else if (d.startsWith("V")) return "void"; else if (d.startsWith("L")) { // object String r = d.substring(1, d.length() - 1); r = r.replace('/', '.'); return r; } else if (d.startsWith("[")) { // array int dimensions = d.lastIndexOf('[') + 1; String type = decodeDescriptor(d.substring(dimensions)); for (int i=0; i < dimensions; i++) { type += "[]"; } return type; } else { throw new ClassFormatError("Unrecognized Type: " + d); } } }