我有一个执行字节码操作的旧库(大约 2005 年),但不涉及堆栈映射。因此我的 jvm (java 8) 提示它们是无效的类。避免错误的唯一方法是使用 -noverify
运行 jvm。但这对我来说不是一个长期的解决方案。
有什么方法可以在生成类后重新生成堆栈映射?我看到 ClassWriter
类有一个选项可以重新生成堆栈映射,但我不确定如何读取字节类并重写一个新的。那可行吗?
最佳答案
当您检测没有堆栈映射的旧类并保留它们的旧版本号时,不会有问题,因为它们将由 JVM 以与以前相同的方式处理,不需要堆栈映射。当然,这意味着您不能注入(inject)更新的字节码功能。
当您检测在转换前具有有效堆栈映射的较新类文件时,您将不会遇到这些问题 described by Antimony .因此,您可以使用 ASM 重新生成堆栈图:
byte[] bytecode = … // result of your instrumentation
ClassReader cr = new ClassReader(bytecode);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cr.accept(cw, ClassReader.SKIP_FRAMES);
bytecode = cw.toByteArray(); // with recalculated stack maps
访问者 API 旨在允许轻松地将阅读器与编写器链接起来,并且仅添加代码来拦截您想要更改的工件。
请注意,由于我们知道我们将使用 ClassWriter.COMPUTE_FRAMES
从头开始重新生成堆栈图帧,因此我们可以将 ClassReader.SKIP_FRAMES
传递给读取器以告知它不要处理我们无论如何都会忽略的源帧。
当我们知道类结构不会改变时,还有另一种优化可能。我们可以将 ClassReader
传递给 ClassWriter
的构造函数,以从未更改的结构中获益,例如目标常量池将使用源常量池的副本进行初始化。但是,必须小心处理此选项。如果我们根本不拦截方法,它也会得到优化,即代码被完全复制,甚至没有重新计算堆栈帧。所以我们需要一个自定义方法访问者来假装代码可能会发生变化:
byte[] bytecode = … // result of your instrumentation
ClassReader cr = new ClassReader(bytecode);
// passing cr to ClassWriter to enable optimizations
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor writer=super.visitMethod(access, name, desc, signature, exceptions);
return new MethodVisitor(Opcodes.ASM5, writer) {
// not changing anything, just preventing code specific optimizations
};
}
}, ClassReader.SKIP_FRAMES);
bytecode = cw.toByteArray(); // with recalculated stack maps
这样,可以将常量池等未更改的工件直接复制到目标字节代码,同时仍然重新计算堆栈映射帧。
不过,有一些注意事项。从头开始生成堆栈图意味着不利用任何关于原始代码结构或转换性质的知识。例如。编译器会知道局部变量声明的形式类型,而 ClassWriter
可能会看到不同的实际类型,它必须为其找到公共(public)基类型。此搜索可能非常昂贵,导致加载延迟的类,甚至在正常执行期间不使用。结果类型甚至可能与原始代码中声明的通用类型不同。这将是一个正确的类型,但可能会再次更改结果代码中类的使用。
如果您在不同的环境中执行检测,ASM 尝试加载类以确定通用类型可能会失败。然后,您将不得不覆盖 ClassWriter.getCommonSuperClass(…)
具有可以在该环境中执行操作的实现。这也是添加优化的地方,如果您对代码有更多了解并且可以提供答案而无需通过类型层次结构进行昂贵的搜索。
通常,建议首先重构旧库以使用 ASM,而不是需要后续的调整步骤。如上所述,当使用启用优化的 ClassReader
和 ClassWriter
链执行代码转换时,ASM 将能够复制所有未更改的方法,包括它们的堆栈映射,并且只重新计算实际更改的方法的堆栈图。在上面的代码中,在后续步骤中进行重新计算,我们不得不禁用优化,因为我们不再知道实际更改了哪些方法。
下一个合乎逻辑的步骤是将堆栈图处理合并到检测中,因为关于实际转换的知识通常允许保留 99% 的现有帧并轻松调整其他帧,而不需要从头开始进行昂贵的重新计算.
关于java - 有什么方法可以从字节码重新生成堆栈图?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46622206/