zoukankan      html  css  js  c++  java
  • (翻译)《Expert .NET 2.0 IL Assembler》 第五章 元数据表的组织 5.2 堆和表

    返回目录

    堆和表

    从逻辑上讲,元数据被表示为一组有名称的流,附带着每个流表示一类的数据。这些流被分为两种类型:元数据堆和元数据表。

    String堆:这种类型的堆包括了0休止符字符的字符串,以UTF-8格式编码。这些字符串直接首尾相接。这个堆的第一个字节总是为0,而且这导致了堆中的第一个字符串总是为空字符串的结果。这个堆的最后一个字节也必须是0(换句话说,堆中的最后一个字符串,就像其它的一样,必须是0休止符)。

    GUID堆:这种类型的堆包括了一些16位的二进制对象,它们是直接首尾相接。这些二进制对象的大小是固定的,因此长度参数和休止符是不需要的。

    Blob堆:这种类型的堆包括了一些任意大小的二进制对象。每个二进制对象都以它的长度(以压缩的形式)作为开始。这些二进制对象在4位的边界上对齐。

    长度压缩公式是相当简单的。如果这个长度(一个无符号的整数)是0x7F或者更少,它就被表示为一个1个字节;如果长度大于0x7F但是不大于0x3FFF,它就被表示为一个2位的无符号的整数,带有一个最重要的位设置。否则,它就被表示为一个4位的无符号的整数,带有两个最重要的位设置。表5-1总结了这个公式。

    5-1 Blob的长度压缩公式

    取值范围

    压缩大小

    压缩值(Big Endian)

    0-0x7F

    1字节

    <value>

    0x80-0x3FF

    2字节

    0x8000|<value>

    0x4000-0x1FFFFFFF

    4字节

    0xC0000000|<value>

    这个压缩公式在元数据中广泛应用。当然,这种压缩只工作于不超过0x1FFFFFFF(536870911)的长度数量,但是这种限制并不是一个问题,因为压缩总是应用于像长度和数量这样的值。

    通用元数据头

    通用元数据头由一个存储签名和一个存储头组成。存储签名是按4位排列的,有如表5-2所描述的结构。

    5-2 元数据存储签名的结构

    类型

    字段

    描述

    DWORD

    lSignature

    物理元数据的“魔术”签名,当前是0x424A5342,或者,被当作BSJB这些字符——4位“founding father”(创立人)Brian Harry、Susan Radke-Sproull、Jason Zander和Bill Evans的首大写字母(我最好称其为“founding”,Susan可能会反对被称为father),他们在1998年开始了运行时的开发工作。

    WORD

    iMajorVer

    主版本(1)

    WORD

    iMinorVer

    次版本(1)

    DWORD

    iExtraData

    保留的;设置为0

    DWORD

    iVersionString

    版本字符串的长度

    BYTE[]

    pVersion

    版本字符串

    存储头紧跟在存储签名之后,被排列在4位的边界上。它的结构是简单的,正如表5-3所示:

    5-3 元数据存储头的结构

    类型

    字段

    描述

    BYTE

    iFlags

    保留的;设置为0

    BYTE

     

    [padding]

    WORD

    iStream

    流的数量

    紧跟在存储头之后的是一个流的头的数组。表5-4描述了一个流的头的结构。

    5-4 元数据流的头的结构

    类型

    字段

    描述

    DWORD

    iOffset

    对于这个流在文件中的偏移量。

    DWORD

    iSize

    流的字节大小。

    char[32]

    rcName

    流的名称;一个以0作为休止符的不大于31个字符(包括0休止符)的ASCII字符串。这个名称可能比较短,在这种情形中,这个流的头的大小会被相应的减少,padded to 4位的边界。

    有六种指定名称的流出现在元数据中:

    #Strings:一个包括了元数据项的字符串堆(类名、方法名,字段名等等)。这个流并不包括在模块的方法中定义或引用的文字常量。

    #blob:一个blob堆包括了内部的元数据二进制对象,比如说默认值、签名等。

    #GUID:一个GUID堆包括了所有类别的全局的唯一标识符。

    #US:一个blob堆包括了用户自定义的字符串。这个流包括了定义在用户代码中的字符串常量。这些字符串以UTD-16的编码格式保存,附带着额外的一个尾部设置为01的字节,用以指出在字符串中是否有大于0x007F的代码字符。这个尾部字节被添加到流线上的在由用户定义的字符串常量生成的字符串对象上的代码转换操作。这个流的最有趣的特征是,这个用户字符串不仅会被任意元数据表引用到,还会显示地被IL代码表明地址(使用ldstr指令)。此外,作为一个实际上的blob堆,US堆不仅可以存储Unicode字符串,还可以存储任意二进制对象,这使那些有趣的实现成为可能。

    #~:一个压缩的(优化的)元数据流。这个流包括了一个优化的由元数据表组成的体系。

    #-:一个未压缩的(未优化的)元数据流。这个流包括了一个未优化的由元数据表组成的体系,它包括了至少一个直接的搜索表(指针表)。

    #~#-流是互斥的——就是说,这个模块的元数据结构是优化的或者未优化的;而不可以在同一时间共存或者在其二者中间的另一个值。如果在流中没有存储任何项,这个流就是无效的,存储头的iStream字段会相应的减少。至少有三个流被确保是存在的:元数据流(#~#-流),字符串流(#Strings),GUID流(#GUID)。元数据项必须至少存在于一个最小程度的配置中,即使是位于一个最不重要的模块中,并且这些元数据项必须具有名称和GUID

    5-3解释了元数据的通用结构。在图5-4中,你可以看到流陂其它流引用的方式,如同被外部的“消费者”引用一样,例如元数据APIIL代码。

    5-3 元数据的通用结构

    5-4 流的引用

     

    元数据表流

    元数据流#~#-开始于在表5-5中描述的头。

    5-5 元数据表流的头结构

    大小

    字段

    描述

    4字节

    Reserved

    保留的;设置为0

    1字节

    Major

    表格式的主版本(对于1.0和1.1版本是1;对于2.0版本是2)

    1字节

    Minor

    表格式的次版本(对于所有版本都是0)。

    1字节

    Heaps

    二进制标记,指出了在堆中使用的偏移量的大小。0x01用来表示字符串堆中的一个4字节无符号整数偏移量,0x02表示GUID堆,0x04表示blob堆。如果没有设置这个标记,相应的堆偏移量是一个2字节无符号整数。Stream流也可以有一个特殊的标记设置:0x02标记,指出了这个流只包括在“编辑并继续”会话期间发生的改变;而0x80标记,指出了元数据可能包括了标记为删除的项。

    1字节

    Rid

    元数据的所有表的最大记录索引的位宽;在运行期计算(在元数据流初始化期间)。

    8字节

    MaskValid

    由存在表组成的位向量,每一位表示一个相应的表(如果存在则为1)。

    8字节

    Sorted

    由分类表组成的位向量,每一位表示一个相应的表(如果分类则为1)。

    紧跟在这个头后面的是一个顺序排列的4字节无符号整数,指出了在每个表中的MaskValid位相量上标记为1的记录数量。

    像其它数据库一样,元数据有一个schema。这个schema是一个由元数据的表和列的描述符组成的体系——就这种意义来说,它是“元数据的元数据”。Schema不是元数据的一部分,也不是托管PE文件的特性;而是CLR的一个特性并且是硬编码的。它不会发生改变只有在对运行时的主版本的彻底检查并且即使在它增长性改变时(正如它在CLR1.02.0版本中改变),通过添加新表并保留那些旧的没有改变的表。

    每个元数据表都有一个描述在表5-6中的结构的描述符。

    5-6 元数据表描述符的结构

    类型

    字段

    描述

    pointer

    pColDefs

    指向一个由列描述符组成的数组的指针

    BYTE

    cCols

    表中列的数量

    BYTE

    iKey

    关键列的索引

    WORD

    cbRec

    表中一个记录的大小

    列描述符,也就是表描述符的pColDefs字段所指向的,具有在表5-7中描述的结构。

    5-7 元数据表的列描述符的结构

    类型

    字段

    描述

    BYTE

    Type

    这列代码的类型

    BYTE

    oColumn

    列的偏移量

    BYTE

    cbColumn

    列的字节大小

    类型,列描述符的第一个字段,是尤其有趣的。CLR现有的发布版本中的元数据schema识别了在表5-8中描述的列的类型的编码。

    5-8 元数据表的列的类型编码

    编码

    描述

    0-63

    这个列保存了在另一个表中的记录索引值(RID);这个特定的编码值指出了是哪一个表。在这个列只是引用来自一个表中的记录时,RID被用作列的类型。这个列的宽度定义在元数据流头的Rid字段中。

    64-95

    这个列保存了指向另一个表的编码符号;这个特定的编码值指出了该编码符号的类型。符号是一些引用,携带着表和被引用的记录的索引。在这个列引用来自多于一个表中的记录时,符号被用作列的类型。被寻址的表和记录的索引都由编码符号值来定义。

    96

    这个列保存了一个2字节的有符号整数。

    97

    这个列保存了一个2字节的无符号整数。

    98

    这个列保存了一个4字节的有符号整数。

    99

    这个列保存了一个4字节的无符号整数。

    100

    这个列保存了一个1字节的无符号整数。

    101

    这个列保存了在字符串堆中的偏移量(#Strings流)。

    102

    这个列保存了在GUID堆中的偏移量(#GUID流)。

    103

    这个列保存了在blob堆中的偏移量(#Blob流)。

    元数据的schema定义了45个表。一旦给定RID类型编码的范围,CLR就会有明确的增长空间。此刻,下面的表就会被定义:

    [0]Module:当前模块的描述符。

    [1]TypeRef:引用类的描述符。

    [2]TypeDef:类或接口定义的描述符。

    [3]FieldPtr:一个从类映射到字段的搜索表,该表在优化过的元数据(#~流)中并不存在。

    [4]Field:字段定义的描述符。

    [5]MethodPtr:一个从类映射到方法的搜索表,该表在优化过的元数据(#~流)中并不存在。

    [6]Method:方法定义的描述符。

    [7]ParamPtr:一个从方法映射到参数的搜索表,该表在优化过的元数据(#~流)中并不存在。

    [8]Param:参数定义的描述符。

    [9]InterfaceImpl:接口定义的描述符。

    [10]MemberRef:成员(字段或方法)引用描述符。

    [11]Constant:常量值的描述符,将存储在#Blog流中的默认值映射到相应的字段、参数和属性。

    [12]CustomAttribute:自定义特性的描述符。

    [13]FieldMarshal:用于托管/非托管互操作的字段或参数分组的描述符。

    [14]DeclSecurity:安全描述符。

    [15]ClassLayout:类布局的描述符,保存关于加载器应该如何展开相应的类的信息。

    [16]FieldLayout:字段布局的描述符,详细指出了独立字段的偏移量和序数。

    [17]StandAloneSig:独立的签名的描述符。签名本质上用于两种场合中:作为本地的方法变量的混合签名,以及作为间接(calli)调用IL指令的参数

    [18]EventMap:一个类映射到事件的表。这不是一个直接搜索表,并确实存在于优化的元数据中。

    [19]EventPtr:一个由事件的MAP映射到事件的搜索表,它不存在于优化的元数据中(#~流)。

    注:MAP,由LINK工具生成的一种文本文件,其中包含有被连接的程序的某些信息,例如程序中的组信息和公共符号信息等。

    [20]Event:事件描述符。

    [21]PropertyMap:一个由类映射到属性的表。这不是一个直接搜索表,并确实存在于优化的元数据中。

    [22]PropertyPtr:一个由属性的MAP映射到属性的搜索表,它不存在于优化的元数据中(#~流)。

    [23]Property:属性描述符。

    [24]MethodSemantics:方法的语义描述符,保存了关于哪个方法关联着一个特定的属性或事件以及在相应的什么场合。

    [25]MethodImpl:方法实现的描述符。

    [26]ModuleRef:模块引用的描述符。

    [27]TypeSpec:类型规范的描述符。

    [28]ImplMap:实现映射的描述符,用于由托管/非托管代码互操作组成的P/Invoke类型。

    [29]FieldRVA:字段映射到数据的描述符。

    [30]ENCLogEdit-and-Continue日志的描述符,保存了关于在内存编辑期间对特定的元数据项作了哪些修改的信息。这个表不存在于优化的元数据(#~流)中。

    [31]ENCMapEdit-and-Continue映射的描述符。这个表不存在于优化的元数据(#~流)中。

    [32]Asssembly:当前编译集的描述符,只能出现在主模块的元数据(#~流)中。

    [33] AssemblyProcessor:这个表没有使用到。

    [34]AssemblyOS:这个表没有使用到。

    [35]AssemblyRef:编译集引用的描述符。

    [36]AssemblyRefProcessor:这个表没有使用到。

    [37]AssemblyRefOS:这个表没有使用到。

    [38]File:文件的描述符,包括了关于当前编译集中其它文件的信息。

    [39]ExportedType:输出类型的描述符,包括了关于被当前编译集输出的公共类的信息,这将在这个编译集的其他模块中声明。只有编译集的主模块应该携带这个表。

    [40]ManifestResource:托管资源的描述符。

    [41]NestedClass:嵌套类的描述符,提供了从嵌套类到它们相应的密闭类的映射。

    [42]GenericParam:类型参数的描述符,用于泛型(参数化的)类和方法。

    [43]MethodSpec:泛型方法实例化的描述符。

    [44]GenericParamConstraint:由特定约束组成的描述符,用于泛型类和方法的类型参数。

    最后三个表是在CLR2.0版本中新添加的。它们在1.01.1版本中并不存在。

    我将在后面的章节讨论各种表及其验证规则的结构化方面,伴随着相应的ILAsm的构造器。

  • 相关阅读:
    浏览器基础知识点及常考面试题
    java设计模式之综述
    maven的基本原理和使用
    maven的介绍和安装
    Spring整合Struts2的方法
    Spring整合Hibernate的方法
    Spring中的事务管理
    Spring中的JDBC操作
    基于XML配置的Sping AOP详解
    基于注解的Sping AOP详解
  • 原文地址:https://www.cnblogs.com/Jax/p/1276372.html
Copyright © 2011-2022 走看看