[译]具有调用堆栈的自身崩溃Minidump
By robot-v1.0
本文链接 https://www.kyfws.com/applications/own-crash-minidump-with-call-stack-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 12 分钟阅读 - 5579 个词 阅读量 0[译]具有调用堆栈的自身崩溃Minidump
原文地址:https://www.codeproject.com/Articles/8810/Own-Crash-Minidump-with-Call-Stack
原文作者:Vladimir Sedach
译文由本站 robot-v1.0 翻译
前言
How to create and analyse your own minidump.
如何创建和分析自己的小型转储.
介绍(Introduction)
我们将说明如何在崩溃时或任何给定时刻使用Call Stack创建自己的小型转储.该演示程序创建一个转储(如图所示),并尝试使用(We explain how to create your own minidump with Call Stack on crash or at anygiven moment. The demo program creates a dump (shown on the picture) and triesto create the system one with the) MiniDumpWriteDump()
的功能(function of)DbgHelp.dll(DbgHelp.dll)没有单独安装的Windows XP以外的系统不支持.system dump.本文介绍了如何解释自己的小型转储,以及需要对项目设置进行哪些修改.我们竞争对手的迷你转储(.System dump is not supported in systems other than Win XP without individualinstallation. The article shows how to interpret the own minidump, and whatmodifications to your project settings are needed. Our competitor’s minidumpproduced by) MiniDumpWriteDump()
还将简要讨论.我们在整篇文章中都引用了演示项目,因此构建它可能是明智的.(is also discussed in brief. Werefer to the demo project throughout the article, so it could be wise to buildit.)
调用堆栈(The Call Stack)
调用堆栈是转储的核心部分.调用堆栈是堆栈中分配的以下帧的列表:(Call Stack is the central part of our dump. Call Stack is a list of thefollowing frames allocated in stack:)
typedef struct STACK_FRAME
{
STACK_FRAME * Ebp; //address of the calling function frame
PBYTE Ret_Addr; //return address
DWORD Param[0]; //parameter list - could be empty
} STACK_FRAME, * PSTACK_FRAME;
每个函数调用都会将这样的框架添加到列表中,每个从函数返回的都将删除该框架.因此,这是一个LIFO列表.当前帧的地址在(Each function call adds such a frame to the list, each return from functiondeletes the frame. So, it is a LIFO list. The address of the current frame is inthe) Ebp
寄存器.(register.) Ebp
调用函数的寄存器由被调用函数保存到框架中.参数和返回地址由调用语句存储到框架中.所以,如果我们知道当前(registerof the calling function is being saved to the frame by the called function.Parameters and return address are stored to the frame by the call statement. So,if we know the current) Ebp
值,我们可以展开整个列表.例外情况是,此值位于(value, we can unwind thewhole list. On exception, this value is in the) EXCEPTION_POINTERS
由返回的结构(structure returned by the) GetExceptionInformation()
调用,否则我们可以获取当前(call.Otherwise, we can get the current) Ebp
从当前函数的第一个参数的地址中减去帧大小(八个字节).(subtracting theframe size (eight bytes) from the address of the first parameter of the currentfunction.) Get_Call_Stack()
填充其(fills its) Str
参数与调用堆栈信息.这是(parameterwith Call Stack info. This is a simplified version of the) Get_Call_Stack()
演示项目.(of the demo project.)
int WINAPI Get_Call_Stack(PEXCEPTION_POINTERS pException, PCHAR Str)
{
CHAR Module_Name[MAX_PATH];
PBYTE Module_Addr;
int Str_Len;
PSTACK_FRAME Ebp;
if (pException)
Ebp = (PSTACK_FRAME)pException->ContextRecord->Ebp;
else
// Frame address of Get_Call_Stack()
Ebp = (PSTACK_FRAME)&pException - 1;
Str[0] = 0;
Str_Len = 0;
for (int Ret_Addr_I = 0;
(Ret_Addr_I < 20) && !IsBadReadPtr(Ebp, sizeof(PSTACK_FRAME)) &&
!IsBadCodePtr(FARPROC(Ebp->Ret_Addr));
Ret_Addr_I++, Ebp = Ebp->Ebp)
{
// Find the module by a return address inside that module
if (Get_Module_By_Ret_Addr(Ebp->Ret_Addr, Module_Name, Module_Addr))
{
// Save module's address and path
Str_Len += wsprintf(Str + Str_Len,
NL "%08X %s",
Module_Addr, Module_Name);
// Save offset of return address
Str_Len += wsprintf(Str + Str_Len,
NL " +%08X",
Ebp->Ret_Addr - Module_Addr);
// Save 5 parameters. We don't know the real number of parameters!
if (!IsBadReadPtr(Ebp, sizeof(PSTACK_FRAME) + 5 * sizeof(DWORD)))
{
Str_Len += wsprintf(Str + Str_Len, " (%X, %X, %X, %X, %X)",
Ebp->Param[0], Ebp->Param[1],
Ebp->Param[2], Ebp->Param[3], Ebp->Param[4]);
}
}
}
return Str_Len;
} //Get_Call_Stack
的(The) Get_Module_By_Ret_Addr()
函数通过模块内部的areturn地址查找模块,然后返回其路径和地址.它使用工具帮助库列出当前进程的所有模块.(function finds the module by areturn address inside that module, and returns its path and address. It uses theTool Help library to list all the modules of the current process.)
BOOL WINAPI Get_Module_By_Ret_Addr(PBYTE Ret_Addr,
PCHAR Module_Name, PBYTE & Module_Addr)
{
MODULEENTRY32 M = {sizeof(M)};
HANDLE hSnapshot = NULL;
Module_Name[0] = 0;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
if ((hSnapshot != INVALID_HANDLE_VALUE) &&
Module32First(hSnapshot, &M))
{
do
{
if (DWORD(Ret_Addr - M.modBaseAddr) < M.modBaseSize)
{
lstrcpyn(Module_Name, M.szExePath, MAX_PATH);
Module_Addr = M.modBaseAddr;
break;
}
} while (Module32Next(hSnapshot, &M));
}
CloseHandle(hSnapshot);
return !!Module_Name[0];
} //Get_Module_By_Ret_Addr
项目设置修改(Project Settings Modification)
由于发行版主要需要minidump,因此我们仅讨论该版本.如果您想使用(Since minidump is primarily needed for release version, we discuss only thatversion. The same settings can be recommended if you want to use the) MiniDumpWriteDump()
的功能(function of)DbgHelp.dll(DbgHelp.dll).应用所有这些修改后,程序的大小和速度只会微不足道地改变,很可能根本不会改变.(. After applying all these modifications, the sizeand speed of your program would change only negligibly, most likely would notchange at all.)
程序数据库((The Program Database () /Zi
生成的编译器选项(compiler option to generate).pdb(.pdb*)*文件)对于我们自己的小型转储和系统之一都是必需的:(*file) is needed for both our own minidump and the system one:*)
项目菜单»设置» C/C ++选项卡»类别:常规»调试信息:程序数据库(Project Menu » Settings » C/C++ Tab » Category: General» Debug info: Program Database)
添加(Add the) /Oy-
编译器优化选项(以及现有的(compiler optimization option (along with theexistent) /O1
要么(or) /O2
选项)以保证所有功能的堆栈框架创建:(option) to guarantee stack framecreation for all the functions:)
项目菜单»设置» C/C ++选项卡»项目选项:/Oy-(Project Menu » Settings » C/C++ Tab » Project Options:/Oy-)
选中以下复选框以添加链接器的调试信息((Check the following checkboxes to add Linker’s debug info () /debug
)和MAP文件:()and MAP file:)
项目菜单»设置»链接选项卡»类别:常规»生成调试信息,生成映射文件(Project Menu » Settings » Link Tab » Category: General» Generate debug info, Generate mapfile)
添加(Add the) /opt:ref
和(and) /mapinfo:lines
链接器选项可消除从未从所得的可执行模块中引用的功能和/或数据,并将行号信息包括到MAP文件中:(Linker optionsto eliminate functions and/or data that is never referenced from the resultingexecutable module, and include line-number info to the MAP file:)
项目菜单»设置»链接选项卡»项目选项:/opt:ref/mapinfo:lines(Project Menu » Settings » Link Tab » Project Options:/opt:ref /mapinfo:lines)
在源代码中查找呼叫堆栈偏移位置(Finding Call Stack offset location in the Source code)
我们可以通过三种不同的方式来做到这一点.首先,两种方法都依赖于MAP文件((We can do it in three different ways. First, two methods rely on the MAP file()MiniDump.map(MiniDump.map))由Linker创建,第三个方法与debugsession一起使用.假设我们在本文开头显示了崩溃转储.偏移量16F3中发生异常() created by Linker, the third method is used with debugsession. Suppose we’ve got a crash dump shown on the picture at the beginning ofthe article. An exception occurred at offset 16F3 in)*迷你转储文件(MiniDump.exe)*我们的演示项目的模块.(moduleof our demo project.)
方法1:MAP文件的行号表(Method 1: Line numbers table of the MAP file)
行号表位于MAP文件的末尾:(The Line numbers table is located at the end of the MAP file:)
Line numbers for \MiniDump.obj(C:\SED\WFun\MiniDump.cpp) segment .text
68 0001:00000000 72 0001:0000000a 74 0001:00000029 76 0001:00000037
79 0001:0000003f 83 0001:00000056 89 0001:0000006d 85 0001:00000093
这里(Here)68(68)0001:00000000(0001:00000000)手段(means)<行号>():<segment中的偏移量>(:).因此,首先我们需要在模块中找到这些拼合的偏移量.可以通过考虑模块的加载地址和段的起始地址来完成:(. So, first we need to find thesegment’s offset in the module. It could be done by taking into account the loadaddress of the module and the start address of the segment:)
Preferred load address is 00400000
Address Publics by Value Rva+Base
0001:00000000 ?Get_Module_By_Ret_Addr@@YGHPAEPADAAPAE@Z 00401000
0001:000000d0 ?Get_Call_Stack@@YGHPAU_EXCEPTION_POINTERS@@PAD@Z 004010d0
细分的开始(The segment’s start)**0001:00000000(0001:00000000)**地址是(address is)00401000(00401000),并且偏移量为00401000-00400000 =1000.现在我们准备返回"线号"表,并在模块中找到偏移量16F3的行号,该行号对应于偏移量16F3-1000 =(, and theoffset is 00401000 - 00400000 = 1000. Now we are ready to return to the Linenumbers table and find the line number of our offset 16F3 in the module, thatcorresponds to offset 16F3 - 1000 =)**000006F3(000006F3)**在细分中:(in the segment:)
328 0001:000006c9 329 0001:000006d5 334 0001:000006e0 335 0001:000006e3
337 0001:000006f7 338 0001:00000709 343 0001:00000710 344 0001:00000713
很容易发现(It’s easy to figure out that)**335(335)****0001:000006e3(0001:000006e3)**是罪魁祸首.(is the culprit.)
方法2:映射文件和程序集列表(Method 2: Map file and Assembly listing)
要生成程序集清单创建,我们需要设置:(To generate the assembly listing creation, we need to set:)
项目菜单»设置» C/C ++选项卡»类别:ListingFiles »清单文件类型:程序集,机器代码和源(Project Menu » Settings » C/C++ Tab » Category: ListingFiles » Listing file type: Assembly, Machine Code, and Source)
首先,我们在中找到偏移量16F3(Let’s first find our offset 16F3 in the)**公众价值(Publics by Value)**在MAP文件中列出:(list in theMAP file:)
Address Publics by Value Rva+Base
0001:00000000 ?Get_Module_By_Ret_Addr@@YGHPAEPADAAPAE@Z 00401000
0001:000000d0 ?Get_Call_Stack@@YGHPAU_EXCEPTION_POINTERS@@PAD@Z 004010d0
0001:00000240 ?Get_Version_Str@@YGHPAD@Z 00401240
0001:000002f0 ?Get_Exception_Info@@YGPADPAU_EXCEPTION_POINTERS@@@Z 004012f0
0001:00000580 ?Create_Dump@@YGXPAU_EXCEPTION_POINTERS@@HH@Z 00401580
0001:000006e0 ?Func_3@@YGXPAX0KH@Z 004016e0
0001:00000710 ?Func_2@@YGXPAX0KH@Z 00401710
该模块的地址为400000(请参见MAP文件中的"首选加载地址"),因此我们需要找出包含地址4016F3的函数.它是(The module’s address is 400000 (see “Preferred load address” in theMAP file), so we need to find out the function that contains address 4016F3. Itis)?Func_3 @@ YGXPAX0KH @ Z(?Func_3@@YGXPAX0KH@Z)****004016e0(004016e0)(对应于((corresponds to) Func_3
在源代码中),在函数4016F3中具有偏移量-004016e0 =(in the source code), with offset in the function 4016F3 - 004016e0 =)**00013(00013)**现在我们可以在程序集列表中按其名称找到该函数((.Now we can find the function by its name in the assembly listing ()MiniDump.cod(MiniDump.cod))和带有偏移量的命令()and the command with offset)00013(00013):(:)
?Func_3@@YGXPAX0KH@Z PROC NEAR ; Func_3, COMDAT
334 : {
00000 55 push ebp
00001 8b ec mov ebp, esp
335 : memcpy(Addr_1, Addr_2, Size);
00003 8b 4d 10 mov ecx, DWORD PTR _Size$[ebp]
00006 56 push esi
00007 8b 75 0c mov esi, DWORD PTR _Addr_2$[ebp]
0000a 8b c1 mov eax, ecx
0000c 57 push edi
0000d 8b 7d 08 mov edi, DWORD PTR _Addr_1$[ebp]
00010 c1 e9 02 shr ecx, 2
00013 f3 a5 rep movsd
难怪我们找到了相同的行号(No wonder we’ve found the same line number)**335(335)**与方法1相同.(as with Method 1.)
方法3:调试会话(Method 3: Debug Session)
使用F8,F10或F7键启动Debug会话.我们认为您之前已经做过很多次了.别忘了我们正在调试发行版.如果您使用的是MFC并想调试其代码,建议您进行更改(Start the Debug session with F8, F10, or F7 key. We suppose you’ve done thatmany times before. Don’t forget we’re debugging the release version. If you areusing MFC and want to debug its code, we’d recommend to change) NDEBUG
至(to) _DEBUG
在:(in:)
项目菜单»设置» C/C ++选项卡»类别:常规»预处理程序定义:(Project Menu » Settings » C/C++ Tab » Category: General» Preprocessor definitions:)
我们的任务仍然是找到偏移量16F3(Our task is still to find the offset 16F3 in)*迷你转储文件(MiniDump.exe)*和在(and in)MiniDump.cpp(MiniDump.cpp)激活反汇编窗口:(.Activate the Disassembly window:)
查看菜单»调试窗口»反汇编Alt + 8(View Menu » Debug Windows » Disassembly Alt+8)
由于此窗口使用地址(而不是偏移量),因此我们需要确定模块的地址:(Since this window uses addresses (not offsets), we need to determine themodule’s address:)
调试菜单»模块… »模块列表(Debug Menu » Modules… » Module List)
迷你转储文件(MiniDump.exe)模块的地址为400000(我们之前从未见过此数字),因此我们寻求的地址是(module’s address is 400000 (we have never seen thisnumber before), so our sought address is)004016F3(004016F3).现在我们非常接近我们的目标-输入(. Now we are very closeto our goal – enter)**004016F3(004016F3)**在:(in:)
编辑菜单»转到… Ctrl + G »转到:»地址:(Edit Menu » Go To… Ctrl+G » Go to what: » Address:)
用力按(Press hard the)**去(Go To)**按钮,然后向上滚动到该语句的开头:(button, and scroll up to the beginning of thestatement:)
335: memcpy(Addr_1, Addr_2, Size);
004016E3 8B 4D 10 mov ecx,dword ptr [ebp+10h]
004016E6 56 push esi
004016E7 8B 75 0C mov esi,dword ptr [ebp+0Ch]
004016EA 8B C1 mov eax,ecx
004016EC 57 push edi
004016ED 8B 7D 08 mov edi,dword ptr [Addr_1]
004016F0 C1 E9 02 shr ecx,2
004016F3 F3 A5 rep movs dword ptr [edi],dword ptr [esi]
顺便说一句,如果要在DLL中查找地址,请执行完全相同的操作:从"模块列表"中获取模块的地址,添加偏移量,然后转到此处.无需设置断点并执行模块.(By the way, if you want to find an address in a DLL, do exactly the same: getthe module’s address from the Module List, add the offset, and Go To there. Noneed for setting a break point and executing the module.)
在MiniDumpWriteDump(),DbgHelp.dll和WinDbg.exe上只说几句话(Just a few words on MiniDumpWriteDump(), DbgHelp.dll, and WinDbg.exe)
尽管在很多情况下,使用Call Stack进行转储已足够,但您也可以考虑使用(Though our dump with Call Stack is quite sufficient in many cases, you mayalso consider using the) MiniDumpWriteDump()
的功能(function of)DbgHelp.dll(DbgHelp.dll)在Windows XP/2003中,无需额外安装即可使用此功能,而在所有其他系统中,可以重新发行此功能.(.This function is available in Win XP/2003 without additional installation, andin all other systems as a redistributable.)DbgHelp.dll(DbgHelp.dll)和(and)WinDbg.exe(WinDbg.exe)(如果不使用VS.NET,则需要分析转储)可以在以下位置下载:((needed to analyze the dump if you are not using VS.NET) can be downloaded at) Windows调试工具(DebuggingTools for Windows) .别忘了将SDK与(. Don’t forget to include the SDK with)*dbghelp.h(dbghelp.h)*进入下载.如果您想知道全局变量的值,则系统小型转储尤其有价值.(into the download. The system minidump is especially valuable if you want toknow the value of your global variables.)
在演示程序中,我们首先检查是否(In the demo program, we first check to see if) MiniDumpWriteDump()
可用,然后调用它来创建一个与我们同名的小型转储(is available, then call it to create a minidump with the same name as of our).可执行程序(.exe*)*文件:(*file:*)
hDbgHelp = LoadLibrary("DBGHELP.DLL");
MiniDumpWriteDump_ = (MINIDUMP_WRITE_DUMP)GetProcAddress(hDbgHelp,
"MiniDumpWriteDump");
if (MiniDumpWriteDump_)
{
MINIDUMP_EXCEPTION_INFORMATION M;
HANDLE hDump_File;
CHAR Dump_Path[MAX_PATH];
M.ThreadId = GetCurrentThreadId();
M.ExceptionPointers = pException; //got by GetExceptionInformation()
M.ClientPointers = 0;
GetModuleFileName(NULL, Dump_Path, sizeof(Dump_Path));
lstrcpy(Dump_Path + lstrlen(Dump_Path) - 3, "dmp");
hDump_File = CreateFile(Dump_Path, GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
MiniDumpWriteDump_(GetCurrentProcess(), GetCurrentProcessId(),
hDump_File, MiniDumpNormal,
(pException) ? &M : NULL, NULL, NULL);
CloseHandle(hDump_File);
}
现在您可以运行(Now you can run)WinDbg.exe(WinDbg.exe)打开并解释(to open and interpret the)MiniDump.dmp(MiniDump.dmp):(:)
文件菜单»打开崩溃转储… Ctrl + D(File Menu » Open Crash Dump… Ctrl+D)
查看菜单»命令Alt + 1(View Menu » Command Alt+1)
输入(Enter).ecxr(.ecxr)(显示异常上下文记录)命令,并欣赏以下视图:((Display Exception Context Record) command, and enjoy theview:)
概要(Summary)
建议的调用堆栈创建方法可以在x86机器上以C/C ++和许多其他语言实现.(Proposed Call Stack creation method can be implemented in C/C++ and manyother languages on x86 machines.)
随意修改和使用演示源代码,无论您想要什么,都不要忘了给文章评分:)(Feel free to modify and use the demo source code for whatever you want, anddon’t forget to rate the article :))
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C++ VC6 WinXP Windows Win2K Visual-Studio Dev 新闻 翻译