This is a skeleton sample of a tracing agent which will work with or without OSGi. The only dependency is on javassist library. With OSGi, it is necessary to set the correct properties so that the tracing classes can be found in the classpath.
package dakaraphi.tracing;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
/**
* TracingAgent can only be launched within a jar file.
* You must specify the following JVM property at launch to indicate location of the agent jar file
* -javaagent: TracingAgent.jar
*
* If running inside an OSGI application. You must make this class and its dependencies global to all OSGI modules
* org.osgi.framework.bootdelegation = dakaraphi.tracing.*,javassist.*
*
* These classes must also be loaded by the JVM boot class loader so that they can be resolved from anywhere.
* This is done by setting the JAR's manifest to have this entry.
* Boot-Class-Path: TracingAgent.jar javassist.jar
*
*
*/
public class TracingAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
System.out.println("Starting TracingAgent");
TracingTransformer transformer = new TracingTransformer(instrumentation, new ClassMethodSelector(), new MethodRewriter());
}
}
The TracingTransformer is responsible for providing the rewritten bytecodes. It is expected that you would provide your own implementations of ClassMethodSelector and MethodRewriter.
package dakaraphi.tracing;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import javassist.ByteArrayClassPath;
import javassist.ClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
public class TracingTransformer implements ClassFileTransformer {
private static Instrumentation instrumentation;
private final ClassMethodSelector classMethodSelector;
private final MethodRewriter methodRewriter;
public TracingTransformer(final Instrumentation instrumentation, ClassMethodSelector classMethodSelector, MethodRewriter methodRewriter) {
this.classMethodSelector = classMethodSelector;
this.methodRewriter = methodRewriter;
instrumentation.addTransformer(this, true);
TracingTransformer.instrumentation = instrumentation;
System.out.println("TracingTransformer active");
}
public byte[] transform(final ClassLoader loader, final String className, final Class classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;
final String classNameDotted = className.replaceAll("/", ".");
if (classMethodSelector.shouldTransformClass(classNameDotted)) {
try {
final ClassPool classpool = ClassPool.getDefault();
final ClassPath loaderClassPath = new LoaderClassPath(loader);
final ClassPath byteArrayClassPath = new ByteArrayClassPath(classNameDotted, byteCode);
// We add the loaderClassPath so that the classpool can find the dependencies needed when it needs to recompile the class
classpool.appendClassPath(loaderClassPath);
// This class has not yet actually been loaded by any classloader, so we must add the class directly so it can be found by the classpool.
classpool.insertClassPath(byteArrayClassPath);
final CtClass editableClass = classpool.get(classNameDotted);
final CtMethod declaredMethods[] = editableClass.getDeclaredMethods();
for (final CtMethod editableMethod : declaredMethods) {
if (classMethodSelector.shouldTransform(classNameDotted, editableMethod.getName())) {
methodRewriter.editMethod(editableMethod, classMethodSelector.findMatchingDefinition(classNameDotted, editableMethod.getName()));
}
}
byteCode = editableClass.toBytecode();
editableClass.detach();
// These appear to only be needed during rewriting
// If we don't remove, the list just keeps growing as we rewrite more classes
// or transform the same class again
classpool.removeClassPath(loaderClassPath);
classpool.removeClassPath(byteArrayClassPath);
System.out.println("Transformed " + classNameDotted);
} catch (Exception ex) {
System.err.println("Unable to transform: " + classNameDotted);
ex.printStackTrace();
}
}
return byteCode;
}
}