我想做的是记录Throwable被抛出方法的事件。**我写了下面的简单代码,没有特意使用COMPUTE_FRAME和COMPUTE_MAX来熟悉堆栈Map帧、操作数堆栈和局部变量的概念。**我只通过插装插入了三个堆栈Map帧:在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)
显然,这一定是我插入stackmap框架时的问题。但是我感到困惑:
Current Frame
和Stackmap Frame
的确切含义和区别是什么?
1.为什么在stackmap框架中的@9处有一个uninitializedThis
?据我所知,一个对象在构造函数调用完成之前总是uninitializedThis
,对吗?
1.我认为我的插桩是正确的,因为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
顺便说一句,我的简化指令代码如下所示:
第一个
2条答案
按热度按时间cetgtptt1#
我们先收拾一下
您正在为整个方法创建异常处理程序,并将该处理程序追加到原始代码之后。假定原始代码有效,则它必须以
…return
、athrow
或goto
指令结束,因为不允许代码“脱离”代码结尾。因此,你在这里附加的代码,通过处理程序到新生成的返回指令的
goto
是不可达的。不可达的代码总是需要一个新的堆栈Map帧来描述它的初始状态,因为验证者无法猜测。但是,当然,您可以省略这些不必要的代码,而不是为无法访问的代码提供一个框架。
简化后的代码如下所示
注意:由于我们不知道插桩代码包含什么类型的帧(例如,它可能会引入新的变量),我们不应该使用基于前一个帧定义堆栈状态的帧类型。上面的例子只是丢弃了所有变量,因为异常处理程序无论如何都不需要它们,这与每一种可能的堆栈状态都是兼容的--至少对于普通方法是这样。
上面的代码足以检测每一个普通的方法,但不能检测构造函数。创建一个覆盖整个构造函数的异常处理程序是不可能的,包括带有堆栈Map的
super(…)
。没有堆栈Map的旧类文件可以安装这样的异常处理程序,只要它不试图return
或使用this
。但是有了堆栈Map,不可能表达处理程序的初始状态:来自JVMS第4.10.1.9节:
<init>
方法的调用抛出异常,则未初始化的对象可能会处于部分初始化状态,并且需要使其永久不可用。这由包含损坏对象的异常帧表示(本地的新值)和flagThisUninit
标志(旧标志)。无法从带有flagThisUninit
标志的明显初始化的对象获取正确初始化的对象,因此该对象永久不可用。*问题是我们不能在栈Map中表示标志,栈Map的框架只包含类型,如果 UninitializedThis 存在,则假定标志
flagThisUninit
存在,这适合描述超构造函数调用之前的情况,当 UninitializedThis 不存在时,则假定标志flagThisUninit
也不存在。其适合于描述在超级构造函数调用之后的情况。但是当超级构造函数调用失败并出现异常时,堆栈状态如上所述,UninitializedThis 已经被local的新值替换,但标志
flagThisUninit
仍然存在。我们无法使用堆栈Map来描述这样的帧,因此,我们无法描述异常处理程序的初始帧。所以,你不能用你的异常处理程序来覆盖超级构造函数调用。你只能为调用之前和之后的代码安装异常处理程序,并且由于不兼容的标志状态,你需要两个不同的处理程序。
8ehkhllq2#
也许
org.objectweb.asm.tree.analysis.Analyzer
类的analyze
方法可以为我们提供一些启示:try块中的每条指令都将执行以下两项操作:
然后,让我们模拟指令的执行:
在上面的代码片段中,
0002
处的locals[0]
是this
;但是,0006
处的locals[0]
是uninitialized_this
。这两个值不兼容。Current Frame
是特定位置处的实际帧,而Stackmap Frame
是另一特定位置处的预期帧。恕我直言,我们不应该捕捉
super()
方法。几件小事:
MyMethodVisitor.visitEnd()
中的代码应该放在visitMax()
方法中。这是因为visitCode()
方法标记了方法体的开始,visitMax()
标记了方法体的结束,而visitEnd()
标记了整个方法的结束。mv.visitTryCatchBlock()
应该放在visitMax()
方法中。如果我们把mv.visitTryCatchBlock()
放在visitCode()
中,它将使所有其他try-catch子句无效。goto
指令之前已经有一个return
。下面的两行代码可能是多余的:最后,为避免不一致,建议使用
COMPUTE_FRAME
选项。