使用 ASM 监视 Java 中的对象创建时出现“VerifyError:期望在堆栈上找到对象/数组”?
Posted
技术标签:
【中文标题】使用 ASM 监视 Java 中的对象创建时出现“VerifyError:期望在堆栈上找到对象/数组”?【英文标题】:"VerifyError: Expecting to find object/array on stack" when using ASM to monitor object creation in Java? 【发布时间】:2020-10-10 11:34:42 【问题描述】:我想做的是监控对象的创建并为该对象记录一个唯一的 ID。所以我使用 ASM 来监控“NEW”指令。在我的方法访问者适配器中:
public void visitTypeInsn(int opcode, String desc)
mv.visitTypeInsn(opcode, desc);
if (opcode == NEW)
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESTATIC, "org/.../.../MyRecorder", "object_new",
"(Ljava/lang/Object;)V", false);
在MyRecorder.java
:
public static void object_new(Object ref)
log("object_new !");
log("MyRecorder: " + ref);
log("ref.getClass().getName(): " + ref.getClass().getName());
但是,这些代码导致java.lang.VerifyError: (...) Expecting to find object/array on stack
。我不知道为什么这行不通。如果这种方式不对,如何监控对象的创建?
其实我也试过监控object.<init>
,而不是监控NEW
指令。但是,使用以下代码,它会抛出 java.lang.VerifyError: (...) Unable to pop operand off an empty stack
:
public void visitMethodInsn(int opc, String owner, String name, String desc, boolean isInterface)
...
mv.visitMethodInsn(opc, owner, name, desc, isInterface);
if (opc == INVOKESPECIAL && name.equals("<init>"))
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESTATIC, "org/myekstazi/agent/PurityRecorder", "object_new",
"(Ljava/lang/Object;)V", false);
【问题讨论】:
生成的字节码是什么样的?您可以在已编译的类上使用javap
来解决这个问题。我猜你可能在NEW
操作码之后干预得太早了,需要等到INVOKESPECIAL
被调用。
@nrainer 我不知道生成的字节码是什么样的,因为我在加载类时使用 java 代理来转换类,所以我没有将它们转储到磁盘。我认为您的意思是在对象初始化后插入这些指令?我也尝试过,但遗憾的是它也无法顺利运行。
问题是你不能用一个未初始化的对象做很多事情。生成的字节码看起来像 new C, dup, invokestatic ...PurityRecorder.object_new, dup, invokespecial C.<init>
@JohannesKuhn 感谢您的评论!你能解释更多关于“不能对未初始化的对象做很多事情”吗?我认为字节码与您描述的完全一样。但我不明白为什么它会抛出 VerifyError,因为它看起来是有效的。
docs.oracle.com/javase/specs/jvms/se14/html/… 搜索未初始化的。
【参考方案1】:
对象实例化分为两条指令。这会使对象实例化变得困难。这实际上是我的多个项目中最大的问题。简单的表达式new Integer(0)
可能会编译为以下代码。
new java/lang/Integer ═╤═
dup ═╪═══╤═
iconst_0 │ │ ═╤═
invokespecial java/lang/Integer.<init>(I)V │ ═╧═══╧═
↓
在invokespecial
初始化之前,无法使用使用new
创建的引用。因此,您不能将您的仪器直接放在new
之后。在上面的示例中,您可以将检测放在invokespecial
后面。但是,您需要将其与对超级构造函数的调用区分开来。 super(0)
之类的语句可能会编译为以下代码。
aload_0 ═╤═
iconst_0 │ ═╤═
invokespecial super class.<init>(I)V ═╧═══╧═
这两个示例可能涵盖了调用<init>
的两种最常见的情况。如果您不对new Integer(0)
创建的值做任何事情,编译器也可能会决定跳过dup
指令。我不确定他们是否真的这样做。不管怎样,第一个例子会变成下面的代码。
new java/lang/Integer ═╤═
iconst_0 │ ═╤═
invokespecial java/lang/Integer.<init>(I)V ═╧═══╧═
这仍然可以通过在new
后面注入dup
和在invokespecial
后面注入invokestatic
来处理。
无论如何,如果您想处理更奇特的情况,字节码还可以包含dup_x1
或dup_x2
之类的指令。 GeneratorAdapter.box(Type)
实际上会生成这样的代码。一些“惰性”字节码操纵器也可能导致new
指令在使用pop
删除实例之前从未初始化实例。如您所见,使用字节码中的对象实例化可能需要大量工作。但是,根据您的情况,您可能不需要支持所有这些情况。很遗憾,我没有经验来估计您需要支持哪些案例。
这是一个不处理dup_x1
和类似指令的示例。因此,它不适用于GeneratorAdapter.box(Type)
生成的代码。除此之外,它似乎在我的测试中运行良好。它使用AnalyzerAdapter
来获取有关操作数堆栈的一些信息。
public final class InstrumentationMethodVisitor extends AnalyzerAdapter
public InstrumentationMethodVisitor(
String owner, int access, String name, String descriptor,
MethodVisitor methodVisitor)
super(ASM8, owner, access, name, descriptor, methodVisitor);
@Override
public void visitTypeInsn(int opcode, String type)
super.visitTypeInsn(opcode, type);
if (opcode == NEW && stack != null)
mv.visitInsn(DUP);
@Override
public void visitInsn(int opcode)
if (opcode == POP && stack != null && stack.get(stack.size() - 1) instanceof Label)
mv.visitInsn(POP);
super.visitInsn(opcode);
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
if (opcode == INVOKESPECIAL && name.equals("<init>"))
boolean hasRelatedNew = stack != null && stack.get(stack.size() - getAmountOfArgumentSlots(descriptor) - 1) instanceof Label;
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
if (hasRelatedNew)
mv.visitMethodInsn(
INVOKESTATIC,
"org/myekstazi/agent/PurityRecorder",
"object_new",
"(Ljava/lang/Object;)V",
false);
else
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
private static int getAmountOfArgumentSlots(String descriptor)
int slots = 0;
for (Type type : Type.getArgumentTypes(descriptor))
slots += type.getSize();
return slots;
【讨论】:
以上是关于使用 ASM 监视 Java 中的对象创建时出现“VerifyError:期望在堆栈上找到对象/数组”?的主要内容,如果未能解决你的问题,请参考以下文章
尝试创建 OkHttpClient 对象时出现 kotlin/TypeCastException
java.lang.UnsatisfiedLinkError 在 teiid jboss 翻译器开发中从 FesApi 创建 EpcDocument 对象时出现异常
使用对象 java.util.Date 时出现错误的星期几 [重复]
使用 StorageLibraryChangeTracker 监视文件夹时出现 0x80080222 错误
解析为对象而不是数组时出现Json错误:JSONObject中的getJSONObject(java.lang.String)无法应用于(int)
使用 initWithRelationship:attributes 创建 RKConnectionDescription 时出现异常: