summaryrefslogtreecommitdiff
path: root/rts/win32/veh_excn.c
diff options
context:
space:
mode:
Diffstat (limited to 'rts/win32/veh_excn.c')
-rw-r--r--rts/win32/veh_excn.c259
1 files changed, 244 insertions, 15 deletions
diff --git a/rts/win32/veh_excn.c b/rts/win32/veh_excn.c
index d925ad8919..2d9de52199 100644
--- a/rts/win32/veh_excn.c
+++ b/rts/win32/veh_excn.c
@@ -5,16 +5,79 @@
* Error Handling implementations for windows
*
* ---------------------------------------------------------------------------*/
-
+#define UNICODE 1
#include "Rts.h"
#include "ghcconfig.h"
#include "veh_excn.h"
+#include "LinkerInternals.h"
#include <assert.h>
+#include <stdbool.h>
+#include <dbghelp.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <wchar.h>
+#include <windows.h>
+#include <stdio.h>
+#include <excpt.h>
+#include <inttypes.h>
+#include <dbghelp.h>
+#include <signal.h>
/////////////////////////////////
// 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,11 +91,19 @@
// Registered exception handler
PVOID __hs_handle = NULL;
+LPTOP_LEVEL_EXCEPTION_FILTER oldTopFilter = NULL;
+bool crash_dump = false;
+bool filter_called = false;
long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data)
{
- long action = EXCEPTION_CONTINUE_SEARCH;
+ if (!crash_dump && filter_called)
+ return EXCEPTION_CONTINUE_EXECUTION;
+
+ long action = EXCEPTION_CONTINUE_SEARCH;
+ int exit_code = EXIT_FAILURE;
ULONG_PTR what;
+ fprintf (stderr, "\n");
// When the system unwinds the VEH stack after having handled an excn,
// return immediately.
@@ -42,21 +113,28 @@ long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data)
switch (exception_data->ExceptionRecord->ExceptionCode) {
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
- fprintf(stdout, "divide by zero\n");
+ fprintf(stderr, "divide by zero\n");
action = EXCEPTION_CONTINUE_EXECUTION;
+ exit_code = SIGFPE;
break;
case EXCEPTION_STACK_OVERFLOW:
- fprintf(stdout, "C stack overflow in generated code\n");
+ fprintf(stderr, "C stack overflow in generated code\n");
action = EXCEPTION_CONTINUE_EXECUTION;
break;
case EXCEPTION_ACCESS_VIOLATION:
what = exception_data->ExceptionRecord->ExceptionInformation[0];
- fprintf(stdout, "Access violation in generated code"
- " when %s %p\n"
- , what == 0 ? "reading" : what == 1 ? "writing" : what == 8 ? "executing data at" : "?"
- , (void*) exception_data->ExceptionRecord->ExceptionInformation[1]
+ fprintf(stderr, "Access violation in generated code"
+ " when %s 0x%" PRIxPTR "\n"
+ , what == 0 ? "reading"
+ : what == 1 ? "writing"
+ : what == 8 ? "executing data at"
+ : "?"
+ , (uintptr_t) exception_data
+ ->ExceptionRecord
+ ->ExceptionInformation[1]
);
action = EXCEPTION_CONTINUE_EXECUTION;
+ exit_code = SIGSEGV;
break;
default:;
}
@@ -66,40 +144,191 @@ long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data)
// But the correct action is still to exit as fast as possible.
if (EXCEPTION_CONTINUE_EXECUTION == action)
{
- fflush(stdout);
- stg_exit(EXIT_FAILURE);
+ fflush(stderr);
+ generateStack (exception_data);
+ generateDump (exception_data);
+ stg_exit(exit_code);
}
}
return action;
}
+long WINAPI __hs_exception_filter(struct _EXCEPTION_POINTERS *exception_data)
+{
+ filter_called = true;
+ long result = EXCEPTION_CONTINUE_EXECUTION;
+ if (oldTopFilter)
+ {
+ result = (*oldTopFilter)(exception_data);
+ if (EXCEPTION_CONTINUE_SEARCH == result)
+ result = EXCEPTION_CONTINUE_EXECUTION;
+ return result;
+ }
+
+ crash_dump = true;
+ 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.");
}
}
+// Generate a crash dump, however in order for these to generate undecorated
+// names we really need to be able to generate PDB files.
+void generateDump (EXCEPTION_POINTERS* pExceptionPointers)
+{
+ if (!RtsFlags.MiscFlags.generate_dump_file)
+ return;
+
+ WCHAR szPath[MAX_PATH];
+ WCHAR szFileName[MAX_PATH];
+ WCHAR const *const szAppName = L"ghc";
+ WCHAR const *const szVersion = L"";
+ DWORD dwBufferSize = MAX_PATH;
+ HANDLE hDumpFile;
+ SYSTEMTIME stLocalTime;
+ MINIDUMP_EXCEPTION_INFORMATION ExpParam;
+
+ GetLocalTime (&stLocalTime);
+ GetTempPathW (dwBufferSize, szPath);
+
+ swprintf (szFileName, MAX_PATH,
+ L"%ls%ls%ls-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
+ szPath, szAppName, szVersion,
+ stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
+ stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
+ GetCurrentProcessId(), GetCurrentThreadId());
+ hDumpFile = CreateFileW (szFileName, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
+
+ ExpParam.ThreadId = GetCurrentThreadId();
+ ExpParam.ExceptionPointers = pExceptionPointers;
+ ExpParam.ClientPointers = TRUE;
+
+ MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
+ hDumpFile, MiniDumpNormal | MiniDumpWithDataSegs |
+ MiniDumpWithThreadInfo | MiniDumpWithCodeSegs,
+ &ExpParam, NULL, NULL);
+
+ fprintf (stderr, "Crash dump created. Dump written to:\n\t%ls", szFileName);
+}
+
+// Generate stack trace information, we can piggy back on information we know
+// about in the runtime linker to resolve symbols. So this is a good opportunity
+// to make the output more useful.
+void generateStack (EXCEPTION_POINTERS* pExceptionPointers)
+{
+ if (!RtsFlags.MiscFlags.generate_stack_trace)
+ return;
+
+ PCONTEXT context = pExceptionPointers->ContextRecord;
+ STACKFRAME64 stackFrame = {0};
+ DWORD machineType;
+
+#if defined(x86_64_HOST_ARCH)
+ machineType = IMAGE_FILE_MACHINE_AMD64;
+ stackFrame.AddrPC.Offset = context->Rip;
+ stackFrame.AddrPC.Mode = AddrModeFlat;
+
+ stackFrame.AddrFrame.Offset = context->Rbp;
+ stackFrame.AddrFrame.Mode = AddrModeFlat;
+
+ stackFrame.AddrStack.Offset = context->Rsp;
+ stackFrame.AddrStack.Mode = AddrModeFlat;
+#else
+ machineType = IMAGE_FILE_MACHINE_I386;
+ stackFrame.AddrPC.Offset = context->Eip;
+ stackFrame.AddrPC.Mode = AddrModeFlat;
+
+ stackFrame.AddrFrame.Offset = context->Ebp;
+ stackFrame.AddrFrame.Mode = AddrModeFlat;
+
+ stackFrame.AddrStack.Offset = context->Esp;
+ stackFrame.AddrStack.Mode = AddrModeFlat;
+#endif
+ fprintf (stderr, "\n Attempting to reconstruct a stack trace...\n\n");
+ if (!SymInitialize (GetCurrentProcess (), NULL, true))
+ fprintf (stderr, " \nNOTE: Symbols could not be loaded. Addresses may"
+ " be unresolved.\n\n");
+
+ /* Maximum amount of stack frames to show. */
+ /* Phyx: I'm not sure if I should make this configurable or not. Would a
+ longer stack really be more useful? usually you only care about the top
+ few. */
+ int max_frames = 35;
+
+ fprintf (stderr, " Frame\tCode address\n");
+ DWORD64 lastBp = 0; /* Prevent loops with optimized stackframes. */
+ while (StackWalk64 (machineType, GetCurrentProcess(), GetCurrentThread(),
+ &stackFrame, context, NULL, SymFunctionTableAccess64,
+ SymGetModuleBase64, NULL) && max_frames > 0)
+ {
+ if (stackFrame.AddrPC.Offset == 0)
+ {
+ fprintf (stderr, "Null address\n");
+ break;
+ }
+ wchar_t buffer[1024];
+ uintptr_t topSp = 0;
+ fprintf (stderr, " * 0x%" PRIxPTR "\t%ls\n",
+ (uintptr_t)stackFrame.AddrFrame.Offset,
+ resolveSymbolAddr ((wchar_t*)&buffer, 1024,
+ (SymbolAddr*)(intptr_t)stackFrame.AddrPC.Offset,
+ &topSp));
+ if (lastBp >= stackFrame.AddrFrame.Offset)
+ {
+ fprintf (stderr, "Stack frame out of sequence...\n");
+ break;
+ }
+ lastBp = stackFrame.AddrFrame.Offset;
+
+ max_frames--;
+ if (max_frames ==0)
+ {
+ fprintf (stderr, "\n ... (maximum recursion depth reached.)\n");
+ }
+ }
+ fprintf (stderr, "\n");
+ fflush(stderr);
+}