Commit 1421d87c authored by Tamar Christina's avatar Tamar Christina Committed by Ben Gamari

Switch VEH to VCH and allow disabling of SEH completely.

Exception handling on Windows is unfortunately a bit complicated.
But essentially the VEH Handlers we currently have are running too
early.

This was a problem as it ran so early it also swallowed C++ exceptions
and other software exceptions which the system could have very well
recovered from.

So instead we use a sequence of chains to for the exception handlers to
run as late as possible. You really can't get any later than this.

Please read the comment in the patch for more details.

I'm also providing a switch to allow people to turn off the exception
handling entirely. In case it does present a problem with their code.

(Reverted and recommitted to fix authorship information)

Test Plan: ./validate

Reviewers: austin, hvr, bgamari, erikd, simonmar

Reviewed By: bgamari

Subscribers: rwbarton, thomie

GHC Trac Issues: #13911, #12110

Differential Revision: https://phabricator.haskell.org/D3911
parent 47888fd8
......@@ -163,6 +163,13 @@ Runtime system
compliance with the model set by the most Java virtual machine
implementations.
- The GHC runtime on Windows now uses Continue handlers instead of Vectorized
handlers to trap exceptions. This change gives other exception handlers a chance
to handle the exception before the runtime does. Furthermore The RTS flag
:rts-flag:`--install-seh-handlers=<yes|no>` Can be used on Wndows to
completely disable the runtime's handling of exceptions. See
:ghc-ticket:`13911`, :ghc-ticket:`12110`.
Template Haskell
~~~~~~~~~~~~~~~~
......
......@@ -220,6 +220,14 @@ Miscellaneous RTS options
capabilities. To disable the timer signal, use the ``-V0`` RTS
option (see above).
.. rts-flag:: --install-seh-handlers=⟨yes|no⟩
If yes (the default), the RTS on Windows installs exception handlers to
catch unhandled exceptions using the Windows exception handling mechanism.
This option is primarily useful for when you are using the Haskell code as a
DLL, and don't want the RTS to ungracefully terminate your application on
erros such as segfaults.
.. rts-flag:: -xm ⟨address⟩
.. index::
......
......@@ -189,6 +189,7 @@ typedef struct _CONCURRENT_FLAGS {
typedef struct _MISC_FLAGS {
Time tickInterval; /* units: TIME_RESOLUTION */
bool install_signal_handlers;
bool install_seh_handlers;
bool machineReadable;
StgWord linkerMemBase; /* address to ask the OS for memory
* for the linker, NULL ==> off */
......
......@@ -131,6 +131,7 @@ data ConcFlags = ConcFlags
data MiscFlags = MiscFlags
{ tickInterval :: RtsTime
, installSignalHandlers :: Bool
, installSEHHandlers :: Bool
, machineReadable :: Bool
, linkerMemBase :: Word
-- ^ address to ask the OS for memory for the linker, 0 ==> off
......@@ -404,6 +405,7 @@ getMiscFlags = do
let ptr = (#ptr RTS_FLAGS, MiscFlags) rtsFlagsPtr
MiscFlags <$> #{peek MISC_FLAGS, tickInterval} ptr
<*> #{peek MISC_FLAGS, install_signal_handlers} ptr
<*> #{peek MISC_FLAGS, install_seh_handlers} ptr
<*> #{peek MISC_FLAGS, machineReadable} ptr
<*> #{peek MISC_FLAGS, linkerMemBase} ptr
......
......@@ -50,6 +50,9 @@
* `Type.Reflection.withTypeable` is now polymorphic in the `RuntimeRep` of
its result.
* Add `installSEHHandlers` to `MiscFlags` in `GHC.RTS.Flags` to determine if
exception handling is enabled.
## 4.10.0.0 *April 2017*
* Bundled with GHC *TBA*
......
......@@ -225,8 +225,9 @@ void initRtsFlagsDefaults(void)
RtsFlags.ConcFlags.ctxtSwitchTime = USToTime(20000); // 20ms
RtsFlags.MiscFlags.install_signal_handlers = true;
RtsFlags.MiscFlags.machineReadable = false;
RtsFlags.MiscFlags.linkerMemBase = 0;
RtsFlags.MiscFlags.install_seh_handlers = true;
RtsFlags.MiscFlags.machineReadable = false;
RtsFlags.MiscFlags.linkerMemBase = 0;
#if defined(THREADED_RTS)
RtsFlags.ParFlags.nCapabilities = 1;
......@@ -426,6 +427,10 @@ usage_text[] = {
#endif
" --install-signal-handlers=<yes|no>",
" Install signal handlers (default: yes)",
#if defined(mingw32_HOST_OS)
" --install-seh-handlers=<yes|no>",
" Install exception handlers (default: yes)",
#endif
#if defined(THREADED_RTS)
" -e<n> Maximum number of outstanding local sparks (default: 4096)",
#endif
......@@ -840,6 +845,16 @@ error = true;
OPTION_UNSAFE;
RtsFlags.MiscFlags.install_signal_handlers = false;
}
else if (strequal("install-seh-handlers=yes",
&rts_argv[arg][2])) {
OPTION_UNSAFE;
RtsFlags.MiscFlags.install_seh_handlers = true;
}
else if (strequal("install-seh-handlers=no",
&rts_argv[arg][2])) {
OPTION_UNSAFE;
RtsFlags.MiscFlags.install_seh_handlers = false;
}
else if (strequal("machine-readable",
&rts_argv[arg][2])) {
OPTION_UNSAFE;
......
......@@ -44,8 +44,6 @@ int hs_main ( int argc, char *argv[], // program args
RtsConfig rts_config) // RTS configuration
{
BEGIN_WINDOWS_VEH_HANDLER
int exit_status;
SchedulerStatus status;
......@@ -56,11 +54,10 @@ int hs_main ( int argc, char *argv[], // program args
}
#endif
hs_init_ghc(&argc, &argv, rts_config);
BEGIN_WINDOWS_VEH_HANDLER
// kick off the computation by creating the main thread with a pointer
// to mainIO_closure representing the computation of the overall program;
// then enter the scheduler with this thread and off we go;
......
......@@ -15,6 +15,57 @@
// Exception / signal handlers.
/////////////////////////////////
/*
SEH (Structured Error Handler) on Windows is quite tricky. On x86 SEHs are
stack based and are stored in FS[0] of each thread. Which means every time we
spawn an OS thread we'd have to set up the error handling. However on x64 it's
table based and memory region based. e.g. you register a handler for a
particular memory range. This means that we'd have to register handlers for
each block of code we load externally or generate internally ourselves.
In Windows XP VEH (Vectored Exception Handler) and VCH (Vectored Continue
Handler) were added. Both of these are global/process wide handlers, the
former handling all exceptions and the latter handling only exceptions which
we're trying to recover from, e.g. a handler returned
EXCEPTION_CONTINUE_EXECUTION.
And lastly you have top level exception filters, which are also process global
but the problem here is that you can only have one, and setting this removes
the previous ones. The chain of exception handling looks like
[ Vectored Exception Handler ]
|
[ Structured Exception Handler ]
|
[ Exception Filters ]
|
[ Vectored Continue Handler ]
To make things more tricky, the exception handlers handle both hardware and
software exceptions Which means previously when we registered VEH handlers
we would also trap software exceptions. Which means when haskell code was
loaded in a C++ or C# context we would swallow exceptions and terminate in
contexes that normally the runtime should be able to continue on, e.g. you
could be handling the segfault in your C++ code, or the div by 0.
We could not handle these exceptions, but GHCi would just die a horrible death
then on normal Haskell only code when such an exception occurs.
So instead, we'll move to Continue handler, to run as late as possible, and
also register a filter which calls any existing filter, and then runs the
continue handlers, we then also only run as the last continue handler so we
don't supercede any other VCH handlers.
Lastly we'll also provide a way for users to disable the exception handling
entirely so even if the new approach doesn't solve the issue they can work
around it. After all, I don't expect any interpreted code if you are running
a haskell dll.
For a detailed analysis see
https://reverseengineering.stackexchange.com/questions/14992/what-are-the-vectored-continue-handlers
and https://www.gamekiller.net/threads/vectored-exception-handler.3237343/
*/
// Define some values for the ordering of VEH Handlers:
// - CALL_FIRST means call this exception handler first
// - CALL_LAST means call this exception handler last
......@@ -28,6 +79,7 @@
// Registered exception handler
PVOID __hs_handle = NULL;
LPTOP_LEVEL_EXCEPTION_FILTER oldTopFilter = NULL;
long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data)
{
......@@ -74,32 +126,61 @@ long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data)
return action;
}
long WINAPI __hs_exception_filter(struct _EXCEPTION_POINTERS *exception_data)
{
long result = EXCEPTION_CONTINUE_EXECUTION;
if (oldTopFilter)
{
result = (*oldTopFilter)(exception_data);
if (EXCEPTION_CONTINUE_SEARCH == result)
result = EXCEPTION_CONTINUE_EXECUTION;
return result;
}
return result;
}
void __register_hs_exception_handler( void )
{
// Allow the VEH handler to be registered only once.
if (!RtsFlags.MiscFlags.install_seh_handlers)
return;
// Allow the VCH handler to be registered only once.
if (NULL == __hs_handle)
{
__hs_handle = AddVectoredExceptionHandler(CALL_FIRST, __hs_exception_handler);
// Be the last one to run, We can then be sure we didn't interfere with
// anything else.
__hs_handle = AddVectoredContinueHandler(CALL_LAST,
__hs_exception_handler);
// should the handler not be registered this will return a null.
assert(__hs_handle);
// Register for an exception filter to ensure the continue handler gets
// hit if no one handled the exception.
oldTopFilter = SetUnhandledExceptionFilter (__hs_exception_filter);
}
else
{
errorBelch("There is no need to call __register_hs_exception_handler() twice, VEH handlers are global per process.");
errorBelch("There is no need to call __register_hs_exception_handler()"
" twice, VEH handlers are global per process.");
}
}
void __unregister_hs_exception_handler( void )
{
if (!RtsFlags.MiscFlags.install_seh_handlers)
return;
if (__hs_handle != NULL)
{
// Should the return value be checked? we're terminating anyway.
RemoveVectoredExceptionHandler(__hs_handle);
RemoveVectoredContinueHandler(__hs_handle);
__hs_handle = NULL;
}
else
{
errorBelch("__unregister_hs_exception_handler() called without having called __register_hs_exception_handler() first.");
errorBelch("__unregister_hs_exception_handler() called without having"
"called __register_hs_exception_handler() first.");
}
}
......@@ -63,6 +63,7 @@
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms681419(v=vs.85).aspx
//
long WINAPI __hs_exception_handler( struct _EXCEPTION_POINTERS *exception_data );
long WINAPI __hs_exception_filter(struct _EXCEPTION_POINTERS *exception_data);
// prototypes to the functions doing the registration and unregistration of the VEH handlers
void __register_hs_exception_handler( void );
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment