NewHandler 和 UnhandledExceptionHandler 的不同堆栈跟踪
我有以下代码:
#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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如 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.