作者:鹏63213 | 来源:互联网 | 2023-02-09 14:17
我一直试图绕过模糊的堆栈映射框架,它只是在一次通过中验证动态加载的类.
很少有堆栈溢出答案和其他资源,我发现非常有用
堆栈映射帧有更好的解释吗?
什么样的Java代码需要stackmap框架?
http://chrononsystems.com/blog/java-7-design-flaw-leads-to-huge-backward-step-for-the-jvm
我理解以下内容 -
每个基本块都应以堆栈映射帧开始.
紧跟在无条件分支之后的每条指令(它是基本块的开始)应该具有堆栈映射帧.
通过ASM创建堆栈映射帧的算法.ASM文档的第3.5节
所有这些文章的缺点在于它没有描述在验证中如何使用堆栈映射框.
更具体地说 - 假设我们有一个如下所述的字节码.在当前位置,操作数堆栈将为空,并且局部变量1的类型将为B.位置L0具有关联的堆栈映射帧.验证者如何使用此信息?
GETSTATIC B.VALUE
ASTORE 1
GOTO L0 <- Current location
L1 GETSTATIC A.VALUE
ASTORE 1
L0 ILOAD 0
IFNE L1
ALOAD 1
ARETURN
注意:请注意,我确实阅读了JVM规范并且很难理解堆栈映射框架.任何帮助都会非常有帮助.
1> Antimony..:
在字节码中的每一点,本地和操作数堆栈中的每个项都有一个隐式类型.在旧系统下,验证程序按原样计算这些类型,但是如果控制流向后移动,则可能会改变目标的类型,这意味着它必须迭代直到收敛.
现在,在这样的跳转目标上明确指定了类型.验证器通过字节码进行单个线性传递.每当它到达堆栈帧时,它断言当前推断的类型与堆栈帧中的显式类型兼容,然后使用堆栈帧类型继续.每当它跳转时,它断言跳转目标处的堆栈帧具有与当前推断类型兼容的类型.
本质上,堆栈帧明确地存储"迭代到收敛"的结果,这意味着验证者只是检查结果是否正确,而不是计算它们,这可以在一次通过中完成.
除此之外,不允许较新的类文件使用jsr
和ret
指令,这使得验证变得更加容易.
作为一个具体示例,假设您有类似以下的代码
.method static foo : ()V
L0: aconst_null
L1: astore_0
L2: new Foo
L3: dup
L4: invokespecial Method Foo ()V
L5: astore_0
L6: goto L2
.end method
在推理验证下,verfier最初会在L2处将var 0的类型推断为NULL.一旦达到L6,它必须返回并将类型更改为Foo.
在堆栈映射验证下,验证器将再次在L2处初始推断var 0的类型为NULL.但是,它看到L2处有一个堆栈帧,并检查堆栈帧中0的类型.无论它是什么,它将0设置为该类型并继续检查.当它到达L6时,它查看跳转目标的堆栈帧(L2),并断言L6(即Foo)的0类型可分配给L2的0类型(在堆栈中指定) L2的框架.
假设L2处的堆栈帧声明0具有Object类型.然后,堆栈映射验证程序在每个步骤推断出以下类型
L0: INVALID (unset)
L1: INVALID (unset)
L2: NULL
(checks stack frame at L2)
(assert that NULL is assignable to Object)
L2: Object
L3: Object
L4: Object
L5: Object
L6: Foo
(check stack frame at L2)
(assert that Foo is assignable to Object)