mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-07-04 05:55:47 -07:00
# fix for exceptions when not running in debugger
move exception-specific stuff into wseh. it now grabs the entry point and wraps it in a __try block (but only for the main EXE). this works around issues with the previous SEH registration code (exceptions weren't caught in release mode when not running in debugger) This was SVN commit r5151.
This commit is contained in:
parent
2405a98b58
commit
4fbaea3780
7 changed files with 398 additions and 397 deletions
|
|
@ -474,8 +474,8 @@ function setup_main_exe ()
|
|||
end
|
||||
|
||||
package.linkoptions = {
|
||||
-- required since main.cpp uses main instead of WinMain and subsystem=Win32
|
||||
"/ENTRY:mainCRTStartup",
|
||||
-- wraps main thread in a __try block(see wseh.cpp). replace with mainCRTStartup if that's undesired.
|
||||
"/ENTRY:wseh_EntryPoint",
|
||||
|
||||
-- see wstartup.h
|
||||
"/INCLUDE:_wstartup_InitAndRegisterShutdown",
|
||||
|
|
|
|||
|
|
@ -17,16 +17,12 @@
|
|||
|
||||
#include "lib/bits.h"
|
||||
#include "lib/posix/posix_pthread.h"
|
||||
#include "lib/byte_order.h" // FOURCC
|
||||
#include "lib/app_hooks.h"
|
||||
#include "lib/sysdep/cpu.h"
|
||||
#include "win.h"
|
||||
#include "wdbg_sym.h"
|
||||
#include "winit.h"
|
||||
#include "wutil.h"
|
||||
|
||||
WINIT_REGISTER_EARLY_INIT(wdbg_Init); // registers exception handler
|
||||
|
||||
|
||||
// protects the breakpoint helper thread.
|
||||
static void lock()
|
||||
|
|
@ -42,6 +38,7 @@ static void unlock()
|
|||
|
||||
static NT_TIB* get_tib()
|
||||
{
|
||||
#if CPU_IA32
|
||||
NT_TIB* tib;
|
||||
__asm
|
||||
{
|
||||
|
|
@ -49,6 +46,7 @@ static NT_TIB* get_tib()
|
|||
mov [tib], eax
|
||||
}
|
||||
return tib;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -358,376 +356,6 @@ LibError debug_remove_all_breaks()
|
|||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// analyze SEH exceptions
|
||||
|
||||
//
|
||||
// analyze exceptions; determine their type and locus
|
||||
//
|
||||
|
||||
// storage for strings built by get_SEH_exception_description and get_cpp_exception_description.
|
||||
static wchar_t description_buf[128];
|
||||
|
||||
// VC++ exception handling internals.
|
||||
// see http://www.codeproject.com/cpp/exceptionhandler.asp
|
||||
struct XTypeInfo
|
||||
{
|
||||
DWORD _;
|
||||
const std::type_info* ti;
|
||||
// ..
|
||||
};
|
||||
|
||||
struct XTypeInfoArray
|
||||
{
|
||||
DWORD count;
|
||||
const XTypeInfo* types[1];
|
||||
};
|
||||
|
||||
struct XInfo
|
||||
{
|
||||
DWORD _[3];
|
||||
const XTypeInfoArray* array;
|
||||
};
|
||||
|
||||
|
||||
// does the given SEH exception look like a C++ exception?
|
||||
// (compiler-specific).
|
||||
static bool isCppException(const EXCEPTION_RECORD* er)
|
||||
{
|
||||
#if MSC_VERSION
|
||||
// notes:
|
||||
// - value of multibyte character constants (e.g. 'msc') aren't
|
||||
// specified by C++, so use FOURCC instead.
|
||||
// - "MS C" compiler is the only interpretation of this magic value that
|
||||
// makes sense, so it is apparently stored in big-endian format.
|
||||
if(er->ExceptionCode != FOURCC_BE(0xe0, 'm','s','c'))
|
||||
return false;
|
||||
|
||||
// exception info = (magic, &thrown_Cpp_object, &XInfo)
|
||||
if(er->NumberParameters != 3)
|
||||
return false;
|
||||
|
||||
// MAGIC_NUMBER1 from exsup.inc
|
||||
if(er->ExceptionInformation[0] != 0x19930520)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
# error "port"
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// if <er> is not a C++ exception, return 0. otherwise, return a description
|
||||
// of the exception type and cause (in English). uses static storage.
|
||||
static const wchar_t* get_cpp_exception_description(const EXCEPTION_RECORD* er)
|
||||
{
|
||||
if(!isCppException(er))
|
||||
return 0;
|
||||
|
||||
// see above for interpretation
|
||||
const ULONG_PTR* const ei = er->ExceptionInformation;
|
||||
|
||||
// note: we can't share a __try below - the failure of
|
||||
// one attempt must not abort the others.
|
||||
|
||||
// get std::type_info
|
||||
char type_buf[100] = {'\0'};
|
||||
const char* type_name = type_buf;
|
||||
__try
|
||||
{
|
||||
const XInfo* xi = (XInfo*)ei[2];
|
||||
const XTypeInfoArray* xta = xi->array;
|
||||
const XTypeInfo* xti = xta->types[0];
|
||||
const std::type_info* ti = xti->ti;
|
||||
|
||||
// strip "class " from start of string (clutter)
|
||||
strcpy_s(type_buf, ARRAY_SIZE(type_buf), ti->name());
|
||||
if(!strncmp(type_buf, "class ", 6))
|
||||
type_name += 6;
|
||||
}
|
||||
__except(EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
|
||||
// std::exception.what()
|
||||
char what[100] = {'\0'};
|
||||
__try
|
||||
{
|
||||
std::exception* e = (std::exception*)ei[1];
|
||||
strcpy_s(what, ARRAY_SIZE(what), e->what());
|
||||
}
|
||||
__except(EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// we got meaningful data; format and return it.
|
||||
if(type_name[0] != '\0' || what[0] != '\0')
|
||||
{
|
||||
swprintf(description_buf, ARRAY_SIZE(description_buf), L"%hs(\"%hs\")", type_name, what);
|
||||
return description_buf;
|
||||
}
|
||||
|
||||
// not a C++ exception; we can't say anything about it.
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// return a description of the exception type (in English).
|
||||
// uses static storage.
|
||||
static const wchar_t* get_SEH_exception_description(const EXCEPTION_RECORD* er)
|
||||
{
|
||||
const DWORD code = er->ExceptionCode;
|
||||
const ULONG_PTR* ei = er->ExceptionInformation;
|
||||
|
||||
// special case for access violations: display type and address.
|
||||
if(code == EXCEPTION_ACCESS_VIOLATION)
|
||||
{
|
||||
const wchar_t* op = (ei[0])? L"writing" : L"reading";
|
||||
const wchar_t* fmt = L"Access violation %s 0x%08X";
|
||||
swprintf(description_buf, ARRAY_SIZE(description_buf), ah_translate(fmt), ah_translate(op), ei[1]);
|
||||
return description_buf;
|
||||
}
|
||||
|
||||
// rationale: we don't use FormatMessage because it is unclear whether
|
||||
// NTDLL's symbol table will always include English-language strings
|
||||
// (we don't want to receive crashlogs in foreign gobbledygook).
|
||||
// it also adds unwanted formatting (e.g. {EXCEPTION} and trailing .).
|
||||
|
||||
switch(code)
|
||||
{
|
||||
// case EXCEPTION_ACCESS_VIOLATION: return L"Access violation";
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT: return L"Datatype misalignment";
|
||||
case EXCEPTION_BREAKPOINT: return L"Breakpoint";
|
||||
case EXCEPTION_SINGLE_STEP: return L"Single step";
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return L"Array bounds exceeded";
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND: return L"FPU denormal operand";
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO: return L"FPU divide by zero";
|
||||
case EXCEPTION_FLT_INEXACT_RESULT: return L"FPU inexact result";
|
||||
case EXCEPTION_FLT_INVALID_OPERATION: return L"FPU invalid operation";
|
||||
case EXCEPTION_FLT_OVERFLOW: return L"FPU overflow";
|
||||
case EXCEPTION_FLT_STACK_CHECK: return L"FPU stack check";
|
||||
case EXCEPTION_FLT_UNDERFLOW: return L"FPU underflow";
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO: return L"Integer divide by zero";
|
||||
case EXCEPTION_INT_OVERFLOW: return L"Integer overflow";
|
||||
case EXCEPTION_PRIV_INSTRUCTION: return L"Privileged instruction";
|
||||
case EXCEPTION_IN_PAGE_ERROR: return L"In page error";
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION: return L"Illegal instruction";
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION: return L"Noncontinuable exception";
|
||||
case EXCEPTION_STACK_OVERFLOW: return L"Stack overflow";
|
||||
case EXCEPTION_INVALID_DISPOSITION: return L"Invalid disposition";
|
||||
case EXCEPTION_GUARD_PAGE: return L"Guard page";
|
||||
case EXCEPTION_INVALID_HANDLE: return L"Invalid handle";
|
||||
}
|
||||
|
||||
// anything else => unknown; display its exception code.
|
||||
// we don't punt to get_exception_description because anything
|
||||
// we get called for will actually be a SEH exception.
|
||||
swprintf(description_buf, ARRAY_SIZE(description_buf), L"Unknown (0x%08X)", code);
|
||||
return description_buf;
|
||||
}
|
||||
|
||||
|
||||
// return a description of the exception <er> (in English).
|
||||
// it is only valid until the next call, since static storage is used.
|
||||
static const wchar_t* get_exception_description(const EXCEPTION_POINTERS* ep)
|
||||
{
|
||||
const EXCEPTION_RECORD* const er = ep->ExceptionRecord;
|
||||
|
||||
// note: more specific than SEH, so try it first.
|
||||
const wchar_t* d = get_cpp_exception_description(er);
|
||||
if(d)
|
||||
return d;
|
||||
|
||||
return get_SEH_exception_description(er);
|
||||
}
|
||||
|
||||
|
||||
// return location at which the exception <er> occurred.
|
||||
// params: see debug_resolve_symbol.
|
||||
static void get_exception_locus(const EXCEPTION_POINTERS* ep,
|
||||
char* file, int* line, char* func)
|
||||
{
|
||||
// HACK: <ep> provides no useful information - ExceptionAddress always
|
||||
// points to kernel32!RaiseException. we use debug_get_nth_caller to
|
||||
// determine the real location.
|
||||
|
||||
const uint skip = 1; // skip RaiseException
|
||||
void* func_addr = debug_get_nth_caller(skip, ep->ContextRecord);
|
||||
(void)debug_resolve_symbol(func_addr, func, file, line);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// exception handler
|
||||
|
||||
/*
|
||||
|
||||
rationale:
|
||||
we want to replace the OS "program error" dialog box because
|
||||
it is not all too helpful in debugging. to that end, there are
|
||||
5 ways to make sure unhandled SEH exceptions are caught:
|
||||
- via WaitForDebugEvent; the app is run from a separate debugger process.
|
||||
this complicates analysis, since the exception is in another
|
||||
address space. also, we are basically implementing a full-featured
|
||||
debugger - overkill.
|
||||
- by wrapping all threads in __try (necessary since the handler chain
|
||||
is in TLS). this can be done with the cooperation of wpthread,
|
||||
but threads not under our control aren't covered.
|
||||
- with a vectored exception handler. this works across threads, but
|
||||
is never called when the process is being debugged (messing with
|
||||
the PEB flag doesn't help; root cause is the Win32
|
||||
KiUserExceptionDispatcher implementation). also, it's only available
|
||||
on WinXP (unacceptable). finally, it is called before __try blocks,
|
||||
so would receive expected/legitimate exceptions.
|
||||
- by setting the per-process unhandled exception filter. as above,
|
||||
this works across threads and isn't called while a debugger is active;
|
||||
it is at least portable across Win32. unfortunately, some Win32 DLLs
|
||||
appear to register their own handlers, so this isn't reliable.
|
||||
- by hooking the exception dispatcher. this isn't future-proof.
|
||||
|
||||
wrapping all threads in a __try appears to be the best choice. however,
|
||||
with wstartup no longer commandeering the exit point, we need to
|
||||
retroactively install an SEH handler. this is done by directly
|
||||
hooking into the TIB handler list.
|
||||
|
||||
since C++ exceptions are implemented via SEH, we can also catch those here;
|
||||
it's nicer than a global try{} and avoids duplicating this code.
|
||||
we can still get at the C++ information (std::exception.what()) by
|
||||
examining the internal exception data structures. these are
|
||||
compiler-specific, but haven't changed from VC5-VC7.1.
|
||||
alternatively, _set_se_translator could be used to translate all
|
||||
SEH exceptions to C++. this way is more reliable/documented, but has
|
||||
several drawbacks:
|
||||
- it wouldn't work at all in C programs,
|
||||
- a new fat exception class would have to be created to hold the
|
||||
SEH exception information (e.g. CONTEXT for a stack trace), and
|
||||
- this information would not be available for C++ exceptions.
|
||||
|
||||
*/
|
||||
|
||||
// called when an exception is detected (see below); provides detailed
|
||||
// debugging information and exits.
|
||||
//
|
||||
// note: keep memory allocs and locking to an absolute minimum, because
|
||||
// they may deadlock the process!
|
||||
LONG WINAPI wdbg_exception_filter(EXCEPTION_POINTERS* ep)
|
||||
{
|
||||
// OutputDebugString raises an exception, which OUGHT to be swallowed
|
||||
// by WaitForDebugEvent but sometimes isn't. if we see it, ignore it.
|
||||
if(ep->ExceptionRecord->ExceptionCode == 0x40010006) // DBG_PRINTEXCEPTION_C
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
|
||||
// if run in a debugger, let it handle exceptions (tends to be more
|
||||
// convenient since it can bring up the crash location)
|
||||
if(IsDebuggerPresent())
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
// note: we risk infinite recursion if someone raises an SEH exception
|
||||
// from within this function. therefore, abort immediately if we have
|
||||
// already been called; the first error is the most important, anyway.
|
||||
static uintptr_t already_crashed = 0;
|
||||
if(!CAS(&already_crashed, 0, 1))
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
// someone is already holding the dbghelp lock - this is bad.
|
||||
// we'll report this problem first and then try to display the
|
||||
// exception info regardless (maybe dbghelp won't blow up).
|
||||
if(win_is_locked(WDBG_SYM_CS) == 1)
|
||||
DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock..");
|
||||
|
||||
// extract details from ExceptionRecord.
|
||||
const wchar_t* description = get_exception_description(ep);
|
||||
char file[DBG_FILE_LEN] = {0};
|
||||
int line = 0;
|
||||
char func_name[DBG_SYMBOL_LEN] = {0};
|
||||
get_exception_locus(ep, file, &line, func_name);
|
||||
|
||||
// this must happen before the error dialog because user could choose to
|
||||
// exit immediately there.
|
||||
wdbg_sym_write_minidump(ep);
|
||||
|
||||
wchar_t buf[500];
|
||||
const wchar_t* msg_fmt =
|
||||
L"Much to our regret we must report the program has encountered an error.\r\n"
|
||||
L"\r\n"
|
||||
L"Please let us know at http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n"
|
||||
L"\r\n"
|
||||
L"Details: unhandled exception (%s)\r\n";
|
||||
swprintf(buf, ARRAY_SIZE(buf), msg_fmt, description);
|
||||
uint flags = 0;
|
||||
|
||||
if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
|
||||
flags = DE_NO_CONTINUE;
|
||||
ErrorReaction er = debug_display_error(buf, flags, 1,ep->ContextRecord, file,line,func_name, NULL);
|
||||
debug_assert(er == ER_CONTINUE); // nothing else possible
|
||||
|
||||
// invoke the Win32 default handler - it calls ExitProcess for
|
||||
// most exception types.
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// install SEH exception handler
|
||||
|
||||
typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE)(_EXCEPTION_RECORD* ExceptionRecord,
|
||||
PVOID EstablisherFrame, _CONTEXT* ContextRecord, PVOID DispatcherContext);
|
||||
|
||||
struct _EXCEPTION_REGISTRATION_RECORD
|
||||
{
|
||||
_EXCEPTION_REGISTRATION_RECORD* Next;
|
||||
PEXCEPTION_ROUTINE Handler;
|
||||
};
|
||||
|
||||
static bool IsUnwinding(DWORD exceptionFlags)
|
||||
{
|
||||
return (exceptionFlags & 2) != 0;
|
||||
}
|
||||
|
||||
static EXCEPTION_DISPOSITION ExceptionHandler(_EXCEPTION_RECORD* ExceptionRecord,
|
||||
PVOID UNUSED(EstablisherFrame), _CONTEXT* ContextRecord, PVOID UNUSED(DispatcherContext))
|
||||
{
|
||||
if(!IsUnwinding(ExceptionRecord->ExceptionFlags))
|
||||
{
|
||||
EXCEPTION_POINTERS ep;
|
||||
ep.ExceptionRecord = ExceptionRecord;
|
||||
ep.ContextRecord = ContextRecord;
|
||||
if(wdbg_exception_filter(&ep) == EXCEPTION_CONTINUE_EXECUTION)
|
||||
return ExceptionContinueExecution;
|
||||
}
|
||||
|
||||
return ExceptionContinueSearch;
|
||||
}
|
||||
|
||||
|
||||
cassert(WDBG_XRR_STORAGE_SIZE >= sizeof(_EXCEPTION_REGISTRATION_RECORD));
|
||||
|
||||
void wdbg_InstallExceptionHandler(void* xrrStorage)
|
||||
{
|
||||
_EXCEPTION_REGISTRATION_RECORD* xrr = (_EXCEPTION_REGISTRATION_RECORD*)xrrStorage;
|
||||
|
||||
// add to front of handler list
|
||||
NT_TIB* tib = get_tib();
|
||||
xrr->Handler = ExceptionHandler;
|
||||
xrr->Next = tib->ExceptionList;
|
||||
tib->ExceptionList = xrr;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static LibError wdbg_Init(void)
|
||||
{
|
||||
static _EXCEPTION_REGISTRATION_RECORD xrrStorage;
|
||||
wdbg_InstallExceptionHandler(&xrrStorage);
|
||||
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// stateless functions
|
||||
|
||||
// return 1 if the pointer appears to be totally bogus, otherwise 0.
|
||||
// this check is not authoritative (the pointer may be "valid" but incorrect)
|
||||
|
|
|
|||
|
|
@ -20,16 +20,4 @@
|
|||
|
||||
extern void wdbg_set_thread_name(const char* name);
|
||||
|
||||
|
||||
const size_t WDBG_XRR_STORAGE_SIZE = 16;
|
||||
|
||||
/**
|
||||
* install an SEH handler for the current thread.
|
||||
*
|
||||
* @param xrrStorage - storage used to hold the SEH handler node that's
|
||||
* entered into the TIB's list. must be at least WDBG_XRR_STORAGE_SIZE bytes
|
||||
* and remain valid over the lifetime of the thread.
|
||||
**/
|
||||
void wdbg_InstallExceptionHandler(void* xrrStorage);
|
||||
|
||||
#endif // #ifndef INCLUDED_WDBG
|
||||
|
|
|
|||
|
|
@ -84,8 +84,8 @@ void CounterHPET::Shutdown()
|
|||
|
||||
bool CounterHPET::IsSafe() const
|
||||
{
|
||||
// the HPET being created to address other timers' problems, it has
|
||||
// no issues of its own.
|
||||
// the HPET having been created to address other timers' problems,
|
||||
// it has no issues of its own.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
#include "lib/sysdep/cpu.h" // CAS
|
||||
|
||||
#include "wposix_internal.h"
|
||||
#include "wtime.h" // timespec
|
||||
#include "wtime.h" // timespec
|
||||
#include "../wseh.h" // wseh_ExceptionFilter
|
||||
|
||||
|
||||
static HANDLE HANDLE_from_pthread(pthread_t p)
|
||||
|
|
@ -469,12 +470,16 @@ static unsigned __stdcall thread_start(void* param)
|
|||
void* arg = func_and_arg->arg;
|
||||
win_free(param);
|
||||
|
||||
u8 xrrStorage[WDBG_XRR_STORAGE_SIZE];
|
||||
wdbg_InstallExceptionHandler(xrrStorage);
|
||||
|
||||
void* ret = func(arg);
|
||||
|
||||
call_tls_dtors();
|
||||
void* ret = 0;
|
||||
__try
|
||||
{
|
||||
ret = func(arg);
|
||||
call_tls_dtors();
|
||||
}
|
||||
__except(wseh_ExceptionFilter(GetExceptionInformation()))
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return (unsigned)(uintptr_t)ret;
|
||||
}
|
||||
|
|
|
|||
362
source/lib/sysdep/win/wseh.cpp
Normal file
362
source/lib/sysdep/win/wseh.cpp
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
/**
|
||||
* =========================================================================
|
||||
* File : wseh.cpp
|
||||
* Project : 0 A.D.
|
||||
* Description : Structured Exception Handling support
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
// license: GPL; see lib/license.txt
|
||||
|
||||
#include "precompiled.h"
|
||||
#include "wdbg.h"
|
||||
|
||||
#include "lib/byte_order.h" // FOURCC
|
||||
#include "lib/sysdep/cpu.h"
|
||||
#include "win.h"
|
||||
#include "wutil.h"
|
||||
#include "wdbg_sym.h" // wdbg_sym_write_minidump
|
||||
|
||||
#if MSC_VERSION >= 1400
|
||||
# include <process.h> // __security_init_cookie
|
||||
# define NEED_COOKIE_INIT
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// analyze an exception (determine description and locus)
|
||||
|
||||
// VC++ exception handling internals.
|
||||
// see http://www.codeproject.com/cpp/exceptionhandler.asp
|
||||
struct XTypeInfo
|
||||
{
|
||||
DWORD _;
|
||||
const std::type_info* ti;
|
||||
// ..
|
||||
};
|
||||
|
||||
struct XTypeInfoArray
|
||||
{
|
||||
DWORD count;
|
||||
const XTypeInfo* types[1];
|
||||
};
|
||||
|
||||
struct XInfo
|
||||
{
|
||||
DWORD _[3];
|
||||
const XTypeInfoArray* array;
|
||||
};
|
||||
|
||||
|
||||
// does the given SEH exception look like a C++ exception?
|
||||
// (compiler-specific).
|
||||
static bool IsCppException(const EXCEPTION_RECORD* er)
|
||||
{
|
||||
#if MSC_VERSION
|
||||
// notes:
|
||||
// - value of multibyte character constants (e.g. 'msc') aren't
|
||||
// specified by C++, so use FOURCC instead.
|
||||
// - "MS C" compiler is the only interpretation of this magic value that
|
||||
// makes sense, so it is apparently stored in big-endian format.
|
||||
if(er->ExceptionCode != FOURCC_BE(0xe0, 'm','s','c'))
|
||||
return false;
|
||||
|
||||
// exception info = (magic, &thrown_Cpp_object, &XInfo)
|
||||
if(er->NumberParameters != 3)
|
||||
return false;
|
||||
|
||||
// MAGIC_NUMBER1 from exsup.inc
|
||||
if(er->ExceptionInformation[0] != 0x19930520)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
# error "port"
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @param er an exception record for which IsCppException returned true.
|
||||
**/
|
||||
static const wchar_t* GetCppExceptionDescription(const EXCEPTION_RECORD* er,
|
||||
wchar_t* description, size_t maxChars)
|
||||
{
|
||||
// see above for interpretation
|
||||
const ULONG_PTR* const ei = er->ExceptionInformation;
|
||||
|
||||
// note: we can't share a __try below - the failure of
|
||||
// one attempt must not abort the others.
|
||||
|
||||
// get std::type_info
|
||||
char type_buf[100] = {'\0'};
|
||||
const char* type_name = type_buf;
|
||||
__try
|
||||
{
|
||||
const XInfo* xi = (XInfo*)ei[2];
|
||||
const XTypeInfoArray* xta = xi->array;
|
||||
const XTypeInfo* xti = xta->types[0];
|
||||
const std::type_info* ti = xti->ti;
|
||||
|
||||
// strip "class " from start of string (clutter)
|
||||
strcpy_s(type_buf, ARRAY_SIZE(type_buf), ti->name());
|
||||
if(!strncmp(type_buf, "class ", 6))
|
||||
type_name += 6;
|
||||
}
|
||||
__except(EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
|
||||
// std::exception.what()
|
||||
char what[160] = {'\0'};
|
||||
__try
|
||||
{
|
||||
std::exception* e = (std::exception*)ei[1];
|
||||
strcpy_s(what, ARRAY_SIZE(what), e->what());
|
||||
}
|
||||
__except(EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
|
||||
// format the info we got (if both are empty, then something is seriously
|
||||
// wrong; it's better to show empty strings than returning 0 to have our
|
||||
// caller display the SEH info)
|
||||
swprintf(description, maxChars, L"%hs(\"%hs\")", type_name, what);
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
static const wchar_t* GetSehExceptionDescription(const EXCEPTION_RECORD* er,
|
||||
wchar_t* description, size_t maxChars)
|
||||
{
|
||||
const DWORD code = er->ExceptionCode;
|
||||
const ULONG_PTR* ei = er->ExceptionInformation;
|
||||
|
||||
// rationale: we don't use FormatMessage because it is unclear whether
|
||||
// NTDLL's symbol table will always include English-language strings
|
||||
// (we don't want to receive crashlogs in foreign gobbledygook).
|
||||
// it also adds unwanted formatting (e.g. {EXCEPTION} and trailing .).
|
||||
|
||||
switch(code)
|
||||
{
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
{
|
||||
// special case: display type and address.
|
||||
const wchar_t* accessType = (ei[0])? L"writing" : L"reading";
|
||||
const ULONG_PTR address = ei[1];
|
||||
swprintf(description, maxChars, L"Access violation %s 0x%08X", accessType, address);
|
||||
return description;
|
||||
}
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT: return L"Datatype misalignment";
|
||||
case EXCEPTION_BREAKPOINT: return L"Breakpoint";
|
||||
case EXCEPTION_SINGLE_STEP: return L"Single step";
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return L"Array bounds exceeded";
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND: return L"FPU denormal operand";
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO: return L"FPU divide by zero";
|
||||
case EXCEPTION_FLT_INEXACT_RESULT: return L"FPU inexact result";
|
||||
case EXCEPTION_FLT_INVALID_OPERATION: return L"FPU invalid operation";
|
||||
case EXCEPTION_FLT_OVERFLOW: return L"FPU overflow";
|
||||
case EXCEPTION_FLT_STACK_CHECK: return L"FPU stack check";
|
||||
case EXCEPTION_FLT_UNDERFLOW: return L"FPU underflow";
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO: return L"Integer divide by zero";
|
||||
case EXCEPTION_INT_OVERFLOW: return L"Integer overflow";
|
||||
case EXCEPTION_PRIV_INSTRUCTION: return L"Privileged instruction";
|
||||
case EXCEPTION_IN_PAGE_ERROR: return L"In page error";
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION: return L"Illegal instruction";
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION: return L"Noncontinuable exception";
|
||||
case EXCEPTION_STACK_OVERFLOW: return L"Stack overflow";
|
||||
case EXCEPTION_INVALID_DISPOSITION: return L"Invalid disposition";
|
||||
case EXCEPTION_GUARD_PAGE: return L"Guard page";
|
||||
case EXCEPTION_INVALID_HANDLE: return L"Invalid handle";
|
||||
}
|
||||
|
||||
// anything else => unknown; display its exception code.
|
||||
// we don't punt to GetExceptionDescription because anything
|
||||
// we get called for will actually be a SEH exception.
|
||||
swprintf(description, maxChars, L"Unknown (0x%08X)", code);
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return a description of the exception type and cause (in English).
|
||||
**/
|
||||
static const wchar_t* GetExceptionDescription(const EXCEPTION_POINTERS* ep,
|
||||
wchar_t* description, size_t maxChars)
|
||||
{
|
||||
const EXCEPTION_RECORD* const er = ep->ExceptionRecord;
|
||||
|
||||
if(IsCppException(er))
|
||||
return GetCppExceptionDescription(er, description, maxChars);
|
||||
else
|
||||
return GetSehExceptionDescription(er, description, maxChars);
|
||||
}
|
||||
|
||||
|
||||
// return location at which the exception <er> occurred.
|
||||
// params: see debug_resolve_symbol.
|
||||
static void GetExceptionLocus(const EXCEPTION_POINTERS* ep,
|
||||
char* file, int* line, char* func)
|
||||
{
|
||||
// HACK: <ep> provides no useful information - ExceptionAddress always
|
||||
// points to kernel32!RaiseException. we use debug_get_nth_caller to
|
||||
// determine the real location.
|
||||
|
||||
const uint skip = 1; // skip RaiseException
|
||||
void* func_addr = debug_get_nth_caller(skip, ep->ContextRecord);
|
||||
(void)debug_resolve_symbol(func_addr, func, file, line);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// exception filter
|
||||
|
||||
// called when an exception is detected (see below); provides detailed
|
||||
// debugging information and exits.
|
||||
//
|
||||
// note: keep memory allocs and locking to an absolute minimum, because
|
||||
// they may deadlock the process!
|
||||
LONG WINAPI wseh_ExceptionFilter(EXCEPTION_POINTERS* ep)
|
||||
{
|
||||
// OutputDebugString raises an exception, which OUGHT to be swallowed
|
||||
// by WaitForDebugEvent but sometimes isn't. if we see it, ignore it.
|
||||
if(ep->ExceptionRecord->ExceptionCode == 0x40010006) // DBG_PRINTEXCEPTION_C
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
|
||||
// if run in a debugger, let it handle exceptions (tends to be more
|
||||
// convenient since it can bring up the crash location)
|
||||
if(IsDebuggerPresent())
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
// make sure we don't recurse infinitely if this function raises an
|
||||
// SEH exception. (we may only have the guard page's 4 KB worth of
|
||||
// stack space if the exception is EXCEPTION_STACK_OVERFLOW)
|
||||
static intptr_t nestingLevel = 0;
|
||||
cpu_AtomicAdd(&nestingLevel, 1);
|
||||
if(nestingLevel >= 3)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
// someone is already holding the dbghelp lock - this is bad.
|
||||
// we'll report this problem first and then try to display the
|
||||
// exception info regardless (maybe dbghelp won't blow up).
|
||||
if(win_is_locked(WDBG_SYM_CS) == 1)
|
||||
DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock..");
|
||||
|
||||
// extract details from ExceptionRecord.
|
||||
wchar_t descriptionBuf[150];
|
||||
const wchar_t* description = GetExceptionDescription(ep, descriptionBuf, ARRAY_SIZE(descriptionBuf));
|
||||
char file[DBG_FILE_LEN] = {0};
|
||||
int line = 0;
|
||||
char func[DBG_SYMBOL_LEN] = {0};
|
||||
GetExceptionLocus(ep, file, &line, func);
|
||||
|
||||
// this must happen before the error dialog because user could choose to
|
||||
// exit immediately there.
|
||||
wdbg_sym_write_minidump(ep);
|
||||
|
||||
wchar_t message[500];
|
||||
const wchar_t* messageFormat =
|
||||
L"Much to our regret we must report the program has encountered an error.\r\n"
|
||||
L"\r\n"
|
||||
L"Please let us know at http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n"
|
||||
L"\r\n"
|
||||
L"Details: unhandled exception (%s)\r\n";
|
||||
swprintf(message, ARRAY_SIZE(message), messageFormat, description);
|
||||
|
||||
uint flags = 0;
|
||||
if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
|
||||
flags = DE_NO_CONTINUE;
|
||||
ErrorReaction er = debug_display_error(message, flags, 1,ep->ContextRecord, file,line,func, 0);
|
||||
debug_assert(er == ER_CONTINUE); // nothing else possible
|
||||
|
||||
// invoke the Win32 default handler - it calls ExitProcess for
|
||||
// most exception types.
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// install SEH exception handler
|
||||
|
||||
/*
|
||||
|
||||
rationale:
|
||||
we want to replace the OS "program error" dialog box because it is not
|
||||
all too helpful in debugging. to that end, there are 5 ways to make sure
|
||||
unhandled SEH exceptions are caught:
|
||||
- via WaitForDebugEvent; the app is run from a separate debugger process.
|
||||
the exception is in another address space, so we'd have to deal with that
|
||||
and basically implement a full-featured debugger - overkill.
|
||||
- by wrapping all threads (their handler chains are in TLS) in __try.
|
||||
this can be done with the cooperation of wpthread, but threads not under
|
||||
our control aren't covered.
|
||||
- with a vectored exception handler. this works across threads, but it's
|
||||
only available on WinXP (unacceptable). since it is called before __try
|
||||
blocks, we would receive expected/legitimate exceptions.
|
||||
- by setting the per-process unhandled exception filter. as above, this works
|
||||
across threads and is at least portable across Win32. unfortunately, some
|
||||
Win32 DLLs appear to register their own handlers, so this isn't reliable.
|
||||
- by hooking the exception dispatcher. this isn't future-proof.
|
||||
|
||||
note that the vectored and unhandled-exception filters aren't called when
|
||||
the process is being debugged (messing with the PEB flag doesn't help;
|
||||
root cause is the Win32 KiUserExceptionDispatcher implementation).
|
||||
however, this is fine since the IDE's debugger is more helpful than our
|
||||
dialog (it is able to jump directly to the offending code).
|
||||
|
||||
wrapping all threads in a __try appears to be the best choice. unfortunately,
|
||||
we cannot retroactively install an SEH handler: the OS ensures SEH chain
|
||||
nodes are on the thread's stack (as defined by NT_TIB) in ascending order.
|
||||
(the handler would also have to be marked via .safeseh, but that is doable)
|
||||
consequently, we'll have to run within a __try; if the init code is to be
|
||||
covered, this must happen within the program entry point.
|
||||
|
||||
note: since C++ exceptions are implemented via SEH, we can also catch
|
||||
those here; it's nicer than a global try{} and avoids duplicating this code.
|
||||
we can still get at the C++ information (std::exception.what()) by examining
|
||||
the internal exception data structures. these are compiler-specific, but
|
||||
haven't changed from VC5-VC7.1.
|
||||
alternatively, _set_se_translator could to translate all SEH exceptions to
|
||||
C++ classes. this way is more reliable/documented, but has several drawbacks:
|
||||
- it wouldn't work at all in C programs,
|
||||
- a new fat exception class would have to be created to hold the
|
||||
SEH exception information (e.g. CONTEXT for a stack trace), and
|
||||
- this information would not be available for C++ exceptions.
|
||||
|
||||
*/
|
||||
|
||||
EXTERN_C int mainCRTStartup();
|
||||
|
||||
static int CallStartupWithinTryBlock()
|
||||
{
|
||||
int ret;
|
||||
__try
|
||||
{
|
||||
ret = mainCRTStartup();
|
||||
}
|
||||
__except(wseh_ExceptionFilter(GetExceptionInformation()))
|
||||
{
|
||||
ret = -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXTERN_C int wseh_EntryPoint()
|
||||
{
|
||||
#ifdef NEED_COOKIE_INIT
|
||||
// 2006-02-16 workaround for R6035 on VC8:
|
||||
//
|
||||
// SEH code compiled with /GS pushes a "security cookie" onto the
|
||||
// stack. since we're called before CRT init, the cookie won't have
|
||||
// been initialized yet, which would cause the CRT to FatalAppExit.
|
||||
// to solve this, we must call __security_init_cookie before any
|
||||
// hidden compiler-generated SEH registration code runs,
|
||||
// which means the __try block must be moved into a helper function.
|
||||
//
|
||||
// NB: wseh_EntryPoint() must not contain local string buffers,
|
||||
// either - /GS would install a cookie here as well (same problem).
|
||||
//
|
||||
// see http://msdn2.microsoft.com/en-US/library/ms235603.aspx
|
||||
__security_init_cookie();
|
||||
#endif
|
||||
return CallStartupWithinTryBlock();
|
||||
}
|
||||
18
source/lib/sysdep/win/wseh.h
Normal file
18
source/lib/sysdep/win/wseh.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* =========================================================================
|
||||
* File : wseh.h
|
||||
* Project : 0 A.D.
|
||||
* Description : Win32 debug support code and exception handler.
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
// license: GPL; see lib/license.txt
|
||||
|
||||
#ifndef INCLUDED_WSEH
|
||||
#define INCLUDED_WSEH
|
||||
|
||||
extern LONG WINAPI wseh_ExceptionFilter(EXCEPTION_POINTERS* ep);
|
||||
|
||||
EXTERN_C int wseh_EntryPoint();
|
||||
|
||||
#endif // #ifndef INCLUDED_WSEH
|
||||
Loading…
Reference in a new issue