[英]“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.