summaryrefslogtreecommitdiff
path: root/rts/win32/veh_excn.c
blob: 3ac008d780d8d0b7fa5a75aa69b57a5f98fc8bb1 (plain)
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/* -----------------------------------------------------------------------------
*
* (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 <assert.h>
#include <stdbool.h>
#include <dbghelp.h>
#include <shellapi.h>
#include <shlobj.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 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;
                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);
            generateStack (exception_data);
            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);
}

// 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 (stdout, "\n Attempting to reconstruct a stack trace...\n\n");
    if (!SymInitialize (GetCurrentProcess (), NULL, true))
        fprintf (stdout, "  \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 (stdout, "   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 (stdout, "Null address\n");
            break;
        }
        wchar_t buffer[1024];
        uintptr_t topSp = 0;
        fprintf (stdout, " * 0x%" PRIxPTR "\t%ls\n",
                 (uintptr_t)stackFrame.AddrFrame.Offset,
                 resolveSymbolAddr ((wchar_t*)&buffer, 1024,
                                   (SymbolAddr*)stackFrame.AddrPC.Offset,
                                   &topSp));
        if (lastBp >= stackFrame.AddrFrame.Offset)
        {
            fprintf (stdout, "Stack frame out of sequence...\n");
            break;
        }
        lastBp = stackFrame.AddrFrame.Offset;

        max_frames--;
        if (max_frames ==0)
        {
            fprintf (stdout, "\n   ... (maximum recursion depth reached.)\n");
        }
    }
    fprintf (stdout, "\n");
    fflush(stdout);
}