【发布时间】:2023-04-04 04:51:01
【问题描述】:
我想要做的是记录一个 Throwable 被抛出方法的事件。 我写了以下简单的代码,并没有刻意使用 COMPUTE_FRAME 和 COMPUTE_MAX 来熟悉堆栈映射帧、操作数堆栈和局部变量的概念。我只通过插桩插入三个堆栈映射帧:后tryEnd 标签,catchStart 标签之后,catchEnd 之后(在我的班级 MyMethodVisitor 中,代码是 sho)。
当我在joda-time的测试过程中尝试我的javaagent时,它崩溃并显示以下消息:
[ERROR] There was an error in the forked process
[ERROR] Stack map does not match the one at exception handler 9
[ERROR] Exception Details:
[ERROR] Location:
[ERROR] org/joda/time/TestAllPackages.<init>(Ljava/lang/String;)V @9: ldc
[ERROR] Reason:
[ERROR] Type 'org/joda/time/TestAllPackages' (current frame, locals[0]) is not assignable to uninitializedThis (stack map, locals[0])
[ERROR] Current Frame:
[ERROR] bci: @2
[ERROR] flags: { flagThisUninit }
[ERROR] locals: { 'org/joda/time/TestAllPackages', 'java/lang/String' }
[ERROR] stack: { 'java/lang/Throwable' }
[ERROR] Stackmap Frame:
[ERROR] bci: @9
[ERROR] flags: { flagThisUninit }
[ERROR] locals: { uninitializedThis, 'java/lang/String' }
[ERROR] stack: { 'java/lang/Throwable' }
[ERROR] Bytecode:
[ERROR] 0x0000000: 2a2b b700 01b1 a700 0912 57b8 005c bfb1
[ERROR] 0x0000010:
[ERROR] Exception Handler Table:
[ERROR] bci [0, 6] => handler: 9
[ERROR] Stackmap Table:
[ERROR] same_frame(@6)
[ERROR] same_locals_1_stack_item_frame(@9,Object[#85])
[ERROR] same_frame(@15)
显然,这一定是我插入堆栈图帧时的问题。但我很困惑:
-
Current Frame和Stackmap Frame的具体含义和区别是什么? - 为什么@9 处的堆栈图帧中有一个
uninitializedThis?据我了解,在构造函数调用完成之前,对象始终为uninitializedThis,对吗? - 我认为我的检测是正确的,因为
org/joda/time/TestAllPackages是this的类型。如何避免org/joda/time/TestAllPackages和uninitializedThis不一致?
当我查看字节码时,它看起来像:
public org.joda.time.TestAllPackages(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=7, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #1 // Method junit/framework/TestCase."<init>":(Ljava/lang/String;)V
5: return
6: goto 15
9: ldc #87 // String org/joda/time/TestAllPackages#<init>#(Ljava/lang/String;)V
11: invokestatic #92 // Method MyRecorder.exception_caught:(Ljava/lang/String;)V
14: athrow
15: return
Exception table:
from to target type
0 6 9 Class java/lang/Throwable
StackMapTable: number_of_entries = 3
frame_type = 6 /* same */
frame_type = 66 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 5 /* same */
LineNumberTable:
line 31: 0
line 32: 5
顺便说一句,我的简化检测代码如下:
public class PreMain {
public static void premain(String args, Instrumentation inst){
inst.addTransformer(new MyTransformer());
}
}
public class MyTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] result = classfileBuffer;
try{
if (className == null || shouldExcludeClass(className)) return result;
ClassReader cr = new ClassReader(classfileBuffer);
// I don't use COMPUTE_FRAME and COMPUTE_MAX deliberately
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new MyClassVistor(cw, className, loader);
cr.accept(cv, 0);
result = cw.toByteArray();
} catch (Throwable t){
t.printStackTrace();
}
return result;
}
}
public class MyClassVistor extends ClassVisitor {
...
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (!isNative && !isEnum && !isAbstract && !"<clinit>".equals(name)){
mv = new MyMethodVisitor(mv, name, access, desc, slashClassName, isStatic, isPublic);
}
return mv;
}
}
public class MyMethodVisitor extends MethodVisitor {
private Label tryStart = new Label();
private Label tryEnd = new Label();
private Label catchStart = new Label();
private Label catchEnd = new Label();
public void visitCode() {
mv.visitCode();
mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, "java/lang/Throwable");
mv.visitLabel(tryStart);
}
@Override
public void visitEnd() {
mv.visitLabel(tryEnd);
mv.visitFrame(F_SAME, 0, null, 0, null); /* This line takes me more than 6 hours to figure out. Why this line can't be omitted? */
mv.visitJumpInsn(GOTO, catchEnd);
mv.visitLabel(catchStart);
// exception caught
mv.visitFrame(F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"}); /* add stackmap frame after jump target */
mv.visitLdcInsn(this.selfMethodId);
mv.visitMethodInsn(INVOKESTATIC, MyRecorder.SLASH_CLASS_NAME, MyRecorder.EXCEPTION_CAUGHT,
"(Ljava/lang/String;)V", false);
mv.visitInsn(ATHROW);
mv.visitLabel(catchEnd);
mv.visitFrame(F_SAME, 0, null, 0, null); /* add stackmap frame after jump target */
// Make up a return statement
switch (Type.getReturnType(selfDesc).getSort()){
case BYTE:
case CHAR:
case SHORT:
case BOOLEAN:
case INT:
mv.visitLdcInsn(0);
mv.visitInsn(IRETURN);
break;
case LONG:
mv.visitLdcInsn(0L);
mv.visitInsn(LRETURN);
break;
case FLOAT:
mv.visitLdcInsn(0f);
mv.visitInsn(FRETURN);
break;
case DOUBLE:
mv.visitLdcInsn(0.0);
mv.visitInsn(DRETURN);
break;
case OBJECT:
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
break;
case VOID:
mv.visitInsn(RETURN);
break;
}
super.visitEnd();
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// +5 because other logic need more space on operand stack
super.visitMaxs(maxStack + 5, maxLocals);
}
}
【问题讨论】:
标签: java jvm instrumentation java-bytecode-asm javaagents