I have been trying to use a Java agent to apply a bytecode transformation with ASM.
I implemented an Agent with the premain method adding a transformer to the Instrumentation.
I added the "Premain-Class" line in the .jar manifest
Premain-Class: <MyAgentPath>
Then I tried to run the application with the agent.
There I have a problem : my transformer modifies some method calls, so if not all involved classes are modified too, it cannot work. And there are some classes which are not modified, like "org.apache.commons.math3.util.FastMath".
Of course then, I got the error :
java.lang.NoSuchMethodError: org.apache.commons.math3.util.FastMath.floor<new_descriptor>
I checked a lot of posts saying it could be the bootstrap loader which does not know the path to this class so I tried to add it using different ways :
Adding the "Boot-Class-Path" line to the manifest :
Boot-Class-Path: <...>/commons-math3<...>.jar
Using the method "appendToBootstrapClassLoaderSearch(JarFile)"
inst.appendToBootstrapClassLoaderSearch("<...>/commons-math3<...>.jar");
Using the JVM argument "-Xbootclasspath/a:"
-Xbootclasspath/a:<...>/commons-math3<...>.jar
None of this changed anything.
I also used the Instrumentation class method getAllLoadedClasses() to see which ones were loaded, and all classes involved in the application process where loaded, including FastMath.
for(Class<?> clazz : MyAgent.getInstInstance().getAllLoadedClasses()){
buffWrite.write(clazz.getName());
As the class "FastMath" gave an error and as the Bootstrap Loader should have its path, I tried adding some method calls to methods from other classes in the same package. It appears the problem does not show for every class of the package.
For example: MathUtils is transformed and a call to the modified method checkFinite(D)V -> checkFinite<new_descriptor>.
So I guess the problem has nothing to do with the paths given to the bootstrap loader.
If you have some ideas about what is happening, I would be glad to hear about it!
A NoSuchMethodError is most likely not caused by not adding something to the bootstrap class loader. The only chance that this could be a problem would be if there were suddenly two such jars available where one was instrumented and the other was not.
If you call change a method checkFinite(D)V to become another method checkFinite<new_descriptor>, then you need to make sure that any class using FastMath.floor updates the descriptor to this method. This means that you need to walk through every method of every class looking for visitMethodIns calls of ASM. It seems like you are missing some. Since you are changing the layout of the FastMath class, you must instrument it while loading it for the first time and you cannot redefine it.
The Java internal classes do not know of FastMath as this is a third-party dependency. Therefore, it should be possible to instrument any call from your agent. It seems to me like you are loading FastMath prematurely.
Related
I'm using a Java Agent (Agent.class) to transform a method in a program (Program.class) in a way that includes a call to the Agent class.
public Program.getMultiplier()F:
ALOAD 1
ALOAD 2
FDIV
+ INVOKESTATIC Agent.getCustomMultiplier(F)F
FRETURN
I've inspected the class loaders and their parents of both Agent and Program classes, and their hierarchy looks like this:
Agent.class: AppClassLoader <- PlatformClassLoader <- null
Program.class: URLClassLoader <- PlatformClassLoader <- null
When the Program executes the added INVOKESTATIC instruction, it throws a ClassNotFoundException -- it cannot find the Agent class as it was loaded by a different class loader.
As a temporary solution, I've tried forcing AppClassLoader to become a parent of URLClassLoader with reflection, which works in older Java versions but has been removed since Java 12.
Is there a more reliable way to make sure my Agent class is visible from any class loader?
You can add classes to the bootstrap class loader using appendToBootstrapClassLoaderSearch. This makes the classes of the specified jar file available to all classes whose defining class loader follows the standard delegation pattern.
But this requires the classes to be packaged in a jar file. When you specify the Agent’s own jar file, you have to be aware that classes loaded through the bootstrap loader are distinct from the classes loaded through the app loader, even when they originate from the same jar file. Further, the classes loaded by the bootstrap loader must not have dependencies to classes loaded by by other class loaders.
If your getCustomMultiplier method is supposed to interact with the running Agent, you have to separate the agent and the class containing this method.
Have your Agent listen to the creation of new ClassLoaders and then attach instances of them to the new ClassLoaders.
This way you preserve your "Agent listens to ClassLoader" interface, even if it now extends beyond the one platform class loader you expected the Agent to listen to.
You may be able to do something specific that works for URLClassLoader, but not all classes are loaded by an instance of URLClassLoader. Any OSGi project won't, most web servers also use their own classloaders in order to support hot reload, etc.
As far as I know there's no way to just casually update some 'global parent of all classloaders' or inject one; there's no such parent, and even if there was, a classloader is free to ignore its parent entirely.
Therefore the general answer is: No, you can't do that.
But, let's get our hacking hats on!
You're an agent already. One of the things you get to do as agent is to 'witness' classes as they are being loaded. Just invoke .addTransformer on the instance of Instrumentation you get in your agentmain and register one.
When you notice the Program class being loaded, do the following:
Take the bytecode and toss it through ASM, BCEL, Bytecode Buddy, or any other java 'class file reader/transformer' framework.
Also open up a class from within your agent's code (I wouldn't use Agent itself, I'd make a class called ProgramAddonMethods or whatnot as a container - everything inside is for the program to use / for your agent to 'inject' into that program.
Add every static member in ProgramAddonMethods directly to Program. As you do so, modify the typename on all accesses (both INVOKESTATIC and the read/write field opcodes) where the etypename is ProgramAddonMethods and make it the fully qualified name of the targeted class instead.
inject the INVOKESTATIC as you already do, but, rewrite it so that it's going to its own class, as you just copied all the static methods and fields over there.
Then return the bytecode of that modified class from your transformer.
This 100% guarantees you cannot possibly run into any module or classpath boundary issues and it will work with any classloader abstraction, guaranteed, but there are some caveats:
Just don't attempt to futz with instance anything. Make it all static methods and fields. You can make fake instance fields using an IdentityHashMap if you must (e.g. a static IdentityHashMap<Foo, String> names; is effectively identical to adding private String name; to the Foo class.. except it's a bit slower of course; presumably as you're already in a mess o reflection that's acceptable here).
Your code has to be 'dependency free'. It cannot rely on anything else, no libraries other than java.*, not even a helper class. This idea quickly runs out of steam if the job you're injecting becomes complicated. If you must, make a classloader for your own agent jar using the appropriate 'thread-safely initialize it only once' guards, and have that load in a bundle that does have the benefit of allowing dependencies.
This is all highly complicated stuff but you appear to have already worked out how to inject INVOKESTATIC calls, so, I think you know how to do this.
This is precisely what lombok does to 'patch' some methods in eclipse to ensure that things like save actions, auto-formatting, and syntax highlighting don't break - lombok injects knowledge of generated notes where appropriate and does it in this exact manner because eclipse uses a classloader platform called Equinox which makes any other solution problematic. You can look at it for inspiration or guidelines, though it's not particularly well documented. You're looking in particular at:
The lombok.eclipse.agent package in the eclipseAgent source root.
The lombok.patcher project which is lombok's only actual dependency, in particular the lombok.patcher.PatchScript.transplantMethod method.
Note that the next method may also interest you: lombok.patcher's 'insert' doesn't move the method - it injects the body of the method directly in there (it 'inlines'). This requires some serious finagling of the stack and is only advised for extremely simple one-liner-esque methods, and probably is excessive and unneccessary firepower for this problem.
DISCLAIMER: I wrote most of that.
I am currently trying to edit a class file in runtime, Example:
Example.java with this code:
public static void execute(){
System.out.println("hello worl");
}
There is no easy way to edit the text in this example, Now i need code that edits the "hello worl" to "hello world" without having access to the Example.java and without restarting the program to edit byte code, Is this possible? I have searched many articles and have not found a defined answer.
This depends on how much access you have.
This simplest way is to alert that class before it loads and force JVM to load your version of it, but then it must be loaded by proper ClassLoader, by your nickname I can assume that you are trying to do some magic using Spigot minecraft engine? Then if you want to change class from other plugin all you need to do is to actually copy this class to your project and load this class in static block of your main class - just make sure that your plugin will load before that other one.
This will cause JVM to load this class before original one, and because of how spigot class loader works - it will be added to global map of plugin classes, so other classes with that name will not be loaded, that other plugin will use your class instead.
This is also possible in other places, not just spigot - but not in every application, as it must have similar class loading - with shared storage of classes for your plugin/jar and plugin/jar that you want to edit.
Other way is to use Javassist library to do something similar but in runtime:
ClassPool classPool = ClassPool.getDefault();
CtClass ctToEdit = classPool.getCtClass("my.class.to.Edit");
CtMethod execute = ctToEdit.getDeclaredMethod("execute");
execute.setBody("{System.out.println(\"hello world\");}");
ctToEdit.toClass(); // force load that class
Javassist will find .class file for that class and allow you to edit it, and then inject it to selected class loader (I think it is system one by default, you can use .toClass(ClassLoader) method too)
Most important part of this trick is that class can't be loaded before this code executes. This is why you need to provide class name manually, never do something like MyClass.class.getName(), this will break this trick.
Note that javassist java compiler is a bit tricky and limited, see their webpage to find more informations and useful tricks.
If class is already loaded... then you have last option.
You can do it using java agents via Instrumentation class - as it allows to reload classes in runtime (with few limitations, just like debugger, you can't change class schema, but you can change anything you want using ClassFileTransformer before class is loaded).
So you need to create special java agent that will edit this class before it loads, or after it load (but then with that few limitations) using https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses(java.lang.instrument.ClassDefinition...)
But normally to do so you need to add special flags or manifest entries to runnable .jar, if you are launching this code on JDK, then it is much easier - with few tricks you can create agent and attach it to your VM in runtime, there is a bit of code needed for that so I will just suggest to use byte-buddy-agent library, then you can get instrumentation with single line of code:
Instrumentation install = ByteBuddyAgent.install();
And you can redefine classes all you want, in your case I would also suggest to use Javassist library to edit code - as it is the easiest of available ones, but you can also use ByteBuddy or raw ASM, for javassist your code would look like this:
Instrumentation instrumentation = ByteBuddyAgent.install();
CtClass ctClass = ClassPool.getDefault().getCtClass(Main.class.getCanonicalName());
ctClass.defrost(); // as this class is already loaded, javassist tries to protect it.
CtMethod execute = ctClass.getDeclaredMethod("execute");
execute.setBody("{System.out.println(\"hello world\");}");
ClassDefinition classDefinition = new ClassDefinition(Main.class, ctClass.toBytecode());
instrumentation.redefineClasses(classDefinition);
If class is already loaded and you are on JRE and you can't change app start arguments for some reasons - so just any of that methods are not possible - write comment and describe way - I know some other magic, but it would take much more time to describe it, so I would need more information.
I have been using premain() with addTransformer(). Since, it gives javassist.ClassNotFound exceptions for certain classes when i run the agent with a server, i thought to try the agentMain() with redefineClasses(). I went through many links, but so far i am unable to find a piece of code that gives me clear idea on how to set up a simple java agent using these two methods. Some help would be really appreciated.
Can we use redefineClasses() with premain()? (When we use redefineClasses() do we still need the transform method?)
I am trying to instrument set of methods of set of classes, where i know the fully qualified name of those classes as com.test.Foo. I wanted to instrument them without going through the entire set of classes loaded onto JVM. I have been reading those documents back and forth, but still i am unable to get a clear idea on how to use that redefineClasses method?
You can call redefineClasses from anywhere, also from a premain method which is nothing but an extension to a normal Java program run by the same JVM process previous to a main method.
A trivial example for running a redefinition is:
instrumentation.redefineClasses(new ClassDefinition(Foo.class, new byte[] {...}));
This way, Foo is set to be represented by the byte array that must contain a valid class file for Foo where all signatures of fields and methods are the same as by the loaded Foo.class. You can use a tool like ASM for instrumenting the class.
If you really only want to instrument Foo, then this might just be the way to go instead of using a ClassFileTransformer.
I'm trying to debug an issue where Sun JVM is trying to load a class which is not used by the static method I call, and it is resulting in a NoClassDefFoundError. Details below:
Method A.x() calls B.getTZ_OFFSET() and it runs into NoClassDefFoundError for OraclePreparedStatement even before the control reaches getTZ_OFFSET. I had a look at the all the static variables and blocks in B and none of them refer to OraclePreparedStatement or anything even closely related to that. I'm assuming that just importing the class doesn't cause the class to be loaded.
How do I find out the dependency, based on which JVM is trying to load OraclePreparedStatement?
The switch -verbose:class only gives the list of classes that have successfully been loaded, not the ones the JVM is trying to load. Is there a way to get that info as well?
Any help will be highly appreciated.
I'm aware that importing ojdbc.jar causes the problem to vanish, but I'm more interested in the root cause, as to why it's trying to load it in the first place.
The class your JVM cannot find may be referenced in a number of places: in the argument list of the method you call, return type, class (static) members of the class you are using, class members of any super classes and static initializers. You have to check all these places to find the hidden reference. The NoClassDefFoundError may also be masking some other error you get, for example an exception object that gets created and references the non existent class.
One way to determine the order classes are loaded is to implement your own class loader. You only need to extend class ClassLoader and override method loadClass(String, boolean), whereby you could print to standard out the name of the class being requested and then delegate to super.loadClass(String, boolean).
I wanted to use, inside Google appengine, a small library; one of the methods of one of its classes (say MyClass.writeToFile()) uses java.io.FileOutputStream, which is among the blacklisted classes (well, not white-listed).
Does this imply that MyClass will fail at classloading time, or just when (if) I try to invoke the offending method? At what point is that checking ("FileOutputStream not allowed") done? When loading MyClass file and detecting that it "refers to/depends on" FileOutputStream (I dont know if this is declared in the MyClass bytecode)? Or when trying to load FileOutputStream, while invoking MyClass.writeToFile() for the first time?
Further, assuming that the method MyClass.writeToFile() is not essential for my library (but MyClass is), is there some workaround, or should one refactor the library (and build, say two different jars, one full fledged and other sandbox-friendly) ?
Do a test deploy. Deployment should fail if I remember it correctly. Classes that are used by a certain class is part of the byte code, and google is verifying it.
Edit: I'm contradicting myself :) This thread indicates that deployment only fail if the FileOutputStream class is loaded:
GoogleAppEngine : possible to disable FileUpload?
Edit2: And this indicates that it is the class loader that is checking / stopping loading of forbidden classes: The Sandbox
Classes are lazyload upon first reference. If your code never tries to use FileOutputStream then there should be no problem.