使用stackmap框架以及它如何帮助进行字节码验证?

时间:2017-03-09 23:40:28

标签: java jvm bytecode java-bytecode-asm

我一直试图绕过模糊的堆栈映射框架,并且只需一次通过即可验证动态加载的类。

很少有堆栈溢出答案&我发现非常有用的其他资源是

  1. Is there a better explanation of stack map frames?

  2. What kind of Java code requires stackmap frames?

  3. http://chrononsystems.com/blog/java-7-design-flaw-leads-to-huge-backward-step-for-the-jvm

  4. 我理解以下内容 -

    1. 每个基本块都应以堆栈映射框开头。
    2. 紧跟无条件分支之后的每条指令(它是基本块的开始)都应该有一个堆栈映射帧。
    3. 通过ASM创建堆栈映射帧的算法。 ASM的Section 3.5 文档
    4. 所有这些文章的缺点在于它没有描述堆栈图框架在验证中的确切用途。

      更具体 - 我们假设我们有一个如下所述的字节码。在当前位置,操作数堆栈将为空,并且局部变量1的类型将为B.位置L0具有关联的堆栈映射帧。验证者如何使用此信息?

          <initial frame for method>
          GETSTATIC B.VALUE
          ASTORE 1 
          GOTO L0 <- Current location
          <stack map frame>
      L1  GETSTATIC A.VALUE
          ASTORE 1
          <stack map frame>
      L0  ILOAD 0
          IFNE L1
          <stack map frame>
          ALOAD 1
          ARETURN
      

      注意: 请注意,我确实阅读了JVM规范并且很难理解堆栈映射框架。任何帮助都会非常有帮助。

1 个答案:

答案 0 :(得分:7)

在字节码的每一点,本地和操作数堆栈中的每个项都有一个隐式类型。在旧系统下,验证者按原样计算这些类型,但是如果控制流向后移动,则可能会改变目标的类型,这意味着它必须迭代直到收敛。

现在,在这样的跳转目标上明确指定了类型。验证器通过字节码进行单个线性传递。每当它到达堆栈帧时,它断言当前推断的类型与堆栈帧中的显式类型兼容,然后使用堆栈帧类型继续。每当它跳转时,它断言跳转目标处的堆栈帧具有与当前推断类型兼容的类型。

基本上,堆栈帧显式存储“迭代到收敛”的结果,这意味着验证者只需检查结果是否正确,而不是计算它们,这可以在一次传递中完成。

除此之外,不允许较新的类文件使用jsrret指令,这使得验证变得更加容易。

作为一个具体示例,假设您有类似以下的代码

.method static foo : ()V
L0: aconst_null
L1: astore_0
L2: new Foo
L3: dup
L4: invokespecial Method Foo <init> ()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类型。然后,stackmap验证程序在每个步骤推断出以下类型

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)