0

Is it possible to dynamically instrument Java bytecode without a Java agent? I have instrumented bytecode using a Java agent before, doing something akin to this:

ClassFileTransformer myTransformer = new Transformer();
instrument.addTransformer(myTransformer, true);
instrument.retransformClasses(classInstance);
instrument.removeTransformer(myTransformer);

But is this possible without the use of a Java agent? What I would like to do is call a method which will do my instrumentation at any given time after the JVM is running, just without the use of an agent.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 2
    Load time transformation could be done with a custom class loader, for classes loaded through this loader. Redefinition or retransformation is not possible. You could start a new agent later-on if the JVM supports it, like at the end of [this answer](https://stackoverflow.com/a/19912148/2711488), however, recent versions of the HotSpot JVM require enabling self-attach at launch time. You could circumvent this by starting another process that does the attach, but you see, things get more and more complicated. – Holger Feb 15 '21 at 17:22
  • @Holger Would I be able to attach to a different JVM and transfer the instance of `Instrumentation` from that one to the one where I would like to retransform? I just can't attach to the JVM where I would like to instrument directly. – Jerry Jone Feb 15 '21 at 18:31
  • @Holger, why would redefinition or retransformation not be possible, provided I manage to get a reference to an `Instrumentation` engine? I am doing that all the time, usually simplifying the matter using `net.bytebuddy:byte-buddy-agent` (which notably does not contain any Byte Buddy functionality, just helps in getting instrumentation started) in order to save manual work. No need to start another process and attach remotely. – kriegaex Feb 16 '21 at 05:58
  • 1
    @kriegaex the only way to get hands on an `Instrumentation` instance, is by using a Java Agent. If `byte-buddy-agent` is not an agent, what else is it? Just because you ignore what this library is actually doing, those steps don’t become unnecessary. [From the author of ByteBuddy](https://stackoverflow.com/questions/56787777/#comment100160373_56787777): “*You need to setup the property on VM startup, not during runtime, otherwise it is ignored. Otherwise, have a look at byte-buddy-agent which can work around the restriction.*” So it *does* contain the work-around. – Holger Feb 16 '21 at 07:36
  • 1
    @JerryJone You don’t need to transfer anything. The helper process is not doing much here. The Agent still is started within your own JVM, that’s what starting an Agent in a JVM implies. All the sub-process is doing, is attaching to your original JVM and telling it to start the Agent. From that point on, everything else will work the same way as with self-attaching. The Agent stub can pass the `Instrumentation` reference to your application and you can use it there. But as hinted by kriegaex, the minimized byte-buddy-agent can do that for you; you don’t need to implement it yourself. – Holger Feb 16 '21 at 07:45
  • @Holger, you are of course right technically. My own comment was unclear. I meant to say that you can pull off to get an `Instrumentation` instance without starting the target JVM with `-javaagent:` and - if attaching remotely from another JVM - you can even go without `-Djdk.attach.allowAttachSelf=true`. The `byte-buddy-agent`, despite being able to run as a Java agent itself, can also just be used as a normal class path dependency and be started in non-agent mode at first. It does all the dirty work for you, including the remote-attach option as a last resort if self-attach is unavailable. – kriegaex Feb 17 '21 at 04:24

1 Answers1

5

The only ways to perform byte code transformation without an instance of an Instrumentation implementation would be

  • Custom class loaders which can change the bytes before calling defineClass (which is limited to classes loaded through that loader)

  • Calling MethodHandles.Lookup.defineClass with modified bytes even before the class has been loaded, which works on the widespread JVMs with lazy loading, but is limited to your own module or modules opened to your module

Neither approach can change already loaded classes. That requires the Instrumentation reference and the only place where a JVM ever hands out such reference, are the initialization methods of Java Agents. So to use it, a Java Agent is unavoidable, even if it may be just a stub storing the reference, for use by your application code.

Note that starting with Java 9, there is the Launcher-Agent-Class manifest attribute for jar files that can specify the class of a Java Agent to start before the class specified with the Main-Class is launched. That way, you can easily have your Agent collaborating with your application code in your JVM, without the need for any additional command line options. The Agent can be as simple as having an agentmain method in your main class storing the Instrumentation reference in a static variable.

See the java.lang.instrument package documentation

Getting hands on an Instrumentation instance when the JVM has not been started with Agents is trickier. It must support launching Agents after startup in general, e.g. via the Attach API. This answer demonstrates at its end such a self-attach to get hands on the Instrumentation. When you have the necessary manifest attribute in your application jar file, you could even use that as agent jar and omit the creation of a temporary stub file.

However, recent JVMs forbid self-attaching unless -Djdk.attach.allowAttachSelf=true has been specified at startup, but I suppose, taking additional steps at startup time, is precisely what you don’t want to do. One way to circumvent this, is to use another process. All this process has to to, is to attach to your original process and tell the JVM to start the Agent. Then, it may already terminate and everything else works the same way as before the introduction of this restriction.

As mentioned in this comment, Byte-Buddy has already implemented those necessary steps and the stripped-down Byte-Buddy-Agent contains that logic only, so you can use it to build your own logic atop it.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • This is a great explanation, comprehensive and yet concise. It would be worth its own blog post. Comments with length limitations, which more often than not are ambiguous because the author (e.g. myself, see above) tries to fit it into the length limit so as to avoid writing multiple comments, are just inadequate transport vehicles for complex information. – kriegaex Feb 17 '21 at 04:31