当程序出现某些问题而无法重现时,我们需要通过分析dump文件来查找程序崩溃的原因。本文将从多个方面对分析dump文件做详细的阐述。
一、转储文件内容
如何查看dump文件的内容?我们可以使用某些工具或者通过编写代码实现。
以下是使用C++读取dump文件并输出其内容的示例:
#include <Windows.h> #include <Dbghelp.h> #include <iostream> const DWORD MaxDumpSize = 64 * 1024 * 1024;//最大处理文件大小,为64M。 void DumpFileContent(const char* dumpFilePath) { HANDLE hFile = CreateFileA(dumpFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);//以只读的方式打开dump文件 if (hFile == INVALID_HANDLE_VALUE) { std::cout << "Open dump file failed!" << std::endl; return; } HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);//创建文件映射 if (hMapping == NULL) { CloseHandle(hFile); std::cout << "CreateFileMapping failed!" << std::endl; return; } LPVOID mapped = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);//内存映射文件 if (mapped == NULL) { CloseHandle(hMapping); CloseHandle(hFile); std::cout << "MapViewOfFile failed!" << std::endl; return; } const DWORD dumpSize = GetFileSize(hFile, NULL); const DWORD viewSize = std::min(dumpSize, MaxDumpSize); std::cout << "file size: " << dumpSize << "; view size: " << viewSize << std::endl; for (DWORD i = 0; i < viewSize; i++) { const BYTE byte = *((BYTE*)mapped + i); std::cout << std::hex << static_cast<unsigned int>(byte) << " "; } std::cout << std::endl; UnmapViewOfFile(mapped); CloseHandle(hMapping); CloseHandle(hFile); } int main() { DumpFileContent("test.dmp"); return 0; }
运行上述代码后可以将输出为dump文件的16进制格式。这样的输出对于一些低级错误或者复杂的bug是有用的。
二、查找错误位置
当程序崩溃时,我们需要查找错误的位置。我们可以使用VS的调试器来查找错误位置,也可以使用WinDbg等工具进行分析。
以下是使用WinDbg分析dump文件并查找错误位置的示例:
!analyze -v
使用上述命令可以分析dump文件并输出详细的错误信息。例如:
FAULTING_IP: +0 000007fe`fafffa55 ?? ??? EXCEPTION_RECORD: ffffffffffffffff -- (.exr 0xffffffffffffffff) ExceptionAddress: 000007fefafffa55 ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 0000000000000000 Parameter[1]: 00000000ffffffff Attempt to read from address ffffffff DEFAULT_BUCKET_ID: INVALID_POINTER_READ PROCESS_NAME: test.exe ERROR_CODE: (NTSTATUS) 0xc0000005 - EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - EXCEPTION_PARAMETER1: 0000000000000000 EXCEPTION_PARAMETER2: 00000000ffffffff READ_ADDRESS: ffffffff FOLLOWUP_IP: test!main+1d [c:\test.cpp @ 12] 00000001`400115dd c3 ret APP: test.exe FAULTING_THREAD: 00000000000005f8 BUGCHECK_STR: APPLICATION_FAULT_INVALID_POINTER_READ PRIMARY_PROBLEM_CLASS: APPLICATION_FAULT LAST_CONTROL_TRANSFER: from 000007fefa3365f1 to 000007fefafffa55 STACK_TEXT: 00000000`00eddc48 000007fe`fa3365f1 : 00000000`00e00c00 00000000`00000000 00000000`00000000 00000000`00000000 : 0x000007fe`fafffa55 00000000`00eddc50 00000001`400115dd : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : test!main+0x1d [c:\test.cpp @ 12] 00000000`00eddc80 00007ffc`c0d37d5d : 00000000`00eddeb8 00000000`77cf5961 00000000`00000000 00000000`00000000 : test!__tmainCRTStartup+0x159 [f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c @ 618] 00000000`00eddcf0 00007ffc`c2663034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0xd 00000000`00eddd20 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
上述结果提供了引起错误的地址和模块、错误类型、错误线程等信息,可以较快地定位到错误位置。
三、查看线程堆栈
线程堆栈信息可以告诉我们程序在出现错误时的执行状态,可以使用VS的调试器来查看线程堆栈信息,也可以使用WinDbg等工具进行分析。
以下是使用WinDbg查看线程堆栈信息的示例:
~#
使用上述命令可以输出线程列表,例如:
. 0 Id: 29a4.3e68 Suspend: 0 Teb: 00000000`00e4a000 Unfrozen # Child-SP RetAddr Call Site 00 00000000`02acf8d8 000007fe`fa3365f1 user32!NtUserGetMessage+0x14 01 00000000`02acf8e0 00000001`400115dd test!main+0x1d [c:\test.cpp @ 12] 02 00000000`02acf910 00007ffc`c0d37d5d test!__tmainCRTStartup+0x159 [f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c @ 618] 03 00000000`02acf980 00007ffc`c2663034 kernel32!BaseThreadInitThunk+0xd 04 00000000`02acfac0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
上述结果提供了线程的堆栈信息,方便我们查看程序在出现错误时的执行状态。
四、查看堆内存分配状态
如果程序崩溃时存在内存泄漏等问题,我们需要查看堆内存分配状态以便找到内存泄漏的根源。我们可以使用VS的调试器来查看内存泄漏情况,也可以使用WinDbg等工具进行分析。
以下是使用WinDbg查看堆内存分配状态的示例:
!heap -s
使用上述命令可以输出堆信息和分配情况统计,例如:
**************************************************************************************************************** HEAP SUMMARY: **************************************************************************************************************** ProcessHeaps 20 ----> Heap 0000000000090000 **** 0000000000010220 - 000000000002d91c ( 2902) 48000 bytes ... **** 00000000ffbaffa0 - 00000000ffba0000 ( 144) 400 bytes ----> Heap 00000000099b0000 **** 00000000099b0240 - 0000000009a8a188 ( 82261) 1688000 bytes ... **** 00000000099ec6f0 - 00000000099ec720 ( 9) 36 bytes Large blocks (>= 512 KiB) detected: 1 Total heap size: 9555940 bytes Total committed: 8491820 bytes Total free: 23436 bytes Max free block: 23436 bytes Largest reserve: 9192960 bytes Commited range: 0000000000090000 - 0000000009a83ff8 Uncommited range: 0000000009a83ff8 - 0000000009c00000 Virtual query: 001b0000 - 001c0000 00001000 MEM_PRIVATE MEM_COMMIT **************************************************************************************************************** Statistical Analysis of the Heap Manager Stats available at Global Heap Stat **************************************************************************************************************** Heap Count Space(@) Space($) Map Space(@) Map Space($) Uncommitted($) settable size($) ------------------------------------------------------------------------------------------------------------------------ 0000000000090000 9662 4861988 $ 47490 2474836480 0 229376 00000000099b0000 49 1536 $ 23 0 0 9534976
上述结果列出了堆的使用情况和统计数据,方便我们查看堆内存分配状态。
五、查看函数调用树
当程序出现错误时,我们可以使用函数调用树查看程序执行的路径。我们可以通过调用栈来查看函数调用树,也可以使用VS的调试器或者WinDbg等工具进行分析。
以下是使用VS的调试器查看函数调用树的示例:
在调试器的调用堆栈窗口中进行查看,该窗口用于显示所有停止的线程的调用堆栈,可在调用堆栈窗口中选择函数调用树,以了解程序执行的路径和顺序。
六、结语
本文介绍了如何从多个方面分析dump文件,包括转储文件内容、查找错误位置、查看线程堆栈、查看堆内存分配状态和查看函数调用树等。
dump文件分析还有许多内容,需要我们不断努力学习和实践。希望本文对读者有所帮助。