Mastering Java Bytecode With ASM - 33rd degree, 2012
-
Upload
anton-arhipov -
Category
Technology
-
view
6.907 -
download
5
description
Transcript of Mastering Java Bytecode With ASM - 33rd degree, 2012
Main sponsor
Mastering Java Bytecode With ASMAnton Arhipov
Me
Anton ArhipovJRebel @Product manager by day, coding monkey by night
[email protected]@antonarhipov
Bytecode
• One-byte instructions• 256 possible opcodes (200+ in use)• Stack based (pop, push)
ASM
• Low-level API for bytecode crunching• Core API – visitors• Tree API – nodes• Analysis package
• http://asm.ow2.org
Why?
• Compilers for JVM• Programming model (AOP, ORM)• Awesome tools for JVM
JRebel
public class Items implements Serializable { private List<Integer> ids = new ArrayList<Integer>(); { ids.add(1); ids.add(100); ids.add(100000); }
public int getId(int i) { return ids.get(i); }}
public class Items implements Serializable { private List<Integer> ids = new ArrayList<Integer>(); { ids.add(1); ids.add(100); ids.add(100000); }
public int getId(int i) { return ids.get(i); }}
public class Items implements Serializable { private List<Integer> ids = new ArrayList<Integer>(); { ids.add(1); ids.add(100); ids.add(100000); }
public int getId(int i) { return ids.get(i); }}
public class Items implements Serializable { private List<Integer> ids = new ArrayList<Integer>(); { ids.add(1); ids.add(100); ids.add(100000); }
public int getId(int i) { return ids.get(i); }}
public class Items implements Serializable { private List<Integer> ids = new ArrayList<Integer>(); { ids.add(1); ids.add(100); ids.add(100000); }
public int getId(int i) { return ids.get(i); }}
Basic Process
• Construct ClassWriter• Stack up the visitors for:• annotations, methods, fields, etc
• Write out bytes
javap
ClassWriter
ClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
Visit Class cw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
Visit Class cw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
Visit Class cw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
Visit Class cw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
Visit Class cw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
Fieldprivate List<Integer> ids = new ArrayList<Integer>();
private List<Integer> ids;
public Items() { ids = new ArrayList<Integer>();}
Visit Field
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE,"ids","Ljava/util/List;","Ljava/util/List<Lj/l/Integer;>;", null);
Visit Field
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE,"ids","Ljava/util/List;","Ljava/util/List<Lj/l/Integer;>;", null);
Descriptor
• Primitives• B, C, S, I, J, F, D, Z, V
• References• Ljava/lang/Object;
• Arrays• Prefixed with [, i.e. [Ljava/lang/Object;
Descriptor
([Ljava/lang/String;)V
Ljava/util/List;
org.objectweb.asm.Type
Type.getObjectType("java/lang/String")
Ljava/lang/String;
Visit Method
MethodVisitor constructor = cw.visitMethod( Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
Visit Method
MethodVisitor get = cw.visitMethod( Opcodes.ACC_PUBLIC, "get", "(I)I", null, null);
public class Items implements Serializable { private List<Integer> ids = new ArrayList<Integer>(); { ids.add(1); ids.add(100); ids.add(100000); }
public int getId(int i) { return ids.get(i); }}
public class Items implements Serializable { private List<Integer> ids = new ArrayList<Integer>(); { ids.add(1); ids.add(100); ids.add(100000); }
public int getId(int i) { return ids.get(i); }}
public Items() { super();
ids = new ArrayList<Integer>(); ids.add(1); ids.add(100); ids.add(100000);}
public Items() { super();
ids = new ArrayList<Integer>(); ids.add(1); ids.add(100); ids.add(100000);}
0: aload_0 1: invokespecial #1 // Object.<init>
super()
MethodVisitor mv = …
mv.visitVarInsn(ALOAD, 0);mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
public Items() { super();
ids = new ArrayList<Integer>(); ids.add(1); ids.add(100); ids.add(100000);}
public Items() { super();
ids = new ArrayList<Integer>(); ids.add(1); ids.add(100); ids.add(100000);}
Assignment
4: aload_0 5: new #2 // ArrayList 8: dup 9: invokespecial #3 // <init>12: putfield #4 // ids
Assignment
4: aload_0 5: new #2 // ArrayList 8: dup 9: invokespecial #3 // <init>12: putfield #4 // ids
Create instance
Assignment
4: aload_0 5: new #2 // ArrayList 8: dup 9: invokespecial #3 // <init>12: putfield #4 // ids
Assign to a field
Assignment
5: new #2 // ArrayList 8: dup 9: invokespecial #3 // <init>12: astore_1 #4 // ids
Assign to local variable
Assignment
mv.visitVarInsn(ALOAD, 0);mv.visitTypeInsn(NEW, "java/util/ArrayList");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V");mv.visitFieldInsn(PUTFIELD, "zt/asm/Items", "ids", "Ljava/util/List;");
Assignment
mv.visitVarInsn(ALOAD, 0);mv.visitTypeInsn(NEW, "java/util/ArrayList");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V");mv.visitFieldInsn(PUTFIELD, "zt/asm/Items", "ids", "Ljava/util/List;");
public Items() { super();
ids = new ArrayList<Integer>(); ids.add(1); ids.add(100); ids.add(100000);}
public Items() { super();
ids = new ArrayList<Integer>(); ids.add(1); ids.add(100); ids.add(100000);}
15: aload_0 16: getfield #4 // ids19: iconst_1 20: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
ids.add(1)
15: aload_0 16: getfield #4 // ids19: iconst_1 20: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
ids.add(1)
15: aload_0 16: getfield #4 // ids19: iconst_1 20: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
ids.add(1)
15: aload_0 16: getfield #4 // ids19: iconst_1 20: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
ids.add(1)
15: aload_0 16: getfield #4 // ids19: iconst_1 20: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
ids.add(1)
15: aload_0 16: getfield #4 // ids19: iconst_1 20: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
ids.add(1)
15: aload_0 16: getfield #4 // ids19: bipush 10020: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
ids.add(100)
15: aload_0 16: getfield #4 // ids19: ldc #7 // int 100000 20: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
ids.add(100_000)
mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, "zt/asm/Items", "ids", "Ljava/util/List;");mv.visitInsn(ICONST_1);mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");mv.visitInsn(POP);
public class Items implements Serializable { private List<Integer> ids = new ArrayList<Integer>(); { ids.add(1); ids.add(100); ids.add(100000); }
public int getId(int i) { return ids.get(i); }}
ASMifiedmv = cw.visitMethod(ACC_PUBLIC, "getId", "(I)I", null, null);mv.visitCode();Label l0 = new Label();mv.visitLabel(l0);mv.visitLineNumber(16, l0);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, "zt/asm/Items", "ids", "Ljava/util/List;");mv.visitVarInsn(ILOAD, 1);mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;");mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");mv.visitInsn(IRETURN);Label l1 = new Label();mv.visitLabel(l1);mv.visitLocalVariable("this", "Lzt/asm/Items;", null, l0, l1, 0);mv.visitLocalVariable("i", "I", null, l0, l1, 1);mv.visitMaxs(2, 2);mv.visitEnd();
ASMifiedmv = cw.visitMethod(ACC_PUBLIC, "getId", "(I)I", null, null);mv.visitCode();Label l0 = new Label();mv.visitLabel(l0);mv.visitLineNumber(16, l0);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, "zt/asm/Items", "ids", "Ljava/util/List;");mv.visitVarInsn(ILOAD, 1);mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;");mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");mv.visitInsn(IRETURN);Label l1 = new Label();mv.visitLabel(l1);mv.visitLocalVariable("this", "Lzt/asm/Items;", null, l0, l1, 0);mv.visitLocalVariable("i", "I", null, l0, l1, 1);mv.visitMaxs(2, 2);mv.visitEnd();
java -cp asm-all-3.3.1.jar:asm-util-3.3.1.jar \org.objectweb.asm.util.ASMifierClassVisitor \Items.class
javappublic class zt.asm.deg.Items { public java.util.List<java.lang.Integer> ids;
public int getId(int); Code: 0: aload_0 1: getfield #4 4: iload_1 5: invokeinterface #8, 2 10: checkcast #9 13: invokevirtual #10 16: ireturn }
Groovyfiedpublic class zt/asm/Items { public Ljava/util/List; ids
@groovyx.ast.bytecode.Bytecode public int getId(int a) { aload 0 getfield zt.asm.Items.ids >> List iload 1 invokeinterface List.get(int) >> Object checkcast Integer invokevirtual Integer.intValue() >> int ireturn }}
https://github.com/melix/groovy-bytecode-ast
Generating bytecode from scratch is too simple …
Transforming the bytecode is much more fun!
Instrumentsome bytecode
1010101010111000101010101010100010001000111011011101011
1010101010111100001010101010100010001000111011011101110
Ninja.class Ninja.class’
How?
• Add –javaagent to hook into class loading process
• Implement ClassFileTransformer• Use bytecode manipulation libraries (Javassist,
cglib, asm) to add any custom logic
java.lang.instrument
How ? (2)
• Use custom ClassLoader–Override ClassLoader#findClass–Use ClassReader(String) to read the class
in and transform it via visitor chain–Call ClassLoader#defineClass explicitly
with the result from the transformation step
java.lang.instrument
import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;
public class Agent {public static void premain(String args, Instrumentation inst) { inst.addTransformer(new ClassFileTransformer(), true); }
public static void agentmain(String args, Instrumentation inst) { premain(args,inst); }}
java.lang.instrument
import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;
public class Agent {public static void premain(String args, Instrumentation inst) { inst.addTransformer(new ClassFileTransformer(), true); }
public static void agentmain(String args, Instrumentation inst) { premain(args,inst); }}
java.lang.instrument
import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;
public class Agent {public static void premain(String args, Instrumentation inst) { inst.addTransformer(new ClassFileTransformer(), true); }
public static void agentmain(String args, Instrumentation inst) { premain(args,inst); }}
META-INF/MANIFEST.MFPremain-Class: AgentAgent-Class: Agent
java –javaagent:agent.jar …
j.l.instrument.ClassFileTransformernew ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class<?>classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){
ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); MyAdapter ca = new MyAdapter(cw); cr.accept(ca, ClassReader.EXPAND_FRAMES); return cw.toByteArray(); }
j.l.instrument.ClassFileTransformernew ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class<?>classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){
ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); MyAdapter ca = new MyAdapter(cw); cr.accept(ca, ClassReader.EXPAND_FRAMES); return cw.toByteArray(); }
j.l.instrument.ClassFileTransformernew ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class<?>classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){
ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
MyAdapter ca = new MyAdapter(cw); cr.accept(ca, ClassReader.EXPAND_FRAMES); return cw.toByteArray(); }
public class MyClassLoader extends ClassLoader {
protected Class findClass(String name) throws ClassNotFoundException {
ClassReader cr = new ClassReader(name); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
MyClassAdapter ca = new MyClassAdapter(cw);
cr.accept(ca, ClassReader.EXPAND_FRAMES);
byte b[] = cw.toByteArray(); return defineClass(name, b, 0, b.length); }
}
SLIDESGOTO IDESLIDESIDE: DEMO
@antonarhipov