diff --git a/instrument/src/main/java/edu/berkeley/cs/jqf/instrument/tracing/ThreadTracer.java b/instrument/src/main/java/edu/berkeley/cs/jqf/instrument/tracing/ThreadTracer.java index aa04296e3..f93ac9d30 100644 --- a/instrument/src/main/java/edu/berkeley/cs/jqf/instrument/tracing/ThreadTracer.java +++ b/instrument/src/main/java/edu/berkeley/cs/jqf/instrument/tracing/ThreadTracer.java @@ -31,7 +31,6 @@ import java.util.ArrayDeque; import java.util.Deque; -import java.util.concurrent.Callable; import java.util.function.Consumer; import java.util.function.Function; @@ -41,7 +40,6 @@ import edu.berkeley.cs.jqf.instrument.tracing.events.ReadEvent; import edu.berkeley.cs.jqf.instrument.tracing.events.ReturnEvent; import edu.berkeley.cs.jqf.instrument.tracing.events.TraceEvent; -import edu.berkeley.cs.jqf.instrument.util.FastBlockingQueue; import janala.logger.inst.*; /** @@ -58,19 +56,18 @@ * * @author Rohan Padhye */ -public class ThreadTracer extends Thread { - protected final FastBlockingQueue queue = new FastBlockingQueue<>(1024*1024); +public class ThreadTracer { protected final Thread tracee; protected final String entryPointClass; protected final String entryPointMethod; protected final Consumer callback; - private final Deque> handlers = new ArrayDeque<>(); + private final Deque handlers = new ArrayDeque<>(); // Values set by GETVALUE_* instructions inserted by Janala private final Values values = new Values(); // Whether to instrument generators (TODO: Make this configurable) - private final boolean instrumentGenerators = false; + private final boolean instrumentGenerators = true; /** @@ -82,7 +79,6 @@ public class ThreadTracer extends Thread { * @param callback the callback to invoke whenever a trace event is emitted */ protected ThreadTracer(Thread tracee, String entryPoint, Consumer callback) { - super("__JWIG_TRACER__: " + tracee.getName()); // The name is important to block snooping this.tracee = tracee; if (entryPoint != null) { int separator = entryPoint.indexOf('#'); @@ -110,7 +106,6 @@ protected static ThreadTracer spawn(Thread thread) { Consumer callback = SingleSnoop.callbackGenerator.apply(thread); ThreadTracer t = new ThreadTracer(thread, entryPoint, callback); - t.start(); return t; } @@ -129,82 +124,19 @@ protected final void emit(TraceEvent e) { * @return whether the instruction queue is empty */ protected final boolean isQueueEmpty() { - return queue.isEmpty(); + return true; } /** - * Sends an instruction to the tracer for processing. + * Handles tracing of a single bytecode instruction. * * @param ins the instruction to process */ protected final void consume(Instruction ins) { - queue.put(ins); + // Apply the visitor at the top of the stack + ins.visit(handlers.peek()); } - /** - * Retrieves the next yet-unprocessed instruction in FIFO sequeuence. - * - *

This method blocks for the next instruction up to a fixed timeout. - * After the timeout, it checks to see if the tracee is alive and if so - * repeats the timed-block. If the tracee is dead, the tracer is - * interrupted.

- * - * @return the next yet-unprocessed instruction in FIFO sequeuence - * @throws InterruptedException if the tracee appears to be dead - */ - protected final Instruction next() throws InterruptedException { - // If a restored instruction exists, take that out instead of polling the queue - if (restored != null) { - Instruction ins = restored; - restored = null; - return ins; - } - // Keep attempting to get instructions while queue is non-empty or tracee is alive - while (!queue.isEmpty() || tracee.isAlive()) { - // Attempt to poll queue with a timeout - Instruction ins = queue.remove(1_000_000_000L); - // Return instruction if available, else re-try - if (ins != null) { - return ins; - } - } - // If tracee is dead, interrupt this thread - throw new InterruptedException(); - } - - // Hack on restore() to prevent deadlocks when main thread waits on put() and logger on restore() - private Instruction restored = null; - - - /** - * Returns an instruction to the queue for processing. - * - *

This is useful for performing a lookahead in the - * instruction stream.

- * - * @param ins the instruction to restore to the queue - */ - protected final void restore(Instruction ins) { - if (restored != null) { - throw new IllegalStateException("Cannot restore multiple instructions"); - } else { - restored = ins; - } - } - - @Override - public void run() { - try { - while (!handlers.isEmpty()) { - handlers.peek().call(); - } - } catch (InterruptedException e) { - // Exit normally - } catch (Throwable e) { - e.printStackTrace(); - handlers.clear(); // Don't do anything else - } - } private static boolean isReturnOrMethodThrow(Instruction inst) { return inst instanceof ARETURN || @@ -268,32 +200,24 @@ private static boolean sameNameDesc(MemberRef m1, MemberRef m2) { - class BaseHandler implements Callable { + class BaseHandler extends ControlFlowInstructionVisitor { @Override - public Void call() throws Exception { - Instruction ins = next(); - if (ins instanceof METHOD_BEGIN) { - METHOD_BEGIN begin = (METHOD_BEGIN) ins; - // Try to match the top-level call with the entry point - String clazz = begin.getOwner(); - String method = begin.getName(); - if ((clazz.equals(entryPointClass) && method.equals(entryPointMethod)) || - (instrumentGenerators && clazz.endsWith("Generator") && method.equals("generate")) ) { - emit(new CallEvent(0, null, 0, begin)); - handlers.push(new TraceEventGeneratingHandler(begin, 0)); - } else { - // Ignore all top-level calls that are not the entry point - handlers.push(new MatchingNullHandler()); - } + public void visitMETHOD_BEGIN(METHOD_BEGIN begin) { + // Try to match the top-level call with the entry point + String clazz = begin.getOwner(); + String method = begin.getName(); + if ((clazz.equals(entryPointClass) && method.equals(entryPointMethod)) || + (instrumentGenerators && clazz.endsWith("Generator") && method.equals("generate")) ) { + emit(new CallEvent(0, null, 0, begin)); + handlers.push(new TraceEventGeneratingHandler(begin, 0)); } else { - // Instructions not nested in a METHOD_BEGIN are quite unexpected - // System.err.println("Unexpected: " + ins); -- XXX: First instruction is a dummy + // Ignore all top-level calls that are not the entry point + handlers.push(new MatchingNullHandler()); } - return null; } } - class TraceEventGeneratingHandler extends ControlFlowInstructionVisitor implements Callable { + class TraceEventGeneratingHandler extends ControlFlowInstructionVisitor { private final int depth; private final MemberRef method; @@ -340,9 +264,9 @@ public void visitINVOKEMETHOD_EXCEPTION(INVOKEMETHOD_EXCEPTION ins) { // Handle end of super() or this() call if (invokingSuperOrThis) { while (true) { // will break when outer caller of found - emit(new ReturnEvent(-1, this.method, 0)); + emit(new ReturnEvent(-1, this.method, -1)); handlers.pop(); - Callable handler = handlers.peek(); + IVisitor handler = handlers.peek(); // We should not reach the BaseHandler without finding // the TraceEventGeneratingHandler who called the outer (). assert (handler instanceof TraceEventGeneratingHandler); @@ -353,8 +277,9 @@ public void visitINVOKEMETHOD_EXCEPTION(INVOKEMETHOD_EXCEPTION ins) { } else { // Found caller of new() assert(traceEventGeneratingHandler.invokeTarget.getName().startsWith("")); - restore(ins); - return; // defer handling to new top of stack + // Let this handler (now top-of-stack) process the instruction + ins.visit(traceEventGeneratingHandler); + break; } } } @@ -405,27 +330,22 @@ public void visitGETVALUE_int(GETVALUE_int gv) { } @Override - public void visitConditionalBranch(Instruction ins) { - try { - Instruction next = next(); - int iid = ins.iid; - int lineNum = ins.mid; - boolean taken; - if ((next instanceof SPECIAL) && ((SPECIAL) next).i == SPECIAL.DID_NOT_BRANCH) { - // Special marker ==> False Branch - taken = false; - } else { - // Not a special marker ==> True Branch - restore(next); // Remember to put this instruction back on the queue - taken = true; - } - emit(new BranchEvent(iid, this.method, lineNum, taken ? 1 : 0)); + public void visitGETVALUE_boolean(GETVALUE_boolean gv) { + values.booleanValue = gv.v; - super.visitConditionalBranch(ins); - } catch (InterruptedException e) { - // Unfortunately, visitor cannot throw a checked exception - throw new RuntimeException(e); // this is unwrappped in call() - } + super.visitGETVALUE_boolean(gv); + } + + @Override + public void visitConditionalBranch(Instruction ins) { + int iid = ins.iid; + int lineNum = ins.mid; + // The branch taken-or-not would have been set by a previous + // GETVALUE instruction + boolean taken = values.booleanValue; + emit(new BranchEvent(iid, this.method, lineNum, taken ? 1 : 0)); + + super.visitConditionalBranch(ins); } @Override @@ -506,34 +426,18 @@ public void visitReturnOrMethodThrow(Instruction ins) { super.visitReturnOrMethodThrow(ins); } + } - @Override - public Void call() throws InterruptedException { - Instruction ins = next(); - - try { - ins.visit(this); - } catch (RuntimeException e) { - // Unwrap thread interruptions if any - if (e.getCause() instanceof InterruptedException) { - throw (InterruptedException) e.getCause(); - } - } + class MatchingNullHandler extends ControlFlowInstructionVisitor { - return null; + @Override + public void visitMETHOD_BEGIN(METHOD_BEGIN begin) { + handlers.push(new MatchingNullHandler()); } - } - class MatchingNullHandler implements Callable { @Override - public Void call() throws InterruptedException { - Instruction ins = next(); - if (ins instanceof METHOD_BEGIN) { - handlers.push(new MatchingNullHandler()); - } else if (isReturnOrMethodThrow(ins)) { - handlers.pop(); - } - return null; + public void visitReturnOrMethodThrow(Instruction ins) { + handlers.pop(); } } } diff --git a/instrument/src/main/java/janala/instrument/GlobalStateForInstrumentation.java b/instrument/src/main/java/janala/instrument/GlobalStateForInstrumentation.java index 7da1abaf9..3b846050f 100644 --- a/instrument/src/main/java/janala/instrument/GlobalStateForInstrumentation.java +++ b/instrument/src/main/java/janala/instrument/GlobalStateForInstrumentation.java @@ -16,7 +16,7 @@ public class GlobalStateForInstrumentation { public int incAndGetId() { iid++; - validate(iid, (32 - CBITS - MBITS)); + validate(iid, IBITS); return getId(); } diff --git a/instrument/src/main/java/janala/instrument/SnoopInstructionMethodAdapter.java b/instrument/src/main/java/janala/instrument/SnoopInstructionMethodAdapter.java index 1b57f9fb2..f0eead228 100644 --- a/instrument/src/main/java/janala/instrument/SnoopInstructionMethodAdapter.java +++ b/instrument/src/main/java/janala/instrument/SnoopInstructionMethodAdapter.java @@ -784,100 +784,97 @@ public void visitMethodInsn(int opcode, String owner, String name, String desc, } } + private void addConditionalJumpInstrumentation(int opcode, Label finalBranchTarget, + String instMethodName, String instMethodDesc) { + int iid = instrumentationState.incAndGetId(); + Label intermediateBranchTarget = new Label(); + Label fallthrough = new Label(); + + // Perform the original jump, but branch to intermediate label + mv.visitJumpInsn(opcode, intermediateBranchTarget); + // If we did not jump, skip to the fallthrough + mv.visitJumpInsn(GOTO, fallthrough); + + // Now instrument the branch target + mv.visitLabel(intermediateBranchTarget); + addBipushInsn(mv, 1); // Mark branch as taken + addValueReadInsn(mv, "B", "GETVALUE_"); // Send value to logger + mv.visitInsn(POP); + addBipushInsn(mv, iid); + addBipushInsn(mv, lastLineNumber); + addBipushInsn(mv, getLabelNum(finalBranchTarget)); + mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, instMethodName, instMethodDesc, false); + mv.visitJumpInsn(GOTO, finalBranchTarget); // Go to actual branch target + + // Now instrument the fall through + mv.visitLabel(fallthrough); + addBipushInsn(mv, 0); // Mark branch as not taken + addValueReadInsn(mv, "B", "GETVALUE_"); // Send value to logger + mv.visitInsn(POP); + addBipushInsn(mv, iid); + addBipushInsn(mv, lastLineNumber); + addBipushInsn(mv, getLabelNum(finalBranchTarget)); + mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, instMethodName, instMethodDesc, false); + + // continue with fall-through code visiting + } + @Override public void visitJumpInsn(int opcode, Label label) { - int iid3; - addBipushInsn(mv, iid3 = instrumentationState.incAndGetId()); - addBipushInsn(mv, lastLineNumber); - addBipushInsn(mv, getLabelNum(label)); switch (opcode) { case IFEQ: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IFEQ", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IFEQ", "(III)V"); break; case IFNE: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IFNE", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IFNE", "(III)V"); break; case IFLT: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IFLT", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IFLT", "(III)V"); break; case IFGE: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IFGE", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IFGE", "(III)V"); break; case IFGT: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IFGT", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IFGT", "(III)V"); break; case IFLE: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IFLE", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IFLE", "(III)V"); break; case IF_ICMPEQ: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IF_ICMPEQ", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IF_ICMPEQ", "(III)V"); break; case IF_ICMPNE: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IF_ICMPNE", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IF_ICMPNE", "(III)V"); break; case IF_ICMPLT: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IF_ICMPLT", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IF_ICMPLT", "(III)V"); break; case IF_ICMPGE: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IF_ICMPGE", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IF_ICMPGE", "(III)V"); break; case IF_ICMPGT: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IF_ICMPGT", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IF_ICMPGT", "(III)V"); break; case IF_ICMPLE: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IF_ICMPLE", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IF_ICMPLE", "(III)V"); break; case IF_ACMPEQ: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IF_ACMPEQ", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IF_ACMPEQ", "(III)V"); break; case IF_ACMPNE: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IF_ACMPNE", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IF_ACMPNE", "(III)V"); break; case GOTO: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "GOTO", "(III)V", false); mv.visitJumpInsn(opcode, label); break; case JSR: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "JSR", "(III)V", false); mv.visitJumpInsn(opcode, label); break; case IFNULL: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IFNULL", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IFNULL", "(III)V"); break; case IFNONNULL: - mv.visitMethodInsn(INVOKESTATIC, Config.instance.analysisClass, "IFNONNULL", "(III)V", false); - mv.visitJumpInsn(opcode, label); - addSpecialInsn(mv, 1); // for true path + addConditionalJumpInstrumentation(opcode, label, "IFNONNULL", "(III)V"); break; default: throw new RuntimeException("Unknown jump opcode " + opcode); diff --git a/instrument/src/main/java/janala/instrument/SnoopInstructionTransformer.java b/instrument/src/main/java/janala/instrument/SnoopInstructionTransformer.java index eac13e25e..cb6858d32 100644 --- a/instrument/src/main/java/janala/instrument/SnoopInstructionTransformer.java +++ b/instrument/src/main/java/janala/instrument/SnoopInstructionTransformer.java @@ -129,6 +129,7 @@ synchronized public byte[] transform(ClassLoader loader, String cname, Class try { cr.accept(cv, 0); } catch (Throwable e) { + System.err.println("Error instrumenting class " + cname); e.printStackTrace(); return null; }