[英]“VerifyError: Expecting to find object/array on stack” when using ASM to monitor object creation in Java?
我想要做的是监视 object 的创建并记录该 object 的唯一 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 的创建?
实际上,我也尝试监视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);
}
}
object 实例化分为两条指令。 这会使 object 实例化难以工作。 这实际上是我的多个项目中最大的问题。 简单的表达式new Integer(0)
可能会编译为以下代码。
new java/lang/Integer ═╤═
dup ═╪═══╤═
iconst_0 │ │ ═╤═
invokespecial java/lang/Integer.<init>(I)V │ ═╧═══╧═
↓
用new
创建的引用在被invokespecial
初始化之前不能使用。 因此,您不能将您的仪器直接放在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
和在invokestatic
后面注入invokespecial
来处理。
无论如何,如果您想处理更多奇特的情况,字节码还可以包含dup_x1
或dup_x2
类的指令。 GeneratorAdapter.box(Type)
实际上会生成这样的代码。 一些“惰性”字节码操纵器也可能导致new
指令,其中实例在使用pop
删除之前从未初始化。 如您所见,在字节码中使用 object 实例化可能需要大量工作。 但是,根据您的情况,您可能不需要支持所有这些情况。 不幸的是,我没有经验来估计您需要支持哪些案例。
这是一个不处理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;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.