第一章 Windows 2000对调试技术的支持
翻译:Kendiv ( fcczj@263.net )
更新:Tuesday, May 03, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
.dbg文件的内部结构
Windows NT 4.0组件的符号化信息均保存在扩展名为.dbg的文件中。如果假设符号文件所在的根目录为:d:\winnt\symbols,则组件filename.ext的符号文件的全路径就是:d:\winnt\symbols\filename.dbg。例如,内核符号可以在文件d:\winnt\symbols\exe\ntoskrnl.dbg中找到。Windows 2000也使用.dbg文件。不过,在Windows 2000下符号化的信息被移动到了独立的.pdb文件中。因此,每个Windows 2000组件在符号文件根目录下都有一个相关的ext\filename.dbg和一个附加的ext\filename.pdb文件,除此之外,Windows NT 4.0和Windows 2000的.dbg文件的内容仍是相同的。
幸运的是,有关.dbg文件的内部信息的文档还是有一小部分的。Win32 SDK头文件winnt.h提供了核心的常量和类型定义,在MSDN中也有一些有关.dbg文件格式的很有帮助的文章。其中最具有启发性的是Matt Pietrek在1999年3月的MSJ(Microsoft Systems Journal,MSJ,现在该命位 MSDN Magazine)的“Under the Hood”专栏中发表的文章。基本上,一个.dbg文件包含一个文件头和数据节。这两部分的大小并不固定,并且将来可能会进一步划分。文件头中包含四个主要的分段(subsections):
1. 一个IMAGE_SEPARATE_DEBUG_HEADER结构,该结构以两个标志性字符“DI”开始。(见列表1-13)
2. 一个IMAGE_SECTION_HEADER类型的数组,该数组中的每个结构都位于对应组件的PE文件中。该数组的大小由IMAGE_SEPARATE_DEBUG_HEADER的NumberOfSections成员指定。
3. 一组以零结尾的ANSI字符串(每个ANSI字符串占8个字节),这些字符串均是导出符号的解码格式(undecorated form)。 IMAGE_SEPARATE_DEBUG_HEADER的ExportedNameSize成员指出了一共有多少个字符串。如果模块没有导出任何符号,ExportedNameSize将为0,并且该分段也将不存在。
4. 一个IMAGE_DEBUG_DIRECTORY类型的数组,这些结构用来描述随后部分的格式以及它们的位置。IMAGE_SEPARATE_DEBUG_HEADER的DebugDirectorySize成员给出了该数组的大小。
#define IMAGE_SEPARATE_DEBUG_SIGNATURE 0x4944 // "DI"
typedef struct _IMAGE_SEPARATE_DEBUG_HEADER
{
WORD Signature;
WORD Flags ;
WORD Machine;
WORD Characteristics;
DWORD TimeDateStamp;
DWORD Checksum;
DWORD ImageBase;
DWORD SizeOf Image;
DWORD NurnberOf Sections ;
DWORD ExportedNamesSize ;
DWORD DebugDirectorySize;
DWORD SectionAlignment;
DWORD Reserved [2 ];
}
IMAGE_SEPARATE_DEBUG_HEADER, *PIMAGE_SEPARATE_DEBUG_HEADER;
//-----------------------------------------------------------
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER
BYTE Name[IMAGE_SIZEOF_SHORT_NAME] ;
union
(
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOf RawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers ;
WORD NumberOf Relocations;
WORD NumberOf Linenumbers ;
DWORD Characteristics;
}
IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_DEBUG_TYPE_UNKNOWN 0
#define IMAGE_DEBUG_TYPE_COFF 1
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
#define IMAGE_DEBUG_TYPE_FPO 3
#define IMAGE_DEBUG_TYPE_MISC 4
#define IMAGE_DEBUG_TYPE_EXCEPTION 5
#define IMAGE_DEBUG_TYPE_FIXUP 6
#define IMAGE_DEBUG_TYPE_OMAP_TO_SRC 7
#define IMAGE_DEBUG_TYPE_OMAP_FROM_SRC 8
#define IMAGE_DEBUG_TYPE_BORLAND 9
#define IMAGE_DEBUG_TYPE_RESERVED10 10
#define IMAGE_DEBUG_TYPE_CLSID 11
//-----------------------------------------------------------
typedef struct _IMAGE_DEBUG_DIRECTORY
{
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Type;
DWORD SizeOfData;
DWORD AddressOfRawData;
DWORD PointerToRawData;
}
IMAGE_DEBUG_DIRECTORY , * PIMAGE_DEBUG_DIRECTORY ;
列表1-13. .dbg文件的文件头结构
由于文件头中的表头分段的大小不能确定,因此它们在.dbg文件中的绝对位置必须通过它们前面的分段的大小来计算出来。一个.dbg文件分析器通常采用如下算法:
l IMAGE_SEPARATE_DEBUG_HEADER结构总是位于文件的开始位置。
l 第一个IMAGE_SECTION_HEADER结构紧随IMAGE_SEPARATE_DEBUG_HEADER结构之后,因此总是可以在文件偏移量为0x30的位置找到该结构。
l 将IMAGE_SECTION_HEADER结构的大小与该结构的个数相乘然后加上第一个IMAGE_SECTION_HEADER结构在文件中的偏移量就可得到第一个导出符号的偏移量。即第一个导出字符串的位置是:0x30+(NumberOfSections*0x28)。
l 通过将ExportedNameSize与导出符号分段的偏移量相加即可得到第一个IMAGE_DEBUG_DIRECTORY结构的位置。
l 通过IMAGE_DEBUG_DIRECTORY项可确定.dbg文件中剩余数据项的偏移量。PointerToRaw和SizeOfData成员分别指出了相关数据块的偏移量和大小。
列表1-13给出了IMAGE_DEBUG_TYPE_*结构的定义。这些结构反映了.dbg文件中所包含的多种数据格式。不过,Windows NT 4.0的符号文件通常仅包含这些结构中的四个:IMAGE_DEBUG_TYPE_COFF、IMAGE_DEBUG_TYPE_CODEVIEW、IMAGE_DEBUG_TYPE_FPO和IMAGE_DEBUG_TYPE_MISC。Windows 2000的.dbg文件通常会增加IMAGE_DEBUG_TYPE_OMAP_TO_SRC、IMAGE_DEBUG_TYPE_OMAP_FROM_SRC以及一个未文档化的类型ID为0x1000的结构。如果你仅对解析或浏览符号感兴趣,那么你只需要了解目录项结构:IMAGE_DEBUG_TYPE_CODEVIEW、IMAGE_DEBUG_TYPE_OMAP_TO_SRC和IMAGE_DEBUG_TYPE_OMAP_FROM_SRC。
本书的CD中包含一个示例DLL----w2k_img.dll,该DLL用于解析.dbg和.pdb文件并导出了多个用于开发内核调试工具的重要函数。可在本书的\src\w2k_img目录下找到该DLL的源代码。w2k_img.dll的一个重要属性是:它所有Win32平台上都可以运行。这不只包括Windows 2000、Windows NT 4.0还包括Windows 9x。像所有Win32世界中的好市民一样,这个DLL的每个函数为支持ANSI和Unicode字符串均提供了独立的接口。默认情况下,客户端使用ANSI版的函数。如果应用程序的源文件中包含了#define UICODE,那么将选择Unicode版的函数。运行于Win32平台上的程序最好选择ANSI版的函数。针对Windows NT/2000开发的程序则可选择Unicode版的函数以获取更好的性能。
在本书CD中还包含一个名为“SBS Windows 2000 CodeView Decompiler”的示例程序,可在CD的\src\w2k_cv目录下找到该程序的Visual C/C++项目文件。该程序是一个简单的用于分析.dbg和.pdb文件,并在Windows控制台中显示它们的内容。在阅读本节时,你可以使用该程序来查看我们正在讨论的这些数据结构。w2k_cv.exe大量使用了w2k_img.dll中的API函数。
列表1-14给出了w2k_img.h中定义的一个最基本的数据结构---IMG_DBG,该结构是由.dbg文件头中的前两个分段串联而成,也就是说,该结构由一个大小固定的基本表头和一组PE节的表头构成。给定PE节表头的数目就可通过IMG_DBG__()宏计算出该结构的实际大小。这一大小还确定了导出符号节(exported-names subsections)在文件中的偏移量。
W2k_img.dll中有几个函数需要一个指向已初始化的IMG_DBG结构的指针。imgDbgLoad()函数可分配一个IMG_DBG结构,并对该结构进行适当的初始化(该函数会用指定的.dbg文件的内容填充该结构)。imgDbgLoad()会对数据进行严格的完整性检查以确定指定的.dbg文件是有效和完整的。imgDbgLoad()函数返回的IMG_DBG结构可传递给多个分析函数,通过这些分析函数我们可得到一些经常使用的线性地址。例如,imgDbgExports()函数可计算出导出符号节(该节紧随IMAGE_SECTION_HEADER数组之后)的线性地址。该函数还可通过扫描整个导出符号节来统计有效符号名的个数,并可通过pdcount参数来返回统计的结果(可选)。
typedef struct _IMG_DBG
{
IMAGE_SEPARATE_DEBUG_HEADER Header;
IMAGE_SECTION_HEADER aSections[];
}
IMG_DBG, *PIMG_DBG, **PPIMG_DBG;
#define IMG_DBG_ sizeof(IMG_DBG)
#define IMG_DBG__(_n) (IMG_DBG_+((_n)*IMAGE_SECTION_HEADER_))
#define IMG_DBG_DATA(_p,_d) ((PVOID)((PBYTE)(_p) + (_d)->PointerToRawData))
列表1-14. IMG_DBG结构以及相关的宏定义
PVOID WINAPI imgDbgLoadA (PBYTE pbPath,
PDWORD pdSize)
{
DWORD dOffset = (pdSize != NULL ? *pdSize : 0);
DWORD dSize = dOffset;
PBYTE pbData = imgFileLoadA (pbPath, &dSize);
if ((pbData != NULL) &&
(!imgDbgVerify ((PIMG_DBG) (pbData + dOffset), dSize)))
{
pbData = imgMemoryDestroy (pbData);
}
if (pdSize != NULL) *pdSize = dSize;
return pbData;
}
// -----------------------------------------------------------------
PVOID WINAPI imgDbgLoadW (PWORD pwPath,
PDWORD pdSize)
{
DWORD dOffset = (pdSize != NULL ? *pdSize : 0);
DWORD dSize = dOffset;
PBYTE pbData = imgFileLoadW (pwPath, &dSize);
if ((pbData != NULL) &&
(!imgDbgVerify ((PIMG_DBG) (pbData + dOffset), dSize)))
{
pbData = imgMemoryDestroy (pbData);
}
if (pdSize != NULL) *pdSize = dSize;
return pbData;
}
// -----------------------------------------------------------------
PBYTE WINAPI imgDbgExports (PIMG_DBG pid,
PDWORD pdCount)
{
DWORD i, j;
DWORD dCount = 0;
PBYTE pbExports = NULL;
if (pid != NULL)
{
pbExports = (PBYTE) pid->aSections
+ (pid->Header.NumberOfSections
* IMAGE_SECTION_HEADER_);
for (i = 0; i < pid->Header.ExportedNamesSize; i = j)
{
if (!pbExports [j = i]) break;
while ((j < pid->Header.ExportedNamesSize) &&
pbExports [j++]);
if ((j > i) && (!pbExports [j-1])) dCount++;
}
}
if (pdCount != NULL) *pdCount = dCount;
return pbExports;
}
列表1-15. imgDbgLoad()和imgDbgExports()函数
列表1-16定义了两个可根据ID(这里的ID形如:IMAGE_DEBGU_TYPE_*)来定位相应的调试目录项(debug directory entry)的API函数。imgDbgDirectories()返回IMAGE_DEBUG_DIRECTORY数组的基地址,imgDbgDirectory()返回指向给定ID所对应的第一个目录项的指针,如果不存在这样的目录项,则返回NULL。
PIMAGE_DEBUG_DIRECTORY WINAPI imgDbgDirectories (PIMG_DBG pid,
PDWORD pdCount)
{
DWORD dCount = 0;
PIMAGE_DEBUG_DIRECTORY pidd = NULL;
if (pid != NULL)
{
pidd = (PIMAGE_DEBUG_DIRECTORY)
((PBYTE) pid
+ IMG_DBG__ (pid->Header.NumberOfSections)
+ pid->Header.ExportedNamesSize);
dCount = pid->Header.DebugDirectorySize
/ IMAGE_DEBUG_DIRECTORY_;
}
if (pdCount != NULL) *pdCount = dCount;
return pidd;
}
// -----------------------------------------------------------------
PIMAGE_DEBUG_DIRECTORY WINAPI imgDbgDirectory (PIMG_DBG pid,
DWORD dType)
{
DWORD dCount, i;
PIMAGE_DEBUG_DIRECTORY pidd = NULL;
if ((pidd = imgDbgDirectories (pid, &dCount)) != NULL)
{
for (i = 0; i < dCount; i++, pidd++)
{
if (pidd->Type == dType) break;
}
if (i == dCount) pidd = NULL;
}
return pidd;
}
列表1-16. imgDbgDirectories()和imgDbgDirectory() API函数
imgDbgDirectories()函数可用来查找.dbg文件中的CodeView数据。列表1-17中的imgDbgCv()函数完成了这一任务。imgDbgCv()函数使用IMAGE_DEBUG_TYPE_CODEVIEW调用imgDbgDirectories(),并使用IMG_DBG_DATA()宏将IMAGE_DEBUG_DIRECTORY项提供的偏移量转化为绝对线性地址。该宏只是简单的将IMG_DBG结构的基地址与给定的偏移量相加然后再将结果转型(typecast)为PVOID类型的指针。如果pdSize参数不为NULL,则imgDbgCv()将CodeView子节的大小保存到该参数中。接下来我们将讨论CodeView数据的内部结构。
针对其他数据子节(data subsection)的函数非常类似。列表1-18给出了imgDbgOmapToSrc()和imgDbgOmapFromSrc()函数以及它们使用的OMAP_TO_SRC和OMAP_FROM_SRC结构。稍后,我们将使用这些结构来计算位于CodeView子节中的符号的线性地址。因为OMAP数据结构是一个长度固定的数组,所以这两个API函数并不返回子节的大小,而是计算数组中的项数。该项数将被保存到*pdcount参数中(如果该参数不为NULL的话)。
PCV_DATA WINAPI imgDbgCv (PIMG_DBG pid,
PDWORD pdSize)
{
PIMAGE_DEBUG_DIRECTORY pidd;
DWORD dSize = 0;
PCV_DATA pcd = NULL;
if ((pidd = imgDbgDirectory (pid, IMAGE_DEBUG_TYPE_CODEVIEW))
!= NULL)
{
pcd = IMG_DBG_DATA (pid, pidd);
dSize = pidd->SizeOfData;
}
if (pdSize != NULL) *pdSize = dSize;
return pcd;
}
列表1-17. imgDbgCv()函数
typedef struct _OMAP_TO_SRC
{
DWORD dTarget;
DWORD dSource;
}
OMAP_TO_SRC, *POMAP_TO_SRC, **PPOMAP_TO_SRC;
#define OMAP_TO_SRC_ sizeof (OMAP_TO_SRC)
// -----------------------------------------------------------------
typedef struct _OMAP_FROM_SRC
{
DWORD dSource;
DWORD dTarget;
}
OMAP_FROM_SRC, *POMAP_FROM_SRC, **PPOMAP_FROM_SRC;
#define OMAP_FROM_SRC_ sizeof (OMAP_FROM_SRC)
// -----------------------------------------------------------------
POMAP_TO_SRC WINAPI imgDbgOmapToSrc (PIMG_DBG pid,
PDWORD pdCount)
{
PIMAGE_DEBUG_DIRECTORY pidd;
DWORD dCount = 0;
POMAP_TO_SRC pots = NULL;
if ((pidd = imgDbgDirectory (pid,
IMAGE_DEBUG_TYPE_OMAP_TO_SRC))
!= NULL)
{
pots = IMG_DBG_DATA (pid, pidd);
dCount = pidd->SizeOfData / OMAP_TO_SRC_;
}
if (pdCount != NULL) *pdCount = dCount;
return pots;
}
// -----------------------------------------------------------------
POMAP_FROM_SRC WINAPI imgDbgOmapFromSrc (PIMG_DBG pid,
PDWORD pdCount)
{
PIMAGE_DEBUG_DIRECTORY pidd;
DWORD dCount = 0;
POMAP_FROM_SRC pofs = NULL;
if ((pidd = imgDbgDirectory (pid,
IMAGE_DEBUG_TYPE_OMAP_FROM_SRC))
!= NULL)
{
pofs = IMG_DBG_DATA (pid, pidd);
dCount = pidd->SizeOfData / OMAP_FROM_SRC_;
}
if (pdCount != NULL) *pdCount = dCount;
return pofs;
}
列表1-18. imgDbgOmapToSrc()和imgDbgOmapFromSrc()函数