diff --git a/build/premake/premake.lua b/build/premake/premake.lua index 5b4a97d5e2..cee4b383d4 100755 --- a/build/premake/premake.lua +++ b/build/premake/premake.lua @@ -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", diff --git a/source/lib/sysdep/win/wdbg.cpp b/source/lib/sysdep/win/wdbg.cpp index 3c36efe4eb..4b5e550c7e 100644 --- a/source/lib/sysdep/win/wdbg.cpp +++ b/source/lib/sysdep/win/wdbg.cpp @@ -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 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 (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 occurred. -// params: see debug_resolve_symbol. -static void get_exception_locus(const EXCEPTION_POINTERS* ep, - char* file, int* line, char* func) -{ - // HACK: 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) diff --git a/source/lib/sysdep/win/wdbg.h b/source/lib/sysdep/win/wdbg.h index f0bc0181e8..b10950b46a 100644 --- a/source/lib/sysdep/win/wdbg.h +++ b/source/lib/sysdep/win/wdbg.h @@ -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 diff --git a/source/lib/sysdep/win/whrt/hpet.cpp b/source/lib/sysdep/win/whrt/hpet.cpp index b5c694d4bb..c851945478 100644 --- a/source/lib/sysdep/win/whrt/hpet.cpp +++ b/source/lib/sysdep/win/whrt/hpet.cpp @@ -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; } diff --git a/source/lib/sysdep/win/wposix/wpthread.cpp b/source/lib/sysdep/win/wposix/wpthread.cpp index 5c81d7bb48..e197e33a12 100644 --- a/source/lib/sysdep/win/wposix/wpthread.cpp +++ b/source/lib/sysdep/win/wposix/wpthread.cpp @@ -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; } diff --git a/source/lib/sysdep/win/wseh.cpp b/source/lib/sysdep/win/wseh.cpp new file mode 100644 index 0000000000..98f1f5f21b --- /dev/null +++ b/source/lib/sysdep/win/wseh.cpp @@ -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 // __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 occurred. +// params: see debug_resolve_symbol. +static void GetExceptionLocus(const EXCEPTION_POINTERS* ep, + char* file, int* line, char* func) +{ + // HACK: 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(); +} diff --git a/source/lib/sysdep/win/wseh.h b/source/lib/sysdep/win/wseh.h new file mode 100644 index 0000000000..7ce4b7b0ac --- /dev/null +++ b/source/lib/sysdep/win/wseh.h @@ -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