1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
|
/* -----------------------------------------------------------------------------
*
* (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 <assert.h>
#include <stdbool.h>
#include <wchar.h>
#include <windows.h>
#include <stdio.h>
#include <excpt.h>
#include <inttypes.h>
#include <Dbghelp.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
#define CALL_FIRST 1
#define CALL_LAST 0
// this should be in <excpt.h>, 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;
bool crash_dump = false;
bool filter_called = false;
long WINAPI __hs_exception_handler(struct _EXCEPTION_POINTERS *exception_data)
{
if (!crash_dump && filter_called)
return EXCEPTION_CONTINUE_EXECUTION;
long action = EXCEPTION_CONTINUE_SEARCH;
ULONG_PTR what;
fprintf (stdout, "\n");
// 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:
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]
);
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);
generateDump (exception_data);
stg_exit(EXIT_FAILURE);
}
}
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 )
{
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.
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.");
}
}
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 (stdout, "Crash dump created. Dump written to:\n\t%ls", szFileName);
}
|