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:
parent
92578ae553
commit
d802b73d94
@ -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;
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user