I was trying to modify the byte code of several classes whose packaging jar files are not in class path - they are loaded by a custom ClassLoader
during runtime given an URL. I tried to use a java agent
with ClassFileTransformer
hoping to intercept those classes but failed. The classloader is part of a legacy project so I cannot make changes to it directly.
The agent works fine on classes loaded by AppClassLoader 'locally' but just ignores those loaded by the custom classloader.
the CustomClassLoader:
public class CustomClassLoader extends URLClassLoader {
public CustomClassLoader(URL[] urls) {
super(urls, CustomClassLoader.class.getClassLoader());
}
// violates parent-delegation pattern
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
try {
clazz = findClass(name);
} catch (ClassNotFoundException e) {
}
if (clazz == null) {
clazz = getParent().loadClass(name);
}
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
}
}
the ClassFileTransformer used in my agent (with javassist):
public class MyTransformer implements ClassFileTransformer
{
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
{
byte[] byteCode = null;
if (className.replace("/", ".").equals("com.example.services.TargetService"))
{
ClassPool cp = ClassPool.getDefault();
CtClass cc;
try
{
cc = cp.get("com.example.services.TargetService");
CtMethod verifyMethod = cc.getDeclaredMethod("verify");
//invalidate the verification process of method : verify
verifyMethod.insertBefore("{return true;}");
byteCode = cc.toBytecode();
cc.detach();
return byteCode;
}
catch (Exception e)
{
e.printStackTrace();
}
}
return byteCode;
}
}
the Agent:
public class Agent
{
public static void premain(String agentArgs, Instrumentation inst)
{
inst.addTransformer(new MyTransformer());
}
}
I came up with a workaround by instrumenting the CustomClassLoader itself, invoking instrumentation.redifineClasses() but don't know how to pass the instrumentation instance into the CustomClassLoader instance; I'm new to instrumentation/class loading and still not quite clear with their mechanism. Any help? Thanks.
To make it simple:
I assume that your class is not properly instrumented because you are calling ClassPool.getDefault()
which does not include class files visible to your custom class loader but only to the system class loader. You never register the classfileBuffer
class file.
As an alternative, you can try out Byte Buddy which offers easier access to the instrumentation API:
new AgentBuilder.Default()
.type(named("com.example.services.TargetService"))
.transform((builder, type, loader) -> {
builder.method(named("verify")).intercept(FixedValue.of(true));
}).installOn(instrumentation);
The above agent can be invoked from the agentmain
or premain
method. You can also disable class file format changes and redefine existing classes in case that the class is already (potentially) loaded during attachment.