diff options
| -rw-r--r-- | rts/Excn.h | 34 | ||||
| -rw-r--r-- | rts/RtsMain.c | 121 | ||||
| -rw-r--r-- | rts/ghc.mk | 14 | ||||
| -rw-r--r-- | rts/win32/OSMem.c | 2 | ||||
| -rw-r--r-- | rts/win32/OSThreads.c | 2 | ||||
| -rw-r--r-- | rts/win32/Ticker.c | 1 | ||||
| -rw-r--r-- | rts/win32/seh_excn.c | 45 | ||||
| -rw-r--r-- | rts/win32/seh_excn.h | 92 | ||||
| -rw-r--r-- | rts/win32/veh_excn.c | 100 | ||||
| -rw-r--r-- | rts/win32/veh_excn.h | 73 | ||||
| -rw-r--r-- | testsuite/tests/rts/all.T | 18 | ||||
| -rw-r--r-- | testsuite/tests/rts/derefnull.stdout-x86_64-unknown-mingw32 | 1 | ||||
| -rw-r--r-- | testsuite/tests/rts/divbyzero.stdout-x86_64-unknown-mingw32 | 1 |
13 files changed, 277 insertions, 227 deletions
diff --git a/rts/Excn.h b/rts/Excn.h new file mode 100644 index 0000000000..b393234021 --- /dev/null +++ b/rts/Excn.h @@ -0,0 +1,34 @@ +/* ----------------------------------------------------------------------------- +* +* (c) The GHC Team 1998-2000 +* +* Hides indirection for EH handlers for different platforms +* +* ---------------------------------------------------------------------------*/ + +#ifndef EXCN_H +#define EXCN_H + +#include "ghcconfig.h" + +// On windows Excn provides two macros +// BEGIN_WINDOWS_VEH_HANDLER and END_WINDOWS_VEH_HANDLER, which +// will catch such exceptions in the entire process and die by +// printing a message and calling stg_exit(1). +// +// For other operating systems an empty macro is defined so +// that no #ifdefs are needed around the usage of these macros. + + +#if defined(mingw32_HOST_OS) +#include "win32/veh_excn.h" + +#define BEGIN_WINDOWS_VEH_HANDLER __register_hs_exception_handler(); +#define END_WINDOWS_VEH_HANDLER __unregister_hs_exception_handler(); +#else +#define BEGIN_WINDOWS_VEH_HANDLER +#define END_WINDOWS_VEH_HANDLER +#endif /* mingw32_HOST_OS */ + +#endif /* EXCN_H */ + diff --git a/rts/RtsMain.c b/rts/RtsMain.c index 3cf4f54cec..667c9e4ae2 100644 --- a/rts/RtsMain.c +++ b/rts/RtsMain.c @@ -8,6 +8,7 @@ #define COMPILING_RTS_MAIN +#include "Excn.h" #include "PosixSource.h" #include "Rts.h" #include "RtsAPI.h" @@ -15,105 +16,81 @@ #include "RtsUtils.h" #include "Prelude.h" #include "Task.h" -#if defined(mingw32_HOST_OS) -#include "win32/seh_excn.h" -#endif #ifdef DEBUG # include "Printer.h" /* for printing */ #endif -#ifdef HAVE_WINDOWS_H -# include <windows.h> -#endif +// Hack: we assume that we're building a batch-mode system unless +// INTERPRETER is set + +#ifndef INTERPRETER /* Hack */ -/* Annoying global vars for passing parameters to real_main() below - * This is to get around problem with Windows SEH, see hs_main(). */ -static int progargc; -static char **progargv; -static StgClosure *progmain_closure; /* This will be ZCMain_main_closure */ -static RtsConfig rtsconfig; +// The rts entry point from a compiled program using a Haskell main +// function. This gets called from a tiny main function generated by +// GHC and linked into each compiled Haskell program that uses a +// Haskell main function. +// +// We expect the caller to pass ZCMain_main_closure for +// main_closure. The reason we cannot refer to this symbol directly +// is because we're inside the rts and we do not know for sure that +// we'll be using a Haskell main function. +// +// NOTE: This function is marked as _noreturn_ in Main.h -/* Hack: we assume that we're building a batch-mode system unless - * INTERPRETER is set - */ -#ifndef INTERPRETER /* Hack */ -static void real_main(void) GNUC3_ATTRIBUTE(__noreturn__); -static void real_main(void) +int hs_main ( int argc, char *argv[], // program args + StgClosure *main_closure, // closure for Main.main + RtsConfig rts_config) // RTS configuration + { + BEGIN_WINDOWS_VEH_HANDLER + int exit_status; SchedulerStatus status; - hs_init_ghc(&progargc, &progargv, rtsconfig); - - /* 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; - - the same for GranSim (we have only one instance of this code) + hs_init_ghc(&argc, &argv, rts_config); - in a parallel setup, where we have many instances of this code - running on different PEs, we should do this only for the main PE - (IAmMainThread is set in startupHaskell) - */ + // 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; + // + // the same for GranSim (we have only one instance of this code) + // + // in a parallel setup, where we have many instances of this code + // running on different PEs, we should do this only for the main PE + // (IAmMainThread is set in startupHaskell) - /* ToDo: want to start with a larger stack size */ + // ToDo: want to start with a larger stack size { Capability *cap = rts_lock(); - rts_evalLazyIO(&cap,progmain_closure, NULL); + rts_evalLazyIO(&cap, main_closure, NULL); status = rts_getSchedStatus(cap); rts_unlock(cap); } - /* check the status of the entire Haskell computation */ + // check the status of the entire Haskell computation switch (status) { case Killed: - errorBelch("main thread exited (uncaught exception)"); - exit_status = EXIT_KILLED; - break; + errorBelch("main thread exited (uncaught exception)"); + exit_status = EXIT_KILLED; + break; case Interrupted: - errorBelch("interrupted"); - exit_status = EXIT_INTERRUPTED; - break; + errorBelch("interrupted"); + exit_status = EXIT_INTERRUPTED; + break; case HeapExhausted: - exit_status = EXIT_HEAPOVERFLOW; - break; + exit_status = EXIT_HEAPOVERFLOW; + break; case Success: - exit_status = EXIT_SUCCESS; - break; + exit_status = EXIT_SUCCESS; + break; default: - barf("main thread completed with invalid status"); + barf("main thread completed with invalid status"); } - shutdownHaskellAndExit(exit_status, 0 /* !fastExit */); -} -/* The rts entry point from a compiled program using a Haskell main - * function. This gets called from a tiny main function generated by - * GHC and linked into each compiled Haskell program that uses a - * Haskell main function. - * - * We expect the caller to pass ZCMain_main_closure for - * main_closure. The reason we cannot refer to this symbol directly - * is because we're inside the rts and we do not know for sure that - * we'll be using a Haskell main function. - */ -int hs_main (int argc, char *argv[], // program args - StgClosure *main_closure, // closure for Main.main - RtsConfig rts_config) // RTS configuration -{ - /* We do this dance with argc and argv as otherwise the SEH exception - stuff (the BEGIN/END CATCH below) on Windows gets confused */ - progargc = argc; - progargv = argv; - progmain_closure = main_closure; - rtsconfig = rts_config; + END_WINDOWS_VEH_HANDLER -#if defined(mingw32_HOST_OS) && defined(i386_HOST_ARCH) - BEGIN_CATCH -#endif - real_main(); -#if defined(mingw32_HOST_OS) && defined(i386_HOST_ARCH) - END_CATCH -#endif + shutdownHaskellAndExit(exit_status, 0 /* !fastExit */); + // No code beyond this point. Dead code elimination will remove it } # endif /* BATCH_MODE */ diff --git a/rts/ghc.mk b/rts/ghc.mk index c5dc06e0e3..7ba4320f90 100644 --- a/rts/ghc.mk +++ b/rts/ghc.mk @@ -177,6 +177,13 @@ endif rts_dist_$1_CC_OPTS += -DRtsWay=\"rts_$1\" +# If we're compiling on windows, enforce that we only support XP+ +# Adding this here means it doesn't have to be done in individual .c files +# and also centralizes the versioning. +ifeq "$$(TargetOS_CPP)" "mingw32" +rts_dist_$1_CC_OPTS += -DWINVER=0x0501 +endif + ifneq "$$(UseSystemLibFFI)" "YES" rts_dist_FFI_SO = rts/dist/build/lib$$(LIBFFI_NAME)$$(soext) else @@ -313,13 +320,6 @@ endif #----------------------------------------------------------------------------- # Flags for compiling specific files - -# If RtsMain.c is built with optimisation then the SEH exception stuff on -# Windows gets confused. -# This has to be in HC rather than CC opts, as otherwise there's a -# -optc-O2 that comes after it. -rts/RtsMain_HC_OPTS += -optc-O0 - rts/RtsMessages_CC_OPTS += -DProjectVersion=\"$(ProjectVersion)\" rts/RtsUtils_CC_OPTS += -DProjectVersion=\"$(ProjectVersion)\" rts/Trace_CC_OPTS += -DProjectVersion=\"$(ProjectVersion)\" diff --git a/rts/win32/OSMem.c b/rts/win32/OSMem.c index d9700727a5..afa5113638 100644 --- a/rts/win32/OSMem.c +++ b/rts/win32/OSMem.c @@ -6,8 +6,6 @@ * * ---------------------------------------------------------------------------*/ -#define _WIN32_WINNT 0x0501 - #include "Rts.h" #include "sm/OSMem.h" #include "RtsUtils.h" diff --git a/rts/win32/OSThreads.c b/rts/win32/OSThreads.c index c3d3af64d2..0c256127e5 100644 --- a/rts/win32/OSThreads.c +++ b/rts/win32/OSThreads.c @@ -7,8 +7,6 @@ * * --------------------------------------------------------------------------*/ -#define _WIN32_WINNT 0x0501 - #include "Rts.h" #include <windows.h> #if defined(THREADED_RTS) diff --git a/rts/win32/Ticker.c b/rts/win32/Ticker.c index 89902e568d..dd04d84118 100644 --- a/rts/win32/Ticker.c +++ b/rts/win32/Ticker.c @@ -2,7 +2,6 @@ * RTS periodic timers. * */ -#define _WIN32_WINNT 0x0501 #include "Rts.h" #include "Ticker.h" diff --git a/rts/win32/seh_excn.c b/rts/win32/seh_excn.c deleted file mode 100644 index da5f64d812..0000000000 --- a/rts/win32/seh_excn.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "ghcconfig.h" -#include "seh_excn.h" - -/* - * Exception / signal handlers. - */ -#if defined(mingw32_HOST_OS) -#if defined(i386_HOST_ARCH) -jmp_buf seh_unwind_to; -unsigned long seh_excn_code; /* variable used to communicate what kind of exception we've caught;nice. */ - -EXCEPTION_DISPOSITION -catchDivZero(struct _EXCEPTION_RECORD* rec, - void* arg1 __attribute__((unused)), - struct _CONTEXT* ctxt __attribute__((unused)), - void* arg2 __attribute__((unused))) -{ - if ((rec->ExceptionFlags & EH_UNWINDING) != 0) { - // When the system unwinds the SEH stack after having handled an excn, - // return immediately. - return ExceptionContinueSearch; - } - switch (rec->ExceptionCode) { - case EXCEPTION_FLT_DIVIDE_BY_ZERO: - case EXCEPTION_INT_DIVIDE_BY_ZERO: - seh_excn_code = 0; - longjmp(seh_unwind_to, rec->ExceptionCode); - return ExceptionContinueExecution; - case EXCEPTION_STACK_OVERFLOW: - seh_excn_code = 1; - longjmp(seh_unwind_to, rec->ExceptionCode); - return ExceptionContinueExecution; - case EXCEPTION_ACCESS_VIOLATION: - seh_excn_code = 2; - longjmp(seh_unwind_to, rec->ExceptionCode); - return ExceptionContinueExecution; - longjmp(seh_unwind_to, rec->ExceptionCode); - return ExceptionContinueExecution; - default: ; - } - return ExceptionContinueSearch; -} -#endif -#endif - diff --git a/rts/win32/seh_excn.h b/rts/win32/seh_excn.h deleted file mode 100644 index 8829e840b7..0000000000 --- a/rts/win32/seh_excn.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef WIN32_SEH_EXCN_H -#define WIN32_SEH_EXCN_H - -#include <stdio.h> -#include <stdlib.h> - -#if defined(__MINGW32__) -/* Stuff needed to install and use SEH exception handlers */ -#include <excpt.h> -#include <setjmp.h> -#include <windows.h> -#elif defined(_MSC_VER) -#include <windows.h> -#else -#include <signal.h> -#endif - -/* Exception handling. - * - * On Win32, the default action for things like division by zero and - * segfaults is to pop up an annoying little dialog box. - * - * This is a pain when we are SSHed into a Windows machine, or when we - * want to debug a problem with gdb. - * - * seh_excn provides two macros, BEGIN_CATCH and END_CATCH, which - * will catch such exceptions in the code they bracket and die by - * printing a message and calling stg_exit(1). - */ -#define ON_DIV_ZERO fprintf(stdout,"divide by zero\n"); fflush(stdout);stg_exit(1) -#define ON_STACK_OVERFLOW fprintf(stdout,"C stack overflow in generated code\n"); fflush(stdout); stg_exit(1) -#define ON_SIGSEGV fprintf(stdout,"Segmentation fault/access violation in generated code\n"); fflush(stdout); stg_exit(1) - -#if defined(__MINGW32__) -extern jmp_buf seh_unwind_to; -extern unsigned long seh_excn_code; -/* - * install an exception handler 'exHandler' which longjmp()s (via 'jumpBuf') - * to the code 'onExnCaught' when successfully catching an exception. - * - * Macro based on Andrew Begel's SEH support code posted to the mingw-users - * mailing list. - */ -#define TRY_BEGIN(jumpBuf, exHandler, onExcnCaught) \ - do { \ - int signal; \ - if ((signal = setjmp(jumpBuf)) != 0) { \ - onExcnCaught; \ - } else { \ - __try1(exHandler); \ - } \ - } while (0); - -#define TRY_END() __except1 - -extern -EXCEPTION_DISPOSITION -catchDivZero(struct _EXCEPTION_RECORD*, - void*, - struct _CONTEXT*, - void*); - -#define ON_EXCN \ - if (seh_excn_code == 1) { \ - ON_STACK_OVERFLOW; \ - } else if ( seh_excn_code == 2 ) { \ - ON_SIGSEGV; \ - } else { \ - ON_DIV_ZERO; \ - } - -#define BEGIN_CATCH TRY_BEGIN(seh_unwind_to, catchDivZero, ON_EXCN) -#define END_CATCH TRY_END() -#elif defined(_MSC_VER) -#define BEGIN_CATCH __try { -#define END_CATCH } __except ( ( ((GetExceptionCode() == EXCEPTION_FLT_DIVIDE_BY_ZERO) || (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) || (GetExceptionCode() == EXCEPTION_STACK_OVERFLOW) || (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) ) { \ - switch ( (GetExceptionCode()) ) { \ - case EXCEPTION_FLT_DIVIDE_BY_ZERO: \ - case EXCEPTION_INT_DIVIDE_BY_ZERO: - ON_DIV_ZERO; break; \ - case EXCEPTION_STACK_OVERFLOW: \ - ON_STACK_OVERFLOW; break; \ - case EXCEPTION_ACCESS_VIOLATION: \ - ON_SIGSEGV; break; \ - } \ - } -#else -#error Cannot determine what sort of Windows system this is -#endif - -#endif /* WIN32_SEH_EXCN_H */ - diff --git a/rts/win32/veh_excn.c b/rts/win32/veh_excn.c new file mode 100644 index 0000000000..a24354e9d6 --- /dev/null +++ b/rts/win32/veh_excn.c @@ -0,0 +1,100 @@ +/* ----------------------------------------------------------------------------- +* +* (c) The GHC Team 1998-2000 +* +* Error Handling implementations for windows +* +* ---------------------------------------------------------------------------*/ + +#include "Rts.h" +#include "ghcconfig.h" +#include "veh_excn.h" +#include <assert.h> + +///////////////////////////////// +// Exception / signal handlers. +///////////////////////////////// + +// 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 <excpt.h>, but it's been removed from MinGW distributions +#ifndef EH_UNWINDING +#define EH_UNWINDING 0x02 +#endif /* EH_UNWINDING */ + +// Registered exception handler +PVOID __hs_handle = NULL; + +long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data) +{ + long action = EXCEPTION_CONTINUE_SEARCH; + + // When the system unwinds the VEH stack after having handled an excn, + // return immediately. + if ((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(stdout, "divide by zero\n"); + action = EXCEPTION_CONTINUE_EXECUTION; + break; + case EXCEPTION_STACK_OVERFLOW: + fprintf(stdout, "C stack overflow in generated code\n"); + action = EXCEPTION_CONTINUE_EXECUTION; + break; + case EXCEPTION_ACCESS_VIOLATION: + fprintf(stdout, "Segmentation fault/access violation in generated code\n"); + action = EXCEPTION_CONTINUE_EXECUTION; + 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(stdout); + stg_exit(1); + } + } + + return action; +} + +void __register_hs_exception_handler( void ) +{ + // Allow the VEH handler to be registered only once. + if (NULL == __hs_handle) + { + __hs_handle = AddVectoredExceptionHandler(CALL_FIRST, __hs_exception_handler); + // should the handler not be registered this will return a null. + assert(__hs_handle); + } + 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 (__hs_handle != NULL) + { + // Should the return value be checked? we're terminating anyway. + RemoveVectoredExceptionHandler(__hs_handle); + __hs_handle = NULL; + } + else + { + errorBelch("__unregister_hs_exception_handler() called without having called __register_hs_exception_handler() first."); + } +} + diff --git a/rts/win32/veh_excn.h b/rts/win32/veh_excn.h new file mode 100644 index 0000000000..4223a2ae2d --- /dev/null +++ b/rts/win32/veh_excn.h @@ -0,0 +1,73 @@ +/* ----------------------------------------------------------------------------- +* +* (c) The GHC Team 1998-2000 +* +* Header for windows Error Handling implementations +* +* ---------------------------------------------------------------------------*/ + +#ifndef WIN32_VEH_EXCN_H +#define WIN32_VEH_EXCN_H + +#include <stdio.h> +#include <stdlib.h> +#include <Rts.h> + +// Stuff needed to install and use VEH exception handlers +#include <excpt.h> +#include <windows.h> + +// Exception handling. +// +// On Windows, the default action for things like division by zero and +// segfaults is to pop up a Dr. Watson error reporting dialog if the exception +// is unhandled by the user code. +// +// This is a pain when we are SSHed into a Windows machine, or when we +// want to debug a problem with gdb (gdb will get a first and second chance to +// handle the exception, but if it doesn't the pop-up will show). +// +// +// Previously this code was handled using SEH (Structured Exception Handlers) +// however each compiler and platform have different ways of dealing with SEH. +// +// MSVC compilers have the keywords __try, __catch and __except to have the +// compiler generate the appropriate SEH handler code for you. +// +// MinGW compilers have no such keywords and require you to manually set the +// SEH Handlers, however because SEH is implemented differently in x86 and x64 +// the methods to use them in GCC differs. +// +// x86: SEH is based on the stack, the SEH handlers are available at FS[0]. +// On startup one would only need to add a new handler there. This has +// a number of issues such as hard to share handlers and it can be exploited. +// +// x64: In order to fix the issues with the way SEH worked in x86, on x64 SEH handlers +// are statically compiled and added to the .pdata section by the compiler. +// Instead of being thread global they can now be Image global since you have to +// specify the RVA of the region of code that the handlers govern. +// +// You can on x64 Dynamically allocate SEH handlers, but it seems that (based on +// experimentation and it's very under-documented) that the dynamic calls cannot override +// static SEH handlers in the .pdata section. +// +// Because of this and because GHC no longer needs to support < windows XP, the better +// alternative for handling errors would be using the in XP introduced VEH. +// +// The bonus is because VEH (Vectored Exception Handler) are a runtime construct the API +// is the same for both x86 and x64 (note that the Context object does contain CPU specific +// structures) and the calls are the same cross compilers. Which means this file can be +// simplified quite a bit. +// Using VEH also means we don't have to worry about the dynamic code generated by GHCi. + +// Prototype of the VEH callback function. +// 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 ); + +// prototypes to the functions doing the registration and unregistration of the VEH handlers +void __register_hs_exception_handler( void ); +void __unregister_hs_exception_handler( void ); + +#endif /* WIN32_VEH_EXCN_H */ + diff --git a/testsuite/tests/rts/all.T b/testsuite/tests/rts/all.T index 88c354f2f5..86b1bcf162 100644 --- a/testsuite/tests/rts/all.T +++ b/testsuite/tests/rts/all.T @@ -7,8 +7,7 @@ test('testblockalloc', # only GHCi triggers the bug, but we run the test all ways for completeness. test('bug1010', normal, compile_and_run, ['+RTS -c -RTS']) test('derefnull', - [when(opsys('mingw32'), expect_broken(6079)), - # LLVM Optimiser considers dereference of a null pointer + [# LLVM Optimiser considers dereference of a null pointer # undefined and marks the code as unreachable which means # that later optimisations remove it altogether. omit_ways(['optllvm']), @@ -21,17 +20,24 @@ test('derefnull', # SIGBUS on OX X (PPC and x86 only; amd64 gives SEGV) when(platform('i386-apple-darwin'), exit_code(138)), when(platform('powerpc-apple-darwin'), exit_code(138)), - when(opsys('mingw32'), exit_code(1))], + when(opsys('mingw32'), exit_code(1)), + # since these test are supposed to crash the + # profile report will be empty always. + # so disable the check for profiling + when(opsys('mingw32'), omit_ways(prof_ways))], compile_and_run, ['']) test('divbyzero', - [when(opsys('mingw32'), expect_broken(6079)), - # SIGFPE on Linux + [# SIGFPE on Linux exit_code(136), # Apparently the output can be different on different # Linux setups, so just ignore it. As long as we get # the right exit code we're OK. when(opsys('linux'), ignore_output), - when(opsys('mingw32'), exit_code(1))], + when(opsys('mingw32'), exit_code(1)), + # since these test are supposed to crash the + # profile report will be empty always. + # so disable the check for profiling + when(opsys('mingw32'), omit_ways(prof_ways))], compile_and_run, ['']) test('outofmem', when(opsys('darwin'), skip), diff --git a/testsuite/tests/rts/derefnull.stdout-x86_64-unknown-mingw32 b/testsuite/tests/rts/derefnull.stdout-x86_64-unknown-mingw32 new file mode 100644 index 0000000000..cbabc63f88 --- /dev/null +++ b/testsuite/tests/rts/derefnull.stdout-x86_64-unknown-mingw32 @@ -0,0 +1 @@ +Segmentation fault/access violation in generated code
diff --git a/testsuite/tests/rts/divbyzero.stdout-x86_64-unknown-mingw32 b/testsuite/tests/rts/divbyzero.stdout-x86_64-unknown-mingw32 new file mode 100644 index 0000000000..275d5d4601 --- /dev/null +++ b/testsuite/tests/rts/divbyzero.stdout-x86_64-unknown-mingw32 @@ -0,0 +1 @@ +divide by zero
|
