Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Manipulation de bytecode au MarsJUG Juin 2012

Manipulation de bytecode au MarsJUG Juin 2012

Julien Ponge

June 20, 2012
Tweet

More Decks by Julien Ponge

Other Decks in Programming

Transcript

  1. Manipulation de bytecode : démocratisons la magie noire ! [MarsJUG

    Extended Remix] 1 Julien Ponge @jponge Frédéric Le Mouël @flemouel
  2. 2 >

  3. 5 Conteneurs (Java EE, Spring) Android liblayout Play! Framework JRebel

    Hibernate Langages sur JVM Terracotta FindBugs
  4. 7 JVM+Bytecode ASM AspectJ Byteman JooFlux ... en fait c’est

    facile ! écrire et transformer du bytecode faire simplement des modifications statiques courantes avec des aspects aspects dynamiques, application à l’amélioration des couvertures de tests aspects dynamiques via invokedynamic
  5. 11 V void Z boolean C char B byte S

    short I int F float J long D double Types primitifs
  6. 15 Constant pool (nombres, String, types) Variables locales Machine à

    pile OpCodes invocation de méthode manipulation de pile get / set champs arithmétique allocation sauts lock (...) 0 java/lang/String 1 “Foo” 2 666 ... ... 0 this (sauf static) 1 arg1 2 arg2 3 int i ... ...
  7. 16 public class HelloWorld { public static void main(String[] args)

    { int i = 1; int j = 2; int k = 100; System.out.println(">>> " + (i + j + k)); } }
  8. 17 $ javap -c HelloWorld Compiled from "HelloWorld.java" public class

    HelloWorld extends java.lang.Object{ public HelloWorld(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return (...)
  9. 17 $ javap -c HelloWorld Compiled from "HelloWorld.java" public class

    HelloWorld extends java.lang.Object{ public HelloWorld(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return (...) invocation private et constructeur + invoke{static, interface, virtual, dynamic} décompilateur constructeur + <cinit>, <clinit> this dans le constant pool
  10. 18 public static void main(java.lang.String[]); Code: 0: iconst_1 1: istore_1

    2: iconst_2 3: istore_2 4: bipush 100 6: istore_3 7: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 10: new #3; //class java/lang/StringBuilder 13: dup 14: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V 17: ldc #5; //String >>> 19: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuil 22: iload_1 23: iload_2 24: iadd 25: iload_3 26: iadd 27: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 30: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 33: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 36: return ? ? ?
  11. 41 package my; public class Main { public static void

    main(String[] args) { for (int i = 0; i < 10; i++) { System.out.println("Hello Devoxx France!"); } } } Générons ceci sans javac !
  12. 42 i: int [start, end] start: i = 0 loop:

    println(System.out, “Hello Devoxx France!”) incr(i, 1) jump(loop) if (i < 10) end: return goto isn’t harmful!
  13. 43 ClassWriter writer = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); writer.visit(V1_6, ACC_PUBLIC,

    "my/Main", null, "java/lang/Object", null); writer.visitSource("Main.java", null); MethodVisitor mv = writer.visitMethod(ACC_STATIC | ACC_PUBLIC, "main", "([Ljava/lang/String;)V", null, null); mv.visitCode(); Classe + main(String...)
  14. 44 Label start = new Label(); Label loop = new

    Label(); Label end = new Label(); mv.visitLabel(start); final int COUNTER_VAR = 1; mv.visitLocalVariable("counter", "I", null, start, end, COUNTER_VAR); mv.visitInsn(ICONST_0); mv.visitVarInsn(ISTORE, COUNTER_VAR); i = 0
  15. 47 File outDir = new File("gen/my"); outDir.mkdirs(); FileOutputStream out =

    new FileOutputStream(new File(outDir, "Main.class")); out.write(writer.toByteArray()); out.close(); .class !
  16. 51 public class ProfilerTransformer implements ClassFileTransformer { public static void

    premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new ProfilerTransformer()); } public byte[] transform(ClassLoader classLoader, String s, Class<?> aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException { ClassReader reader = new ClassReader(bytes); ClassWriter writer = new ClassWriter(reader, COMPUTE_FRAMES | COMPUTE_MAXS); ProfilerClassVisitor profiler = new ProfilerClassVisitor(ASM4, writer); reader.accept(profiler, 0); return writer.toByteArray(); } }
  17. 52 @Override public void visitCode() { super.visitCode(); startVarId = newLocal(Type.getType("J"));

    mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J"); mv.visitVarInsn(LSTORE, startVarId); }
  18. 53 @Override public void visitInsn(int opcode) { if ((opcode >=

    IRETURN && opcode <= RETURN) || opcode == ATHROW) { (...) mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J"); mv.visitVarInsn(LLOAD, startVarId); mv.visitInsn(LSUB); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V"); } super.visitInsn(opcode); }
  19. 54 ➟ adapters, inliner, optimizers, ... ➟ modèle objet ➟

    analyse statique de code ➟ tracing, retro-engineering, ... org.objectweb.asm.commons org.objectweb.asm.tree org.objectweb.asm.tree.analysis org.objectweb.asm.util
  20. 56

  21. 58 public class UserController { // (...) public void update(UserProfile

    profile) throws ControllerException { validator.check(profile); if (logger.isLevelEnabled(DEBUG)) { logger.debug("Updating: " + profile); } dao.save(profile); redirect("/home"); } // (...) } “cross-cutting concerns” “business logic”
  22. 59 Pointcuts Advices Inter-type declarations Où ? Quoi ? +

    méthodes + attributs + hiérarchie + vérifications + ordre
  23. 61 aspect ArgsTracer { before(): target(HelloWorld) && call(void run(String...)) {

    System.out.println("Calling run()"); } } Que se passe t-il avec un before ?
  24. 62 --- HelloWorld.bytecode.original 2012-04-11 22:36:22.000000000 +0200 +++ HelloWorld.bytecode.weaved 2012-04-11 22:45:51.000000000

    +0200 @@ -19,8 +19,16 @@ 3: dup 4: invokespecial #34; //Method "<init>":()V 7: aload_0 - 8: invokevirtual #35; //Method run:([Ljava/lang/String;)V - 11: return + 8: astore_1 + 9: astore_2 + 10: invokestatic #44; //Method ArgsTracer.aspectOf:()LArgsTracer; + 13: aload_2 + 14: aload_1 + 15: invokevirtual #48; //Method ArgsTracer.ajc$before$ArgsTracer$1$66cd2654:(LHelloWorld; [Ljava/lang/String;)V + 18: aload_2 + 19: aload_1 + 20: invokevirtual #35; //Method run:([Ljava/lang/String;)V + 23: return }
  25. 63 aspect MakePersonComparable { declare parents: Person implements Comparable<Person>; public

    int Person.compareTo(Person other) { return getName().compareTo(other.getName()); } } Que se passe t-il avec des déclarations inter-types ?
  26. 64 $ javap -c -private Person Compiled from "Person.java" public

    class Person extends java.lang.Object implements java.lang.Comparable{ (...) public int compareTo(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #1; //class Person 5: invokevirtual #50; //Method compareTo:(LPerson;)I 8: ireturn public int compareTo(Person); Code: 0: aload_0 1: aload_1 2: invokestatic #60; //Method MakePersonComparable.ajc$interMethod $MakePersonComparable$Person$compareTo:(LPerson;LPerson;)I 5: ireturn }
  27. 65 $ javap -c -private MakePersonComparable Compiled from "MakePersonComparable.aj" public

    class MakePersonComparable extends java.lang.Object{ (...) public static int ajc$interMethod$MakePersonComparable$Person$compareTo(Person, Person); Code: 0: aload_0 1: invokevirtual #44; //Method Person.getName:()Ljava/lang/String; 4: aload_1 5: invokevirtual #44; //Method Person.getName:()Ljava/lang/String; 8: invokevirtual #49; //Method java/lang/String.compareTo:(Ljava/lang/String;)I 11: ireturn (...) }
  28. 68 AspectJ est puissant et expressif AspectJ est pratique pour

    des modifications statiques AspectJ est à utiliser avec modération ↪ Spring Roo
  29. 69

  30. 70 Règles dynamiques ! tests : injection de fautes traces

    d’exécution suivi de ressources (...)
  31. 72 Byteman agent rule1.btm rule2.btm rule3.btm JVM void redefineClasses(ClassDefinition... definitions)

    throws ClassNotFoundException, UnmodifiableClassException classes bmsubmit bminstall java -javagent:lib/byteman.jar=...
  32. 73 RULE calling print in HelloWorld CLASS HelloWorld METHOD print(String)

    IF true DO traceln("calling print() with str = " + $1) ENDRULE
  33. 75 AT ENTRY EXIT THROW (count) AT, AFTER INVOKE <method>

    (count) SYNCHRONIZE (count) AT, AFTER LINE <number> READ <field> (count) READ $<var> (count) WRITE <field> (count) WRITE $<var> (count) Spécifier une cible
  34. 76 debug(msg) traceln(msg) traceOpen(id, filename) traceln(id, msg) traceClose(id) traceStack() traceStack(id)

    killThread() killJVM() killJVM(code) Waiters Rendez-vous Joiners Countdowns Flags Counters Timers Helpers actions + conditions + les vôtres
  35. File.renameTo() File.renameTo() Lecture flux + File.exists() + File.delete() Lecture flux

    + File.exists() + File.delete() Lecture flux + corruption GZIP Lecture flux + corruption GZIP 81 Difficile à reproduire ...
  36. 83 @BMScript(value = "create_but_fail_to_mkdir", dir = BYTEMAN_SCRIPTS) @Test(expected = BlobStoreException.class)

    public void create_but_fail_to_mkdir() throws IOException { new BlobStore(new File(temporaryFolder.getRoot(), "missing")); } RULE inject a fault into BlobStore to fail on mkdirs() CLASS blob.store.BlobStore METHOD ensureValidWorkingDirectory(File) AFTER INVOKE java.io.File.mkdirs() IF true DO $! = false ENDRULE
  37. RULE create a countdown when entering BlobStore#rewriteIndex() CLASS blob.store.BlobStore METHOD

    rewriteIndex AT ENTRY IF true DO createCountDown("KillIndex", 1) ENDRULE RULE inject a fault into BlobStore#rewriteIndex() to fail appending to the index CLASS com.google.common.io.Files METHOD append AT ENTRY IF countDown("KillIndex") DO throw new java.io.IOException("Simulating a I/O error") ENDRULE private void rewriteIndex() { if (!indexFile.delete()) { throw new BlobStoreException("Could not delete " + indexFile); } try { for (String key : index.keySet()) { append(indexLineFor(key, index.get(key)), indexFile, UTF_8); } } catch (IOException e) { throw new BlobStoreException(e); } } #fail à la 2ème itération
  38. (...) iconst_1 iconst_2 invokedynamic add(II)I + bootstrap (...) fonction typée,

    mais cible résolue à l’exécution renvoie un java.lang.invoke.CallSite via un invokestatic ou autre
  39. 90 CallSite method handle chaîne de combinateurs méthode cible cible

    (modifiable) asType() asCollector() dropArguments() guardWithTest() (...) (JIT-friendly)
  40. 91 JooFlux invokeXYZ 㱺 invokedynamic agent JVM agent JMX remplacement

    de méthode rechargement de méthode ajout d’aspect dynamique impact minimal coté JIT