我最终使用Byte Buddy编写了一个 Java 代理,它使用 Advice API 在进入和离开方法时打印一条消息。根据我当前的配置,该代理似乎仅适用于由应用程序类加载器加载的类。
但是,我希望它也适用于任何类加载器加载的类。我遇到过多种技术(请参阅enableBootstrapInjection()或ignore()),但它们似乎不起作用。事实上,enableBootstrapInjection()它已经从 ByteBuddy 中消失了,并且该ignore()方法使我的 JVM 恐慌,因为我相信我遇到了循环问题,例如尝试检测该类java.lang.instrument(但这似乎不是唯一的问题,而且我找不到列出这些错误的方法)。
这是我的代理的简化版本:
AgentBuilder mybuilder = new AgentBuilder.Default()
.ignore(nameStartsWith("net.bytebuddy."))
.disableClassFormatChanges()
.with(RedefinitionStrategy.RETRANSFORMATION)
.with(InitializationStrategy.NoOp.INSTANCE)
.with(TypeStrategy.Default.REDEFINE);
mybuilder.type(nameMatches(".*").and(not(nameMatches("^src.Agent")))) // to prevent instrumenting itself
.transform((builder, type, classLoader, module) -> {
try {
return builder
.visit(Advice.to(TraceAdvice.class).on(isMethod()));
} catch (SecurityException e) {
e.printStackTrace();
return null;
}
}
).installOn(inst);
System.out.println("Done");
Run Code Online (Sandbox Code Playgroud)
如有必要,还有我的建议课程的简化版本:
public class TraceAdvice {
@Advice.OnMethodEnter
static void onEnter(
@Origin Method method,
@AllArguments(typing = DYNAMIC) Object[] args
) {
System.out.println("[+]");
}
@Advice.OnMethodExit
static void onExit() {
System.out.println("[-]");
}
}
Run Code Online (Sandbox Code Playgroud)
例如,我意识到检测 java.io.PrintStream.println 的循环依赖,我可以忽略此类方法(例如.and(not(nameMatches("^java.io.PrintStream")))第 7 行)。
以下是激活日志记录并获取有用的日志输出的方法。我还向您展示如何手动重新转换已加载的引导类。安装转换器后加载的引导类将自动进行转换,您也可以在下面的日志中看到。
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.reflect.Method;
import java.util.Properties;
import static net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy.RETRANSFORMATION;
import static net.bytebuddy.matcher.ElementMatchers.*;
class ByteBuddyInstrumentBootstrapClasses {
public static void main(String[] args) throws UnmodifiableClassException {
Instrumentation instrumentation = ByteBuddyAgent.install();
installTransformer(instrumentation);
// Use already loaded bootstrap class 'Properties'
System.out.println("Java version: " + System.getProperties().getProperty("java.version"));
// Retransform already loaded bootstrap class 'Properties'
instrumentation.retransformClasses(Properties.class);
// Use retransformed bootstrap class 'Properties' (should yield advice output)
System.out.println("Java version: " + System.getProperties().getProperty("java.version"));
}
private static void installTransformer(Instrumentation instrumentation) {
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(RETRANSFORMATION)
// Make sure we see helpful logs
.with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
.with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
.ignore(none())
// Ignore Byte Buddy and JDK classes we are not interested in
.ignore(
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("jdk.internal.reflect."))
.or(nameStartsWith("java.lang.invoke."))
.or(nameStartsWith("com.sun.proxy."))
)
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.type(any())
.transform((builder, type, classLoader, module) -> builder
.visit(Advice.to(TraceAdvice.class).on(isMethod()))
).installOn(instrumentation);
}
public static class TraceAdvice {
@Advice.OnMethodEnter
static void onEnter(@Advice.Origin Method method) {
// Avoid '+' string concatenation because of https://github.com/raphw/byte-buddy/issues/740
System.out.println("[+] ".concat(method.toString()));
}
@Advice.OnMethodExit
static void onExit(@Advice.Origin Method method) {
// Avoid '+' string concatenation because of https://github.com/raphw/byte-buddy/issues/740
System.out.println("[-] ".concat(method.toString()));
}
}
}
Run Code Online (Sandbox Code Playgroud)
控制台日志:
[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@2a54a73f on sun.instrument.InstrumentationImpl@16a0ee18
[Byte Buddy] TRANSFORM com.sun.tools.attach.VirtualMachine [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418, module jdk.attach, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM ByteBuddyInstrumentBootstrapClasses [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418, unnamed module @4e07b95f, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM com.intellij.rt.execution.application.AppMainV2$1 [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418, unnamed module @4e07b95f, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM com.intellij.rt.execution.application.AppMainV2 [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418, unnamed module @4e07b95f, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM com.intellij.rt.execution.application.AppMainV2$Agent [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418, unnamed module @4e07b95f, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM sun.text.resources.cldr.ext.FormatData_de [jdk.internal.loader.ClassLoaders$PlatformClassLoader@7203c7ff, module jdk.localedata, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM sun.util.resources.provider.LocaleDataProvider [jdk.internal.loader.ClassLoaders$PlatformClassLoader@7203c7ff, module jdk.localedata, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM sun.util.resources.provider.NonBaseLocaleDataMetaInfo [jdk.internal.loader.ClassLoaders$PlatformClassLoader@7203c7ff, module jdk.localedata, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo [jdk.internal.loader.ClassLoaders$PlatformClassLoader@7203c7ff, module jdk.localedata, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.Formattable [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.Formatter$Conversion [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.Formatter$Flags [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.Formatter$FormatSpecifier [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.Formatter$FixedString [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.Formatter$FormatString [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.regex.ASCII [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.regex.IntHashSet [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.regex.Matcher [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.regex.MatchResult [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.lang.CharacterData00 [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.lang.StringUTF16$CharsSpliterator [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.stream.IntPipeline$9$1 [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] TRANSFORM java.util.stream.Sink$ChainedInt [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@2a54a73f on sun.instrument.InstrumentationImpl@16a0ee18
Java version: 14.0.2
[Byte Buddy] TRANSFORM java.util.Properties [null, module java.base, Thread[main,5,main], loaded=true]
[+] public java.lang.String java.util.Properties.getProperty(java.lang.String)
[-] public java.lang.String java.util.Properties.getProperty(java.lang.String)
Java version: 14.0.2
[Byte Buddy] TRANSFORM java.util.IdentityHashMap$IdentityHashMapIterator [null, module java.base, Thread[main,5,main], loaded=false]
[Byte Buddy] TRANSFORM java.util.IdentityHashMap$KeyIterator [null, module java.base, Thread[main,5,main], loaded=false]
[+] public boolean java.util.IdentityHashMap$IdentityHashMapIterator.hasNext()
[-] public boolean java.util.IdentityHashMap$IdentityHashMapIterator.hasNext()
[+] public java.lang.Object java.util.IdentityHashMap$KeyIterator.next()
[+] protected int java.util.IdentityHashMap$IdentityHashMapIterator.nextIndex()
[-] protected int java.util.IdentityHashMap$IdentityHashMapIterator.nextIndex()
[-] public java.lang.Object java.util.IdentityHashMap$KeyIterator.next()
[+] public boolean java.util.IdentityHashMap$IdentityHashMapIterator.hasNext()
[-] public boolean java.util.IdentityHashMap$IdentityHashMapIterator.hasNext()
[Byte Buddy] TRANSFORM java.lang.Shutdown [null, module java.base, Thread[DestroyJavaVM,5,main], loaded=false]
[Byte Buddy] TRANSFORM java.lang.Shutdown$Lock [null, module java.base, Thread[DestroyJavaVM,5,main], loaded=false]
[+] static void java.lang.Shutdown.shutdown()
[+] private static void java.lang.Shutdown.runHooks()
[-] private static void java.lang.Shutdown.runHooks()
[-] static void java.lang.Shutdown.shutdown()
Run Code Online (Sandbox Code Playgroud)
请注意,第一次调用System.getProperties().getProperty("java.version")不会产生建议日志记录,但重新转换后的第二次调用会产生建议日志记录。
查看 GitHub 存储库后进行更新:
我理解正确吗?模块launcher尝试将模块动态附加agent到另一个已运行的 JVM。这看起来很复杂。您是否尝试过使用参数启动另一个 JVM -javaagent:/path/to/agent.jar?您稍后仍然可以尝试其他策略。但无论哪种方式,请注意,您的代理类Agent不会CompleteSTE像这样出现在启动类路径上。
鉴于建议代码将内联到目标类(也是引导类)中,这意味着引导类需要能够在引导类路径上找到建议代码引用的所有类。有两种方法可以实现这一目标:
除了 . 之外,还添加-Xbootclasspath/a:/path/to/agent.jar到目标 JVM 命令行-javaagent:/path/to/agent.jar。当然,只有当您对目标 JVM 的命令行有影响力时,这才有效。动态附加到任何正在运行的 JVM 都不会以这种方式工作,因为您来不及指定启动类路径选项。
将实际代理划分为“跳板代理”和另一个包含建议代码引用的类的 JAR。额外的 JAR 可以作为资源打包在代理 JAR 内,或者驻留在文件系统的某个位置,具体取决于您的解决方案的通用性。跳板代理将
Instrumentation::appendToBootstrapClassLoaderSearch(JarFile),Class.forName(..).getMethod(..).invoke(..)。顺便说一句,如果 Byte Buddy (BB) 建议引用的类本身使用 BB API,则还需要将 BB 本身放在启动类路径上。所有这些都绝非微不足道,因此您要尽力避免这种情况。当我试图弄清楚如何最好地实现我的专用模拟工具Sarek时,我经历了所有这些。
更新 2:我在这个 GitHub fork中对 OP 的原始存储库进行了简化和大规模重组。