Reputation: 61
I have been playing recently with java instrumentation API and a byte buddy. My goal is to change the behavior of an already loaded class. I was able to change the existing method but I`ve failed with adding a completely new one.
First approach:
public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println(("[Agent] In agentmain/premain method"));
Class<?> clazz = Class.forName("com.example.instrumentation.agent.AppService");
inst.addTransformer(new AppServiceTransformer(), true);
inst.retransformClasses(clazz);
}
public class AppServiceTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = null;
System.out.println("Transformation");
try {
byteCode = new ByteBuddy()
.redefine(classBeingRedefined)
// .defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
// .intercept(FixedValue.value("This is a message from the ByteBuddy hacker !!!"))
.method(named("getAnswer"))
.intercept(FixedValue.value("Service has been hacked :)"))
.make()
.getBytes();
} catch (Throwable e) {
System.err.println(e);
System.err.println("Failed to transform");
}
return byteCode;
}
}
Above code works, when I attach this agent to an already running VM it alters the behavior of the specified method. However when I uncomment the code responsible for defining a new method what I get is a
Exception in thread "main" com.sun.tools.attach.AgentInitializationException: Agent JAR loaded but agent failed to initialize
I tried running above example as a premain agent loaded at application start-up. For this case altering the behaviour of a the method works but adding a new one throws
Failed to transform
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:513)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:525)
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
Second approach:
public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println(("[Agent] In agentmain/premain method"));
new AgentBuilder.Default()
.type(named("com.jarek.example.instrumentation.agent.AppService"))
.transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
System.out.println("Entered transform");
return builder.method(named("getAnswer"))
.intercept(FixedValue.value("Service has been hacked :)"))
.defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
.intercept(FixedValue.value("This is experimental feature"));
}
})
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.installOn(inst);
}
I can see in the console that the agent have entered the transform method, however the new method isn`t added to the class and the behaviour of the existing one is not altered. Using this solution as a premain agent works perfectly in both cases.
Third approach:
public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println(("[Agent] In agentmain/premain method"));
Class<?> clazz = Class.forName("com.example.instrumentation.agent.AppService");
ByteBuddyAgent.install();
new ByteBuddy()
.redefine(clazz)
// .defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
// .intercept(FixedValue.value("This is a message from the ByteBuddy hacker !!!"))
.method(named("getAnswer"))
.intercept(FixedValue.value("Service has been hacked :)"))
.make()
.load(clazz.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}
This case works only for the premain agent for altering an existing method. The attempt to add a new method throws
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
When attaching this as a agent to already running application nothing happens.
Upvotes: 0
Views: 1686
Reputation: 44042
With an AgentBuilder
, you should register a Listener
to see if errors happen during retransformation. You probably should set .disableClassFormatChanges()
as the average JVM does not support adding methods or fields to a class that already is defined.
Adding a field or method is impossible as it is today, only the code evolution VM supports it as of today and it is doubtful if this feature ever makes it to OpenJDK.
Upvotes: 2
Reputation: 219
Since your requirement is to build a framework using which you can collect application metrics. Firstly, there are tools like VisualVM which helps you get metrics from a running java application and see the insights. This will be completely external to your application and won't require any code changes.
If you want a greater control over the metrics, you may onboard Spring Boot Admin and this will do it realtime for you without any code changes. There are plethora of features present in Spring Boot Admin.
Upvotes: -1