Java - Inject java agent in to running jvm - java

Basically, I am trying to write something that lists every class loaded by the JVM. What I wrote works, but it only works for the jvm it is running on.
I crafted a java agent to dynamically inject into another JVM, but then realized I don't actually know how to inject it.
How do I actually send this agent into another JVM?
Is it possible?

Dynamic agents need to declare an agentmain(String, Instrumentation) method which is executed upon attachment within the target VM. You can use the tools.jar dependency which is (until Java 9) only included in a JDK but not a JRE. You can however bundle your agent program with a JDK and attach to JVMs from there.
The biggest pitfall is that the API differs for different VMs; you can however use a library like byte-buddy-agent which contains different implementations for different VMs. An attachment can be done using:
ByteBuddyAgent.attach("my.jar", "my-pid");
This attaches the agent contained in my.jar onto the Java process with id my-id.

Agents can be injected with HotSpot Attach API.
Run the following snippet with $JAVA_HOME/lib/tools.jar on the class path.
VirtualMachine vm = VirtualMachine.attach(PID);
try {
vm.loadAgent(agentJar);
} finally {
vm.detach();
}
Alternatively you may do this with my command-line jattach utility:
$ jattach PID load instrument false /path/to/agent.jar
Note that in order to support dynamic attach, your Java agent should have agentmain method and Agent-Class property in MANIFEST.MF.

As far as I understand from the comment, you are interested in something that can inspect remote JVM from within another Java process. If it is the case, then you need a Serviceability Agent rather than Java Agent.
Serviceability Agent API allows you to attach to another JVM process, to read its memory, to reconstruct VM structures and to inspect remote objects in reflection-like manner.
Here is a sample tool to list all classes loaded by a remote JVM:
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.tools.Tool;
public class ListRemoteClasses extends Tool {
public static void main(String[] args) {
new ListRemoteClasses().execute(args);
}
#Override
public void run() {
VM.getVM().getSystemDictionary().classesDo(klass -> {
String className = klass.getName().asString().replace('/', '.');
System.out.println(className);
});
}
}
How to run it:
java -cp $JAVA_HOME/lib/sa-jdi.jar:. ListRemoteClasses PID

It's hard to provide assistance without looking at the content that you've written but this is just to notify that there is a class named as Instrumentation interface (public interface Instrumentation) from java.lang.instrument package that provides services needed to instrument Java programming language code.
One such method provided by this class is getInitiatedClasses which returns an array containing all the classes that are loaded.
Look at the documentation here
getInitiatedClasses
Class[] getInitiatedClasses(ClassLoader loader)
Returns an array of all classes for which loader is an initiating loader. If the
supplied loader is null, classes initiated by the bootstrap class
loader are returned.
Parameters: loader - the loader whose initiated class list will be returned
Returns: an array containing all the classes for which loader is an initiating loader, zero-length if there are none

Related

NoClassDefFoundError after instrumenting code

I'm attaching my Java agent dynamically to a java process which instruments the code. Basically it adds a static call to every start of method:
//method start
AgentClass.staticMethod();
//method body
AgentClass lies in the agent's .jar. But after instrumentation, the process starts executing the new code and it throws a NoClassDefFoundError, it cannot find AgentClass.
I tried to istrument the classes in a way to include a try-catch block and load AgentClass with forName like this:
try {
AgentClass.staticMethod();
} catch(NoClassDefFoundError e) {
Class.forName("AgentClass");
}
But then I got several errors related to the recalculation of stack frames like:
Caused by: java.lang.VerifyError: Inconsistent stackmap frames at branch target 20
I solved this by using visitMaxs() (I am using ASM library).Then I got this: StackMapTable error: bad offset.
This was solved by using GOTO instead of RETURN but then I got: ClassFormatError: Illegal local variable table in method.
Is there any easier way to solve my initial NoClassDefFoundError error?
UPDATE: My agent classes are loaded with the Application Classloader(sun.misc.Launcher$AppClassLoader), and the process which I wanted to instrument loads classes with a custom URL classloader.
UPDATE2:
This is what I wanted to transform into bytecode:
try {
AgentClass agent = AgentClass.staticMethod();
} catch (Throwable e) {
try {
Class.forName("AgentClass");
} catch (ClassNotFoundException ex) {
}
}
My MethodVisitor(I am not very good at bytecode, so the bytecode was generated automatically by ASM, using a TraceClassVisitor.):
protected MethodVisitor createVisitor(MethodVisitor mv,final String name,final String desc,int access,String signature,String[]exceptions){
int variablesCount = (8 & access) != 0 ? 0 : 1;
Type[]args=Type.getArgumentTypes(desc);
for(int i=0;i<args.length; ++i){
Type arg=args[i];
variablesCount+=arg.getSize();
}
final int varCount=variablesCount;
return new MethodVisitor(458752,mv){
public void visitCode(){
Label label0=new Label();
Label label1=new Label();
Label label2=new Label();
this.mv.visitTryCatchBlock(label0,label1,label2,"java/lang/Throwable");
Label label3=new Label();
Label label4=new Label();
Label label5=new Label();
this.mv.visitTryCatchBlock(label3,label4,label5,"java/lang/ClassNotFoundException");
this.mv.visitLabel(label0);
this.mv.visitLineNumber(42,label0);
this.mv.visitMethodInsn(Opcodes.INVOKESTATIC,"AgentClass","staticMethod","()LAgentClass;",false);
this.mv.visitVarInsn(Opcodes.ASTORE,varCount);
this.mv.visitLabel(label1);
this.mv.visitLineNumber(48,label1);
Label label6=new Label();
this.mv.visitJumpInsn(Opcodes.GOTO,label6);
this.mv.visitLabel(label2);
this.mv.visitLineNumber(43,label2);
this.mv.visitFrame(Opcodes.F_SAME1,0,null,1,new Object[]{"java/lang/Throwable"});
this.mv.visitVarInsn(Opcodes.ASTORE,0);
this.mv.visitLabel(label3);
this.mv.visitLineNumber(45,label3);
this.mv.visitLdcInsn("AgentClass");
this.mv.visitMethodInsn(Opcodes.INVOKESTATIC,"java/lang/Class","forName","(Ljava/lang/String;)Ljava/lang/Class;",false);
this.mv.visitInsn(Opcodes.POP);
this.mv.visitLabel(label4);
this.mv.visitLineNumber(47,label4);
this.mv.visitJumpInsn(Opcodes.GOTO,label6);
this.mv.visitLabel(label5);
this.mv.visitLineNumber(46,label5);
this.mv.visitFrame(Opcodes.F_FULL,1,new Object[]{"java/lang/Throwable"},1,new Object[]{"java/lang/ClassNotFoundException"});
this.mv.visitVarInsn(Opcodes.ASTORE,1);
this.mv.visitLabel(label6);
this.mv.visitLineNumber(49,label6);
this.mv.visitFrame(Opcodes.F_CHOP,1,null,0,null);
this.mv.visitInsn(Opcodes.RETURN);
this.mv.visitLocalVariable("e","Ljava/lang/Throwable;",null,label3,label6,0);
this.mv.visitMaxs(1, 2);
super.visitCode();
}
...
}
}
UPDATE 3
This is how I attach my agent during runtime:
final VirtualMachine attachedVm = VirtualMachine.attach(String.valueOf(processID));
attachedVm.loadAgent(pathOfAgent, argStr);
attachedVm.detach();
For now my guess is that your class loader hierarchy is something like:
boot class loader
platform class loader
system/application class loader
custom URL class loader
Or maybe:
boot class loader
platform class loader
system/application class loader
custom URL class loader
I.e. the application class loader and the custom URL class loader are siblings or in some other way in different parts of the class loader hierarchy, i.e. the classes loaded in one of them are unknown to the other one.
The way to solve this would be to find a common ancestor and make sure the classes needed for your instrumentation scheme are loaded there. I usually use the bootstrap class loader. Before I explain to you how to programmatically add classes to the bootstrap class loader, please try adding your agent JAR to the bootstrap class path manually on the Java command line via -Xbootclasspath/a:/path/to/your/agent.jar and see if the custom URL class loader then finds the class. I would be very surprised if that would not work. Then please report back and we can continue.
Please also explain how you attach the instrumentation agent:
via -javaagent:/path/to/your/agent.jar or
via hot-attachment during runtime (if so, please show the code)
Update after some clarifying OP comments:
It is possible to add a JAR (not single classes) to the bootstrap class path by calling method Instrumentation.appendToBootstrapClassLoaderSearch(JarFile). In your agent's premain or (for hot-attachment) agentmain methods the JVM passes you an Instrumentation instance you can use for that purpose.
Caveat: You need to add the JAR before any of the classes you need on the bootstrap classpath have been imported or used by other, already loaded classes (including the agent class itself). So if in your case the AgentClass method called by the other class in the sibling class loader happens to reside inside the same class housing the premain and agentmain methods, you want to factor that method (and all others that might be called from outside) into another utility class. Also, do not directly refer to that class from the agent main class, rather first make the agent add its own JAR to the boot class path and then call any methods in there via reflection rather than directly from the agent main class. After the agent main class has done its job, other classes can refer to the classes that are now on the bootstrap class path directly, the problem is solved.
One problem remains, though: How does the agent find out the JAR path to add to the bootstrap class path? That is up to you. You can set a system property on the command line, read the path from a file, hard-code, hand it over as an agent configuration string passed to premain/agentmain via attachedVm.loadAgent(agentPath, configString) (in this case configString containing the agent path again) or whatever. Alternatively, create an inner JAR as a resource inside the main agent JAR, containing the classes to be put on the bootstrap class loader. The agent can load the resource, save it into a temporary file and then add the temp-file path to the bootstrap class path. This is a bit complicated, but clean and thus quite popular among agent developers. Sometimes this scheme is referred to as a "trampoline agent" approach.

JVM locks JAR files on startup

I've implemented a custom ClassLoader which is set via system property -Djava.system.class.loader=com.MyClassLoader. The CL contains a static initializer that invokes some code (before any class is loaded) manipulating the byte code of a class file within a jar (maven dependency) using the javassist library. This works fine, except that i cannot replace the old jar with the new one since the JVM is locking the file and only releases it when it terminates. Why is that and how can I enforce the JVM to release the lock?
Here is a little code snippet:
public class CustomClassLoader extends ClassLoader {
static {
...
modifyJar();
}
private static void modifyJar(){
URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
URL[] urls = urlClassLoader.getURLs();
for(URL url : urls) {
//find matching jar and modify byte code
}
replaceJarFile(metaData);
}
private static void replaceJarFile(JarMetaData jmd){
//add modified class to new jar file
JarFile jar = new JarFile(jmd.getJarFile());
...
//this method call returns false, jar is locked by another process (the JVM)
if(oldJarFile.delete()){
...
}
}
}
OS: Windows 10
JDK version: 1.8.0_131
It has never been specified that changing the jars in use by a JVM should be possible. And the JVM is locking the jars for quiet a long time now. Changing the classes within a jar in use would also bear the semantic problem of how to handle modifications to already loaded classes or even an ongoing class loading overlapping with a write.
Since modifying the jar would be a permanent change, the most reasonable approach for that would be doing it before starting the JVM, e.g. in a different JVM.
But if you want to change class definitions on-the-fly, you should write a Java Agent. For those JVMs supporting Java Agents, the Instrumentation API offers everything needed, e.g. transforming classes at load time or even redefining already loaded classes.
It also offers a standard way of adding jar files to the bootstrap or system class path, whereas assuming that the application class loader is a subclass of URLClassLoader will start to fail with Java 9.

Registering multiple .dll libraries into a single java class using JNA

First of all some context, to thoroughly explain the methods I've already tried:
I'm working in a java-based programming platform on windows which provides access to custom java functions with several other extensions. Within the source code of this modelling platform, there is a class "CVODE" which grants access to native library "cvode" to import the functionality of a C++ library CVODE.
//imports
public class CVODE {
static {
Native.register("cvode");
}
public static native int ... //methods
}
I created shared libraries from the CVODE library, which resulted in 2 files: sundials_cvode.dll and sundials_nvecserial.dll.
Adding the first library to my java path obviously resulted in
Unexpected Exception UnsatisfiedLinkError: Unable to load library 'cvode': The specified module could not be found.
as the names were not compatible. Therefore I changed the name of sundials_cvode.dll to cvode.dll and retried. Resulting in an error indicating that not all methods are present in the library sundials_cvode.dll:
Unexpected Exception UnsatisfiedLinkError: Error looking up function 'N_VDestroy_Serial': The specified procedure could not be found.
This convinces me that the library is being found and loaded correctly, but not all methods are available. Examining the dll's in question led me to the conclusion that the CVODE class requires functions from both the sundials_cvode.dll and sundials_nvecserial.dll libraries. Therefore I tried changing the platform source-code to
public class CVODE {
static {
Native.register("sundials_cvode");
Native.register("sundials_nvecserial");
}
public static native int ... //methods
}
which still results in
Unexpected Exception UnsatisfiedLinkError: Error looking up function 'N_VNew_Serial': The specified procedure could not be found.
I have confirmed this method is present in both the class file and in the dll:
So I can only guess the error results from calling the Native.register() twice. resulting in the 2nd library not being loaded or an error down the way. I'd appreciate some insight in what I'm doing wrong or how I can gain a better overview of what's going wrong.
As far as I know, you can only load one dll per class, i.e. split the classes into two, each providing the methods the particular dll provides.
See also here: https://stackoverflow.com/a/32630857/1274747

Determining whether library class is used from GWT app or regular Java

I'm writing a couple of library classes that I am sharing between several projects. Some of these projects are plain-old Java and others are GWT applications. For some of these classes the exact implementation is different whether they need to run in GWT or in Java (Let's not get into exactly why, but just as one of many examples, Date.getMonth is deprecated in Java, but the Calendar replacement isn't available in GWT).
Is there a way to mark certain sections of code as pertaining to one or the other scenario?
I looked at using deferred binding and class-replacement in GWT, but that requires instantiation of classes using GWT.create() which isn't available for a plain-old Java app and will therefore lead to compile errors.
Found a solution that works beautifully: the <super-source> tag in my library's .gwt.xml file!
Basically, I have two versions of the following EnvironmentFlags class in my library. One in the actual library that is used by Java located in folder "lib":
my.library.EnvironmentFlags looks like this:
package my.library;
public class EnvironmentFlags {
public static final boolean IS_GWT = false;
public static final boolean IS_DEV_MODE = false;
}
And then a file in the folder "super/my/library" that looks like this:
package my.library;
import com.google.gwt.core.client.GWT;
public class EnvironmentFlags {
public static final boolean IS_GWT = true;
public static final boolean IS_DEV_MODE = !GWT.isProdMode();
}
Now the magic: The .gwt.xml file of my library looks like this:
<module>
<source path='lib' />
<super-source path='super' />
</module>
This leads to plain-old Java using the first version of the EnvironmentFlags class, which simply sets both flags to false, while the GWT compiler replaces the source of that class with the second version loaded from the super-source directory, which sets the GWT flag to true and the DEV_MODE flag to whatever it gets from GWT.
Now, in my code I can simply use the following:
if (EnvironmentFlags.IS_GWT) {
// Do GWT stuff
} else {
// Do plain-old Java stuff
}
and both, the Java and the GWT compiler should drop the respective unreachable/unneeded code from the compiled result, i.e. no run-time overhead needed.
PS: The IS_DEV_MODE flag doesn't actually have anything to do with my original question. I just included it as a freebie which allows me to have my code act differently (more verbose, for example) depending on whether I am testing or deploying my GWT app.
Sounds like you could use the static GWT.isClient() which returns true if your code is running in GWT environment (Dev or Production) or false elsewhere. You'll have to include gwt-user.jar in your server classpath. For example, running the following in a JVM:
import com.google.gwt.core.shared.GWT;
public class Main {
public static void main(String[] args) {
System.out.println(GWT.isClient() ? "Running client-side."
: "Running server-side.");
}
}
Will produce Running server-side. in your console.

UnsatisfiedLinkError java exception when the class with the native method is not in the default package

I am trying to open a URL with the default Windows browser, in Java. Unfortunately, I cannot use the Desktop class utilities since the code has to be compatible with 1.5.
As a solution, I am calling ShellExecute by using a native method:
public class ShellExec {
public native int execute(String document);
{
System.loadLibrary("HSWShellExec");
}
public static void main(String args[]) throws IOException {
new ShellExec().execute("http://www.google.com/");
}
}
I put the DLL file in the Eclipse project root which apparently is included in java.library.path .
Everything works just perfect if ShellExec is in the default package, but if I move it in any other package, the native call fails with:
Exception in thread "main" java.lang.UnsatisfiedLinkError: apackage.ShellExec.execute(Ljava/lang/String;)I
at apackage.ShellExec.execute(Native Method)
at apackage.ShellExec.main(ShellExec.java:13)
Anybody has any ideea why? I am using the DLL from http://www.heimetli.ch/shellexec.html
Thanks
..later edit:
Eventually this class, and others, will be utility classes in an Eclipse RCP application, and all the external DLLs will be placed in a common lib folder to which the java.library.path will point to. The DLLs are seen, but I get the same type of errors as the simple example from above.
pass the VM argument -Djava.library.path=<path-to-dll-folder> to your project launch configuration.
The block you are loading the library in is not static to the class, just defined as an anonymous block in an instance of ShellExec. Since you never create an instance of ShellExec, the anonymous block never gets called and the library never gets loaded.
Instead you should have
static {
System.loadLibrary("HSWShellExec");
}
I think that will solve your problem.

Categories