首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第36期->技术专题
期刊号: 类型: 关键词:
MS.Net CLR扩展PE结构分析 (3)

作者:Flier Lu <flier@nsfocus.com>
主页:http://www.nsfocus.com
日期:2002-11-01

6.4 类型成员定义

  在介绍了类型定义之后,我们来看看类型的成员定义的组织形式。
本节将依次介绍字段、方法、属性和事件,以及它们之间的关联性。

6.4.1 字段

  字段是类型的一种成员,保存着值类型的一个实例或引用类型的引用。

6.4.1.1 ttFieldDef ($04)

  字段类型在Metadata中的定义非常简单,但功能非常强大,
实现了普通类字段、静态类字段、常量乃至某些语言的全局变量等特性的实现。

constructor TJclClrTableFieldRow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FFlags           := Table.ReadWord;
  FNameOffset      := Table.ReadIndex(hkString);
  FSignatureOffset := Table.ReadIndex(hkBlob);
  FParentToken     := nil;
end;

  Name定义了字段的名称,Signature定义了字段的类型以及相关详细定义。
Signature的概念在上一篇文章中我们已经做了介绍,这里不再罗嗦,集中精力
看看Flags是如何让字段这个简单概念的应用变得丰富多彩起来的。
  首先是字段的可见性,与类型的可见性类似,可以分为一下几类

  fdPrivateScope              =   $0000;     // Member not referenceable.
  fdPrivate                   =   $0001;     // Accessible only by the
parent type.
  fdFamANDAssem               =   $0002;     // Accessible by sub-types only
in this Assembly.
  fdAssembly                  =   $0003;     // Accessibly by anyone in the
Assembly.
  fdFamily                    =   $0004;     // Accessible only by type and
sub-types.
  fdFamORAssem                =   $0005;     // Accessibly by sub-types
anywhere, plus anyone in assembly.
  fdPublic                    =   $0006;     // Accessibly by anyone who has
visibility to this scope.

  除fdPrivateScope这种编译器内部使用的可见性以外,和上次介绍的类型
可见性基本相同。
  然后是字段所独有的特性

  fdStatic                    =   $0010;     // Defined on type, else per
instance.
  fdInitOnly                  =   $0020;     // Field may only be
initialized, not written to after init.
  fdLiteral                   =   $0040;     // Value is compile time
constant.
  fdNotSerialized             =   $0080;     // Field does not have to be
serialized when type is remoted.

  fdSpecialName               =   $0200;     // field is special.  Name
describes how.

  fdPinvokeImpl               =   $2000;     // Implementation is forwarded
through pinvoke.

  fdRTSpecialName             =   $0400;     // Runtime(metadata internal
APIs) should check name encoding.
  fdHasFieldMarshal           =   $1000;     // Field has marshalling
information.
  fdHasDefault                =   $8000;     // Field has default.
  fdHasFieldRVA               =   $0100;     // Field has RVA.

  标志位fdStatic定义此字段是静态成员变量,是类一级的成员。
反之如果不定义此位,则字段是每个类实例或者说对象一个的成员。
  值得注意的是,如果一个字段的所有者(由TypeDef的FieldList定义)
是名为"<Module>"的特殊名字类型,则此字段的作用域在整个模块,
也就是我们通常意义上的全局变量。相应后面将介绍的方法的定义中,
也使用此机制定义全局函数。

  标志位fdInitOnly定义此字段只能被初始化,初始化结束后就不可修改
在C#中可以通过readonly关键字定义此类字段,并在构造函数中进行赋值。
  标志位fdLiteral定义此字段是编译期就确定值的常量。如果此标志位被
置位则相应fdStatic也应该置位,因为常量(const)只能是类一级定义的。
  这里值得一提的是常量与只读字段的区别。前者往往被认为是类型的一部分,
是在编译期就确定了,并将值嵌入代码中;后者则是行为上类似常量,
但初始值在运行期决定,与普通字段没有本质上的区别。
  例如你在一个Assembly里面定义了一个常量和一个只读字段,而另一个
Assembly里面使用到这两个定义,此时如果你重新修改、编译定义所在Assembly
则使用定义的Assembly中,常量值无法更新(编译时已经获取并固化进代码)
而只读字段可以很方便地自动获得新值。

  标志位fdNotSerialized定义此字段不进行序列化操作,仅当字段所属类型
支持序列化时,此标志才有效。

  fdSpecialName和fdRTSpecialName标志位是用于标示特殊名称字段的。
例如定义一个Enum类型如下:
  enum EnumClass
  {
        black = 1,
        white = 0
  };
  此EnumClass类型的FieldList就会有三个Field,其中有一个名称为"value__"
的字段,被设置specialname和rtspecialname标志,因为此字段是一个
名称特殊的内部使用字段,表示此enum。此外还有两个常量字段表示Enum的不同值。

  此外fdHasFieldMarshal、fdHasDefault和fdHasFieldRVA标志位,
表示此字段在ttFieldMarshal、ttConstant和ttFieldRVA表中有相应条目,
等会我们介绍这几类表时再详细讨论。

  最后fdPinvokeImpl标志位表示此字段由Native Code实现,一般在与
现有DLL或COM组件进行互操作时使用。

6.4.1.2 ttConstant ($0b)

  所有有缺省值即fdHasDefault标志位置位的字段,在Constant表中都有
相应的记录,表示此字段的缺省值

constructor TJclClrTableConstantRow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FKind         := Table.ReadByte;
                   Table.ReadByte; // padding zero
  FParentIdx    := Table.ReadIndex([ttParamDef, ttFieldDef, ttPropertyDef]);
  FValueOffset  := Table.ReadIndex(hkBlob);
end;

  Kind表示值得类型;
  Parent指向拥有此缺省值的成员类型,可以是字段、参数或属性;
  Value指向Blob堆,存储值的实际数据。

  注意此表虽然命名为Constant,但并非仅仅为常量服务,而是所有带缺省值的
字段、参数或属性共用。常量在CLR中仅仅是一种设置fdStatic、fdLiteral和
fdHasDefault等等标志位的字段而已。

6.4.1.3 ttClassLayout ($0f) 和 ttFieldLayout ($10)

  上次在介绍类型定义时,曾经接触过类型的内存布局这个概念,这里我们从实现
角度来看看内存布局的真实面目。
  首先是ttClassLayout表,定义类型一级的内存布局信息,如下

constructor TJclClrTableClassLayoutRow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FPackingSize := Table.ReadWord;
  FClassSize   := Table.ReadDWord;
  FParentIdx   := Table.ReadIndex([ttTypeDef]);
end;

  PackingSize定义内存对齐的字节数;ClassSize定义类型数据的绝对大小。
可以参看System.Runtime.InteropServices.StructLayoutAttribute
属性中的Pack和Size属性。
  Parent则表示此内存布局应用于那个类型。

  在字段一级,则通过ttFieldLayout表给予用户精确到每字段每字节的定义权力。

constructor TJclClrTableFieldLayoutRow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FOffset   := Table.ReadDWord;
  FFieldIdx := Table.ReadIndex([ttFieldDef]);
end;

  Offset表示此字段数据的起始位置相对于所在类型的内存偏移。
  Field表示此字段布局应用于哪个字段

6.4.1.4 ttFieldMarshal ($0d)

  ttFieldMarshal表将Field或Param表中的一项,与Blob堆中一个信息链接起来。

constructor TJclClrTableFieldMarshalRow.Create(
  const ATable: TJclClrTable);
begin
  inherited;

  FParentIdx        := Table.ReadIndex([ttFieldDef, ttParamDef]);
  FNativeTypeOffset := Table.ReadIndex(hkBlob);
end;

  Parent定义此Marshal作用的字段或参数,NativeType则指向Blob堆中一个定义。
Marshal操作一般发生在托管代码与未托管代码如DLL或COM组件进行交互式,或者是在
进行远程调用时也可能用到。因为CLR中的类型非常丰富,但其它语言或规范如COM的IDL中
无法表项,因而在传递参数和使用字段时,需要将CLR的类型转换为指定的Marshal类型。
以后有机会介绍CLR与非托管环境互操作的实现时再详细解说。

6.4.1.5 ttFieldRVA ($1d)

  ttFieldRVA表用于在PE映像文件中定义静态字段的数据

constructor TJclClrTableFieldRVARow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FRVA      := Table.ReadDWord;
  FFieldIdx := Table.ReadIndex([ttFieldDef]);
end;

  Field定义使用此数据的字段;RVA定义数据所在位置相对PE头的偏移。
在IL汇编中可以通过.data指令定义此类值,如

.data theInt = int32(123)
.field public static int32 myInt at theInt

  定义了一个int32类型静态字段myInt使用theInt标签所指向的数据。

6.4.1.6 小节

  在了解了字段及其相关表定义后,我们来看看他们之间的关系,如下

                      +-------------+
                      | FieldLayout |
                      +-------------+   +----------+
                           1 ^          | Constant |
      +----------+           |   +----->+----------+
      | TypeDef  |         1 |   |1    1
      +----------+ 1   * +----------+
      |Field List|------>| FieldDef |
      +----------+       +----------+
                           1 |   |1    1
                             |   +----->+----------+
                           1 v          | FieldRVA |
                      +--------------+  +----------+
                      | FieldMarshal |
                      +--------------+

6.4.2 方法

  类一级表定义的是结构、字段及其相关表定义的是数据,而Method表定义的则是真正的代码实现。

6.4.2.1 ttMethodDef ($06)

  Method表定义了CLR中所有方法的相关属性,从普通的类成员函数到特殊的构造函数等,
从运算符重载函数到属性的实现,都是通过此表在内部定义。

constructor TJclClrTableMethodDefRow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FRVA          := Table.ReadDWord;
  FImplFlags    := Table.ReadWord;
  FFlags        := Table.ReadWord;
  FNameOffset   := Table.ReadIndex(hkString);
  FSignatureOffset := Table.ReadIndex(hkBlob);
  FParamListIdx := Table.ReadIndex([ttParamDef]);

  FParentToken  := nil;
  FParams       := nil;
end;

  RVA指向一个COR_ILMETHOD结构,定义Method的主体(Body)部分,我们等会详细解释。
  ImplFlag和Flags都是标志位,定义方法的属性,顾名思义前者是方法实现上的属性,
后者则包含其它方面;NameOffset指向String堆中的方法名称字符串;
SignatureOffset类似前面Field中的Signature,存储方法的详细定义信息;
ParamList指向Param表中的一个索引,定义方法的参数列表。

  我们首先还是来看看方法的标志位的定义
  Flag的低三位是一个枚举值,表示方法的可见性,和TypeDef中相同,
可以在CorHdr.h中找到CorMethodAttr枚举类型定义

// MethodDef attr bits, Used by DefineMethod.
typedef enum CorMethodAttr
{
    // member access mask - Use this mask to retrieve accessibility
information.
    mdMemberAccessMask          =   0x0007,
    mdPrivateScope              =   0x0000,     // Member not referenceable.
    mdPrivate                   =   0x0001,     // Accessible only by the
parent type.
    mdFamANDAssem               =   0x0002,     // Accessible by sub-types
only in this Assembly.
    mdAssem                     =   0x0003,     // Accessibly by anyone in
the Assembly.
    mdFamily                    =   0x0004,     // Accessible only by type
and sub-types.
    mdFamORAssem                =   0x0005,     // Accessibly by sub-types
anywhere, plus anyone in assembly.
    mdPublic                    =   0x0006,     // Accessibly by anyone who
has visibility to this scope.

    // method contract attributes.
    mdStatic                    =   0x0010,     // Defined on type, else per
instance.
    mdFinal                     =   0x0020,     // Method may not be
overridden.
    mdVirtual                   =   0x0040,     // Method virtual.
    mdHideBySig                 =   0x0080,     // Method hides by name+sig,
else just by name.

    // vtable layout mask - Use this mask to retrieve vtable attributes.
    mdVtableLayoutMask          =   0x0100,
    mdReuseSlot                 =   0x0000,     // The default.
    mdNewSlot                   =   0x0100,     // Method always gets a new
slot in the vtable.
    // end vtable layout mask

    // method implementation attributes.
    mdCheckAccessOnOverride     =   0x0200,     // Overridability is the
same as the visibility.
    mdAbstract                  =   0x0400,     // Method does not provide
an implementation.
    mdSpecialName               =   0x0800,     // Method is special.  Name
describes how.

    // interop attributes
    mdPinvokeImpl               =   0x2000,     // Implementation is
forwarded through pinvoke.
    mdUnmanagedExport           =   0x0008,     // Managed method exported
via thunk to unmanaged code.

    // Reserved flags for runtime use only.
    mdReservedMask              =   0xd000,
    mdRTSpecialName             =   0x1000,     // Runtime should check name
encoding.
    mdHasSecurity               =   0x4000,     // Method has security
associate with it.
    mdRequireSecObject          =   0x8000,     // Method calls another
method containing security code.

} CorMethodAttr;

  mdStatic、mdFinal、mdVirtual和mdAbstract分别定义方法的修饰苻。
  static方法在类一级使用,无需实例化对象;final方法不允许子类重载它,
在C#中可以使用sealed关键字限定一个虚函数不被子类重载,实现上就是final方法;
virtual方法就不罗嗦了,想必大家都对OO不陌生吧。HideBySig定义方法将以匹配
Signature而不仅仅匹配名字的方式隐藏父类的函数。Abstract定义此方法是纯虚函数,
子类必须实现了此方法才能实例化。

  mdNewSlot属性是有个很有趣的特性。普通方法在名字或Signature(具体匹配判定
方法与实现相关,参见前面的mdHideBySig属性)与父类方法匹配,且方法是虚函数时,
子类方法会使用和父类方法相同的在VTable中的索引,虚函数的实现就是依赖于此机制。
但mdNewSlot属性提供给用户一个跟灵活的选择,允许子类和父类的匹配方法在VTable
中使用不同的Slot(实际上VTable就是一个数组,每个虚方法使用一个索引,数组没项指向
方法的实现,此处每个数组项是一个Slot)。在C#中可以通过new关键字来定义,如下

public class MyBaseC
{
   public int x;
   public void Invoke();
}
public class MyDerivedC : MyBaseC
{
   new public void Invoke();
}

  这样MyDerivedC和MyBaseC两个类中的Invoke()方法就完全不同了。
通过指定new和virtual修饰苻可以保证方法使用新的slot,而new和override
修饰苻在这里是无法同时使用的,前者要求要新的slot,后者则要求使用同一个slot。
  mdSpecialName和mdRTSpecialName属性定义此方法是一个特殊名称的方法。
如C#中的构造函数、析构函数、运算符重载以及事件、属性等特性,都是通过此类方法定义。
如普通构造函数名称固定为.ctor;类构造函数为.cctor;运算符重载使用特定名称
(等会会详细介绍);属性的get/set操作分别以get_属性名/set_属性名的方法实现。
  mdPinvokeImpl和mdUnmanagedExport定义此方法是通过PInvoke(与COM组件
互操作)或导入DLL中函数的方法实现的,因而其RVA=0,没有函数实体。
  mdHasSecurity和mdRequireSecObject定义方法的安全性方面属性。
前者表示方法在DeclSecurity表中有定义项目;后者表示方法调用了一个有安全限制的方法。

  ImplFlag标志定义方法在实现上的属性。

// MethodImpl attr bits, used by DefineMethodImpl.
typedef enum CorMethodImpl
{
    // code impl mask
    miCodeTypeMask      =   0x0003,   // Flags about code type.
    miIL                =   0x0000,   // Method impl is IL.
    miNative            =   0x0001,   // Method impl is native.
    miOPTIL             =   0x0002,   // Method impl is OPTIL
    miRuntime           =   0x0003,   // Method impl is provided by the
runtime.
    // end code impl mask

    // managed mask
    miManagedMask       =   0x0004,   // Flags specifying whether the code
is managed or unmanaged.
    miUnmanaged         =   0x0004,   // Method impl is unmanaged, otherwise
managed.
    miManaged           =   0x0000,   // Method impl is managed.
    // end managed mask

    // implementation info and interop
    miForwardRef        =   0x0010,   // Indicates method is defined; used
primarily in merge scenarios.
    miPreserveSig       =   0x0080,   // Indicates method sig is not to be
mangled to do HRESULT conversion.

    miInternalCall      =   0x1000,   // Reserved for internal use.

    miSynchronized      =   0x0020,   // Method is single threaded through
the body.
    miNoInlining        =   0x0008,   // Method may not be inlined.
    miMaxMethodImplVal  =   0xffff,   // Range check value
} CorMethodImpl;

  低三位定义了方法的实现代码类型:IL代码和Native本机代码最常见;
OPTIL目前没有使用,可能是为优化的IL代码保留(OPT = optimize ?);
Runtime表示此方法在运行时才生成。
  其余属性意义大家可参看注释。
  Name和Signature就不多说了,和类型定义完全一样。ParamList和TypeDef
中的FieldList的使用方法也完全一样。

6.4.2.2 方法实现的定义

  上一节我们提到MethodDef表的RVA字段指向了方法主体部分。
下面我们来对方法的实现结构做一个简单的分析。

  RVA指向数据第一字节的低三位定义了方法头的结构类型

// Indicates the format for the COR_ILMETHOD header
const CorILMethod_FormatShift     = 3;
const CorILMethod_FormatMask      = ((1 shl CorILMethod_FormatShift) - 1);

const CorILMethod_TinyFormat      = $0002;
const CorILMethod_FatFormat       = $0003;

const CorILMethod_TinyFormatEven  = $0002;
const CorILMethod_TinyFormatOdd   = $0006;

  为节约空间,CLR中使用了Tiny和Fat两种方法头。这里CLR做了一个优化,
对Tiny方法头,如果代码字节数为奇数,则置位标志的第4位
  CorILMethod_TinyFormat1     = 0x0006

  IMAGE_COR_ILMETHOD_TINY = packed record
    Flags_CodeSize: Byte;
  end;
  TImageCorILMethodTiny = IMAGE_COR_ILMETHOD_TINY;
  PImageCorILMethodTiny = ^TImageCorILMethodTiny;

  IMAGE_COR_ILMETHOD_FAT = packed record
    Flags_Size,
    MaxStack: Word;
    CodeSize: DWORD;
    LocalVarSigTok: TJclClrToken;
  end;
  TImageCorILMethodFat = IMAGE_COR_ILMETHOD_FAT;
  PImageCorILMethodFat = ^TImageCorILMethodFat;

  PImageCorILMethodHeader = ^TImageCorILMethodHeader;
  TImageCorILMethodHeader = packed record
  case Boolean of
    True:  ( Tiny: TImageCorILMethodTiny );
    False: ( Fat:  TImageCorILMethodFat  );
  end;

  当方法长度小于64字节且没有局部变量时(准确的说,方法还需要满足不使用
异常处理代码、没有额外数据、操作数堆栈小于8个入口等条件),使用Tiny格式。
  Tiny格式只使用6位表示方法代码长度,即最多64字节。而方法的IL代码直接
跟着方法头。
  Fat格式使用12位标志,除低三位用于定义方法头类型外,还定义了

    CorILMethod_InitLocals      = 0x0010,           // call default
constructor on all local vars
    CorILMethod_MoreSects       = 0x0008,           // there is another
attribute after this one

  CorILMethod_InitLocals定义此方法需要调用缺省构造函数初始化局部变量。
  CorILMethod_MoreSects定义此方法在方法头后还有其它的结构。

  接着的4位定义方法头的双字数,目前是3个双字,即12个字节。
  MaxStack定义操作数堆栈的最大项数。因为IL代码是基于堆栈的指令集,
其操作都是针对堆栈中的值和引用进行的,此值就是限定程序堆栈的大小。
CodeSize定义代码的实际长度;LocalVarSigTok定义一个Token指向一个
描述方法局部变量的Signature。分析代码如下

constructor TJclClrMethodBody.Create(const AMethod:
TJclClrTableMethodDefRow);
var
  ILMethod: PImageCorILMethodHeader;
begin
  FMethod  := AMethod;
  FEHTable := TObjectList.Create;

  ILMethod := FMethod.Table.Stream.Metadata.Image.RvaToVa(FMethod.RVA);
  if (ILMethod.Tiny.Flags_CodeSize and CorILMethod_FormatMask) =
CorILMethod_TinyFormat then
  begin
    FSize              := (ILMethod.Tiny.Flags_CodeSize shr
CorILMethod_FormatShift) and ((1 shl 6) - 1);
    FCode              := Pointer(DWORD(ILMethod) + 1);
    FMaxStack          := 0;
    FLocalVarSignToken := 0;
  end
  else
  begin
    FSize              := ILMethod.Fat.CodeSize;
    FCode              := Pointer(DWORD(ILMethod) + (ILMethod.Fat.Flags_Size
shr 12) * SizeOf(DWORD));
    FMaxStack          := ILMethod.Fat.MaxStack;
    FLocalVarSignToken := ILMethod.Fat.LocalVarSigTok;

    if IsBitSet(ILMethod.Fat.Flags_Size, CorILMethod_MoreSects) then
      ParseMoreSections(Pointer((DWORD(FCode) + FSize + 1) and not 1));
  end;
end;

  如果方法头中没有定义CorILMethod_MoreSects,则方法头后直接跟随
实现方法的IL代码;如果定义了,则有一个四字节对齐的结构表示跟随的结构类型。

  IMAGE_COR_ILMETHOD_SECT_SMALL = packed record
    Kind: Byte;
    Datasize: Byte;
    Padding: Word;
  end;
  TImageCorILMethodSectSmall = IMAGE_COR_ILMETHOD_SECT_SMALL;
  PImageCorILMethodSectSmall = ^TImageCorILMethodSectSmall;

  IMAGE_COR_ILMETHOD_SECT_FAT = packed record
    Kind_DataSize: DWORD;
  end;
  TImageCorILMethodSectFat = IMAGE_COR_ILMETHOD_SECT_FAT;
  PImageCorILMethodSectFat = ^TImageCorILMethodSectFat;

  PImageCorILMethodSectHeader = ^TImageCorILMethodSectHeader;
  TImageCorILMethodSectHeader = packed record
  case Boolean of
    True:  ( Small: TImageCorILMethodSectSmall );
    False: ( Fat:   TImageCorILMethodSectFat   );
  end;

  Kind定义结构的类型和属性,如下

const CorILMethod_Sect_Reserved   = 0;
const CorILMethod_Sect_EHTable    = 1;
const CorILMethod_Sect_OptILTable = 2;

const CorILMethod_Sect_KindMask   = $3F; // The mask for decoding the type
code
const CorILMethod_Sect_FatFormat  = $40; // fat format
const CorILMethod_Sect_MoreSects  = $80; // there is another attribute after
this one

  EHTable是结构化异常的定义节。
  OptILTable目前没有使用,可能是配合前面提到的miOPTIL方法属性的;
  CorILMethod_Sect_FatFormat标志表示此结构使用长数据长度定义的
IMAGE_COR_ILMETHOD_SECT_FAT;
  CorILMethod_Sect_MoreSects表示此数据后还有更多的附加数据。

procedure TJclClrMethodBody.ParseMoreSections(SectHeader:
PImageCorILMethodSectHeader);
var
  SectSize: DWORD;
begin
  if IsBitSet(SectHeader.Small.Kind, CorILMethod_Sect_FatFormat) then
    SectSize := SectHeader.Fat.Kind_DataSize shr 8
  else
    SectSize := SectHeader.Small.Datasize;

  if IsBitSet(SectHeader.Small.Kind, CorILMethod_Sect_EHTable) then
    AddEHTable(PImageCorILMethodSectEH(SectHeader))
  else if IsBitSet(SectHeader.Small.Kind, CorILMethod_Sect_OptILTable) then
    AddOptILTable(Pointer(DWORD(FCode) + FSize), SectSize);

  if IsBitSet(SectHeader.Small.Kind, CorILMethod_Sect_MoreSects) then
    ParseMoreSections(Pointer(DWORD(SectHeader) +
SizeOf(TImageCorILMethodSectHeader) + SectSize));
end;

  目前此类扩展数据节只使用了EHTable,也就是异常处理定义,结构如下

  IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT = packed record
    Flags,
    TryOffset,
    TryLength,             // relative to start of try block
    HandlerOffset,
    HandlerLength: DWORD;  // relative to start of handler
    case Boolean of
      True:  ( ClassToken: DWORD );   // use for type-based exception
handlers
      False: ( FilterOffset: DWORD ); // use for filter-based exception
handlers (COR_ILEXCEPTION_FILTER is set)
  end;
  TImageCorILMethodSectEHClauseFat = IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT;
  PImageCorILMethodSectEHClauseFat = ^TImageCorILMethodSectEHClauseFat;

  IMAGE_COR_ILMETHOD_SECT_EH_FAT = packed record
    SectFat: IMAGE_COR_ILMETHOD_SECT_FAT;
    Clauses: array[0..MaxWord-1] of IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT;
// actually variable size
  end;
  TImageCorILMethodSectEHFat = IMAGE_COR_ILMETHOD_SECT_EH_FAT;
  PImageCorILMethodSectEHFat = ^TImageCorILMethodSectEHFat;

  IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL = packed record
    Flags,
    TryOffset: Word;
    TryLength: Byte;     // relative to start of try block
    HandlerOffset: Word;
    HandlerLength: Byte; // relative to start of handler
    case Boolean of
      True:  ( ClassToken: DWORD );   // use for type-based exception
handlers
      False: ( FilterOffset: DWORD ); // use for filter-based exception
handlers (COR_ILEXCEPTION_FILTER is set)
  end;
  TImageCorILMethodSectEHClauseSmall =
IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL;
  PImageCorILMethodSectEHClauseSmall = ^TImageCorILMethodSectEHClauseSmall;

  IMAGE_COR_ILMETHOD_SECT_EH_SMALL = packed record
    SectSmall: IMAGE_COR_ILMETHOD_SECT_SMALL;
    Clauses: array[0..MaxWord-1] of IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL;
// actually variable size
  end;
  TImageCorILMethodSectEHSmall = IMAGE_COR_ILMETHOD_SECT_EH_SMALL;
  PImageCorILMethodSectEHSmall = ^TImageCorILMethodSectEHSmall;

  IMAGE_COR_ILMETHOD_SECT_EH = packed record
  case Boolean of
    True:  ( Small: IMAGE_COR_ILMETHOD_SECT_EH_SMALL );
    False: ( Fat:   IMAGE_COR_ILMETHOD_SECT_EH_FAT   );
  end;
  TImageCorILMethodSectEH = IMAGE_COR_ILMETHOD_SECT_EH;
  PImageCorILMethodSectEH = ^TImageCorILMethodSectEH;

  每个异常处理如catch, finally由一个Try块和一个Handler块组成,
分别定义此异常处理需要保护的代码块(Try)和出现异常时处理的代码块(Handler)
如对以下代码

    void SimpleEHFunc()
    {
      try
      {
        Console.WriteLine("SimpleEHFunc");
      }
      catch
      {
        Console.WriteLine("catch");
      }
      finally
      {
        Console.WriteLine("finally");
      }
    }

  CLR并不直接使用类似Win32中,以代码方式在堆栈中构造异常处理链结点的方式,
而是通过定义异常处理节要求JIT编译器自动生成异常处理代码。
  如对上述代码会生成两个异常处理项:一项定义Try块包含输出"SimpleEHFunc"
的代码,Handler块包含输出"catch"的代码;另外一项则将整个try...catch包含
在Try块中,Handler块则包含输出"finally"的代码。不同类型异常处理都是通过
异常处理表定义,只是通过Flags区分不同的异常处理代码类型。

const COR_ILEXCEPTION_CLAUSE_NONE       = $0000; // This is a typed handler
const COR_ILEXCEPTION_CLAUSE_OFFSETLEN  = $0000; // Deprecated
const COR_ILEXCEPTION_CLAUSE_DEPRECATED = $0000; // Deprecated
const COR_ILEXCEPTION_CLAUSE_FILTER     = $0001; // If this bit is on, then
this EH entry is for a filter
const COR_ILEXCEPTION_CLAUSE_FINALLY    = $0002; // This clause is a finally
clause
const COR_ILEXCEPTION_CLAUSE_FAULT      = $0004; // Fault clause (finally
that is called on exception only)

  而异常处理表的定义与前面类似,也是分为Small和Fat两类,以节省空间。
处理代码如下

procedure TJclClrMethodBody.AddEHTable(EHTable: PImageCorILMethodSectEH);
var
  I, Count: Integer;
  FatFormat: Boolean;
begin
  FatFormat := IsBitSet( EHTable.Small.SectSmall.Kind,
CorILMethod_Sect_FatFormat);
  if FatFormat then
    Count := ((EHTable.Fat.SectFat.Kind_DataSize shr 8) - SizeOf(DWORD)) div
SizeOf(TImageCorILMethodSectEHClauseFat)
  else
    Count := (EHTable.Small.SectSmall.Datasize - SizeOf(DWORD)) div
SizeOf(TImageCorILMethodSectEHClauseSmall);

  for I:=0 to Count-1 do
  begin
    if FatFormat then
      FEHTable.Add(TJclClrExceptionHandler.Create(EHTable.Fat.Clauses[I]))
    else
      
FEHTable.Add(TJclClrExceptionHandler.Create(EHTable.Small.Clauses[I]));
  end;
end;

6.4.2.3 ttMethodSemantics ($18)

  MethodSemantics表用于将属性或事件映射到其实现代码所在的方法上。

constructor TJclClrTableMethodSemanticsRow.Create(const ATable:
TJclClrTable);
begin
  inherited;

  FSemantics      := Table.ReadWord;
  FMethodIdx      := Table.ReadIndex([ttMethodDef]);
  FAssociationIdx := Table.ReadIndex([ttEventDef, ttPropertyDef]);
end;

  Semantics指向方法类型;Method指向方法的实现所在;Association指向
方法被使用到的地方。

typedef enum CorMethodSemanticsAttr
{
    msSetter    =   0x0001,     // Setter for property
    msGetter    =   0x0002,     // Getter for property
    msOther     =   0x0004,     // other method for property or event
    msAddOn     =   0x0008,     // AddOn method for event
    msRemoveOn  =   0x0010,     // RemoveOn method for event
    msFire      =   0x0020,     // Fire method for event
} CorMethodSemanticsAttr;

  对属性来说,setter和getter定义此属性的get/set实现代码所在,如

   private string caption;
   public string Caption {
      get {
         return caption;
      }
      set {
         if (caption != value) {
            caption = value;
            Repaint();
         }
      }
   }

  此时在代码中会有一个caption字段,存储Caption属性的实际值。
Caption属性的get和set操作分别被映射到get_Caption和set_Caption方法上,
get_和set_前缀是由C#编译器字段添加的,并设置了方法的SpecName标志。
  而对事件来说,AddOn/RemoveOn/Fire定义事件的添加/删除/引发代码所在,如

   public event MouseEventHandler MouseDown {
      add { AddEventHandler(mouseDownEventKey, value); }
      remove { RemoveEventHandler(mouseDownEventKey, value); }
   }

  C#编译器会为以上代码生成add_MouseDown和remove_MouseDown方法,
将相应代码放入方法中。此外事件还可以有fire类型方法,在事件被引发时调用,
一般以fire_(或raise_)前缀方法存储,如fire_MouseDown。
  对于属性和方法,还可以定义若干msOther类型方法,这些方法必须由实现者针对
不同的Semantics类型,根据不同方法名称前缀进行处理。

6.4.2.4 ttMethodImpl ($19)

  MethodImpl表用于将一个类型中的方法的实现映射到另一个方法上。

constructor TJclClrTableMethodImplRow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FClassIdx             := Table.ReadIndex([ttTypeDef]);
  FMethodBodyIdx        := Table.ReadIndex([ttMethodDef, ttMemberRef]);
  FMethodDeclarationIdx := Table.ReadIndex([ttMethodDef, ttMemberRef]);
end;

  Class指向TypeDef表中进行函数映射的类型;MethodBody指向实现函数;
MethodDeclaration指向定义函数。
  通过此类映射,可以让编译器开发者越过CLR定义的缺省继承机制。

6.4.2.5 ttParamDef ($08)

  前面分析MethodDef表时曾经提到,对Method的参数,是通过ParamDef表进行定义的。

constructor TJclClrTableParamDefRow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FFlagMask   := Table.ReadWord;
  FSequence   := Table.ReadWord;
  FNameOffset := Table.ReadIndex(hkString);

  FMethod     := nil;
  FFlags      := ParamFlags(FFlagMask);
end;

  Name定义参数的名称;Sequence定义参数的位置,0表示函数返回值,大于0小于参数个数的值
表示参数的位置;Flags则定义参数的属性,如下

// Param attr bits, used by DefineParam.
typedef enum CorParamAttr
{
    pdIn                        =   0x0001,     // Param is [In]
    pdOut                       =   0x0002,     // Param is [out]
    pdOptional                  =   0x0010,     // Param is optional

    // Reserved flags for Runtime use only.
    pdReservedMask              =   0xf000,
    pdHasDefault                =   0x1000,     // Param has default value.
    pdHasFieldMarshal           =   0x2000,     // Param has FieldMarshal.

    pdUnused                    =   0xcfe0,
} CorParamAttr;

  In和Out表示参数传递的方向,In表示参数由方法调用者初始化并传入;
Out表示此参数调用者不用初始化,由被调用方法赋值;Optional表示此参数可选。
HasDefault和HasFieldMarshal与FieldDef表中相应标志意义相同。

6.4.2.6 ttMemberRef ($0a)

  MemberRef表用于引用其它Module或Assembly中定义的类型成员,
实际上合并了FieldRef和MethodRef两类表的元素。

constructor TJclClrTableMemberRefRow.Create(const ATable: TJclClrTable);
begin
  inherited;

  FClassIdx     := Table.ReadIndex([ttTypeRef, ttModuleRef, ttMethodDef,
ttTypeSpec, ttTypeDef]);
  FNameOffset   := Table.ReadIndex(hkString);
  FSignatureOffset := Table.ReadIndex(hkBlob);
end;
  Class指向各种类型定义表中可能的引用类型,而Name和Signature则定义
引用的类型的详细信息。

6.4.2.7 小节

  在了解了方法及其相关表定义后,我们来看看他们之间的关系,如下

                         +------------+
                       1 | MethodImpl |
                    +--->+------------+
                  1 |         1   |1
  +---------+1  *+-----------+<---+
  | TypeDef |--->| MethodDef |         +----------+<-+ 1
  +---------+    +-----------+<---+    | EventDef |  |
                     1 |      1   |1   +----------+  |
                       |        +-----------------+--+ 1
                     * v        | MethodSemantics |
                 +----------+ 1 +-----------------+--+ 1
                 | ParamDef |       +-------------+  |
                 +----------+       | PropertyDef |  |
                                    +-------------+<-+ 1

待续……
版权所有,未经许可,不得转载