Reputation: 3343
I've got some ByteBuddy-based instrumentation that I want to provide both for embedded use and as an agent.
The code goes something like this:
public static void premain(String arguments, Instrumentation instrumentation) {
installedInPremain = true;
new AgentBuilder.Default()
.type(ElementMatchers.named("com.acme.FooBar"))
.transform((builder, typeDescription, classLoader, module) -> visit(builder))
.installOn(instrumentation);
}
public static void instrumentOnDemand() {
ByteBuddyAgent.install();
DynamicType.Builder<URLPropertyLoader> typeBuilder = new ByteBuddy().redefine(FooBar.class);
DynamicType.Builder<FooBar> visited = visit(typeBuilder);
visited.make().load(FooBar.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}
private static <T> DynamicType.Builder<T> visit(DynamicType.Builder<T> builder) {
return builder.visit(Advice.to(SnoopLoad.class).on(named("load").and(takesArguments(0))))
.visit(Advice.to(SnoopOpenStream.class).on(named("openStream")))
.visit(Advice.to(SnoopPut.class).on(named("put")));
}
Then somewhere else, somebody would do:
new FooBar().load("abc123")
And if I was running with instrumentOnDemand
, everything would be fine and dandy, but if I was running the agent with premain, I'd get:
Exception in thread "main" java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "com/acme/FooBar"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.acme.FooBarAnalyzer.load(FooBarAnalyzer.java:121)
at com.acme.FooBarAnalyzer.main(FooBarAnalyzer.java:107)
I would guess that because my advices reference FooBar
as @Advice.This
parameter, it gets loaded prematurely, but wasn't the whole point of having an agent to be able to redefine these?
Also, how come it worked in the on-demand case? I guess I need to tweak the agent builder, or am I missing a step?
Gah... the perils of copying tutorials without understanding... pointers to documentation are also more than welcome!
Upvotes: 0
Views: 787
Reputation: 44032
You can just use the AgentBuilder.Transformer.ForAdvice
for both the startup and the dynamic instrumentation. This is normally a better choice as this is also more robust against different class loader constellations. This way you do not need to duplicate your logic.
Upvotes: 1
Reputation: 3343
Ok, the solution was to use AgentBuilder.Transformer.ForAdvice()
, but that created ugly duplication between premain
and visit
.
public static void premain(String arguments, Instrumentation instrumentation) {
installedInPremain = true;
new AgentBuilder.Default()
.type(named("com.acme.FooBar"))
// .transform((builder, typeDescription, classLoader, module) -> visit(builder))
// CAN'T WE HAVE THIS SHARED???
.transform(advice("load", "SnoopLoad"))
.transform(advice("openStream", "SnoopOpenStream"))
.transform(advice("put", "SnoopPut"))
.installOn(instrumentation);
}
private static AgentBuilder.Transformer.ForAdvice advice(String name, String snoopLoad) {
return new AgentBuilder.Transformer.ForAdvice().advice(named(load), "com.acme.PropAnalyzerAgent$" + snoopLoad);
}
private static <T> DynamicType.Builder<T> visit(DynamicType.Builder<T> builder) {
// CAN'T WE HAVE THIS SHARED???
return builder.visit(Advice.to(SnoopLoad.class).on(named("load")))
.visit(Advice.to(SnoopOpenStream.class).on(named("openStream")))
.visit(Advice.to(SnoopPut.class).on(named("put")));
}
public static void instrumentOnDemand() {
DynamicType.Builder<FooBar> typeBuilder = new ByteBuddy().redefine(FooBar.class);
DynamicType.Builder<FooBar> visited = visit(typeBuilder);
visited.make().load(FooBar.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}
Nothing that can't be solved by a layer of indirection, but I'd like to think that it's already handled in nicer way somehow.
Upvotes: 0