-
Notifications
You must be signed in to change notification settings - Fork 5
Agent Instrumentation
Bytefrog's Java agent instruments the code being traced, injecting function calls to trace method entries, method exits, thrown exceptions, and uncaught exceptions. We utilize ASM for facilitating the instrumentation process.
A public singleton Trace
(com.secdec.bytefrog.agent.trace.Trace
) exists. Method calls on this object are injected during the instrumentation process. Every trace method takes the method metadata (the fully qualified name of the method, the full JVM signature, and any access flags) as well as any specific data needed for the event.
Method entries are instrumented by inserting a call to Trace.methodEntry
at the beginning of every traced method. This records the method entry along with the method metadata.
Thrown exceptions are recorded by inserting a call to Trace.methodThrow
prior to any throw
instructions. This records the method metadata, the thrown exception, and the line number (if available).
Execution may leave a method in one of two ways: normal return, and throwing an exception.
Normal method returns are instrumented by inserting a call to Trace.methodExit
before any return
call (including one implicitly at the end of the method). This records the method exit along with the method metadata and the line number (if available).
Uncaught exceptions are detected as they leave a method. This is accomplished by wrapping the entire method body in a try
block. A generic catch
block is then inserted, calling Trace.methodBubble
and re-throwing the exception to allow it to continue up the execution stack. For uncaught exceptions, the exception and method metadata are recorded.
As an example, consider the following Java method:
public void foo() {
if (badCondition) throw new Exception("failed");
return 4;
}
After Bytefrog instrumentation, the method will effectively become:
public void foo() {
try {
com.secdec.bytefrog.agent.trace.Trace.methodEntry("com/foo/bar.foo;1;()V");
if (badCondition) {
Exception ex = new Exception("failed");
com.secdec.bytefrog.agent.trace.Trace.methodThrow(ex, "com/foo/bar.foo;1;()V", 2);
throw ex;
}
com.secdec.bytefrog.agent.trace.Trace.methodExit("com/foo/bar.foo;1;()V", 3);
return 4;
} catch (Throwable t) {
com.secdec.bytefrog.agent.trace.Trace.methodBubble(t, "com/foo/bar.foo;1;()V");
throw t;
}
}
This instrumentation happens dynamically as the class file is loaded by the JVM. As such, these changes exist only in memory, and only for as long as that JVM instances is executing. The original code remains unmodified.