Date Category 未分类

闲来无事了,接着写吧。
上回书说到,一个Assembly的MetaData里面大概会有40多种表,然后我们根据一个例子分析了SSCLI下的PE文件的结构和MetaData其中的两种表Module和TypeRef。这次我们依然结合上次的例子,把剩余的几张元数据表分析完。
剩余还有12种元数据表,分别是TypeDef,Field,Method,Param,MemberRef,CustomAttribute,StandAloneSig,PropertyMap,Property,MethodSemantic,Assembly和AssemblyRef。像上面一样,我们首先看表类型的定义,在clr\src\md\runtime\metamodelcolumndefs.h中。

//————————————————————————-  
// TypeDef  
SCHEMA\_TABLE\_START(TypeDef)  
SCHEMA_ITEM(TypeDef, ULONG, Flags)  
SCHEMA\_ITEM\_STRING(TypeDef, Name)  
SCHEMA\_ITEM\_STRING(TypeDef, Namespace)  
SCHEMA\_ITEM\_CDTKN(TypeDef, Extends, TypeDefOrRef)  
SCHEMA\_ITEM\_RID(TypeDef, FieldList, Field)  
SCHEMA\_ITEM\_RID(TypeDef, MethodList, Method)

SCHEMA_TABLE_END(TypeDef)

//————————————————————————-  
//Field  
SCHEMA\_TABLE\_START(Field)  
SCHEMA_ITEM(Field, USHORT, Flags)  
SCHEMA\_ITEM\_STRING(Field,Name)  
SCHEMA\_ITEM\_BLOB(Field,Signature)

SCHEMA_TABLE_END(Field)

//————————————————————————-  
//Method  
SCHEMA\_TABLE\_START(Method)  
SCHEMA_ITEM(Method, ULONG, RVA)  
SCHEMA_ITEM(Method, USHORT, ImplFlags)  
SCHEMA_ITEM(Method, USHORT, Flags)  
SCHEMA\_ITEM\_STRING(Method,Name)  
SCHEMA\_ITEM\_BLOB(Method,Signature)  
SCHEMA\_ITEM\_RID(Method,ParamList,Param)

SCHEMA_TABLE_END(Method)

//————————————————————————-  
// Param  
SCHEMA\_TABLE\_START(Param)  
SCHEMA_ITEM(Param, USHORT, Flags)  
SCHEMA_ITEM(Param, USHORT, Sequence)  
SCHEMA\_ITEM\_STRING(Param,Name)

SCHEMA_TABLE_END(Param)

//————————————————————————-  
//MemberRef  
SCHEMA\_TABLE\_START(MemberRef)  
SCHEMA\_ITEM\_NOFIXED()  
SCHEMA\_ITEM\_CDTKN(MemberRef,Class,MemberRefParent)  
SCHEMA\_ITEM\_STRING(MemberRef,Name)  
SCHEMA\_ITEM\_BLOB(MemberRef,Signature)

SCHEMA_TABLE_END(MemberRef)

//————————————————————————-
//CustomAttribute
SCHEMA_TABLE_START(CustomAttribute)
SCHEMA_ITEM_NOFIXED()
SCHEMA_ITEM_CDTKN(CustomAttribute,Parent,HasCustomAttribute)
SCHEMA_ITEM_CDTKN(CustomAttribute,Type,CustomAttributeType)
SCHEMA_ITEM_BLOB(CustomAttribute,Value)
SCHEMA_TABLE_END(CustomAttribute)

//————————————————————————-  
//StandAloneSig  
SCHEMA\_TABLE\_START(StandAloneSig)  
SCHEMA\_ITEM\_NOFIXED()  
SCHEMA\_ITEM\_BLOB(StandAloneSig,Signature)

SCHEMA_TABLE_END(StandAloneSig)

//————————————————————————-  
//PropertyMap  
SCHEMA\_TABLE\_START(PropertyMap)  
SCHEMA\_ITEM\_NOFIXED()  
SCHEMA\_ITEM\_RID(PropertyMap,Parent,TypeDef)  
SCHEMA\_ITEM\_RID(PropertyMap,PropertyList,Property)  
SCHEMA\_TABLE\_END(PropertyMap)

//————————————————————————-  
//Property  
SCHEMA\_TABLE\_START(Property)  
SCHEMA_ITEM(Property, USHORT, PropFlags)  
SCHEMA\_ITEM\_STRING(Property,Name)  
SCHEMA\_ITEM\_BLOB(Property,Type)  
SCHEMA\_TABLE\_END(Property)

//————————————————————————-  
//MethodSemantics  
SCHEMA\_TABLE\_START(MethodSemantics)  
SCHEMA_ITEM(MethodSemantics, USHORT, Semantic)  
SCHEMA\_ITEM\_RID(MethodSemantics,Method,Method)  
SCHEMA\_ITEM\_CDTKN(MethodSemantics,Association,HasSemantic)  
SCHEMA\_TABLE\_END(MethodSemantics)

//————————————————————————-  
// Assembly  
SCHEMA\_TABLE\_START(Assembly)  
SCHEMA_ITEM(Assembly, ULONG, HashAlgId)  
SCHEMA_ITEM(Assembly, USHORT, MajorVersion)  
SCHEMA_ITEM(Assembly, USHORT, MinorVersion)  
SCHEMA_ITEM(Assembly, USHORT, BuildNumber)  
SCHEMA_ITEM(Assembly, USHORT, RevisionNumber)  
SCHEMA_ITEM(Assembly, ULONG, Flags)  
SCHEMA\_ITEM\_BLOB(Assembly, PublicKey)  
SCHEMA\_ITEM\_STRING(Assembly, Name)  
SCHEMA\_ITEM\_STRING(Assembly, Locale)  
SCHEMA\_TABLE\_END(Assembly)

//————————————————————————-  
// AssemblyRef  
SCHEMA\_TABLE\_START(AssemblyRef)  
SCHEMA_ITEM(AssemblyRef, USHORT, MajorVersion)  
SCHEMA_ITEM(AssemblyRef, USHORT, MinorVersion)  
SCHEMA_ITEM(AssemblyRef, USHORT, BuildNumber)  
SCHEMA_ITEM(AssemblyRef, USHORT, RevisionNumber)  
SCHEMA_ITEM(AssemblyRef, ULONG, Flags)  
SCHEMA\_ITEM\_BLOB(AssemblyRef, PublicKeyOrToken)  
SCHEMA\_ITEM\_STRING(AssemblyRef, Name)  
SCHEMA\_ITEM\_STRING(AssemblyRef, Locale)

SCHEMA_ITEM_BLOB(AssemblyRef, HashValue)
SCHEMA_TABLE_END(AssemblyRef)
这几个宏也可以展开为MetaData表的描述信息,由于在前面的文章中我们已经演示过展开Module和TypeRef定义的宏,这里就不再展开了。读者感兴趣的可以自己尝试一下。我们还是切入正题,开始逐一分析每个表。

TypeDef表描述了该程序集中的某个类型定义,它包含如下属性:4字节的Flags,Flags描述了这个类型的可访问性,例如C#中的public, private等等,具体的定义在clr/src/inc/Corhdr.h中。

// TypeDef/ExportedType attr bits, used

by DefineTypeDef.
typedef enum CorTypeAttr
{
// Use this mask to retrieve the type visibility information.

tdVisibilityMask = 0x00000007,
tdNotPublic = 0x00000000, // Class is not public scope.
tdPublic = 0x00000001, // Class is public scope.
tdNestedPublic = 0x00000002, // Class is nested with public visibility.
tdNestedPrivate = 0x00000003, // Class is nested with private visibility.
tdNestedFamily = 0x00000004, // Class is nested with family visibility.
tdNestedAssembly = 0x00000005, // Class is nested with assembly visibility.
tdNestedFamANDAssem = 0x00000006, // Class is nested with family and assembly visibility.
tdNestedFamORAssem = 0x00000007, // Class is nested with family or assembly visibility.
………………
} CorTypeAttr;

指向#Strings流的两字节的Name索引,两字节的NameSpace索引,分别描述了类型的名称和命名空间。2个Byte的名为Extends的指向mdtTypeDefOrRef类型Coded Token的索引,描述了该类型继承了那个父类和实现了哪些接口。2个Byte的FieldList和MethodList索引,描述了该类型有哪些成员变量和成员函数,一个类型有哪些成员变量或成员函数应该是一个范围,这个范围的开始就是FieldList和MethodList所指向FieldDef或MethodDef中的方法,范围的结尾是FieldDef表结尾,或者下一个TypeDef中的FieldList指向的项。这样每个TypeDef占14个Byte。
在本例中,一共有3个TypeDef,它们的二进制值分别是:00 00 00 00/01 00/00 00/00 00/01 00/01 00|01 00 10 00/2B 00/00 00/05 00/01 00/01 00|01 00 10 00/30 00/00 00/05 00/02 00/05 00。我们分析第一项,其Flags值为0x00000000,Name为0x0001,值为“”;NameSpace为空。其实这是一个伪类型,定义为模块级的函数和变量就放在这个里面,但是C#中似乎不允许定义模块级的方法和变量,所以这项实际上没什么用。
那么接着分析第二项,其Flags的值为0x00100001,从CorTypeAttr的定义可以知道意思为tdPublic和tdBeforeFieldInit。Name索引是0x002B,值为“Echo”;Namespace值为空。Extends值为0x0005,由于它是个mdtTypeDefOrRef类型的Coded Token,解码后其实是0x01000001,正如上次分析的,指向TypeDef表的第一项:System.Object;然后是FieldList和MethodList,值都为0x0001。我们先看下一项Typedef的FieldList为0x0002,MethodList为0x0005,也就是说Echo类有一个Field,4个Method(我们猜一下:一个Field肯定就是private string toEcho了,4个Method么,DoEcho算一个,一个EchoString属性其实会被编译成两个Method,一个get一个set,最后一个其实是编译器给我们加上的构造函数)。综合一下,这个Typedef的意思是说,这个Assembly里面定义了一个Echo类,有public的访问属性,它继承自System.Object,它含有一个Field和四个方法。剩下一个Typedef就是Hello类了,就不再详细分析了。

Field表描述了MetaData中的所有成员变量定义。Field表包含下面一些列:2个Byte的Flags,定义可参见clr/src/inc/Corhdr.h中的CorFieldAttr枚举,这里就不罗列了。2个Byte的到#Strings流的Name索引,2个Byte的到#Blob流的Signature索引。一个FieldDef有6个Byte。
本例中仅有的1个FieldDef的值是01 00 36 00 0A 00。Flags的值为0x0001,参考CorFieldAttr,其意思为fdPrivate;Name的值是0x0036,值是“toEcho”;Signature的值是0x000A,对应#Blob流中的值是06 0E。总结一下,这个项表示该Assembly中有一个名为toEcho的Field,有Private的访问属性,签名是06 0E。

Method表描述了MetaData中的所有成员函数定义。Method标包含了下面一些列:4个Byte的RVA,两个Byte的ImpFlags,两个Byte的Flags,指向#Strings流的两个Byte的Name索引,指向#Blob流得两个Byte的Signature,两个Byte的ParamList。因此一个MethodDef有14个Byte。
RVA定义了方法开始的偏移地址,注意,是针对当前段(也就是.text段的偏移,不是针对MetaData起始地址的偏移。)。ImpFlags是方法实现上的标记,定义可参见clr/src/inc/Corhdr.h中的CorMethodImpl枚举。Flags是方法上的其他标记,定义可参见clr/src/inc/Corhdr.h中的CorMethodAttr枚举。Name是这个方法的名称,Signature没多大意义,ParamList是指向ParamDef表的索引向,也是一个范围,范围开始结束的方法跟前面讲述的TypeDef中的FieldList,MethodList差不多。
在本例中,一共有6个MethodDef,其值分别为:50 20 00 00/00 00/86 08/3D 00/0D 00/01 00|68 20 00 00/00 00/86 08/4C 00/11 00/01 00|7C 20 00 00 00 00 86 00 5B 00 0D 00 02 00|A8 20 00 00 00 00 86 18 62 00 16 00 02 00|C4 20 00 00/00 00/96 00/73 00/1E 00/02 00|F4 20 00 00 00 00 86 18 62 00 16 00 03 00。
我们这次不看第1项,看第5项(因为这个比较有代表性,在IMAGE_COR20_HEADER中,我们知道EntryPointToken的值是0x60000005,那么Method的第5项就应该是入口的Main函数,这里顺便验证一下),其RVA的值为0x000020C4。那么,我们可以算得其偏移地址为 = 0x000020C4 – 0x00002000 + 0x00000200 = 0x000002C4,一会我们再看这里放了什么。ImpFlags值为0x0000,Flags值为0x0096,表示mdHideBySig | mdStatic | mdPublic。Name的值是0x0073,为“Main”;Signature值为0x001E,相应的#Blob数据为“00 01 01 1D 0E”。ParamList值为0x0002,表示指向ParamDef表的第二项。综合一下,第五项描述了一个方法,名字叫Main,有public static属性,有一个参数,代码开始于0x000002C4。
下面就要分析IL代码的格式:为了节省空间(又是节省空间添的麻烦),SSCLI定义了两种方法格式,Tiny和Fat。当代码小于64 byte,并且没有局部变量的时候,我们可以选用Tiny方法,否则就只能Fat了。Tiny和Fat的定义可以在clr/src/inc/Corhdr.h中找到,列举如下,省略了一些关系不大的方法:

typedef enum CorILMethodFlags
{
// Indicates the format for the COR_ILMETHOD header
CorILMethod_FormatShift = 3,
&

nbsp; CorILMethod_FormatMask &

nbsp; = ((1 << CorILMethod_FormatShift) – 1),
CorILMethod_TinyFormat = 0x0002, // use this code if the code size is even
CorILMethod_SmallFormat = 0x0000,
CorILMethod_FatFormat = 0x0003,
CorILMethod_TinyFormat1 = 0x0006, // use this code if the code size is odd
} CorILMethodFlags;

/***************************/
/* Used when the method is tiny (< 64 bytes), and there are no local vars */
typedef struct IMAGE_COR_ILMETHOD_TINY
{
BYTE Flags_CodeSize;
} IMAGE_COR_ILMETHOD_TINY;

/************/
// This strucuture is the ”fat” layout, where no compression is attempted.
// Note that this structure can be added on at the end, thus making it extensible
typedef struct IMAGE_COR_ILMETHOD_FAT
{
unsigned Flags : 12; // Flags 0x13
unsigned Size : 4; // size in DWords of this structure (currently 3) 0y0011
unsigned MaxStack : 16; // maximum number of items (I4, I, I8, obj …), on the operand stack 0x2
DWORD CodeSize; // size of the code 0x22
mdSignature LocalVarSigTok; // token that indicates the signature of the local vars (0 means none) 0x11000002
} IMAGE_COR_ILMETHOD_FAT;

typedef struct tagCOR_ILMETHOD_TINY : IMAGE_COR_ILMETHOD_TINY
{
bool IsTiny() const { return((Flags_CodeSize & (CorILMethod_FormatMask >> 1)) == CorILMethod_TinyFormat); }
unsigned GetCodeSize() const { return(((unsigned) Flags_CodeSize) >> (CorILMethod_FormatShift-1)); }
unsigned GetMaxStack() const { return(8); }
BYTE* GetCode() const { return(((BYTE*) this) + sizeof(struct tagCOR_ILMETHOD_TINY)); }
} COR_ILMETHOD_TINY;

typedef struct tagCOR_ILMETHOD_FAT : IMAGE_COR_ILMETHOD_FAT
{
unsigned GetSize() const {
BYTE* p = (BYTE*)this;
return *(p+1) >> 4;
}
unsigned GetFlags() const {
BYTE* p = (BYTE*)this;
return ((unsigned)*(p+0)) | (( ((unsigned)*(p+1)) << 8) & 0x0F);
}
bool IsFat() const {
return (*(BYTE*)this & CorILMethod_FormatMask) == CorILMethod_FatFormat;
}

BYTE* GetCode() const {  
    return(((BYTE\*) this) + 4\*GetSize());  
}  
const COR\_ILMETHOD\_SECT* GetSect() const {  
    if (!(*(B &#8230;