NewHandler 和 UnhandledExceptionHandler 的不同堆栈跟踪

发布于 2025-01-16 15:40:25 字数 9156 浏览 1 评论 0原文

我有以下代码:

#include <windows.h>
#include <minidumpapiset.h>
#include <strsafe.h>
#include <fileapi.h>
#include <iostream>
#include <signal.h>
#include <minwinbase.h>
#include <new.h>
#include "StackWalker.h"

int minidumpId = 0;

#ifndef _AddressOfReturnAddress

// Taken from: http://msdn.microsoft.com/en-us/library/s975zw7k(VS.71).aspx
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

// _ReturnAddress and _AddressOfReturnAddress should be prototyped before use
EXTERNC void* _AddressOfReturnAddress(void);
EXTERNC void* _ReturnAddress(void);
EXTERNC int __cdecl _purecall();

#endif

EXCEPTION_POINTERS ExceptionPointers;
EXCEPTION_RECORD ExceptionRecord;
CONTEXT ContextRecord;

void GetExceptionPointers(DWORD exceptionCode, EXCEPTION_POINTERS** exceptionPointers)
{
    // The following code was taken from VC++ 8.0 CRT (invarg.c: line 104)
    ZeroMemory(&ExceptionPointers, sizeof(EXCEPTION_POINTERS));
    ZeroMemory(&ExceptionRecord, sizeof(EXCEPTION_RECORD));
    ZeroMemory(&ContextRecord, sizeof(CONTEXT));

    // Looks like a workaround for some bug in RtlCaptureContext. But no description.
#ifdef _X86_

    __asm {
        mov dword ptr[ContextRecord.Eax], eax
        mov dword ptr[ContextRecord.Ecx], ecx
        mov dword ptr[ContextRecord.Edx], edx
        mov dword ptr[ContextRecord.Ebx], ebx
        mov dword ptr[ContextRecord.Esi], esi
        mov dword ptr[ContextRecord.Edi], edi
        mov word ptr[ContextRecord.SegSs], ss
        mov word ptr[ContextRecord.SegCs], cs
        mov word ptr[ContextRecord.SegDs], ds
        mov word ptr[ContextRecord.SegEs], es
        mov word ptr[ContextRecord.SegFs], fs
        mov word ptr[ContextRecord.SegGs], gs
        pushfd
        pop[ContextRecord.EFlags]
    }

    ContextRecord.ContextFlags = CONTEXT_CONTROL;
#pragma warning(push)
#pragma warning(disable : 4311)
    ContextRecord.Eip = (ULONG)_ReturnAddress();
    ContextRecord.Esp = (ULONG)_AddressOfReturnAddress();
#pragma warning(pop)
    ContextRecord.Ebp = *(static_cast<ULONG*>(_AddressOfReturnAddress()) - 1);


#elif defined(_IA64_) || defined(_AMD64_) || defined(_ARM_) || defined(_ARM64_)

    CaptureContext(&ContextRecord);

#else /* defined (_IA64_) || defined (_AMD64_) || defined(_ARM_) || defined(_ARM64_) */

    ZeroMemory(&ContextRecord, sizeof(ContextRecord));

#endif /* defined (_IA64_) || defined (_AMD64_) || defined(_ARM_) || defined(_ARM64_) */

    ExceptionRecord.ExceptionCode = exceptionCode;
    ExceptionRecord.ExceptionAddress = _ReturnAddress();
    ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;

    *exceptionPointers = &ExceptionPointers;
    (*exceptionPointers)->ExceptionRecord = &ExceptionRecord;
    (*exceptionPointers)->ContextRecord = &ContextRecord;
}


class DbgLibrary final
{
public:
    DbgLibrary()
    {
        dbgLibrary = LoadLibraryW(L"dbghelp.dll");
    }

    ~DbgLibrary()
    {
        FreeLibrary(dbgLibrary);
    }

    explicit operator bool() const
    {
        return dbgLibrary != NULL;
    }

    bool WriteMinidump(HANDLE file, EXCEPTION_POINTERS* exceptionPointers) const
    {
        MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
        exceptionInformation.ThreadId = GetCurrentThreadId();
        exceptionInformation.ExceptionPointers = exceptionPointers;
        exceptionInformation.ClientPointers = FALSE;

        MINIDUMP_CALLBACK_INFORMATION callbackInformation;
        callbackInformation.CallbackRoutine = NULL;
        callbackInformation.CallbackParam = NULL;

        typedef BOOL(WINAPI* LPMINIDUMPWRITEDUMP)(HANDLE processHandle, DWORD ProcessId, HANDLE fileHandle,
            MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
            CONST PMINIDUMP_USER_STREAM_INFORMATION UserEncoderParam,
            CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

        LPMINIDUMPWRITEDUMP pfnMiniDumpWriteDump =
            (LPMINIDUMPWRITEDUMP)GetProcAddress(dbgLibrary, "MiniDumpWriteDump");
        if (NULL == pfnMiniDumpWriteDump)
        {
            return false;
        }

        BOOL isWriteSucceed = pfnMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal,
            &exceptionInformation, NULL, &callbackInformation);
        return isWriteSucceed;
    }

private:
    HMODULE dbgLibrary;
};

inline HANDLE CreateNativeFile(const wchar_t* filePath)
{
    HANDLE file = NULL;
    file = CreateFileW(filePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    return file;
}

void CreateMiniDump(PEXCEPTION_POINTERS exceptionPointers)
{
    const DbgLibrary dbgLibrary;
    if (dbgLibrary)
    {
        wchar_t FILE_PATH[4096];
        // Write `exceptionPointers` to the minidump file
        StringCbPrintfW(FILE_PATH, sizeof(FILE_PATH), L"%ls\\%ls_%ld.dmp", ".",
            L"minidump", minidumpId++);
        HANDLE hMinidump = CreateNativeFile(FILE_PATH);
        if (hMinidump != INVALID_HANDLE_VALUE)
        {
            dbgLibrary.WriteMinidump(hMinidump, exceptionPointers);
            CloseHandle(hMinidump);
        }
    }
}

LONG WINAPI SehHandler(PEXCEPTION_POINTERS exceptionPointers)
{
    std::cerr << "SehHandler\n";
    CreateMiniDump(exceptionPointers);
    return EXCEPTION_EXECUTE_HANDLER;
}

void SigsegvHandler(int)
{
    std::cerr << "SigsegvHandler\n";
    PEXCEPTION_POINTERS exceptionPointers = static_cast<PEXCEPTION_POINTERS>(_pxcptinfoptrs);

    // Write minidump file
    CreateMiniDump(exceptionPointers);
}

int __cdecl NewHandler(size_t size)
{
    std::cerr << "NewHandler\n";
    // 'new' operator memory allocation exception
    PEXCEPTION_POINTERS exceptionPointers;
    GetExceptionPointers(STATUS_NO_MEMORY, &exceptionPointers);

    CreateMiniDump(exceptionPointers);

    return 0;
}

struct A5 {
    void F()
    {
        while (true)
        {
            int* a = new int[50000000];
        }
    }
};

struct A4 {
    A5 a;
    void F()
    {
        a.F();
    }
};

struct A3 {
    A4 a;
    void F()
    {
        a.F();
    }
};

struct A2 {
    A3 a;
    void F()
    {
        a.F();
    }
};

struct A1 {
    A2 a;
    void F()
    {
        a.F();
    }
};

int main()
{
    SetUnhandledExceptionFilter(SehHandler);
    signal(SIGSEGV, SigsegvHandler);
    _set_new_handler(NewHandler);
    A1().F();
    return 0;
}

这里将调用两个处理程序:NewHandler 和 SehHandler。第一个是因为运算符 new[] 中的 bad_alloc,第二个是因为未处理的异常。在这两个处理程序中,我创建了包含崩溃信息的小型转储。

  • NewHandler:
Thread 0 (crashed)
 0  StackWalker_VC2017.exe!_callnewh [new_handler.cpp : 79 + 0x2]
    eip = 0x0040a636   esp = 0x0019fefc   ebp = 0x0019ff08   ebx = 0x00311000
    esi = 0x00401d10   edi = 0x00655368   eax = 0x0042eed0   ecx = 0x00000000
    edx = 0x00655368   efl = 0x00000202
    Found by: given as instruction pointer in context
 1  StackWalker_VC2017.exe!operator new(unsigned int) [new_scalar.cpp : 40 + 0x8]
    eip = 0x00404a05   esp = 0x0019ff10   ebp = 0x0019ff14
    Found by: call frame info
 2  StackWalker_VC2017.exe!A5::F() [main.cpp : 197 + 0xa]
    eip = 0x00401d0a   esp = 0x0019ff1c   ebp = 0x0019ff28
    Found by: call frame info
 3  StackWalker_VC2017.exe!main [main.cpp : 239 + 0x8]
    eip = 0x00402500   esp = 0x0019ff24   ebp = 0x0019ff28
    Found by: call frame info
 4  StackWalker_VC2017.exe!static int __scrt_common_main_seh() [exe_common.inl : 288 + 0x1c]
    eip = 0x00404c5d   esp = 0x0019ff30   ebp = 0x0019ff70
    Found by: call frame info
 5  kernel32.dll + 0x1fa29
    eip = 0x7712fa29   esp = 0x0019ff78   ebp = 0x0019ff80
    Found by: call frame info
 6  ntdll.dll + 0x67a9e
    eip = 0x77c97a9e   esp = 0x0019ff88   ebp = 0x0019ffdc
    Found by: previous frame's frame pointer
 7  ntdll.dll + 0x67a6e
    eip = 0x77c97a6e   esp = 0x0019ffe4   ebp = 0x0019ffec
    Found by: previous frame's frame pointer
  • SehHandler:
Thread 0 (crashed)
 0  KERNELBASE.dll + 0x12b812
    eip = 0x76ddb812   esp = 0x0019fe68   ebp = 0x0019fec4   ebx = 0x19930520
    esi = 0x00645a90   edi = 0x0042c754   eax = 0x0019fe68   ecx = 0x00000003
    edx = 0x00000000   efl = 0x00000212
    Found by: given as instruction pointer in context
 1  StackWalker_VC2017.exe!_CxxThrowException [throw.cpp : 74 + 0x19]
    eip = 0x00405a98   esp = 0x0019fecc   ebp = 0x0019fef4
    Found by: previous frame's frame pointer
 2  StackWalker_VC2017.exe!__scrt_throw_std_bad_alloc() [throw_bad_alloc.cpp : 35 + 0x16]
    eip = 0x0040509c   esp = 0x0019fefc   ebp = 0x0019ff10
    Found by: call frame info
 3  StackWalker_VC2017.exe!main [main.cpp : 239 + 0x8]
    eip = 0x00402500   esp = 0x0019ff24   ebp = 0x0019ff14
    Found by: call frame info with scanning

使用breakpad minidump_stackwalk提取堆栈: 问题是为什么 SehHandler stacktrace 没有所有函数调用? 主要问题是在项目中我使用崩溃处理程序来记录转储中的信息。但是在每个 NewHandler 调用上创建 minidump 并不是不合适的解决方案,因为有时 bad_alloc 可以被修复并在 try/catch 块中抛出异常,这意味着它是预期的行为。所以我想在未处理的异常处理程序中处理 bad_alloc ,这样它肯定会崩溃。此外,问题仅发生在发布版本中。

I have the following code:

#include <windows.h>
#include <minidumpapiset.h>
#include <strsafe.h>
#include <fileapi.h>
#include <iostream>
#include <signal.h>
#include <minwinbase.h>
#include <new.h>
#include "StackWalker.h"

int minidumpId = 0;

#ifndef _AddressOfReturnAddress

// Taken from: http://msdn.microsoft.com/en-us/library/s975zw7k(VS.71).aspx
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

// _ReturnAddress and _AddressOfReturnAddress should be prototyped before use
EXTERNC void* _AddressOfReturnAddress(void);
EXTERNC void* _ReturnAddress(void);
EXTERNC int __cdecl _purecall();

#endif

EXCEPTION_POINTERS ExceptionPointers;
EXCEPTION_RECORD ExceptionRecord;
CONTEXT ContextRecord;

void GetExceptionPointers(DWORD exceptionCode, EXCEPTION_POINTERS** exceptionPointers)
{
    // The following code was taken from VC++ 8.0 CRT (invarg.c: line 104)
    ZeroMemory(&ExceptionPointers, sizeof(EXCEPTION_POINTERS));
    ZeroMemory(&ExceptionRecord, sizeof(EXCEPTION_RECORD));
    ZeroMemory(&ContextRecord, sizeof(CONTEXT));

    // Looks like a workaround for some bug in RtlCaptureContext. But no description.
#ifdef _X86_

    __asm {
        mov dword ptr[ContextRecord.Eax], eax
        mov dword ptr[ContextRecord.Ecx], ecx
        mov dword ptr[ContextRecord.Edx], edx
        mov dword ptr[ContextRecord.Ebx], ebx
        mov dword ptr[ContextRecord.Esi], esi
        mov dword ptr[ContextRecord.Edi], edi
        mov word ptr[ContextRecord.SegSs], ss
        mov word ptr[ContextRecord.SegCs], cs
        mov word ptr[ContextRecord.SegDs], ds
        mov word ptr[ContextRecord.SegEs], es
        mov word ptr[ContextRecord.SegFs], fs
        mov word ptr[ContextRecord.SegGs], gs
        pushfd
        pop[ContextRecord.EFlags]
    }

    ContextRecord.ContextFlags = CONTEXT_CONTROL;
#pragma warning(push)
#pragma warning(disable : 4311)
    ContextRecord.Eip = (ULONG)_ReturnAddress();
    ContextRecord.Esp = (ULONG)_AddressOfReturnAddress();
#pragma warning(pop)
    ContextRecord.Ebp = *(static_cast<ULONG*>(_AddressOfReturnAddress()) - 1);


#elif defined(_IA64_) || defined(_AMD64_) || defined(_ARM_) || defined(_ARM64_)

    CaptureContext(&ContextRecord);

#else /* defined (_IA64_) || defined (_AMD64_) || defined(_ARM_) || defined(_ARM64_) */

    ZeroMemory(&ContextRecord, sizeof(ContextRecord));

#endif /* defined (_IA64_) || defined (_AMD64_) || defined(_ARM_) || defined(_ARM64_) */

    ExceptionRecord.ExceptionCode = exceptionCode;
    ExceptionRecord.ExceptionAddress = _ReturnAddress();
    ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;

    *exceptionPointers = &ExceptionPointers;
    (*exceptionPointers)->ExceptionRecord = &ExceptionRecord;
    (*exceptionPointers)->ContextRecord = &ContextRecord;
}


class DbgLibrary final
{
public:
    DbgLibrary()
    {
        dbgLibrary = LoadLibraryW(L"dbghelp.dll");
    }

    ~DbgLibrary()
    {
        FreeLibrary(dbgLibrary);
    }

    explicit operator bool() const
    {
        return dbgLibrary != NULL;
    }

    bool WriteMinidump(HANDLE file, EXCEPTION_POINTERS* exceptionPointers) const
    {
        MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
        exceptionInformation.ThreadId = GetCurrentThreadId();
        exceptionInformation.ExceptionPointers = exceptionPointers;
        exceptionInformation.ClientPointers = FALSE;

        MINIDUMP_CALLBACK_INFORMATION callbackInformation;
        callbackInformation.CallbackRoutine = NULL;
        callbackInformation.CallbackParam = NULL;

        typedef BOOL(WINAPI* LPMINIDUMPWRITEDUMP)(HANDLE processHandle, DWORD ProcessId, HANDLE fileHandle,
            MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
            CONST PMINIDUMP_USER_STREAM_INFORMATION UserEncoderParam,
            CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

        LPMINIDUMPWRITEDUMP pfnMiniDumpWriteDump =
            (LPMINIDUMPWRITEDUMP)GetProcAddress(dbgLibrary, "MiniDumpWriteDump");
        if (NULL == pfnMiniDumpWriteDump)
        {
            return false;
        }

        BOOL isWriteSucceed = pfnMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal,
            &exceptionInformation, NULL, &callbackInformation);
        return isWriteSucceed;
    }

private:
    HMODULE dbgLibrary;
};

inline HANDLE CreateNativeFile(const wchar_t* filePath)
{
    HANDLE file = NULL;
    file = CreateFileW(filePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    return file;
}

void CreateMiniDump(PEXCEPTION_POINTERS exceptionPointers)
{
    const DbgLibrary dbgLibrary;
    if (dbgLibrary)
    {
        wchar_t FILE_PATH[4096];
        // Write `exceptionPointers` to the minidump file
        StringCbPrintfW(FILE_PATH, sizeof(FILE_PATH), L"%ls\\%ls_%ld.dmp", ".",
            L"minidump", minidumpId++);
        HANDLE hMinidump = CreateNativeFile(FILE_PATH);
        if (hMinidump != INVALID_HANDLE_VALUE)
        {
            dbgLibrary.WriteMinidump(hMinidump, exceptionPointers);
            CloseHandle(hMinidump);
        }
    }
}

LONG WINAPI SehHandler(PEXCEPTION_POINTERS exceptionPointers)
{
    std::cerr << "SehHandler\n";
    CreateMiniDump(exceptionPointers);
    return EXCEPTION_EXECUTE_HANDLER;
}

void SigsegvHandler(int)
{
    std::cerr << "SigsegvHandler\n";
    PEXCEPTION_POINTERS exceptionPointers = static_cast<PEXCEPTION_POINTERS>(_pxcptinfoptrs);

    // Write minidump file
    CreateMiniDump(exceptionPointers);
}

int __cdecl NewHandler(size_t size)
{
    std::cerr << "NewHandler\n";
    // 'new' operator memory allocation exception
    PEXCEPTION_POINTERS exceptionPointers;
    GetExceptionPointers(STATUS_NO_MEMORY, &exceptionPointers);

    CreateMiniDump(exceptionPointers);

    return 0;
}

struct A5 {
    void F()
    {
        while (true)
        {
            int* a = new int[50000000];
        }
    }
};

struct A4 {
    A5 a;
    void F()
    {
        a.F();
    }
};

struct A3 {
    A4 a;
    void F()
    {
        a.F();
    }
};

struct A2 {
    A3 a;
    void F()
    {
        a.F();
    }
};

struct A1 {
    A2 a;
    void F()
    {
        a.F();
    }
};

int main()
{
    SetUnhandledExceptionFilter(SehHandler);
    signal(SIGSEGV, SigsegvHandler);
    _set_new_handler(NewHandler);
    A1().F();
    return 0;
}

Here two handlers would be invoked: NewHandler and SehHandler. The first one because of bad_alloc in operator new[], the second one because of unhandled exception. In both handlers I create minidump with information about crash.

  • NewHandler:
Thread 0 (crashed)
 0  StackWalker_VC2017.exe!_callnewh [new_handler.cpp : 79 + 0x2]
    eip = 0x0040a636   esp = 0x0019fefc   ebp = 0x0019ff08   ebx = 0x00311000
    esi = 0x00401d10   edi = 0x00655368   eax = 0x0042eed0   ecx = 0x00000000
    edx = 0x00655368   efl = 0x00000202
    Found by: given as instruction pointer in context
 1  StackWalker_VC2017.exe!operator new(unsigned int) [new_scalar.cpp : 40 + 0x8]
    eip = 0x00404a05   esp = 0x0019ff10   ebp = 0x0019ff14
    Found by: call frame info
 2  StackWalker_VC2017.exe!A5::F() [main.cpp : 197 + 0xa]
    eip = 0x00401d0a   esp = 0x0019ff1c   ebp = 0x0019ff28
    Found by: call frame info
 3  StackWalker_VC2017.exe!main [main.cpp : 239 + 0x8]
    eip = 0x00402500   esp = 0x0019ff24   ebp = 0x0019ff28
    Found by: call frame info
 4  StackWalker_VC2017.exe!static int __scrt_common_main_seh() [exe_common.inl : 288 + 0x1c]
    eip = 0x00404c5d   esp = 0x0019ff30   ebp = 0x0019ff70
    Found by: call frame info
 5  kernel32.dll + 0x1fa29
    eip = 0x7712fa29   esp = 0x0019ff78   ebp = 0x0019ff80
    Found by: call frame info
 6  ntdll.dll + 0x67a9e
    eip = 0x77c97a9e   esp = 0x0019ff88   ebp = 0x0019ffdc
    Found by: previous frame's frame pointer
 7  ntdll.dll + 0x67a6e
    eip = 0x77c97a6e   esp = 0x0019ffe4   ebp = 0x0019ffec
    Found by: previous frame's frame pointer
  • SehHandler:
Thread 0 (crashed)
 0  KERNELBASE.dll + 0x12b812
    eip = 0x76ddb812   esp = 0x0019fe68   ebp = 0x0019fec4   ebx = 0x19930520
    esi = 0x00645a90   edi = 0x0042c754   eax = 0x0019fe68   ecx = 0x00000003
    edx = 0x00000000   efl = 0x00000212
    Found by: given as instruction pointer in context
 1  StackWalker_VC2017.exe!_CxxThrowException [throw.cpp : 74 + 0x19]
    eip = 0x00405a98   esp = 0x0019fecc   ebp = 0x0019fef4
    Found by: previous frame's frame pointer
 2  StackWalker_VC2017.exe!__scrt_throw_std_bad_alloc() [throw_bad_alloc.cpp : 35 + 0x16]
    eip = 0x0040509c   esp = 0x0019fefc   ebp = 0x0019ff10
    Found by: call frame info
 3  StackWalker_VC2017.exe!main [main.cpp : 239 + 0x8]
    eip = 0x00402500   esp = 0x0019ff24   ebp = 0x0019ff14
    Found by: call frame info with scanning

Extracted stacks using breakpad minidump_stackwalk:
The question is why SehHandler stacktrace does not have all function calls?
The main problem is that in project I use crash handlers for logging information in dumps. But creating minidump on each NewHandler call is not inappropriate solution, because sometimes bad_alloc could be fixed and exception thrown in try/catch block, that means that it is expected behaviour. So I want to handle bad_alloc in unhandled exception handler, so that it would definitely be crash. Also problem occurs only in release builds.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

ぃ弥猫深巷。 2025-01-23 15:40:25

https://developercommunity.visualstudio.com/t/stdbad-alloc-failures-are-undebuggable/542559?viewtype=solutions 这是 msvc 中的错误。不幸的是,对于发布版本没有好的解决方案。

As mentioned in https://developercommunity.visualstudio.com/t/stdbad-alloc-failures-are-undebuggable/542559?viewtype=solutions it is bug in msvc. Unfortunately there is no good solution for release builds.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文