下面,我们就开始分析激动人心的MetaData了。
首先,是在IMAGE_COR20_HEADER中根据名为MetaData的IMAGE_DATA_DIRECTORY项查找MetaData表的位置,计算方法跟上文提到的找COM Header位置的方法是一样的,我们可以算得,MetaData表的位置是:0x00002148 – 0x00002000 + 0x00000200 = 0x00000348。

MetaData的基本设计方法是采用了简化的复合文档(Compound document)。基本的组织结构如下:
// +——————-+
// | Signature |
// +——————-+
// | Stream 1, 2, [] |
// +——————-+
// | STORAGEHEADER |
// | Extra data |
// | STORAGESTREAM[] |
// +——————-+
// | offset |
// +——————-+

在clr\src\md\inc\MdFileFormat.h中,定义如下:

struct STORAGESIGNATURE
{
public:
//Ingored many getter and setter function
private:
ULONG m_lSignature; // “Magic” signature.0x424a5342
USHORT m_iMajorVer; // Major file version.0x0001
USHORT m_iMinorVer; // Minor file version.0x0001
ULONG m_lExtraData; // Offset to next structure of information 0x0
ULONG m_lVersionStringLength; // Length of version string0x00000008
BYTE m_pVersion[0]; // Version string”v1.0.0″
};

struct STORAGEHEADER
{
BYTE fFlags; // STGHDR_xxx flags.0x00
BYTE pad;0x00
USHORT iStreams; // How many streams are there.0x0005
// function ignored
};
其中fFlags的含义如下:
enum
{
STGHDR_NORMAL = 0x00, // Normal default flags.
STGHDR_EXTRADATA = 0x01, // Additional data exists after header.
};

紧接着STORAGEHEADER下面是STORAGESTREAM存储流的信息,从头部我们知道有五个流,SSCLI中定义了席面的函数来得到流:

STORAGESTREAM MDFormat::GetFirstStream(// Return pointer to the first stream.
STORAGEHEADER
pHeader, // Return copy of header struct.
const void pvMd) // Pointer to the full file.
{
const BYTE
pbMd; // Working pointer.

// Header data starts after signature.  
pbMd = (const BYTE *) pvMd;  
pbMd += sizeof(STORAGESIGNATURE);  
pbMd += ((STORAGESIGNATURE*)pvMd)->VersionStringLength();  
STORAGEHEADER \*pHdr = (STORAGEHEADER \*) pbMd;  
\*pHeader = \*pHdr;  
pbMd += sizeof(STORAGEHEADER);

// If there is extra data, skip over it.  
if (pHdr->fFlags & STGHDR_EXTRADATA)  
    pbMd = pbMd + sizeof(ULONG) + VAL32(\*(ULONG \*) pbMd);

// The pointer is now at the first stream in the list.  
return ((STORAGESTREAM *) pbMd);

}

inline STORAGESTREAM *NextStream()  
{  
    int         iLen = (int)(strlen(rcName) + 1);  
    iLen = ALIGN4BYTE(iLen);  
    return ((STORAGESTREAM \*) (((BYTE\*)this) + (sizeof(ULONG) * 2) + iLen));  
}

Metadata Stream有五种常见类型,#String, #Blob, #Guid, #US(User String)和#~流,每种类型流最多只能出现一次,#US和#Blob流可省略。下面以此介绍每一个流,因为~#流最复杂,因此放在最后介绍。

Strings流和#US流:

struct STORAGESTREAM
{
ULONG ulOffset; // Offset in file for this stream.0x000001D0
ULONG ulSize; // Size of the file.0x000000CC
char rcName[MAXSTREAMNAME]; // Start of name, null terminated.”#Strings”
};

struct&nbsp

;STORAGESTREAM
{
ULONG ulOffset; // Offset in file for this stream.0x0000029C
ULONG ulSize; // Size of the file.0x00000044
char rcName[MAXSTREAMNAME]; // Start of name, null terminated.”#US”
};

这两个流存放的都是字符串。#String流存储的是字符串,代码中的一切描述符字符串都保存在

这里。以#0字符分隔。流的开始总有一个\0字符代表一个空字符串。#US流存储的是用户定义的字符串,字符串以UTF8编码保存。在访问Stream中的字符串时,mdtString的RID代表的不是行号,而是字符在Stream中的偏移。我们举两个例子看看:
使用ILDASM工具反汇编Hello.exe,然后选择“显示标记值”就可以看到所有的Token了,我们随便找到两个字符串Token,分别是0x70000017和0x7000002F,然后看它们所代表的值。首先,我们要算出#US的首地址(能在反汇编里找到的Token肯定是用户定义的字符串,系统用的是不会让我们看到的。),计算公式是流的首地址加上偏移 = 0x00000348 + 0x0000029C = 0x000005E4。然后分别加上两个字符串的首地址偏移量0x17和0x2F,就是两个字符串的偏移:0x000005FB和0x00000613。我们可以以看到,它们分别是:“Hello World”和“Echo: {0}”,正是我们程序中出现的字符。

GUID流:

struct STORAGESTREAM
{
ULONG ulOffset; // Offset in file for this stream.0x000002E0
ULONG ulSize; // Size of the file.0x00000010
char rcName[MAXSTREAMNAME]; // Start of name, null terminated.”#GUID”
};
GUID流就是一个GUID的数组,数组中元素的个数就是:STORAGESTREAM.ulSize / sizeof(GUID)。如果这篇文章读者能仔细看到这里,那么你一定对GUID耳熟能详,定义如下:
typedef struct _GUID
{
ULONG Data1; // NOTE: diff from Win32, for LP64
USHORT Data2;
USHORT Data3;
UCHAR Data4[ 8 ];
} GUID;
对于本文件中的GUID,我们可以看到,其起始位置 = 0x00000348 + 0x000002E0 = 0x00000628。数组中只有一个GUID,因为 0x10 / sizeof(GUID) = 1。其值为:{6BE151AB-E2FF-3D10-5CA8-7B77DA98426C}

Blob流:

struct STORAGESTREAM
{
ULONG ulOffset; // Offset in file for this stream.0x000002F0
ULONG ulSize; // Size of the file.0x00000040
char rcName[MAXSTREAMNAME]; // Start of name, null terminated.”#Blob”
};
BLOB 是二进制大对象(binary large object)的首字母缩写,顾名思义#Blob流存放的是大宗的二进制数据。本例中,起始位置 = 0x00000348 + 0x000002F0 = 0x00000638。

~流:

struct STORAGESTREAM
{
ULONG ulOffset; // Offset in file for this stream.0x00000068
ULONG ulSize; // Size of the file.0x00000168
char rcName[MAXSTREAMNAME]; // Start of name, null terminated.”#~”
};

~流是存放MetaData的地方。该Assembly中几乎所有的描述信息都放在这里,因此,这是几个流中最重要的一个。MetaData都是以表的形式存放的。流的起始位置 = 0x00000348 + 0x00000068 = 0x000003B0。此处起连续存放着CMiniMdSchemaBase类和CMiniMdSchema类(定义在clr\src\md\inc\Metamodel.h中)。找得我好苦好苦啊:’( 但还是给我挖出来了:-)。

class CMiniMdSchemaBase
{
public:
ULONG m_ulReserved; // Reserved, must be zero.0x00000000
BYTE m_major; // Version numbers.0x01
BYTE m_minor;0x00
BYTE m_heaps; // Bits for heap sizes.0x00
BYTE m_rid; // log-base-2 of largest rid.0x01
unsigned _int64 m_maskvalid;// Bit mask of present table counts.0x9`01a21557
unsigned
_int64 m_sorted; // Bit mask of sorted tables.0x200`3301fa00
};

class CMiniMdSchema : public CMiniMdSchemaBase
{
public:
ULONG m_cRecs[TBL_COUNT]; // Counts of various tables.
ULONG m_ulExtra; &nb

sp; // Extra data, only persisted if non-zero.
};

其中TBL_COUNT是一个枚举,告诉我们在MetaData中一共有多少种表,表是Metadata中相当重要的一个概念,我们在下文中会详细探讨。该枚举定义在clr\src\inc\metamodelpub.h中,原定义有个宏,展开后是这样的。因此我们可以知道TBL_COUNT在SSCLI中的实际值是42。我们也可以看到,这个枚举其实是上文列出的CorTokenType枚举的超集。

enum
{
TBL_Module, // 0
TBL_TypeRef,
TBL_TypeDef,
TBL_FieldPtr,
TBL_Field,
TBL_MethodPtr,
TBL_Method,
TBL_ParamPtr,
TBL_Param,
TBL_InterfaceImpl,
TBL_MemberRef,// 10
TBL_Constant,
TBL_CustomAttribute,
TBL_FieldMarshal,
TBL_DeclSecurity,
TBL_ClassLayout,
TBL_FieldLayout,
TBL_StandAloneSig,
TBL_EventMap,
TBL_EventPtr,
TBL_Event,// 20
TBL_PropertyMap,
TBL_PropertyPtr,
TBL_Property,
TBL_MethodSemantics,
TBL_MethodImpl,
TBL_ModuleRef,
TBL_TypeSpec,
TBL_ImplMap,
TBL_FieldRVA,
TBL_ENCLog,// 30
TBL_ENCMap,
TBL_Assembly,
TBL_AssemblyProcessor,
TBL_AssemblyOS,
TBL_AssemblyRef,
TBL_AssemblyRefProcessor,
TBL_AssemblyRefOS,
TBL_File,
TBL_ExportedType,
TBL_ManifestResource,
TBL_NestedClass,

TBL_COUNT// 42

};
CMiniMdSchemaBase 中m_heaps是一个位图,表示指向某个堆的索引的长度。第一位表示#Strings流,
第二位表示#Guid流,第三位没用,第四位表示#Blob流。如相应位设ç½

®ä¸º1,则表示对应流用4字节索引。本例中,m_heaps为0,表示所有的流均用2字节索引。代码在clr\src\md\runtime\metamodel.cpp的CMiniMdBase::SchemaPopulate2中,如下:
m_iStringsMask = (m_Schema.m_heaps & CMiniMdSchema::HEAP_STRING_4) ? 0xffffffff : 0xffff;
m_iGuidsMask = (m_Schema.m_heaps & CMiniMdSchema::HEAP_GUID_4) ? 0xffffffff : 0xffff;
m_iBlobsMask = (m_Schema.m_heaps & CMiniMdSchema::HEAP_BLOB_4) ? 0xffffffff : 0xffff;
CMiniMdSchemaBase 中的m_maskvalid和m_sorted是两个64bit的位图。用来标识相对应的位的表的状态,其中m_maskvalid表示表表是否存在,m_sorted表示表是否排序。例如m_maskvalid的最低位为1表示TBL_Module表存在。m_sorted第10位为0表示TBL_MemberRef表没有排序。这里值得一提的是CMiniMdSchema类中的定义m_cRecs[TBL_COUNT];只是内存中的镜像,在PE文件中为了节省空间(当然要节省空间,Assembly设计的可是可以通过网络即时下载的,带宽啊……),并没有实际存放42个ULONG,而只是存放存在的ULONG。每个ULONG表示对应的表有几行。在clr\src\md\runtime\Metamodel.cpp中的CMiniMdSchema::LoadFrom中,加载m_cRecs的代码是下面的样子:

unsigned __int64 maskvalid = m_maskvalid;
for (iSrc=0, iDst=0; iDst>= 1)
{
if (maskvalid & 1)
{
m_cRecs[iDst] = VAL32(pSource->m_cRecs[iSrc++]);
}
}
这段代码利用m_maskvalid域中的位图,把存在表项加载到了相应的内存数组中。在本例中,m_maskvalid的值为0x901a21557,包含27个1,因此在PE文件中,m_cRecs处只存在14个ULONG。根据m_maskvalid,0x901a21557转成二进制为100100000001101000100001010101010111,我们知道他们的值分别是:
// Module – 0x00000001
// TypeRef – 0x00000004
// TypeDef – 0x00000003
// Field – 0x00000001
// Method – 0x00000006
// Param – 0x00000002
// MemberRef – 0x00000004
// CustomAttribute- 0x00000001
// StandAloneSig – 0x00000002
// PropertyMap – 0x00000001
// Property – 0x00000001
// MethodSemantic- 0x00000002
// Assembly – 0x00000001
// AssemblyRef – 0x00000001

紧接着下面是附加数据,对应的是CMiniMdSchema中的m_ulExtra。但是该域不一定存在,判断是否有附加数据是看CMiniMdSchemaBase的m_heaps的第7个bit是否为1。在本例中,m_heaps为0,所以不存在附加数据。SSCLI中对应的代码也在CMiniMdSchema::LoadFrom中,如下:
enum
{
EXTRA_DATA = 0x40, // If set, schema persists an extra 4 bytes of data.
}
// Retrieve the extra data.
if (m_heaps & EXTRA_DATA)
{
m_ulExtra = (VAL32(*reinterpret_cast(&pSource->m_cRecs[iSrc])));
ulData += sizeof(ULONG);
}

紧接着附加数据的就是元数据表(Metadata Table)。读者如果熟悉关系型数据库理论,会发现这里的元数据表与关系型数据库中的表非常相似:每个表由若干行若干列构成,每一行表示一个记录,每一 …