Debugging 如何使用MiniDumpWriteDump获得有意义的堆栈跟踪

Debugging 如何使用MiniDumpWriteDump获得有意义的堆栈跟踪,debugging,crash,stack-trace,dbghelp,minidumpwritedump,Debugging,Crash,Stack Trace,Dbghelp,Minidumpwritedump,我试图通过编程生成堆栈跟踪。当我的用户发生崩溃,特别是随机崩溃时,很难说服他们完成转储过程,这样我就可以解决问题。在过去,一旦他们向我发送跟踪,我就会将其中的地址交叉引用到Intermediate/foo.map文件,以找出问题出在哪个函数上(这是最好的方法吗?) 我从网上找到的各种示例中构建了一个库,以输出一个小型转储文件,使我的工作更轻松。我安排了一次崩溃,但是我从minidump文件获得的堆栈跟踪与我从附加windbg获得的实时堆栈跟踪大不相同。这两个例子如下: MiniDump.dmp:

我试图通过编程生成堆栈跟踪。当我的用户发生崩溃,特别是随机崩溃时,很难说服他们完成转储过程,这样我就可以解决问题。在过去,一旦他们向我发送跟踪,我就会将其中的地址交叉引用到Intermediate/foo.map文件,以找出问题出在哪个函数上(这是最好的方法吗?)

我从网上找到的各种示例中构建了一个库,以输出一个小型转储文件,使我的工作更轻松。我安排了一次崩溃,但是我从minidump文件获得的堆栈跟踪与我从附加windbg获得的实时堆栈跟踪大不相同。这两个例子如下:

MiniDump.dmp:

KERNELBASE.dll!76a6c42d()
[Frames below may be incorrect and/or missing, no symbols loaded for KERNELBASE.dll]
KERNELBASE.dll!76a6c42d()
kernel32.dll!75bd14bd()
game.exe!00759035()
game.exe!00575ba3()
0:000:x86> kv
ChildEBP RetAddr  Args to Child              
00186f44 00bc8ea9 19460268 0018a9b7 03f70a28 Minidump!crashme+0x2 (FPO: [0,0,0]) (CONV: cdecl) [c:\project\debug\minidump.cpp @ 68]
0018795c 00b9ef31 0018796c 03f56c00 6532716d Main!LoadPlugin+0x339 (FPO: [1,642,4]) (CONV: cdecl) [c:\project\main\pluginloader.cpp @ 129]
00188968 00b9667d 19460268 0018a9ac 00000000 Main!Command+0x1f1 (FPO: [2,1024,4]) (CONV: cdecl) [c:\project\main\commands.cpp @ 2617]
*** WARNING: Unable to verify checksum for C:\Game\game.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Game\game.exe
0018b1a8 005b5095 19460268 0018beac 00000000 Main!Hook::Detour+0x52d (FPO: [2,2570,0]) (CONV: thiscall) [c:\project\main\hook.cpp @ 275]
WARNING: Stack unwind information not available. Following frames may be wrong.
0018b1b4 00000000 19495200 19495200 00000006 game+0x1b5095
WinDbg.exe:

KERNELBASE.dll!76a6c42d()
[Frames below may be incorrect and/or missing, no symbols loaded for KERNELBASE.dll]
KERNELBASE.dll!76a6c42d()
kernel32.dll!75bd14bd()
game.exe!00759035()
game.exe!00575ba3()
0:000:x86> kv
ChildEBP RetAddr  Args to Child              
00186f44 00bc8ea9 19460268 0018a9b7 03f70a28 Minidump!crashme+0x2 (FPO: [0,0,0]) (CONV: cdecl) [c:\project\debug\minidump.cpp @ 68]
0018795c 00b9ef31 0018796c 03f56c00 6532716d Main!LoadPlugin+0x339 (FPO: [1,642,4]) (CONV: cdecl) [c:\project\main\pluginloader.cpp @ 129]
00188968 00b9667d 19460268 0018a9ac 00000000 Main!Command+0x1f1 (FPO: [2,1024,4]) (CONV: cdecl) [c:\project\main\commands.cpp @ 2617]
*** WARNING: Unable to verify checksum for C:\Game\game.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Game\game.exe
0018b1a8 005b5095 19460268 0018beac 00000000 Main!Hook::Detour+0x52d (FPO: [2,2570,0]) (CONV: thiscall) [c:\project\main\hook.cpp @ 275]
WARNING: Stack unwind information not available. Following frames may be wrong.
0018b1b4 00000000 19495200 19495200 00000006 game+0x1b5095
game.exe不是我的,我没有源代码/符号。Main.dll被注入game.exe,它提供前端功能,从游戏中加载其他dll。调试代码,并且暂存的崩溃位于Minidump.dll中。Main.dll加载Minidump后,它调用AfterLoad(),后者设置异常过滤器,然后触发崩溃。相关小型转储代码如下所示:

当我打开MiniDump.dmp时,我把它指向了我所有的符号文件(除了game.exe,我没有),而这部分似乎工作正常。我把它指向game.exe二进制文件,因为我有这个。不过,我从中得到的堆栈跟踪确实没有什么帮助。我的最终目标是用户可以加载DLL,导致崩溃,并通过电子邮件将转储文件发送给我。然后我将附加符号文件和二进制文件,并能够为它们诊断问题。是我做错了什么,还是我不可能得到我想要的

typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(
    HANDLE hProcess, 
    DWORD ProcessId, 
    HANDLE hFile, 
    MINIDUMP_TYPE DumpType,
    CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
    CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
    CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);

LONG WINAPI WriteDumpFilter(struct _EXCEPTION_POINTERS *pExceptionPointers)
{
    HANDLE hFile = NULL;
    HMODULE hDll = NULL;
    MINIDUMPWRITEDUMP pMiniDumpWriteDump = NULL;
    _MINIDUMP_EXCEPTION_INFORMATION ExceptionInformation = {0}; 

    //load MiniDumpWriteDump
    hDll = LoadLibrary(TEXT("DbgHelp.dll"));
    pMiniDumpWriteDump = (MINIDUMPWRITEDUMP)GetProcAddress(hDll, "MiniDumpWriteDump");

    //create output file
    hFile = CreateFile( _T( "C:\\temp\\MiniDump.dmp"), 
                            GENERIC_READ|GENERIC_WRITE, 0, NULL, 
                            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); 

    //bail if we don't have a file
    if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) 
    {
        //get exception information
        ExceptionInformation.ThreadId           = GetCurrentThreadId(); 
        ExceptionInformation.ExceptionPointers  = pExceptionPointers; 
        ExceptionInformation.ClientPointers     = TRUE; 

        //write the debug dump
        pMiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), 
                            hFile, MiniDumpWithFullMemory, &ExceptionInformation, 
                            NULL, NULL ); 


        //close the debug output file
        CloseHandle(hFile); 
    }

    return EXCEPTION_EXECUTE_HANDLER;
}

VOID crashme() {int* foo = 0; *foo = 0;}

VOID AfterLoad(VOID)
{
    SetUnhandledExceptionFilter(WriteDumpFilter);
    crashme();
}
为了简化问题,我试图从所有细节中删去一些赘述,但如果需要的话,我可以更明确一些。我发现CodeProject写得很好,我试图找到更多的背景信息来帮助我理解这个问题,但我所能找到的并不能帮助我理解他们只是一步一步地让它运行(这已经是了)。有人知道我做错了什么,或者可能会告诉我相关的阅读材料吗


根据Sergei的建议,我在windbg中添加了.ecxr并获得了更好的输出,但它仍然与我将windbg直接挂接到进程并触发崩溃时得到的跟踪不匹配。这是微型转储跟踪

  *** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
0018e774 00759035 e06d7363 00000001 00000003 KERNELBASE!RaiseException+0x58
0018e7b4 00575ba3 00000000 00000000 00000001 game+0x359035
0018fc50 0057788a 009855ef 0018fdcb 00000001 game+0x175ba3
0018fc78 77b7e013 012d9230 002d91d0 002d9200 game+0x17788a
0018fc90 77ba9567 00290000 00000000 002d91d0 ntdll!RtlFreeHeap+0x7e
0018fd6c 0076ece2 0018ff78 007e1b7e ffffffff ntdll!LdrRemoveLoadAsDataTable+0x4e0
002bbc38 5c306174 61666544 00746c75 5d4c3055 game+0x36ece2
002bbc3c 61666544 00746c75 5d4c3055 8c000000 0x5c306174
002bbc40 00746c75 5d4c3055 8c000000 00000101 0x61666544
002bbc44 5d4c3055 8c000000 00000101 01000000 game+0x346c75
002bbc48 8c000000 00000101 01000000 00000000 0x5d4c3055
002bbc4c 00000000 01000000 00000000 0000006e 0x8c000000
以及将调试器附加到进程的跟踪

0:000:x86> kv
ChildEBP RetAddr  Args to Child              
00186f44 00bc8ea9 19460268 0018a9b7 03f70a28 Minidump!crashme+0x2 (FPO: [0,0,0]) (CONV: cdecl) [c:\project\debug\minidump.cpp @ 68]
0018795c 00b9ef31 0018796c 03f56c00 6532716d Main!LoadPlugin+0x339 (FPO: [1,642,4]) (CONV: cdecl) [c:\project\main\pluginloader.cpp @ 129]
00188968 00b9667d 19460268 0018a9ac 00000000 Main!Command+0x1f1 (FPO: [2,1024,4]) (CONV: cdecl) [c:\project\main\commands.cpp @ 2617]
*** WARNING: Unable to verify checksum for C:\Game\game.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Game\game.exe
0018b1a8 005b5095 19460268 0018beac 00000000 Main!Hook::Detour+0x52d (FPO: [2,2570,0]) (CONV: thiscall) [c:\project\main\hook.cpp @ 275]
WARNING: Stack unwind information not available. Following frames may be wrong.
0018b1b4 00000000 19495200 19495200 00000006 game+0x1b5095
我没有game.exe的源代码(我有错误所在的DLL的源代码),但是我反编译了game.exe,这里是game+0x359035的源代码

.text:00759001 ; =============== S U B R O U T I N E =======================================
.text:00759001
.text:00759001 ; Attributes: library function bp-based frame
.text:00759001
.text:00759001 ; __stdcall _CxxThrowException(x, x)
.text:00759001 __CxxThrowException@8 proc near         ; CODE XREF: .text:0040100Fp
.text:00759001                                         ; sub_401640+98p ...
.text:00759001
.text:00759001 dwExceptionCode = dword ptr -20h
.text:00759001 dwExceptionFlags= dword ptr -1Ch
.text:00759001 nNumberOfArguments= dword ptr -10h
.text:00759001 Arguments       = dword ptr -0Ch
.text:00759001 var_8           = dword ptr -8
.text:00759001 var_4           = dword ptr -4
.text:00759001 arg_0           = dword ptr  8
.text:00759001 arg_4           = dword ptr  0Ch
.text:00759001
.text:00759001                 push    ebp
.text:00759002                 mov     ebp, esp
.text:00759004                 sub     esp, 20h
.text:00759007                 mov     eax, [ebp+arg_0]
.text:0075900A                 push    esi
.text:0075900B                 push    edi
.text:0075900C                 push    8
.text:0075900E                 pop     ecx
.text:0075900F                 mov     esi, offset unk_853A3C
.text:00759014                 lea     edi, [ebp+dwExceptionCode]
.text:00759017                 rep movsd
.text:00759019                 mov     [ebp+var_8], eax
.text:0075901C                 mov     eax, [ebp+arg_4]
.text:0075901F                 mov     [ebp+var_4], eax
.text:00759022                 lea     eax, [ebp+Arguments]
.text:00759025                 push    eax             ; lpArguments
.text:00759026                 push    [ebp+nNumberOfArguments] ; nNumberOfArguments
.text:00759029                 push    [ebp+dwExceptionFlags] ; dwExceptionFlags
.text:0075902C                 push    [ebp+dwExceptionCode] ; dwExceptionCode
.text:0075902F                 call    ds:RaiseException
.text:00759035                 pop     edi
.text:00759036                 pop     esi
.text:00759037                 leave
.text:00759038                 retn    8
.text:00759038 __CxxThrowException@8 endp
我触发的错误在Minidump.dll中,但堆栈顶部的代码在game.exe中。在game.exe里面可能有很多我不知道的事情,它可能是劫持了我触发的错误吗?也就是说,我触发了DLL中的错误,但是game.exe中的某些设置在调用写入迷你转储的异常过滤器之前捕获了程序流

如果是这种情况,当我将调试器附加到进程时,触发错误并获得指向DLL中的错误的正确输出,那么这意味着game.exe在调试器执行跟踪之前没有捕获程序流。如何使我的迷你转储代码以相同的方式运行。。。这正进入我不太熟悉的领域。有什么想法吗


我再往回追,调用该函数的函数中有一行:

.text:00575A8D                 mov     esi, offset aCrashDumpTooLa ; "Crash dump too large to send.\n"
因此,我认为game.exe在我的代码尝试获取转储之前劫持了异常以进行自己的转储。然后我的转储跟踪就是game.exe转储过程的跟踪


回答

我已经弄明白了。我不知道如何回答我自己的帖子,所以这里是交易

.text:0057494A                 push    offset aDbghelp_dll ; "DbgHelp.dll"
.text:0057494F                 call    ds:LoadLibraryA
.text:00574955                 test    eax, eax
.text:00574957                 jz      short loc_5749C8
.text:00574959                 push    offset aMinidumpwrited ; "MiniDumpWriteDump"
.text:0057495E                 push    eax             ; hModule
.text:0057495F                 call    ds:GetProcAddress
.text:00574965                 mov     edi, eax
.text:00574967                 test    edi, edi
.text:00574969                 jz      short loc_5749C8
.text:0057496B                 mov     edx, lpFileName
.text:00574971                 push    0               ; hTemplateFile
.text:00574973                 push    80h             ; dwFlagsAndAttributes
.text:00574978                 push    2               ; dwCreationDisposition
.text:0057497A                 push    0               ; lpSecurityAttributes
.text:0057497C                 push    0               ; dwShareMode
.text:0057497E                 push    40000000h       ; dwDesiredAccess
.text:00574983                 push    edx             ; lpFileName
.text:00574984                 call    ds:CreateFileA
.text:0057498A                 mov     esi, eax
.text:0057498C                 cmp     esi, 0FFFFFFFFh
.text:0057498F                 jz      short loc_5749C8
.text:00574991                 call    ds:GetCurrentThreadId
.text:00574997                 push    0
.text:00574999                 push    0
.text:0057499B                 mov     [ebp+var_1C], eax
.text:0057499E                 lea     eax, [ebp+var_1C]
.text:005749A1                 push    eax
.text:005749A2                 push    0
.text:005749A4                 push    esi
.text:005749A5                 mov     [ebp+var_18], ebx
.text:005749A8                 mov     [ebp+var_14], 1
.text:005749AF                 call    ds:__imp_GetCurrentProcessId
.text:005749B5                 push    eax
.text:005749B6                 call    ds:GetCurrentProcess
.text:005749BC                 push    eax
.text:005749BD                 call    edi
.text:005749BF                 push    esi             ; hObject
.text:005749C0                 call    ds:CloseHandle
.text:005749C6                 jmp     short loc_574A02

这是game.exe提供的。原来game.exe有自己的迷你转储。我的迷你转储是在他们之后触发的,所以我在堆栈跟踪中看到的是他们转储过程的跟踪。我在游戏的安装目录中找到了一个dmp文件,一旦我将我的符号加载到其中,它就显示了我想要的正确输出。

你做得很好。打开生成的迷你转储文件时,在加载符号后,请执行以下操作:

.ecxr
首先将上下文设置为保存在
例外信息
参数中的内容设置为
minidumpWriteMap()
。然后你将有一个合法的堆栈跟踪

我们在我工作的地方使用类似的转储生成机制

不过,还有一些未来的陷阱。您需要检查转储捕获机制是否在调用
abort()
时触发


为此,请检查
\u set\u invalid\u parameter\u handler()
信号(SIGABRT,…)
您做得很好。打开生成的迷你转储文件时,在加载符号后,请执行以下操作:

.ecxr
首先将上下文设置为保存在
例外信息
参数中的内容设置为
minidumpWriteMap()
。然后你将有一个合法的堆栈跟踪

我们在我工作的地方使用类似的转储生成机制

不过,还有一些未来的陷阱。您需要检查转储捕获机制是否在调用
abort()
时触发


为此,请检查
\u set\u invalid\u parameter\u handler()
信号(SIGABRT,…)
我找到了它。基本上game.exe有自己的minidumpWriteMap代码,在我的代码之前触发。所以我得到的堆栈跟踪不是错误的跟踪,而是game.exe自己进行小型转储的跟踪。我在最初的帖子中提到了更多的细节


谢谢

我明白了。基本上game.exe有自己的minidumpWriteMap代码,在我的代码之前触发。所以我得到的堆栈跟踪不是错误的跟踪,而是game.exe自己进行小型转储的跟踪。我把更多的细节放在了ori上