首页 -> 安全研究
安全研究
绿盟月刊
绿盟安全月刊->第36期->技术专题
作者: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
待续……
版权所有,未经许可,不得转载