Reputation: 8837
I'm using a Java Agent and Javassist to add some logging to some JDK classes. Essentially when the system loads some TLS classes, Javassist will add some additional bytecode to them to help me debug some connection problems.
Here's the problem, given this class is included in the agent jar:
package com.something.myagent;
public class MyAgentPrinter {
public static final void sayHello() {
System.out.println("Hello!");
}
}
In my agent's transform method, let's say I tried to invoke that class using javassist:
// this is only called for sun.security.ssl.Handshaker
ClassPool cp = getClassPool(classfileBuffer, className);
CtClass cc = cp.get(className);
CtMethod declaredMethod = cc.getDeclaredMethod("calculateKeys");
declaredMethod.insertAfter("com.something.myagent.MyAgentPrinter.sayHello();");
cc.freeze();
return cc.toBytecode();
You think that would work, but instead I get this:
java.lang.NoClassDefFoundError: com/something/myagent/MyAgentPrinter
at sun.security.ssl.Handshaker.printLogLine(Handshaker.java)
at sun.security.ssl.Handshaker.calculateKeys(Handshaker.java:1160)
at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:292)
Is there any way to add that class [MyAgentPrinter
] to the application's class path?
Upvotes: 6
Views: 3750
Reputation: 298233
Your Agent’s jar file is already added to the class path, as specified by the java.lang.instrument
package documentation:
The agent class will be loaded by the system class loader (see
ClassLoader.getSystemClassLoader
). This is the class loader which typically loads the class containing the applicationmain
method. Thepremain
methods will be run under the same security and classloader rules as the applicationmain
method.
This is the reason why Javassist can find the Agent’s classes when you are transforming the byte code.
The problem seems to be that you are transforming a sun.**
class which is likely loaded by the bootstrap loader or extension loader. The standard class loading delegation is
application loader → extension loader → bootstrap loader
, so classes available to the application loader are not available to classes loaded by the extension or bootstrap loader.
So, to make them available to all classes, you have to add the Agent’s classes to the bootstrap loader:
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) throws IOException {
JarURLConnection connection = (JarURLConnection)
MyAgent.class.getResource("MyAgent.class").openConnection();
inst.appendToBootstrapClassLoaderSearch(connection.getJarFile());
// proceed
}
}
It’s critical to do this before any other action, i.e. before the classes you want to make available to instrumented code have been loaded. This implies that the Agent class itself, i.e. the class containing the premain
method can not get accessed by the instrumented code. The Agent class also shouldn’t have direct references to MyAgentPrinter
to avoid unintended early loading.
A more reliable way is to add a Boot-Class-Path
entry to the Agent jar’s manifest, see the “Manifest Attributes” section of the package documentation, so that the entry gets added before the Agent starts. But then, the name of the jar file must not change afterwards.
Upvotes: 8