Mastering Java Bytecode With ASM - 33rd degree, 2012
Upcoming SlideShare
Loading in...5
×
 

Mastering Java Bytecode With ASM - 33rd degree, 2012

on

  • 6,012 views

 

Statistics

Views

Total Views
6,012
Views on SlideShare
6,010
Embed Views
2

Actions

Likes
6
Downloads
116
Comments
1

1 Embed 2

https://twimg0-a.akamaihd.net 2

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • Programs are composed of classes, classes are thebytecode, blah blahblahJava bytecode is stack based: most of the operations push to the stack of consume values from the stack
  • ObjectWeb ASM is the de-facto standard decomposing, modifying, and recomposing Javabytecode. ASM provides a simple library that exposes the internal aggregate components of a given Java class through its visitor oriented API. On top of visitor API ASM provides tree API that represents classes as object constructs.ASM is very popular and is used widely in various Java open-source projects. In fact, this could be the primary motivation for someone the learn this library.
  • Why would learn Java bytecode and ASM at all?Well, creating a brand new JVM language might be a good idea  This is what all the cool kids are doing, right?Secondly – programming model that is exposed by many frameworks is backed with bytecode generation or instrumentation. AspectJ, for instance uses bytecode instrumentation extensively.Also, there are some many awesome tools that do awesome stuff, like …. JRebel for instance 
  • This presentation gives some pointe
  • Let’s start with an example: the class “Items” implements an interface,…
  • Includes a field declaration & assignment,..
  • Initializer block, ..
  • and a virtual method.
  • The most common scenario to generate bytecode that corresponds to the example source, is to create ClassWriter, visit the structure – fields, methods, etc, and after the job is done, write out the final bytes.
  • Actually, before going further, let’s mention javap– the Java class disassembler. The output of javap command isn’t particularly useful as there’s no way to modify it and compile back to the executable code. However, it is much more easier to read the bytecodes produced by javap, rather than the code that is written using ASM API. So, javap is good for reference when studying the bytecode.
  • Let’s go on and construct a ClassWriter.. The constructor takes anint which is composed of different flags.COMPUTE_MAXS says that the sizes of local variables and operand stack parts will be computed automatically. Still have to call visitMaxs with any argumentsCOMPUTE_FRAMES – everything is computed automatically. ASM will compute the stack map, still have to call visitMaxs
  • To create class signature, we’ll call ClassWriter#visit methodThe interface is full of constants, as one may notice.
  • Java version. V1_1 – V1_7 are available.
  • Accessor modifiers:ACC_PUBLIC, ACC_PRIVATEACC_SYNTHETIC, ACC_BRIDGE
  • Types declared as strings 
  • Even this API call, not that complex, but looks ugly
  • Field declaration with assignment is actually split between the field declaration for the class, and assignment is performed in constructor. So this is something we need to keep in mind when generating the code.
  • fisitField method creates the field declaration
  • The ugliest part of ASM API is the descriptiors. Sorry, I’d like to rant the library a bit on this one 
  • Blah blah descriptors, blah blah descriptors, blah blah…
  • some descriptor examples…
  • Fortunately, Type can help a bit with all this descriptor mess
  • Let’s generate some methods nowFirst of all, the constructor. There isn’t a special API to generate a constructor, only the special name, , tells that the method is actually a constructor.
  • The real method isn’t any different from the constructor from the API point of view
  • Now it is time to generate the constructor body
  • The constructor consists of super() callField assignmentSome method calls
  • The super() call for the regular class is the call to Obect.
  • Assigning a value to a field takes a series of opcodes to complete
  • At first the value has to be created and placed on the stack
  • Next,putfield, putstatic or xstoreopcode can be used to assign the value to the required location
  • The ASM code doesn’t look very different to the javap-like code, besides all the noise it brings
  • After the field is initialized, let’s add some values to the list: a byte, a short and an integer
  • Adding the values to the list results in method calls with a parameter. The parameter has to be loaded to the stack in order to be passed to a method.As the autoboxing takes place, Integer.valueOf is called in order to add an Integer to the List
  • The ASM code is getting uglier… and the captain doesn’t like it.
  • Let’s take a step back and select a shortcut for the taks
  • ASM code can be easily generated
  • ... using ASMifierClassVisitor (ASMifier in ASM4). The output is rather noisy, with all the labels,but the noise is easy to remove.
  • However,javap output is still the cleanest and easiest to read and there are some alternatives to ASM visitor API
  • For instance, Groovy AST transformations make it possible to write code which is very close to javap output.

Mastering Java Bytecode With ASM - 33rd degree, 2012 Mastering Java Bytecode With ASM - 33rd degree, 2012 Presentation Transcript

  • Main sponsorMastering Java Bytecode With ASM Anton Arhipov
  • MeAnton ArhipovJRebel @Product manager byday, coding monkey bynightanton@zeroturnaround.com@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
  • 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
  • ClassWriterClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
  • Visit Classcw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
  • Visit Classcw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
  • Visit Classcw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
  • Visit Classcw.visit( Opcodes.V1_6, Opcodes.ACC_PUBLIC, "zt/asm/Items", null, "java/lang/Object", new String[] {"java/io/Serializable"});
  • Visit Classcw.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 FieldFieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE,"ids","Ljava/util/List;","Ljava/util/List<Lj/l/Integer;>;", null);
  • Visit FieldFieldVisitor 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/util/List;([Ljava/lang/String;)V
  • org.objectweb.asm.TypeType.getObjectType("java/lang/String") Ljava/lang/String;
  • Visit MethodMethodVisitor constructor = cw.visitMethod( Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
  • Visit MethodMethodVisitor 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);}
  • super() 0: aload_0 1: invokespecial #1 // Object.<init>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 Create instance 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 Assign to a field
  • Assignment 5: new #2 // ArrayList 8: dup 9: invokespecial #3 // <init>12: astore_1 #4 // ids Assign to local variable
  • Assignmentmv.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;");
  • Assignmentmv.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);}
  • ids.add(1)15: aload_016: getfield #4 // ids19: iconst_120: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
  • ids.add(1)15: aload_016: getfield #4 // ids19: iconst_120: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
  • ids.add(1)15: aload_016: getfield #4 // ids19: iconst_120: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
  • ids.add(1)15: aload_016: getfield #4 // ids19: iconst_120: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
  • ids.add(1)15: aload_016: getfield #4 // ids19: iconst_120: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
  • ids.add(1)15: aload_016: getfield #4 // ids19: iconst_120: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
  • ids.add(100)15: aload_016: getfield #4 // ids19: bipush 10020: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
  • ids.add(100_000)15: aload_016: getfield #4 // ids19: ldc #7 // int 10000020: invokestatic #5 // Integer.valueOf23: invokeinterface #6, 2 // List.add28: pop
  • 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(); java -cp asm-all-3.3.1.jar:asm-util-3.3.1.jar mv.visitLabel(l0);mv.visitLineNumber(16, l0); org.objectweb.asm.util.ASMifierClassVisitor mv.visitVarInsn(ALOAD, 0); Items.classmv.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();
  • 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! 
  • Instrument some bytecode
  • Ninja.class Ninja.class’10101010101 1010101010111000101010 1110000101010101010001 1010101000100010001110 0001000111011011101011 11011101110
  • 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.instrumentimport 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.instrumentimport 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.instrumentimport 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.MF Premain-Class: Agent java –javaagent:agent.jar … Agent-Class: Agent
  • 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
  • @antonarhipovanton@zeroturnaround.com