4.7.4 StackMapTable 属性

  StackMapTable 属性是一个变长属性,位于 Code(§4.7.3)属性的属性表中。这个属性会在虚拟机类加载的类型阶段(§4.10.1)被使用。
  StackMapTable 属性包含 0 至多个栈映射帧(Stack Map Frames),每个栈映射帧都显式或隐式地指定了一个字节码偏移量,用于表示局部变量表和操作数栈的验证类型(Verification Types §4.10.1)。
  类型检测器(Type Checker)会检查和处理目标方法的局部变量和操作数栈所需要的类型。本章节中,一个存储单元(Location)的含义是唯一的局部变量或操作数栈项。
  我们还将用到术语“栈映射帧”(Stack Map Frame)和“类型状态”(Type State)来描述如何从方法的局部变量和操作数栈的存储单元映射到验证类型(Verification Types)。 当描述 Class 文件侧的映射时,我们通常使用的术语是“栈映射帧”,而当描述类型检查器侧的映射关系时,我们通常使用的术语是“类型状态”。
  在版本号大于或等于 50.0 的 Class 文件中,如果方法的 Code 属性中没有附带StackMapTable 属性,那就意味着它带有一个隐式的 StackMap 属性。这个 StackMap 属性的作用等同于 number_of_entries 值为 0 的 StackMapTable 属性。一个方法的 Code 属性最多只能有一个 StackMapTable 属性,否则将抛出 ClassFormatError 异常。
  StackMapTable 属性的格式如下:

StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}

  StackMapTable 结构项的说明如下:

  attribute_name_index
    attribute_name_index 项的值必须是对常量池的有效索引,常量池在该索引的项处必须是CONSTANT_Utf8_info(§4.4.7)结构,表示“StackMapTable”字符串。

  attribute_length
    attribute_length 项的值表示当前属性的长度,不包括开始的 6 个字节。
  number_of_entries
    number_of_entries 项的值给出了 entries 表中的成员数量。Entries 表的每个成员是都是一个 stack_map_frame 结构的项。
  entries[]
    entries 表给出了当前方法所需的 stack_map_frame 结构。

  每个 stack_map_frame 结构都使用一个特定的字节偏移量来表示类型状态。每个帧类型(Frame Type)都显式或隐式地标明一个 offset_delta(增量偏移量)值,用于计算每个帧在运行时的实际字节码偏移量。使用时帧的字节偏移量计算方法为:前一帧的字节码偏移量(Bytecode Offset)加上 offset_delta 的值再加 1,如果前一个帧是方法的初始帧
(Initial Frame),那这时候字节码偏移量就是 offset_delta。
  只要保证栈映射帧有正确的存储顺序,在类型检查时我们就可以使用增量偏移量而不是实际的
字节码偏移量。此外,由于堆每一个帧都使用了 offset_delta+1 的计算方式,我们可以确保偏
移量不会重复。
  在 Code 属性的 code[]数组项中,如果偏移量 i 的位置是某条指令的起点,同时这个 Code属性包含有 StackMapTable 属性,它的 entries 项中也有一个适用于地址偏移量 i 的stack_map_frame 结构,那我们就说这条指令拥有一个与之相对应的栈映射帧。
  stack_map_frame 结构的第一个字节作为类型标记(Tag),第一个字节后会跟随 0 或多个字节用于说明更多信息,这些信息因类型标记的不同而变化。
  一个栈映射帧可以包含若干种帧类型(Frame Types):

union stack_map_frame {
same_frame;
same_locals_1_stack_item_frame;
same_locals_1_stack_item_frame_extended;
chop_frame;
same_frame_extended;
append_frame; full_frame;
}

  所有的帧类型,包括 full_frame,它们的部分语义会依赖于前置帧,这点使得确定基准帧(Very First Frame)变得尤为重要,方法的初始帧是隐式的,它通过方法描述符计算得出,详细信息请参考methodInitialStackFrame(§4.10.1.3.3)。
  帧类型 same_frame 的类型标记(frame_type)的取值范围是 0 至 63,如果类型标记所确定的帧类型是 same_frame 类型,则明当前帧拥有和前一个栈映射帧完全相同的 locals[]数组,并且对应的 stack 项的成员个数为 0。当前帧的 offset_delta 值就使用 frame_type 项的值来表示。

same_frame {
u1 frame_type = SAME; /* 0-63 */
}

  帧类型 same_locals_1_stack_item_frame 的类型标记的取值范围是 64 至 127。如果类型标记所确定的帧类型是 same_locals_1_stack_item_frame 类型,则说明当前帧拥有和前一个栈映射帧完全相同的 locals[]数组,同时对应的 stack[]数组的成员个数为 1。当前帧的 offset_delta 值为 frame_type-64。并且有一个 verification_type_info 项跟随在
此帧类型之后,用于表示那一个 stack 项的成员。

same_locals_1_stack_item_frame {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM;/* 64-127 */
verification_type_info stack[1];
}

  范围在 128 至 246 的类型标记值是为未来使用而预留的。
  帧类型 same_locals_1_stack_item_frame_extended 由值为 247 的类型标记表示,它表明当前帧拥有和前一个栈映射帧完全相同的 locals[]数组,同时对应的 stack[]数组的成员个数为 1。当前帧的 offset_delta 的值需要由 offset_delta 项明确指定。有一个 stack[]数组的成员跟随在 offset_delta 项之后。

same_locals_1_stack_item_frame_extended {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED;/* 247 */
u2 offset_delta; verification_type_info stack[1];
}

  帧类型 chop_frame 的类型标记的取值范围是 248 至 250。如果类型标记所确定的帧类型是为 chop_frame,则说明对应的操作数栈为空,并且拥有和前一个栈映射帧相同的 locals[]数组,不过其中的第 k 个之后的 locals 项是不存在的。k 的值由 251-frame_type 确定。

chop_frame {
u1 frame_type = CHOP; /* 248-250 */
u2 offset_delta;
}

  帧类型 same_frame_extended 由值为 251 的类型标记表示。如果类型标记所确定的帧类型是 same_frame_extended 类型,则说明当前帧有拥有和前一个栈映射帧的完全相同的locals[]数组,同时对应的 stack[]数组的成员数量为 0。

same_frame_extended {
u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */
u2 offset_delta;
}

  帧类型 append_frame 的类型标记的取值范围是 252 至 254。如果类型标记所确定的帧类型为 append_frame,则说明对应操作数栈为空,并且包含和前一个栈映射帧相同的 locals[]数组,不过还额外附加 k 个的 locals 项。k 值为 frame_type-251。

append_frame {
u1 frame_type = APPEND; /* 252-254 */
u2 offset_delta;
verification_type_info locals[frame_type - 251];
}

  在 locals[]数组中,索引为 0 的(第一个)成员表示第一个添加的局部变量。如果要从条件“locals[M]表示第 N 个局部变量”中推导出结论“locals[M+1]就表示第 N+1 个局部变量”的话,那就意味着 locals[M]一定是下列结构之一:
  Top_variable_info
  Integer_variable_info
  Float_variable_info
  Null_variable_info
  UninitializedThis_variable_info
  Object_variable_info
  Uninitialized_variable_info
  否则,locals[M+1]就将表示第 N+2 个局部变量。对于任意的索引 i,locals[i]所表示的局部变量的索引都不能大于此方法的局部变量表的最大索引值。
  在 stack[]数组中,索引为 0 的(第一个)成员表示操作数栈的最底部的元素,之后的成员依次靠近操作数栈的顶部。操作数栈栈底的元素对应的索引为 0,我们称之为元素 0,之后元素依次是元素 1、元素 2 等。如果要从条件“stack[M]表示第 N 个元素”中推导出结论“stack[M+1]表示第 N+1 个元素”的话,那就意味着 stack[M]一定是下列结构之一:
  Top_variable_info
  Integer_variable_info
  Float_variable_info
  Null_variable_info
  UninitializedThis_variable_info
  Object_variable_info
  Uninitialized_variable_info
  否则,stack[M+1]将表示第 N+2 个元素,对于任意的索引 i,stack[i]所表示的栈元素索
引都不能大于此方法的操作数的最大深度。
  verification_type_info 结构的第一个字节 tag 作为类型标记,之后跟随 0 至多个字节表示由 tag 类型所决定的信息。每个 verification_type_info 结构可以描述 1 个至 2 个存储单元的验证类型信息。

union verification_type_info {
Top_variable_info;
Integer_variable_info;
Float_variable_info;
Long_variable_info;
Double_variable_info;
Null_variable_info;
UninitializedThis_variable_info;
Object_variable_info;
Uninitialized_variable_info;
}

  Top_variable_info 类型说明这个局部变量拥有验证类型 top(ᴛ)。

Top_variable_info {
u1 tag = ITEM_Top; /* 0 */
}

  Integer_variable_info 类型说明这个局部变量包含验证类型 int

Integer_variable_info {
u1 tag = ITEM_Integer; /* 1 */
}

  Float_variable_info 类型说明局部变量包含验证类型 float

Float_variable_info {
u1 tag = ITEM_Float; /* 2 */
}

  Long_variable_info 类型说明存储单元包含验证类型 long,如果存储单元是局部变量,则要求:
    不能是最大索引值的局部变量。
    按顺序计数的下一个局部变量包含验证类型 ᴛ
  如果单元存储是操作数栈成员,则要求:
    当前的存储单元不能在栈顶。
    靠近栈顶方向的下一个存储单元包含验证类型 ᴛ。
  Long_variable_info 结构在局部变量表或操作数栈中占用 2 个存储单元。

Long_variable_info {
u1 tag = ITEM_Long; /* 4 */
}

  Double_variable_info 类型说明存储单元包含验证类型 double。如果存储单元是局部变量,则要求:
    不能是最大索引值的局部变量。
    按顺序计数的下一个局部变量包含验证类型 ᴛ
  如果单元存储是操作数栈成员,则要求:
    当前的存储单元不能在栈顶。
    靠近栈顶方向的下一个存储单元包含验证类型 ᴛ。
  Double_variable_info 结构在局部变量表或操作数栈中占用 2 个存储单元。

Double_variable_info {
u1 tag = ITEM_Double; /* 3 */
}

  Null_variable_info 类型说明存储单元包含验证类型 null。

Null_variable_info {
u1 tag = ITEM_Null; /* 5 */
}

  UninitializedThis_variable_info 类型说明存储单元包含验证类型uninitializedThis。

UninitializedThis_variable_info {
u1 tag = ITEM_UninitializedThis; /* 6 */
}

  Object_variable_info 类型说明存储单元包含某个 Class 的实例。由常量池在cpool_index 给出的索引处的 CONSTANT_CLASS_Info(§4.4.1)结构表示。

Object_variable_info {
u1 tag = ITEM_Object; /* 7 */
u2 cpool_index;
}

  Uninitialized_variable_info 说明存储单元包含验证类型
  uninitialized(offset)。offset 项给出了一个偏移量,表示在包含此 StackMapTable 属性的 Code 属性中,new 指令创建的对象所存储的位置。

Uninitialized_variable_info {
u1 tag = ITEM_Uninitialized /* 8 */
u2 offset;
}

java虚拟机规范(se8)——class文件格式(六)的更多相关文章

  1. java虚拟机规范(se8)——class文件格式(四)

    4.7 属性 属性用于class文件格式中的ClassFile,field_info,method_info和Code_attribute结构. 所有的属性都是下面的格式: attribute_inf ...

  2. java虚拟机规范(se8)——class文件格式(一)

    第四章 class文件格式 本章介绍了java虚拟机的class文件格式.每一个class文件包含一个单独的类或者接口的定义.虽然类和接口不一定都定义在文件中(比如类和接口亦可以通过类加载器直接生成) ...

  3. java虚拟机规范(se8)——class文件格式(五)

    4.7.1 定义和命名新属性 允许编译器定义和发布的class文件在class文件结构体.field_info结构体.method_info结构体和Code结构体中的attributes表中包含新的属 ...

  4. java虚拟机规范(se8)——class文件格式(三)

    4.5 字段 字段使用field_info结构来描述. 在同一个class文件中的两个字段不能有相同的名称和描述符. 结构的格式如下: field_info { u2 access_flags; u2 ...

  5. java虚拟机规范(se8)——class文件格式(二)

    4.4 常量池 java虚拟机指令并不依赖类.接口.类实例或者数组的运行时布局.相反,指令依靠常量池中的符号信息. 所有的常量池条目都有如下的通用结构: cp_info { u1 tag; u1 in ...

  6. java虚拟机规范(se8)——class文件格式(七)

    4.7.5 Exceptions 属性 Exceptions 属性是一个变长属性,它位于 method_info(§4.6)结构的属性表中. Exceptions 属性指出了一个方法需要检查的可能抛出 ...

  7. java虚拟机规范(se8)——java虚拟机结构(一)

    本文翻译自:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html 第二章 虚拟机结构 本文档描述了一个抽象的虚拟机规范,并不描述 ...

  8. java虚拟机规范(se8)——java虚拟机结构(六)

    2.11 指令集简介 java虚拟机指令由一个字节的操作码,接着时0个或多个操作数组成,操作码描述了执行的操作,操作数提供了操作所需的参数或者数据.许多指令没有操作数只包含一个操作码. 如果忽略异常处 ...

  9. java虚拟机规范(se8)——java虚拟机的编译(四)

    3.12 抛出和处理异常 在程序中使用throw关键字来抛出异常.编译结果很简单. void cantBeZero(int i) throws TestExc { if (i == 0) { thro ...

随机推荐

  1. Opencv识别图中人脸

    #!/usr/bin/python #coding=utf-8 # 识别图片中的人脸 import face_recognition jobs_image = face_recognition.loa ...

  2. Python的pip源切换为国内阿里云镜像

    Python的pip源切换为国内阿里云镜像 找到用户目录 C:\Users\用户\pip,如果不存在就新建该文件夹. 新建文件pip.ini,并用文本编辑器输入以下内容并保存 [global] ind ...

  3. PCB学习

    一.PCB设置 在线DRC:自动更正,会提示短路. 对象捕捉>>智能元件snap,可以智能抓取中心点,勾选 智能TrackEnds: 撤销重做:30步 旋转步骤:90.000(可以按空格旋 ...

  4. c#一些操作

    C# FileStream 按大小分段读取文本内容 using System.IO; namespace FileStreamRead { class Program { static void Ma ...

  5. 企业资源计划(ERP)

    ERP(企业资源计划)一般指企业资源计划(ERP) 物资资源管理(物流).人力资源管理(人流).财务资源管理(财流).信息资源管理(信息流) 信息技术在企业管理学上的应用可分做如下发展阶段:A. MI ...

  6. LOJ 2302 「NOI2017」整数——压位线段树

    题目:https://loj.ac/problem/2302 压30位,a最多落在两个位置上,拆成两次操作. 该位置加了 a 之后,如果要进位或者借位,查询一下连续一段 0 / 1 ,修改掉,再在含有 ...

  7. CVE-2017-0213 | 记一次失败的提权经历

    环境: CVE-2017-0213下载 提权步骤: 提权失败.... 好迷啊,,,,事后查了一下补丁 我的wind7上也没装啊,然后防火墙也是关闭的 迷了迷了....

  8. Linux测试端口的连通性的四种方法

    目录 1.telnet 2.ssh 3.crul 4.wget 方法一.telnet telnet为用户提供了在本地计算机上完成远程主机工作的能力,因此可以通过telnet来测试端口的连通性,具体用法 ...

  9. (转)Docker 网络

    转:https://www.cnblogs.com/allcloud/p/7150564.html 本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker ...

  10. 第二章(1.3)Python基础知识(输入输出)

    一.?输出 用print加上字符串,就可以向屏幕上输出指定的文字 print?'hello, world' print也可以打印整数. >>> print?300 二.?输入 Pyt ...