From d802b73d94d9310e9aa0b5e21ffbf620f44fc0f3 Mon Sep 17 00:00:00 2001 From: janwas Date: Mon, 4 Jun 2007 22:59:14 +0000 Subject: [PATCH] winit: rename register macros for more clarity wdbg: cleanup, improve exception catcher (previously potentially failed if __try block came in non-main thread). required since wstartup no longer commandeers the entry point. winit, wstartup: update documentation This was SVN commit r5141. --- source/lib/sysdep/win/wcpu.cpp | 2 +- source/lib/sysdep/win/wdbg.cpp | 273 ++++++++++++---------- source/lib/sysdep/win/wdbg.h | 16 +- source/lib/sysdep/win/wdbg_sym.cpp | 4 +- source/lib/sysdep/win/wdir_watch.cpp | 4 +- source/lib/sysdep/win/wdll_delay_load.cpp | 2 +- source/lib/sysdep/win/whrt/whrt.cpp | 4 +- source/lib/sysdep/win/winit.cpp | 3 +- source/lib/sysdep/win/winit.h | 62 ++--- source/lib/sysdep/win/wposix/waio.cpp | 4 +- source/lib/sysdep/win/wposix/wposix.cpp | 2 +- source/lib/sysdep/win/wposix/wpthread.cpp | 12 +- source/lib/sysdep/win/wposix/wsock.cpp | 4 +- source/lib/sysdep/win/wposix/wtime.cpp | 2 +- source/lib/sysdep/win/wstartup.cpp | 124 +++++----- source/lib/sysdep/win/wstartup.h | 6 +- source/lib/sysdep/win/wutil.cpp | 4 +- 17 files changed, 286 insertions(+), 242 deletions(-) diff --git a/source/lib/sysdep/win/wcpu.cpp b/source/lib/sysdep/win/wcpu.cpp index bf9e0d4eeb..ccde24ca13 100644 --- a/source/lib/sysdep/win/wcpu.cpp +++ b/source/lib/sysdep/win/wcpu.cpp @@ -17,7 +17,7 @@ #include "wutil.h" #include "winit.h" -WINIT_REGISTER_INIT_EARLY(wcpu_Init); // wcpu -> whrt +WINIT_REGISTER_EARLY_INIT(wcpu_Init); // wcpu -> whrt static uint numProcessors = 0; diff --git a/source/lib/sysdep/win/wdbg.cpp b/source/lib/sysdep/win/wdbg.cpp index 1d1718d7a6..98a88e8e70 100644 --- a/source/lib/sysdep/win/wdbg.cpp +++ b/source/lib/sysdep/win/wdbg.cpp @@ -25,12 +25,7 @@ #include "winit.h" #include "wutil.h" -WINIT_REGISTER_INIT_MAIN(wdbg_Init); - -// used to prevent the vectored exception handler from taking charge when -// an exception is raised from the main thread (allows __try blocks to -// get control). latched in wdbg_Init. -static DWORD main_thread_id; +WINIT_REGISTER_EARLY_INIT(wdbg_Init); // registers exception handler // protects the breakpoint helper thread. @@ -45,43 +40,15 @@ static void unlock() } -void debug_puts(const char* text) +static NT_TIB* get_tib() { - OutputDebugStringA(text); -} - - -////////////////////////////////////////////////////////////////////////////// - - -// inform the debugger of the current thread's description, which it then -// displays instead of just the thread handle. -void wdbg_set_thread_name(const char* name) -{ - // we pass information to the debugger via a special exception it - // swallows. if not running under one, bail now to avoid - // "first chance exception" warnings. - if(!IsDebuggerPresent()) - return; - - // presented by Jay Bazuzi (from the VC debugger team) at TechEd 1999. - const struct ThreadNameInfo + NT_TIB* tib; + __asm { - DWORD type; - const char* name; - DWORD thread_id; // any valid ID or -1 for current thread - DWORD flags; - } - info = { 0x1000, name, (DWORD)-1, 0 }; - __try - { - RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - // if we get here, the debugger didn't handle the exception. - debug_warn("thread name hack doesn't work under this debugger"); + mov eax, fs:[NT_TIB.Self] + mov [tib], eax } + return tib; } @@ -143,7 +110,9 @@ void debug_heap_enable(DebugHeapChecks what) //----------------------------------------------------------------------------- +// thread suspension +// suspend a thread, execute a user callback, revive the thread. // to avoid deadlock, be VERY CAREFUL to avoid anything that may block, // including locks taken by the OS (e.g. malloc, GetProcAddress). @@ -207,11 +176,9 @@ static LibError call_while_suspended(WhileSuspendedFunc func, void* user_arg) } -////////////////////////////////////////////////////////////////////////////// -// +//----------------------------------------------------------------------------- // breakpoints -// -////////////////////////////////////////////////////////////////////////////// +//----------------------------------------------------------------------------- // breakpoints are set by storing the address of interest in a // debug register and marking it 'enabled'. @@ -390,11 +357,8 @@ LibError debug_remove_all_breaks() } -////////////////////////////////////////////////////////////////////////////// -// -// exception handler -// -////////////////////////////////////////////////////////////////////////////// +//----------------------------------------------------------------------------- +// analyze SEH exceptions // // analyze exceptions; determine their type and locus @@ -594,53 +558,59 @@ static void get_exception_locus(const EXCEPTION_POINTERS* ep, } -// called* when an SEH exception was not caught by the app; -// provides detailed debugging information and exits. -// (via win.cpp!entry's __except or vectored_exception_handler; see below) +//----------------------------------------------------------------------------- +// 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! -// -// 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 is very difficult to guarantee. -// - 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). -// worse, it's only available on WinXP (unacceptable). -// - 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. -// -// so, SNAFU. we compromise and register a regular __except filter at -// program entry point and add a vectored exception handler -// (if supported by the OS) to cover other threads. -// we steer clear of the process-wide unhandled exception filter, -// because it is not understood in what cases it can be overwritten; -// this precludes reliable use. -// -// 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. LONG WINAPI wdbg_exception_filter(EXCEPTION_POINTERS* ep) { // OutputDebugString raises an exception, which OUGHT to be swallowed @@ -648,6 +618,11 @@ LONG WINAPI wdbg_exception_filter(EXCEPTION_POINTERS* ep) 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. @@ -693,39 +668,64 @@ LONG WINAPI wdbg_exception_filter(EXCEPTION_POINTERS* ep) } -static LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ep) +//----------------------------------------------------------------------------- +// install SEH exception handler + +typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE)(_EXCEPTION_RECORD* ExceptionRecord, PVOID EstablisherFrame, _CONTEXT* ContextRecord, PVOID DispatcherContext); + +struct _EXCEPTION_REGISTRATION_RECORD { - // since we're called from the vectored handler chain, - // ignore exceptions from the main thread. this allows - // __try blocks to take charge; entry() catches all exceptions with a - // standard filter and relays them to wdbg_exception_filter. - if(main_thread_id == GetCurrentThreadId()) - return EXCEPTION_CONTINUE_SEARCH; - return wdbg_exception_filter(ep); + _EXCEPTION_REGISTRATION_RECORD* Next; + PEXCEPTION_ROUTINE Handler; +}; + +static bool IsUnwinding(DWORD exceptionFlags) +{ + return (exceptionFlags & 2) != 0; +} + +static EXCEPTION_DISPOSITION ExceptionHandler(_EXCEPTION_RECORD* ExceptionRecord, PVOID EstablisherFrame, _CONTEXT* ContextRecord, PVOID 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) { - // see decl - main_thread_id = GetCurrentThreadId(); - - // add vectored exception handler (if supported by the OS). - // see rationale above. -#if _WIN32_WINNT >= 0x0500 // this is how winbase.h tests for it - const HMODULE hKernel32Dll = LoadLibrary("kernel32.dll"); - PVOID (WINAPI *pAddVectoredExceptionHandler)(IN ULONG FirstHandler, IN PVECTORED_EXCEPTION_HANDLER VectoredHandler); - *(void**)&pAddVectoredExceptionHandler = GetProcAddress(hKernel32Dll, "AddVectoredExceptionHandler"); - FreeLibrary(hKernel32Dll); - if(pAddVectoredExceptionHandler) - pAddVectoredExceptionHandler(TRUE, vectored_exception_handler); -#endif + 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) @@ -766,17 +766,6 @@ bool debug_is_code_ptr(void* p) } -static NT_TIB* get_tib() -{ - NT_TIB* tib; - __asm - { - mov eax, fs:[NT_TIB.Self] - mov [tib], eax - } - return tib; -} - bool debug_is_stack_ptr(void* p) { uintptr_t addr = (uintptr_t)p; @@ -795,6 +784,38 @@ bool debug_is_stack_ptr(void* p) } +void debug_puts(const char* text) +{ + OutputDebugStringA(text); +} +// inform the debugger of the current thread's description, which it then +// displays instead of just the thread handle. +void wdbg_set_thread_name(const char* name) +{ + // we pass information to the debugger via a special exception it + // swallows. if not running under one, bail now to avoid + // "first chance exception" warnings. + if(!IsDebuggerPresent()) + return; + // presented by Jay Bazuzi (from the VC debugger team) at TechEd 1999. + const struct ThreadNameInfo + { + DWORD type; + const char* name; + DWORD thread_id; // any valid ID or -1 for current thread + DWORD flags; + } + info = { 0x1000, name, (DWORD)-1, 0 }; + __try + { + RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + // if we get here, the debugger didn't handle the exception. + debug_warn("thread name hack doesn't work under this debugger"); + } +} diff --git a/source/lib/sysdep/win/wdbg.h b/source/lib/sysdep/win/wdbg.h index 146ef47829..f0bc0181e8 100644 --- a/source/lib/sysdep/win/wdbg.h +++ b/source/lib/sysdep/win/wdbg.h @@ -17,11 +17,19 @@ # error "port this or define to implementation function" #endif -// internal use only: + extern void wdbg_set_thread_name(const char* name); -// see rationale at definition. -struct _EXCEPTION_POINTERS; -extern long __stdcall wdbg_exception_filter(_EXCEPTION_POINTERS* ep); + +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/wdbg_sym.cpp b/source/lib/sysdep/win/wdbg_sym.cpp index 228065cd48..c1d88fcdaa 100644 --- a/source/lib/sysdep/win/wdbg_sym.cpp +++ b/source/lib/sysdep/win/wdbg_sym.cpp @@ -37,8 +37,8 @@ #pragma comment(lib, "oleaut32.lib") // VariantChangeType #endif -WINIT_REGISTER_INIT_MAIN(wdbg_sym_Init); -WINIT_REGISTER_SHUTDOWN_MAIN(wdbg_sym_Shutdown); +WINIT_REGISTER_MAIN_INIT(wdbg_sym_Init); +WINIT_REGISTER_MAIN_SHUTDOWN(wdbg_sym_Shutdown); // note: it is safe to use debug_assert/debug_warn/CHECK_ERR even during a // stack trace (which is triggered by debug_assert et al. in app code) because diff --git a/source/lib/sysdep/win/wdir_watch.cpp b/source/lib/sysdep/win/wdir_watch.cpp index fe6b87f157..076833941a 100644 --- a/source/lib/sysdep/win/wdir_watch.cpp +++ b/source/lib/sysdep/win/wdir_watch.cpp @@ -22,8 +22,8 @@ #include "winit.h" #include "wutil.h" -WINIT_REGISTER_INIT_MAIN(wdir_watch_Init); -WINIT_REGISTER_SHUTDOWN_MAIN(wdir_watch_Shutdown); +WINIT_REGISTER_MAIN_INIT(wdir_watch_Init); +WINIT_REGISTER_MAIN_SHUTDOWN(wdir_watch_Shutdown); // rationale for polling: // much simpler than pure asynchronous notification: no need for a diff --git a/source/lib/sysdep/win/wdll_delay_load.cpp b/source/lib/sysdep/win/wdll_delay_load.cpp index 27cbaceb85..370e9f0ae0 100644 --- a/source/lib/sysdep/win/wdll_delay_load.cpp +++ b/source/lib/sysdep/win/wdll_delay_load.cpp @@ -15,7 +15,7 @@ #include "win.h" #include "winit.h" -WINIT_REGISTER_SHUTDOWN_LATE2(wdll_Shutdown); // last - DLLs are unloaded here +WINIT_REGISTER_LATE_SHUTDOWN2(wdll_Shutdown); // last - DLLs are unloaded here //----------------------------------------------------------------------------- // delay loading (modified from VC7 DelayHlp.cpp and DelayImp.h) diff --git a/source/lib/sysdep/win/whrt/whrt.cpp b/source/lib/sysdep/win/whrt/whrt.cpp index 1238cca595..6987b8969c 100644 --- a/source/lib/sysdep/win/whrt/whrt.cpp +++ b/source/lib/sysdep/win/whrt/whrt.cpp @@ -22,8 +22,8 @@ #include "counter.h" -WINIT_REGISTER_INIT_EARLY2(whrt_Init); // whrt -> wtime -WINIT_REGISTER_SHUTDOWN_LATE(whrt_Shutdown); +WINIT_REGISTER_EARLY_INIT2(whrt_Init); // whrt -> wtime +WINIT_REGISTER_LATE_SHUTDOWN(whrt_Shutdown); namespace ERR diff --git a/source/lib/sysdep/win/winit.cpp b/source/lib/sysdep/win/winit.cpp index 36efec5579..eb4454af1c 100644 --- a/source/lib/sysdep/win/winit.cpp +++ b/source/lib/sysdep/win/winit.cpp @@ -11,9 +11,10 @@ #include "precompiled.h" #include "winit.h" -#include "win.h" // GetTickCount +#include "win.h" // GetTickCount for quick'n dirty timing // see http://blogs.msdn.com/larryosterman/archive/2004/09/27/234840.aspx +// for discussion of a similar mechanism. typedef LibError (*PfnLibErrorVoid)(void); diff --git a/source/lib/sysdep/win/winit.h b/source/lib/sysdep/win/winit.h index 1929ab74e2..ec637d07e9 100644 --- a/source/lib/sysdep/win/winit.h +++ b/source/lib/sysdep/win/winit.h @@ -16,26 +16,35 @@ Overview -------- -participating modules store function pointer(s) to their init and/or -shutdown function in a specific COFF section. the sections are +This facility allows registering init and shutdown functions with only +one line of code and zero runtime overhead. It provides for dependencies +between modules, allowing groups of functions to run before others. + + +Details +------- + +Participating modules store function pointer(s) to their init and/or +shutdown function in a specific COFF section. The sections are grouped according to the desired notification and the order in which functions are to be called (useful if one module depends on another). -they are then gathered by the linker and arranged in alphabetical order. -placeholder variables in the sections indicate where the series of +They are then gathered by the linker and arranged in alphabetical order. +Placeholder variables in the sections indicate where the series of functions begins and ends for a given notification time. -at runtime, all of the function pointers between the markers are invoked. +At runtime, all of the function pointers between the markers are invoked. Example ------- -WINIT_REGISTER_INIT_MAIN(wtime_Init); // whrt -> wtime +(at file scope:) +WINIT_REGISTER_MAIN_INIT(InitCallback); Rationale --------- -several methods of module init are possible: (see Large Scale C++ Design) +Several methods of module init are possible: (see Large Scale C++ Design) - on-demand initialization: each exported function would have to check if init already happened. that would be brittle and hard to verify. - singleton: variant of the above, but not applicable to a @@ -56,15 +65,15 @@ several methods of module init are possible: (see Large Scale C++ Design) */ + //----------------------------------------------------------------------------- // section declarations // section names are of the format "WINIT${type}{group}". // {type} is I for initialization- or S for shutdown functions. // {group} is [0, 9] - see below. -// note: __declspec(allocate) requires sections to be 'declared' -// before using them. - +// note: __declspec(allocate) requires declaring segments in advance via +// #pragma section. #pragma section("WINIT$I$", read) #pragma section("WINIT$I0", read) #pragma section("WINIT$I1", read) @@ -81,6 +90,7 @@ several methods of module init are possible: (see Large Scale C++ Design) #pragma section("WINIT$SZ", read) #pragma comment(linker, "/merge:WINIT=.rdata") + //----------------------------------------------------------------------------- // Function groups @@ -90,14 +100,14 @@ several methods of module init are possible: (see Large Scale C++ Design) // (this is because the linker sorts sections alphabetically but doesn't // specify the order in which object files are processed.) -// register a function to be called at the given time. +// these macros register a function to be called at the given time. // usage: invoke at file scope, passing a function identifier/symbol. // rationale: // - __declspec(allocate) requires section declarations, but allows users to // write only one line (instead of needing an additional #pragma data_seg) // - fixed groups instead of passing a group number are more clear and // encourage thinking about init order. (__declspec(allocate) requires -// a single string literal anyway and doesn't support merging) +// a single string literal anyway and doesn't support string merging) // - why EXTERN_C and __pragma? VC8's link-stage optimizer believes // the static function pointers defined by WINIT_REGISTER_* to be unused; // unless action is taken, they would be removed. to prevent this, we @@ -107,29 +117,29 @@ several methods of module init are possible: (see Large Scale C++ Design) // very early init; must not fail, since error handling code *crashes* // if called before these have completed. -#define WINIT_REGISTER_INIT_CRITICAL(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I0")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +#define WINIT_REGISTER_CRITICAL_INIT(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I0")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) // meant for modules with dependents but whose init is complicated and may -// raise error/warning messages (=> can't go in WINIT_REGISTER_INIT_CRITICAL) -#define WINIT_REGISTER_INIT_EARLY(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I1")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +// raise error/warning messages (=> can't go in WINIT_REGISTER_CRITICAL_INIT) +#define WINIT_REGISTER_EARLY_INIT(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I1")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) -// available for dependents of WINIT_REGISTER_INIT_EARLY-modules that -// must still come before WINIT_REGISTER_INIT_MAIN. -#define WINIT_REGISTER_INIT_EARLY2(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I2")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +// available for dependents of WINIT_REGISTER_EARLY_INIT-modules that +// must still come before WINIT_REGISTER_MAIN_INIT. +#define WINIT_REGISTER_EARLY_INIT2(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I2")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) // most modules will go here unless they are often used or // have many dependents. -#define WINIT_REGISTER_INIT_MAIN(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I6")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +#define WINIT_REGISTER_MAIN_INIT(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I6")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) // available for any modules that may need to come after -// WINIT_REGISTER_INIT_MAIN (unlikely) -#define WINIT_REGISTER_INIT_LATE(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I7")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +// WINIT_REGISTER_MAIN_INIT (unlikely) +#define WINIT_REGISTER_LATE_INIT(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$I7")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) -#define WINIT_REGISTER_SHUTDOWN_EARLY(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S0")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) -#define WINIT_REGISTER_SHUTDOWN_EARLY2(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S1")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) -#define WINIT_REGISTER_SHUTDOWN_MAIN(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S6")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) -#define WINIT_REGISTER_SHUTDOWN_LATE(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S7")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) -#define WINIT_REGISTER_SHUTDOWN_LATE2(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S8")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +#define WINIT_REGISTER_EARLY_SHUTDOWN(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S0")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +#define WINIT_REGISTER_EARLY_SHUTDOWN2(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S1")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +#define WINIT_REGISTER_MAIN_SHUTDOWN(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S6")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +#define WINIT_REGISTER_LATE_SHUTDOWN(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S7")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) +#define WINIT_REGISTER_LATE_SHUTDOWN2(func) static LibError func(void); EXTERN_C __declspec(allocate("WINIT$S8")) LibError (*p##func)(void) = func; __pragma(comment(linker, "/include:_p"#func)) //----------------------------------------------------------------------------- diff --git a/source/lib/sysdep/win/wposix/waio.cpp b/source/lib/sysdep/win/wposix/waio.cpp index 97c29da965..4beb6eb592 100644 --- a/source/lib/sysdep/win/wposix/waio.cpp +++ b/source/lib/sysdep/win/wposix/waio.cpp @@ -20,8 +20,8 @@ #include "lib/sysdep/cpu.h" #include "lib/bits.h" -WINIT_REGISTER_INIT_MAIN(waio_Init); -WINIT_REGISTER_SHUTDOWN_MAIN(waio_Shutdown); +WINIT_REGISTER_MAIN_INIT(waio_Init); +WINIT_REGISTER_MAIN_SHUTDOWN(waio_Shutdown); static void lock() diff --git a/source/lib/sysdep/win/wposix/wposix.cpp b/source/lib/sysdep/win/wposix/wposix.cpp index ac92115db6..da4f1c4cd3 100644 --- a/source/lib/sysdep/win/wposix/wposix.cpp +++ b/source/lib/sysdep/win/wposix/wposix.cpp @@ -15,7 +15,7 @@ #include "crt_posix.h" // _getcwd #include "lib/bits.h" -WINIT_REGISTER_INIT_CRITICAL(wposix_Init); // wposix -> error handling +WINIT_REGISTER_CRITICAL_INIT(wposix_Init); // wposix -> error handling //----------------------------------------------------------------------------- // sysconf diff --git a/source/lib/sysdep/win/wposix/wpthread.cpp b/source/lib/sysdep/win/wposix/wpthread.cpp index ac01bfae06..5c81d7bb48 100644 --- a/source/lib/sysdep/win/wposix/wpthread.cpp +++ b/source/lib/sysdep/win/wposix/wpthread.cpp @@ -469,14 +469,10 @@ static unsigned __stdcall thread_start(void* param) void* arg = func_and_arg->arg; win_free(param); - void* ret = (void*)-1; - __try - { - ret = func(arg); - } - __except(wdbg_exception_filter(GetExceptionInformation())) - { - } + u8 xrrStorage[WDBG_XRR_STORAGE_SIZE]; + wdbg_InstallExceptionHandler(xrrStorage); + + void* ret = func(arg); call_tls_dtors(); diff --git a/source/lib/sysdep/win/wposix/wsock.cpp b/source/lib/sysdep/win/wposix/wsock.cpp index 449d297013..1050dc8531 100644 --- a/source/lib/sysdep/win/wposix/wsock.cpp +++ b/source/lib/sysdep/win/wposix/wsock.cpp @@ -20,8 +20,8 @@ #pragma comment(lib, "ws2_32.lib") #endif -WINIT_REGISTER_INIT_MAIN(wsock_Init); -WINIT_REGISTER_SHUTDOWN_MAIN(wsock_Shutdown); +WINIT_REGISTER_MAIN_INIT(wsock_Init); +WINIT_REGISTER_MAIN_SHUTDOWN(wsock_Shutdown); uint16_t htons(uint16_t s) { diff --git a/source/lib/sysdep/win/wposix/wtime.cpp b/source/lib/sysdep/win/wposix/wtime.cpp index 266a3310a5..2796689c6d 100644 --- a/source/lib/sysdep/win/wposix/wtime.cpp +++ b/source/lib/sysdep/win/wposix/wtime.cpp @@ -19,7 +19,7 @@ #include "lib/sysdep/cpu.h" // cpu_i64FromDouble #include "lib/sysdep/win/whrt/whrt.h" -WINIT_REGISTER_INIT_MAIN(wtime_Init); // whrt -> wtime +WINIT_REGISTER_MAIN_INIT(wtime_Init); // whrt -> wtime // NT system time and FILETIME are hectonanoseconds since Jan. 1, 1601 UTC. // SYSTEMTIME is a struct containing month, year, etc. diff --git a/source/lib/sysdep/win/wstartup.cpp b/source/lib/sysdep/win/wstartup.cpp index 30ba5c7e73..71112a2e7a 100644 --- a/source/lib/sysdep/win/wstartup.cpp +++ b/source/lib/sysdep/win/wstartup.cpp @@ -13,46 +13,76 @@ #include "winit.h" +/* -//----------------------------------------------------------------------------- -// shutdown +Shutdown +-------- -// note: the alternative of using atexit has two disadvantages. -// - the call to atexit must come after _cinit, which means we'd need to be -// called manually from main (see discussion on init below) -// - other calls to atexit from ctors or hidden compiler-generated init code -// for static objects would cause those handlers to be called after ours, -// which may cause shutdown order bugs. +our shutdown function must be called after static C++ dtors, since they +may use wpthread and wtime functions. how to accomplish this? -//----------------------------------------------------------------------------- -// init +hooking ExitProcess is one way of ensuring we're the very last bit of +code to be called. this has several big problems, though: +- if other exit paths (such as CorExitProcess) are added by MS and + triggered via external code injected into our process, we miss our cue + to shut down. one annoying consequence would be that the Aken driver + remains loaded. however, users also can terminate our process at any + time, so we need to safely handle lack of shutdown anyway. +- IAT hooking breaks if kernel32 is delay-loaded (who knows what + people are going to do..) +- Detours-style trampolines are nonportable (the Detours library currently + only ships with code to handle IA-32) and quite risky, since antivirus + programs may flag this activity. -// the init functions need to be called before any use of Windows-specific -// code. in particular, static ctors may use whrt or wpthread, so we ought to -// be initialized before them as well. -// -// one possibility is using WinMain as the entry point, and then calling the -// application's main(), but this is expressly forbidden by the C standard. -// VC apparently makes use of this and changes its calling convention. -// if we call it, everything appears to work but stack traces in -// release mode are incorrect (symbol address is off by 4). -// -// another alternative is re#defining the app's main function to app_main, -// having the OS call our main, and then dispatching to app_main. -// however, this leads to trouble when another library (e.g. SDL) wants to -// do the same. -// moreover, this file is compiled into a static library and used both for -// the 0ad executable as well as the separate self-test. this means -// we can't enable the main() hook for one and disable it in the other. -// -// requiring users to call us at the beginning of main is brittle in general, -// comes after static ctors, and is difficult to achieve in external code -// such as the (automatically generated) self-test. -// -// commandeering the entry point, doing init there and then calling -// mainCRTStartup would work, but doesn't help with shutdown - additional -// measures are required (see above). note that this approach means we're -// initialized before _cinit, denying the use of non-stateless CRT functions. +having all exit paths call our shutdown and then _cexit would work, +provided we actually find and control all of them (unhandled exceptions +and falling off the end of the last thread are non-obvious examples). +that aside, it'd require changes to calling code, and we're trying to +keep this mess hidden and transparent to users. + +the remaining alternative is to use atexit. this approach has the +advantage of being covered by the CRT runtime checks and leak detector, +because those are shut down after the atexit handlers are called. +however, it does require that our shutdown callback to be the very last, +i.e. registered first. fortunately, the init stage can guarantee this. + + +Init +---- + +For the same reasons as above, our init really should happen before static +C++ ctors are called. + +using WinMain as the entry point and then calling the application's main() +doesn't satisy the above requirement, and is expressly forbidden by ANSI C. +(VC apparently makes use of this and changes its calling convention. +if we call it, everything appears to work but stack traces in release +mode are incorrect, since symbol addresses are off by 4 bytes.) + +another alternative is re#defining the app's main function to app_main, +having the OS call our main, and then dispatching to app_main. +however, this leads to trouble when another library (e.g. SDL) wants to +do the same. +moreover, this file is compiled into a static library and used both for +the 0ad executable as well as the separate self-test. this means +we can't enable the main() hook for one and disable it in the other. + +requiring users to call us at the beginning of main is brittle in general, +comes after static ctors, and is difficult to achieve in external code +such as the (automatically generated) self-test. + +commandeering the entry point, doing init there and then calling +mainCRTStartup would work, but doesn't allow the use of atexit for shutdown +(nor any other non-stateless CRT functions to be called during init). + +the way out is to have an init-and-call-atexit function triggered by means +of the CRT init mechanism. we arrange init order such that this happens +before static C++ ctors, thus meeting all of the above requirements. +(note: this is possible because crtexe.c and its .CRT$XI and .CRT$XC +sections holding static initializers and ctors are linked into the +application, not the CRT DLL.) + +*/ EXTERN_C void InitAndRegisterShutdown() @@ -65,25 +95,3 @@ EXTERN_C void InitAndRegisterShutdown() EXTERN_C void(*pInitAndRegisterShutdown)() = InitAndRegisterShutdown; #pragma comment(linker, "/include:_pInitAndRegisterShutdown") #pragma data_seg() - - -//----------------------------------------------------------------------------- -// SEH wrapper - -#include "wdbg.h" // wdbg_exception_filter - -typedef int(*PfnIntVoid)(void); - -static int RunWithinTryBlock(PfnIntVoid func) -{ - int ret; - //__try - { - ret = func(); - } - //__except(wdbg_exception_filter(GetExceptionInformation())) - { - ret = -1; - } - return ret; -} diff --git a/source/lib/sysdep/win/wstartup.h b/source/lib/sysdep/win/wstartup.h index 850c184033..7dccec6571 100644 --- a/source/lib/sysdep/win/wstartup.h +++ b/source/lib/sysdep/win/wstartup.h @@ -12,9 +12,9 @@ // be called at the appropriate times. // // the current implementation manages to trigger winit initialization -// between CRT init and static C++ ctors. that means wpthread etc. APIs -// are safe to use from the ctors and also that winit initializers are -// allowed to use non-stateless CRT functions such as atexit. +// in-between calls to CRT init and the static C++ ctors. that means +// wpthread etc. APIs are safe to use from ctors, and winit initializers +// are allowed to use non-stateless CRT functions such as atexit. #ifndef INCLUDED_WSTARTUP #define INCLUDED_WSTARTUP diff --git a/source/lib/sysdep/win/wutil.cpp b/source/lib/sysdep/win/wutil.cpp index de18a10549..69af4a9c1d 100644 --- a/source/lib/sysdep/win/wutil.cpp +++ b/source/lib/sysdep/win/wutil.cpp @@ -19,8 +19,8 @@ #include "win.h" #include "winit.h" -WINIT_REGISTER_INIT_EARLY(wutil_Init); -WINIT_REGISTER_SHUTDOWN_LATE(wutil_Shutdown); +WINIT_REGISTER_EARLY_INIT(wutil_Init); +WINIT_REGISTER_LATE_SHUTDOWN(wutil_Shutdown); //----------------------------------------------------------------------------- // safe allocator