第一章 Windows 2000对调试技术的支持
翻译:Kendiv ( fcczj@263.net )
更新:Tuesday, May 03, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
CodeView子节(CodeView Subsections)
CodeView是微软自己的调试信息格式。随着微软C/C++编译器和链接器的发展它也经历了很多变化。有些CodeView版本之间的区别非常明显。不过,所有版本的CodeView在它们的开始位置都有一个32位的签名来唯一标识其采用的数据格式。Windows NT 4.0符号文件使用NB09格式,该格式由CodeView 4.10引入。Windows 2000的符号文件包含的则是NB10格式的CodeView数据,这一格式只是引入了独立.pdb文件,这一点我在前面已经提及过。
NB09版的CodeView中的数据被进一步细化为一个目录及其下属的目录项。就像Matt Pietrek在MSJ所发表的文章中所指出的那样, 对于.dbg文件,大多数基本的CodeView结构在SDK的一组示例性头文件中都有定义。如果你安装了SDK示例,你会在\Program Files\Microsoft Platform SDK\Samples\SdkTools\Image\Include目录下发现一组非常有趣的文件。你解析CodeView所需的文件名为:cvexefmt.h和cvinfo.h。很不幸的是,这些文件已经很长时间没有更新了,这些文件的日期还停留在
尽管今天看来原始的OMF格式已经过时了,但它仍被公认为是一种灵活的文件格式。它的一个目标就是尽可能的减少对内存和磁盘空间的使用量。另一个非常重要的属性是:即使应用程序并不完全了解此种格式的所有部分,也可以成功的解析这个格式的文件。基本的OMF数据结构是一种带有标识的记录,开始处的标识字节给出了记录中所包含的数据的类型。这种设计使得OMF读取器可以在记录之间灵活的移动,以选出它们感兴趣的记录。微软的CodeView格式就采用了这种设计方案,cvexetmt.h中CodeView结构名称的OMF前缀可以说明这一点。尽管CodeView记录和原始的OMF记录一样仅包含了很少的数据,但它仍保留了此种格式的基本特性:不需要理解记录中的所有内容,就可以读取此种格式。
列表1-19给出了多个基本的CodeView结构,它们都来自w2k_img.h。其中的一些定义和cvexefmt.h和cvinfo.h中的定义类似,但它们可以满足w2k_img.dll的所有需求。在所有CodeView数据中都出现了CV_HEADER结构,但格式的版本除外。签名是一个32位的格式版本ID,它类似于CV_SIGNATURE_NB09或者CV_SIGNATURE_NB10。lOffset成员给出了CodeView目录相对于表头地址的偏移量。在Windows NT 4.0的NB09格式的符号文件中,这一偏移量似乎总是8,这表示紧随表头之后的就是CodeView目录结构。Windows 2000符号文件中这一偏移量则为0。在稍后我们将详细讨论这一格式。
#define CV_SIGNATURE_NB ''''BN''''
#define CV_SIGNATURE_NB09 ''''90BN''''
#define CV_SIGNATURE_NB10 ''''01BN''''
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
typedef union _CV_SIGNATURE
{
WORD wMagic; // ''''BN''''
DWORD dVersion; // ''''xxBN''''
BYTE abText [4]; // "NBxx"
}
CV_SIGNATURE, *PCV_SIGNATURE, **PPCV_SIGNATURE;
#define CV_SIGNATURE_ sizeof (CV_SIGNATURE)
// -----------------------------------------------------------------
typedef struct _CV_HEADER
{
CV_SIGNATURE Signature;
LONG lOffset;
}
CV_HEADER, *PCV_HEADER, **PPCV_HEADER;
#define CV_HEADER_ sizeof (CV_HEADER)
// -----------------------------------------------------------------
typedef struct _CV_DIRECTORY
{
WORD wSize; // in bytes, including this member
WORD wEntrySize; // in bytes
DWORD dEntries;
LONG lOffset;
DWORD dFlags;
}
CV_DIRECTORY, *PCV_DIRECTORY, **PPCV_DIRECTORY;
#define CV_DIRECTORY_ sizeof (CV_DIRECTORY)
// -----------------------------------------------------------------
#define sstModule 0x0120 // CV_MODULE
#define sstGlobalPub 0x
#define sstSegMap 0x012D // SV_SEGMAP
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
typedef struct _CV_ENTRY
{
WORD wSubSectionType; // sst*
WORD wModuleIndex; // -1 if not applicable
LONG lSubSectionOffset; // relative to CV_HEADER
DWORD dSubSectionSize; // in bytes, not including padding
}
CV_ENTRY, *PCV_ENTRY, **PPCV_ENTRY;
#define CV_ENTRY_ sizeof (CV_ENTRY)
// -----------------------------------------------------------------
typedef struct _CV_NB09 // CodeView 4.10
{
CV_HEADER Header;
CV_DIRECTORY Directory;
CV_ENTRY Entries [];
}
CV_NB09, *PCV_NB09, **PPCV_NB09;
#define CV_NB09_ sizeof (CV_NB09)
// -----------------------------------------------------------------
typedef struct _CV_NB10 // PDB reference
{
CV_HEADER Header;
DWORD dSignature; // seconds since
DWORD dAge; // 1++
BYTE abPdbName []; // zero-terminated
}
CV_NB10, *PCV_NB10, **PPCV_NB10;
#define CV_NB10_ sizeof (CV_NB10)
列表1-19. CodeView的数据结构
NB09版的CodeView目录由一个CV_DIRECTORY结构和紧随其后的一个CV_ENTRY数组构成。列表1-19中的CV_NB09结构反映了这种结构特点。CV_NB09结构包含CodeView表头、目录和一个Entry数组。CV_DIRECTORY结构的dEntries成员给出了Entries[]数组的大小。数组中的每个CV_ENTRY都指向CodeView的一个子节,该子节的类型由CV_ENTRY结构的wSubSectionType成员给出。Cvexefmt.h中定义了至少21种子节类型。不过,Windows NT 4.0仅使用其中的3个:sstModule(0x0120)、sstGlobalPub(0x
列表1-20中给出的imgCvEntry()函数可以方便的按类型查找CodeView目录项。该函数的pc09参数指向一个CV_NB09结构,这意味着,该参数将指向.dbg文件中签名为NB09的CodeView数据块。dType参数用于指定CodeView子节的类型ID(形如sst*),dIndex参数用于表示在多个类型相同的子节中选择哪个子节的实例。因此,仅当dType为sstModule时,dIndex才能被设置为一个非0值。
PCV_ENTRY WINAPI imgCvEntry (PCV_NB09 pc09,
DWORD dType,
DWORD dIndex)
{
DWORD i, j;
PCV_ENTRY pce = NULL;
if ((pc09 != NULL) &&
(pc09->Header.Signature.dVersion == CV_SIGNATURE_NB09))
{
for (i = j = 0; i < pc09->Directory.dEntries; i++)
{
if ((pc09->Entries [i].wSubSectionType == dType) &&
(j++ == dIndex))
{
pce = pc09->Entries + i;
break;
}
}
}
return pce;
}
// -----------------------------------------------------------------
PCV_PUBSYM WINAPI imgCvSymbols (PCV_NB09 pc09,
PDWORD pdCount,
PDWORD pdSize)
{
PCV_ENTRY pce;
PCV_PUBSYM pcp1;
DWORD i;
DWORD dCount = 0;
DWORD dSize = 0;
PCV_PUBSYM pcp = NULL;
if ((pce = imgCvEntry (pc09, sstGlobalPub, 0)) != NULL)
{
pcp = CV_PUBSYM_DATA ((PBYTE) pc09
+ pce->lSubSectionOffset);
dSize = pce->dSubSectionSize;
for (i = 0; dSize - i >= CV_PUBSYM_;
i += CV_PUBSYM_SIZE (pcp1))
{
pcp1 = (PCV_PUBSYM) ((PBYTE) pcp + i);
if (dSize - i < CV_PUBSYM_SIZE (pcp1)) break;
if (pcp1->Header.wRecordType == CV_PUB32) dCount++;
}
}
if (pdCount != NULL) *pdCount = dCount;
if (pdSize != NULL) *pdSize = dSize;
return pcp;
}
列表1-20. imgCvEntry()和imgCvSymbols()函数
CodeView符号
列表1-20底部的imgCvSymbols()函数将返回一个指向首个CodeView符号记录的指针。sstGlobalPub子节包含一个长度确定的CV_SYMHASH表头,紧随其后的是一组长度可变的CV_PUBSYM记录。列表1-21给出了这两个结构的定义。首先,imgCvSymbols()调用imgCvEntry()来找出CV_ENTRY,该结构的wSubSectionType成员将被设置为sstGlobalPub。如果imgCvEntry()返回一个CV_ENTRY结构,则将使用列表1-4底部的CV_PUBSYM_DATA()宏来跳过前导的CV_SYMHASH结构。最后,imgCvSymbols()通过遍历CV_PUBSYM记录列表来统计符号的个数,并使用CV_PUBSYM_SIZE()宏(参见列表1-21)计算每个记录的大小。
CV_PUBSYM列表和OMF对象文件的内容有些相似。前面已经提到过,一个OMF数据流由长度可变的记录组成,每个记录的开始位置的第一个字节为标志,紧随其后的一个WORD存放的是该记录的大小。CV_PUBSYM记录与之类似。CV_PUBSYM记录的开始位置是一个OMF_HEADER结构,该结构由wRecordSize和wRecordType成员构成。可看出这OMF非常相似,不同之处是标志字节之后的存放长度的WORD被扩展为16位。CV_PUBSYM结构的最后一部分是符号名,该符号名采用的是PASCAL格式,这一格式是OMF记录常用的格式。PASCAL格式的字符串的第一个字节用来记录该字符串的长度,其后的8位用来存储字符。和C风格的字符串不同,它并不以0表示结束。在符号名结束之后,CV_PUBSYM还将占有其后的16个位,这样做是为了到达32位边界。这16位由OMF_HEADER结构中的wRecordSize成员使用。要特别注意的是,wRecordSize给出的CV_PUBSYM结构的大小,并不包括wRecordSize自己占用的空间。这也是列表1-21中的CV_PUBSYM_SIZE()宏会在wRecordSize之上再加上sizeof(WORD),以获取整个记录体的大小。
typedef struct _CV_SYMHASH
{
WORD wSymbolHashIndex;
WORD wAddressHashIndex;
DWORD dSymbolInfoSize;
DWORD dSymbolHashSize;
DWORD dAddressHashSize;
}
CV_SYMHASH, *PCV_SYMHASH, **PPCV_SYMHASH;