LavaJUG - bytecode et JVM

LavaJUG - bytecode et JVM

2a4bd42b6a3db5858ff2250e236f9631?s=128

Frédéric Le Mouël

February 07, 2013
Tweet

Transcript

  1. 1 Julien Ponge @jponge Frédéric Le Mouël @flemouel Lava JUG

    vendredi 8 février 13
  2. 2 @jponge @flemouel vendredi 8 février 13

  3. 3 Objectifs JVM, bytecode, JIT Il n’y a pas que

    le GC dans la vie Golo, langage dynamique Pourquoi, quoi, comment ? Partie 1 Partie 2 vendredi 8 février 13
  4. Manipulation de bytecode : démocratisons la magie noire ! 4

    Julien Ponge @jponge Frédéric Le Mouël @flemouel vendredi 8 février 13
  5. 5 Nous faisons du middleware. dynamique pervasif adaptatif reloading multi-tenancy

    resource-control dynamic aspects (...) (...) vendredi 8 février 13
  6. 6 Conteneurs (Java EE, Spring) Android liblayout Play! Framework JRebel

    Hibernate Langages sur JVM Terracotta FindBugs vendredi 8 février 13
  7. 7 java.lang.reflect ... parfois il faut “plus” vendredi 8 février

    13
  8. 8 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 vendredi 8 février 13
  9. JVM + Bytecode crash course 9 vendredi 8 février 13

  10. 10 “JVM Bytecode for Dummies” — Charles Nutter @headius Clair,

    complet et didactique ! vendredi 8 février 13
  11. 11 java/lang/String Nom de classe vendredi 8 février 13

  12. 12 V void Z boolean C char B byte S

    short I int F float J long D double Types primitifs vendredi 8 février 13
  13. 13 Ljava/lang/String; Type objet : L; vendredi 8 février 13

  14. 14 [I [Ljava/lang/String; Tableaux : [ vendredi 8 février 13

  15. 15 (II)I int foo(int, int) (Ljava/lang/String;)V void foo(String) vendredi 8

    février 13
  16. 16 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 ... ... vendredi 8 février 13
  17. 17 public class HelloWorld { public static void main(String[] args)

    { int i = 1; int j = 2; int k = 100; System.out.println(">>> " + (i + j + k)); } } vendredi 8 février 13
  18. 18 $ 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 (...) vendredi 8 février 13
  19. 18 $ 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 vendredi 8 février 13
  20. 19 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 ? ? ? vendredi 8 février 13
  21. 20 1 iconst_1 0 args 1 2 3 variables vendredi

    8 février 13
  22. 21 istore_1 0 args 1 1 2 3 variables vendredi

    8 février 13
  23. 22 2 iconst_2 0 args 1 1 2 3 variables

    vendredi 8 février 13
  24. 23 istore_2 0 args 1 1 2 2 3 variables

    vendredi 8 février 13
  25. 24 100 bipush 100 0 args 1 1 2 2

    3 variables vendredi 8 février 13
  26. 25 istore_3 0 args 1 1 2 2 3 100

    variables vendredi 8 février 13
  27. 26 out getstatic System.out 0 args 1 1 2 2

    3 100 variables vendredi 8 février 13
  28. 27 builder out new StringBuilder 0 args 1 1 2

    2 3 100 variables vendredi 8 février 13
  29. 28 builder builder out dup 0 args 1 1 2

    2 3 100 variables vendredi 8 février 13
  30. 29 builder out invokespecial StringBuilder.<init> 0 args 1 1 2

    2 3 100 variables vendredi 8 février 13
  31. 30 “>>> “ builder out ldc “>>> ” 0 args

    1 1 2 2 3 100 variables vendredi 8 février 13
  32. 31 builder out invokevirtual append(String) 0 args 1 1 2

    2 3 100 variables vendredi 8 février 13
  33. 32 1 builder out iload_1 0 args 1 1 2

    2 3 100 variables vendredi 8 février 13
  34. 33 2 1 builder out iload_2 0 args 1 1

    2 2 3 100 variables vendredi 8 février 13
  35. 34 3 builder out iadd 0 args 1 1 2

    2 3 100 variables vendredi 8 février 13
  36. 35 100 3 builder out iload_3 0 args 1 1

    2 2 3 100 variables vendredi 8 février 13
  37. 36 103 builder out iadd 0 args 1 1 2

    2 3 100 variables vendredi 8 février 13
  38. 37 builder out invokevirtual append(int) 0 args 1 1 2

    2 3 100 variables vendredi 8 février 13
  39. 38 “>>> 103” out invokevirtual toString() 0 args 1 1

    2 2 3 100 variables vendredi 8 février 13
  40. 39 invokevirtual println(String) 0 args 1 1 2 2 3

    100 variables vendredi 8 février 13
  41. 40 return 0 args 1 1 2 2 3 100

    variables vendredi 8 février 13
  42. ASM 41 vendredi 8 février 13

  43. 42 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 ! vendredi 8 février 13
  44. 43 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! vendredi 8 février 13
  45. 44 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...) vendredi 8 février 13
  46. 45 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 vendredi 8 février 13
  47. 46 mv.visitLabel(loop); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("Hello Devoxx France!"); mv.visitMethodInsn(INVOKEVIRTUAL,

    "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); println() vendredi 8 février 13
  48. 47 mv.visitIincInsn(COUNTER_VAR, 1); mv.visitVarInsn(ILOAD, COUNTER_VAR); mv.visitLdcInsn(10); mv.visitJumpInsn(IF_ICMPLT, loop); mv.visitLabel(end); mv.visitInsn(RETURN);

    mv.visitMaxs(666, 666); mv.visitEnd(); writer.visitEnd(); i++, (i < 10)?, goto vendredi 8 février 13
  49. 48 File outDir = new File("gen/my"); outDir.mkdirs(); FileOutputStream out =

    new FileOutputStream(new File(outDir, "Main.class")); out.write(writer.toByteArray()); out.close(); .class ! vendredi 8 février 13
  50. 49 (réalisé sans trucages) vendredi 8 février 13

  51. 50 profiling de chaque méthode utilisée par un programme vendredi

    8 février 13
  52. Manifest-Version: 1.0 Premain-Class: profiler.ProfilerTransformer Can-Redefine-Classes: true Can-Retransform-Classes: true Can-Set-Native-Method-Prefix: false

    java -javaagent:profiler.jar some.Main vendredi 8 février 13
  53. 52 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(); } } vendredi 8 février 13
  54. 53 @Override public void visitCode() { super.visitCode(); startVarId = newLocal(Type.getType("J"));

    mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J"); mv.visitVarInsn(LSTORE, startVarId); } vendredi 8 février 13
  55. 54 @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); } vendredi 8 février 13
  56. 55 ➟ 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 vendredi 8 février 13
  57. 56 “Ok, et pour les cas plus simples ?” vendredi

    8 février 13
  58. 57 vendredi 8 février 13

  59. 58 WTF Layer WTF Layer WTF Layer Security, validation, logging,

    transactions, ... vendredi 8 février 13
  60. 59 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” vendredi 8 février 13
  61. 60 Pointcuts Advices Inter-type declarations Où ? Quoi ? +

    méthodes + attributs + hiérarchie + vérifications + ordre vendredi 8 février 13
  62. 61 (demo) * * en cas de plantage, ne jettez

    pas de tomates, merci. vendredi 8 février 13
  63. 62 aspect ArgsTracer { before(): target(HelloWorld) && call(void run(String...)) {

    System.out.println("Calling run()"); } } Que se passe t-il avec un before ? vendredi 8 février 13
  64. 63 --- 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 } vendredi 8 février 13
  65. vendredi 8 février 13

  66. vendredi 8 février 13

  67. 66 AspectJ est puissant et expressif AspectJ est pratique pour

    des modifications statiques AspectJ est à utiliser avec modération ↪ Spring Roo vendredi 8 février 13
  68. 67 vendredi 8 février 13

  69. 68 Règles dynamiques ! tests : injection de fautes traces

    d’exécution suivi de ressources (...) vendredi 8 février 13
  70. 69 Event Condition Action “100+ instances ?” “Jetter une exception

    !” new java.io.FileInputStream(...); vendredi 8 février 13
  71. 70 Byteman agent rule1.btm rule2.btm rule3.btm JVM void redefineClasses(ClassDefinition... definitions)

    throws ClassNotFoundException, UnmodifiableClassException classes bmsubmit bminstall java -javagent:lib/byteman.jar=... vendredi 8 février 13
  72. 71 RULE calling print in HelloWorld CLASS HelloWorld METHOD print(String)

    IF true DO traceln("calling print() with str = " + $1) ENDRULE vendredi 8 février 13
  73. 72 java.lang.String ^java.lang.Object ^java.util.List java.util.List Types CLASS INTERFACE CLASS INTERFACE

    vendredi 8 février 13
  74. 73 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 vendredi 8 février 13
  75. 74 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 vendredi 8 février 13
  76. 75 (demo) * * en cas de plantage, ne jettez

    pas de tomates, merci. vendredi 8 février 13
  77. vendredi 8 février 13

  78. foo.txt BlobStore.java LICENSE COPYING (...) work/index work/e6a92ec2fe5fba022c31c32c97ea455cee4b2736 work/a9a1282b23431aab89c9a4647fd88569eb9a389d work/6a55a0ac5df9e1edce33995a8ac59f17a04a64c9 vendredi

    8 février 13
  79. 78 maximum testable humainement ≃ vendredi 8 février 13

  80. File.renameTo() File.renameTo() Lecture flux + File.exists() + File.delete() Lecture flux

    + File.exists() + File.delete() Lecture flux + corruption GZIP Lecture flux + corruption GZIP 79 Difficile à reproduire ... vendredi 8 février 13
  81. 80 @RunWith(BMUnitRunner.class) public class BlobStoreTest { ... } vendredi 8

    février 13
  82. 81 @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 vendredi 8 février 13
  83. 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 vendredi 8 février 13
  84. 83 versus vendredi 8 février 13

  85. Méconnu Magique Simple Utile ! 84 vendredi 8 février 13

  86. 85 JooFlux vendredi 8 février 13

  87. 86 (demo) * * en cas de plantage, ne jettez

    pas de tomates, merci. vendredi 8 février 13
  88. Pas de langage d’aspects Pas de pré/post compilation Un agent

    Java JVM non-modifiée JooFlux Dév appli Runtime vendredi 8 février 13
  89. invokedynamic 101 vendredi 8 février 13

  90. invokestatic public static int add(int a, int b) invokevirtual public

    int add(int a, int b) invokeinterface int add(int a, int b) invokespecial private int add(int a, int b) vendredi 8 février 13
  91. public Object call(Object receiver, Object[] args) throws Throwable { return

    CallSiteArray.defaultCall(this, receiver, args); } public Object call(Object receiver) throws Throwable { return call(receiver, CallSiteArray.NOPARAM); } public Object call(Object receiver, Object arg1) throws Throwable { return call(receiver, ArrayUtil.createArray(arg1)); } public Object call(Object receiver, Object arg1, Object arg2) throws Throwable { return call(receiver, ArrayUtil.createArray(arg1, arg2)); } public Object call(Object receiver, Object arg1, Object arg2, Object arg3) throws Throwable { return call(receiver, ArrayUtil.createArray(arg1, arg2, arg3)); } public Object call(Object receiver, Object arg1, Object arg2, Object arg3, Object arg4) throws Throwable { return call(receiver, ArrayUtil.createArray(arg1, arg2, arg3, arg4)); } “Tous les chemins mènent à Rome” vendredi 8 février 13
  92. public static Object invoke(Object object, String methodName, Object[] parameters) {

    try { Class[] classTypes = new Class[parameters.length]; for (int i = 0; i < classTypes.length; i++) { classTypes[i] = parameters[i].getClass(); } Method method = object.getClass().getMethod(methodName, classTypes); return method.invoke(object, parameters); } catch (Throwable t) { return InvokerHelper.invokeMethod(object, methodName, parameters); } } Difficile pour le JIT ! CallSites génériques Réflexivité vendredi 8 février 13
  93. invokedynamic nom symbolique + signature + bootstrap CallSite Constant Mutable

    Volatile MethodHandle direct ou combinateurs branchements adaptation filtres (...) 7 vendredi 8 février 13
  94. vendredi 8 février 13

  95. Implémentation vendredi 8 février 13

  96. 1 2 3 4 Chargement des classes Interception Management Runtime

    vendredi 8 février 13
  97. (...) ldc #3 ldc #5 invokedynamic foo (II)I bootstraper (...)

    Call site JMX agent JooFlux bootstraper Modified bytecode Registry Target method combinators Load time action Run time action Original bytecode JVM agent 1 2 3 4 vendredi 8 février 13
  98. Réécriture de bytecode ASM + Agent Bootstrap JooFlux Casse la

    vérification de bytecode ! 1 vendredi 8 février 13
  99. sémantique champ volatile impact mesuré négligeable VolatileCallSite 2 vendredi 8

    février 13
  100. invokestatic invokespecial Method handle direct invokevirtual invokeinterface Method handle sur

    le premier receveur 3 vendredi 8 février 13
  101. invokeinterface Polymorphic inline-cache guardWithTest(Class1, obj) guardWithTest(Class2, obj) fallback(callsite, args[]) Class1#foo(II)I

    Class2#foo(II)I obj.foo(1, 2) 3 vendredi 8 février 13
  102. 4 public interface JooFluxManagementMXBean { public String getName(); public int

    getNumberOfRegisteredCallSites(); public Set<String> getRegisteredCallSiteKeys(); public String getCallSiteType(String target); public void changeCallSiteTarget(String methodType, String oldTarget, String newTarget); public void applyBeforeAspect(String callSitesKey, String aspectClass, String aspectMethod); public void applyAfterAspect(String callSitesKey, String aspectClass, String aspectMethod); } vendredi 8 février 13
  103. (...) ldc #3 ldc #5 invokedynamic foo (II)I bootstraper (...)

    Call site JMX agent JooFlux bootstraper Modified bytecode Registry Target method combinators Load time action Run time action Original bytecode JVM agent 4 Registre : 0 impact perfs Enregistré à la 1ère interception Modification via API vendredi 8 février 13
  104. Évaluation vendredi 8 février 13

  105. Interception / redirection de méthode Injection d’aspect ‘vide’ Langages dynamiques

    Plates-formes AOP Micro Fibonacci(40) Macro SCImark Fork/Join Clojure Benchmarks X V V V vendredi 8 février 13
  106. 0 2 4 6 8 Byteman Jooflux Avant Après Avant

    + Après Injection d’aspect ‘vide’ x 137 x 134 x 306 x 6 x 2 x 5 Microbench vendredi 8 février 13
  107. Interception/redirection méthode 0 5 10 15 Clojure JRuby Groovy Rhino

    JS Jython Java+JooFlux Min Max x 1 x 48 x 18 x 15 x 2,2 x 10 x 3 x 6 x 1,2 x 2 Microbench vendredi 8 février 13
  108. Interception/redirection méthode 0,9 1 1,1 SCIMark Fork/Join Clojure Min Max

    x 0,99 x 0,97 x 1,04 x 0,93 x 1,01 x 1,01 Macrobench vendredi 8 février 13
  109. Conclusion vendredi 8 février 13

  110. JVM généraliste Interception à grain fin Résultats initiaux encourageants Approche

    nouvelle, générique, sans langage dédié Bilan vendredi 8 février 13
  111. Injection atomique multi-aspects Rollback / versioning Vérification formelle pré-injection Aspects

    contextuels / Internet of Things Contrôle de ressources avec isolation adaptative Perspectives vendredi 8 février 13
  112. 111 https://github.com/dynamid/jooflux Questions / réponses Julien Ponge Frédéric Le Mouël

    http://dynamid.citi-lab.fr/ vendredi 8 février 13