e45ea2b49c04de6231f4fb60932d644f600d9ba6
[ghc.git] / rts / win32 / veh_excn.c
1 /* -----------------------------------------------------------------------------
2 *
3 * (c) The GHC Team 1998-2000
4 *
5 * Error Handling implementations for windows
6 *
7 * ---------------------------------------------------------------------------*/
8
9 #include "Rts.h"
10 #include "ghcconfig.h"
11 #include "veh_excn.h"
12 #include <assert.h>
13
14 /////////////////////////////////
15 // Exception / signal handlers.
16 /////////////////////////////////
17
18 /*
19 SEH (Structured Error Handler) on Windows is quite tricky. On x86 SEHs are
20 stack based and are stored in FS[0] of each thread. Which means every time we
21 spawn an OS thread we'd have to set up the error handling. However on x64 it's
22 table based and memory region based. e.g. you register a handler for a
23 particular memory range. This means that we'd have to register handlers for
24 each block of code we load externally or generate internally ourselves.
25
26 In Windows XP VEH (Vectored Exception Handler) and VCH (Vectored Continue
27 Handler) were added. Both of these are global/process wide handlers, the
28 former handling all exceptions and the latter handling only exceptions which
29 we're trying to recover from, e.g. a handler returned
30 EXCEPTION_CONTINUE_EXECUTION.
31
32 And lastly you have top level exception filters, which are also process global
33 but the problem here is that you can only have one, and setting this removes
34 the previous ones. The chain of exception handling looks like
35
36 [ Vectored Exception Handler ]
37 |
38 [ Structured Exception Handler ]
39 |
40 [ Exception Filters ]
41 |
42 [ Vectored Continue Handler ]
43
44 To make things more tricky, the exception handlers handle both hardware and
45 software exceptions Which means previously when we registered VEH handlers
46 we would also trap software exceptions. Which means when haskell code was
47 loaded in a C++ or C# context we would swallow exceptions and terminate in
48 contexes that normally the runtime should be able to continue on, e.g. you
49 could be handling the segfault in your C++ code, or the div by 0.
50
51 We could not handle these exceptions, but GHCi would just die a horrible death
52 then on normal Haskell only code when such an exception occurs.
53
54 So instead, we'll move to Continue handler, to run as late as possible, and
55 also register a filter which calls any existing filter, and then runs the
56 continue handlers, we then also only run as the last continue handler so we
57 don't supercede any other VCH handlers.
58
59 Lastly we'll also provide a way for users to disable the exception handling
60 entirely so even if the new approach doesn't solve the issue they can work
61 around it. After all, I don't expect any interpreted code if you are running
62 a haskell dll.
63
64 For a detailed analysis see
65 https://reverseengineering.stackexchange.com/questions/14992/what-are-the-vectored-continue-handlers
66 and https://www.gamekiller.net/threads/vectored-exception-handler.3237343/
67 */
68
69 // Define some values for the ordering of VEH Handlers:
70 // - CALL_FIRST means call this exception handler first
71 // - CALL_LAST means call this exception handler last
72 #define CALL_FIRST 1
73 #define CALL_LAST 0
74
75 // this should be in <excpt.h>, but it's been removed from MinGW distributions
76 #if !defined(EH_UNWINDING)
77 #define EH_UNWINDING 0x02
78 #endif /* EH_UNWINDING */
79
80 // Registered exception handler
81 PVOID __hs_handle = NULL;
82 LPTOP_LEVEL_EXCEPTION_FILTER oldTopFilter = NULL;
83
84 long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data)
85 {
86 long action = EXCEPTION_CONTINUE_SEARCH;
87 ULONG_PTR what;
88
89 // When the system unwinds the VEH stack after having handled an excn,
90 // return immediately.
91 if ((exception_data->ExceptionRecord->ExceptionFlags & EH_UNWINDING) == 0)
92 {
93 // Error handling cases covered by this implementation.
94 switch (exception_data->ExceptionRecord->ExceptionCode) {
95 case EXCEPTION_FLT_DIVIDE_BY_ZERO:
96 case EXCEPTION_INT_DIVIDE_BY_ZERO:
97 fprintf(stdout, "divide by zero\n");
98 action = EXCEPTION_CONTINUE_EXECUTION;
99 break;
100 case EXCEPTION_STACK_OVERFLOW:
101 fprintf(stdout, "C stack overflow in generated code\n");
102 action = EXCEPTION_CONTINUE_EXECUTION;
103 break;
104 case EXCEPTION_ACCESS_VIOLATION:
105 what = exception_data->ExceptionRecord->ExceptionInformation[0];
106 fprintf(stdout, "Access violation in generated code"
107 " when %s %p\n"
108 , what == 0 ? "reading" : what == 1 ? "writing" : what == 8 ? "executing data at" : "?"
109 , (void*) exception_data->ExceptionRecord->ExceptionInformation[1]
110 );
111 action = EXCEPTION_CONTINUE_EXECUTION;
112 break;
113 default:;
114 }
115
116 // If an error has occurred and we've decided to continue execution
117 // then we've done so to prevent something else from handling the error.
118 // But the correct action is still to exit as fast as possible.
119 if (EXCEPTION_CONTINUE_EXECUTION == action)
120 {
121 fflush(stdout);
122 stg_exit(EXIT_FAILURE);
123 }
124 }
125
126 return action;
127 }
128
129 long WINAPI __hs_exception_filter(struct _EXCEPTION_POINTERS *exception_data)
130 {
131 long result = EXCEPTION_CONTINUE_EXECUTION;
132 if (oldTopFilter)
133 {
134 result = (*oldTopFilter)(exception_data);
135 if (EXCEPTION_CONTINUE_SEARCH == result)
136 result = EXCEPTION_CONTINUE_EXECUTION;
137 return result;
138 }
139
140 return result;
141 }
142
143 void __register_hs_exception_handler( void )
144 {
145 if (!RtsFlags.MiscFlags.install_seh_handlers)
146 return;
147
148 // Allow the VCH handler to be registered only once.
149 if (NULL == __hs_handle)
150 {
151 // Be the last one to run, We can then be sure we didn't interfere with
152 // anything else.
153 __hs_handle = AddVectoredContinueHandler(CALL_LAST,
154 __hs_exception_handler);
155 // should the handler not be registered this will return a null.
156 assert(__hs_handle);
157
158 // Register for an exception filter to ensure the continue handler gets
159 // hit if no one handled the exception.
160 oldTopFilter = SetUnhandledExceptionFilter (__hs_exception_filter);
161 }
162 else
163 {
164 errorBelch("There is no need to call __register_hs_exception_handler()"
165 " twice, VEH handlers are global per process.");
166 }
167 }
168
169 void __unregister_hs_exception_handler( void )
170 {
171 if (!RtsFlags.MiscFlags.install_seh_handlers)
172 return;
173
174 if (__hs_handle != NULL)
175 {
176 // Should the return value be checked? we're terminating anyway.
177 RemoveVectoredContinueHandler(__hs_handle);
178 __hs_handle = NULL;
179 }
180 else
181 {
182 errorBelch("__unregister_hs_exception_handler() called without having"
183 "called __register_hs_exception_handler() first.");
184 }
185 }
186