Michael Rasmussen ZeroTurnaround Terminology
Basic ASM classes
Generating bytecode
Transforming bytecode
Bytecode in the Wild
Binary names ◦ In Java source code file Uses dots to separate, e.g. java.lang.String In accordance with the JLS http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.1
◦ In class file format Uses slash to separate, e.g. java/lang/String Also commonly referred to as Internal name or Internal form In accordance with the JVMS http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2 String representing a class’ name
Fully qualified class name ◦ uses / as package separator
Example: ◦ java.lang.String “java/lang/String” ◦ java.util.ArrayList “java/util/ArrayList” Internal definition of a type
Primitive types ◦ Single character “J” (long), “I” (int), “S” (short), “B” (byte), “C” (char) “F" (float), “D” (double) “Z” (boolean), “V” (void) Object types ◦ Binary name enclosed by L; “Ljava/lang/String;” “Ljava/util/ArrayList;”
Array types ◦ [ followed by type descriptor “[B” byte[] “[Ljava/lang/String;” String[] “[[D” double[][] Multiple [ indicates multiple dimensions Defines parameter and return types
Consists of: ◦ Enclosed in parenthesis: 0 or more Type Descriptors Describing the parameters ◦ 1 Type Descriptor Describing the return type Example ◦ “(Ljava/lang/String;)I” Takes a java.lang.String as arguments Returns an int ◦ “()V” Takes zero arguments Returns nothing (void) ◦ “(DD)D” Takes two arguments of type double Returns a double Constructor ◦ Special name: “
Static initializer ◦ Special name: “
public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World! 5: invokevirtual #4 // Method java/io/PrintStream.println: // (Ljava/lang/String;)V 8: return }
Maven dependency:
Gradle dependency:
compile 'org.ow2.asm:asm-debug-all:5.2' ASM is based on the Visitor pattern
ClassVisitor – visit Class members ◦ ClassWriter – write .class bytes ◦ ClassReader – read .class bytes
MethodVisitor – visit Method bytecode ◦ GeneratorAdapter – wrapper to ease generation
Label - Branch target Type - Helper class for types Base Visitor to visit a class ◦ Contains visitors for (among others): Describing the class, superclass, interfaces Visiting fields Visiting methods visit(...) ◦ Visits the overall class int version Java version int access Access for class (ACC_PUBLIC) String name Name of class String signature Generic signature String superName Name of superclass String[] interfaces Names of implemented interfaces visitField(…) : FieldVisitor ◦ Visit a field on the class int access Access for the field (public/private) String name Name of the field String desc Type descriptor of the field String signature Generic signature for the field Object value Default value (for static primitives) visitMethod(…) : MethodVisitor ◦ Visit a method on the class int access Access for the method String name Name of the method String desc Method descriptor of the method String signature Generic signature for the method String[] exceptions Declared thrown exceptions Extends ClassVisitor
Basic class to generate a class
Use toByteArray() to get actual bytecode Basic class to parse a class
Use accept() with a ClassVisitor ◦ visits on the passed ClassVisitor the individual parts of the class being parsed Base Visitor to visit a method
Has visit methods for individual bytecode opcode-groups visitInsn(int opcode) ◦ Visits a zero operand instruction xRETURN, ATHROW xCONST_n xALOAD, xASTORE, ARRAYLENGTH xADD, xSUB, xMUL, xDIV, xREM, xNEG xOR, xAND, xXOR, xSHL, xSHR, xUSHR I2x, L2x, F2x, D2x LCMP, FCMPx, DCMPx NOP, SWAP, DUP, DUP2, POP, POP2, DUP_x MONITORENTER, MONITOREXIT visitIntInsn(int opcode, int operand) ◦ Visits an instruction with a single int operand BIPUSH, SIPUSH Pushed the value “operand” onto the stack NEWARRAY Creates a primitive array of the type specified by “operand”: T_BOOLEAN, T_CHAR, T_FLOAT, T_DOUBLE, T_BYTE, T_SHORT, T_INT, T_LONG Size of array is popped from the stack visitVarInsn(int opcode, int var) ◦ Visits a local instruction xLOAD xSTORE
◦ “var” indicates the local index to access visitTypeInsn(int opcode, String type) ◦ Visits a type instruction. NEW ANEWARRAY CHECKCAST INSTANCEOF
◦ “type” is the type’s binary name visitFieldInsn(int opcode, String owner, String name, String desc) ◦ Visits a field instruction GETSTATIC, PUTSTATIC GETFIELD, PUTFIELD
◦ “owner” is the binary-name of the class that holds the field ◦ “name” is the name of the field ◦ “desc” is the type-descriptor for the field visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) ◦ Visits a method instruction INVOKESTATIC INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE
◦ “owner” is the binary-name of the class that holds the method ◦ “name” is the name of the method ◦ “desc” is the method-descriptor of the method ◦ “itf” indicates if it’s an interface-method visitJumpInsn(int opcode, Label label) ◦ Visits a jump instruction. GOTO, IF_ICMPx, IFx, IF_ACMPx, etc
◦ “label” points to the target location to jump to if condition is met visitLdcInsn(Object cst) ◦ Visits an LDC instruction. LDC
◦ If “cst” is of the type Integer, Long, Float or Double, the value of that number is used as a constant. ◦ If “cst” is of type String, the content of that String is used as a constant. ◦ If “cst” is of type Type, the type indicated by that Type is used as a constant (equivalent to using .class in Java source code) visitIincInsn(int var, int increment) ◦ Visits an IINC instruction. IINC
◦ “var” points to the local index to modify ◦ “increment” is the value the local should be incremented by (valid range: -32768 .. 32767) visitLabel(Label label) ◦ Visits a label.
◦ Adds the target location “label”, for use with, among others, jump opcodes. Wrapper around MethodVisitor ◦ Convenience methods for most opcodes
◦ Easier to write
◦ Takes care of long/double taking two slots! Access arguments by index, not by local
◦ Takes care of using the right opcode depending on type Symbolizes branch targets in methods
◦ Example: Label labelGoto = new Label(); mv.visitJumpInsn(GOTO, labelGoto); mv.visitLabel(labelGoto); ASM class for type/method descriptors ◦ Contains several useful helper methods Type.getInternalName(Class c) : String Returns the binary-name for a class Type.getDescriptor(Class c) : String Returns the type-description for a class Type.getType(Class c) : Type Return a Type object for the specified class Type.getMethodDescriptor(Type returnType, Type... paramTypes) : String Returns the method-descriptor for the types specified
Basics for generating a class ◦ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_6, ACC_PUBLIC, "className", null, Type.getInternalName(Object.class), null);
// Visit individual fields and methods // cw.visitField(...) // cw.visitMethod(...)
cw.visitEnd();
byte[] classBytes = cw.toByteArray(); // define classBytes using a ClassLoader public static void main(String[] args) { System.out.println("Hello World"); } Resulting bytecode: ◦ GETSTATIC // Get the static variable System.out java/lang/System out Ljava/io/PrintStream; // which is of the type PrintStream LDC "Hello World" // Push “Hello World” to the stack INVOKEVIRTUAL java/io/PrintStream // invoke println on PrintStream println // with the pushed string (Ljava/lang/String;)V RETURN // All methods must return, // even void methods
◦
public static void main(String[] args) { System.out.println("Hello World"); } Basics for generating a method ◦ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String[].class)), null, null); mv.visitCode();
mv.visitFieldInsn(GETSTATIC, Type.getInternalName(System.class), "out", Type.getDescriptor(PrintStream.class));
mv.visitLdcInsn("Hello World!");
mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), "println”, Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1); mv.visitEnd();
public static void main(String[] args) { System.out.println("Hello World"); } GeneratorAdapter ◦ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String[].class)), null, null); GeneratorAdapter ga = new GeneratorAdapter(mv, ACC_PUBLIC | ACC_STATIC, "main", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String[].class)));
ga.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class));
ga.push("Hello World");
ga.invokeVirtual(Type.getType(PrintStream.class), Method.getMethod("void println(java.lang.String)"));
ga.returnValue(); ga.endMethod();
public static void main(String[] args) { System.out.println("Hello World"); } public int gt(int x, int y) { if (x > y) return 1; else return -1; } Resulting bytecode: ◦ ILOAD 1 // push local[1] {x} ILOAD 2 // push local[2] {y} IF_ICMPLE :le // if local[1] <= local[2] jump ICONST_1 // push +1 IRETURN // return value :le ICONST_M1 // push -1 IRETURN // return value
public int gt(int x, int y) { if (x > y) return 1; else return -1; } GeneratorAdapter ◦ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "gt", Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE), null, null); mv.visitCode();
Label labelElse = new Label(); mv.visitVarInst(ILOAD, 1); mv.visitVarInst(ILOAD, 2); mv.visitJumpInst(IF_ICMPLE, labelElse);
mv.visitInst(ICONST_1); mv.visitInst(IRETURN);
mv.visitLabel(labelElse); mv.visitInst(ICONST_M1); mv.visitInst(IRETURN);
mv.visitMaxs(2, 3); mv.visitEnd();
public int gt(int x, int y) { if (x > y) return 1; else return -1; } GeneratorAdapter ◦ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "gt", Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE), null, null); GeneratorAdapter ga = new GeneratorAdapter(mv, ACC_PUBLIC, "gt", Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE));
Label labelElse = ga.newLabel(); ga.loadArg(0); ga.loadArg(1); ga.ifICmp(LE, labelElse);
ga.push(1); ga.returnValue();
ga.visitLabel(labelElse); ga.push(-1); ga.returnValue();
ga.endMethod();
public int gt(int x, int y) { if (x > y) return 1; else return -1; } Basic loop example:
◦ public static void count() { for (int i = 0; i < 10; i++) { System.out.println(i); } } Basic loop example: ◦ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "count", Type.getMethodDescriptor(Type.VOID_TYPE), null, null); Label labelLoop = new Label(); Label labelEnd = new Label(); mv.visitCode(); mv.visitInsn(ICONST_0); mv.visitVarInsn(ISTORE, 1); mv.visitLabel(labelLoop); mv.visitVarInsn(ILOAD, 1); mv.visitIntInsn(BIPUSH, 10); mv.visitJumpInsn(IF_ICMPGE, labelEnd); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitVarInsn(ILOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); mv.visitIincInsn(1, 1); mv.visitJumpInsn(GOTO, labelLoop); mv.visitLabel(labelEnd); mv.visitInst(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); public static void count() { for (int i = 0; i < 10; i++) { System.out.println(i); } } Basic loop example: ◦ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "count", Type.getMethodDescriptor(Type.VOID_TYPE), null, null); GeneratorAdapter ga = new GeneratorAdapter(mv, ACC_PUBLIC | ACC_STATIC, "count", Type.getMethodDescriptor(Type.VOID_TYPE));
int localI = ga.newLocal(Type.INT_TYPE); Label labelLoop = ga.newLabel(); Label labelEnd = ga.newLabel(); ga.push(0); ga.storeLocal(localI); ga.visitLabel(labelLoop); ga.loadLocal(localI); ga.push(10); ga.ifICmp(GeneratorAdapter.GE, labelEnd); ga.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class)); ga.loadLocal(localI); ga.invokeVirtual(Type.getType(PrintStream.class), Method.getMethod("void println(int)")); ga.iinc(localI, 1); ga.goTo(labelLoop); ga.visitLabel(labelEnd); ga.returnValue();
ga.endMethod(); public static void count() { for (int i = 0; i < 10; i++) { System.out.println(i); } } mv = cw.visitMethod(ACC_PUBLIC, "calc", "(II)I", null, null); Label labelStart = new Label(); Label labelEnd = new Label(); mv.visitCode(); mv.visitInsn(ICONST_1); mv.visitVarInsn(ISTORE, 3); mv.visitInsn(ICONST_0); mv.visitVarInsn(ISTORE, 4); mv.visitLabel(labelStart); mv.visitVarInsn(ILOAD, 4); mv.visitVarInsn(ILOAD, 2); mv.visitJumpInsn(IF_ICMPGE, labelEnd); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 1); mv.visitInsn(IMUL); mv.visitVarInsn(ISTORE, 3); mv.visitIincInsn(4, 1); mv.visitJumpInsn(GOTO, labelStart); mv.visitLabel(labelEnd); mv.visitVarInsn(ILOAD, 3); mv.visitInsn(IRETURN); mv.visitMaxs(2, 5); mv.visitEnd(); mv = cw.visitMethod(ACC_PUBLIC, "calc", "(II)I", null, null); Label labelStart = new Label(); Label labelEnd = new Label(); mv.visitCode(); mv.visitInsn(ICONST_1); mv.visitVarInsn(ISTORE, 3); // int local3 = 1 mv.visitInsn(ICONST_0); mv.visitVarInsn(ISTORE, 4); // int local4 = 0 mv.visitLabel(labelStart); mv.visitVarInsn(ILOAD, 4); mv.visitVarInsn(ILOAD, 2); mv.visitJumpInsn(IF_ICMPGE, labelEnd); // if (local4 >= local2) goto end mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 1); mv.visitInsn(IMUL); mv.visitVarInsn(ISTORE, 3); // local3 = local3 * local1; mv.visitIincInsn(4, 1); // local4++ mv.visitJumpInsn(GOTO, labelStart); // goto start mv.visitLabel(labelEnd); mv.visitVarInsn(ILOAD, 3); mv.visitInsn(IRETURN); // return result mv.visitMaxs(2, 5); mv.visitEnd(); mv = cw.visitMethod(ACC_PUBLIC, "calc", "(II)I", null, null); Label labelStart = new Label(); Label labelEnd = new Label(); mv.visitCode(); mv.visitInsn(ICONST_1); mv.visitVarInsn(ISTORE, 3); mv.visitInsn(ICONST_0); mv.visitVarInsn(ISTORE, 4); mv.visitLabel(labelStart); mv.visitVarInsn(ILOAD, 4); mv.visitVarInsn(ILOAD, 2); mv.visitJumpInsn(IF_ICMPGE, labelEnd); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 1); mv.visitInsn(IMUL); mv.visitVarInsn(ISTORE, 3); mv.visitIincInsn(4, 1); mv.visitJumpInsn(GOTO, labelStart); mv.visitLabel(labelEnd); mv.visitVarInsn(ILOAD, 3); mv.visitInsn(IRETURN); public void calc(int x, int y) { mv.visitMaxs(2, 5); mv.visitEnd(); int result = ; for (int i = 0; i < y; i++) { result = result * x; } return result; } package com.test; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; import java.io.PrintStream; import static org.objectweb.asm.Opcodes.*; public class Test { public static void main(String[] args) throws Exception { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cw.visit(V1_6, ACC_PUBLIC, "test/TestClass", null, "java/lang/Object", new String[] { "java/lang/Runnable" });
// Create Constructor MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "
// public void run(); mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null); ga = new GeneratorAdapter(mv, ACC_PUBLIC, "run", "()V"); ga.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class)); ga.push("Hello World"); ga.invokeVirtual(Type.getType(PrintStream.class), Method.getMethod("void println(String)")); ga.returnValue(); ga.endMethod();
cw.visitEnd(); final byte[] classBytes = cw.toByteArray();
new ClassLoader() {{ ((Runnable)defineClass(classBytes, 0, classBytes.length).newInstance()).run(); }}; } }
Basics for transforming a class ◦ ClassReader cr = new ClassReader(orgBytes); ClassWriter cw = new ClassWriter();
ClassVisitor ca = new MyClassVisitor(cw);
cr.accept(ca);
byte[] transformedBytes = cw.toByteArray();
◦ class MyClassVisitor extends ClassVisitor { public MethodVisitor visitMethod(...) { MethodVisitor mv = super.visitMethod(...); if (!name.equals(“
◦ class MyMethodVisitor extends MethodVisitor { public void visitCode() { super.visitCode(); visitLdcInsn(“Entering method: ” + methodName); visitMethodInsn(INVOKESTATIC, “MyHelper”, “print”, “(Ljava/lang/String;)V”); } } Modifying opcodes ◦ Changing all hardcoded string to lowercase
◦ class MyMethodVisitor extends MethodVisitor { public void vistLdcInsn(Object cst) { if (cst instanceof String) cst = ((String)cst).toLowerCase(); super.visitLdcInsn(cst); } } Modifying opcodes ◦ Calling a different method Example, change from print() to println()
◦ class MyMethodVisitor extends MethodVisitor { public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { if ((name.equals(“print”) && owner.equals(“java/lang/PrintStream”)) name = “println”; super.visitMethodInsn(opcode, owner, name, desc, itf); } } Modifying opcodes ◦ Removing method calls Example, remove all calls to println()
◦ class MyMethodVisitor extends MethodVisitor { public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { if ((name.equals(“println”) && owner.equals(“java/lang/PrintStream”)) { List
Build-time generation
Build-time post processing
Runtime generation and scanning
Load-time instrumentation
Compilers ◦ You should know what this is already!
If not, you’ll probably fail this course
Retro translation ◦ Transforming bytecode to be compatible with earlier versions Conversion of added features in bytecode LDC class constant from Java 5 on INVOKEDYNAMIC for creating Lambda classes
Conversion of added API Integer.valueOf(x) -> new Integer(x) for JDK 5->4 Example ◦ Java 5+ version: LDC com/pkg/Some.class
◦ Java 4 version: GETSTATIC $class$com_pkg_Some IFNONNULL :OK LDC “com/pkg/Some” INVOKESTATIC Class.forName(String):Class PUTSTATIC $class$com_pkg_Some :OK GETSTATIC $class$com_pkg_Some Shading ◦ The act of renaming classes and usages thereof
◦ Often used to bundle libraries Shading them with unique package prefix so they don’t interfere with other versions of same library present on class path
◦ Often very fast, as it can be done by changing constant pool entries Process classes in an archive ◦ Modify the bytecode to map classes to new name
Example, ASM is bundled in the JDK ◦ Multiple times!
◦ jdk.internal.org.objectweb.asm ◦ com.sun.xml.internal.ws.org.objectweb.asm Usually done as part of the build tool chain
Examples ◦ Maven: maven-shade-plugin https://maven.apache.org/plugins/maven-shade-plugin/
◦ Gradle: Gradle Shadow https://github.com/johnrengelman/shadow Obfuscation ◦ The act of obfuscating the bytecode, to make it harder to decompile
Common patterns ◦ Renaming classes and members ◦ Stripping debug information ◦ Mask String-literals by applying a function Advanced patterns ◦ Utilize invokedynamic and custom callsites, to mask method invocations Why obfuscate? ◦ Java bytecode is easy to reverse
◦ javac compiler is “stupid” Doesn’t optimize code Common patterns for everything
◦ Obfuscation makes it harder to read, not impossible Renaming classes and members ◦ Similar to shading
◦ Utilizes that return type is part of the method descriptor to rename many methods to same name
◦ Have to remember to exempt public API or implementations of exposed API! Renaming ◦ Before class Person { private String name; private int age; public Person() { ... } public void greet() { ... } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } } Renaming ◦ After class a { private String a; private int a; public a() { ... } public void a() { ... } public void a(String) { this.a = $1; } public String a() { return a; } public void a(int) { this.a = $1; } public int a() { return a; } } String-literal obfuscation ◦ Before static final String url = “http://google.com”;
◦ After static final String url = $deopf$(“uggc://tbbtyr.pbz”); private static String $deopf$(String) { … }
◦ Algorithm used for obfuscating the String varies
Dynamic Proxies ◦ Dynamically generates bytecode at runtime ◦ Used exhaustively in Java EE CDI (Contexts and Dependency Injection) JMS (Java Message Service) EJB (Enterprise JavaBean) etc. Example providers
◦ java.lang.reflect.Proxy (JDK Proxy)
◦ Cglib
◦ Javassist Generate implementations of interfaces dynamically at runtime (or sub types) Example ◦ Proxy.newProxyInstance(… Runnable.class ...) ◦ Will create a class akin to class $Proxy1 extends Proxy implements Runnable { public void run() { handle.invoke(...) } /* ... More method ... */ } ◦ Delegating all invocations on the interface to the invocation handler
Class loader specific instrumentation ◦ Use custom ClassLoader that performs the transformation before defining the class
Global instrumentation ◦ Use the Instrumentation API https://docs.oracle.com/javase/8/docs/api/java/lang/ins trument/package-summary.html ◦ You register a transformer, that is invoked for every class loaded afterwards ◦ Often done with Java Agent (-javaagent) Profilers, APMs, etc ◦ Injects code to monitor usage, performance, resources, etc JVisualVM, YourKit, JProfiler XRebel, New Relic, AppDynamics
Other agent ◦ Load-time-weaving (e.g. AspectJ) ◦ Reload agents (e.g. JRebel) Aspect Oriented Programming
◦ Load-time weaving of aspects Adding aspects to methods at runtime Changing Removing
Classic tracing profiling can be considered an around advice ◦ Basic tracing profiler: Before: { methodBody(); }
After: { Profiler.enterMethod(...); try { methodBody(); } finally { Profiler.exitMethod(...); } } Basics for transforming a class ◦ ClassReader cr = new ClassReader(orgBytes); ClassWriter cw = new ClassWriter();
ClassVisitor ca = new MyClassVisitor(cw);
cr.accept(ca);
byte[] transformedBytes = cw.toByteArray();
◦ class MyClassVisitor extends ClassVisitor { public MethodVisitor visitMethod(...) { MethodVisitor mv = super.visitMethod(...); if (!name.equals(“
◦ Class TracingMethodVisitor extends MethodVisitor { private final Label labelStart = new Label(), labelFinally = new Label(); public void visitCode() { super.visitCode(); visitMethodInsn(INVOKESTATIC, “Profiler”, “enterMethod”, “()V”); visitTryCatchBlock(labelStart, labelFinally, labelFinally, null); visitLabel(labelStart); }
public void visitInsn(int opcode) { if (opcode == ATHROW || opcode == RETURN || ...) { visitMethodInsn(INVOKESTATIC, “Profiler”, “exitMethod”, “()V”); } }
public void visitMaxs(int s, int l) { visitLabel(labelFinally); visitInsn(RETURN); super.visitMaxs(s, l); } } JRebel enabled developers to instantly see code changes in the running application
Heavily relies on bytecode transformation to Prepare classes so they can be reloaded Reload the code when new code is available public void test() { assertEquals(++i, method());
}
public static int method() {
return 2; } public void test();
Code:
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: dup_x1
8: putfield #2 // Field i:I
11: invokestatic #3 // Method method:()I
14: invokestatic #4 // Method assertEquals:(II)V
17: return
public static int method();
Code:
0: iconst_2
1: ireturn public void test(); 71: dup
0: aload_0 72: invokestatic #93 // Method zt/atf.isNoForwarding:(LObject;)Z
1: invokevirtual #64 // Method zt/F._jr$iCur:()Z 75: ifne 80
4: ifeq 25 78: pop
7: aload_0 79: return
8: dup 80: pop
9: invokestatic #68 // Method _jr$ig$i:(LTest$$M$_jr_1;)I 81: goto 7
12: iconst_1
13: iadd public static int method();
14: dup_x1 0: ldc #30 // class Test$$M$_jr_1
15: invokestatic #42 // Method _jr$ip$i:(LTest$$M$_jr_1;I)V 2: getfield #38 // Field Class._jr$isRel:Z
18: invokestatic #69 // Method method:()I 5: ifne 10
21: invokestatic #70 // Method assertEquals:(II)V 8: iconst_2
24: return 9: ireturn
25: ldc #30 // class Test$$M$_jr_1 10: ldc #30 // class Test$$M$_jr_1
27: getfield #38 // Field Class._jr$isRel:Z 12: getfield #38 // Field Class._jr$isRel:Z
30: ifne 56 15: ifne 22
33: aload_0 18: invokestatic #69 // Method method:()I
34: invokestatic #76 // Method zt/ata.getCurrentVersion:(Lzt/F;)Lzt/F; 21: ireturn
37: dup 22: ldc #30 // class Test$$M$_jr_1
38: aload_0 24: invokestatic #51 // Method zt/atr.getMeta:(LClass;)Lzt/C;
39: if_acmpeq 80 27: iconst_2
42: dup 28: aconst_null
43: instanceof #30 // class Test$$M$_jr_1 29: ldc #96 // String method
46: ifeq 80 31: getstatic #99 // Field java/lang/Integer.TYPE:Ljava/lang/Class;
49: checkcast #30 // class Test$$M$_jr_1 34: invokestatic #89 // Method zt/atf.invokeWithNoParams:(...)LObject;
52: invokevirtual #78 // Method test:()V 37: dup
55: return 38: invokestatic #93 // Method zt/atf.isNoForwarding:(LObject;)Z
56: ldc #30 // class Test$$M$_jr_1 41: ifne 51
58: invokestatic #51 // Method zt/atr.getMeta:(LClass;)Ltz/reload/C; 44: checkcast #98 // class java/lang/Integer
61: iconst_1 47: invokevirtual #102 // Method java/lang/Integer.intValue:()I
62: aload_0 50: ireturn
63: ldc #79 // String test 51: pop
65: getstatic #85 // Field java/lang/Void.TYPE:Ljava/lang/Class; 52: goto 8
68: invokestatic #89 // Method zt/atf.invokeNoParams:(...)LObject; public void test();
0: aload_0
1: invokevirtual #64 // Method zt/F._jr$iCur:()Z
4: ifeq 25
7: aload_0
8: dup
9: invokestatic #68 // Method _jr$ig$i:(LTest$$M$_jr_1;)I
12: iconst_1
13: iadd
14: dup_x1
15: invokestatic #42 // Method _jr$ip$i:(LTest$$M$_jr_1;I)V
18: invokestatic #69 // Method method:()I
21: invokestatic #70 // Method assertEquals:(II)V
24: return
25: aload_0
26: invokedynamic #81, 0 // INDY #0:test:(LTest$$M$_jr_1;)V
31: return
public static int method();
0: ldc #30 // class Test$$M$_jr_1
2: getfield #38 // Field Class._jr$isRel:Z
5: ifne 10
8: iconst_2
9: ireturn
10: invokedynamic #83, 0 // INDY #1:method:()I
15: ireturn