Disassembly

Brian S. O'Neill edited this page Jun 2, 2013 · 2 revisions
Clone this wiki locally

Class disassembly

A disassembly tool is provided which serves as a generic class disassembler. It also supports an output format which demonstrates how to use the Cojen API.

The disassembly tool can disassemble a class specified by a file path or by class name. For example, the standard String class can be disassembled as follows:

java org.cojen.classfile.DisassemblyTool java.lang.String

The disassembly tool supports a format option, which can be "assembly" or "builder". If not specified, it defaults to "assembly". Here is sample output generated by disassembling one of Cojen's utility classes, in assembly format:

/**
 * Disassembled on Thu Dec 14 08:30:00 PST 2006.
 * 
 * @target 1.0
 * @source BeanIntrospector.java
 */
public class org.cojen.util.BeanIntrospector extends java.lang.Object {
    /**
     * @synthetic
     */
    static java.lang.Class class$java$lang$Object;

    private static java.util.Map cPropertiesCache;

    static {
        // max stack: 2
        // max locals: 0
        // line 35
        new org.cojen.util.WeakIdentityMap
        dup
        invokespecial void org.cojen.util.WeakIdentityMap.<init>()
        putstatic java.util.Map org.cojen.util.BeanIntrospector.cPropertiesCache
        return
    }

    public static void main(java.lang.String[])
        throws java.lang.Exception
    {
        // max stack: 3
        // max locals: 1
        // line 38
        getstatic java.io.PrintStream java.lang.System.out
        aload_0
        iconst_0
        aaload
        invokestatic java.lang.Class java.lang.Class.forName(java.lang.String)
        invokestatic java.util.Map org.cojen.util.BeanIntrospector.getAllProperties(java.lang.Class)
        invokevirtual void java.io.PrintStream.println(java.lang.Object)
        // line 39
        return
    }

    ...

}

When disassembled using the builder format, a valid Java source file is generated which when compiled and run, rebuilds the original class. This demonstrates how to use Cojen, and it also allows Java classes to be modified when source code is not available.

Even if the generated class file builder is run without modification, the emitted class file will likely produce a different binary representation than the original class. This is because Cojen re-orders the class constant pool and reduces local variable usage.

java org.cojen.classfile.DisassemblyTool -f builder org.cojen.util.BeanIntrospector
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

import org.cojen.classfile.ClassFile;
import org.cojen.classfile.CodeBuilder;
import org.cojen.classfile.FieldInfo;
import org.cojen.classfile.Label;
import org.cojen.classfile.LocalVariable;
import org.cojen.classfile.Location;
import org.cojen.classfile.MethodInfo;
import org.cojen.classfile.Modifiers;
import org.cojen.classfile.Opcode;
import org.cojen.classfile.TypeDesc;

/**
 * Builds ClassFile for org.cojen.util.BeanIntrospector
 *
 * @author auto-generated
 */
public class ClassFileBuilder {
    public static void main(String[] args) throws Exception {
        // public class org.cojen.util.BeanIntrospector
        ClassFile cf = createClassFile();

        if (args.length > 0) {
            File file = new File(args[0]);
            if (file.isDirectory()) {
                writeClassFiles(cf, file);
            } else {
                OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
                cf.writeTo(out);
                out.close();
            }
        }
    }

    private static void writeClassFiles(ClassFile cf, File dir) throws Exception {
        File file = new File(dir, cf.getClassName().replace('.', '/') + ".class");
        file.getParentFile().mkdirs();
        OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
        cf.writeTo(out);
        out.close();

        ClassFile[] innerClasses = cf.getInnerClasses();
        for (int i=0; i<innerClasses.length; i++) {
            writeClassFiles(innerClasses[i], dir);
        }
    }

    public static ClassFile createClassFile() {
        ClassFile cf = new ClassFile("org.cojen.util.BeanIntrospector", "java.lang.Object");
        cf.setTarget("1.0");
        cf.setSourceFile("BeanIntrospector.java");

        createStaticInitializer(cf);

        //
        // Create fields
        //

        cf.addField(Modifiers.PRIVATE.toStatic(true),
                    "cPropertiesCache",
                    TypeDesc.forClass("java.util.Map"));

        FieldInfo fi = cf.addField(Modifiers.NONE.toStatic(true),
                                   "class$java$lang$Object",
                                   TypeDesc.forClass("java.lang.Class"));
        fi.markSynthetic();

        //
        // Create constructors
        //

        // public void <init>()
        createConstructor_1(cf);

        //
        // Create methods
        //

        // public static void main(java.lang.String[])
        createMethod_1(cf);

        // public static java.util.Map getAllProperties(java.lang.Class)
        createMethod_2(cf);

        ...

        return cf;
    }

    private static void createStaticInitializer(ClassFile cf) {
        MethodInfo mi = cf.addInitializer();
        CodeBuilder b = new CodeBuilder(mi);

        b.mapLineNumber(35);
        TypeDesc type_1 = TypeDesc.forClass("org.cojen.util.WeakIdentityMap");
        b.newObject(type_1);
        b.dup();
        b.invokeConstructor("org.cojen.util.WeakIdentityMap", null);
        TypeDesc type_2 = TypeDesc.forClass("java.util.Map");
        b.storeStaticField("cPropertiesCache", type_2);
        b.returnVoid();
    }

    // public void <init>()
    private static void createConstructor_1(ClassFile cf) {
        MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, null);
        CodeBuilder b = new CodeBuilder(mi);

        b.mapLineNumber(33);
        b.loadThis();
        b.invokeSuperConstructor(null);

        b.mapLineNumber(307);
        b.returnVoid();
    }

    // public static void main(java.lang.String[])
    private static void createMethod_1(ClassFile cf) {
        MethodInfo mi = cf.addMethod(Modifiers.PUBLIC_STATIC,
                                     "main",
                                     null,
                                     new TypeDesc[] {TypeDesc.STRING.toArrayType()});
        mi.addException(TypeDesc.forClass("java.lang.Exception"));
        CodeBuilder b = new CodeBuilder(mi);

        LocalVariable var_1 = b.getParameter(0);

        b.mapLineNumber(38);
        TypeDesc type_1 = TypeDesc.forClass("java.io.PrintStream");
        b.loadStaticField("java.lang.System", "out", type_1);
        b.loadLocal(var_1);
        b.loadConstant(0);
        b.loadFromArray(TypeDesc.OBJECT);
        TypeDesc type_2 = TypeDesc.forClass("java.lang.Class");
        TypeDesc[] params_1 = new TypeDesc[] {TypeDesc.STRING};
        b.invokeStatic("java.lang.Class", "forName", type_2, params_1);
        TypeDesc type_3 = TypeDesc.forClass("java.util.Map");
        TypeDesc[] params_2 = new TypeDesc[] {type_2};
        b.invokeStatic("getAllProperties", type_3, params_2);
        TypeDesc[] params_3 = new TypeDesc[] {TypeDesc.OBJECT};
        b.invokeVirtual("java.io.PrintStream", "println", null, params_3);

        b.mapLineNumber(39);
        b.returnVoid();
    }

    ...

}