由于我们会在后续的文章介绍class验证环节,其中在校验方法时需要使用到StackMap.那么什么是StackMap呢?

从Java 6开始,JVM规范有一个更新文档,JSR 202,里面提到一种新的字节码校验算法,“类型检查”;在此之前是用“类型推导”的算法。为了支持新算法,Class文件从版本50开始添加了一个新的属性表,叫做StackMapTable,里面记录的是一个方法中操作数栈与局部变量区的类型在一些特定位置的状态。

在版本号大于或等于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];
}

其中各项的含义如下:

  • 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;
}  same_frame {  u1 frame_type = SAME;/* 0-63 */
}  same_locals_1_stack_item_frame {  u1 frame_type = SAME_LOCALS_1_STACK_ITEM;/* 64-127 */  verification_type_info stack[1];
}  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 {  u1 frame_type=CHOP; /* 248-250 */  u2 offset_delta;
}  same_frame_extended {  u1 frame_type = SAME_FRAME_EXTENDED;/* 251*/  u2 offset_delta;
}  append_frame {  u1 frame_type = APPEND; /* 252-254 */  u2 offset_delta;  verification_type_info locals[frame_type -251];
}  full_frame {  u1 frame_type = FULL_FRAME; /* 255 */  u2 offset_delta;  u2 number_of_locals;  verification_type_info locals[number_of_locals];  u2 number_of_stack_items;  verification_type_info stack[number_of_stack_items];
}

这里需要提的是,在kvm内部使用的是栈类型的数据结构为,关于这点,是本人看kvm源码总结出来.关于相关文档并没有找到:

kvm_frame {  u2 offset_delta;  u2 number_of_locals;  verification_type_info locals[number_of_locals];  u2 number_of_stack_items;  verification_type_info stack[number_of_stack_items];
}

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结构在局部变量表或操作数栈中占用2个存储单元。
Long_variable_info {
u1 tag = ITEM_Long; /* 4 */
} // 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;
}

基本块

关于栈帧的形成,是在每个基本块开始的位置生成。

一个“基本块”(basic block)就是一个方法中的代码最长的直线型一段段代码序列。“直线型”也就是说代码序列中除了末尾之外不能有控制流(跳转)指令。

一个基本块的开头可以是方法的开头,也可以是某条跳转指令的跳转目标;
一个基本块的结尾可以是方法的末尾,也可以是某条跳转指令(Java中就是goto、if*系列等;invoke*系列的方法调用指令不算在跳转指令中).

如果一个方法代码如下:

public class Foo {  public void foo() {  // basic block 1 start  int i = 0;  int j = 0;  if (i > 0) { // basic block 1 end  // basic block 2 start  int k = 0;  // basic block 2 end  }  // basic block 3 start  int l = 0;  // basic block 3 end  }
}

那么可以看到就有3个基本块。不过在Java Class文件里StackMapTable关心的是类型检查,为了进一步压缩这个表的大小,使用的基本块定义比通常的定义要更宽松些:一个条件跳转的直落分支与条件跳转前的代码算在同一个基本块内。于是前面的例子就变成:

public class Foo {  public void foo() {  // basic block 1 start  int i = 0;  int j = 0;  if (i > 0) {  int k = 0;  // basic block 1 end  }  // basic block 2 start  int l = 0;  // basic block 2 end  }
}

这个方法就会有一个StackMapTable属性表,其中有一个stack frame map记录(本来应该是两个,但第一个是隐式的,不记录在属性表里)。

public void foo();  Code:  Stack=1, Locals=4, Args_size=1  /* basic block 1 start */  0:   iconst_0  1:   istore_1  2:   iconst_0  3:   istore_2  4:   iload_1  5:   ifle    10  8:   iconst_0  9:   istore_3  /* basic block 1 end */  /* basic block 2 start */  10:  iconst_0 /* stack frame map 1 refers to here */  11:  istore_3  12:  return  /* basic block 2 end */  LocalVariableTable:  Start  Length  Slot  Name   Signature  10      0      3    k       I  0      13      0    this       LFoo;  2      11      1    i       I  4      9      2    j       I  12      1      3    l       I  StackMapTable: number_of_entries = 1  frame_type = 253 /* append */  offset_delta = 10  locals = [ int, int ]

隐式的第一个基本块的stack frame map是从方法签名计算出来的。这个例子foo是个实例方法,没有显示声明的参数,所以参数个数是1,也就是隐藏参数this。那么在字节码偏移量0的位置上,操作数栈为空,
局部变量区:[ Foo ]

下一个基本块从字节码偏移量10开始。此处变量k已经过了作用域,所以局部变量区的有效内容应该是:
局部变量区:[ Foo, int, int ]
这就比前一个基本块开头处的状态多了2个局部变量,类型分别是[ int, int ],所以就有了上面对应的StackMapTable项了,253 - 251 = 2。


kvm 实现

上文这是介绍了一下理论,在kvm内部是通过定义如下数据结构来实现的

struct pointerListStruct {long  length;cellOrPointer data[1];
};

其在分配内存的时候,其最终分配的大小为: 2 * number_of_entries.

其最终的内存布局如下:

kvm读取StackMap属性

kvm读取StackMap属性,是在loadCodeAttribute方法中调用loadStackMaps实现的.代码如下:


unsigned short codeAttrNameIndex = loadShort(ClassFileH); // 读取name index
unsigned int   codeAttrLength    = loadCell(ClassFileH); // 读取AttrLength
if (!strcmp(codeAttrName, "StackMap")) {unsigned int stackMapAttrSize;if (!needStackMap) {raiseExceptionWithMessage(ClassFormatError,KVM_MSG_DUPLICATE_STACKMAP_ATTRIBUTE);}needStackMap = FALSE;// 真正读取stackMapAttrSize = loadStackMaps(ClassFileH, thisMethodH);if (stackMapAttrSize != codeAttrLength) {raiseExceptionWithMessage(ClassFormatError,KVM_MSG_BAD_ATTRIBUTE_SIZE);}
}

loadStackMaps的代码如下:

static long
loadStackMaps(FILEPOINTER_HANDLE ClassFileH, METHOD_HANDLE thisMethodH)
{long bytesRead;INSTANCE_CLASS CurrentClass = unhand(thisMethodH)->ofClass;START_TEMPORARY_ROOTS// 1. 读取 number_of_entriesunsigned short nStackMaps = loadShort(ClassFileH);// 2. 分配内存DECLARE_TEMPORARY_ROOT(POINTERLIST, stackMaps,(POINTERLIST)callocObject(SIZEOF_POINTERLIST(2*nStackMaps),GCT_POINTERLIST));METHOD thisMethod = unhand(thisMethodH); /* Very volatile */// 为stackMap,分配内存,大小为(maxStack+frameSize + 2)* 4,这里保存verification_type_infounsigned tempSize = (thisMethod->u.java.maxStack + thisMethod->frameSize + 2);DECLARE_TEMPORARY_ROOT(unsigned short*, stackMap,(unsigned short *)mallocBytes(sizeof(unsigned short) * tempSize));unsigned short stackMapIndex;stackMaps->length = nStackMaps;unhand(thisMethodH)->u.java.stackMaps.verifierMap = stackMaps;bytesRead = 2;/*这里读取的数据结构如下:* kvm_frame {u2 offset_delta;u2 number_of_locals;verification_type_info locals[number_of_locals];u2 number_of_stack_items;verification_type_info stack[number_of_stack_items];}**/for (stackMapIndex = 0; stackMapIndex < nStackMaps; stackMapIndex++) {unsigned short i, index;thisMethod = unhand(thisMethodH);/* Read in the offset */stackMaps->data[stackMapIndex + nStackMaps].cell =loadShort(ClassFileH); // 读取offset_delta 在stackMaps的后面保存bytesRead += 2;/*这里通过循环读取如下数据: *   u2 number_of_locals;verification_type_info locals[number_of_locals];u2 number_of_stack_items;verification_type_info stack[number_of_stack_items];*/for (index = 0, i = 0 ; i < 2; i++) {unsigned short j;unsigned short size = loadShort(ClassFileH); // number_of_locals,number_of_stack_itemsunsigned short size_delta = 0;unsigned short size_index = index++;unsigned short maxSize = (i == 0 ? thisMethod->frameSize: thisMethod->u.java.maxStack);bytesRead += 2;// 读取verification_type_infofor (j = 0; j < size; j++) {unsigned char stackType = loadByte(ClassFileH);bytesRead += 1;/* We are reading the j-th element of the stack map.* This corresponds to the value in the j + size_delta'th* local register or stack location** j + size_delta 对应的是local registe 或者是stack*/if (j + size_delta >= maxSize) {raiseExceptionWithMessage(ClassFormatError,KVM_MSG_BAD_STACKMAP);} else if (stackType == ITEM_NewObject) {/** Uninitialized_variable_info说明存储单元包含验证类型uninitialized(offset)。* offset项给出了一个偏移量,表示在包含此StackMapTable属性的Code属性中,new指令创建的对象所存储的位置* Uninitialized_variable_info {  u1 tag = ITEM_Uninitialized ;*   u2 offset; }*/unsigned short instr = loadShort(ClassFileH); // offsetbytesRead += 2;if (instr >= thisMethod->u.java.codeLength) {raiseExceptionWithMessage(ClassFormatError,KVM_MSG_BAD_NEWOBJECT);}stackMap[index++] = ENCODE_NEWOBJECT(instr);} else if (stackType < ITEM_Object) {/** 数据类型*/stackMap[index++] = stackType;if (stackType == ITEM_Long || stackType == ITEM_Double){if (j + size_delta + 1 >= maxSize) {raiseExceptionWithMessage(ClassFormatError,KVM_MSG_BAD_STACKMAP);}stackMap[index++] = (stackType == ITEM_Long)? ITEM_Long_2: ITEM_Double_2;size_delta++;}} else if (stackType == ITEM_Object) {/** Object_variable_info类型说明存储单元包含某个Class的实例。由常量池在cpool_index给出的索引处的CONSTANT_CLASS_Info(§4.4.1)结构表示*/unsigned short classIndex = loadShort(ClassFileH);CONSTANTPOOL ConstantPool = CurrentClass->constPool;CLASS clazz;bytesRead += 2;verifyConstantPoolEntry(CurrentClass,classIndex, CONSTANT_Class);clazz = CP_ENTRY(classIndex).clazz;stackMap[index++] = clazz->key;} else {raiseExceptionWithMessage(ClassFormatError,KVM_MSG_BAD_STACKMAP);}}stackMap[size_index] = size + size_delta; // 这里实际保存的是verification_type_info所占用的大小}/**  检查是否有重复* */for (i = 0; ; i++) {if (i == stackMapIndex) {/* 此时没有之前的stackMap是重复的,则需要分配内存,进行数据复制 */char *temp = mallocBytes(index * sizeof(unsigned short));memcpy(temp, stackMap, index * sizeof(short));stackMaps->data[stackMapIndex].cellp = (cell*)temp;break;} else {unsigned short *tempMap =(unsigned short *)stackMaps->data[i].cellp;/*   这里获取的是Loacl的长度*/unsigned short tempLen = tempMap[0];/*  这里获取的是Loacl的长度 */unsigned short mapLen = stackMap[0];/*  tempMap[tempLen + 1] 获得是stack的长度,长度 数据数据数据数据数据数据 长度 数据数据数据数据数据数据, tempMap[0] +tempMap[tempLen + 1]* 只是计算了两部分数据的长度,但是没有计算2个记录数据长度的大小.由于tempLen 的类型为unsigned short,因此+2正好可以加上2个记录数据长度的大小 */tempLen += tempMap[tempLen + 1] + 2;mapLen += stackMap[mapLen + 1] + 2;/*  如果长度相同的话且内容相同的话,则直接引用即可.节省内存*/if (mapLen == tempLen &&memcmp(stackMap, tempMap,mapLen * sizeof(unsigned short)) == 0) {/* We have found a duplicate */stackMaps->data[stackMapIndex].cellp = (cell*)tempMap;break;}}}}END_TEMPORARY_ROOTSreturn bytesRead;
}

关于该方法,注释的内容较详细,就不展开了


参考资料

本文的内容参考如下书籍,文章:

  1. JVM 规范, (java se 7, java se 8),涉及 StackMap的章节

  2. 能介绍一下StackMapTable属性的运作原理吗?

StackMap属性解析相关推荐

  1. element 往node里面增加属性值_【Vue原理】Compile - 源码版 之 Parse 属性解析

    写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧 研究基于 Vue版本 [2.5.17] 如果你觉得排版难 ...

  2. Android常见XML属性解析

    常见XML属性解析 属性 描述 android:id android:id的设置,通常有三种方式,详见下文 android:layout_width 控件宽度 android:layout_heigh ...

  3. PE知识复习之PE的各种头属性解析

    PE知识复习之PE的各种头属性解析 一丶DOS头结构体 typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // M ...

  4. Maven配置文件(setting.xml)属性解析

    前置信息 Maven 版本:apache-maven-3.5.2 Nexus 信息:nexus2.nexus3 镜像仓库:如果仓库 X 可以提供仓库 Y 存储的所有的内容,那么仓库 X 就可以说是 仓 ...

  5. Django Meta元数据类属性解析

    Django Meta元数据类属性解析 Model 是 Django ORM 的核心,它有许多特性,比如我们提到过的模型类继承,还有未讲到过的的元数据.每个 Model 都是一个 Python 类,且 ...

  6. springMVC自定义方法属性解析器

    使用场景例子: 用户登陆系统一般会往Session里放置一个VO对象,然后在controller里会来获取用户的userId等信息. 之前的写法是:@SessionAttributes配合@Model ...

  7. FLASH中button组件的selected和toggle属性解析

    在flash中,button组件具有以下属性: 分别为:emphasized.enabled.label.lablePlacement.selected.toggle.visible 下面详细对sel ...

  8. avdd-supply and vdd_io-supply两个属性解析调用regulator_get(dev, “vdd_io“)

    Touch panel DTS 分析(MSM8994平台,Atmel 芯片) 在MSM8994平台下,Touch panel的DTS节点写在/kernel/arch/arm/boot/dts/qcom ...

  9. Objective-C property属性解析

    @interface - @property (原子性,可写性,内存管理) id name; @end 原子性:    nonatomic, atomic   默认atomic 可写性:    rea ...

  10. 【思科】BGP的community属性解析

    BGP的community是一种路由标记方法,用于确保路由过滤和选择的连续性,并且具有可传递性. 实验拓扑: 实验需求: 1.在R1上设置11.0/24 community属性值100:11,将属性传 ...

最新文章

  1. requireJS的基本使用
  2. 学号 20175223 《Java程序设计》第4周学习总结
  3. 各个企业创始人记录--【持续更新中!!!】
  4. mysql boost 5.7.21_mysql 5.7.21 安装配置方法图文教程(window)
  5. shutil.rmtree()
  6. 教你正确把加减乘除运用在MySQL里面
  7. python -sorted 学习
  8. XJTUOJ13 (数论+FFT)
  9. java例程练习(东软笔试题——n阶平面魔方)
  10. 算法:回溯九 Plus在数字字符串中加入加号,求所有情况的和
  11. 深度解读:2021 中国低代码平台发展现状
  12. linux开发板系统备份
  13. USB网卡收发数据分析
  14. BeautifulSoup总结及contents内容分析
  15. Python基于深度学习多标签分类模型实现云状识别
  16. Linux刻录光盘win10认不到,win10系统刻录光盘光驱无法识别光盘的具体方法
  17. c# .NET 使用MVC控制器导出Excel并打开
  18. 一个跨界程序员:不务正业的这几年,我如何让自己的收入翻了十倍(转)
  19. zoho在线文档使用小技巧
  20. (微软官方工具)局域网键鼠共享工具:Mouse without borders

热门文章

  1. 路由器当交换机用的设置方法
  2. 使用PIE-Engine探寻地球灯光蕴藏的秘密
  3. Intent.ACTION_VIEW
  4. 笔记73-应用容器Docker
  5. 判断邮箱正确的c语言代码,如何用c语言来识别电子邮箱是否正确
  6. 数据分析系列:Z 检验和 T 检验的应用及代码实现
  7. 发现一个识图比较厉害的网站
  8. ARP报文目的MAC为什么不是广播地址?
  9. 谷歌分析数据导入4种方式
  10. 节选自周国平《风中的纸屑》里的一段话