1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5/* EXCEP.CPP
6 *
7 */
8//
9
10//
11
12#include "common.h"
13
14#include "frames.h"
15#include "threads.h"
16#include "excep.h"
17#include "object.h"
18#include "field.h"
19#include "dbginterface.h"
20#include "cgensys.h"
21#include "comutilnative.h"
22#include "sigformat.h"
23#include "siginfo.hpp"
24#include "gcheaputilities.h"
25#include "eedbginterfaceimpl.h" //so we can clearexception in COMPlusThrow
26#include "perfcounters.h"
27#include "asmconstants.h"
28
29#include "exceptionhandling.h"
30
31
32
33#if !defined(DACCESS_COMPILE)
34
35VOID ResetCurrentContext()
36{
37 LIMITED_METHOD_CONTRACT;
38}
39
40LONG CLRNoCatchHandler(EXCEPTION_POINTERS* pExceptionInfo, PVOID pv)
41{
42 return EXCEPTION_CONTINUE_SEARCH;
43}
44
45#endif // !DACCESS_COMPILE
46
47inline PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrameWorker(UINT_PTR establisherFrame)
48{
49 LIMITED_METHOD_DAC_CONTRACT;
50
51 SIZE_T rbp = establisherFrame + REDIRECTSTUB_ESTABLISHER_OFFSET_RBP;
52 PTR_PTR_CONTEXT ppContext = dac_cast<PTR_PTR_CONTEXT>((TADDR)rbp + REDIRECTSTUB_RBP_OFFSET_CONTEXT);
53 return *ppContext;
54}
55
56PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(DISPATCHER_CONTEXT * pDispatcherContext)
57{
58 LIMITED_METHOD_DAC_CONTRACT;
59
60 return GetCONTEXTFromRedirectedStubStackFrameWorker(pDispatcherContext->EstablisherFrame);
61}
62
63PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(CONTEXT * pContext)
64{
65 LIMITED_METHOD_DAC_CONTRACT;
66
67 return GetCONTEXTFromRedirectedStubStackFrameWorker(pContext->Rbp);
68}
69
70#if !defined(DACCESS_COMPILE)
71
72FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (DISPATCHER_CONTEXT *pDispatcherContext)
73{
74 LIMITED_METHOD_CONTRACT;
75
76 return (FaultingExceptionFrame*)(pDispatcherContext->EstablisherFrame + THROWSTUB_ESTABLISHER_OFFSET_FaultingExceptionFrame);
77}
78
79#endif // !DACCESS_COMPILE
80
81#if !defined(DACCESS_COMPILE)
82
83#define AMD64_SIZE64_PREFIX 0x48
84#define AMD64_ADD_IMM8_OP 0x83
85#define AMD64_ADD_IMM32_OP 0x81
86#define AMD64_JMP_IMM8_OP 0xeb
87#define AMD64_JMP_IMM32_OP 0xe9
88#define AMD64_JMP_IND_OP 0xff
89#define AMD64_JMP_IND_RAX 0x20
90#define AMD64_LEA_OP 0x8d
91#define AMD64_POP_OP 0x58
92#define AMD64_RET_OP 0xc3
93#define AMD64_RET_OP_2 0xc2
94#define AMD64_REP_PREFIX 0xf3
95#define AMD64_NOP 0x90
96#define AMD64_INT3 0xCC
97
98#define AMD64_IS_REX_PREFIX(x) (((x) & 0xf0) == 0x40)
99
100#define FAKE_PROLOG_SIZE 1
101#define FAKE_FUNCTION_CODE_SIZE 1
102
103#ifdef DEBUGGING_SUPPORTED
104//
105// If there is an Int3 opcode at the Address then this tries to get the
106// correct Opcode for the address from the managed patch table. If this is
107// called on an address which doesn't currently have an Int3 then the current
108// opcode is returned. If there is no managed patch in the patch table
109// corresponding to this address then the current opcode (0xCC) at Address is
110// is returned. If a 0xCC is returned from this function it indicates an
111// unmanaged patch at the address.
112//
113// If there is a managed patch at the address HasManagedBreakpoint is set to true.
114//
115// If there is a 0xCC at the address before the call to GetPatchedOpcode and
116// still a 0xCC when we return then this is considered an unmanaged patch and
117// HasManagedBreakpoint is set to true.
118//
119UCHAR GetOpcodeFromManagedBPForAddress(ULONG64 Address, BOOL* HasManagedBreakpoint, BOOL* HasUnmanagedBreakpoint)
120{
121 // If we don't see a breakpoint then quickly return.
122 if (((UCHAR)*(BYTE*)Address) != AMD64_INT3)
123 {
124 return ((UCHAR)*(BYTE*)Address);
125 }
126
127 UCHAR PatchedOpcode;
128 PatchedOpcode = (UCHAR)g_pDebugInterface->GetPatchedOpcode((CORDB_ADDRESS_TYPE*)(BYTE*)Address);
129
130 // If a non Int3 opcode is returned from GetPatchedOpcode then
131 // this function has a managed breakpoint
132 if (PatchedOpcode != AMD64_INT3)
133 {
134 (*HasManagedBreakpoint) = TRUE;
135 }
136 else
137 {
138 (*HasUnmanagedBreakpoint) = TRUE;
139 }
140
141 return PatchedOpcode;
142}
143#endif // DEBUGGING_SUPPORTED
144
145PEXCEPTION_ROUTINE
146RtlVirtualUnwind (
147 IN ULONG HandlerType,
148 IN ULONG64 ImageBase,
149 IN ULONG64 ControlPc,
150 IN PT_RUNTIME_FUNCTION FunctionEntry,
151 IN OUT PCONTEXT ContextRecord,
152 OUT PVOID *HandlerData,
153 OUT PULONG64 EstablisherFrame,
154 IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL
155 )
156{
157 CONTRACTL
158 {
159 NOTHROW;
160 GC_NOTRIGGER;
161 SO_TOLERANT;
162 }
163 CONTRACTL_END;
164
165 // The indirection should be taken care of by the caller
166 _ASSERTE((FunctionEntry->UnwindData & RUNTIME_FUNCTION_INDIRECT) == 0);
167
168#ifdef DEBUGGING_SUPPORTED
169 if (CORDebuggerAttached())
170 {
171 return RtlVirtualUnwind_Worker(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers);
172 }
173 else
174#endif // DEBUGGING_SUPPORTED
175 {
176 return RtlVirtualUnwind_Unsafe(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers);
177 }
178}
179
180#ifdef DEBUGGING_SUPPORTED
181PEXCEPTION_ROUTINE
182RtlVirtualUnwind_Worker (
183 IN ULONG HandlerType,
184 IN ULONG64 ImageBase,
185 IN ULONG64 ControlPc,
186 IN PT_RUNTIME_FUNCTION FunctionEntry,
187 IN OUT PCONTEXT ContextRecord,
188 OUT PVOID *HandlerData,
189 OUT PULONG64 EstablisherFrame,
190 IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL
191 )
192{
193 CONTRACTL
194 {
195 NOTHROW;
196 GC_NOTRIGGER;
197 SO_TOLERANT;
198 }
199 CONTRACTL_END;
200
201 // b/c we're only called by the safe RtlVirtualUnwind we are guaranteed
202 // that the debugger is attched when we get here.
203 _ASSERTE(CORDebuggerAttached());
204
205 LOG((LF_CORDB, LL_EVERYTHING, "RVU_CBSW: in RtlVitualUnwind_ClrDbgSafeWorker, ControlPc=0x%p\n", ControlPc));
206
207 BOOL InEpilogue = FALSE;
208 BOOL HasManagedBreakpoint = FALSE;
209 BOOL HasUnmanagedBreakpoint = FALSE;
210 UCHAR TempOpcode = NULL;
211 PUCHAR NextByte;
212 ULONG CurrentOffset;
213 ULONG FrameRegister;
214 ULONG64 BranchTarget;
215 PUNWIND_INFO UnwindInfo;
216
217 // 64bit Whidbey does NOT support interop debugging, so if this
218 // is not managed code, normal unwind
219 if (!ExecutionManager::IsManagedCode((PCODE) ControlPc))
220 {
221 goto NORMAL_UNWIND;
222 }
223
224 UnwindInfo = (PUNWIND_INFO)(FunctionEntry->UnwindData + ImageBase);
225 CurrentOffset = (ULONG)(ControlPc - (FunctionEntry->BeginAddress + ImageBase));
226
227 // control stopped in prologue, normal unwind
228 if (CurrentOffset < UnwindInfo->SizeOfProlog)
229 {
230 goto NORMAL_UNWIND;
231 }
232
233 // ASSUMPTION: only the first byte of an opcode will be patched by the CLR debugging code
234
235 // determine if we're in an epilog and if there is at least one managed breakpoint
236 NextByte = (PUCHAR)ControlPc;
237
238 TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint);
239
240 // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint
241 _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3);
242
243 // Check for an indication of the start of a epilogue:
244 // add rsp, imm8
245 // add rsp, imm32
246 // lea rsp, -disp8[fp]
247 // lea rsp, -disp32[fp]
248 if ((TempOpcode == AMD64_SIZE64_PREFIX)
249 && (NextByte[1] == AMD64_ADD_IMM8_OP)
250 && (NextByte[2] == 0xc4))
251 {
252 // add rsp, imm8.
253 NextByte += 4;
254 }
255 else if ((TempOpcode == AMD64_SIZE64_PREFIX)
256 && (NextByte[1] == AMD64_ADD_IMM32_OP)
257 && (NextByte[2] == 0xc4))
258 {
259 // add rsp, imm32.
260 NextByte += 7;
261 }
262 else if (((TempOpcode & 0xf8) == AMD64_SIZE64_PREFIX)
263 && (NextByte[1] == AMD64_LEA_OP))
264 {
265 FrameRegister = ((TempOpcode & 0x7) << 3) | (NextByte[2] & 0x7);
266
267 if ((FrameRegister != 0)
268 && (FrameRegister == UnwindInfo->FrameRegister))
269 {
270 if ((NextByte[2] & 0xf8) == 0x60)
271 {
272 // lea rsp, disp8[fp].
273 NextByte += 4;
274 }
275 else if ((NextByte[2] &0xf8) == 0xa0)
276 {
277 // lea rsp, disp32[fp].
278 NextByte += 7;
279 }
280 }
281 }
282
283 // if we haven't eaten any of the code stream detecting a stack adjustment
284 // then TempOpcode is still valid
285 if (((ULONG64)NextByte) != ControlPc)
286 {
287 TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint);
288 }
289
290 // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint
291 _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3);
292
293 // Check for any number of:
294 // pop nonvolatile-integer-register[0..15].
295 while (TRUE)
296 {
297 if ((TempOpcode & 0xf8) == AMD64_POP_OP)
298 {
299 NextByte += 1;
300 }
301 else if (AMD64_IS_REX_PREFIX(TempOpcode)
302 && ((NextByte[1] & 0xf8) == AMD64_POP_OP))
303 {
304 NextByte += 2;
305 }
306 else
307 {
308 // when we break out here TempOpcode will hold the next Opcode so there
309 // is no need to call GetOpcodeFromManagedBPForAddress again
310 break;
311 }
312 TempOpcode = GetOpcodeFromManagedBPForAddress((ULONG64)NextByte, &HasManagedBreakpoint, &HasUnmanagedBreakpoint);
313
314 // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint
315 _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3);
316 }
317
318 // TempOpcode == NextByte[0] unless NextByte[0] is a breakpoint
319 _ASSERTE(TempOpcode == NextByte[0] || NextByte[0] == AMD64_INT3);
320
321 // If the next instruction is a return, then control is currently in
322 // an epilogue and execution of the epilogue should be emulated.
323 // Otherwise, execution is not in an epilogue and the prologue should
324 // be unwound.
325 if (TempOpcode == AMD64_RET_OP || TempOpcode == AMD64_RET_OP_2)
326 {
327 // A return is an unambiguous indication of an epilogue
328 InEpilogue = TRUE;
329 NextByte += 1;
330 }
331 else if (TempOpcode == AMD64_REP_PREFIX && NextByte[1] == AMD64_RET_OP)
332 {
333 // A return is an unambiguous indication of an epilogue
334 InEpilogue = TRUE;
335 NextByte += 2;
336 }
337 else if (TempOpcode == AMD64_JMP_IMM8_OP || TempOpcode == AMD64_JMP_IMM32_OP)
338 {
339 // An unconditional branch to a target that is equal to the start of
340 // or outside of this routine is logically a call to another function.
341 BranchTarget = (ULONG64)NextByte - ImageBase;
342
343 if (TempOpcode == AMD64_JMP_IMM8_OP)
344 {
345 BranchTarget += 2 + (CHAR)NextByte[1];
346 NextByte += 2;
347 }
348 else
349 {
350 BranchTarget += 5 + *((LONG UNALIGNED *)&NextByte[1]);
351 NextByte += 5;
352 }
353
354 // Now determine whether the branch target refers to code within this
355 // function. If not, then it is an epilogue indicator.
356 //
357 // A branch to the start of self implies a recursive call, so
358 // is treated as an epilogue.
359 if (BranchTarget <= FunctionEntry->BeginAddress ||
360 BranchTarget >= FunctionEntry->EndAddress)
361 {
362 _ASSERTE((UnwindInfo->Flags & UNW_FLAG_CHAININFO) == 0);
363 InEpilogue = TRUE;
364 }
365 }
366 else if ((TempOpcode == AMD64_JMP_IND_OP) && (NextByte[1] == 0x25))
367 {
368 // An unconditional jump indirect.
369
370 // This is a jmp outside of the function, probably a tail call
371 // to an import function.
372 InEpilogue = TRUE;
373 NextByte += 2;
374 }
375 else if (((TempOpcode & 0xf8) == AMD64_SIZE64_PREFIX)
376 && (NextByte[1] == AMD64_JMP_IND_OP)
377 && (NextByte[2] & 0x38) == AMD64_JMP_IND_RAX)
378 {
379 //
380 // This is an indirect jump opcode: 0x48 0xff /4. The 64-bit
381 // flag (REX.W) is always redundant here, so its presence is
382 // overloaded to indicate a branch out of the function - a tail
383 // call.
384 //
385 // Such an opcode is an unambiguous epilogue indication.
386 //
387 InEpilogue = TRUE;
388 NextByte += 3;
389 }
390
391 if (InEpilogue && HasUnmanagedBreakpoint)
392 {
393 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);
394 _ASSERTE(!"RtlVirtualUnwind is about to fail b/c you are unwinding through\n"
395 "the epilogue of a function and have a 0xCC in the codestream. This is\n"
396 "probably caused by having set that breakpoint yourself in the debugger,\n"
397 "you might try to remove the bp and ignore this assert.");
398 }
399
400 if (!(InEpilogue && HasManagedBreakpoint))
401 {
402 goto NORMAL_UNWIND;
403 }
404 else
405 {
406 // InEpilogue && HasManagedBreakpoint, this means we have to make the fake code buffer
407
408 // We explicitly handle the case where the new below can't allocate, but we're still
409 // getting an assert from inside new b/c we can be called within a FAULT_FORBID scope.
410 //
411 // If new does fail we will still end up crashing, but the debugger doesn't have to
412 // be OOM hardened in Whidbey and this is a debugger only code path so we're ok in
413 // that department.
414 FAULT_NOT_FATAL();
415
416 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"));
417
418 // IMPLEMENTATION NOTE:
419 // It is of note that we are significantly pruning the funtion here in making the fake
420 // code buffer, all that we are making room for is 1 byte for the prologue, 1 byte for
421 // function code and what is left of the epilogue to be executed. This is _very_ closely
422 // tied to the implmentation of RtlVirtualUnwind and the knowledge that by passing the
423 // the test above and having InEpilogue==TRUE then the code path which will be followed
424 // through RtlVirtualUnwind is known.
425 //
426 // In making this fake code buffer we need to ensure that we don't mess with the outcome
427 // of the test in RtlVirtualUnwind to determine that control stopped within a function
428 // epilogue, or the unwinding that will happen when that test comes out TRUE. To that end
429 // we have preserved a single byte representing the Prologue as a section of the buffer
430 // as well as a single byte representation of the Function code so that tests to make sure
431 // that we're out of the prologue will not fail.
432
433 T_RUNTIME_FUNCTION FakeFunctionEntry;
434
435 //
436 // The buffer contains 4 sections
437 //
438 // UNWIND_INFO: The fake UNWIND_INFO will be first, we are making a copy within the
439 // buffer because it needs to be addressable through a 32bit offset
440 // of NewImageBase like the fake code buffer
441 //
442 // Prologue: A single byte representing the function Prologue
443 //
444 // Function Code: A single byte representing the Function's code
445 //
446 // Epilogue: This contains what is left to be executed of the Epilogue which control
447 // stopped in, it can be as little as a "return" type statement or as much
448 // as the whole Epilogue containing a stack adjustment, pops and "return"
449 // type statement.
450 //
451 //
452 // Here is the layout of the buffer:
453 //
454 // UNWIND_INFO copy:
455 // pBuffer[0]
456 // ...
457 // pBuffer[sizeof(UNWIND_INFO) - 1]
458 // PROLOGUE:
459 // pBuffer[sizeof(UNWIND_INFO) + 0] <----------------- THIS IS THE START OF pCodeBuffer
460 // FUNCTION CODE:
461 // pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE]
462 // EPILOGUE
463 // pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE]
464 // ...
465 // pBuffer[sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue]
466 //
467 ULONG SizeOfEpilogue = (ULONG)((ULONG64)NextByte - ControlPc);
468 ULONG SizeOfBuffer = (ULONG)(sizeof(UNWIND_INFO) + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue);
469 BYTE *pBuffer = (BYTE*) new (nothrow) BYTE[SizeOfBuffer];
470 BYTE *pCodeBuffer;
471 ULONG64 NewImageBase;
472 ULONG64 NewControlPc;
473
474 // <TODO> This WILL fail during unwind because we KNOW there is a managed breakpoint
475 // in the epilog and we're in the epilog, but we could not allocate a buffer to
476 // put our cleaned up code into, what to do? </TODO>
477 if (pBuffer == NULL)
478 {
479 // TODO: can we throw OOM here? or will we just go recursive b/c that will eventually get to the same place?
480 _ASSERTE(!"OOM when trying to allocate buffer for virtual unwind cleaned code, BIG PROBLEM!!");
481 goto NORMAL_UNWIND;
482 }
483
484 NewImageBase = ((((ULONG64)pBuffer) >> 32) << 32);
485 pCodeBuffer = pBuffer + sizeof(UNWIND_INFO);
486
487#if defined(_DEBUG)
488 // Fill the buffer up to the rest of the epilogue to be executed with Int3
489 for (int i=0; i<(FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE); i++)
490 {
491 pCodeBuffer[i] = AMD64_INT3;
492 }
493#endif
494
495 // Copy the UNWIND_INFO and the Epilogue into the buffer
496 memcpy(pBuffer, (const void*)UnwindInfo, sizeof(UNWIND_INFO));
497 memcpy(&(pCodeBuffer[FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE]), (const void*)(BYTE*)ControlPc, SizeOfEpilogue);
498
499 _ASSERTE((UCHAR)*(BYTE*)ControlPc == (UCHAR)pCodeBuffer[FAKE_PROLOG_SIZE+FAKE_FUNCTION_CODE_SIZE]);
500
501 HasManagedBreakpoint = FALSE;
502 HasUnmanagedBreakpoint = FALSE;
503
504 // The buffer cleaning implementation here just runs through the buffer byte by byte trying
505 // to get a real opcode from the patch table for any 0xCC that it finds. There is the
506 // possiblity that the epilogue will contain a 0xCC in an immediate value for which a
507 // patch won't be found and this will report a false positive for HasUnmanagedBreakpoint.
508 BYTE* pCleanCodePc = pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE;
509 BYTE* pRealCodePc = (BYTE*)ControlPc;
510 while (pCleanCodePc < (pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue))
511 {
512 // If we have a breakpoint at the address then try to get the correct opcode from
513 // the managed patch using GetOpcodeFromManagedBPForAddress.
514 if (AMD64_INT3 == ((UCHAR)*pCleanCodePc))
515 {
516 (*pCleanCodePc) = GetOpcodeFromManagedBPForAddress((ULONG64)pRealCodePc, &HasManagedBreakpoint, &HasUnmanagedBreakpoint);
517 }
518
519 pCleanCodePc++;
520 pRealCodePc++;
521 }
522
523 // On the second pass through the epilogue assuming things are working as
524 // they should we should once again have at least one managed breakpoint...
525 // otherwise why are we here?
526 _ASSERTE(HasManagedBreakpoint == TRUE);
527
528 // This would be nice to assert, but we can't w/ current buffer cleaning implementation, see note above.
529 // _ASSERTE(HasUnmanagedBreakpoint == FALSE);
530
531 ((PUNWIND_INFO)pBuffer)->SizeOfProlog = FAKE_PROLOG_SIZE;
532
533 FakeFunctionEntry.BeginAddress = (ULONG)((ULONG64)pCodeBuffer - NewImageBase);
534 FakeFunctionEntry.EndAddress = (ULONG)((ULONG64)(pCodeBuffer + (FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE + SizeOfEpilogue)) - NewImageBase);
535 FakeFunctionEntry.UnwindData = (ULONG)((ULONG64)pBuffer - NewImageBase);
536
537 NewControlPc = (ULONG64)(pCodeBuffer + FAKE_PROLOG_SIZE + FAKE_FUNCTION_CODE_SIZE);
538
539 RtlVirtualUnwind_Unsafe((ULONG)HandlerType, (ULONG64)NewImageBase, (ULONG64)NewControlPc, &FakeFunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers);
540
541 // Make sure to delete the whole buffer and not just the code buffer
542 delete[] pBuffer;
543
544 return NULL; // if control left in the epilog then RtlVirtualUnwind will not return an exception handler
545 }
546
547NORMAL_UNWIND:
548 return RtlVirtualUnwind_Unsafe(HandlerType, ImageBase, ControlPc, FunctionEntry, ContextRecord, HandlerData, EstablisherFrame, ContextPointers);
549}
550#endif // DEBUGGING_SUPPORTED
551
552#undef FAKE_PROLOG_SIZE
553#undef FAKE_FUNCTION_CODE_SIZE
554
555#undef AMD64_SIZE64_PREFIX
556#undef AMD64_ADD_IMM8_OP
557#undef AMD64_ADD_IMM32_OP
558#undef AMD64_JMP_IMM8_OP
559#undef AMD64_JMP_IMM32_OP
560#undef AMD64_JMP_IND_OP
561#undef AMD64_JMP_IND_RAX
562#undef AMD64_POP_OP
563#undef AMD64_RET_OP
564#undef AMD64_RET_OP_2
565#undef AMD64_NOP
566#undef AMD64_INT3
567
568#endif // !DACCESS_COMPILE
569
570#ifndef DACCESS_COMPILE
571// Returns TRUE if caller should resume execution.
572BOOL
573AdjustContextForVirtualStub(
574 EXCEPTION_RECORD *pExceptionRecord,
575 CONTEXT *pContext)
576{
577 LIMITED_METHOD_CONTRACT;
578
579 // Nothing to adjust
580
581 return FALSE;
582}
583
584#endif
585
586