summaryrefslogtreecommitdiff
path: root/src/vm/amd64/excepamd64.cpp
blob: 2fc553a987819233baab8e605c2a209bc319fc8d (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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

/*  EXCEP.CPP
 *
 */
//

//

#include "common.h"

#include "frames.h"
#include "threads.h"
#include "excep.h"
#include "object.h"
#include "field.h"
#include "dbginterface.h"
#include "cgensys.h"
#include "comutilnative.h"
#include "sigformat.h"
#include "siginfo.hpp"
#include "gc.h"
#include "eedbginterfaceimpl.h" //so we can clearexception in COMPlusThrow
#include "perfcounters.h"
#include "asmconstants.h"

#include "exceptionhandling.h"



#if !defined(DACCESS_COMPILE)

VOID ResetCurrentContext()
{
    LIMITED_METHOD_CONTRACT;
}

LONG CLRNoCatchHandler(EXCEPTION_POINTERS* pExceptionInfo, PVOID pv)
{
    return EXCEPTION_CONTINUE_SEARCH;
}

#endif // !DACCESS_COMPILE

inline PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrameWorker(UINT_PTR establisherFrame)
{
    LIMITED_METHOD_DAC_CONTRACT;
    
    SIZE_T rbp = establisherFrame + REDIRECTSTUB_ESTABLISHER_OFFSET_RBP;
    PTR_PTR_CONTEXT ppContext = dac_cast<PTR_PTR_CONTEXT>((TADDR)rbp + REDIRECTSTUB_RBP_OFFSET_CONTEXT);
    return *ppContext;
}

PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(DISPATCHER_CONTEXT * pDispatcherContext)
{
    LIMITED_METHOD_DAC_CONTRACT;
    
    return GetCONTEXTFromRedirectedStubStackFrameWorker(pDispatcherContext->EstablisherFrame);
}

PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(CONTEXT * pContext)
{
    LIMITED_METHOD_DAC_CONTRACT;
    
    return GetCONTEXTFromRedirectedStubStackFrameWorker(pContext->Rbp);
}

#if !defined(DACCESS_COMPILE)

FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (DISPATCHER_CONTEXT *pDispatcherContext)
{
    LIMITED_METHOD_CONTRACT;
    
    return (FaultingExceptionFrame*)(pDispatcherContext->EstablisherFrame + THROWSTUB_ESTABLISHER_OFFSET_FaultingExceptionFrame);
}

#endif // !DACCESS_COMPILE

#if !defined(DACCESS_COMPILE)

#define AMD64_SIZE64_PREFIX 0x48
#define AMD64_ADD_IMM8_OP 0x83
#define AMD64_ADD_IMM32_OP 0x81
#define AMD64_JMP_IMM8_OP 0xeb
#define AMD64_JMP_IMM32_OP 0xe9
#define AMD64_JMP_IND_OP 0xff
#define AMD64_JMP_IND_RAX 0x20
#define AMD64_LEA_OP 0x8d
#define AMD64_POP_OP 0x58
#define AMD64_RET_OP 0xc3
#define AMD64_RET_OP_2 0xc2
#define AMD64_REP_PREFIX 0xf3
#define AMD64_NOP 0x90
#define AMD64_INT3 0xCC

#define AMD64_IS_REX_PREFIX(x)      (((x) & 0xf0) == 0x40)

#define FAKE_PROLOG_SIZE 1
#define FAKE_FUNCTION_CODE_SIZE 1

#ifdef DEBUGGING_SUPPORTED
//
// If there is an Int3 opcode at the Address then this tries to get the
// correct Opcode for the address from the managed patch table. If this is
// called on an address which doesn't currently have an Int3 then the current
// opcode is returned. If there is no managed patch in the patch table
// corresponding to this address then the current opcode (0xCC) at Address is
// is returned. If a 0xCC is returned from this function it indicates an
// unmanaged patch at the address.
//
// If there is a managed patch at the address HasManagedBreakpoint is set to true.
//
// If there is a 0xCC at the address before the call to GetPatchedOpcode and 
// still a 0xCC when we return then this is considered an unmanaged patch and 
// HasManagedBreakpoint is set to true.
//
UCHAR GetOpcodeFromManagedBPForAddress(ULONG64 Address, BOOL* HasManagedBreakpoint, BOOL* HasUnmanagedBreakpoint)
{
    // If we don't see a breakpoint then quickly return.
    if (((UCHAR)*(BYTE*)Address) != AMD64_INT3)
    {
        return ((UCHAR)*(BYTE*)Address);
    }

    UCHAR PatchedOpcode;
    PatchedOpcode = (UCHAR)g_pDebugInterface->GetPatchedOpcode((CORDB_ADDRESS_TYPE*)(BYTE*)Address);

    // If a non Int3 opcode is returned from GetPatchedOpcode then 
    // this function has a managed breakpoint
    if (PatchedOpcode != AMD64_INT3)
    {
        (*HasManagedBreakpoint) = TRUE;
    }
    else
    {
        (*HasUnmanagedBreakpoint) = TRUE;
    }

    return PatchedOpcode;
}
#endif // DEBUGGING_SUPPORTED

PEXCEPTION_ROUTINE
RtlVirtualUnwind (
          IN ULONG HandlerType,
          IN ULONG64 ImageBase,
          IN ULONG64 ControlPc,
          IN PT_RUNTIME_FUNCTION FunctionEntry,
          IN OUT PCONTEXT ContextRecord,
          OUT PVOID *HandlerData,
          OUT PULONG64 EstablisherFrame,
          IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL
          )
{
    CONTRACTL 
    {
        NOTHROW;
        GC_NOTRIGGER;
        SO_TOLERANT;
    } 
    CONTRACTL_END;

    // The indirection should be taken care of by the caller 
    _ASSERTE((FunctionEntry->UnwindData & RUNTIME_FUNCTION_INDIRECT) == 0);

#ifdef DEBUGGING_SUPPORTED
    if (CORDebuggerAttached())
    {
        return RtlVirtualUnwind_Worker(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers);
    }
    else
#endif // DEBUGGING_SUPPORTED
    {
        return RtlVirtualUnwind_Unsafe(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers);        
    }
}

#ifdef DEBUGGING_SUPPORTED
PEXCEPTION_ROUTINE
RtlVirtualUnwind_Worker (
          IN ULONG HandlerType,
          IN ULONG64 ImageBase,
          IN ULONG64 ControlPc,
          IN PT_RUNTIME_FUNCTION FunctionEntry,
          IN OUT PCONTEXT ContextRecord,
          OUT PVOID *HandlerData,
          OUT PULONG64 EstablisherFrame,
          IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL
          )
{
    CONTRACTL 
    {
        NOTHROW;
        GC_NOTRIGGER;
        SO_TOLERANT;
    } 
    CONTRACTL_END;

    // b/c we're only called by the safe RtlVirtualUnwind we are guaranteed 
    // that the debugger is attched when we get here.
    _ASSERTE(CORDebuggerAttached());

    LOG((LF_CORDB, LL_EVERYTHING, "RVU_CBSW: in RtlVitualUnwind_ClrDbgSafeWorker, ControlPc=0x%p\n", ControlPc));

    BOOL     InEpilogue = FALSE;
    BOOL     HasManagedBreakpoint = FALSE;
    BOOL     HasUnmanagedBreakpoint = FALSE;
    UCHAR    TempOpcode = NULL;
    PUCHAR   NextByte;
    ULONG    CurrentOffset;
    ULONG    FrameRegister;
    ULONG64  BranchTarget;
    PUNWIND_INFO UnwindInfo;

    // 64bit Whidbey does NOT support interop debugging, so if this
    // is not managed code, normal unwind
    if (!ExecutionManager::IsManagedCode((PCODE) ControlPc))
    {
        goto NORMAL_UNWIND;
    }

    UnwindInfo = (PUNWIND_INFO)(FunctionEntry->UnwindData + ImageBase);
    CurrentOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase));

    // control stopped in prologue, normal unwind
    if (CurrentOffset < UnwindInfo->SizeOfProlog)
    {
        goto NORMAL_UNWIND;
    }
    
    // ASSUMPTION: only the first byte of an opcode will be patched by the CLR debugging code

    // determine if we're in an epilog and if there is at least one managed breakpoint
    NextByte = (PUCHAR)ControlPc;

    TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint);
   
    // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint
    _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3);

    // Check for an indication of the start of a epilogue:
    //   add rsp, imm8
    //   add rsp, imm32
    //   lea rsp, -disp8[fp]
    //   lea rsp, -disp32[fp]
    if ((TempOpcode == AMD64_SIZE64_PREFIX) 
        && (NextByte[1] == AMD64_ADD_IMM8_OP) 
        && (NextByte[2] == 0xc4)) 
    {
        // add rsp, imm8.
        NextByte += 4;
    } 
    else if ((TempOpcode == AMD64_SIZE64_PREFIX) 
            && (NextByte[1] == AMD64_ADD_IMM32_OP) 
            && (NextByte[2] == 0xc4)) 
    {
        // add rsp, imm32.
        NextByte += 7;
    } 
    else if (((TempOpcode & 0xf8) == AMD64_SIZE64_PREFIX) 
            && (NextByte[1] == AMD64_LEA_OP)) 
    {
        FrameRegister = ((TempOpcode & 0x7) << 3) | (NextByte[2] & 0x7);

        if ((FrameRegister != 0)
            && (FrameRegister == UnwindInfo->FrameRegister)) 
        {
            if ((NextByte[2] & 0xf8) == 0x60) 
            {
                // lea rsp, disp8[fp].
                NextByte += 4;
            } 
            else if ((NextByte[2] &0xf8) == 0xa0) 
            {
                // lea rsp, disp32[fp].
                NextByte += 7;
            }
        }
    }    

    // if we haven't eaten any of the code stream detecting a stack adjustment 
    // then TempOpcode is still valid
    if (((ULONG64)NextByte) != ControlPc)
    {
        TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint);
    }

    // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint
    _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3);

    // Check for any number of:
    //   pop nonvolatile-integer-register[0..15].
    while (TRUE) 
    {
        if ((TempOpcode & 0xf8) == AMD64_POP_OP) 
        {
            NextByte += 1;
        } 
        else if (AMD64_IS_REX_PREFIX(TempOpcode) 
                && ((NextByte[1] & 0xf8) == AMD64_POP_OP)) 
        {
            NextByte += 2;
        } 
        else 
        {
            // when we break out here TempOpcode will hold the next Opcode so there
            // is no need to call GetOpcodeFromManagedBPForAddress again
            break;
        }
        TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint);

        // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint
        _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3);
    }

    // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint
    _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3);
    
    // If the next instruction is a return, then control is currently in
    // an epilogue and execution of the epilogue should be emulated.
    // Otherwise, execution is not in an epilogue and the prologue should
    // be unwound.
    if (TempOpcode == AMD64_RET_OP || TempOpcode == AMD64_RET_OP_2)
    {
        // A return is an unambiguous indication of an epilogue
        InEpilogue = TRUE;
        NextByte += 1;
    } 
    else if (TempOpcode == AMD64_REP_PREFIX && NextByte[1] == AMD64_RET_OP)
    {
        // A return is an unambiguous indication of an epilogue
        InEpilogue = TRUE;
        NextByte += 2;
    } 
    else if (TempOpcode == AMD64_JMP_IMM8_OP || TempOpcode == AMD64_JMP_IMM32_OP) 
    {
        // An unconditional branch to a target that is equal to the start of
        // or outside of this routine is logically a call to another function.
        BranchTarget = (ULONG64)NextByte - ImageBase;

        if (TempOpcode == AMD64_JMP_IMM8_OP) 
        {
            BranchTarget += 2 + (CHAR)NextByte[1];
            NextByte += 2;
        } 
        else 
        {
            BranchTarget += 5 + *((LONG UNALIGNED *)&NextByte[1]);
            NextByte += 5;
        }

        // Now determine whether the branch target refers to code within this
        // function. If not, then it is an epilogue indicator.
        //
        // A branch to the start of self implies a recursive call, so
        // is treated as an epilogue.
        if (BranchTarget <= FunctionEntry->BeginAddress ||
            BranchTarget >= FunctionEntry->EndAddress) 
        {
            _ASSERTE((UnwindInfo->Flags & UNW_FLAG_CHAININFO) == 0);
            InEpilogue = TRUE;
        }
    }
    else if ((TempOpcode == AMD64_JMP_IND_OP) && (NextByte[1] == 0x25)) 
    {
        // An unconditional jump indirect.

        // This is a jmp outside of the function, probably a tail call
        // to an import function.
        InEpilogue = TRUE;
        NextByte += 2;
    }
    else if (((TempOpcode & 0xf8) == AMD64_SIZE64_PREFIX) 
            && (NextByte[1] == AMD64_JMP_IND_OP) 
            && (NextByte[2] & 0x38) == AMD64_JMP_IND_RAX) 
    {
        //
        // This is an indirect jump opcode: 0x48 0xff /4.  The 64-bit
        // flag (REX.W) is always redundant here, so its presence is
        // overloaded to indicate a branch out of the function - a tail
        // call.
        //
        // Such an opcode is an unambiguous epilogue indication.
        //
        InEpilogue = TRUE;
        NextByte += 3;
    }

    if (InEpilogue && HasUnmanagedBreakpoint)
    {        
        STRESS_LOG1(LF_CORDB, LL_ERROR, "RtlVirtualUnwind is about to fail b/c the ControlPc (0x%p) is in the epilog of a function which has a 0xCC in its epilog.", ControlPc);
        _ASSERTE(!"RtlVirtualUnwind is about to fail b/c you are unwinding through\n"
                  "the epilogue of a function and have a 0xCC in the codestream. This is\n"
                  "probably caused by having set that breakpoint yourself in the debugger,\n"
                  "you might try to remove the bp and ignore this assert.");
    }
    
    if (!(InEpilogue && HasManagedBreakpoint))
    {
        goto NORMAL_UNWIND;
    }
    else
    {
        // InEpilogue && HasManagedBreakpoint, this means we have to make the fake code buffer

        // We explicitly handle the case where the new below can't allocate, but we're still
        // getting an assert from inside new b/c we can be called within a FAULT_FORBID scope.
        //
        // If new does fail we will still end up crashing, but the debugger doesn't have to 
        // be OOM hardened in Whidbey and this is a debugger only code path so we're ok in
        // that department.
        FAULT_NOT_FATAL();

        LOG((LF_CORDB, LL_EVERYTHING, "RVU_CBSW: Function has >1 managed bp in the epilogue, and we are in the epilogue, need a code buffer for RtlVirtualUnwind\n"));
            
        // IMPLEMENTATION NOTE:
        // It is of note that we are significantly pruning the funtion here in making the fake
        // code buffer, all that we are making room for is 1 byte for the prologue, 1 byte for
        // function code and what is left of the epilogue to be executed. This is _very_ closely
        // tied to the implmentation of RtlVirtualUnwind and the knowledge that by passing the
        // the test above and having InEpilogue==TRUE then the code path which will be followed
        // through RtlVirtualUnwind is known.
        // 
        // In making this fake code buffer we need to ensure that we don't mess with the outcome
        // of the test in RtlVirtualUnwind to determine that control stopped within a function
        // epilogue, or the unwinding that will happen when that test comes out TRUE. To that end 
        // we have preserved a single byte representing the Prologue as a section of the buffer 
        // as well as a single byte representation of the Function code so that tests to make sure
        // that we're out of the prologue will not fail. 
    
        T_RUNTIME_FUNCTION FakeFunctionEntry;

        //
        // The buffer contains 4 sections
        //
        // UNWIND_INFO:     The fake UNWIND_INFO will be first, we are making a copy within the
        //                  buffer because it needs to be addressable through a 32bit offset
        //                  of NewImageBase like the fake code buffer
        //
        // Prologue:        A single byte representing the function Prologue
        //
        // Function Code:   A single byte representing the Function's code
        //
        // Epilogue:        This contains what is left to be executed of the Epilogue which control
        //                  stopped in, it can be as little as a "return" type statement or as much
        //                  as the whole Epilogue containing a stack adjustment, pops and "return"
        //                  type statement.
        //
        //
        // Here is the layout of the buffer:
        //
        // UNWIND_INFO copy:
        //    pBuffer[0]
        //     ...
        //    pBuffer[sizeof(UNWIND_INFO) - 1]
        // PROLOGUE:
        //    pBuffer[sizeof(UNWIND_INFO) + 0] <----------------- THIS IS THE START OF pCodeBuffer
        // FUNCTION CODE:
        //    pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE]
        // EPILOGUE
        //    pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE]
        //     ...
        //    pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue]
        //
        ULONG   SizeOfEpilogue = (ULONG)((ULONG64)NextByte - ControlPc);
        ULONG   SizeOfBuffer = (ULONG)(sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue);
        BYTE   *pBuffer = (BYTE*) new (nothrow) BYTE[SizeOfBuffer];
        BYTE   *pCodeBuffer;            
        ULONG64 NewImageBase;
        ULONG64 NewControlPc;

        // <TODO> This WILL fail during unwind because we KNOW there is a managed breakpoint
        // in the epilog and we're in the epilog, but we could not allocate a buffer to
        // put our cleaned up code into, what to do? </TODO>
        if (pBuffer == NULL)
        {
            // TODO: can we throw OOM here? or will we just go recursive b/c that will eventually get to the same place?
            _ASSERTE(!"OOM when trying to allocate buffer for virtual unwind cleaned code, BIG PROBLEM!!");
            goto NORMAL_UNWIND;
        }

        NewImageBase = ((((ULONG64)pBuffer) >> 32) << 32);
        pCodeBuffer = pBuffer + sizeof(UNWIND_INFO);

#if defined(_DEBUG)
        // Fill the buffer up to the rest of the epilogue to be executed with Int3
        for (int i=0; i<(FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE); i++)
        {
            pCodeBuffer[i] = AMD64_INT3;
        }
#endif

        // Copy the UNWIND_INFO and the Epilogue into the buffer
        memcpy(pBuffer, (const void*)UnwindInfo, sizeof(UNWIND_INFO));
        memcpy(&(pCodeBuffer[FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE]), (const void*)(BYTE*)ControlPc, SizeOfEpilogue);

        _ASSERTE((UCHAR)*(BYTE*)ControlPc == (UCHAR)pCodeBuffer[FAKE_PROLOG_SIZE+FAKE_FUNCTION_CODE_SIZE]);

        HasManagedBreakpoint = FALSE;
        HasUnmanagedBreakpoint = FALSE;

        // The buffer cleaning implementation here just runs through the buffer byte by byte trying 
        // to get a real opcode from the patch table for any 0xCC that it finds. There is the
        // possiblity that the epilogue will contain a 0xCC in an immediate value for which a
        // patch won't be found and this will report a false positive for HasUnmanagedBreakpoint.
        BYTE* pCleanCodePc = pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE;
        BYTE* pRealCodePc = (BYTE*)ControlPc;
        while (pCleanCodePc < (pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue))
        {
            // If we have a breakpoint at the address then try to get the correct opcode from
            // the managed patch using GetOpcodeFromManagedBPForAddress.
            if (AMD64_INT3 == ((UCHAR)*pCleanCodePc))
            {
                (*pCleanCodePc) = GetOpcodeFromManagedBPForAddress((ULONG64)pRealCodePc, &HasManagedBreakpoint, &HasUnmanagedBreakpoint);
            }

            pCleanCodePc++;
            pRealCodePc++;
        }

        // On the second pass through the epilogue assuming things are working as
        // they should we should once again have at least one managed breakpoint...
        // otherwise why are we here?
        _ASSERTE(HasManagedBreakpoint == TRUE);

        // This would be nice to assert, but we can't w/ current buffer cleaning implementation, see note above.
        // _ASSERTE(HasUnmanagedBreakpoint == FALSE);

        ((PUNWIND_INFO)pBuffer)->SizeOfProlog = FAKE_PROLOG_SIZE;

        FakeFunctionEntry.BeginAddress = (ULONG)((ULONG64)pCodeBuffer - NewImageBase);
        FakeFunctionEntry.EndAddress   = (ULONG)((ULONG64)(pCodeBuffer + (FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue)) - NewImageBase);
        FakeFunctionEntry.UnwindData   = (ULONG)((ULONG64)pBuffer - NewImageBase);

        NewControlPc = (ULONG64)(pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE);

        RtlVirtualUnwind_Unsafe((ULONG)HandlerType, (ULONG64)NewImageBase, (ULONG64)NewControlPc, &FakeFunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers);

        // Make sure to delete the whole buffer and not just the code buffer
        delete[] pBuffer;

        return NULL; // if control left in the epilog then RtlVirtualUnwind will not return an exception handler
    }
    
NORMAL_UNWIND:
    return RtlVirtualUnwind_Unsafe(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers);
}
#endif // DEBUGGING_SUPPORTED

#undef FAKE_PROLOG_SIZE
#undef FAKE_FUNCTION_CODE_SIZE

#undef AMD64_SIZE64_PREFIX
#undef AMD64_ADD_IMM8_OP
#undef AMD64_ADD_IMM32_OP
#undef AMD64_JMP_IMM8_OP
#undef AMD64_JMP_IMM32_OP
#undef AMD64_JMP_IND_OP
#undef AMD64_JMP_IND_RAX
#undef AMD64_POP_OP
#undef AMD64_RET_OP
#undef AMD64_RET_OP_2
#undef AMD64_NOP
#undef AMD64_INT3

#endif // !DACCESS_COMPILE

#ifndef DACCESS_COMPILE
// Returns TRUE if caller should resume execution.
BOOL
AdjustContextForVirtualStub(
        EXCEPTION_RECORD *pExceptionRecord,
        CONTEXT *pContext)
{
    LIMITED_METHOD_CONTRACT;

    // Nothing to adjust

    return FALSE;
}

#endif