Skip to content

Agent Instrumentation

Robert Ferris edited this page Apr 18, 2014 · 6 revisions

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.

Data Collection

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

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

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).

Method Exits

Execution may leave a method in one of two ways: normal return, and throwing an exception.

Normal Method Return

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 Exception

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.

Example

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.

Clone this wiki locally