2023年1月

我一直在想,应用程序抛出未处理的异常和附加到进程的调试器之间会发生什么。显然这些信息就在我眼皮底下,但我是瞎子。Windows调试器关于事后调试的文档包含了您想要知道的所有详细信息。

生成公有调试符号有2种形式:1.编译链接过程中生成;2.从私有调试符号文件中剥离生成

编译链接过程中生成

方式一

工程属性-Linker-Debuging-strip private symbols

 

 

方式二

工程属性-Linker-Command Line-Additional Options 增加选项/PDBSTRIPPED:xxx.pdb 

简介

调试应用程序时,调试器必须加载可执行模块的符号,以便能够显示有意义的调用堆栈、当前源代码行、变量值等。如果您曾经调试过在另一个系统上创建的小型转储,那么您已经知道除了符号之外,调试器还需要访问创建转储时由应用程序加载的相同版本的模块。如果调试器找不到与模块完全相同的版本(即匹配的模块),则无法加载模块的符号,从而严重限制了成功调试的可能性。在本文中,我们将讨论VS.NET和WinDbg调试器用来识别和定位匹配模块的规则。我们将看到如何告诉调试器在哪里查找匹配的模块。我们还将讨论无法找到匹配模块的情况,并尝试找到解决方法。

模块匹配

调试器如何决定模块是否匹配?他们如何知道需要什么版本的模块?让我们看看Minidump里面。每个小型转储都包含转储进程加载的模块列表。此列表存储为小型转储模块结构的数组(如DbgHelp文档中所述)。此结构声明如下:

typedef struct_MINIDUMP_MODULE {
ULONG64 BaseOfImage;
ULONG32 SizeOfImage;
ULONG32 CheckSum;
ULONG32 TimeDateStamp;
RVA ModuleNameRva;
VS_FIXEDFILEINFO VersionInfo;
MINIDUMP_LOCATION_DESCRIPTOR CvRecord;
MINIDUMP_LOCATION_DESCRIPTOR MiscRecord;
ULONG64 Reserved0;
//Reserved for future use. ULONG64 Reserved1; //Reserved for future use. } MINIDUMP_MODULE, *PMINIDUMP_MODULE;

简介

在过去的几年中,崩溃转储成为我们调试活动的一个重要部分。当我们的软件在客户的机器出现故障时,创建应用程序状态的快照并使用在开发人员机器上运行的传统调试器对其进行分析的可能性是非常宝贵的。第一代崩溃转储(通常称为“完全用户转储”)捕获了整个进程虚拟内存的内容。尽管对于事后调试毫无疑问是有用的,但这样的转储常常变得如此巨大,以至于不可能或至少不方便将它们以电子方式传输给软件开发人员。此外,没有以编程方式创建此类转储的公共API,我们必须依赖外部工具(如Dr.Watson或Userdump)来创建它们。
一个新的崩溃转储系列,叫做“minidumps”,与Windows XP一起出现在我们面前。minidump是高度可定制的。在最流行的配置中,小型转储包含的信息刚好足以恢复失败进程中所有线程的调用堆栈,并在失败时检查本地变量的值。这种转储很小(通常只有几千字节),因此很容易以电子方式将它们传输给软件开发人员。但如果需要,小型转储可以包含比旧式崩溃转储更多的信息(例如,小型转储可以包含有关进程使用的内核对象的信息)。此外,可再发行的DbgHelp.dll公开了一个用于以编程方式创建小型转储的公共API,我们不再依赖外部工具。
小型转储的可定制性给我们带来了一个问题:我们需要多少关于应用程序状态的信息才能有效地进行调试,同时使小型转储尽可能小?虽然对调用堆栈和局部变量值的了解通常足以调试简单的访问冲突,但更困难的问题将需要额外的信息。例如,我们可能需要查看全局变量的值,检查堆的完整性,或者分析进程虚拟内存的布局。同时,如果可执行文件本身在开发人员的计算机上可用,则可执行模块的代码部分可能是多余的。
幸运的是,DbgHelp函数(MiniDumpWriteDump和MiniDumpCallback)提供了这样的控制级别,甚至更多。在本文中,我们将探讨如何使用这些函数来创建小转储,这些小转储虽然小,但仍然包含足够的信息,可以进行有效的调试。我们还将看到小型转储中可以包含哪些类型的数据,以及如何使用流行的调试器(WinDbg和VS.NET)来查看这些数据。

Minidump Types

让我们从一些代码开始。图1包含MiniDumpWriteDump函数的声明,图2显示了如何使用该函数创建一个简单的minidump。

 Figure 1:

BOOL MiniDumpWriteDump(
HANDLE hProcess,
DWORD ProcessId,
HANDLE hFile,
MINIDUMP_TYPE DumpType,
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);

Figure
2:void CreateMiniDump( EXCEPTION_POINTERS*pep )
{
//Open the file HANDLE hFile= CreateFile( _T("MiniDump.dmp"), GENERIC_READ |GENERIC_WRITE,0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );if( ( hFile != NULL ) && ( hFile !=INVALID_HANDLE_VALUE ) )
{
//Create the minidump MINIDUMP_EXCEPTION_INFORMATION mdei;

mdei.ThreadId
=GetCurrentThreadId();
mdei.ExceptionPointers
=pep;
mdei.ClientPointers
=FALSE;

MINIDUMP_TYPE mdt
=MiniDumpNormal;

BOOL rv
=MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(),
hFile, mdt, (pep
!= 0) ? &mdei : 0, 0, 0);if( !rv )
_tprintf( _T(
"MiniDumpWriteDump failed. Error: %u \n"), GetLastError() );else_tprintf( _T("Minidump created.\n") );//Close the file CloseHandle( hFile );

}
else{
_tprintf( _T(
"CreateFile failed. Error: %u \n"), GetLastError() );
}

}

函数MiniDumpCallback

如果要自定义MINIDUMP类型标志无法访问的MINIDUMP的内容,可以使用MiniDumpCallback函数。这是一个用户定义的回调,当MiniDumpWriteDump需要用户决定是否将某些数据包含到minidump中时,它将被调用。借助此功能,我们可以完成以下任务:

  • 从minidump的模块信息中排除可执行模块(全部或部分)
  • 从minidump的线程信息中排除线程(全部或部分)
  • 将用户指定范围内存的内容包含到小型转储中

让我们看看MiniDumpCallback函数的声明

BOOL CALLBACK MiniDumpCallback(
PVOID CallbackParam,
constPMINIDUMP_CALLBACK_INPUT CallbackInput,
PMINIDUMP_CALLBACK_OUTPUT CallbackOutput
);