【问题标题】:Instrument Java bytecode without agent在没有代理的情况下检测 Java 字节码
【发布时间】:2021-05-18 13:50:07
【问题描述】:

是否可以在没有 Java 代理的情况下动态检测 Java 字节码?我之前使用 Java 代理对字节码进行了检测,做了类似的事情:

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

但是,如果不使用 Java 代理,这可能吗?我想做的是调用一个方法,该方法将在 JVM 运行后的任何给定时间执行我的检测,而无需使用代理。

【问题讨论】:

  • 加载时间转换可以使用自定义类加载器完成,对于通过此加载器加载的类。重新定义或重新转换是不可能的。如果 JVM 支持,您可以稍后启动一个新代理,例如 this answer 的末尾,但是,HotSpot JVM 的最新版本需要在启动时启用自连接。您可以通过启动另一个执行附加的进程来规避此问题,但是您会看到,事情变得越来越复杂。
  • @Holger 我能否附加到不同的 JVM 并将 Instrumentation 的实例从那个转移到我想重新转换的那个?我只是无法连接到我想直接检测的 JVM。
  • @Holger,如果我设法获得对Instrumentation 引擎的引用,为什么无法重新定义或重新转换?我一直都在这样做,通常使用net.bytebuddy:byte-buddy-agent(它特别不包含任何字节好友功能,只是有助于开始检测)来简化问题,以节省手动工作。无需启动另一个进程并远程附加。
  • @kriegaex 获得Instrumentation 实例的唯一方法是使用Java 代理。如果byte-buddy-agent 不是代理,那还有什么?仅仅因为你忽略了这个库实际上在做什么,这些步骤并没有变得不必要。 From the author of ByteBuddy: “你需要在虚拟机启动时设置该属性,而不是在运行时设置,否则会被忽略。否则,请查看可以解决该限制的 byte-buddy-agent。” 所以它确实包含解决方法。
  • @JerryJone 你不需要转移任何东西。辅助进程在这里没有做太多。代理仍然在您自己的 JVM 中启动,这就是在 JVM 中启动代理的含义。所有子进程都在做,附加到您的原始 JVM 并告诉它启动代理。从那时起,其他一切都将以与自连接相同的方式工作。代理存根可以将Instrumentation 引用传递给您的应用程序,您可以在那里使用它。但正如 kriegaex 所暗示的,最小化的 byte-buddy-agent 可以为您做到这一点;你不需要自己实现它。

标签: bytecode instrumentation agent java-bytecode-asm


【解决方案1】:

在没有Instrumentation 实现实例的情况下执行字节码转换的唯一方法是

  • 可以在调用defineClass 之前更改字节的自定义类加载器(仅限于通过该加载器加载的类)

  • 在加载类之前使用修改后的字节调用MethodHandles.Lookup.defineClass,这适用于具有延迟加载的广泛 JVM,但仅限于您自己的模块或向您的模块打开的模块

这两种方法都不能更改已加载的类。这需要Instrumentation 引用,而JVM 发出此类引用的唯一地方是Java 代理的初始化方法。所以要使用它,Java 代理是不可避免的,即使它可能只是一个存储引用的存根,供您的应用程序代码使用。

请注意,从 Java 9 开始,jar 文件的 Launcher-Agent-Class 清单属性可以指定 Java 代理的类,以便在使用 Main-Class 指定的类启动之前启动。这样,您可以轻松地让您的代理与您的 JVM 中的应用程序代码协作,而无需任何额外的命令行选项。代理可以简单到在您的主类中使用 agentmain 方法,将 Instrumentation 引用存储在静态变量中。

the java.lang.instrument package documentation

当 JVM 尚未使用代理启动时,获取 Instrumentation 实例会比较棘手。一般来说,它必须支持启动后启动代理,例如通过附加 API。 This answer 在其结尾展示了这样一种自我连接,以获取Instrumentation。当您的应用程序 jar 文件中有必要的清单属性时,您甚至可以将其用作代理 jar 并省略临时存根文件的创建。

然而,最近的 JVM 禁止自连接,除非在启动时指定了 -Djdk.attach.allowAttachSelf=true,但我想,在启动时采取额外的步骤,正是你不想做的事情。避免这种情况的一种方法是使用另一个过程。所有这个过程所要做的就是附加到您的原始进程并告诉 JVM 启动代理。然后,它可能已经终止,其他一切都与引入此限制之前的方式相同。

正如this comment 中提到的,Byte-Buddy 已经实现了这些必要的步骤,并且精简后的 Byte-Buddy-Agent 仅包含该逻辑,因此您可以使用它在其上构建自己的逻辑。

【讨论】:

  • 这是一个很好的解释,全面而简洁。它值得拥有自己的博客文章。有长度限制的评论,通常是模棱两可的,因为作者(例如我自己,见上文)试图将其放入长度限制以避免编写多个 cmets,这对于复杂信息来说是不合适的运输工具。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-12
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
相关资源
最近更新 更多