Mercurial > hg > release > thermostat-2.0
changeset 1581:610cf7f75c49
Handle exceptions in instrumented code
Reviewed-by: neugens
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-November/011810.html
author | Omair Majid <omajid@redhat.com> |
---|---|
date | Fri, 28 Nov 2014 16:44:05 -0500 |
parents | 3c0f219f340a |
children | 874a0ce0c0d5 |
files | vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/AsmBasedInstrumentor.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfileRecorder.java |
diffstat | 3 files changed, 95 insertions(+), 15 deletions(-) [+] |
line wrap: on
line diff
--- a/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/AsmBasedInstrumentor.java Fri Nov 28 16:40:46 2014 -0500 +++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/AsmBasedInstrumentor.java Fri Nov 28 16:44:05 2014 -0500 @@ -39,11 +39,12 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; import org.objectweb.asm.commons.JSRInlinerAdapter; +import org.objectweb.asm.util.CheckClassAdapter; public class AsmBasedInstrumentor extends ProfilerInstrumentor { @@ -59,7 +60,13 @@ ClassWriter writer = new ClassLoaderFriendlyClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS, cl); InstrumentingClassAdapter instrumentor = new InstrumentingClassAdapter(writer); reader.accept(instrumentor, ClassReader.SKIP_FRAMES); - return writer.toByteArray(); + byte[] data = writer.toByteArray(); + + // check that the bytecode is valid + reader = new ClassReader(data); + reader.accept(new CheckClassAdapter(new ClassWriter(0)), 0); + + return data; } catch (Exception e) { System.err.println("Error transforming: " + className); @@ -87,20 +94,63 @@ @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); - if (mv != null) { + // FIXME instrument constructors + if (mv != null && !(name.equals("<init>"))) { MethodVisitor instrumentor = new InstrumentingMethodAdapter(mv, className, access, name, desc); mv = new JSRInlinerAdapter(instrumentor, access, name, desc, signature, exceptions); } + return mv; } } + /** + * Inserts a try-finally around the an arbitrary method and calls + * {@link ProfileRecorder} to record method execution times. + * <p> + * Functionally, it should transform: + * + * <pre> + * public Object foo(int bar) { + * // do something + * return object; + * } + * </pre> + * + * to + * + * <pre> + * public Object foo(int bar) { + * ProfilerData.enterMethod("description"); + * try { + * // do something + * return object + * } finally { + * profilerData.exitMethod("description"); + * } + * } + * </pre> + * + * Java bytecode has no concept of {@code finally} in a {@code try}- + * {@code catch}-{@code finally} block. The {@code finally} code needs to be + * duplicated in a {@code catch} block as well as in the normal-return + * block. Because there may already be exception-handling code in the + * method, it adds an exception-table entry in the last place that covers + * the entire method. + */ static class InstrumentingMethodAdapter extends AdviceAdapter { + + private static final String EXIT_METHOD = "exitMethod"; + private String className; private String methodName; + private Label startFinally = new Label(); + private Label endFinally = new Label(); + protected InstrumentingMethodAdapter(MethodVisitor mv, String className, int access, String methodName, String desc) { super(Opcodes.ASM5, mv, access, methodName, desc); @@ -109,13 +159,40 @@ } @Override + public void visitCode() { + super.visitCode(); + + mv.visitLabel(startFinally); + } + + @Override protected void onMethodEnter() { callProfilerRecorder("enterMethod"); } @Override protected void onMethodExit(int opCode) { - callProfilerRecorder("exitMethod"); + // An ATHROW will be caught in the catch-all-exceptions block and handled there + if (opCode != ATHROW) { + callProfilerRecorder(EXIT_METHOD); + } + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + + // add a catch-all-exceptions handler + mv.visitLabel(endFinally); + callProfilerRecorder(EXIT_METHOD); + mv.visitInsn(ATHROW); + + // This is important. This catch-all-exceptions handler must be the + // last entry in the exception table so it only gets invoked if + // there is no other handler and the exception would have been + // thrown to the caller. + mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null); + + super.visitMaxs(maxStack, maxLocals); } private void callProfilerRecorder(String method) {
--- a/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java Fri Nov 28 16:40:46 2014 -0500 +++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java Fri Nov 28 16:44:05 2014 -0500 @@ -145,13 +145,16 @@ if (toTransform.size() > 0) { System.out.println("AGENT: Retransforming " + toTransform.size() + " classes"); - try { - instrumentation.retransformClasses(toTransform.toArray(new Class<?>[toTransform.size()])); - } catch (UnmodifiableClassException e) { - throw new AssertionError("Tried to modify an unmodifiable class", e); - } catch (InternalError e) { - e.printStackTrace(); - System.err.println("Error retransforming already loaded classes."); + for (Class<?> klass : toTransform) { + try { + instrumentation.retransformClasses(klass); + } catch (UnmodifiableClassException e) { + throw new AssertionError("Tried to modify an unmodifiable class", e); + } catch (Error e) { + System.err.println("Failed to trasnform: " + klass.getName()); + System.err.println("Unable to retransform all classes. Some classes may not have been instrumented correctly!"); + e.printStackTrace(); + } } } long end = System.nanoTime();
--- a/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfileRecorder.java Fri Nov 28 16:40:46 2014 -0500 +++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfileRecorder.java Fri Nov 28 16:44:05 2014 -0500 @@ -108,10 +108,10 @@ long diff = currentTime - oldTime; if (!fullyQualifiedName.equals(stack.peek())) { - System.out.println("name: '" + fullyQualifiedName + "'"); - System.out.println("stack top: '" + stack.peek() + "'"); - System.out.println("stack: " + stack); - throw new AssertionError("should not happen"); + throw new AssertionError("should not happen:\n" + + "name: '" + fullyQualifiedName + "'\n" + + "stack top: '" + stack.peek() + "'\n" + + "stack: " + stack); } addData(stack.poll(), diff);