summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc.cpp
blob: 17dd3a0d87c8ccb97ee1f986b54e9ef779af94b0 (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
/* $Id: except-x86-vcc.cpp $ */
/** @file
 * IPRT - Visual C++ Compiler - x86 Exception Handler Filter.
 */

/*
 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
 * in the VirtualBox distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 *
 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include <iprt/nt/nt-and-windows.h>

#include "internal/compiler-vcc.h"
#include "except-vcc.h"


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
/**
 * Extended exception registration record used by rtVccEh4DoLocalUnwind
 * and rtVccEh4DoLocalUnwindHandler.
 */
typedef struct EH4_LOCAL_UNWIND_XCPT_REG
{
    /** Security cookie. */
    uintptr_t                       uEHCookieFront;
    /** The actual registration record.   */
    EXCEPTION_REGISTRATION_RECORD   XcptRegRec;
    /** @name rtVccEh4DoLocalUnwind parameters
     * @{ */
    PEH4_XCPT_REG_REC_T             pEh4XcptRegRec;
    uint32_t                        uTargetTryLevel;
    uint8_t const                  *pbFrame;
    /** @} */
    /** Security cookie. */
    uintptr_t                       uEHCookieBack;
} EH4_LOCAL_UNWIND_XCPT_REG;



/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
extern "C" uintptr_t __security_cookie;


/*********************************************************************************************************************************
*   Internal Functions                                                                                                           *
*********************************************************************************************************************************/
DECLASM(LONG)                   rtVccEh4DoFiltering(PFN_EH4_XCPT_FILTER_T pfnFilter, uint8_t const *pbFrame);
DECLASM(DECL_NO_RETURN(void))   rtVccEh4JumpToHandler(PFN_EH4_XCPT_HANDLER_T pfnHandler, uint8_t const *pbFrame);
DECLASM(void)                   rtVccEh4DoGlobalUnwind(PEXCEPTION_RECORD pXcptRec, PEXCEPTION_REGISTRATION_RECORD pXcptRegRec);
DECLASM(void)                   rtVccEh4DoFinally(PFN_EH4_FINALLY_T pfnFinally, bool fAbend, uint8_t const *pbFrame);
extern "C" EXCEPTION_DISPOSITION __stdcall
rtVccEh4DoLocalUnwindHandler(PEXCEPTION_RECORD pXcptRec, PVOID pvEstFrame, PCONTEXT pCpuCtx, PVOID pvDispCtx);


#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable:4733)  /* warning C4733: Inline asm assigning to 'FS:0': handler not registered as safe handler */
#endif

/**
 * Calls the __finally blocks up to @a uTargetTryLevel is reached, starting with
 * @a pEh4XcptRegRec->uTryLevel.
 *
 * @param   pEh4XcptRegRec  The EH4 exception registration record.
 * @param   uTargetTryLevel The target __try level to stop unwinding at.
 * @param   pbFrame         The frame pointer (EBP).
 */
static void rtVccEh4DoLocalUnwind(PEH4_XCPT_REG_REC_T pEh4XcptRegRec, uint32_t uTargetTryLevel, uint8_t const *pbFrame)
{
    /*
     * Manually set up exception handler.
     */
    EH4_LOCAL_UNWIND_XCPT_REG RegRec =
    {
        __security_cookie ^ (uintptr_t)&RegRec,
        {
            (EXCEPTION_REGISTRATION_RECORD *)__readfsdword(RT_UOFFSETOF(NT_TIB, ExceptionList)),
            rtVccEh4DoLocalUnwindHandler /* safeseh (.sxdata) entry emitted by except-x86-vcc-asm.asm */
        },
        pEh4XcptRegRec,
        uTargetTryLevel,
        pbFrame,
        __security_cookie ^ (uintptr_t)&RegRec
    };
    __writefsdword(RT_UOFFSETOF(NT_TIB, ExceptionList), (uintptr_t)&RegRec.XcptRegRec);

    /*
     * Do the unwinding.
     */
    uint32_t uCurTryLevel = pEh4XcptRegRec->uTryLevel;
    while (   uCurTryLevel != EH4_TOPMOST_TRY_LEVEL
           && (   uCurTryLevel > uTargetTryLevel
               || uTargetTryLevel == EH4_TOPMOST_TRY_LEVEL /* if we knew what 0xffffffff meant, this could probably be omitted */ ))
    {
        PCEH4_SCOPE_TAB_T const pScopeTable = (PCEH4_SCOPE_TAB_T)(pEh4XcptRegRec->uEncodedScopeTable ^ __security_cookie);
        PCEH4_SCOPE_TAB_REC_T const pEntry  = &pScopeTable->aScopeRecords[uCurTryLevel];

        pEh4XcptRegRec->uTryLevel = uCurTryLevel = pEntry->uEnclosingLevel;

        /* __finally scope table entries have no filter sub-function. */
        if (!pEntry->pfnFilter)
        {
            //RTAssertMsg2("rtVccEh4DoLocalUnwind: Calling %p (level %#x)\n", pEntry->pfnFinally, uCurTryLevel);
            rtVccEh4DoFinally(pEntry->pfnFinally, true /*fAbend*/, pbFrame);

            /* Read the try level again in case it changed... */
            uCurTryLevel = pEh4XcptRegRec->uTryLevel;
        }
    }

    /*
     * Deregister exception handler.
     */
    __writefsdword(RT_UOFFSETOF(NT_TIB, ExceptionList), (uintptr_t)RegRec.XcptRegRec.Next);
}

#ifdef _MSC_VER
# pragma warning(pop)
#endif

/**
 * Exception handler for rtVccEh4DoLocalUnwind.
 */
EXCEPTION_DISPOSITION __stdcall
rtVccEh4DoLocalUnwindHandler(PEXCEPTION_RECORD pXcptRec, PVOID pvEstFrame, PCONTEXT pCpuCtx, PVOID pvDispCtx)
{
    EH4_LOCAL_UNWIND_XCPT_REG *pMyRegRec = RT_FROM_MEMBER(pvEstFrame, EH4_LOCAL_UNWIND_XCPT_REG, XcptRegRec);
    __security_check_cookie(pMyRegRec->uEHCookieFront ^ (uintptr_t)pMyRegRec);
    __security_check_cookie(pMyRegRec->uEHCookieBack  ^ (uintptr_t)pMyRegRec);

    /*
     * This is a little sketchy as it isn't all that well documented by the OS
     * vendor, but if invoked while unwinding, we return ExceptionCollidedUnwind
     * and update the *ppDispCtx value to point to the colliding one.
     */
    if (pXcptRec->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))
    {
        rtVccEh4DoLocalUnwind(pMyRegRec->pEh4XcptRegRec, pMyRegRec->uTargetTryLevel, pMyRegRec->pbFrame);

        PEXCEPTION_REGISTRATION_RECORD *ppDispCtx = (PEXCEPTION_REGISTRATION_RECORD *)pvDispCtx;
        *ppDispCtx = &pMyRegRec->XcptRegRec;
        return ExceptionCollidedUnwind;
    }

    /*
     * In all other cases we do nothing special.
     */
    RT_NOREF(pCpuCtx);
    return ExceptionContinueSearch;
}


/**
 * This validates the CPU context, may terminate the application if invalid.
 */
DECLINLINE(void) rtVccValidateExceptionContextRecord(PCONTEXT pCpuCtx)
{
    if (RT_LIKELY(   !rtVccIsGuardICallChecksActive()
                  || rtVccIsPointerOnTheStack(pCpuCtx->Esp)))
    { /* likely */ }
    else
        rtVccCheckContextFailed(pCpuCtx);
}


/**
 * Helper that validates stack cookies.
 */
DECLINLINE(void) rtVccEh4ValidateCookies(PCEH4_SCOPE_TAB_T pScopeTable, uint8_t const *pbFrame)
{
    if (pScopeTable->offGSCookie != EH4_NO_GS_COOKIE)
    {
        uintptr_t uGsCookie = *(uintptr_t const *)&pbFrame[pScopeTable->offGSCookie];
        uGsCookie          ^=          (uintptr_t)&pbFrame[pScopeTable->offGSCookieXor];
        __security_check_cookie(uGsCookie);
    }

    uintptr_t uEhCookie = *(uintptr_t const *)&pbFrame[pScopeTable->offEHCookie];
    uEhCookie          ^=          (uintptr_t)&pbFrame[pScopeTable->offEHCookieXor];
    __security_check_cookie(uEhCookie);
}


/**
 * Call exception filters, handlers and unwind code for x86 code.
 *
 * This is called for windows' structured exception handling (SEH) in x86 32-bit
 * code, i.e. the __try/__except/__finally stuff in Visual C++.  The compiler
 * generate scope records for the __try/__except blocks as well as unwind
 * records for __finally and probably C++ stack object destructors.
 *
 * @returns Exception disposition.
 * @param   pXcptRec    The exception record.
 * @param   pXcptRegRec The exception registration record, taken to be the frame
 *                      address.
 * @param   pCpuCtx     The CPU context for the exception.
 * @param   pDispCtx    Dispatcher context.
 */
extern "C" __declspec(guard(suppress))
DWORD _except_handler4(PEXCEPTION_RECORD pXcptRec, PEXCEPTION_REGISTRATION_RECORD pXcptRegRec, PCONTEXT pCpuCtx, PVOID pvCtx)
{
    /*
     * The registration record (probably chained on FS:[0] like in the OS/2 days)
     * points to a larger structure specific to _except_handler4.  The structure
     * is planted right after the saved caller EBP value when establishing the
     * stack frame, so EBP = pXcptRegRec + 1;
     */
    PEH4_XCPT_REG_REC_T const pEh4XcptRegRec = RT_FROM_MEMBER(pXcptRegRec, EH4_XCPT_REG_REC_T, XcptRec);
    uint8_t * const           pbFrame        = (uint8_t *)&pEh4XcptRegRec[1];
    PCEH4_SCOPE_TAB_T const   pScopeTable    = (PCEH4_SCOPE_TAB_T)(pEh4XcptRegRec->uEncodedScopeTable ^ __security_cookie);

    /*
     * Validate the stack cookie and exception context.
     */
    rtVccEh4ValidateCookies(pScopeTable, pbFrame);
    rtVccValidateExceptionContextRecord(pCpuCtx);

    /*
     * If dispatching an exception, call the exception filter functions and jump
     * to the __except blocks if so directed.
     */
    if (IS_DISPATCHING(pXcptRec->ExceptionFlags))
    {
        uint32_t uTryLevel = pEh4XcptRegRec->uTryLevel;
        //RTAssertMsg2("_except_handler4: dispatch: uTryLevel=%#x\n", uTryLevel);
        while (uTryLevel != EH4_TOPMOST_TRY_LEVEL)
        {
            PCEH4_SCOPE_TAB_REC_T const pEntry    = &pScopeTable->aScopeRecords[uTryLevel];
            PFN_EH4_XCPT_FILTER_T const pfnFilter = pEntry->pfnFilter;
            if (pfnFilter)
            {
                /* Call the __except filtering expression: */
                //RTAssertMsg2("_except_handler4: Calling pfnFilter=%p\n", pfnFilter);
                EXCEPTION_POINTERS XcptPtrs = { pXcptRec, pCpuCtx };
                pEh4XcptRegRec->pXctpPtrs = &XcptPtrs;
                LONG lRet = rtVccEh4DoFiltering(pfnFilter, pbFrame);
                pEh4XcptRegRec->pXctpPtrs = NULL;
                //RTAssertMsg2("_except_handler4: pfnFilter=%p -> %ld\n", pfnFilter, lRet);
                rtVccEh4ValidateCookies(pScopeTable, pbFrame);

                /* Return if we're supposed to continue execution (the convention
                   it to match negative values rather than the exact defined value):  */
                AssertCompile(EXCEPTION_CONTINUE_EXECUTION == -1);
                if (lRet <= EXCEPTION_CONTINUE_EXECUTION)
                    return ExceptionContinueExecution;

                /* Similarly, the handler is executed for any positive value. */
                AssertCompile(EXCEPTION_CONTINUE_SEARCH == 0);
                AssertCompile(EXCEPTION_EXECUTE_HANDLER == 1);
                if (lRet >= EXCEPTION_EXECUTE_HANDLER)
                {
                    /* We're about to resume execution in the __except block, so unwind
                       up to it first. */
                    //RTAssertMsg2("_except_handler4: global unwind\n");
                    rtVccEh4DoGlobalUnwind(pXcptRec, &pEh4XcptRegRec->XcptRec);
                    if (pEh4XcptRegRec->uTryLevel != EH4_TOPMOST_TRY_LEVEL)
                    {
                        //RTAssertMsg2("_except_handler4: local unwind\n");
                        rtVccEh4DoLocalUnwind(pEh4XcptRegRec, uTryLevel, pbFrame);
                    }
                    rtVccEh4ValidateCookies(pScopeTable, pbFrame);

                    /* Now jump to the __except block.  This will _not_ return. */
                    //RTAssertMsg2("_except_handler4: jumping to __except block %p (level %#x)\n", pEntry->pfnHandler, pEntry->uEnclosingLevel);
                    pEh4XcptRegRec->uTryLevel = pEntry->uEnclosingLevel;
                    rtVccEh4ValidateCookies(pScopeTable, pbFrame); /* paranoia^2 */

                    rtVccEh4JumpToHandler(pEntry->pfnHandler, pbFrame);
                    /* (not reached) */
                }
            }

            /*
             * Next try level.
             */
            uTryLevel = pEntry->uEnclosingLevel;
        }
    }
    /*
     * If not dispatching we're unwinding, so we call any __finally blocks.
     */
    else
    {
        //RTAssertMsg2("_except_handler4: unwind: uTryLevel=%#x\n", pEh4XcptRegRec->uTryLevel);
        if (pEh4XcptRegRec->uTryLevel != EH4_TOPMOST_TRY_LEVEL)
        {
            rtVccEh4DoLocalUnwind(pEh4XcptRegRec, EH4_TOPMOST_TRY_LEVEL, pbFrame);
            rtVccEh4ValidateCookies(pScopeTable, pbFrame);
        }
    }

    RT_NOREF(pvCtx);
    return ExceptionContinueSearch;
}