1
0
forked from 0ad/0ad

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.
This commit is contained in:
janwas 2007-06-04 22:59:14 +00:00
parent 92578ae553
commit d802b73d94
17 changed files with 286 additions and 242 deletions

View File

@ -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;

View File

@ -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");
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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))
//-----------------------------------------------------------------------------

View File

@ -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()

View File

@ -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

View File

@ -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();

View File

@ -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)
{

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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