java - Decorating ClassLoader methods using ByteBuddy
问题描述
I have a project which allows one to log classes as they are loaded by the JVM (https://github.com/jdeppe-pivotal/classload-tracer). The limitation with this is that a class has to be loaded successfully in order to be logged.
Now I'd like to try and expand this and instrument various ClassLoader
methods in order to log classes as they are attempted to by loaded (regardless of success). I'm hoping to use ByteBuddy for this. Unfortunately I'm not having much luck.
Here is the code for the agent:
public class ScratchAgent {
private static final PrintWriter out;
public static final ByteArrayOutputStream baos;
static {
baos = new ByteArrayOutputStream(10);
out = new PrintWriter(baos);
}
public static void premain(String arg, Instrumentation inst) throws Exception {
File temp = Files.createTempDirectory("tmp").toFile();
ClassInjector.UsingInstrumentation
.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, inst)
.inject(Collections.singletonMap(
new TypeDescription.ForLoadedType(MyInterceptor.class),
ClassFileLocator.ForClassLoader.read(MyInterceptor.class)));
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly())
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.ignore(ElementMatchers.nameStartsWith("net.bytebuddy."))
.enableBootstrapInjection(inst, temp)
.type(is(TestClass.class))
.or(ElementMatchers.isSubTypeOf(ClassLoader.class)
.or(ElementMatchers.nameContainsIgnoreCase("classloader"))
)
.transform((builder, type, classLoader, module) -> builder
.visit(Advice.to(MyInterceptor.class)
.on(hasMethodName("fooMethod")))
.visit(Advice.to(MyInterceptor.class)
.on(hasMethodName("loadClass"))))
.installOn(inst);
}
public static class MyInterceptor {
@Advice.OnMethodEnter
public static void decorate(
@Advice.Argument(0) String arg,
@Advice.This Object thisThis,
@Advice.Origin Method method,
@Advice.Origin Class<?> clazz) {
System.out.println("--->>> OK " + method.getName()
+ "(" + arg + ") " + thisThis);
out.println("--->>> OK " + arg);
out.flush();
}
}
}
And here is a test:
@Test
public void sanity() throws Exception {
ScratchAgent.premain(null, ByteBuddyAgent.install());
String result = new TestClass().fooMethod("world");
assertThat(result).isEqualTo("Hello world");
assertThat(ScratchAgent.baos.toString()).isEqualTo("--->>> OK world\n");
}
But this produces the following error:
[Byte Buddy] TRANSFORM io.pivotal.test.TestClass [sun.misc.Launcher$AppClassLoader@18b4aac2, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$2 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$3$1 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$3 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader$2 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$1 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.SystemClassLoaderAction [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.misc.Launcher$AppClassLoader$1 [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.misc.Launcher$ExtClassLoader$1 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$7 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader$ParallelLoaders [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader$NativeLibrary [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader$3 [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.misc.Launcher$ExtClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.misc.Launcher$AppClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.reflect.DelegatingClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.security.SecureClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader [null, null, loaded=true]
--->>> OK fooMethod(world) io.pivotal.test.TestClass@2ca26d77
--->>> OK loadClass(org.assertj.core.api.Assertions) sun.misc.Launcher$AppClassLoader@18b4aac2
--->>> OK loadClass(org.junit.runners.model.MultipleFailureException) sun.misc.Launcher$AppClassLoader@18b4aac2
--->>> OK loadClass(org.junit.runner.notification.RunNotifier$7) sun.misc.Launcher$AppClassLoader@18b4aac2
--->>> OK loadClass(net.bytebuddy.pool.TypePool$Default$WithLazyResolution$LazyResolution) sun.misc.Launcher$AppClassLoader@18b4aac2
[Byte Buddy] ERROR java.lang.Throwable$WrappedPrintStream [null, null, loaded=false]
[Byte Buddy] ERROR java.lang.Throwable$PrintStreamOrWriter [null, null, loaded=false]
[Byte Buddy] ERROR java.util.IdentityHashMap$KeySet [null, null, loaded=false]
Exception in thread "main" java.lang.NoClassDefFoundError: org/junit/runners/model/MultipleFailureException
at org.junit.internal.runners.model.EachTestNotifier.addFailure(EachTestNotifier.java:20)
at org.junit.runners.ParentRunner.run(ParentRunner.java:369)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
[Byte Buddy] ERROR java.lang.Shutdown [null, null, loaded=false]
java.lang.NoClassDefFoundError: net/bytebuddy/pool/TypePool$Default$WithLazyResolution$LazyResolution
at net.bytebuddy.pool.TypePool$Default$WithLazyResolution.doDescribe(TypePool.java:1319)
at net.bytebuddy.pool.TypePool$AbstractBase.describe(TypePool.java:408)
at net.bytebuddy.pool.TypePool$AbstractBase$Hierarchical.describe(TypePool.java:471)
at net.bytebuddy.agent.builder.AgentBuilder$DescriptionStrategy$Default$1.apply(AgentBuilder.java:3373)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.resolve(AgentBuilder.java:10499)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:10469)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10432)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1500(AgentBuilder.java:10198)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:10807)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:10754)
at java.security.AccessController.doPrivileged(Native Method)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10355)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
[Byte Buddy] ERROR java.lang.Shutdown$Lock [null, null, loaded=false]
java.lang.NoClassDefFoundError: net/bytebuddy/pool/TypePool$Default$WithLazyResolution$LazyResolution
at net.bytebuddy.pool.TypePool$Default$WithLazyResolution.doDescribe(TypePool.java:1319)
at net.bytebuddy.pool.TypePool$AbstractBase.describe(TypePool.java:408)
at net.bytebuddy.pool.TypePool$AbstractBase$Hierarchical.describe(TypePool.java:471)
at net.bytebuddy.agent.builder.AgentBuilder$DescriptionStrategy$Default$1.apply(AgentBuilder.java:3373)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.resolve(AgentBuilder.java:10499)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:10469)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10432)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1500(AgentBuilder.java:10198)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:10807)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:10754)
at java.security.AccessController.doPrivileged(Native Method)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10355)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
at java.lang.Shutdown.<clinit>(Shutdown.java:61)
Process finished with exit code 1
I realize my test isn't doing anything to test the ClassLoader decoration but I'm just iterating on using a 'normal' class first and slowly adding in the necessary bits to instrument ClassLoader
classes.
What am I doing wrong? Is it even feasible to instrument ClassLoader
s? Thanks!
解决方案
When you instrument a class, additional class might be loaded during the instrumentation. If this triggers class loading of classes that you rely on, for example classes of your own agent that is not in the Byte Buddy namespace or classes of the boot loader, this might trigger an instrumentation circularity and therefore a no class def found error that you experience.
Try to use a POOL_ONLY
TypeStrategy
that avoids class loading for the most and you probably need to reduce the amount of classes being instrumented to avoid instrumenting classes that are needed to establish your logging.
推荐阅读
- c++ - GDB 步骤 (in) 不适用于 std::string
- python-3.x - 检查值列表是否在另一个嵌套列表python中并返回匹配
- postgresql - 从选择语句插入时如何使用postgres Insert ....ON CONFLICT DO UPDATE
- python - 从数组中减去行的平均值
- python - 细胞的条件存在
- markdown - 在 Next.js 中使用 PrismJS 并在 markdown 中突出显示代码
- python - 用于 Postgres 的 Python 中 jsonb 数组的正确格式是什么?
- javascript - 如何声明一个函数,该函数将接受一个带参数的函数,而其参数中没有参数?
- visual-studio-code - 如何让不同的快捷方式在vscode中打开不同的工作区
- python - DeepCom 通过谷歌云训练模型(seq2 seq 模型):../config/default.yaml 错误