ASM framework for bytecode instrumentation is a powerful tool. There is an interesting feature named ASMifier that I’ll show in this article. Given a classfile (Java bytecode), ASMifier can generate a Java source code with all the invocations to its own API that are necessary to replicate it. In other words, if you compile and invoke the generated Java source code, what you get is exactly the same classfile you had. The advantage is that you can now play with the generated source and easily create variations of the classfile.
Let’s see an example.
Original Java application:
1 2 3 4 5 |
public class Hello { public static void main(String args[]) { System.out.println("Hello!"); } } |
Once compiled, we get the following bytecodes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Hello { public Hello(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return } |
Now we can apply ASMifier:
1 |
java jdk.internal.org.objectweb.asm.util.ASMifier Hello.class |
And finally we get the following Java source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import jdk.internal.org.objectweb.asm.AnnotationVisitor; import jdk.internal.org.objectweb.asm.Attribute; import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.ConstantDynamic; import jdk.internal.org.objectweb.asm.FieldVisitor; import jdk.internal.org.objectweb.asm.Handle; import jdk.internal.org.objectweb.asm.Label; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; import jdk.internal.org.objectweb.asm.Type; import jdk.internal.org.objectweb.asm.TypePath; public class HelloDump implements Opcodes { public static byte[] dump () throws Exception { ClassWriter classWriter = new ClassWriter(0); FieldVisitor fieldVisitor; MethodVisitor methodVisitor; AnnotationVisitor annotationVisitor0; classWriter.visit(57, ACC_PUBLIC | ACC_SUPER, "Hello", null, "java/lang/Object", null); classWriter.visitSource("Hello.java", null); { methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); methodVisitor.visitCode(); Label label0 = new Label(); methodVisitor.visitLabel(label0); methodVisitor.visitLineNumber(1, label0); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); methodVisitor.visitInsn(RETURN); methodVisitor.visitMaxs(1, 1); methodVisitor.visitEnd(); } { methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); methodVisitor.visitCode(); Label label0 = new Label(); methodVisitor.visitLabel(label0); methodVisitor.visitLineNumber(3, label0); methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); methodVisitor.visitLdcInsn("Hello!"); methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); Label label1 = new Label(); methodVisitor.visitLabel(label1); methodVisitor.visitLineNumber(4, label1); methodVisitor.visitInsn(RETURN); methodVisitor.visitMaxs(2, 1); methodVisitor.visitEnd(); } classWriter.visitEnd(); return classWriter.toByteArray(); } } |