Add ability to produce crash dumps on Windows
[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 #define UNICODE 1
9 #include "Rts.h"
10 #include "ghcconfig.h"
11 #include "veh_excn.h"
12 #include <assert.h>
13 #include <stdbool.h>
14 #include <wchar.h>
15 #include <windows.h>
16 #include <stdio.h>
17 #include <excpt.h>
18 #include <inttypes.h>
19 #include <Dbghelp.h>
20
21 /////////////////////////////////
22 // Exception / signal handlers.
23 /////////////////////////////////
24
25 /*
26 SEH (Structured Error Handler) on Windows is quite tricky. On x86 SEHs are
27 stack based and are stored in FS[0] of each thread. Which means every time we
28 spawn an OS thread we'd have to set up the error handling. However on x64 it's
29 table based and memory region based. e.g. you register a handler for a
30 particular memory range. This means that we'd have to register handlers for
31 each block of code we load externally or generate internally ourselves.
32
33 In Windows XP VEH (Vectored Exception Handler) and VCH (Vectored Continue
34 Handler) were added. Both of these are global/process wide handlers, the
35 former handling all exceptions and the latter handling only exceptions which
36 we're trying to recover from, e.g. a handler returned
37 EXCEPTION_CONTINUE_EXECUTION.
38
39 And lastly you have top level exception filters, which are also process global
40 but the problem here is that you can only have one, and setting this removes
41 the previous ones. The chain of exception handling looks like
42
43 [ Vectored Exception Handler ]
44 |
45 [ Structured Exception Handler ]
46 |
47 [ Exception Filters ]
48 |
49 [ Vectored Continue Handler ]
50
51 To make things more tricky, the exception handlers handle both hardware and
52 software exceptions Which means previously when we registered VEH handlers
53 we would also trap software exceptions. Which means when haskell code was
54 loaded in a C++ or C# context we would swallow exceptions and terminate in
55 contexes that normally the runtime should be able to continue on, e.g. you
56 could be handling the segfault in your C++ code, or the div by 0.
57
58 We could not handle these exceptions, but GHCi would just die a horrible death
59 then on normal Haskell only code when such an exception occurs.
60
61 So instead, we'll move to Continue handler, to run as late as possible, and
62 also register a filter which calls any existing filter, and then runs the
63 continue handlers, we then also only run as the last continue handler so we
64 don't supercede any other VCH handlers.
65
66 Lastly we'll also provide a way for users to disable the exception handling
67 entirely so even if the new approach doesn't solve the issue they can work
68 around it. After all, I don't expect any interpreted code if you are running
69 a haskell dll.
70
71 For a detailed analysis see
72 https://reverseengineering.stackexchange.com/questions/14992/what-are-the-vectored-continue-handlers
73 and https://www.gamekiller.net/threads/vectored-exception-handler.3237343/
74 */
75
76 // Define some values for the ordering of VEH Handlers:
77 // - CALL_FIRST means call this exception handler first
78 // - CALL_LAST means call this exception handler last
79 #define CALL_FIRST 1
80 #define CALL_LAST 0
81
82 // this should be in <excpt.h>, but it's been removed from MinGW distributions
83 #if !defined(EH_UNWINDING)
84 #define EH_UNWINDING 0x02
85 #endif /* EH_UNWINDING */
86
87 // Registered exception handler
88 PVOID __hs_handle = NULL;
89 LPTOP_LEVEL_EXCEPTION_FILTER oldTopFilter = NULL;
90 bool crash_dump = false;
91 bool filter_called = false;
92
93 long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data)
94 {
95 if (!crash_dump && filter_called)
96 return EXCEPTION_CONTINUE_EXECUTION;
97
98 long action = EXCEPTION_CONTINUE_SEARCH;
99 ULONG_PTR what;
100 fprintf (stdout, "\n");
101
102 // When the system unwinds the VEH stack after having handled an excn,
103 // return immediately.
104 if ((exception_data->ExceptionRecord->ExceptionFlags & EH_UNWINDING) == 0)
105 {
106 // Error handling cases covered by this implementation.
107 switch (exception_data->ExceptionRecord->ExceptionCode) {
108 case EXCEPTION_FLT_DIVIDE_BY_ZERO:
109 case EXCEPTION_INT_DIVIDE_BY_ZERO:
110 fprintf(stdout, "divide by zero\n");
111 action = EXCEPTION_CONTINUE_EXECUTION;
112 break;
113 case EXCEPTION_STACK_OVERFLOW:
114 fprintf(stdout, "C stack overflow in generated code\n");
115 action = EXCEPTION_CONTINUE_EXECUTION;
116 break;
117 case EXCEPTION_ACCESS_VIOLATION:
118 what = exception_data->ExceptionRecord->ExceptionInformation[0];
119 fprintf(stdout, "Access violation in generated code"
120 " when %s %p\n"
121 , what == 0 ? "reading" : what == 1 ? "writing" : what == 8 ? "executing data at" : "?"
122 , (void*) exception_data->ExceptionRecord->ExceptionInformation[1]
123 );
124 action = EXCEPTION_CONTINUE_EXECUTION;
125 break;
126 default:;
127 }
128
129 // If an error has occurred and we've decided to continue execution
130 // then we've done so to prevent something else from handling the error.
131 // But the correct action is still to exit as fast as possible.
132 if (EXCEPTION_CONTINUE_EXECUTION == action)
133 {
134 fflush(stdout);
135 generateDump (exception_data);
136 stg_exit(EXIT_FAILURE);
137 }
138 }
139
140 return action;
141 }
142
143 long WINAPI __hs_exception_filter(struct _EXCEPTION_POINTERS *exception_data)
144 {
145 filter_called = true;
146 long result = EXCEPTION_CONTINUE_EXECUTION;
147 if (oldTopFilter)
148 {
149 result = (*oldTopFilter)(exception_data);
150 if (EXCEPTION_CONTINUE_SEARCH == result)
151 result = EXCEPTION_CONTINUE_EXECUTION;
152 return result;
153 }
154
155 crash_dump = true;
156
157 return result;
158 }
159
160 void __register_hs_exception_handler( void )
161 {
162 if (!RtsFlags.MiscFlags.install_seh_handlers)
163 return;
164
165 // Allow the VCH handler to be registered only once.
166 if (NULL == __hs_handle)
167 {
168 // Be the last one to run, We can then be sure we didn't interfere with
169 // anything else.
170 __hs_handle = AddVectoredContinueHandler(CALL_LAST,
171 __hs_exception_handler);
172 // should the handler not be registered this will return a null.
173 assert(__hs_handle);
174
175 // Register for an exception filter to ensure the continue handler gets
176 // hit if no one handled the exception.
177 oldTopFilter = SetUnhandledExceptionFilter (__hs_exception_filter);
178 }
179 else
180 {
181 errorBelch("There is no need to call __register_hs_exception_handler()"
182 " twice, VEH handlers are global per process.");
183 }
184 }
185
186 void __unregister_hs_exception_handler( void )
187 {
188 if (!RtsFlags.MiscFlags.install_seh_handlers)
189 return;
190
191 if (__hs_handle != NULL)
192 {
193 // Should the return value be checked? we're terminating anyway.
194 RemoveVectoredContinueHandler(__hs_handle);
195 __hs_handle = NULL;
196 }
197 else
198 {
199 errorBelch("__unregister_hs_exception_handler() called without having"
200 "called __register_hs_exception_handler() first.");
201 }
202 }
203
204 // Generate a crash dump, however in order for these to generate undecorated
205 // names we really need to be able to generate PDB files.
206 void generateDump (EXCEPTION_POINTERS* pExceptionPointers)
207 {
208 if (!RtsFlags.MiscFlags.generate_dump_file)
209 return;
210
211 WCHAR szPath[MAX_PATH];
212 WCHAR szFileName[MAX_PATH];
213 WCHAR const *const szAppName = L"ghc";
214 WCHAR const *const szVersion = L"";
215 DWORD dwBufferSize = MAX_PATH;
216 HANDLE hDumpFile;
217 SYSTEMTIME stLocalTime;
218 MINIDUMP_EXCEPTION_INFORMATION ExpParam;
219
220 GetLocalTime (&stLocalTime);
221 GetTempPathW (dwBufferSize, szPath);
222
223 swprintf (szFileName, MAX_PATH,
224 L"%ls%ls%ls-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
225 szPath, szAppName, szVersion,
226 stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
227 stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
228 GetCurrentProcessId(), GetCurrentThreadId());
229 hDumpFile = CreateFileW (szFileName, GENERIC_READ|GENERIC_WRITE,
230 FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
231
232 ExpParam.ThreadId = GetCurrentThreadId();
233 ExpParam.ExceptionPointers = pExceptionPointers;
234 ExpParam.ClientPointers = TRUE;
235
236 MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
237 hDumpFile, MiniDumpNormal | MiniDumpWithDataSegs |
238 MiniDumpWithThreadInfo | MiniDumpWithCodeSegs,
239 &ExpParam, NULL, NULL);
240
241 fprintf (stdout, "Crash dump created. Dump written to:\n\t%ls", szFileName);
242 }