/* ----------------------------------------------------------------------------- * * (c) The GHC Team 1998-2000 * * Error Handling implementations for windows * * ---------------------------------------------------------------------------*/ #define UNICODE 1 #include "Rts.h" #include "ghcconfig.h" #include "veh_excn.h" #include "LinkerInternals.h" #include #include #include #include #include #include #include #include #include #include #include ///////////////////////////////// // 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 #define CALL_FIRST 1 #define CALL_LAST 0 // this should be in , but it's been removed from MinGW distributions #if !defined(EH_UNWINDING) #define EH_UNWINDING 0x02 #endif /* EH_UNWINDING */ // Registered exception handler PVOID __hs_handle = NULL; LPTOP_LEVEL_EXCEPTION_FILTER oldTopFilter = NULL; long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data) { 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. if (exception_data && exception_data->ExceptionRecord && (exception_data->ExceptionRecord->ExceptionFlags & EH_UNWINDING) ==0) { // Error handling cases covered by this implementation. switch (exception_data->ExceptionRecord->ExceptionCode) { case EXCEPTION_FLT_DIVIDE_BY_ZERO: case EXCEPTION_INT_DIVIDE_BY_ZERO: fprintf(stderr, "divide by zero\n"); action = EXCEPTION_CONTINUE_EXECUTION; exit_code = SIGFPE; break; case EXCEPTION_STACK_OVERFLOW: fprintf(stderr, "C stack overflow in generated code\n"); action = EXCEPTION_CONTINUE_EXECUTION; break; case EXCEPTION_ACCESS_VIOLATION: { if (exception_data->ExceptionRecord->NumberParameters < 2) { fprintf(stderr, "Access violation in generated code. " "Empty exception record."); } else { what = exception_data->ExceptionRecord ->ExceptionInformation[0]; 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:; } // If an error has occurred and we've decided to continue execution // then we've done so to prevent something else from handling the error. // But the correct action is still to exit as fast as possible. if (EXCEPTION_CONTINUE_EXECUTION == action) { fflush(stderr); hs_restoreConsoleCP (); generateStack (exception_data); generateDump (exception_data); stg_exit(exit_code); } } return action; } /* Registered top level exception filter. We're not very interested in handling the error here, that's why we have __hs_exception_handler, but we do want to register the fact that the filter was called. This allows us to prevent continuing to run when the exception was completely unhandled. EXCEPTION_CONTINUE_EXECUTION is returned so that the OS gives the VEH handlers a chance to run. */ 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; } void __register_hs_exception_handler( void ) { if (!RtsFlags.MiscFlags.install_seh_handlers) return; // Allow the VCH handler to be registered only once. if (NULL == __hs_handle) { // 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. CHECK(__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."); } } 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. RemoveVectoredContinueHandler(__hs_handle); __hs_handle = NULL; } else { 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); }