diff --git a/src/coreclr/inc/bitvector.h b/src/coreclr/inc/bitvector.h
index df06b4c75c66ec..0f17697dddce74 100644
--- a/src/coreclr/inc/bitvector.h
+++ b/src/coreclr/inc/bitvector.h
@@ -32,7 +32,9 @@
#define UNDEF_ASSERTE
#endif
+#ifndef FEATURE_NATIVEAOT
#define USE_BITVECTOR 1
+#endif
#if USE_BITVECTOR
/* The bitvector class is meant to be a drop in replacement for an integer
diff --git a/src/coreclr/inc/daccess.h b/src/coreclr/inc/daccess.h
index 7783fde153f942..ce5dcb8916dd06 100644
--- a/src/coreclr/inc/daccess.h
+++ b/src/coreclr/inc/daccess.h
@@ -2374,6 +2374,7 @@ typedef DPTR(int32_t) PTR_int32_t;
typedef DPTR(uint32_t) PTR_uint32_t;
typedef DPTR(uint64_t) PTR_uint64_t;
typedef DPTR(uintptr_t) PTR_uintptr_t;
+typedef DPTR(TADDR) PTR_TADDR;
#ifndef NATIVEAOT
typedef ArrayDPTR(BYTE) PTR_BYTE;
@@ -2395,7 +2396,6 @@ typedef DPTR(ULONG64) PTR_ULONG64;
typedef DPTR(INT64) PTR_INT64;
typedef DPTR(UINT64) PTR_UINT64;
typedef DPTR(SIZE_T) PTR_SIZE_T;
-typedef DPTR(TADDR) PTR_TADDR;
typedef DPTR(int) PTR_int;
typedef DPTR(BOOL) PTR_BOOL;
typedef DPTR(unsigned) PTR_unsigned;
diff --git a/src/coreclr/inc/eetwain.h b/src/coreclr/inc/eetwain.h
index e9c16514aa375b..71729a9182f3a1 100644
--- a/src/coreclr/inc/eetwain.h
+++ b/src/coreclr/inc/eetwain.h
@@ -629,123 +629,7 @@ HRESULT FixContextForEnC(PCONTEXT pCtx,
};
#ifdef TARGET_X86
-bool UnwindStackFrame(PREGDISPLAY pContext,
- EECodeInfo *pCodeInfo,
- unsigned flags,
- CodeManState *pState);
-
-size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken,
- unsigned curOffset,
- hdrInfo * infoPtr);
-#endif
-
-/*****************************************************************************
- ToDo: Do we want to include JIT/IL/target.h?
- */
-
-enum regNum
-{
- REGI_EAX, REGI_ECX, REGI_EDX, REGI_EBX,
- REGI_ESP, REGI_EBP, REGI_ESI, REGI_EDI,
- REGI_COUNT,
- REGI_NA = REGI_COUNT
-};
-
-/*****************************************************************************
- Register masks
- */
-
-enum RegMask
-{
- RM_EAX = 0x01,
- RM_ECX = 0x02,
- RM_EDX = 0x04,
- RM_EBX = 0x08,
- RM_ESP = 0x10,
- RM_EBP = 0x20,
- RM_ESI = 0x40,
- RM_EDI = 0x80,
-
- RM_NONE = 0x00,
- RM_ALL = (RM_EAX|RM_ECX|RM_EDX|RM_EBX|RM_ESP|RM_EBP|RM_ESI|RM_EDI),
- RM_CALLEE_SAVED = (RM_EBP|RM_EBX|RM_ESI|RM_EDI),
- RM_CALLEE_TRASHED = (RM_ALL & ~RM_CALLEE_SAVED),
-};
-
-/*****************************************************************************
- *
- * Helper to extract basic info from a method info block.
- */
-
-struct hdrInfo
-{
- unsigned int methodSize; // native code bytes
- unsigned int argSize; // in bytes
- unsigned int stackSize; // including callee saved registers
- unsigned int rawStkSize; // excluding callee saved registers
- ReturnKind returnKind; // The ReturnKind for this method.
-
- unsigned int prologSize;
-
- // Size of the epilogs in the method.
- // For methods which use CEE_JMP, some epilogs may end with a "ret" instruction
- // and some may end with a "jmp". The epilogSize reported should be for the
- // epilog with the smallest size.
- unsigned int epilogSize;
-
- unsigned char epilogCnt;
- bool epilogEnd; // is the epilog at the end of the method
-
- bool ebpFrame; // locals and arguments addressed relative to EBP
- bool doubleAlign; // is the stack double-aligned? locals addressed relative to ESP, and arguments relative to EBP
- bool interruptible; // intr. at all times (excluding prolog/epilog), not just call sites
-
- bool handlers; // has callable handlers
- bool localloc; // uses localloc
- bool editNcontinue; // has been compiled in EnC mode
- bool varargs; // is this a varargs routine
- bool profCallbacks; // does the method have Enter-Leave callbacks
- bool genericsContext;// has a reported generic context parameter
- bool genericsContextIsMethodDesc;// reported generic context parameter is methoddesc
- bool isSpeculativeStackWalk; // is the stackwalk seeded by an untrusted source (e.g., sampling profiler)?
-
- // These always includes EBP for EBP-frames and double-aligned-frames
- RegMask savedRegMask:8; // which callee-saved regs are saved on stack
-
- // Count of the callee-saved registers, excluding the frame pointer.
- // This does not include EBP for EBP-frames and double-aligned-frames.
- unsigned int savedRegsCountExclFP;
-
- unsigned int untrackedCnt;
- unsigned int varPtrTableSize;
- unsigned int argTabOffset; // INVALID_ARGTAB_OFFSET if argtab must be reached by stepping through ptr tables
- unsigned int gsCookieOffset; // INVALID_GS_COOKIE_OFFSET if there is no GuardStack cookie
-
- unsigned int syncStartOffset; // start/end code offset of the protected region in synchronized methods.
- unsigned int syncEndOffset; // INVALID_SYNC_OFFSET if there not synchronized method
- unsigned int syncEpilogStart; // The start of the epilog. Synchronized methods are guaranteed to have no more than one epilog.
- unsigned int revPInvokeOffset; // INVALID_REV_PINVOKE_OFFSET if there is no Reverse PInvoke frame
-
- enum { NOT_IN_PROLOG = -1, NOT_IN_EPILOG = -1 };
-
- int prologOffs; // NOT_IN_PROLOG if not in prolog
- int epilogOffs; // NOT_IN_EPILOG if not in epilog. It is never 0
-
- //
- // Results passed back from scanArgRegTable
- //
- regNum thisPtrResult; // register holding "this"
- RegMask regMaskResult; // registers currently holding GC ptrs
- RegMask iregMaskResult; // iptr qualifier for regMaskResult
- unsigned argHnumResult;
- PTR_CBYTE argTabResult; // Table of encoded offsets of pending ptr args
- unsigned argTabBytes; // Number of bytes in argTabResult[]
-
- // These next two are now large structs (i.e 132 bytes each)
-
- ptrArgTP argMaskResult; // pending arguments mask
- ptrArgTP iargMaskResult; // iptr qualifier for argMaskResult
-};
+#include "gc_unwind_x86.h"
/*****************************************************************************
How the stackwalkers buffer will be interpreted
@@ -756,6 +640,9 @@ struct CodeManStateBuf
DWORD hdrInfoSize;
hdrInfo hdrInfoBody;
};
+
+#endif
+
//*****************************************************************************
#endif // _EETWAIN_H
//*****************************************************************************
diff --git a/src/coreclr/inc/gc_unwind_x86.h b/src/coreclr/inc/gc_unwind_x86.h
new file mode 100644
index 00000000000000..e5be6b2e4aa43f
--- /dev/null
+++ b/src/coreclr/inc/gc_unwind_x86.h
@@ -0,0 +1,138 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#ifndef _UNWIND_X86_H
+#define _UNWIND_X86_H
+
+// This file is shared between CoreCLR and NativeAOT. Some of the differences are handled
+// with the FEATURE_NATIVEAOT and FEATURE_EH_FUNCLETS defines. There are three main methods
+// that are used by both runtimes - DecodeGCHdrInfo, UnwindStackFrameX86, and EnumGcRefsX86.
+//
+// The IN_EH_FUNCLETS and IN_EH_FUNCLETS_COMMA macros are used to specify some parameters
+// for the above methods that are specific for a certain runtime or configuration.
+#ifdef FEATURE_EH_FUNCLETS
+#define IN_EH_FUNCLETS(a) a
+#define IN_EH_FUNCLETS_COMMA(a) a,
+#else
+#define IN_EH_FUNCLETS(a)
+#define IN_EH_FUNCLETS_COMMA(a)
+#endif
+
+enum regNum
+{
+ REGI_EAX, REGI_ECX, REGI_EDX, REGI_EBX,
+ REGI_ESP, REGI_EBP, REGI_ESI, REGI_EDI,
+ REGI_COUNT,
+ REGI_NA = REGI_COUNT
+};
+
+/*****************************************************************************
+ Register masks
+ */
+
+enum RegMask
+{
+ RM_EAX = 0x01,
+ RM_ECX = 0x02,
+ RM_EDX = 0x04,
+ RM_EBX = 0x08,
+ RM_ESP = 0x10,
+ RM_EBP = 0x20,
+ RM_ESI = 0x40,
+ RM_EDI = 0x80,
+
+ RM_NONE = 0x00,
+ RM_ALL = (RM_EAX|RM_ECX|RM_EDX|RM_EBX|RM_ESP|RM_EBP|RM_ESI|RM_EDI),
+ RM_CALLEE_SAVED = (RM_EBP|RM_EBX|RM_ESI|RM_EDI),
+ RM_CALLEE_TRASHED = (RM_ALL & ~RM_CALLEE_SAVED),
+};
+
+/*****************************************************************************
+ *
+ * Helper to extract basic info from a method info block.
+ */
+
+struct hdrInfo
+{
+ unsigned int methodSize; // native code bytes
+ unsigned int argSize; // in bytes
+ unsigned int stackSize; // including callee saved registers
+ unsigned int rawStkSize; // excluding callee saved registers
+ ReturnKind returnKind; // The ReturnKind for this method.
+
+ unsigned int prologSize;
+
+ // Size of the epilogs in the method.
+ // For methods which use CEE_JMP, some epilogs may end with a "ret" instruction
+ // and some may end with a "jmp". The epilogSize reported should be for the
+ // epilog with the smallest size.
+ unsigned int epilogSize;
+
+ unsigned char epilogCnt;
+ bool epilogEnd; // is the epilog at the end of the method
+
+ bool ebpFrame; // locals and arguments addressed relative to EBP
+ bool doubleAlign; // is the stack double-aligned? locals addressed relative to ESP, and arguments relative to EBP
+ bool interruptible; // intr. at all times (excluding prolog/epilog), not just call sites
+
+ bool handlers; // has callable handlers
+ bool localloc; // uses localloc
+ bool editNcontinue; // has been compiled in EnC mode
+ bool varargs; // is this a varargs routine
+ bool profCallbacks; // does the method have Enter-Leave callbacks
+ bool genericsContext;// has a reported generic context parameter
+ bool genericsContextIsMethodDesc;// reported generic context parameter is methoddesc
+ bool isSpeculativeStackWalk; // is the stackwalk seeded by an untrusted source (e.g., sampling profiler)?
+
+ // These always includes EBP for EBP-frames and double-aligned-frames
+ RegMask savedRegMask:8; // which callee-saved regs are saved on stack
+
+ // Count of the callee-saved registers, excluding the frame pointer.
+ // This does not include EBP for EBP-frames and double-aligned-frames.
+ unsigned int savedRegsCountExclFP;
+
+ unsigned int untrackedCnt;
+ unsigned int varPtrTableSize;
+ unsigned int argTabOffset; // INVALID_ARGTAB_OFFSET if argtab must be reached by stepping through ptr tables
+ unsigned int gsCookieOffset; // INVALID_GS_COOKIE_OFFSET if there is no GuardStack cookie
+
+ unsigned int syncStartOffset; // start/end code offset of the protected region in synchronized methods.
+ unsigned int syncEndOffset; // INVALID_SYNC_OFFSET if there not synchronized method
+ unsigned int syncEpilogStart; // The start of the epilog. Synchronized methods are guaranteed to have no more than one epilog.
+ unsigned int revPInvokeOffset; // INVALID_REV_PINVOKE_OFFSET if there is no Reverse PInvoke frame
+
+ enum { NOT_IN_PROLOG = -1, NOT_IN_EPILOG = -1 };
+
+ int prologOffs; // NOT_IN_PROLOG if not in prolog
+ int epilogOffs; // NOT_IN_EPILOG if not in epilog. It is never 0
+
+ //
+ // Results passed back from scanArgRegTable
+ //
+ regNum thisPtrResult; // register holding "this"
+ RegMask regMaskResult; // registers currently holding GC ptrs
+ RegMask iregMaskResult; // iptr qualifier for regMaskResult
+ unsigned argHnumResult;
+ PTR_CBYTE argTabResult; // Table of encoded offsets of pending ptr args
+ unsigned argTabBytes; // Number of bytes in argTabResult[]
+
+ // These next two are now large structs (i.e 132 bytes each)
+
+ ptrArgTP argMaskResult; // pending arguments mask
+ ptrArgTP iargMaskResult; // iptr qualifier for argMaskResult
+};
+
+bool UnwindStackFrameX86(PREGDISPLAY pContext,
+ PTR_CBYTE methodStart,
+ DWORD curOffs,
+ hdrInfo * info,
+ PTR_CBYTE table,
+ IN_EH_FUNCLETS_COMMA(PTR_CBYTE funcletStart)
+ IN_EH_FUNCLETS_COMMA(bool isFunclet)
+ bool updateAllRegs);
+
+size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken,
+ unsigned curOffset,
+ hdrInfo * infoPtr);
+
+#endif // _UNWIND_X86_H
diff --git a/src/coreclr/inc/gcinfo.h b/src/coreclr/inc/gcinfo.h
index 2a6ba1914f0b31..f334b099f2578e 100644
--- a/src/coreclr/inc/gcinfo.h
+++ b/src/coreclr/inc/gcinfo.h
@@ -13,7 +13,6 @@
/*****************************************************************************/
#include "daccess.h"
-#include "windef.h" // For BYTE
// Use the lower 2 bits of the offsets stored in the tables
// to encode properties
@@ -56,9 +55,17 @@ const unsigned this_OFFSET_FLAG = 0x2; // the offset is "this"
struct GCInfoToken
{
PTR_VOID Info;
- UINT32 Version;
+ uint32_t Version;
- static UINT32 ReadyToRunVersionToGcInfoVersion(UINT32 readyToRunMajorVersion)
+#ifdef FEATURE_NATIVEAOT
+ GCInfoToken(PTR_VOID info)
+ {
+ Info = info;
+ Version = GCINFO_VERSION;
+ }
+#endif
+
+ static uint32_t ReadyToRunVersionToGcInfoVersion(uint32_t readyToRunMajorVersion)
{
// GcInfo version is current from ReadyToRun version 2.0
return GCINFO_VERSION;
diff --git a/src/coreclr/inc/gcinfodecoder.h b/src/coreclr/inc/gcinfodecoder.h
index 34af8c53055687..eb60728af5b1f7 100644
--- a/src/coreclr/inc/gcinfodecoder.h
+++ b/src/coreclr/inc/gcinfodecoder.h
@@ -31,7 +31,17 @@
#ifdef FEATURE_NATIVEAOT
+#include "gcinfo.h"
+
typedef ArrayDPTR(const uint8_t) PTR_CBYTE;
+#ifdef TARGET_X86
+// Bridge few additional pointer types used in x86 unwinding code
+typedef DPTR(DWORD) PTR_DWORD;
+typedef DPTR(WORD) PTR_WORD;
+typedef DPTR(BYTE) PTR_BYTE;
+typedef DPTR(signed char) PTR_SBYTE;
+typedef DPTR(INT32) PTR_INT32;
+#endif
#define LIMITED_METHOD_CONTRACT
#define SUPPORTS_DAC
@@ -50,22 +60,12 @@ typedef ArrayDPTR(const uint8_t) PTR_CBYTE;
#define SSIZE_T intptr_t
#define LPVOID void*
+#define CHECK_APP_DOMAIN 0
+
typedef void * OBJECTREF;
#define GET_CALLER_SP(pREGDISPLAY) ((TADDR)0)
-struct GCInfoToken
-{
- PTR_VOID Info;
- UINT32 Version;
-
- GCInfoToken(PTR_VOID info)
- {
- Info = info;
- Version = 2;
- }
-};
-
#else // FEATURE_NATIVEAOT
// Stuff from cgencpu.h:
@@ -179,6 +179,7 @@ enum ICodeManagerFlags
ExecutionAborted = 0x0002, // execution of this function has been aborted
// (i.e. it will not continue execution at the
// current location)
+ AbortingCall = 0x0004, // The current call will never return
ParentOfFuncletStackFrame
= 0x0040, // A funclet for this frame was previously reported
diff --git a/src/coreclr/inc/regdisp.h b/src/coreclr/inc/regdisp.h
index 6bfa083beddae6..ec47b9019dbc02 100644
--- a/src/coreclr/inc/regdisp.h
+++ b/src/coreclr/inc/regdisp.h
@@ -131,6 +131,12 @@ inline LPVOID GetRegdisplayFPAddress(REGDISPLAY *display) {
return (LPVOID)display->GetEbpLocation();
}
+inline void SetRegdisplayPCTAddr(REGDISPLAY *display, TADDR addr)
+{
+ display->PCTAddr = addr;
+ display->ControlPC = *PTR_PCODE(addr);
+}
+
// This function tells us if the given stack pointer is in one of the frames of the functions called by the given frame
inline BOOL IsInCalleesFrames(REGDISPLAY *display, LPVOID stackPointer) {
diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
index 63da57ba78c3c7..6ab3377f5e5d10 100644
--- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
+++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
@@ -41,9 +41,6 @@ EXTERN_C CODE_LOCATION ReturnFromUniversalTransition;
EXTERN_C CODE_LOCATION ReturnFromUniversalTransition_DebugStepTailCall;
#endif
-#ifdef TARGET_X86
-EXTERN_C CODE_LOCATION RhpCallFunclet2;
-#endif
EXTERN_C CODE_LOCATION RhpCallCatchFunclet2;
EXTERN_C CODE_LOCATION RhpCallFinallyFunclet2;
EXTERN_C CODE_LOCATION RhpCallFilterFunclet2;
@@ -779,20 +776,6 @@ void StackFrameIterator::UnwindFuncletInvokeThunk()
PTR_uintptr_t SP;
-#ifdef TARGET_X86
- // First, unwind RhpCallFunclet
- SP = (PTR_uintptr_t)(m_RegDisplay.SP + 0x4); // skip the saved assembly-routine-EBP
- m_RegDisplay.SetIP(*SP++);
- m_RegDisplay.SetSP((uintptr_t)dac_cast(SP));
- SetControlPC(dac_cast(m_RegDisplay.GetIP()));
-
- ASSERT(
- EQUALS_RETURN_ADDRESS(m_ControlPC, RhpCallCatchFunclet2) ||
- EQUALS_RETURN_ADDRESS(m_ControlPC, RhpCallFinallyFunclet2) ||
- EQUALS_RETURN_ADDRESS(m_ControlPC, RhpCallFilterFunclet2)
- );
-#endif
-
bool isFilterInvoke = EQUALS_RETURN_ADDRESS(m_ControlPC, RhpCallFilterFunclet2);
#if defined(UNIX_AMD64_ABI)
@@ -876,7 +859,7 @@ void StackFrameIterator::UnwindFuncletInvokeThunk()
m_RegDisplay.pR15 = SP++;
#elif defined(TARGET_X86)
- SP = (PTR_uintptr_t)(m_RegDisplay.SP);
+ SP = (PTR_uintptr_t)(m_RegDisplay.SP + 0x4); // skip the saved assembly-routine-EBP
if (!isFilterInvoke)
{
@@ -1809,25 +1792,6 @@ StackFrameIterator::ReturnAddressCategory StackFrameIterator::CategorizeUnadjust
return InThrowSiteThunk;
}
-#ifdef TARGET_X86
- if (EQUALS_RETURN_ADDRESS(returnAddress, RhpCallFunclet2))
- {
- PORTABILITY_ASSERT("CategorizeUnadjustedReturnAddress");
-#if 0
- // See if it is a filter funclet based on the caller of RhpCallFunclet
- PTR_uintptr_t SP = (PTR_uintptr_t)(m_RegDisplay.SP + 0x4); // skip the saved assembly-routine-EBP
- PTR_uintptr_t ControlPC = *SP++;
- if (EQUALS_RETURN_ADDRESS(ControlPC, RhpCallFilterFunclet2))
- {
- return InFilterFuncletInvokeThunk;
- }
- else
-#endif
- {
- return InFuncletInvokeThunk;
- }
- }
-#else // TARGET_X86
if (EQUALS_RETURN_ADDRESS(returnAddress, RhpCallCatchFunclet2) ||
EQUALS_RETURN_ADDRESS(returnAddress, RhpCallFinallyFunclet2))
{
@@ -1838,7 +1802,6 @@ StackFrameIterator::ReturnAddressCategory StackFrameIterator::CategorizeUnadjust
{
return InFilterFuncletInvokeThunk;
}
-#endif // TARGET_X86
return InManagedCode;
#endif // defined(USE_PORTABLE_HELPERS)
}
diff --git a/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc b/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc
index b7f6554993cd19..7e147cad9c1316 100644
--- a/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc
+++ b/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc
@@ -134,7 +134,7 @@ PTFF_SAVE_RAX equ 00000100h ;; RAX is saved if it contains a GC ref
PTFF_SAVE_ALL_SCRATCH equ 00000700h
PTFF_RAX_IS_GCREF equ 00010000h ;; iff PTFF_SAVE_RAX: set -> eax is Object, clear -> eax is scalar
PTFF_RAX_IS_BYREF equ 00020000h ;; iff PTFF_SAVE_RAX: set -> eax is ByRef, clear -> eax is Object or scalar
-PTFF_THREAD_ABORT equ 00040000h ;; indicates that ThreadAbortException should be thrown when returning from the transition
+PTFF_THREAD_ABORT equ 00100000h ;; indicates that ThreadAbortException should be thrown when returning from the transition
;; These must match the TrapThreadsFlags enum
TrapThreadsFlags_None equ 0
@@ -163,6 +163,8 @@ G_EPHEMERAL_HIGH equ _g_ephemeral_high
G_CARD_TABLE equ _g_card_table
RhpWaitForGC2 equ @RhpWaitForGC2@4
RhpTrapThreads equ _RhpTrapThreads
+RhpStressGc equ @RhpStressGc@0
+RhpGcPoll2 equ @RhpGcPoll2@4
ifdef FEATURE_GC_STRESS
THREAD__HIJACKFORGCSTRESS equ ?HijackForGcStress@Thread@@SGXPAUPAL_LIMITED_CONTEXT@@@Z
@@ -178,6 +180,7 @@ EXTERN RhExceptionHandling_FailedAllocation : PROC
EXTERN RhThrowHwEx : PROC
EXTERN RhThrowEx : PROC
EXTERN RhRethrow : PROC
+EXTERN RhpGcPoll2 : PROC
ifdef FEATURE_GC_STRESS
EXTERN THREAD__HIJACKFORGCSTRESS : PROC
diff --git a/src/coreclr/nativeaot/Runtime/i386/AsmOffsetsCpu.h b/src/coreclr/nativeaot/Runtime/i386/AsmOffsetsCpu.h
index 7e19e77b7e885c..ad428db1250cef 100644
--- a/src/coreclr/nativeaot/Runtime/i386/AsmOffsetsCpu.h
+++ b/src/coreclr/nativeaot/Runtime/i386/AsmOffsetsCpu.h
@@ -7,7 +7,7 @@
//
// NOTE: the offsets MUST be in hex notation WITHOUT the 0x prefix
-PLAT_ASM_SIZEOF(bc, ExInfo)
+PLAT_ASM_SIZEOF(c0, ExInfo)
PLAT_ASM_OFFSET(0, ExInfo, m_pPrevExInfo)
PLAT_ASM_OFFSET(4, ExInfo, m_pExContext)
PLAT_ASM_OFFSET(8, ExInfo, m_exception)
@@ -15,7 +15,7 @@ PLAT_ASM_OFFSET(0c, ExInfo, m_kind)
PLAT_ASM_OFFSET(0d, ExInfo, m_passNumber)
PLAT_ASM_OFFSET(10, ExInfo, m_idxCurClause)
PLAT_ASM_OFFSET(14, ExInfo, m_frameIter)
-PLAT_ASM_OFFSET(b8, ExInfo, m_notifyDebuggerSP)
+PLAT_ASM_OFFSET(bc, ExInfo, m_notifyDebuggerSP)
PLAT_ASM_OFFSET(0, PInvokeTransitionFrame, m_RIP)
PLAT_ASM_OFFSET(4, PInvokeTransitionFrame, m_FramePointer)
@@ -23,12 +23,12 @@ PLAT_ASM_OFFSET(8, PInvokeTransitionFrame, m_pThread)
PLAT_ASM_OFFSET(0c, PInvokeTransitionFrame, m_Flags)
PLAT_ASM_OFFSET(10, PInvokeTransitionFrame, m_PreservedRegs)
-PLAT_ASM_SIZEOF(a4, StackFrameIterator)
+PLAT_ASM_SIZEOF(a8, StackFrameIterator)
PLAT_ASM_OFFSET(08, StackFrameIterator, m_FramePointer)
PLAT_ASM_OFFSET(0c, StackFrameIterator, m_ControlPC)
PLAT_ASM_OFFSET(10, StackFrameIterator, m_RegDisplay)
-PLAT_ASM_OFFSET(9c, StackFrameIterator, m_OriginalControlPC)
-PLAT_ASM_OFFSET(a0, StackFrameIterator, m_pPreviousTransitionFrame)
+PLAT_ASM_OFFSET(a0, StackFrameIterator, m_OriginalControlPC)
+PLAT_ASM_OFFSET(a4, StackFrameIterator, m_pPreviousTransitionFrame)
PLAT_ASM_SIZEOF(1c, PAL_LIMITED_CONTEXT)
PLAT_ASM_OFFSET(0, PAL_LIMITED_CONTEXT, IP)
@@ -40,7 +40,7 @@ PLAT_ASM_OFFSET(10, PAL_LIMITED_CONTEXT, Rsi)
PLAT_ASM_OFFSET(14, PAL_LIMITED_CONTEXT, Rax)
PLAT_ASM_OFFSET(18, PAL_LIMITED_CONTEXT, Rbx)
-PLAT_ASM_SIZEOF(24, REGDISPLAY)
+PLAT_ASM_SIZEOF(28, REGDISPLAY)
PLAT_ASM_OFFSET(1c, REGDISPLAY, SP)
PLAT_ASM_OFFSET(0c, REGDISPLAY, pRbx)
diff --git a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm
index 127c1b617b8f81..2172d3982d1821 100644
--- a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm
+++ b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm
@@ -10,10 +10,18 @@
include AsmMacros.inc
-RhpCallFunclet equ @RhpCallFunclet@0
-RhpThrowHwEx equ @RhpThrowHwEx@0
+;; input: ECX: possible exception object
+;; EDX: funclet IP
+;; EAX: funclet EBP
+CALL_FUNCLET macro SUFFIX
+ push ebp
+ mov ebp, eax
+ mov eax, ecx
+ call edx
+ALTERNATE_ENTRY _RhpCall&SUFFIX&Funclet2
+ pop ebp
+endm
-extern RhpCallFunclet : proc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
@@ -25,7 +33,7 @@ extern RhpCallFunclet : proc
;; OUTPUT:
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-FASTCALL_FUNC RhpThrowHwEx, 0
+FASTCALL_FUNC RhpThrowHwEx, 8
esp_offsetof_ExInfo textequ %0
esp_offsetof_Context textequ %SIZEOF__ExInfo
@@ -74,7 +82,7 @@ FASTCALL_FUNC RhpThrowHwEx, 0
;; edx contains the address of the ExInfo
call RhThrowHwEx
-ALTERNATE_ENTRY RhpThrowHwEx2
+ALTERNATE_ENTRY _RhpThrowHwEx2
;; no return
int 3
@@ -90,7 +98,7 @@ FASTCALL_ENDFUNC
;; OUTPUT:
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-FASTCALL_FUNC RhpThrowEx, 0
+FASTCALL_FUNC RhpThrowEx, 4
esp_offsetof_ExInfo textequ %0
esp_offsetof_Context textequ %SIZEOF__ExInfo
@@ -151,7 +159,7 @@ FASTCALL_FUNC RhpThrowEx, 0
;; edx contains the address of the ExInfo
call RhThrowEx
-ALTERNATE_ENTRY RhpThrowEx2
+ALTERNATE_ENTRY _RhpThrowEx2
;; no return
int 3
@@ -171,7 +179,6 @@ FASTCALL_ENDFUNC
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FASTCALL_FUNC RhpRethrow, 0
-
esp_offsetof_ExInfo textequ %0
esp_offsetof_Context textequ %SIZEOF__ExInfo
@@ -266,13 +273,14 @@ endm
;;
;; INPUT: ECX: exception object
;; EDX: handler funclet address
-;; [ESP + 4]: REGDISPLAY*
-;; [ESP + 8]: ExInfo*
+;; [ESP + 4]: ExInfo*
+;; [ESP + 8]: REGDISPLAY*
+;; (CLR calling convention switches the last two parameters!)
;;
;; OUTPUT:
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-FASTCALL_FUNC RhpCallCatchFunclet, 0
+FASTCALL_FUNC RhpCallCatchFunclet, 16
FUNCLET_CALL_PROLOGUE 2
@@ -283,8 +291,8 @@ FASTCALL_FUNC RhpCallCatchFunclet, 0
;; [esp + 10h]: ebx save
esp_offsetof_PrevEBP textequ %14h ;; [esp + 14h]: prev ebp
esp_offsetof_RetAddr textequ %18h ;; [esp + 18h]: return address
- esp_offsetof_RegDisplay textequ %1ch ;; [esp + 1Ch]: REGDISPLAY*
- esp_offsetof_ExInfo textequ %20h ;; [esp + 20h]: ExInfo*
+ esp_offsetof_RegDisplay textequ %20h ;; [esp + 20h]: REGDISPLAY*
+ esp_offsetof_ExInfo textequ %1ch ;; [esp + 1ch]: ExInfo*
;; Clear the DoNotTriggerGc state before calling out to our managed catch funclet.
INLINE_GETTHREAD eax, ebx ;; eax <- Thread*, ebx is trashed
@@ -313,9 +321,7 @@ FASTCALL_FUNC RhpCallCatchFunclet, 0
;; ECX still contains the exception object
;; EDX: funclet IP
;; EAX: funclet EBP
- call RhpCallFunclet
-
-ALTERNATE_ENTRY RhpCallCatchFunclet2
+ CALL_FUNCLET Catch
;; eax: resume IP
mov [esp + esp_offsetof_ResumeIP], eax ;; save for later
@@ -379,7 +385,7 @@ FASTCALL_ENDFUNC
;; OUTPUT:
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-FASTCALL_FUNC RhpCallFinallyFunclet, 0
+FASTCALL_FUNC RhpCallFinallyFunclet, 8
FUNCLET_CALL_PROLOGUE 0
@@ -409,9 +415,7 @@ FASTCALL_FUNC RhpCallFinallyFunclet, 0
;; ECX: not used
;; EDX: funclet IP
;; EAX: funclet EBP
- call RhpCallFunclet
-
-ALTERNATE_ENTRY RhpCallFinallyFunclet2
+ CALL_FUNCLET Finally
pop edx ;; restore REGDISPLAY*
@@ -446,7 +450,7 @@ FASTCALL_ENDFUNC
;; OUTPUT:
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-FASTCALL_FUNC RhpCallFilterFunclet, 0
+FASTCALL_FUNC RhpCallFilterFunclet, 12
FUNCLET_CALL_PROLOGUE 0
@@ -463,9 +467,7 @@ FASTCALL_FUNC RhpCallFilterFunclet, 0
;; EAX contains the funclet EBP value
mov edx, [esp + 0] ;; reload filter funclet address
- call RhpCallFunclet
-
-ALTERNATE_ENTRY RhpCallFilterFunclet2
+ CALL_FUNCLET Filter
;; EAX contains the result of the filter execution
mov edx, [ebp + 8]
diff --git a/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm b/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm
index b5876f059f6a40..7e2715d3dd7685 100644
--- a/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm
+++ b/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm
@@ -11,8 +11,6 @@
include AsmMacros.inc
DEFAULT_PROBE_SAVE_FLAGS equ PTFF_SAVE_ALL_PRESERVED + PTFF_SAVE_RSP
-PROBE_SAVE_FLAGS_EVERYTHING equ DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_ALL_SCRATCH
-PROBE_SAVE_FLAGS_RAX_IS_GCREF equ DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX + PTFF_RAX_IS_GCREF
;;
;; The prolog for all GC suspension hijackes (normal and stress). Sets up an EBP frame,
@@ -25,7 +23,7 @@ PROBE_SAVE_FLAGS_RAX_IS_GCREF equ DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX + P
;; EAX: not trashed or saved
;; EBP: new EBP frame with correct return address
;; ESP: points to saved scratch registers (ECX & EDX)
-;; ECX: trashed
+;; ECX: return value flags
;; EDX: thread pointer
;;
HijackFixupProlog macro
@@ -44,11 +42,15 @@ HijackFixupProlog macro
mov ecx, [edx + OFFSETOF__Thread__m_pvHijackedReturnAddress]
mov [ebp + 4], ecx
+ ;; Fetch the return address flags
+ mov ecx, [edx + OFFSETOF__Thread__m_uHijackedReturnValueFlags]
+
;;
;; Clear hijack state
;;
mov dword ptr [edx + OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation], 0
mov dword ptr [edx + OFFSETOF__Thread__m_pvHijackedReturnAddress], 0
+ mov dword ptr [edx + OFFSETOF__Thread__m_uHijackedReturnValueFlags], 0
endm
@@ -136,7 +138,7 @@ PopProbeFrame macro
pop eax
endm
-RhpThrowHwEx equ @RhpThrowHwEx@0
+RhpThrowHwEx equ @RhpThrowHwEx@8
extern RhpThrowHwEx : proc
;;
@@ -179,6 +181,25 @@ Abort:
RhpWaitForGC endp
+RhpGcPoll proc
+ cmp [RhpTrapThreads], TrapThreadsFlags_None
+ jne @F ; forward branch - predicted not taken
+ ret
+@@:
+ jmp RhpGcPollRare
+
+RhpGcPoll endp
+
+RhpGcPollRare proc
+ push ebp
+ mov ebp, esp
+ PUSH_COOP_PINVOKE_FRAME ecx
+ call RhpGcPoll2
+ POP_COOP_PINVOKE_FRAME
+ pop ebp
+ ret
+RhpGcPollRare endp
+
ifdef FEATURE_GC_STRESS
;;
;; Set the Thread state and invoke RhpStressGC().
@@ -237,7 +258,7 @@ FASTCALL_FUNC RhpGcProbeHijack, 0
HijackFixupEpilog
WaitForGC:
- mov ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX
+ or ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX
jmp RhpWaitForGC
FASTCALL_ENDFUNC
@@ -246,7 +267,7 @@ ifdef FEATURE_GC_STRESS
FASTCALL_FUNC RhpGcStressHijack, 0
HijackFixupProlog
- mov ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX
+ or ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX
jmp RhpGcStressProbe
FASTCALL_ENDFUNC
diff --git a/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm b/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm
index 246f4297900646..953ea11b74de6d 100644
--- a/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm
+++ b/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm
@@ -99,15 +99,16 @@ DEFINE_WRITE_BARRIER macro DESTREG, REFREG
;; Define a helper with a name of the form RhpAssignRefEAX etc. (along with suitable calling standard
;; decoration). The location to be updated is in DESTREG. The object reference that will be assigned into that
;; location is in one of the other general registers determined by the value of REFREG.
-FASTCALL_FUNC RhpAssignRef&REFREG&, 0
+FASTCALL_FUNC RhpAssignRef&REFREG&, 8
;; Export the canonical write barrier under unqualified name as well
ifidni ,
- @RhpAssignRef@0 label proc
- PUBLIC @RhpAssignRef@0
- ALTERNATE_ENTRY RhpAssignRefAVLocation
+ ALTERNATE_ENTRY RhpAssignRef
+ ALTERNATE_ENTRY _RhpAssignRefAVLocation
endif
+ ALTERNATE_ENTRY _RhpAssignRef&REFREG&AVLocation
+
;; Write the reference into the location. Note that we rely on the fact that no GC can occur between here
;; and the card table update we may perform below.
mov dword ptr [DESTREG], REFREG
@@ -196,15 +197,16 @@ DEFINE_CHECKED_WRITE_BARRIER macro DESTREG, REFREG
;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular:
;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen on the first instruction
;; - Function "UnwindSimpleHelperToCaller" assumes the stack contains just the pushed return address
-FASTCALL_FUNC RhpCheckedAssignRef&REFREG&, 0
+FASTCALL_FUNC RhpCheckedAssignRef&REFREG&, 8
;; Export the canonical write barrier under unqualified name as well
ifidni ,
- @RhpCheckedAssignRef@0 label proc
- PUBLIC @RhpCheckedAssignRef@0
- ALTERNATE_ENTRY RhpCheckedAssignRefAVLocation
+ ALTERNATE_ENTRY RhpCheckedAssignRef
+ ALTERNATE_ENTRY _RhpCheckedAssignRefAVLocation
endif
+ ALTERNATE_ENTRY _RhpCheckedAssignRef&REFREG&AVLocation
+
;; Write the reference into the location. Note that we rely on the fact that no GC can occur between here
;; and the card table update we may perform below.
mov dword ptr [DESTREG], REFREG
@@ -238,29 +240,76 @@ DEFINE_CHECKED_WRITE_BARRIER EDX, EBP
;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular:
;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen at @RhpCheckedLockCmpXchgAVLocation@0
;; - Function "UnwindSimpleHelperToCaller" assumes the stack contains just the pushed return address
-;; pass third argument in EAX
-FASTCALL_FUNC RhpCheckedLockCmpXchg
-ALTERNATE_ENTRY RhpCheckedLockCmpXchgAVLocation
+FASTCALL_FUNC RhpCheckedLockCmpXchg, 12
+ mov eax, [esp+4]
+ALTERNATE_ENTRY _RhpCheckedLockCmpXchgAVLocation
lock cmpxchg [ecx], edx
- jne RhpCheckedLockCmpXchg_NoBarrierRequired_ECX_EDX
+ jne RhpCheckedLockCmpXchg_NoBarrierRequired_ECX_EDX
- DEFINE_CHECKED_WRITE_BARRIER_CORE RhpCheckedLockCmpXchg, ECX, EDX, ret
+ DEFINE_CHECKED_WRITE_BARRIER_CORE RhpCheckedLockCmpXchg, ECX, EDX, ret 4
FASTCALL_ENDFUNC
;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular:
;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen at @RhpCheckedXchgAVLocation@0
;; - Function "UnwindSimpleHelperToCaller" assumes the stack contains just the pushed return address
-FASTCALL_FUNC RhpCheckedXchg, 0
+FASTCALL_FUNC RhpCheckedXchg, 8
;; Setup eax with the new object for the exchange, that way it will automatically hold the correct result
;; afterwards and we can leave edx unaltered ready for the GC write barrier below.
mov eax, edx
-ALTERNATE_ENTRY RhpCheckedXchgAVLocation
+ALTERNATE_ENTRY _RhpCheckedXchgAVLocation
xchg [ecx], eax
DEFINE_CHECKED_WRITE_BARRIER_CORE RhpCheckedXchg, ECX, EDX, ret
FASTCALL_ENDFUNC
+;;
+;; RhpByRefAssignRef simulates movs instruction for object references.
+;;
+;; On entry:
+;; edi: address of ref-field (assigned to)
+;; esi: address of the data (source)
+;;
+;; On exit:
+;; edi, esi are incremented by 4,
+;; ecx: trashed
+;;
+FASTCALL_FUNC RhpByRefAssignRef, 8
+ALTERNATE_ENTRY _RhpByRefAssignRefAVLocation1
+ mov ecx, [esi]
+ALTERNATE_ENTRY _RhpByRefAssignRefAVLocation2
+ mov [edi], ecx
+
+ ;; Check whether the writes were even into the heap. If not there's no card update required.
+ cmp edi, [G_LOWEST_ADDRESS]
+ jb RhpByRefAssignRef_NoBarrierRequired
+ cmp edi, [G_HIGHEST_ADDRESS]
+ jae RhpByRefAssignRef_NoBarrierRequired
+
+ UPDATE_GC_SHADOW BASENAME, ecx, edi
+
+ ;; If the reference is to an object that's not in an ephemeral generation we have no need to track it
+ ;; (since the object won't be collected or moved by an ephemeral collection).
+ cmp ecx, [G_EPHEMERAL_LOW]
+ jb RhpByRefAssignRef_NoBarrierRequired
+ cmp ecx, [G_EPHEMERAL_HIGH]
+ jae RhpByRefAssignRef_NoBarrierRequired
+
+ mov ecx, edi
+ shr ecx, 10
+ add ecx, [G_CARD_TABLE]
+ cmp byte ptr [ecx], 0FFh
+ je RhpByRefAssignRef_NoBarrierRequired
+
+ mov byte ptr [ecx], 0FFh
+
+RhpByRefAssignRef_NoBarrierRequired:
+ ;; Increment the pointers before leaving
+ add esi,4
+ add edi,4
+ ret
+FASTCALL_ENDFUNC
+
end
diff --git a/src/coreclr/nativeaot/Runtime/regdisplay.h b/src/coreclr/nativeaot/Runtime/regdisplay.h
index b9c0175210578a..d5082fb9efbe74 100644
--- a/src/coreclr/nativeaot/Runtime/regdisplay.h
+++ b/src/coreclr/nativeaot/Runtime/regdisplay.h
@@ -46,8 +46,41 @@ struct REGDISPLAY
inline void SetIP(PCODE IP) { this->IP = IP; }
inline void SetSP(uintptr_t SP) { this->SP = SP; }
+
+#ifdef TARGET_X86
+ TADDR PCTAddr;
+
+ inline unsigned long *GetEaxLocation() { return (unsigned long *)pRax; }
+ inline unsigned long *GetEcxLocation() { return (unsigned long *)pRcx; }
+ inline unsigned long *GetEdxLocation() { return (unsigned long *)pRdx; }
+ inline unsigned long *GetEbpLocation() { return (unsigned long *)pRbp; }
+ inline unsigned long *GetEbxLocation() { return (unsigned long *)pRbx; }
+ inline unsigned long *GetEsiLocation() { return (unsigned long *)pRsi; }
+ inline unsigned long *GetEdiLocation() { return (unsigned long *)pRdi; }
+
+ inline void SetEaxLocation(unsigned long *loc) { pRax = (PTR_uintptr_t)loc; }
+ inline void SetEcxLocation(unsigned long *loc) { pRcx = (PTR_uintptr_t)loc; }
+ inline void SetEdxLocation(unsigned long *loc) { pRdx = (PTR_uintptr_t)loc; }
+ inline void SetEbxLocation(unsigned long *loc) { pRbx = (PTR_uintptr_t)loc; }
+ inline void SetEsiLocation(unsigned long *loc) { pRsi = (PTR_uintptr_t)loc; }
+ inline void SetEdiLocation(unsigned long *loc) { pRdi = (PTR_uintptr_t)loc; }
+ inline void SetEbpLocation(unsigned long *loc) { pRbp = (PTR_uintptr_t)loc; }
+#endif
};
+#ifdef TARGET_X86
+inline TADDR GetRegdisplayFP(REGDISPLAY *display)
+{
+ return (TADDR)*display->GetEbpLocation();
+}
+
+inline void SetRegdisplayPCTAddr(REGDISPLAY *display, TADDR addr)
+{
+ display->PCTAddr = addr;
+ display->SetIP(*PTR_PCODE(addr));
+}
+#endif
+
#elif defined(TARGET_ARM)
struct REGDISPLAY
diff --git a/src/coreclr/nativeaot/Runtime/rhassert.h b/src/coreclr/nativeaot/Runtime/rhassert.h
index ecf0297980ae19..34403e216f5b16 100644
--- a/src/coreclr/nativeaot/Runtime/rhassert.h
+++ b/src/coreclr/nativeaot/Runtime/rhassert.h
@@ -44,6 +44,10 @@ void Assert(const char * expr, const char * file, unsigned int line_num, const c
#define _ASSERTE(_expr) ASSERT(_expr)
#endif
+#ifndef _ASSERTE_ALL_BUILDS
+#define _ASSERTE_ALL_BUILDS(_expr) ASSERT(_expr)
+#endif
+
#define PORTABILITY_ASSERT(message) \
ASSERT_UNCONDITIONALLY(message); \
ASSUME(0); \
diff --git a/src/coreclr/nativeaot/Runtime/threadstore.cpp b/src/coreclr/nativeaot/Runtime/threadstore.cpp
index 6ec8a44ed2e7bd..8357bca1051574 100644
--- a/src/coreclr/nativeaot/Runtime/threadstore.cpp
+++ b/src/coreclr/nativeaot/Runtime/threadstore.cpp
@@ -477,7 +477,6 @@ GVAL_IMPL(uint32_t, SECTIONREL__tls_CurrentThread);
//
// This routine supports the !Thread debugger extension routine
//
-typedef DPTR(TADDR) PTR_TADDR;
// static
PTR_Thread ThreadStore::GetThreadFromTEB(TADDR pTEB)
{
diff --git a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp
index 6bb6cbcf00945c..1ff934919fd199 100644
--- a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp
+++ b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp
@@ -19,6 +19,23 @@
#define GCINFODECODER_NO_EE
#include "gcinfodecoder.cpp"
+#ifdef TARGET_X86
+#define FEATURE_EH_FUNCLETS
+
+// Disable contracts
+#define LIMITED_METHOD_CONTRACT
+#define LIMITED_METHOD_DAC_CONTRACT
+#define CONTRACTL
+#define CONTRACTL_END
+#define NOTHROW
+#define GC_NOTRIGGER
+#define HOST_NOCALLS
+
+#include "../../inc/gcdecoder.cpp"
+#include "../../inc/gc_unwind_x86.h"
+#include "../../vm/gc_unwind_x86.inl"
+#endif
+
#define UBF_FUNC_KIND_MASK 0x03
#define UBF_FUNC_KIND_ROOT 0x00
#define UBF_FUNC_KIND_HANDLER 0x01
@@ -351,7 +368,6 @@ uint32_t CoffNativeCodeManager::GetCodeOffset(MethodInfo* pMethodInfo, PTR_VOID
bool CoffNativeCodeManager::IsSafePoint(PTR_VOID pvAddress)
{
-#ifdef USE_GC_INFO_DECODER
MethodInfo pMethodInfo;
if (!FindMethodInfo(pvAddress, &pMethodInfo))
{
@@ -361,6 +377,7 @@ bool CoffNativeCodeManager::IsSafePoint(PTR_VOID pvAddress)
PTR_uint8_t gcInfo;
uint32_t codeOffset = GetCodeOffset(&pMethodInfo, pvAddress, &gcInfo);
+#ifdef USE_GC_INFO_DECODER
GcInfoDecoder decoder(
GCInfoToken(gcInfo),
GcInfoDecoderFlags(DECODE_INTERRUPTIBILITY),
@@ -369,9 +386,11 @@ bool CoffNativeCodeManager::IsSafePoint(PTR_VOID pvAddress)
return decoder.IsInterruptible();
#else
- // x86 has custom GC info, see DecodeGCHdrInfo in eetwain.cpp
- PORTABILITY_ASSERT("IsSafePoint");
- RhFailFast();
+ // Extract the necessary information from the info block header
+ hdrInfo info;
+ DecodeGCHdrInfo(GCInfoToken(gcInfo), codeOffset, &info);
+
+ return info.interruptible && info.prologOffs == hdrInfo::NOT_IN_PROLOG && info.epilogOffs == hdrInfo::NOT_IN_EPILOG;
#endif
}
@@ -381,10 +400,20 @@ void CoffNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo,
GCEnumContext * hCallback,
bool isActiveStackFrame)
{
-#ifdef USE_GC_INFO_DECODER
PTR_uint8_t gcInfo;
uint32_t codeOffset = GetCodeOffset(pMethodInfo, safePointAddress, &gcInfo);
+ ICodeManagerFlags flags = (ICodeManagerFlags)0;
+ if (((CoffNativeMethodInfo *)pMethodInfo)->executionAborted)
+ flags = ICodeManagerFlags::ExecutionAborted;
+
+ if (IsFilter(pMethodInfo))
+ flags = (ICodeManagerFlags)(flags | ICodeManagerFlags::NoReportUntracked);
+
+ if (isActiveStackFrame)
+ flags = (ICodeManagerFlags)(flags | ICodeManagerFlags::ActiveStackFrame);
+
+#ifdef USE_GC_INFO_DECODER
if (!isActiveStackFrame)
{
// If we are not in the active method, we are currently pointing
@@ -402,16 +431,6 @@ void CoffNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo,
codeOffset
);
- ICodeManagerFlags flags = (ICodeManagerFlags)0;
- if (((CoffNativeMethodInfo *)pMethodInfo)->executionAborted)
- flags = ICodeManagerFlags::ExecutionAborted;
-
- if (IsFilter(pMethodInfo))
- flags = (ICodeManagerFlags)(flags | ICodeManagerFlags::NoReportUntracked);
-
- if (isActiveStackFrame)
- flags = (ICodeManagerFlags)(flags | ICodeManagerFlags::ActiveStackFrame);
-
if (!decoder.EnumerateLiveSlots(
pRegisterSet,
isActiveStackFrame /* reportScratchSlots */,
@@ -423,9 +442,22 @@ void CoffNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo,
assert(false);
}
#else
- // x86 has custom GC info, see EnumGcRefs in eetwain.cpp
- PORTABILITY_ASSERT("EnumGcRefs");
- RhFailFast();
+ size_t unwindDataBlobSize;
+ CoffNativeMethodInfo* pNativeMethodInfo = (CoffNativeMethodInfo *) pMethodInfo;
+ PTR_VOID pUnwindDataBlob = GetUnwindDataBlob(m_moduleBase, pNativeMethodInfo->runtimeFunction, &unwindDataBlobSize);
+ PTR_uint8_t p = dac_cast(pUnwindDataBlob) + unwindDataBlobSize;
+ uint8_t unwindBlockFlags = *p++;
+
+ ::EnumGcRefsX86(pRegisterSet,
+ (PTR_CBYTE)(m_moduleBase + pNativeMethodInfo->mainRuntimeFunction->BeginAddress),
+ codeOffset,
+ GCInfoToken(gcInfo),
+ (PTR_CBYTE)(m_moduleBase + pNativeMethodInfo->runtimeFunction->BeginAddress),
+ (unwindBlockFlags & UBF_FUNC_KIND_MASK) != UBF_FUNC_KIND_ROOT,
+ (unwindBlockFlags & UBF_FUNC_KIND_MASK) == UBF_FUNC_KIND_FILTER,
+ flags,
+ hCallback->pCallback,
+ hCallback);
#endif
}
@@ -474,8 +506,13 @@ uintptr_t CoffNativeCodeManager::GetConservativeUpperBoundForOutgoingArgs(Method
// all outgoing arguments.
upperBound = dac_cast(basePointer + slot);
#else
- PORTABILITY_ASSERT("GetConservativeUpperBoundForOutgoingArgs");
- RhFailFast();
+ hdrInfo info;
+ DecodeGCHdrInfo(GCInfoToken(p), 0, &info);
+ assert(info.revPInvokeOffset != INVALID_REV_PINVOKE_OFFSET);
+ upperBound =
+ info.ebpFrame ?
+ dac_cast(pRegisterSet->GetFP()) - info.revPInvokeOffset :
+ dac_cast(pRegisterSet->GetSP()) + info.revPInvokeOffset;
#endif
}
else
@@ -535,7 +572,6 @@ uintptr_t CoffNativeCodeManager::GetConservativeUpperBoundForOutgoingArgs(Method
NULL);
upperBound = dac_cast(context.Sp);
-
#else
PORTABILITY_ASSERT("GetConservativeUpperBoundForOutgoingArgs");
upperBound = NULL;
@@ -587,21 +623,46 @@ bool CoffNativeCodeManager::UnwindStackFrame(MethodInfo * pMethodInfo,
}
*ppPreviousTransitionFrame = *(PInvokeTransitionFrame**)(basePointer + slot);
+#else
+ hdrInfo info;
+ DecodeGCHdrInfo(GCInfoToken(p), 0, &info);
+ assert(info.revPInvokeOffset != INVALID_REV_PINVOKE_OFFSET);
+ *ppPreviousTransitionFrame =
+ info.ebpFrame ?
+ *(PInvokeTransitionFrame**)(dac_cast(pRegisterSet->GetFP()) - info.revPInvokeOffset) :
+ *(PInvokeTransitionFrame**)(dac_cast(pRegisterSet->GetSP()) + info.revPInvokeOffset);
+#endif
if ((flags & USFF_StopUnwindOnTransitionFrame) != 0)
{
return true;
}
-#else
- PORTABILITY_ASSERT("UnwindStackFrame");
- RhFailFast();
-#endif
}
else
{
*ppPreviousTransitionFrame = NULL;
}
+#if defined(TARGET_X86)
+ PTR_uint8_t gcInfo;
+ uint32_t codeOffset = GetCodeOffset(pMethodInfo, (PTR_VOID)pRegisterSet->IP, &gcInfo);
+
+ hdrInfo infoBuf;
+ size_t infoSize = DecodeGCHdrInfo(GCInfoToken(gcInfo), codeOffset, &infoBuf);
+ PTR_CBYTE table = gcInfo + infoSize;
+
+ if (!::UnwindStackFrameX86(pRegisterSet,
+ (PTR_CBYTE)(m_moduleBase + pNativeMethodInfo->mainRuntimeFunction->BeginAddress),
+ codeOffset,
+ &infoBuf,
+ table,
+ (PTR_CBYTE)(m_moduleBase + pNativeMethodInfo->runtimeFunction->BeginAddress),
+ (unwindBlockFlags & UBF_FUNC_KIND_MASK) != UBF_FUNC_KIND_ROOT,
+ true))
+ {
+ return false;
+ }
+#else
CONTEXT context;
KNONVOLATILE_CONTEXT_POINTERS contextPointers;
@@ -635,10 +696,7 @@ bool CoffNativeCodeManager::UnwindStackFrame(MethodInfo * pMethodInfo,
FOR_EACH_NONVOLATILE_REGISTER(REGDISPLAY_TO_CONTEXT);
-#if defined(TARGET_X86)
- PORTABILITY_ASSERT("CoffNativeCodeManager::UnwindStackFrame");
-#elif defined(TARGET_AMD64)
-
+#if defined(TARGET_AMD64)
if (!(flags & USFF_GcUnwind))
{
memcpy(&context.Xmm6, pRegisterSet->Xmm, sizeof(pRegisterSet->Xmm));
@@ -696,7 +754,7 @@ bool CoffNativeCodeManager::UnwindStackFrame(MethodInfo * pMethodInfo,
for (int i = 8; i < 16; i++)
pRegisterSet->D[i - 8] = context.V[i].Low;
}
-#endif // defined(TARGET_X86)
+#endif
FOR_EACH_NONVOLATILE_REGISTER(CONTEXT_TO_REGDISPLAY);
@@ -705,6 +763,8 @@ bool CoffNativeCodeManager::UnwindStackFrame(MethodInfo * pMethodInfo,
#undef REGDISPLAY_TO_CONTEXT
#undef CONTEXT_TO_REGDISPLAY
+#endif // defined(TARGET_X86)
+
return true;
}
@@ -732,7 +792,6 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn
PTR_PTR_VOID * ppvRetAddrLocation, // out
GCRefKind * pRetValueKind) // out
{
-#ifdef USE_GC_INFO_DECODER
CoffNativeMethodInfo * pNativeMethodInfo = (CoffNativeMethodInfo *)pMethodInfo;
size_t unwindDataBlobSize;
@@ -757,6 +816,7 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn
if ((unwindBlockFlags & UBF_FUNC_HAS_EHINFO) != 0)
p += sizeof(int32_t);
+#ifdef USE_GC_INFO_DECODER
// Decode the GC info for the current method to determine its return type
GcInfoDecoderFlags flags = DECODE_RETURN_KIND;
#if defined(TARGET_ARM64)
@@ -845,8 +905,35 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn
return false;
#endif // defined(TARGET_AMD64)
#else // defined(USE_GC_INFO_DECODER)
- PORTABILITY_ASSERT("GetReturnAddressHijackInfo");
- RhFailFast();
+ PTR_uint8_t gcInfo;
+ uint32_t codeOffset = GetCodeOffset(pMethodInfo, (PTR_VOID)pRegisterSet->IP, &gcInfo);
+ hdrInfo infoBuf;
+ size_t infoSize = DecodeGCHdrInfo(GCInfoToken(gcInfo), codeOffset, &infoBuf);
+
+ // TODO: Hijack with saving the return value in FP stack
+ if (infoBuf.returnKind == RT_Float)
+ {
+ return false;
+ }
+
+ REGDISPLAY registerSet = *pRegisterSet;
+
+ if (!::UnwindStackFrameX86(®isterSet,
+ (PTR_CBYTE)(m_moduleBase + pNativeMethodInfo->mainRuntimeFunction->BeginAddress),
+ codeOffset,
+ &infoBuf,
+ gcInfo + infoSize,
+ (PTR_CBYTE)(m_moduleBase + pNativeMethodInfo->runtimeFunction->BeginAddress),
+ (unwindBlockFlags & UBF_FUNC_KIND_MASK) != UBF_FUNC_KIND_ROOT,
+ false))
+ {
+ return false;
+ }
+
+ *ppvRetAddrLocation = (PTR_PTR_VOID)registerSet.PCTAddr;
+ *pRetValueKind = GetGcRefKind(infoBuf.returnKind);
+
+ return true;
#endif
}
diff --git a/src/coreclr/unwinder/i386/unwinder.cpp b/src/coreclr/unwinder/i386/unwinder.cpp
index 45781ef0ff6b02..57d1268a129a0f 100644
--- a/src/coreclr/unwinder/i386/unwinder.cpp
+++ b/src/coreclr/unwinder/i386/unwinder.cpp
@@ -21,15 +21,23 @@ BOOL OOPStackUnwinderX86::Unwind(T_CONTEXT* pContextRecord, T_KNONVOLATILE_CONTE
rd.pCurrentContextPointers = pContextPointers;
}
- CodeManState codeManState;
- codeManState.dwIsSet = 0;
-
DWORD ControlPc = pContextRecord->Eip;
EECodeInfo codeInfo;
codeInfo.Init((PCODE) ControlPc);
- if (!UnwindStackFrame(&rd, &codeInfo, UpdateAllRegs, &codeManState))
+ GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken();
+ hdrInfo hdrInfoBody;
+ DWORD hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, codeInfo.GetRelOffset(), &hdrInfoBody);
+
+ if (!UnwindStackFrameX86(&rd,
+ PTR_CBYTE(codeInfo.GetSavedMethodCode()),
+ codeInfo.GetRelOffset(),
+ &hdrInfoBody,
+ dac_cast(gcInfoToken.Info) + hdrInfoSize,
+ PTR_CBYTE(codeInfo.GetJitManager()->GetFuncletStartAddress(&codeInfo)),
+ codeInfo.IsFunclet(),
+ UpdateAllRegs))
{
return FALSE;
}
diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp
index 366bf86af3d024..323dec316a8a83 100644
--- a/src/coreclr/vm/eetwain.cpp
+++ b/src/coreclr/vm/eetwain.cpp
@@ -9,8 +9,6 @@
#include "dbginterface.h"
#include "gcenv.h"
-#define RETURN_ADDR_OFFS 1 // in DWORDS
-
#ifdef USE_GC_INFO_DECODER
#include "gcinfodecoder.h"
#endif
@@ -19,50 +17,7 @@
#include "gccover.h"
#endif // HAVE_GCCOVER
-#include "argdestination.h"
-
-#define X86_INSTR_TEST_ESP_SIB 0x24
-#define X86_INSTR_PUSH_0 0x6A // push 00, entire instruction is 0x6A00
-#define X86_INSTR_PUSH_IMM 0x68 // push NNNN,
-#define X86_INSTR_W_PUSH_IND_IMM 0x35FF // push [NNNN]
-#define X86_INSTR_CALL_REL32 0xE8 // call rel32
-#define X86_INSTR_W_CALL_IND_IMM 0x15FF // call [addr32]
-#define X86_INSTR_NOP 0x90 // nop
-#define X86_INSTR_NOP2 0x9090 // 2-byte nop
-#define X86_INSTR_NOP3_1 0x9090 // 1st word of 3-byte nop
-#define X86_INSTR_NOP3_3 0x90 // 3rd byte of 3-byte nop
-#define X86_INSTR_NOP4 0x90909090 // 4-byte nop
-#define X86_INSTR_NOP5_1 0x90909090 // 1st dword of 5-byte nop
-#define X86_INSTR_NOP5_5 0x90 // 5th byte of 5-byte nop
-#define X86_INSTR_INT3 0xCC // int3
-#define X86_INSTR_HLT 0xF4 // hlt
-#define X86_INSTR_PUSH_EAX 0x50 // push eax
-#define X86_INSTR_PUSH_EBP 0x55 // push ebp
-#define X86_INSTR_W_MOV_EBP_ESP 0xEC8B // mov ebp, esp
-#define X86_INSTR_POP_ECX 0x59 // pop ecx
-#define X86_INSTR_RET 0xC2 // ret imm16
-#define X86_INSTR_RETN 0xC3 // ret
-#define X86_INSTR_XOR 0x33 // xor
-#define X86_INSTR_w_TEST_ESP_EAX 0x0485 // test [esp], eax
-#define X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX 0x8485 // test [esp-dwOffset], eax
-#define X86_INSTR_w_LEA_ESP_EBP_BYTE_OFFSET 0x658d // lea esp, [ebp-bOffset]
-#define X86_INSTR_w_LEA_ESP_EBP_DWORD_OFFSET 0xa58d // lea esp, [ebp-dwOffset]
-#define X86_INSTR_w_LEA_EAX_ESP_BYTE_OFFSET 0x448d // lea eax, [esp-bOffset]
-#define X86_INSTR_w_LEA_EAX_ESP_DWORD_OFFSET 0x848d // lea eax, [esp-dwOffset]
-#define X86_INSTR_JMP_NEAR_REL32 0xE9 // near jmp rel32
-#define X86_INSTR_w_JMP_FAR_IND_IMM 0x25FF // far jmp [addr32]
-
-#ifndef USE_GC_INFO_DECODER
-
-
-#ifdef _DEBUG
-// For dumping of verbose info.
-#ifndef DACCESS_COMPILE
-static bool trFixContext = false;
-#endif
-static bool trEnumGCRefs = false;
-static bool dspPtr = false; // prints the live ptrs as reported
-#endif
+#ifdef TARGET_X86
// NOTE: enabling compiler optimizations, even for debug builds.
// Comment this out in order to be able to fully debug methods here.
@@ -70,5097 +25,1349 @@ static bool dspPtr = false; // prints the live ptrs as reported
#pragma optimize("tg", on)
#endif
-__forceinline unsigned decodeUnsigned(PTR_CBYTE& src)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
-#ifdef DACCESS_COMPILE
- PTR_CBYTE begin = src;
-#endif
-
- BYTE byte = *src++;
- unsigned value = byte & 0x7f;
- while (byte & 0x80)
- {
-#ifdef DACCESS_COMPILE
- // In DAC builds, the target data may be corrupt. Rather than return incorrect data
- // and risk wasting time in a potentially long loop, we want to fail early and gracefully.
- // The data is encoded with 7 value-bits per byte, and so we may need to read a maximum
- // of 5 bytes (7*5=35) to read a full 32-bit integer.
- if ((src - begin) > 5)
- {
- DacError(CORDBG_E_TARGET_INCONSISTENT);
- }
-#endif
-
- byte = *src++;
- value <<= 7;
- value += byte & 0x7f;
- }
- return value;
-}
-
-__forceinline int decodeSigned(PTR_CBYTE& src)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
-#ifdef DACCESS_COMPILE
- PTR_CBYTE begin = src;
-#endif
+void promoteVarArgs(PTR_BYTE argsStart, PTR_VASigCookie varArgSig, GCCONTEXT* ctx);
- BYTE byte = *src++;
- BYTE first = byte;
- int value = byte & 0x3f;
- while (byte & 0x80)
- {
-#ifdef DACCESS_COMPILE
- // In DAC builds, the target data may be corrupt. Rather than return incorrect data
- // and risk wasting time in a potentially long loop, we want to fail early and gracefully.
- // The data is encoded with 7 value-bits per byte, and so we may need to read a maximum
- // of 5 bytes (7*5=35) to read a full 32-bit integer.
- if ((src - begin) > 5)
- {
- DacError(CORDBG_E_TARGET_INCONSISTENT);
- }
-#endif
+#include "gc_unwind_x86.inl"
- byte = *src++;
- value <<= 7;
- value += byte & 0x7f;
- }
- if (first & 0x40)
- value = -value;
- return value;
-}
+#endif // TARGET_X86
-// Fast versions of the above, with one iteration of the loop unrolled
-#define fastDecodeUnsigned(src) (((*(src) & 0x80) == 0) ? (unsigned) (*(src)++) : decodeUnsigned((src)))
-#define fastDecodeSigned(src) (((*(src) & 0xC0) == 0) ? (unsigned) (*(src)++) : decodeSigned((src)))
+#include "argdestination.h"
-// Fast skipping past encoded integers
#ifndef DACCESS_COMPILE
-#define fastSkipUnsigned(src) { while ((*(src)++) & 0x80) { } }
-#define fastSkipSigned(src) { while ((*(src)++) & 0x80) { } }
-#else
-// In DAC builds we want to trade-off a little perf in the common case for reliaiblity against corrupt data.
-#define fastSkipUnsigned(src) (decodeUnsigned(src))
-#define fastSkipSigned(src) (decodeSigned(src))
-#endif
-
+#ifndef FEATURE_EH_FUNCLETS
/*****************************************************************************
*
- * Decodes the X86 GcInfo header and returns the decoded information
- * in the hdrInfo struct.
- * curOffset is the code offset within the active method used in the
- * computation of PrologOffs/EpilogOffs.
- * Returns the size of the header (number of bytes decoded).
+ * Setup context to enter an exception handler (a 'catch' block).
+ * This is the last chance for the runtime support to do fixups in
+ * the context before execution continues inside a filter, catch handler,
+ * or finally.
*/
-size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken,
- unsigned curOffset,
- hdrInfo * infoPtr)
+void EECodeManager::FixContext( ContextType ctxType,
+ EHContext *ctx,
+ EECodeInfo *pCodeInfo,
+ DWORD dwRelOffset,
+ DWORD nestingLevel,
+ OBJECTREF thrownObject,
+ CodeManState *pState,
+ size_t ** ppShadowSP,
+ size_t ** ppEndRegion)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
- HOST_NOCALLS;
- SUPPORTS_DAC;
} CONTRACTL_END;
- PTR_CBYTE table = (PTR_CBYTE) gcInfoToken.Info;
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xFEEF);
-#endif
-
- infoPtr->methodSize = fastDecodeUnsigned(table);
-
- _ASSERTE(curOffset >= 0);
- _ASSERTE(curOffset <= infoPtr->methodSize);
-
- /* Decode the InfoHdr */
+ _ASSERTE((ctxType == FINALLY_CONTEXT) == (thrownObject == NULL));
- InfoHdr header;
- table = decodeHeader(table, gcInfoToken.Version, &header);
+ _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
+ CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
- BOOL hasArgTabOffset = FALSE;
- if (header.untrackedCnt == HAS_UNTRACKED)
- {
- hasArgTabOffset = TRUE;
- header.untrackedCnt = fastDecodeUnsigned(table);
- }
+ /* Extract the necessary information from the info block header */
- if (header.varPtrTableSize == HAS_VARPTR)
- {
- hasArgTabOffset = TRUE;
- header.varPtrTableSize = fastDecodeUnsigned(table);
- }
+ stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(pCodeInfo->GetGCInfoToken(),
+ dwRelOffset,
+ &stateBuf->hdrInfoBody);
+ pState->dwIsSet = 1;
- if (header.gsCookieOffset == HAS_GS_COOKIE_OFFSET)
- {
- header.gsCookieOffset = fastDecodeUnsigned(table);
+#ifdef _DEBUG
+ if (trFixContext) {
+ printf("FixContext [%s][%s] for %s.%s: ",
+ stateBuf->hdrInfoBody.ebpFrame?"ebp":" ",
+ stateBuf->hdrInfoBody.interruptible?"int":" ",
+ "UnknownClass","UnknownMethod");
+ fflush(stdout);
}
+#endif
- if (header.syncStartOffset == HAS_SYNC_OFFSET)
- {
- header.syncStartOffset = decodeUnsigned(table);
- header.syncEndOffset = decodeUnsigned(table);
-
- _ASSERTE(header.syncStartOffset != INVALID_SYNC_OFFSET && header.syncEndOffset != INVALID_SYNC_OFFSET);
- _ASSERTE(header.syncStartOffset < header.syncEndOffset);
- }
+ /* make sure that we have an ebp stack frame */
- if (header.revPInvokeOffset == HAS_REV_PINVOKE_FRAME_OFFSET)
- {
- header.revPInvokeOffset = fastDecodeUnsigned(table);
- }
+ _ASSERTE(stateBuf->hdrInfoBody.ebpFrame);
+ _ASSERTE(stateBuf->hdrInfoBody.handlers); // @TODO : This will always be set. Remove it
- /* Some sanity checks on header */
+ TADDR baseSP;
+ GetHandlerFrameInfo(&stateBuf->hdrInfoBody, ctx->Ebp,
+ ctxType == FILTER_CONTEXT ? ctx->Esp : IGNORE_VAL,
+ ctxType == FILTER_CONTEXT ? (DWORD) IGNORE_VAL : nestingLevel,
+ &baseSP,
+ &nestingLevel);
- _ASSERTE( header.prologSize +
- (size_t)(header.epilogCount*header.epilogSize) <= infoPtr->methodSize);
- _ASSERTE( header.epilogCount == 1 || !header.epilogAtEnd);
+ _ASSERTE((size_t)ctx->Ebp >= baseSP);
+ _ASSERTE(baseSP >= (size_t)ctx->Esp);
- _ASSERTE( header.untrackedCnt <= header.argCount+header.frameSize);
+ ctx->Esp = (DWORD)baseSP;
- _ASSERTE( header.ebpSaved || !(header.ebpFrame || header.doubleAlign));
- _ASSERTE(!header.ebpFrame || !header.doubleAlign );
- _ASSERTE( header.ebpFrame || !header.security );
- _ASSERTE( header.ebpFrame || !header.handlers );
- _ASSERTE( header.ebpFrame || !header.localloc );
- _ASSERTE( header.ebpFrame || !header.editNcontinue); // : Esp frames NYI for EnC
+ // EE will write Esp to **pShadowSP before jumping to handler
- /* Initialize the infoPtr struct */
+ PTR_TADDR pBaseSPslots =
+ GetFirstBaseSPslotPtr(ctx->Ebp, &stateBuf->hdrInfoBody);
+ *ppShadowSP = (size_t *)&pBaseSPslots[-(int) nestingLevel ];
+ pBaseSPslots[-(int)(nestingLevel+1)] = 0; // Zero out the next slot
- infoPtr->argSize = header.argCount * 4;
- infoPtr->ebpFrame = header.ebpFrame;
- infoPtr->interruptible = header.interruptible;
- infoPtr->returnKind = (ReturnKind) header.returnKind;
+ // EE will write the end offset of the filter
+ if (ctxType == FILTER_CONTEXT)
+ *ppEndRegion = (size_t *)pBaseSPslots + 1;
- infoPtr->prologSize = header.prologSize;
- infoPtr->epilogSize = header.epilogSize;
- infoPtr->epilogCnt = header.epilogCount;
- infoPtr->epilogEnd = header.epilogAtEnd;
+ /* This is just a simple assignment of throwObject to ctx->Eax,
+ just pretend the cast goo isn't there.
+ */
- infoPtr->untrackedCnt = header.untrackedCnt;
- infoPtr->varPtrTableSize = header.varPtrTableSize;
- infoPtr->gsCookieOffset = header.gsCookieOffset;
+ *((OBJECTREF*)&(ctx->Eax)) = thrownObject;
+}
- infoPtr->syncStartOffset = header.syncStartOffset;
- infoPtr->syncEndOffset = header.syncEndOffset;
- infoPtr->revPInvokeOffset = header.revPInvokeOffset;
+#endif // !FEATURE_EH_FUNCLETS
- infoPtr->doubleAlign = header.doubleAlign;
- infoPtr->handlers = header.handlers;
- infoPtr->localloc = header.localloc;
- infoPtr->editNcontinue = header.editNcontinue;
- infoPtr->varargs = header.varargs;
- infoPtr->profCallbacks = header.profCallbacks;
- infoPtr->genericsContext = header.genericsContext;
- infoPtr->genericsContextIsMethodDesc = header.genericsContextIsMethodDesc;
- infoPtr->isSpeculativeStackWalk = false;
- /* Are we within the prolog of the method? */
- if (curOffset < infoPtr->prologSize)
- {
- infoPtr->prologOffs = curOffset;
- }
- else
- {
- infoPtr->prologOffs = hdrInfo::NOT_IN_PROLOG;
- }
- /* Assume we're not in the epilog of the method */
- infoPtr->epilogOffs = hdrInfo::NOT_IN_EPILOG;
+/*****************************************************************************/
- /* Are we within an epilog of the method? */
+bool VarIsInReg(ICorDebugInfo::VarLoc varLoc)
+{
+ LIMITED_METHOD_CONTRACT;
- if (infoPtr->epilogCnt)
+ switch(varLoc.vlType)
{
- unsigned epilogStart;
-
- if (infoPtr->epilogCnt > 1 || !infoPtr->epilogEnd)
- {
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xFACE);
-#endif
- epilogStart = 0;
- for (unsigned i = 0; i < infoPtr->epilogCnt; i++)
- {
- epilogStart += fastDecodeUnsigned(table);
- if (curOffset > epilogStart &&
- curOffset < epilogStart + infoPtr->epilogSize)
- {
- infoPtr->epilogOffs = curOffset - epilogStart;
- }
- }
- }
- else
- {
- epilogStart = infoPtr->methodSize - infoPtr->epilogSize;
-
- if (curOffset > epilogStart &&
- curOffset < epilogStart + infoPtr->epilogSize)
- {
- infoPtr->epilogOffs = curOffset - epilogStart;
- }
- }
+ case ICorDebugInfo::VLT_REG:
+ case ICorDebugInfo::VLT_REG_REG:
+ case ICorDebugInfo::VLT_REG_STK:
+ return true;
- infoPtr->syncEpilogStart = epilogStart;
+ default:
+ return false;
}
+}
- unsigned argTabOffset = INVALID_ARGTAB_OFFSET;
- if (hasArgTabOffset)
- {
- argTabOffset = fastDecodeUnsigned(table);
- }
- infoPtr->argTabOffset = argTabOffset;
+#ifdef FEATURE_REMAP_FUNCTION
+/*****************************************************************************
+ * Last chance for the runtime support to do fixups in the context
+ * before execution continues inside an EnC updated function.
+ * It also adjusts ESP and munges on the stack. So the caller has to make
+ * sure that this stack region is not needed (by doing a localloc).
+ * Also, if this returns EnC_FAIL, we should not have munged the
+ * context ie. transcated commit
+ * The plan of attack is:
+ * 1) Error checking up front. If we get through here, everything
+ * else should work
+ * 2) Get all the info about current variables, registers, etc
+ * 3) zero out the stack frame - this'll initialize _all_ variables
+ * 4) Put the variables from step 3 into their new locations.
+ *
+ * Note that while we use the ShuffleVariablesGet/Set methods, they don't
+ * have any info/logic that's internal to the runtime: another codemanger
+ * could easily duplicate what they do, which is why we're calling into them.
+ */
- size_t frameDwordCount = header.frameSize;
+HRESULT EECodeManager::FixContextForEnC(PCONTEXT pCtx,
+ EECodeInfo * pOldCodeInfo,
+ const ICorDebugInfo::NativeVarInfo * oldMethodVars,
+ SIZE_T oldMethodVarsCount,
+ EECodeInfo * pNewCodeInfo,
+ const ICorDebugInfo::NativeVarInfo * newMethodVars,
+ SIZE_T newMethodVarsCount)
+{
+ CONTRACTL {
+ DISABLED(NOTHROW);
+ DISABLED(GC_NOTRIGGER);
+ } CONTRACTL_END;
- /* Set the rawStackSize to the number of bytes that it bumps ESP */
+ HRESULT hr = S_OK;
- infoPtr->rawStkSize = (UINT)(frameDwordCount * sizeof(size_t));
+ // Grab a copy of the context before the EnC update.
+ T_CONTEXT oldCtx = *pCtx;
- /* Calculate the callee saves regMask and adjust stackSize to */
- /* include the callee saves register spills */
+#if defined(TARGET_X86)
- unsigned savedRegs = RM_NONE;
- unsigned savedRegsCount = 0;
+ /* Extract the necessary information from the info block header */
- if (header.ediSaved)
- {
- savedRegsCount++;
- savedRegs |= RM_EDI;
- }
- if (header.esiSaved)
- {
- savedRegsCount++;
- savedRegs |= RM_ESI;
- }
- if (header.ebxSaved)
- {
- savedRegsCount++;
- savedRegs |= RM_EBX;
- }
- if (header.ebpSaved)
- {
- savedRegsCount++;
- savedRegs |= RM_EBP;
- }
+ hdrInfo oldInfo, newInfo;
- infoPtr->savedRegMask = (RegMask)savedRegs;
+ DecodeGCHdrInfo(pOldCodeInfo->GetGCInfoToken(),
+ pOldCodeInfo->GetRelOffset(),
+ &oldInfo);
- infoPtr->savedRegsCountExclFP = savedRegsCount;
- if (header.ebpFrame || header.doubleAlign)
- {
- _ASSERTE(header.ebpSaved);
- infoPtr->savedRegsCountExclFP = savedRegsCount - 1;
- }
+ DecodeGCHdrInfo(pNewCodeInfo->GetGCInfoToken(),
+ pNewCodeInfo->GetRelOffset(),
+ &newInfo);
- frameDwordCount += savedRegsCount;
+ //1) Error checking up front. If we get through here, everything
+ // else should work
- infoPtr->stackSize = (UINT)(frameDwordCount * sizeof(size_t));
+ if (!oldInfo.editNcontinue || !newInfo.editNcontinue) {
+ LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC EnC_INFOLESS_METHOD\n"));
+ return CORDBG_E_ENC_INFOLESS_METHOD;
+ }
- _ASSERTE(infoPtr->gsCookieOffset == INVALID_GS_COOKIE_OFFSET ||
- (infoPtr->gsCookieOffset < infoPtr->stackSize) &&
- ((header.gsCookieOffset % sizeof(void*)) == 0));
+ if (!oldInfo.ebpFrame || !newInfo.ebpFrame) {
+ LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC Esp frames NYI\n"));
+ return E_FAIL; // Esp frames NYI
+ }
- return table - PTR_CBYTE(gcInfoToken.Info);
-}
+ if (pCtx->Esp != pCtx->Ebp - oldInfo.stackSize + sizeof(DWORD)) {
+ LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC stack should be empty\n"));
+ return E_FAIL; // stack should be empty - @TODO : Barring localloc
+ }
-/*****************************************************************************/
+ if (oldInfo.handlers)
+ {
+ bool hasInnerFilter;
+ TADDR baseSP;
+ FrameType frameType = GetHandlerFrameInfo(&oldInfo, pCtx->Ebp,
+ pCtx->Esp, IGNORE_VAL,
+ &baseSP, NULL, &hasInnerFilter);
+ _ASSERTE(frameType != FR_INVALID);
+ _ASSERTE(!hasInnerFilter); // FixContextForEnC() is called for bottommost funclet
-// We do a "pop eax; jmp eax" to return from a fault or finally handler
-const size_t END_FIN_POP_STACK = sizeof(TADDR);
+ // If the method is in a fuclet, and if the framesize grows, we are in trouble.
-inline
-size_t GetLocallocSPOffset(hdrInfo * info)
-{
- LIMITED_METHOD_DAC_CONTRACT;
+ if (frameType != FR_NORMAL)
+ {
+ /* @TODO : What if the new method offset is in a fuclet,
+ and the old is not, or the nesting level changed, etc */
- _ASSERTE(info->localloc && info->ebpFrame);
+ if (oldInfo.stackSize != newInfo.stackSize) {
+ LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC stack size mismatch\n"));
+ return CORDBG_E_ENC_IN_FUNCLET;
+ }
+ }
+ }
- unsigned position = info->savedRegsCountExclFP +
- 1;
- return position * sizeof(TADDR);
-}
+ /* @TODO: Check if we have grown out of space for locals, in the face of localloc */
+ _ASSERTE(!oldInfo.localloc && !newInfo.localloc);
-inline
-size_t GetParamTypeArgOffset(hdrInfo * info)
-{
- LIMITED_METHOD_DAC_CONTRACT;
+ // @TODO: If nesting level grows above the MAX_EnC_HANDLER_NESTING_LEVEL,
+ // we should return EnC_NESTED_HANLDERS
+ _ASSERTE(oldInfo.handlers && newInfo.handlers);
- _ASSERTE((info->genericsContext || info->handlers) && info->ebpFrame);
+ LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: Checks out\n"));
- unsigned position = info->savedRegsCountExclFP +
- info->localloc +
- 1; // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
- return position * sizeof(TADDR);
-}
+#elif defined(TARGET_AMD64) || defined(TARGET_ARM64)
-inline size_t GetStartShadowSPSlotsOffset(hdrInfo * info)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- _ASSERTE(info->handlers && info->ebpFrame);
-
- return GetParamTypeArgOffset(info) +
- sizeof(TADDR); // Slot for end-of-last-executed-filter
-}
-
-/*****************************************************************************
- * Returns the start of the hidden slots for the shadowSP for functions
- * with exception handlers. There is one slot per nesting level starting
- * near Ebp and is zero-terminated after the active slots.
- */
-
-inline
-PTR_TADDR GetFirstBaseSPslotPtr(TADDR ebp, hdrInfo * info)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- _ASSERTE(info->handlers && info->ebpFrame);
-
- size_t offsetFromEBP = GetStartShadowSPSlotsOffset(info)
- + sizeof(TADDR); // to get to the *start* of the next slot
-
- return PTR_TADDR(ebp - offsetFromEBP);
-}
-
-inline size_t GetEndShadowSPSlotsOffset(hdrInfo * info, unsigned maxHandlerNestingLevel)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- _ASSERTE(info->handlers && info->ebpFrame);
-
- unsigned numberOfShadowSPSlots = maxHandlerNestingLevel +
- 1 + // For zero-termination
- 1; // For a filter (which can be active at the same time as a catch/finally handler
-
- return GetStartShadowSPSlotsOffset(info) +
- (numberOfShadowSPSlots * sizeof(TADDR));
-}
-
-/*****************************************************************************
- * returns the base frame pointer corresponding to the target nesting level.
- */
-
-inline
-TADDR GetOutermostBaseFP(TADDR ebp, hdrInfo * info)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- // we are not taking into account double alignment. We are
- // safe because the jit currently bails on double alignment if there
- // are handles or localalloc
- _ASSERTE(!info->doubleAlign);
- if (info->localloc)
- {
- // If the function uses localloc we will fetch the ESP from the localloc
- // slot.
- PTR_TADDR pLocalloc = PTR_TADDR(ebp - GetLocallocSPOffset(info));
-
- return (*pLocalloc);
- }
- else
- {
- // Default, go back all the method's local stack size
- return ebp - info->stackSize + sizeof(int);
- }
-}
-
-/*****************************************************************************
- *
- * For functions with handlers, checks if it is currently in a handler.
- * Either of unwindESP or unwindLevel will specify the target nesting level.
- * If unwindLevel is specified, info about the funclet at that nesting level
- * will be returned. (Use if you are interested in a specific nesting level.)
- * If unwindESP is specified, info for nesting level invoked before the stack
- * reached unwindESP will be returned. (Use if you have a specific ESP value
- * during stack walking.)
- *
- * *pBaseSP is set to the base SP (base of the stack on entry to
- * the current funclet) corresponding to the target nesting level.
- * *pNestLevel is set to the nesting level of the target nesting level (useful
- * if unwindESP!=IGNORE_VAL
- * *pHasInnerFilter will be set to true (only when unwindESP!=IGNORE_VAL) if a filter
- * is currently active, but the target nesting level is an outer nesting level.
- * *pHadInnerFilter - was the last use of the frame to execute a filter.
- * This mainly affects GC lifetime reporting.
- */
-
-enum FrameType
-{
- FR_NORMAL, // Normal method frame - no exceptions currently active
- FR_FILTER, // Frame-let of a filter
- FR_HANDLER, // Frame-let of a callable catch/fault/finally
-
- FR_INVALID, // Invalid frame (for speculative stackwalks)
-};
-
-enum { IGNORE_VAL = -1 };
-
-FrameType GetHandlerFrameInfo(hdrInfo * info,
- TADDR frameEBP,
- TADDR unwindESP,
- DWORD unwindLevel,
- TADDR * pBaseSP = NULL, /* OUT */
- DWORD * pNestLevel = NULL, /* OUT */
- bool * pHasInnerFilter = NULL, /* OUT */
- bool * pHadInnerFilter = NULL) /* OUT */
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- HOST_NOCALLS;
- SUPPORTS_DAC;
- } CONTRACTL_END;
-
- _ASSERTE(info->ebpFrame && info->handlers);
- // One and only one of them should be IGNORE_VAL
- _ASSERTE((unwindESP == (TADDR) IGNORE_VAL) !=
- (unwindLevel == (DWORD) IGNORE_VAL));
- _ASSERTE(pHasInnerFilter == NULL || unwindESP != (TADDR) IGNORE_VAL);
-
- // Many of the conditions that we'd like to assert cannot be asserted in the case that we're
- // in the middle of a stackwalk seeded by a profiler, since such seeds can't be trusted
- // (profilers are external, untrusted sources). So during profiler walks, we test the condition
- // and throw an exception if it's not met. Otherwise, we just assert the condition.
- #define FAIL_IF_SPECULATIVE_WALK(condition) \
- if (info->isSpeculativeStackWalk) \
- { \
- if (!(condition)) \
- { \
- return FR_INVALID; \
- } \
- } \
- else \
- { \
- _ASSERTE(condition); \
- }
-
- PTR_TADDR pFirstBaseSPslot = GetFirstBaseSPslotPtr(frameEBP, info);
- TADDR baseSP = GetOutermostBaseFP(frameEBP, info);
- bool nonLocalHandlers = false; // Are the funclets invoked by EE (instead of managed code itself)
- bool hasInnerFilter = false;
- bool hadInnerFilter = false;
-
- /* Get the last non-zero slot >= unwindESP, or lvl curSlotVal ||
- (baseSP == curSlotVal && pSlot == pFirstBaseSPslot));
-
- if (curSlotVal == LCL_FINALLY_MARK)
- {
- // Locally called finally
- baseSP -= sizeof(TADDR);
- }
- else
- {
- // Is this a funclet we unwound before (can only happen with filters) ?
- // If unwindESP is specified, normally we expect it to be the last entry in the shadow slot array.
- // Or, if there is a filter, we expect unwindESP to be the second last entry. However, this may
- // not be the case in DAC builds. For example, the user can use .cxr in an EH clause to set a
- // CONTEXT captured in the try clause. In this case, unwindESP will be the ESP of the parent
- // function, but the shadow slot array will contain the SP of the EH clause, which is closer to
- // the leaf than the parent method.
-
- if (unwindESP != (TADDR) IGNORE_VAL &&
- unwindESP > END_FIN_POP_STACK +
- (curSlotVal & ~ICodeManager::SHADOW_SP_BITS))
- {
- // In non-DAC builds, the only time unwindESP is closer to the root than entries in the shadow
- // slot array is when the last entry in the array is for a filter. Also, filters can't have
- // nested handlers.
- if ((pSlot[0] & ICodeManager::SHADOW_SP_IN_FILTER) &&
- (pSlot[-1] == 0) &&
- !(baseSP & ICodeManager::SHADOW_SP_IN_FILTER))
- {
- if (pSlot[0] & ICodeManager::SHADOW_SP_FILTER_DONE)
- hadInnerFilter = true;
- else
- hasInnerFilter = true;
- break;
- }
- else
- {
-#if defined(DACCESS_COMPILE)
- // In DAC builds, this could happen. We just need to bail out of this loop early.
- break;
-#else // !DACCESS_COMPILE
- // In non-DAC builds, this is an error.
- FAIL_IF_SPECULATIVE_WALK(FALSE);
-#endif // DACCESS_COMPILE
- }
- }
-
- nonLocalHandlers = true;
- baseSP = curSlotVal;
- }
- }
-#endif // FEATURE_EH_FUNCLETS
-
- if (unwindESP != (TADDR) IGNORE_VAL)
- {
- FAIL_IF_SPECULATIVE_WALK(baseSP >= unwindESP ||
- baseSP == unwindESP - sizeof(TADDR)); // About to locally call a finally
-
- if (baseSP < unwindESP) // About to locally call a finally
- baseSP = unwindESP;
- }
- else
- {
- FAIL_IF_SPECULATIVE_WALK(lvl == unwindLevel); // unwindLevel must be currently active on stack
- }
-
- if (pBaseSP)
- *pBaseSP = baseSP & ~ICodeManager::SHADOW_SP_BITS;
-
- if (pNestLevel)
- {
- *pNestLevel = (DWORD)lvl;
- }
-
- if (pHasInnerFilter)
- *pHasInnerFilter = hasInnerFilter;
-
- if (pHadInnerFilter)
- *pHadInnerFilter = hadInnerFilter;
-
- if (baseSP & ICodeManager::SHADOW_SP_IN_FILTER)
- {
- FAIL_IF_SPECULATIVE_WALK(!hasInnerFilter); // nested filters not allowed
- return FR_FILTER;
- }
- else if (nonLocalHandlers)
- {
- return FR_HANDLER;
- }
- else
- {
- return FR_NORMAL;
- }
-
- #undef FAIL_IF_SPECULATIVE_WALK
-}
-
-// Returns the number of bytes at the beginning of the stack frame that shouldn't be
-// modified by an EnC. This is everything except the space for locals and temporaries.
-inline size_t GetSizeOfFrameHeaderForEnC(hdrInfo * info)
-{
- WRAPPER_NO_CONTRACT;
-
- // See comment above Compiler::lvaAssignFrameOffsets() in src\jit\il\lclVars.cpp
- // for frame layout
-
- // EnC supports increasing the maximum handler nesting level by always
- // assuming that the max is MAX_EnC_HANDLER_NESTING_LEVEL. Methods with
- // a higher max cannot be updated by EnC
-
- // Take the offset (from EBP) of the last slot of the header, plus one for the EBP slot itself
- // to get the total size of the header.
- return sizeof(TADDR) +
- GetEndShadowSPSlotsOffset(info, MAX_EnC_HANDLER_NESTING_LEVEL);
-}
-#endif // !USE_GC_INFO_DECODER
-
-#ifndef DACCESS_COMPILE
-#ifndef FEATURE_EH_FUNCLETS
-
-/*****************************************************************************
- *
- * Setup context to enter an exception handler (a 'catch' block).
- * This is the last chance for the runtime support to do fixups in
- * the context before execution continues inside a filter, catch handler,
- * or finally.
- */
-void EECodeManager::FixContext( ContextType ctxType,
- EHContext *ctx,
- EECodeInfo *pCodeInfo,
- DWORD dwRelOffset,
- DWORD nestingLevel,
- OBJECTREF thrownObject,
- CodeManState *pState,
- size_t ** ppShadowSP,
- size_t ** ppEndRegion)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- } CONTRACTL_END;
-
- _ASSERTE((ctxType == FINALLY_CONTEXT) == (thrownObject == NULL));
-
- _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
- CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
-
- /* Extract the necessary information from the info block header */
-
- stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(pCodeInfo->GetGCInfoToken(),
- dwRelOffset,
- &stateBuf->hdrInfoBody);
- pState->dwIsSet = 1;
-
-#ifdef _DEBUG
- if (trFixContext) {
- printf("FixContext [%s][%s] for %s.%s: ",
- stateBuf->hdrInfoBody.ebpFrame?"ebp":" ",
- stateBuf->hdrInfoBody.interruptible?"int":" ",
- "UnknownClass","UnknownMethod");
- fflush(stdout);
- }
-#endif
-
- /* make sure that we have an ebp stack frame */
-
- _ASSERTE(stateBuf->hdrInfoBody.ebpFrame);
- _ASSERTE(stateBuf->hdrInfoBody.handlers); // @TODO : This will always be set. Remove it
-
- TADDR baseSP;
- GetHandlerFrameInfo(&stateBuf->hdrInfoBody, ctx->Ebp,
- ctxType == FILTER_CONTEXT ? ctx->Esp : IGNORE_VAL,
- ctxType == FILTER_CONTEXT ? (DWORD) IGNORE_VAL : nestingLevel,
- &baseSP,
- &nestingLevel);
-
- _ASSERTE((size_t)ctx->Ebp >= baseSP);
- _ASSERTE(baseSP >= (size_t)ctx->Esp);
-
- ctx->Esp = (DWORD)baseSP;
-
- // EE will write Esp to **pShadowSP before jumping to handler
-
- PTR_TADDR pBaseSPslots =
- GetFirstBaseSPslotPtr(ctx->Ebp, &stateBuf->hdrInfoBody);
- *ppShadowSP = (size_t *)&pBaseSPslots[-(int) nestingLevel ];
- pBaseSPslots[-(int)(nestingLevel+1)] = 0; // Zero out the next slot
-
- // EE will write the end offset of the filter
- if (ctxType == FILTER_CONTEXT)
- *ppEndRegion = (size_t *)pBaseSPslots + 1;
-
- /* This is just a simple assignment of throwObject to ctx->Eax,
- just pretend the cast goo isn't there.
- */
-
- *((OBJECTREF*)&(ctx->Eax)) = thrownObject;
-}
-
-#endif // !FEATURE_EH_FUNCLETS
-
-
-
-
-
-/*****************************************************************************/
-
-bool VarIsInReg(ICorDebugInfo::VarLoc varLoc)
-{
- LIMITED_METHOD_CONTRACT;
-
- switch(varLoc.vlType)
- {
- case ICorDebugInfo::VLT_REG:
- case ICorDebugInfo::VLT_REG_REG:
- case ICorDebugInfo::VLT_REG_STK:
- return true;
-
- default:
- return false;
- }
-}
-
-#ifdef FEATURE_REMAP_FUNCTION
-/*****************************************************************************
- * Last chance for the runtime support to do fixups in the context
- * before execution continues inside an EnC updated function.
- * It also adjusts ESP and munges on the stack. So the caller has to make
- * sure that this stack region is not needed (by doing a localloc).
- * Also, if this returns EnC_FAIL, we should not have munged the
- * context ie. transcated commit
- * The plan of attack is:
- * 1) Error checking up front. If we get through here, everything
- * else should work
- * 2) Get all the info about current variables, registers, etc
- * 3) zero out the stack frame - this'll initialize _all_ variables
- * 4) Put the variables from step 3 into their new locations.
- *
- * Note that while we use the ShuffleVariablesGet/Set methods, they don't
- * have any info/logic that's internal to the runtime: another codemanger
- * could easily duplicate what they do, which is why we're calling into them.
- */
-
-HRESULT EECodeManager::FixContextForEnC(PCONTEXT pCtx,
- EECodeInfo * pOldCodeInfo,
- const ICorDebugInfo::NativeVarInfo * oldMethodVars,
- SIZE_T oldMethodVarsCount,
- EECodeInfo * pNewCodeInfo,
- const ICorDebugInfo::NativeVarInfo * newMethodVars,
- SIZE_T newMethodVarsCount)
-{
- CONTRACTL {
- DISABLED(NOTHROW);
- DISABLED(GC_NOTRIGGER);
- } CONTRACTL_END;
-
- HRESULT hr = S_OK;
-
- // Grab a copy of the context before the EnC update.
- T_CONTEXT oldCtx = *pCtx;
-
-#if defined(TARGET_X86)
-
- /* Extract the necessary information from the info block header */
-
- hdrInfo oldInfo, newInfo;
-
- DecodeGCHdrInfo(pOldCodeInfo->GetGCInfoToken(),
- pOldCodeInfo->GetRelOffset(),
- &oldInfo);
-
- DecodeGCHdrInfo(pNewCodeInfo->GetGCInfoToken(),
- pNewCodeInfo->GetRelOffset(),
- &newInfo);
-
- //1) Error checking up front. If we get through here, everything
- // else should work
-
- if (!oldInfo.editNcontinue || !newInfo.editNcontinue) {
- LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC EnC_INFOLESS_METHOD\n"));
- return CORDBG_E_ENC_INFOLESS_METHOD;
- }
-
- if (!oldInfo.ebpFrame || !newInfo.ebpFrame) {
- LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC Esp frames NYI\n"));
- return E_FAIL; // Esp frames NYI
- }
-
- if (pCtx->Esp != pCtx->Ebp - oldInfo.stackSize + sizeof(DWORD)) {
- LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC stack should be empty\n"));
- return E_FAIL; // stack should be empty - @TODO : Barring localloc
- }
-
- if (oldInfo.handlers)
- {
- bool hasInnerFilter;
- TADDR baseSP;
- FrameType frameType = GetHandlerFrameInfo(&oldInfo, pCtx->Ebp,
- pCtx->Esp, IGNORE_VAL,
- &baseSP, NULL, &hasInnerFilter);
- _ASSERTE(frameType != FR_INVALID);
- _ASSERTE(!hasInnerFilter); // FixContextForEnC() is called for bottommost funclet
-
- // If the method is in a fuclet, and if the framesize grows, we are in trouble.
-
- if (frameType != FR_NORMAL)
- {
- /* @TODO : What if the new method offset is in a fuclet,
- and the old is not, or the nesting level changed, etc */
-
- if (oldInfo.stackSize != newInfo.stackSize) {
- LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC stack size mismatch\n"));
- return CORDBG_E_ENC_IN_FUNCLET;
- }
- }
- }
-
- /* @TODO: Check if we have grown out of space for locals, in the face of localloc */
- _ASSERTE(!oldInfo.localloc && !newInfo.localloc);
-
- // @TODO: If nesting level grows above the MAX_EnC_HANDLER_NESTING_LEVEL,
- // we should return EnC_NESTED_HANLDERS
- _ASSERTE(oldInfo.handlers && newInfo.handlers);
-
- LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: Checks out\n"));
-
-#elif defined(TARGET_AMD64) || defined(TARGET_ARM64)
-
- // Strategy for zeroing out the frame on x64:
- //
- // The stack frame looks like this (stack grows up)
- //
- // =======================================
- // <--- RSP == RBP (invariant: localalloc disallowed before remap)
- // Arguments for next call (if there is one)
- // PSPSym (optional)
- // JIT temporaries (if any)
- // Security object (if any)
- // Local variables (if any)
- // ---------------------------------------
- // Frame header (stuff we must preserve, such as bool for synchronized
- // methods, saved FP, saved callee-preserved registers, etc.)
- // Return address (also included in frame header)
- // ---------------------------------------
- // Arguments for this frame (that's getting remapped). Will naturally be preserved
- // since fixed-frame size doesn't include this.
- // =======================================
- //
- // Goal: Zero out everything AFTER (above) frame header.
- //
- // How do we find this stuff?
- //
- // EECodeInfo::GetFixedStackSize() gives us the full size from the top ("Arguments
- // for next call") all the way down to and including Return Address.
- //
- // GetSizeOfEditAndContinuePreservedArea() gives us the size in bytes of the
- // frame header at the bottom.
- //
- // So we start at RSP, and zero out:
- // GetFixedStackSize() - GetSizeOfEditAndContinuePreservedArea() bytes.
- //
- // We'll need to restore PSPSym; location gotten from GCInfo.
- // We'll need to copy security object; location gotten from GCInfo.
- //
- // On ARM64 the JIT generates a slightly different frame and we do not have
- // the invariant FP == SP, since the FP needs to point at the saved fp/lr
- // pair for ETW stack walks. The frame there looks something like:
- // =======================================
- // Arguments for next call (if there is one) <- SP
- // JIT temporaries
- // Locals
- // PSPSym
- // --------------------------------------- ^ zeroed area
- // MonitorAcquired (for synchronized methods)
- // Saved FP <- FP
- // Saved LR
- // --------------------------------------- ^ preserved area
- // Arguments
- //
- // The JIT reports the size of the "preserved" area, which includes
- // MonitorAcquired when it is present. It could also include other local
- // values that need to be preserved across EnC transitions, but no explicit
- // treatment of these is necessary here beyond preserving the values in
- // this region.
-
- // GCInfo for old method
- GcInfoDecoder oldGcDecoder(
- pOldCodeInfo->GetGCInfoToken(),
- GcInfoDecoderFlags(DECODE_SECURITY_OBJECT | DECODE_PSP_SYM | DECODE_EDIT_AND_CONTINUE),
- 0 // Instruction offset (not needed)
- );
-
- // GCInfo for new method
- GcInfoDecoder newGcDecoder(
- pNewCodeInfo->GetGCInfoToken(),
- GcInfoDecoderFlags(DECODE_SECURITY_OBJECT | DECODE_PSP_SYM | DECODE_EDIT_AND_CONTINUE),
- 0 // Instruction offset (not needed)
- );
-
- UINT32 oldSizeOfPreservedArea = oldGcDecoder.GetSizeOfEditAndContinuePreservedArea();
- UINT32 newSizeOfPreservedArea = newGcDecoder.GetSizeOfEditAndContinuePreservedArea();
-
- LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC: Got old and new EnC preserved area sizes of %u and %u\n", oldSizeOfPreservedArea, newSizeOfPreservedArea));
- // This ensures the JIT generated EnC compliant code.
- if ((oldSizeOfPreservedArea == NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA) ||
- (newSizeOfPreservedArea == NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA))
- {
- _ASSERTE(!"FixContextForEnC called on a non-EnC-compliant method frame");
- return CORDBG_E_ENC_INFOLESS_METHOD;
- }
-
- TADDR oldStackBase = GetSP(&oldCtx);
-
- LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC: Old SP=%p, FP=%p\n", (void*)oldStackBase, (void*)GetFP(&oldCtx)));
-
-#if defined(TARGET_AMD64)
- // Note: we cannot assert anything about the relationship between oldFixedStackSize
- // and newFixedStackSize. It's possible the edited frame grows (new locals) or
- // shrinks (less temporaries).
- DWORD oldFixedStackSize = pOldCodeInfo->GetFixedStackSize();
- DWORD newFixedStackSize = pNewCodeInfo->GetFixedStackSize();
-
- // This verifies no localallocs were used in the old method.
- // JIT is required to emit frame register for EnC-compliant code
- _ASSERTE(pOldCodeInfo->HasFrameRegister());
- _ASSERTE(pNewCodeInfo->HasFrameRegister());
-
-#elif defined(TARGET_ARM64)
- DWORD oldFixedStackSize = oldGcDecoder.GetSizeOfEditAndContinueFixedStackFrame();
- DWORD newFixedStackSize = newGcDecoder.GetSizeOfEditAndContinueFixedStackFrame();
-#else
- PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
-#endif
-
- LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC: Old and new fixed stack sizes are %u and %u\n", oldFixedStackSize, newFixedStackSize));
-
-#if defined(TARGET_AMD64) && defined(TARGET_WINDOWS)
- // win-x64: SP == FP before localloc
- if (oldStackBase != GetFP(&oldCtx))
- {
- return E_FAIL;
- }
-#else
- // All other 64-bit targets use frame chaining with the FP stored right below the
- // return address (LR is always pushed on arm64). FP + 16 == SP + oldFixedStackSize
- // gives the caller's SP before stack alloc.
- if (GetFP(&oldCtx) + 16 != oldStackBase + oldFixedStackSize)
- {
- return E_FAIL;
- }
-#endif
-
- // EnC remap inside handlers is not supported
- if (pOldCodeInfo->IsFunclet() || pNewCodeInfo->IsFunclet())
- return CORDBG_E_ENC_IN_FUNCLET;
-
- if (oldSizeOfPreservedArea != newSizeOfPreservedArea)
- {
- _ASSERTE(!"FixContextForEnC called with method whose frame header size changed from old to new version.");
- return E_FAIL;
- }
-
- TADDR callerSP = oldStackBase + oldFixedStackSize;
-
-#ifdef _DEBUG
- // If the old method has a PSPSym, then its value should == initial-SP (i.e.
- // oldStackBase) for x64 and callerSP for arm64
- INT32 nOldPspSymStackSlot = oldGcDecoder.GetPSPSymStackSlot();
- if (nOldPspSymStackSlot != NO_PSP_SYM)
- {
-#if defined(TARGET_AMD64)
- TADDR oldPSP = *PTR_TADDR(oldStackBase + nOldPspSymStackSlot);
- _ASSERTE(oldPSP == oldStackBase);
-#else
- TADDR oldPSP = *PTR_TADDR(callerSP + nOldPspSymStackSlot);
- _ASSERTE(oldPSP == callerSP);
-#endif
- }
-#endif // _DEBUG
-
-#else
- PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
-#endif
-
- // 2) Get all the info about current variables, registers, etc
-
- const ICorDebugInfo::NativeVarInfo * pOldVar;
-
- // sorted by varNumber
- ICorDebugInfo::NativeVarInfo * oldMethodVarsSorted = NULL;
- ICorDebugInfo::NativeVarInfo * oldMethodVarsSortedBase = NULL;
- ICorDebugInfo::NativeVarInfo *newMethodVarsSorted = NULL;
- ICorDebugInfo::NativeVarInfo *newMethodVarsSortedBase = NULL;
-
- SIZE_T *rgVal1 = NULL;
- SIZE_T *rgVal2 = NULL;
-
- {
- SIZE_T local;
-
- // We'll need to sort the old native var info by variable number, since the
- // order of them isn't necc. the same. We'll use the number as the key.
- // We will assume we may have hidden arguments (which have negative values as the index)
-
- unsigned oldNumVars = unsigned(-ICorDebugInfo::UNKNOWN_ILNUM);
- for (pOldVar = oldMethodVars, local = 0;
- local < oldMethodVarsCount;
- local++, pOldVar++)
- {
- DWORD varNumber = pOldVar->varNumber;
- if (signed(varNumber) >= 0)
- {
- // This is an explicit (not special) var, so add its varNumber + 1 to our
- // max count ("+1" because varNumber is zero-based).
- oldNumVars = max(oldNumVars, unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) + varNumber + 1);
- }
- }
-
- oldMethodVarsSortedBase = new (nothrow) ICorDebugInfo::NativeVarInfo[oldNumVars];
- if (!oldMethodVarsSortedBase)
- {
- hr = E_FAIL;
- goto ErrExit;
- }
- oldMethodVarsSorted = oldMethodVarsSortedBase + (-ICorDebugInfo::UNKNOWN_ILNUM);
-
- memset((void *)oldMethodVarsSortedBase, 0, oldNumVars * sizeof(ICorDebugInfo::NativeVarInfo));
-
- for (local = 0; local < oldNumVars;local++)
- oldMethodVarsSortedBase[local].loc.vlType = ICorDebugInfo::VLT_INVALID;
-
- BYTE **rgVCs = NULL;
- DWORD oldMethodOffset = pOldCodeInfo->GetRelOffset();
-
- for (pOldVar = oldMethodVars, local = 0;
- local < oldMethodVarsCount;
- local++, pOldVar++)
- {
- DWORD varNumber = pOldVar->varNumber;
-
- _ASSERTE(varNumber + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < oldNumVars);
-
- // Only care about old local variables alive at oldMethodOffset
- if (pOldVar->startOffset <= oldMethodOffset &&
- pOldVar->endOffset > oldMethodOffset)
- {
- // Indexing should be performed with a signed value - could be negative.
- oldMethodVarsSorted[(int32_t)varNumber] = *pOldVar;
- }
- }
-
- // 3) Next sort the new var info by varNumber. We want to do this here, since
- // we're allocating memory (which may fail) - do this before going to step 2
-
- // First, count the new vars the same way we did the old vars above.
-
- const ICorDebugInfo::NativeVarInfo * pNewVar;
-
- unsigned newNumVars = unsigned(-ICorDebugInfo::UNKNOWN_ILNUM);
- for (pNewVar = newMethodVars, local = 0;
- local < newMethodVarsCount;
- local++, pNewVar++)
- {
- DWORD varNumber = pNewVar->varNumber;
- if (signed(varNumber) >= 0)
- {
- // This is an explicit (not special) var, so add its varNumber + 1 to our
- // max count ("+1" because varNumber is zero-based).
- newNumVars = max(newNumVars, unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) + varNumber + 1);
- }
- }
-
- // sorted by varNumber
- newMethodVarsSortedBase = new (nothrow) ICorDebugInfo::NativeVarInfo[newNumVars];
- if (!newMethodVarsSortedBase)
- {
- hr = E_FAIL;
- goto ErrExit;
- }
- newMethodVarsSorted = newMethodVarsSortedBase + (-ICorDebugInfo::UNKNOWN_ILNUM);
-
- memset(newMethodVarsSortedBase, 0, newNumVars * sizeof(ICorDebugInfo::NativeVarInfo));
- for (local = 0; local < newNumVars;local++)
- newMethodVarsSortedBase[local].loc.vlType = ICorDebugInfo::VLT_INVALID;
-
- DWORD newMethodOffset = pNewCodeInfo->GetRelOffset();
-
- for (pNewVar = newMethodVars, local = 0;
- local < newMethodVarsCount;
- local++, pNewVar++)
- {
- DWORD varNumber = pNewVar->varNumber;
-
- _ASSERTE(varNumber + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < newNumVars);
-
- // Only care about new local variables alive at newMethodOffset
- if (pNewVar->startOffset <= newMethodOffset &&
- pNewVar->endOffset > newMethodOffset)
- {
- // Indexing should be performed with a signed valued - could be negative.
- newMethodVarsSorted[(int32_t)varNumber] = *pNewVar;
- }
- }
-
- _ASSERTE(newNumVars >= oldNumVars ||
- !"Not allowed to reduce the number of locals between versions!");
-
- LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: gathered info!\n"));
-
- rgVal1 = new (nothrow) SIZE_T[newNumVars];
- if (rgVal1 == NULL)
- {
- hr = E_FAIL;
- goto ErrExit;
- }
-
- rgVal2 = new (nothrow) SIZE_T[newNumVars];
- if (rgVal2 == NULL)
- {
- hr = E_FAIL;
- goto ErrExit;
- }
-
- // 4) Next we'll zero them out, so any variables that aren't in scope
- // in the old method, but are in scope in the new, will have the
- // default, zero, value.
-
- memset(rgVal1, 0, sizeof(SIZE_T) * newNumVars);
- memset(rgVal2, 0, sizeof(SIZE_T) * newNumVars);
-
- unsigned varsToGet = (oldNumVars > newNumVars)
- ? newNumVars
- : oldNumVars;
-
- // 2) Get all the info about current variables, registers, etc.
-
- hr = g_pDebugInterface->GetVariablesFromOffset(pOldCodeInfo->GetMethodDesc(),
- varsToGet,
- oldMethodVarsSortedBase,
- oldMethodOffset,
- &oldCtx,
- rgVal1,
- rgVal2,
- newNumVars,
- &rgVCs);
- if (FAILED(hr))
- {
- goto ErrExit;
- }
-
-
- LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: got vars!\n"));
-
- /*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
- * IMPORTANT : Once we start munging on the context, we cannot return
- * EnC_FAIL, as this should be a transacted commit,
- **=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/
-
-#if defined(TARGET_X86)
- // Zero out all the registers as some may hold new variables.
- pCtx->Eax = pCtx->Ecx = pCtx->Edx = pCtx->Ebx = pCtx->Esi = pCtx->Edi = 0;
-
- // 3) zero out the stack frame - this'll initialize _all_ variables
-
- /*-------------------------------------------------------------------------
- * Adjust the stack height
- */
- pCtx->Esp -= (newInfo.stackSize - oldInfo.stackSize);
-
- // Zero-init the local and tempory section of new stack frame being careful to avoid
- // touching anything in the frame header.
- // This is necessary to ensure that any JIT temporaries in the old version can't be mistaken
- // for ObjRefs now.
- size_t frameHeaderSize = GetSizeOfFrameHeaderForEnC( &newInfo );
- _ASSERTE( frameHeaderSize <= oldInfo.stackSize );
- _ASSERTE( GetSizeOfFrameHeaderForEnC( &oldInfo ) == frameHeaderSize );
-
-#elif defined(TARGET_AMD64) && !defined(UNIX_AMD64_ABI)
-
- // Next few statements zero out all registers that may end up holding new variables.
-
- // volatile int registers (JIT may use these to enregister variables)
- pCtx->Rax = pCtx->Rcx = pCtx->Rdx = pCtx->R8 = pCtx->R9 = pCtx->R10 = pCtx->R11 = 0;
-
- // volatile float registers
- pCtx->Xmm1.High = pCtx->Xmm1.Low = 0;
- pCtx->Xmm2.High = pCtx->Xmm2.Low = 0;
- pCtx->Xmm3.High = pCtx->Xmm3.Low = 0;
- pCtx->Xmm4.High = pCtx->Xmm4.Low = 0;
- pCtx->Xmm5.High = pCtx->Xmm5.Low = 0;
-
- // 3) zero out the stack frame - this'll initialize _all_ variables
-
- /*-------------------------------------------------------------------------
- * Adjust the stack height
- */
-
- TADDR newStackBase = callerSP - newFixedStackSize;
-
- SetSP(pCtx, newStackBase);
-
- // We want to zero-out everything pushed after the frame header. This way we'll zero
- // out locals (both old & new) and temporaries. This is necessary to ensure that any
- // JIT temporaries in the old version can't be mistaken for ObjRefs now. (I am told
- // this last point is less of an issue on x64 as it is on x86, but zeroing out the
- // temporaries is still the cleanest, most robust way to go.)
- size_t frameHeaderSize = newSizeOfPreservedArea;
- _ASSERTE(frameHeaderSize <= oldFixedStackSize);
- _ASSERTE(frameHeaderSize <= newFixedStackSize);
-
- // For EnC-compliant x64 code, FP == SP. Since SP changed above, update FP now
- pCtx->Rbp = newStackBase;
-
-#else
-#if defined(TARGET_ARM64)
- // Zero out volatile part of stack frame
- // x0-x17
- memset(&pCtx->X[0], 0, sizeof(pCtx->X[0]) * 18);
- // v0-v7
- memset(&pCtx->V[0], 0, sizeof(pCtx->V[0]) * 8);
- // v16-v31
- memset(&pCtx->V[16], 0, sizeof(pCtx->V[0]) * 16);
-#elif defined(TARGET_AMD64)
- // SysV ABI
- pCtx->Rax = pCtx->Rdi = pCtx->Rsi = pCtx->Rdx = pCtx->Rcx = pCtx->R8 = pCtx->R9 = 0;
-
- // volatile float registers
- memset(&pCtx->Xmm0, 0, sizeof(pCtx->Xmm0) * 16);
-#else
- PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
-#endif
-
- TADDR newStackBase = callerSP - newFixedStackSize;
-
- SetSP(pCtx, newStackBase);
-
- size_t frameHeaderSize = newSizeOfPreservedArea;
- _ASSERTE(frameHeaderSize <= oldFixedStackSize);
- _ASSERTE(frameHeaderSize <= newFixedStackSize);
-
- // EnC prolog saves only FP (and LR on arm64), and FP points to saved FP for frame chaining.
- // These should already be set up from previous version.
- _ASSERTE(GetFP(pCtx) == callerSP - 16);
-#endif
-
- // Perform some debug-only sanity checks on stack variables. Some checks are
- // performed differently between X86/AMD64.
-
-#ifdef _DEBUG
- for( unsigned i = 0; i < newNumVars; i++ )
- {
- // Make sure that stack variables existing in both old and new methods did not
- // move. This matters if the address of a local is used in the remapped method.
- // For example:
- //
- // static unsafe void Main(string[] args)
- // {
- // int x;
- // int* p = &x;
- // <- Edit made here - cannot move address of x
- // *p = 5;
- // }
- //
- if ((i + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < oldNumVars) && // Does variable exist in old method?
- (oldMethodVarsSorted[i].loc.vlType == ICorDebugInfo::VLT_STK) && // Is the variable on the stack?
- (newMethodVarsSorted[i].loc.vlType == ICorDebugInfo::VLT_STK))
- {
- SIZE_T * pOldVarStackLocation = NativeVarStackAddr(oldMethodVarsSorted[i].loc, &oldCtx);
- SIZE_T * pNewVarStackLocation = NativeVarStackAddr(newMethodVarsSorted[i].loc, pCtx);
- _ASSERTE(pOldVarStackLocation == pNewVarStackLocation);
- }
-
- // Sanity-check that the range we're clearing contains all of the stack variables
-
-#if defined(TARGET_X86)
- const ICorDebugInfo::VarLoc &varLoc = newMethodVarsSortedBase[i].loc;
- if( varLoc.vlType == ICorDebugInfo::VLT_STK )
- {
- // This is an EBP frame, all stack variables should be EBP relative
- _ASSERTE( varLoc.vlStk.vlsBaseReg == ICorDebugInfo::REGNUM_EBP );
- // Generic special args may show up as locals with positive offset from EBP, so skip them
- if( varLoc.vlStk.vlsOffset <= 0 )
- {
- // Normal locals must occur after the header on the stack
- _ASSERTE( unsigned(-varLoc.vlStk.vlsOffset) >= frameHeaderSize );
- // Value must occur before the top of the stack
- _ASSERTE( unsigned(-varLoc.vlStk.vlsOffset) < newInfo.stackSize );
- }
-
- // Ideally we'd like to verify that the stack locals (if any) start at exactly the end
- // of the header. However, we can't easily determine the size of value classes here,
- // and so (since the stack grows towards 0) can't easily determine where the end of
- // the local lies.
- }
-#elif defined(TARGET_AMD64) || defined(TARGET_ARM64)
- switch(newMethodVarsSortedBase[i].loc.vlType)
- {
- default:
- // No validation here for non-stack locals
- break;
-
- case ICorDebugInfo::VLT_STK_BYREF:
- {
- // For byrefs, verify that the ptr will be zeroed out
-
- SIZE_T regOffs = GetRegOffsInCONTEXT(newMethodVarsSortedBase[i].loc.vlStk.vlsBaseReg);
- TADDR baseReg = *(TADDR *)(regOffs + (BYTE*)pCtx);
- TADDR addrOfPtr = baseReg + newMethodVarsSortedBase[i].loc.vlStk.vlsOffset;
-
- _ASSERTE(
- // The ref must exist in the portion we'll zero-out
- (
- (newStackBase <= addrOfPtr) &&
- (addrOfPtr < newStackBase + (newFixedStackSize - frameHeaderSize))
- ) ||
- // OR in the caller's frame (for parameters)
- (addrOfPtr >= newStackBase + newFixedStackSize));
-
- // Deliberately fall through, so that we also verify that the value that the ptr
- // points to will be zeroed out
- // ...
- }
- __fallthrough;
-
- case ICorDebugInfo::VLT_STK:
- case ICorDebugInfo::VLT_STK2:
- case ICorDebugInfo::VLT_REG_STK:
- case ICorDebugInfo::VLT_STK_REG:
- SIZE_T * pVarStackLocation = NativeVarStackAddr(newMethodVarsSortedBase[i].loc, pCtx);
- _ASSERTE (pVarStackLocation != NULL);
- _ASSERTE(
- // The value must exist in the portion we'll zero-out
- (
- (newStackBase <= (TADDR) pVarStackLocation) &&
- ((TADDR) pVarStackLocation < newStackBase + (newFixedStackSize - frameHeaderSize))
- ) ||
- // OR in the caller's frame (for parameters)
- ((TADDR) pVarStackLocation >= newStackBase + newFixedStackSize));
- break;
- }
-#else // !X86, !X64, !ARM64
- PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
-#endif
- }
-
-#endif // _DEBUG
-
- // Clear the local and temporary stack space
-
-#if defined(TARGET_X86)
- memset((void*)(size_t)(pCtx->Esp), 0, newInfo.stackSize - frameHeaderSize );
-#elif defined(TARGET_AMD64) || defined(TARGET_ARM64)
- memset((void*)newStackBase, 0, newFixedStackSize - frameHeaderSize);
-
- // Restore PSPSym for the new function. Its value should be set to our new FP. But
- // first, we gotta find PSPSym's location on the stack
- INT32 nNewPspSymStackSlot = newGcDecoder.GetPSPSymStackSlot();
- if (nNewPspSymStackSlot != NO_PSP_SYM)
- {
-#if defined(TARGET_AMD64)
- *PTR_TADDR(newStackBase + nNewPspSymStackSlot) = newStackBase;
-#elif defined(TARGET_ARM64)
- *PTR_TADDR(callerSP + nNewPspSymStackSlot) = callerSP;
-#else
- PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
-#endif
- }
-#else // !X86, !X64, !ARM64
- PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
-#endif
-
- // 4) Put the variables from step 3 into their new locations.
-
- LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: set vars!\n"));
-
- // Move the old variables into their new places.
-
- hr = g_pDebugInterface->SetVariablesAtOffset(pNewCodeInfo->GetMethodDesc(),
- newNumVars,
- newMethodVarsSortedBase,
- newMethodOffset,
- pCtx, // place them into the new context
- rgVal1,
- rgVal2,
- rgVCs);
-
- /*-----------------------------------------------------------------------*/
- }
-ErrExit:
- if (oldMethodVarsSortedBase)
- delete[] oldMethodVarsSortedBase;
- if (newMethodVarsSortedBase)
- delete[] newMethodVarsSortedBase;
- if (rgVal1 != NULL)
- delete[] rgVal1;
- if (rgVal2 != NULL)
- delete[] rgVal2;
-
- LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: exiting!\n"));
-
- return hr;
-}
-#endif // !FEATURE_METADATA_UPDATER
-
-#endif // #ifndef DACCESS_COMPILE
-
-#ifdef USE_GC_INFO_DECODER
-/*****************************************************************************
- *
- * Is the function currently at a "GC safe point" ?
- */
-bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo,
- DWORD dwRelOffset)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- } CONTRACTL_END;
-
- GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
-
- GcInfoDecoder gcInfoDecoder(
- gcInfoToken,
- DECODE_INTERRUPTIBILITY,
- dwRelOffset
- );
-
- return gcInfoDecoder.IsInterruptible();
-}
-
-#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
-bool EECodeManager::HasTailCalls( EECodeInfo *pCodeInfo)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- } CONTRACTL_END;
-
- GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
-
- GcInfoDecoder gcInfoDecoder(
- gcInfoToken,
- DECODE_HAS_TAILCALLS,
- 0
- );
-
- return gcInfoDecoder.HasTailCalls();
-}
-#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64
-
-#if defined(TARGET_AMD64) && defined(_DEBUG)
-
-struct FindEndOfLastInterruptibleRegionState
-{
- unsigned curOffset;
- unsigned endOffset;
- unsigned lastRangeOffset;
-};
-
-bool FindEndOfLastInterruptibleRegionCB (
- UINT32 startOffset,
- UINT32 stopOffset,
- LPVOID hCallback)
-{
- FindEndOfLastInterruptibleRegionState *pState = (FindEndOfLastInterruptibleRegionState*)hCallback;
-
- //
- // If the current range doesn't overlap the given range, keep searching.
- //
- if ( startOffset >= pState->endOffset
- || stopOffset < pState->curOffset)
- {
- return false;
- }
-
- //
- // If the range overlaps the end, then the last point is the end.
- //
- if ( stopOffset > pState->endOffset
- /*&& startOffset < pState->endOffset*/)
- {
- // The ranges should be sorted in increasing order.
- CONSISTENCY_CHECK(startOffset >= pState->lastRangeOffset);
-
- pState->lastRangeOffset = pState->endOffset;
- return true;
- }
-
- //
- // See if the end of this range is the closet to the end that we've found
- // so far.
- //
- if (stopOffset > pState->lastRangeOffset)
- pState->lastRangeOffset = stopOffset;
-
- return false;
-}
-
-/*
- Locates the end of the last interruptible region in the given code range.
- Returns 0 if the entire range is uninterruptible. Returns the end point
- if the entire range is interruptible.
-*/
-unsigned EECodeManager::FindEndOfLastInterruptibleRegion(unsigned curOffset,
- unsigned endOffset,
- GCInfoToken gcInfoToken)
-{
-#ifndef DACCESS_COMPILE
- GcInfoDecoder gcInfoDecoder(
- gcInfoToken,
- DECODE_FOR_RANGES_CALLBACK
- );
-
- FindEndOfLastInterruptibleRegionState state;
- state.curOffset = curOffset;
- state.endOffset = endOffset;
- state.lastRangeOffset = 0;
-
- gcInfoDecoder.EnumerateInterruptibleRanges(&FindEndOfLastInterruptibleRegionCB, &state);
-
- return state.lastRangeOffset;
-#else
- DacNotImpl();
- return NULL;
-#endif // #ifndef DACCESS_COMPILE
-}
-
-#endif // TARGET_AMD64 && _DEBUG
-
-
-#else // !USE_GC_INFO_DECODER
-
-/*****************************************************************************
- *
- * Is the function currently at a "GC safe point" ?
- */
-bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo,
- DWORD dwRelOffset)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- SUPPORTS_DAC;
- } CONTRACTL_END;
-
- hdrInfo info;
- BYTE * table;
-
- /* Extract the necessary information from the info block header */
-
- table = (BYTE *)DecodeGCHdrInfo(pCodeInfo->GetGCInfoToken(),
- dwRelOffset,
- &info);
-
- /* workaround: prevent interruption within prolog/epilog */
-
- if (info.prologOffs != hdrInfo::NOT_IN_PROLOG || info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
- return false;
-
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
-#endif
-
- return (info.interruptible);
-}
-
-
-/*****************************************************************************/
-static
-PTR_CBYTE skipToArgReg(const hdrInfo& info, PTR_CBYTE table)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- SUPPORTS_DAC;
- } CONTRACTL_END;
-
-#ifdef _DEBUG
- PTR_CBYTE tableStart = table;
-#else
- if (info.argTabOffset != INVALID_ARGTAB_OFFSET)
- {
- return table + info.argTabOffset;
- }
-#endif
-
- unsigned count;
-
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
-#endif
-
- /* Skip over the untracked frame variable table */
-
- count = info.untrackedCnt;
- while (count-- > 0) {
- fastSkipSigned(table);
- }
-
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xCAFE);
-#endif
-
- /* Skip over the frame variable lifetime table */
-
- count = info.varPtrTableSize;
- while (count-- > 0) {
- fastSkipUnsigned(table); fastSkipUnsigned(table); fastSkipUnsigned(table);
- }
-
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *) == 0xBABE);
-#endif
-
-#ifdef _DEBUG
- if (info.argTabOffset != INVALID_ARGTAB_OFFSET)
- {
- CONSISTENCY_CHECK_MSGF((info.argTabOffset == (unsigned) (table - tableStart)),
- ("table = %p, tableStart = %p, info.argTabOffset = %d", table, tableStart, info.argTabOffset));
- }
-#endif
-
- return table;
-}
-
-/*****************************************************************************/
-
-#define regNumToMask(regNum) RegMask(1<<(regNum))
-
-/*****************************************************************************
- Helper for scanArgRegTable() and scanArgRegTableI() for regMasks
- */
-
-void * getCalleeSavedReg(PREGDISPLAY pContext, regNum reg)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
- switch (reg)
- {
- case REGI_EBP: return pContext->GetEbpLocation();
- case REGI_EBX: return pContext->GetEbxLocation();
- case REGI_ESI: return pContext->GetEsiLocation();
- case REGI_EDI: return pContext->GetEdiLocation();
-
- default: _ASSERTE(!"bad info.thisPtrResult"); return NULL;
- }
-}
-
-/*****************************************************************************
- These functions converts the bits in the GC encoding to RegMask
- */
-
-inline
-RegMask convertCalleeSavedRegsMask(unsigned inMask) // EBP,EBX,ESI,EDI
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
- _ASSERTE((inMask & 0x0F) == inMask);
-
- unsigned outMask = RM_NONE;
- if (inMask & 0x1) outMask |= RM_EDI;
- if (inMask & 0x2) outMask |= RM_ESI;
- if (inMask & 0x4) outMask |= RM_EBX;
- if (inMask & 0x8) outMask |= RM_EBP;
-
- return (RegMask) outMask;
-}
-
-inline
-RegMask convertAllRegsMask(unsigned inMask) // EAX,ECX,EDX,EBX, EBP,ESI,EDI
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
- _ASSERTE((inMask & 0xEF) == inMask);
-
- unsigned outMask = RM_NONE;
- if (inMask & 0x01) outMask |= RM_EAX;
- if (inMask & 0x02) outMask |= RM_ECX;
- if (inMask & 0x04) outMask |= RM_EDX;
- if (inMask & 0x08) outMask |= RM_EBX;
- if (inMask & 0x20) outMask |= RM_EBP;
- if (inMask & 0x40) outMask |= RM_ESI;
- if (inMask & 0x80) outMask |= RM_EDI;
-
- return (RegMask)outMask;
-}
-
-/*****************************************************************************
- * scan the register argument table for the not fully interruptible case.
- this function is called to find all live objects (pushed arguments)
- and to get the stack base for EBP-less methods.
-
- NOTE: If info->argTabResult is NULL, info->argHnumResult indicates
- how many bits in argMask are valid
- If info->argTabResult is non-NULL, then the argMask field does
- not fit in 32-bits and the value in argMask meaningless.
- Instead argHnum specifies the number of (variable-length) elements
- in the array, and argTabBytes specifies the total byte size of the
- array. [ Note this is an extremely rare case ]
- */
-
-#ifdef _PREFAST_
-#pragma warning(push)
-#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
-#endif
-static
-unsigned scanArgRegTable(PTR_CBYTE table,
- unsigned curOffs,
- hdrInfo * info)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- SUPPORTS_DAC;
- } CONTRACTL_END;
-
- regNum thisPtrReg = REGI_NA;
-#ifdef _DEBUG
- bool isCall = false;
-#endif
- unsigned regMask = 0; // EBP,EBX,ESI,EDI
- unsigned argMask = 0;
- unsigned argHnum = 0;
- PTR_CBYTE argTab = 0;
- unsigned argTabBytes = 0;
- unsigned stackDepth = 0;
-
- unsigned iregMask = 0; // EBP,EBX,ESI,EDI
- unsigned iargMask = 0;
- unsigned iptrMask = 0;
-
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
-#endif
-
- unsigned scanOffs = 0;
-
- _ASSERTE(scanOffs <= info->methodSize);
-
- if (info->ebpFrame) {
- /*
- Encoding table for methods with an EBP frame and
- that are not fully interruptible
-
- The encoding used is as follows:
-
- this pointer encodings:
-
- 01000000 this pointer in EBX
- 00100000 this pointer in ESI
- 00010000 this pointer in EDI
-
- tiny encoding:
-
- 0bsdDDDD
- requires code delta < 16 (4-bits)
- requires pushed argmask == 0
-
- where DDDD is code delta
- b indicates that register EBX is a live pointer
- s indicates that register ESI is a live pointer
- d indicates that register EDI is a live pointer
-
- small encoding:
-
- 1DDDDDDD bsdAAAAA
-
- requires code delta < 120 (7-bits)
- requires pushed argmask < 64 (5-bits)
-
- where DDDDDDD is code delta
- AAAAA is the pushed args mask
- b indicates that register EBX is a live pointer
- s indicates that register ESI is a live pointer
- d indicates that register EDI is a live pointer
-
- medium encoding
-
- 0xFD aaaaaaaa AAAAdddd bseDDDDD
-
- requires code delta < 0x1000000000 (9-bits)
- requires pushed argmask < 0x1000000000000 (12-bits)
-
- where DDDDD is the upper 5-bits of the code delta
- dddd is the low 4-bits of the code delta
- AAAA is the upper 4-bits of the pushed arg mask
- aaaaaaaa is the low 8-bits of the pushed arg mask
- b indicates that register EBX is a live pointer
- s indicates that register ESI is a live pointer
- e indicates that register EDI is a live pointer
-
- medium encoding with interior pointers
-
- 0xF9 DDDDDDDD bsdAAAAAA iiiIIIII
-
- requires code delta < (8-bits)
- requires pushed argmask < (5-bits)
-
- where DDDDDDD is the code delta
- b indicates that register EBX is a live pointer
- s indicates that register ESI is a live pointer
- d indicates that register EDI is a live pointer
- AAAAA is the pushed arg mask
- iii indicates that EBX,EDI,ESI are interior pointers
- IIIII indicates that bits is the arg mask are interior
- pointers
-
- large encoding
-
- 0xFE [0BSD0bsd][32-bit code delta][32-bit argMask]
-
- b indicates that register EBX is a live pointer
- s indicates that register ESI is a live pointer
- d indicates that register EDI is a live pointer
- B indicates that register EBX is an interior pointer
- S indicates that register ESI is an interior pointer
- D indicates that register EDI is an interior pointer
- requires pushed argmask < 32-bits
-
- large encoding with interior pointers
-
- 0xFA [0BSD0bsd][32-bit code delta][32-bit argMask][32-bit interior pointer mask]
-
-
- b indicates that register EBX is a live pointer
- s indicates that register ESI is a live pointer
- d indicates that register EDI is a live pointer
- B indicates that register EBX is an interior pointer
- S indicates that register ESI is an interior pointer
- D indicates that register EDI is an interior pointer
- requires pushed argmask < 32-bits
- requires pushed iArgmask < 32-bits
-
- huge encoding This is the only encoding that supports
- a pushed argmask which is greater than
- 32-bits.
-
- 0xFB [0BSD0bsd][32-bit code delta]
- [32-bit table count][32-bit table size]
- [pushed ptr offsets table...]
-
- b indicates that register EBX is a live pointer
- s indicates that register ESI is a live pointer
- d indicates that register EDI is a live pointer
- B indicates that register EBX is an interior pointer
- S indicates that register ESI is an interior pointer
- D indicates that register EDI is an interior pointer
- the list count is the number of entries in the list
- the list size gives the byte-length of the list
- the offsets in the list are variable-length
- */
- while (scanOffs < curOffs)
- {
- iregMask = 0;
- iargMask = 0;
- argTab = NULL;
-#ifdef _DEBUG
- isCall = true;
-#endif
-
- /* Get the next byte and check for a 'special' entry */
-
- unsigned encType = *table++;
-#if defined(DACCESS_COMPILE)
- // In this scenario, it is invalid to have a zero byte in the GC info encoding (refer to the
- // comments above). At least one bit has to be set. For example, a byte can represent which
- // register is the "this" pointer, and this byte has to be 0x10, 0x20, or 0x40. Having a zero
- // byte indicates there is most likely some sort of DAC error, and it may lead to problems such as
- // infinite loops. So we bail out early instead.
- if (encType == 0)
- {
- DacError(CORDBG_E_TARGET_INCONSISTENT);
- UNREACHABLE();
- }
-#endif // DACCESS_COMPILE
-
- switch (encType)
- {
- unsigned val, nxt;
-
- default:
-
- /* A tiny or small call entry */
- val = encType;
- if ((val & 0x80) == 0x00) {
- if (val & 0x0F) {
- /* A tiny call entry */
- scanOffs += (val & 0x0F);
- regMask = (val & 0x70) >> 4;
- argMask = 0;
- argHnum = 0;
- }
- else {
- /* This pointer liveness encoding */
- regMask = (val & 0x70) >> 4;
- if (regMask == 0x1)
- thisPtrReg = REGI_EDI;
- else if (regMask == 0x2)
- thisPtrReg = REGI_ESI;
- else if (regMask == 0x4)
- thisPtrReg = REGI_EBX;
- else
- _ASSERTE(!"illegal encoding for 'this' pointer liveness");
- }
- }
- else {
- /* A small call entry */
- scanOffs += (val & 0x7F);
- val = *table++;
- regMask = val >> 5;
- argMask = val & 0x1F;
- argHnum = 5;
- }
- break;
-
- case 0xFD: // medium encoding
-
- argMask = *table++;
- val = *table++;
- argMask |= ((val & 0xF0) << 4);
- argHnum = 12;
- nxt = *table++;
- scanOffs += (val & 0x0F) + ((nxt & 0x1F) << 4);
- regMask = nxt >> 5; // EBX,ESI,EDI
-
- break;
-
- case 0xF9: // medium encoding with interior pointers
-
- scanOffs += *table++;
- val = *table++;
- argMask = val & 0x1F;
- argHnum = 5;
- regMask = val >> 5;
- val = *table++;
- iargMask = val & 0x1F;
- iregMask = val >> 5;
-
- break;
-
- case 0xFE: // large encoding
- case 0xFA: // large encoding with interior pointers
-
- val = *table++;
- regMask = val & 0x7;
- iregMask = val >> 4;
- scanOffs += *dac_cast(table); table += sizeof(DWORD);
- argMask = *dac_cast(table); table += sizeof(DWORD);
- argHnum = 31;
- if (encType == 0xFA) // read iargMask
- {
- iargMask = *dac_cast(table); table += sizeof(DWORD);
- }
- break;
-
- case 0xFB: // huge encoding This is the only partially interruptible
- // encoding that supports a pushed ArgMask
- // which is greater than 32-bits.
- // The ArgMask is encoded using the argTab
- val = *table++;
- regMask = val & 0x7;
- iregMask = val >> 4;
- scanOffs += *dac_cast(table); table += sizeof(DWORD);
- argHnum = *dac_cast(table); table += sizeof(DWORD);
- argTabBytes = *dac_cast(table); table += sizeof(DWORD);
- argTab = table; table += argTabBytes;
-
- argMask = 0;
- break;
-
- case 0xFF:
- scanOffs = curOffs + 1;
- break;
-
- } // end case
-
- // iregMask & iargMask are subsets of regMask & argMask respectively
-
- _ASSERTE((iregMask & regMask) == iregMask);
- _ASSERTE((iargMask & argMask) == iargMask);
-
- } // end while
-
- }
- else {
-
-/*
- * Encoding table for methods with an ESP frame and are not fully interruptible
- * This encoding does not support a pushed ArgMask greater than 32
- *
- * The encoding used is as follows:
- *
- * push 000DDDDD ESP push one item with 5-bit delta
- * push 00100000 [pushCount] ESP push multiple items
- * reserved 0011xxxx
- * skip 01000000 [Delta] Skip Delta, arbitrary sized delta
- * skip 0100DDDD Skip small Delta, for call (DDDD != 0)
- * pop 01CCDDDD ESP pop CC items with 4-bit delta (CC != 00)
- * call 1PPPPPPP Call Pattern, P=[0..79]
- * call 1101pbsd DDCCCMMM Call RegMask=pbsd,ArgCnt=CCC,
- * ArgMask=MMM Delta=commonDelta[DD]
- * call 1110pbsd [ArgCnt] [ArgMask] Call ArgCnt,RegMask=pbsd,[32-bit ArgMask]
- * call 11111000 [PBSDpbsd][32-bit delta][32-bit ArgCnt]
- * [32-bit PndCnt][32-bit PndSize][PndOffs...]
- * iptr 11110000 [IPtrMask] Arbitrary 32-bit Interior Pointer Mask
- * thisptr 111101RR This pointer is in Register RR
- * 00=EDI,01=ESI,10=EBX,11=EBP
- * reserved 111100xx xx != 00
- * reserved 111110xx xx != 00
- * reserved 11111xxx xxx != 000 && xxx != 111(EOT)
- *
- * The value 11111111 [0xFF] indicates the end of the table.
- *
- * An offset (at which stack-walking is performed) without an explicit encoding
- * is assumed to be a trivial call-site (no GC registers, stack empty before and
- * after) to avoid having to encode all trivial calls.
- *
- * Note on the encoding used for interior pointers
- *
- * The iptr encoding must immediately precede a call encoding. It is used to
- * transform a normal GC pointer addresses into an interior pointers for GC purposes.
- * The mask supplied to the iptr encoding is read from the least signicant bit
- * to the most signicant bit. (i.e the lowest bit is read first)
- *
- * p indicates that register EBP is a live pointer
- * b indicates that register EBX is a live pointer
- * s indicates that register ESI is a live pointer
- * d indicates that register EDI is a live pointer
- * P indicates that register EBP is an interior pointer
- * B indicates that register EBX is an interior pointer
- * S indicates that register ESI is an interior pointer
- * D indicates that register EDI is an interior pointer
- *
- * As an example the following sequence indicates that EDI.ESI and the 2nd pushed pointer
- * in ArgMask are really interior pointers. The pointer in ESI in a normal pointer:
- *
- * iptr 11110000 00010011 => read Interior Ptr, Interior Ptr, Normal Ptr, Normal Ptr, Interior Ptr
- * call 11010011 DDCCC011 RRRR=1011 => read EDI is a GC-pointer, ESI is a GC-pointer. EBP is a GC-pointer
- * MMM=0011 => read two GC-pointers arguments on the stack (nested call)
- *
- * Since the call instruction mentions 5 GC-pointers we list them in the required order:
- * EDI, ESI, EBP, 1st-pushed pointer, 2nd-pushed pointer
- *
- * And we apply the Interior Pointer mask mmmm=10011 to the above five ordered GC-pointers
- * we learn that EDI and ESI are interior GC-pointers and that the second push arg is an
- * interior GC-pointer.
- */
-
-#if defined(DACCESS_COMPILE)
- DWORD cbZeroBytes = 0;
-#endif // DACCESS_COMPILE
-
- while (scanOffs <= curOffs)
- {
- unsigned callArgCnt;
- unsigned skip;
- unsigned newRegMask, inewRegMask;
- unsigned newArgMask, inewArgMask;
- unsigned oldScanOffs = scanOffs;
-
- if (iptrMask)
- {
- // We found this iptrMask in the previous iteration.
- // This iteration must be for a call. Set these variables
- // so that they are available at the end of the loop
-
- inewRegMask = iptrMask & 0x0F; // EBP,EBX,ESI,EDI
- inewArgMask = iptrMask >> 4;
-
- iptrMask = 0;
- }
- else
- {
- // Zero out any stale values.
-
- inewRegMask = 0;
- inewArgMask = 0;
- }
-
- /* Get the next byte and decode it */
-
- unsigned val = *table++;
-#if defined(DACCESS_COMPILE)
- // In this scenario, a 0 means that there is a push at the current offset. For a struct with
- // two double fields, the JIT may use two movq instructions to push the struct onto the stack, and
- // the JIT will encode 4 pushes at the same code offset. This means that we can have up to 4
- // consecutive bytes of 0 without changing the code offset. Having more than 4 consecutive bytes
- // of zero indicates that there is most likely some sort of DAC error, and it may lead to problems
- // such as infinite loops. So we bail out early instead.
- if (val == 0)
- {
- cbZeroBytes += 1;
- if (cbZeroBytes > 4)
- {
- DacError(CORDBG_E_TARGET_INCONSISTENT);
- UNREACHABLE();
- }
- }
- else
- {
- cbZeroBytes = 0;
- }
-#endif // DACCESS_COMPILE
-
-#ifdef _DEBUG
- if (scanOffs != curOffs)
- isCall = false;
-#endif
-
- /* Check pushes, pops, and skips */
-
- if (!(val & 0x80)) {
-
- // iptrMask can immediately precede only calls
-
- _ASSERTE(inewRegMask == 0);
- _ASSERTE(inewArgMask == 0);
-
- if (!(val & 0x40)) {
-
- unsigned pushCount;
-
- if (!(val & 0x20))
- {
- //
- // push 000DDDDD ESP push one item, 5-bit delta
- //
- pushCount = 1;
- scanOffs += val & 0x1f;
- }
- else
- {
- //
- // push 00100000 [pushCount] ESP push multiple items
- //
- _ASSERTE(val == 0x20);
- pushCount = fastDecodeUnsigned(table);
- }
-
- if (scanOffs > curOffs)
- {
- scanOffs = oldScanOffs;
- goto FINISHED;
- }
-
- stackDepth += pushCount;
- }
- else if ((val & 0x3f) != 0) {
- //
- // pop 01CCDDDD pop CC items, 4-bit delta
- //
- scanOffs += val & 0x0f;
- if (scanOffs > curOffs)
- {
- scanOffs = oldScanOffs;
- goto FINISHED;
- }
- stackDepth -= (val & 0x30) >> 4;
-
- } else if (scanOffs < curOffs) {
- //
- // skip 01000000 [Delta] Skip arbitrary sized delta
- //
- skip = fastDecodeUnsigned(table);
- scanOffs += skip;
- }
- else // don't process a skip if we are already at curOffs
- goto FINISHED;
-
- /* reset regs and args state since we advance past last call site */
-
- regMask = 0;
- iregMask = 0;
- argMask = 0;
- iargMask = 0;
- argHnum = 0;
-
- }
- else /* It must be a call, thisptr, or iptr */
- {
- switch ((val & 0x70) >> 4) {
- default: // case 0-4, 1000xxxx through 1100xxxx
- //
- // call 1PPPPPPP Call Pattern, P=[0..79]
- //
- decodeCallPattern((val & 0x7f), &callArgCnt,
- &newRegMask, &newArgMask, &skip);
- // If we've already reached curOffs and the skip amount
- // is non-zero then we are done
- if ((scanOffs == curOffs) && (skip > 0))
- goto FINISHED;
- // otherwise process this call pattern
- scanOffs += skip;
- if (scanOffs > curOffs)
- goto FINISHED;
-#ifdef _DEBUG
- isCall = true;
-#endif
- regMask = newRegMask;
- argMask = newArgMask; argTab = NULL;
- iregMask = inewRegMask;
- iargMask = inewArgMask;
- stackDepth -= callArgCnt;
- argHnum = 2; // argMask is known to be <= 3
- break;
-
- case 5:
- //
- // call 1101RRRR DDCCCMMM Call RegMask=RRRR,ArgCnt=CCC,
- // ArgMask=MMM Delta=commonDelta[DD]
- //
- newRegMask = val & 0xf; // EBP,EBX,ESI,EDI
- val = *table++; // read next byte
- skip = callCommonDelta[val>>6];
- // If we've already reached curOffs and the skip amount
- // is non-zero then we are done
- if ((scanOffs == curOffs) && (skip > 0))
- goto FINISHED;
- // otherwise process this call encoding
- scanOffs += skip;
- if (scanOffs > curOffs)
- goto FINISHED;
-#ifdef _DEBUG
- isCall = true;
-#endif
- regMask = newRegMask;
- iregMask = inewRegMask;
- callArgCnt = (val >> 3) & 0x7;
- stackDepth -= callArgCnt;
- argMask = (val & 0x7); argTab = NULL;
- iargMask = inewArgMask;
- argHnum = 3;
- break;
-
- case 6:
- //
- // call 1110RRRR [ArgCnt] [ArgMask]
- // Call ArgCnt,RegMask=RRR,ArgMask
- //
-#ifdef _DEBUG
- isCall = true;
-#endif
- regMask = val & 0xf; // EBP,EBX,ESI,EDI
- iregMask = inewRegMask;
- callArgCnt = fastDecodeUnsigned(table);
- stackDepth -= callArgCnt;
- argMask = fastDecodeUnsigned(table); argTab = NULL;
- iargMask = inewArgMask;
- argHnum = sizeof(argMask) * 8; // The size of argMask in bits
- break;
-
- case 7:
- switch (val & 0x0C)
- {
- case 0x00:
- //
- // 0xF0 iptr 11110000 [IPtrMask] Arbitrary Interior Pointer Mask
- //
- iptrMask = fastDecodeUnsigned(table);
- break;
-
- case 0x04:
- //
- // 0xF4 thisptr 111101RR This pointer is in Register RR
- // 00=EDI,01=ESI,10=EBX,11=EBP
- //
- {
- static const regNum calleeSavedRegs[] =
- { REGI_EDI, REGI_ESI, REGI_EBX, REGI_EBP };
- thisPtrReg = calleeSavedRegs[val&0x3];
- }
- break;
-
- case 0x08:
- //
- // 0xF8 call 11111000 [PBSDpbsd][32-bit delta][32-bit ArgCnt]
- // [32-bit PndCnt][32-bit PndSize][PndOffs...]
- //
- val = *table++;
- skip = *dac_cast(table); table += sizeof(DWORD);
-// [VSUQFE 4670]
- // If we've already reached curOffs and the skip amount
- // is non-zero then we are done
- if ((scanOffs == curOffs) && (skip > 0))
- goto FINISHED;
-// [VSUQFE 4670]
- scanOffs += skip;
- if (scanOffs > curOffs)
- goto FINISHED;
-#ifdef _DEBUG
- isCall = true;
-#endif
- regMask = val & 0xF;
- iregMask = val >> 4;
- callArgCnt = *dac_cast(table); table += sizeof(DWORD);
- stackDepth -= callArgCnt;
- argHnum = *dac_cast(table); table += sizeof(DWORD);
- argTabBytes = *dac_cast(table); table += sizeof(DWORD);
- argTab = table;
- table += argTabBytes;
- break;
-
- case 0x0C:
- //
- // 0xFF end 11111111 End of table marker
- //
- _ASSERTE(val==0xff);
- goto FINISHED;
-
- default:
- _ASSERTE(!"reserved GC encoding");
- break;
- }
- break;
-
- } // end switch
-
- } // end else (!(val & 0x80))
-
- // iregMask & iargMask are subsets of regMask & argMask respectively
-
- _ASSERTE((iregMask & regMask) == iregMask);
- _ASSERTE((iargMask & argMask) == iargMask);
-
- } // end while
-
- } // end else ebp-less frame
-
-FINISHED:
-
- // iregMask & iargMask are subsets of regMask & argMask respectively
-
- _ASSERTE((iregMask & regMask) == iregMask);
- _ASSERTE((iargMask & argMask) == iargMask);
-
- if (scanOffs != curOffs)
- {
- /* must have been a boring call */
- info->regMaskResult = RM_NONE;
- info->argMaskResult = ptrArgTP(0);
- info->iregMaskResult = RM_NONE;
- info->iargMaskResult = ptrArgTP(0);
- info->argHnumResult = 0;
- info->argTabResult = NULL;
- info->argTabBytes = 0;
- }
- else
- {
- info->regMaskResult = convertCalleeSavedRegsMask(regMask);
- info->argMaskResult = ptrArgTP(argMask);
- info->argHnumResult = argHnum;
- info->iregMaskResult = convertCalleeSavedRegsMask(iregMask);
- info->iargMaskResult = ptrArgTP(iargMask);
- info->argTabResult = argTab;
- info->argTabBytes = argTabBytes;
- }
-
-#ifdef _DEBUG
- if (scanOffs != curOffs) {
- isCall = false;
- }
- _ASSERTE(thisPtrReg == REGI_NA || (!isCall || (regNumToMask(thisPtrReg) & info->regMaskResult)));
-#endif
- info->thisPtrResult = thisPtrReg;
-
- _ASSERTE(int(stackDepth) < INT_MAX); // check that it did not underflow
- return (stackDepth * sizeof(unsigned));
-}
-#ifdef _PREFAST_
-#pragma warning(pop)
-#endif
-
-
-/*****************************************************************************
- * scan the register argument table for the fully interruptible case.
- this function is called to find all live objects (pushed arguments)
- and to get the stack base for fully interruptible methods.
- Returns size of things pushed on the stack for ESP frames
-
- Arguments:
- table - The pointer table
- curOffsRegs - The current code offset that should be used for reporting registers
- curOffsArgs - The current code offset that should be used for reporting args
- info - Incoming arg used to determine if there's a frame, and to save results
- */
-
-static
-unsigned scanArgRegTableI(PTR_CBYTE table,
- unsigned curOffsRegs,
- unsigned curOffsArgs,
- hdrInfo * info)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- SUPPORTS_DAC;
- } CONTRACTL_END;
-
- regNum thisPtrReg = REGI_NA;
- unsigned ptrRegs = 0; // The mask of registers that contain pointers
- unsigned iptrRegs = 0; // The subset of ptrRegs that are interior pointers
- unsigned ptrOffs = 0; // The code offset of the table entry we are currently looking at
- unsigned argCnt = 0; // The number of args that have been pushed
-
- ptrArgTP ptrArgs(0); // The mask of stack values that contain pointers.
- ptrArgTP iptrArgs(0); // The subset of ptrArgs that are interior pointers.
- ptrArgTP argHigh(0); // The current mask position that corresponds to the top of the stack.
-
- bool isThis = false;
- bool iptr = false;
-
- // The comment before the call to scanArgRegTableI in EnumGCRefs
- // describes why curOffsRegs can be smaller than curOffsArgs.
- _ASSERTE(curOffsRegs <= curOffsArgs);
-
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
-#endif
-
- bool hasPartialArgInfo;
-
-#ifndef UNIX_X86_ABI
- hasPartialArgInfo = info->ebpFrame;
-#else
- // For x86/Linux, interruptible code always has full arg info
- //
- // This should be aligned with emitFullArgInfo setting at
- // emitter::emitEndCodeGen (in JIT)
- hasPartialArgInfo = false;
-#endif
-
- /*
- Encoding table for methods that are fully interruptible
-
- The encoding used is as follows:
-
- ptr reg dead 00RRRDDD [RRR != 100]
- ptr reg live 01RRRDDD [RRR != 100]
-
- non-ptr arg push 10110DDD [SSS == 110]
- ptr arg push 10SSSDDD [SSS != 110] && [SSS != 111]
- ptr arg pop 11CCCDDD [CCC != 000] && [CCC != 110] && [CCC != 111]
- little delta skip 11000DDD [CCC == 000]
- bigger delta skip 11110BBB [CCC == 110]
-
- The values used in the encodings are as follows:
-
- DDD code offset delta from previous entry (0-7)
- BBB bigger delta 000=8,001=16,010=24,...,111=64
- RRR register number (EAX=000,ECX=001,EDX=010,EBX=011,
- EBP=101,ESI=110,EDI=111), ESP=100 is reserved
- SSS argument offset from base of stack. This is
- redundant for frameless methods as we can
- infer it from the previous pushes+pops. However,
- for EBP-methods, we only report GC pushes, and
- so we need SSS
- CCC argument count being popped (includes only ptrs for EBP methods)
-
- The following are the 'large' versions:
-
- large delta skip 10111000 [0xB8] , encodeUnsigned(delta)
-
- large ptr arg push 11111000 [0xF8] , encodeUnsigned(pushCount)
- large non-ptr arg push 11111001 [0xF9] , encodeUnsigned(pushCount)
- large ptr arg pop 11111100 [0xFC] , encodeUnsigned(popCount)
- large arg dead 11111101 [0xFD] , encodeUnsigned(popCount) for caller-pop args.
- Any GC args go dead after the call,
- but are still sitting on the stack
-
- this pointer prefix 10111100 [0xBC] the next encoding is a ptr live
- or a ptr arg push
- and contains the this pointer
-
- interior or by-ref 10111111 [0xBF] the next encoding is a ptr live
- pointer prefix or a ptr arg push
- and contains an interior
- or by-ref pointer
-
-
- The value 11111111 [0xFF] indicates the end of the table.
- */
-
-#if defined(DACCESS_COMPILE)
- bool fLastByteIsZero = false;
-#endif // DACCESS_COMPILE
-
- /* Have we reached the instruction we're looking for? */
-
- while (ptrOffs <= curOffsArgs)
- {
- unsigned val;
-
- int isPop;
- unsigned argOfs;
-
- unsigned regMask;
-
- // iptrRegs & iptrArgs are subsets of ptrRegs & ptrArgs respectively
-
- _ASSERTE((iptrRegs & ptrRegs) == iptrRegs);
- _ASSERTE((iptrArgs & ptrArgs) == iptrArgs);
-
- /* Now find the next 'life' transition */
-
- val = *table++;
-#if defined(DACCESS_COMPILE)
- // In this scenario, a zero byte means that EAX is going dead at the current offset. Since EAX
- // can't go dead more than once at any given offset, it's invalid to have two consecutive bytes
- // of zero. If this were to happen, then it means that there is most likely some sort of DAC
- // error, and it may lead to problems such as infinite loops. So we bail out early instead.
- if ((val == 0) && fLastByteIsZero)
- {
- DacError(CORDBG_E_TARGET_INCONSISTENT);
- UNREACHABLE();
- }
- fLastByteIsZero = (val == 0);
-#endif // DACCESS_COMPILE
-
- if (!(val & 0x80))
- {
- /* A small 'regPtr' encoding */
-
- regNum reg;
-
- ptrOffs += (val ) & 0x7;
- if (ptrOffs > curOffsArgs) {
- iptr = isThis = false;
- goto REPORT_REFS;
- }
- else if (ptrOffs > curOffsRegs) {
- iptr = isThis = false;
- continue;
- }
-
- reg = (regNum)((val >> 3) & 0x7);
- regMask = 1 << reg; // EAX,ECX,EDX,EBX,---,EBP,ESI,EDI
-
-#if 0
- printf("regMask = %04X -> %04X\n", ptrRegs,
- (val & 0x40) ? (ptrRegs | regMask)
- : (ptrRegs & ~regMask));
-#endif
-
- /* The register is becoming live/dead here */
-
- if (val & 0x40)
- {
- /* Becomes Live */
- _ASSERTE((ptrRegs & regMask) == 0);
-
- ptrRegs |= regMask;
-
- if (isThis)
- {
- thisPtrReg = reg;
- }
- if (iptr)
- {
- iptrRegs |= regMask;
- }
- }
- else
- {
- /* Becomes Dead */
- _ASSERTE((ptrRegs & regMask) != 0);
-
- ptrRegs &= ~regMask;
-
- if (reg == thisPtrReg)
- {
- thisPtrReg = REGI_NA;
- }
- if (iptrRegs & regMask)
- {
- iptrRegs &= ~regMask;
- }
- }
- iptr = isThis = false;
- continue;
- }
-
- /* This is probably an argument push/pop */
-
- argOfs = (val & 0x38) >> 3;
-
- /* 6 [110] and 7 [111] are reserved for other encodings */
- if (argOfs < 6)
- {
-
- /* A small argument encoding */
-
- ptrOffs += (val & 0x07);
- if (ptrOffs > curOffsArgs) {
- iptr = isThis = false;
- goto REPORT_REFS;
- }
- isPop = (val & 0x40);
-
- ARG:
-
- if (isPop)
- {
- if (argOfs == 0)
- continue; // little skip encoding
-
- /* We remove (pop) the top 'argOfs' entries */
-
- _ASSERTE(argOfs || argOfs <= argCnt);
-
- /* adjust # of arguments */
-
- argCnt -= argOfs;
- _ASSERTE(argCnt < MAX_PTRARG_OFS);
-
-// printf("[%04X] popping %u args: mask = %04X\n", ptrOffs, argOfs, (int)ptrArgs);
-
- do
- {
- _ASSERTE(!isZero(argHigh));
-
- /* Do we have an argument bit that's on? */
-
- if (intersect(ptrArgs, argHigh))
- {
- /* Turn off the bit */
-
- setDiff(ptrArgs, argHigh);
- setDiff(iptrArgs, argHigh);
-
- /* We've removed one more argument bit */
-
- argOfs--;
- }
- else if (hasPartialArgInfo)
- argCnt--;
- else /* full arg info && not a ref */
- argOfs--;
-
- /* Continue with the next lower bit */
-
- argHigh >>= 1;
- }
- while (argOfs);
-
- _ASSERTE(!hasPartialArgInfo ||
- isZero(argHigh) ||
- (argHigh == CONSTRUCT_ptrArgTP(1, (argCnt-1))));
-
- if (hasPartialArgInfo)
- {
- // We always leave argHigh pointing to the next ptr arg.
- // So, while argHigh is non-zero, and not a ptrArg, we shift right (and subtract
- // one arg from our argCnt) until it is a ptrArg.
- while (!intersect(argHigh, ptrArgs) && (!isZero(argHigh)))
- {
- argHigh >>= 1;
- argCnt--;
- }
- }
-
- }
- else
- {
- /* Add a new ptr arg entry at stack offset 'argOfs' */
-
- if (argOfs >= MAX_PTRARG_OFS)
- {
- _ASSERTE_ALL_BUILDS(!"scanArgRegTableI: args pushed 'too deep'");
- }
- else
- {
- /* Full arg info reports all pushes, and thus
- argOffs has to be consistent with argCnt */
-
- _ASSERTE(hasPartialArgInfo || argCnt == argOfs);
-
- /* store arg count */
-
- argCnt = argOfs + 1;
- _ASSERTE((argCnt < MAX_PTRARG_OFS));
-
- /* Compute the appropriate argument offset bit */
-
- ptrArgTP argMask = CONSTRUCT_ptrArgTP(1, argOfs);
-
-// printf("push arg at offset %02u --> mask = %04X\n", argOfs, (int)argMask);
-
- /* We should never push twice at the same offset */
-
- _ASSERTE(!intersect( ptrArgs, argMask));
- _ASSERTE(!intersect(iptrArgs, argMask));
-
- /* We should never push within the current highest offset */
-
- // _ASSERTE(argHigh < argMask);
-
- /* This is now the highest bit we've set */
-
- argHigh = argMask;
-
- /* Set the appropriate bit in the argument mask */
-
- ptrArgs |= argMask;
-
- if (iptr)
- iptrArgs |= argMask;
- }
-
- iptr = isThis = false;
- }
- continue;
- }
- else if (argOfs == 6)
- {
- if (val & 0x40) {
- /* Bigger delta 000=8,001=16,010=24,...,111=64 */
- ptrOffs += (((val & 0x07) + 1) << 3);
- }
- else {
- /* non-ptr arg push */
- _ASSERTE(!hasPartialArgInfo);
- ptrOffs += (val & 0x07);
- if (ptrOffs > curOffsArgs) {
- iptr = isThis = false;
- goto REPORT_REFS;
- }
- argHigh = CONSTRUCT_ptrArgTP(1, argCnt);
- argCnt++;
- _ASSERTE(argCnt < MAX_PTRARG_OFS);
- }
- continue;
- }
-
- /* argOfs was 7 [111] which is reserved for the larger encodings */
-
- _ASSERTE(argOfs==7);
-
- switch (val)
- {
- case 0xFF:
- iptr = isThis = false;
- goto REPORT_REFS; // the method might loop !!!
-
- case 0xB8:
- val = fastDecodeUnsigned(table);
- ptrOffs += val;
- continue;
-
- case 0xBC:
- isThis = true;
- break;
-
- case 0xBF:
- iptr = true;
- break;
-
- case 0xF8:
- case 0xFC:
- isPop = val & 0x04;
- argOfs = fastDecodeUnsigned(table);
- goto ARG;
-
- case 0xFD: {
- argOfs = fastDecodeUnsigned(table);
- _ASSERTE(argOfs && argOfs <= argCnt);
-
- // Kill the top "argOfs" pointers.
-
- ptrArgTP argMask;
- for(argMask = CONSTRUCT_ptrArgTP(1, argCnt); (argOfs != 0); argMask >>= 1)
- {
- _ASSERTE(!isZero(argMask) && !isZero(ptrArgs)); // there should be remaining pointers
-
- if (intersect(ptrArgs, argMask))
- {
- setDiff(ptrArgs, argMask);
- setDiff(iptrArgs, argMask);
- argOfs--;
- }
- }
-
- // For partial arg info, need to find the next highest pointer for argHigh
-
- if (hasPartialArgInfo)
- {
- for(argHigh = ptrArgTP(0); !isZero(argMask); argMask >>= 1)
- {
- if (intersect(ptrArgs, argMask)) {
- argHigh = argMask;
- break;
- }
- }
- }
- } break;
-
- case 0xF9:
- argOfs = fastDecodeUnsigned(table);
- argCnt += argOfs;
- break;
-
- default:
- _ASSERTE(!"Unexpected special code %04X");
- }
- }
-
- /* Report all live pointer registers */
-REPORT_REFS:
-
- _ASSERTE((iptrRegs & ptrRegs) == iptrRegs); // iptrRegs is a subset of ptrRegs
- _ASSERTE((iptrArgs & ptrArgs) == iptrArgs); // iptrArgs is a subset of ptrArgs
-
- /* Save the current live register, argument set, and argCnt */
-
- info->regMaskResult = convertAllRegsMask(ptrRegs);
- info->argMaskResult = ptrArgs;
- info->argHnumResult = 0;
- info->iregMaskResult = convertAllRegsMask(iptrRegs);
- info->iargMaskResult = iptrArgs;
-
- info->thisPtrResult = thisPtrReg;
- _ASSERTE(thisPtrReg == REGI_NA || (regNumToMask(thisPtrReg) & info->regMaskResult));
-
- if (hasPartialArgInfo)
- {
- return 0;
- }
- else
- {
- _ASSERTE(int(argCnt) < INT_MAX); // check that it did not underflow
- return (argCnt * sizeof(unsigned));
- }
-}
-
-/*****************************************************************************/
-
-unsigned GetPushedArgSize(hdrInfo * info, PTR_CBYTE table, DWORD curOffs)
-{
- SUPPORTS_DAC;
-
- unsigned sz;
-
- if (info->interruptible)
- {
- sz = scanArgRegTableI(skipToArgReg(*info, table),
- curOffs,
- curOffs,
- info);
- }
- else
- {
- sz = scanArgRegTable(skipToArgReg(*info, table),
- curOffs,
- info);
- }
-
- return sz;
-}
-
-/*****************************************************************************/
-
-inline
-void TRASH_CALLEE_UNSAVED_REGS(PREGDISPLAY pContext)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
-#ifdef _DEBUG
- /* This is not completely correct as we lose the current value, but
- it should not really be useful to anyone. */
- static DWORD s_badData = 0xDEADBEEF;
- pContext->SetEaxLocation(&s_badData);
- pContext->SetEcxLocation(&s_badData);
- pContext->SetEdxLocation(&s_badData);
-#endif //_DEBUG
-}
-
-/*****************************************************************************
- * Sizes of certain i386 instructions which are used in the prolog/epilog
- */
-
-// Can we use sign-extended byte to encode the imm value, or do we need a dword
-#define CAN_COMPRESS(val) ((INT8)(val) == (INT32)(val))
-
-#define SZ_ADD_REG(val) ( 2 + (CAN_COMPRESS(val) ? 1 : 4))
-#define SZ_AND_REG(val) SZ_ADD_REG(val)
-#define SZ_POP_REG 1
-#define SZ_LEA(offset) SZ_ADD_REG(offset)
-#define SZ_MOV_REG_REG 2
-
-bool IsMarkerInstr(BYTE val)
-{
- SUPPORTS_DAC;
-
-#ifdef _DEBUG
- if (val == X86_INSTR_INT3)
- {
- return true;
- }
-#ifdef HAVE_GCCOVER
- else // GcCover might have stomped on the instruction
- {
- if (GCStress::IsEnabled())
- {
- if (IsGcCoverageInterruptInstructionVal(val))
- {
- return true;
- }
- }
- }
-#endif // HAVE_GCCOVER
-#endif // _DEBUG
-
- return false;
-}
-
-/* Check if the given instruction opcode is the one we expect.
- This is a "necessary" but not "sufficient" check as it ignores the check
- if the instruction is one of our special markers (for debugging and GcStress) */
-
-bool CheckInstrByte(BYTE val, BYTE expectedValue)
-{
- SUPPORTS_DAC;
- return ((val == expectedValue) || IsMarkerInstr(val));
-}
-
-/* Similar to CheckInstrByte(). Use this to check a masked opcode (ignoring
- optional bits in the opcode encoding).
- valPattern is the masked out value.
- expectedPattern is the mask value we expect.
- val is the actual instruction opcode
- */
-bool CheckInstrBytePattern(BYTE valPattern, BYTE expectedPattern, BYTE val)
-{
- SUPPORTS_DAC;
-
- _ASSERTE((valPattern & val) == valPattern);
-
- return ((valPattern == expectedPattern) || IsMarkerInstr(val));
-}
-
-/* Similar to CheckInstrByte() */
-
-bool CheckInstrWord(WORD val, WORD expectedValue)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
- return ((val == expectedValue) || IsMarkerInstr(val & 0xFF));
-}
-
-// Use this to check if the instruction at offset "walkOffset" has already
-// been executed
-// "actualHaltOffset" is the offset when the code was suspended
-// It is assumed that there is linear control flow from offset 0 to "actualHaltOffset".
-//
-// This has been factored out just so that the intent of the comparison
-// is clear (compared to the opposite intent)
-
-bool InstructionAlreadyExecuted(unsigned walkOffset, unsigned actualHaltOffset)
-{
- SUPPORTS_DAC;
- return (walkOffset < actualHaltOffset);
-}
-
-// skips past a "arith REG, IMM"
-inline unsigned SKIP_ARITH_REG(int val, PTR_CBYTE base, unsigned offset)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- unsigned delta = 0;
- if (val != 0)
- {
-#ifdef _DEBUG
- // Confirm that arith instruction is at the correct place
- _ASSERTE(CheckInstrBytePattern(base[offset ] & 0xFD, 0x81, base[offset]) &&
- CheckInstrBytePattern(base[offset+1] & 0xC0, 0xC0, base[offset+1]));
- // only use DWORD form if needed
- _ASSERTE(((base[offset] & 2) != 0) == CAN_COMPRESS(val) ||
- IsMarkerInstr(base[offset]));
-#endif
- delta = 2 + (CAN_COMPRESS(val) ? 1 : 4);
- }
- return(offset + delta);
-}
-
-inline unsigned SKIP_PUSH_REG(PTR_CBYTE base, unsigned offset)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- // Confirm it is a push instruction
- _ASSERTE(CheckInstrBytePattern(base[offset] & 0xF8, 0x50, base[offset]));
- return(offset + 1);
-}
-
-inline unsigned SKIP_POP_REG(PTR_CBYTE base, unsigned offset)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- // Confirm it is a pop instruction
- _ASSERTE(CheckInstrBytePattern(base[offset] & 0xF8, 0x58, base[offset]));
- return(offset + 1);
-}
-
-inline unsigned SKIP_MOV_REG_REG(PTR_CBYTE base, unsigned offset)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- // Confirm it is a move instruction
- // Note that only the first byte may have been stomped on by IsMarkerInstr()
- // So we can check the second byte directly
- _ASSERTE(CheckInstrBytePattern(base[offset] & 0xFD, 0x89, base[offset]) &&
- (base[offset+1] & 0xC0) == 0xC0);
- return(offset + 2);
-}
-
-inline unsigned SKIP_LEA_ESP_EBP(int val, PTR_CBYTE base, unsigned offset)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
-#ifdef _DEBUG
- // Confirm it is the right instruction
- // Note that only the first byte may have been stomped on by IsMarkerInstr()
- // So we can check the second byte directly
- WORD wOpcode = *(PTR_WORD)base;
- _ASSERTE((CheckInstrWord(wOpcode, X86_INSTR_w_LEA_ESP_EBP_BYTE_OFFSET) &&
- (val == *(PTR_SBYTE)(base+2)) &&
- CAN_COMPRESS(val)) ||
- (CheckInstrWord(wOpcode, X86_INSTR_w_LEA_ESP_EBP_DWORD_OFFSET) &&
- (val == *(PTR_INT32)(base+2)) &&
- !CAN_COMPRESS(val)));
-#endif
-
- unsigned delta = 2 + (CAN_COMPRESS(val) ? 1 : 4);
- return(offset + delta);
-}
-
-inline unsigned SKIP_LEA_EAX_ESP(int val, PTR_CBYTE base, unsigned offset)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
-#ifdef _DEBUG
- WORD wOpcode = *(PTR_WORD)(base + offset);
- if (CheckInstrWord(wOpcode, X86_INSTR_w_LEA_EAX_ESP_BYTE_OFFSET))
- {
- _ASSERTE(val == *(PTR_SBYTE)(base + offset + 3));
- _ASSERTE(CAN_COMPRESS(val));
- }
- else
- {
- _ASSERTE(CheckInstrWord(wOpcode, X86_INSTR_w_LEA_EAX_ESP_DWORD_OFFSET));
- _ASSERTE(val == *(PTR_INT32)(base + offset + 3));
- _ASSERTE(!CAN_COMPRESS(val));
- }
-#endif
-
- unsigned delta = 3 + (CAN_COMPRESS(-val) ? 1 : 4);
- return(offset + delta);
-}
-
-inline unsigned SKIP_HELPER_CALL(PTR_CBYTE base, unsigned offset)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- unsigned delta;
-
- if (CheckInstrByte(base[offset], X86_INSTR_CALL_REL32))
- {
- delta = 5;
- }
- else
- {
-#ifdef _DEBUG
- WORD wOpcode = *(PTR_WORD)(base+offset);
- _ASSERTE(CheckInstrWord(wOpcode, X86_INSTR_W_CALL_IND_IMM));
-#endif
- delta = 6;
- }
-
- return(offset+delta);
-}
-
-unsigned SKIP_ALLOC_FRAME(int size, PTR_CBYTE base, unsigned offset)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- SUPPORTS_DAC;
- } CONTRACTL_END;
-
- _ASSERTE(size != 0);
-
- if (size == sizeof(void*))
- {
- // JIT emits "push eax" instead of "sub esp,4"
- return SKIP_PUSH_REG(base, offset);
- }
-
- const int STACK_PROBE_PAGE_SIZE_BYTES = 4096;
- const int STACK_PROBE_BOUNDARY_THRESHOLD_BYTES = 1024;
-
- int lastProbedLocToFinalSp = size;
-
- if (size < STACK_PROBE_PAGE_SIZE_BYTES)
- {
- // sub esp, size
- offset = SKIP_ARITH_REG(size, base, offset);
- }
- else
- {
- WORD wOpcode = *(PTR_WORD)(base + offset);
-
- if (CheckInstrWord(wOpcode, X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX))
- {
- // In .NET 5.0 and earlier for frames that have size smaller than 0x3000 bytes
- // JIT emits one or two 'test eax, [esp-dwOffset]' instructions before adjusting the stack pointer.
- _ASSERTE(size < 0x3000);
-
- // test eax, [esp-0x1000]
- offset += 7;
- lastProbedLocToFinalSp -= 0x1000;
-
- if (size >= 0x2000)
- {
-#ifdef _DEBUG
- wOpcode = *(PTR_WORD)(base + offset);
- _ASSERTE(CheckInstrWord(wOpcode, X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX));
-#endif
- //test eax, [esp-0x2000]
- offset += 7;
- lastProbedLocToFinalSp -= 0x1000;
- }
-
- // sub esp, size
- offset = SKIP_ARITH_REG(size, base, offset);
- }
- else
- {
- bool pushedStubParam = false;
-
- if (CheckInstrByte(base[offset], X86_INSTR_PUSH_EAX))
- {
- // push eax
- offset = SKIP_PUSH_REG(base, offset);
- pushedStubParam = true;
- }
-
- if (CheckInstrByte(base[offset], X86_INSTR_XOR))
- {
- // In .NET Core 3.1 and earlier for frames that have size greater than or equal to 0x3000 bytes
- // JIT emits the following loop.
- _ASSERTE(size >= 0x3000);
-
- offset += 2;
- // xor eax, eax 2
- // [nop] 0-3
- // loop:
- // test [esp + eax], eax 3
- // sub eax, 0x1000 5
- // cmp eax, -size 5
- // jge loop 2
-
- // R2R images that support ReJIT may have extra nops we need to skip over.
- while (offset < 5)
- {
- if (CheckInstrByte(base[offset], X86_INSTR_NOP))
- {
- offset++;
- }
- else
- {
- break;
- }
- }
-
- offset += 15;
-
- if (pushedStubParam)
- {
- // pop eax
- offset = SKIP_POP_REG(base, offset);
- }
-
- // sub esp, size
- return SKIP_ARITH_REG(size, base, offset);
- }
- else
- {
- // In .NET 5.0 and later JIT emits a call to JIT_StackProbe helper.
-
- if (pushedStubParam)
- {
- // lea eax, [esp-size+4]
- offset = SKIP_LEA_EAX_ESP(-size + 4, base, offset);
- // call JIT_StackProbe
- offset = SKIP_HELPER_CALL(base, offset);
- // pop eax
- offset = SKIP_POP_REG(base, offset);
- // sub esp, size
- return SKIP_ARITH_REG(size, base, offset);
- }
- else
- {
- // lea eax, [esp-size]
- offset = SKIP_LEA_EAX_ESP(-size, base, offset);
- // call JIT_StackProbe
- offset = SKIP_HELPER_CALL(base, offset);
- // mov esp, eax
- return SKIP_MOV_REG_REG(base, offset);
- }
- }
- }
- }
-
- if (lastProbedLocToFinalSp + STACK_PROBE_BOUNDARY_THRESHOLD_BYTES > STACK_PROBE_PAGE_SIZE_BYTES)
- {
-#ifdef _DEBUG
- WORD wOpcode = *(PTR_WORD)(base + offset);
- _ASSERTE(CheckInstrWord(wOpcode, X86_INSTR_w_TEST_ESP_EAX));
-#endif
- // test [esp], eax
- offset += 3;
- }
-
- return offset;
-}
-
-#endif // !USE_GC_INFO_DECODER
-
-
-#if defined(FEATURE_EH_FUNCLETS)
-
-void EECodeManager::EnsureCallerContextIsValid( PREGDISPLAY pRD, EECodeInfo * pCodeInfo /*= NULL*/, unsigned flags /*= 0*/)
-{
- CONTRACTL
- {
- NOTHROW;
- GC_NOTRIGGER;
- SUPPORTS_DAC;
- }
- CONTRACTL_END;
-
- if( !pRD->IsCallerContextValid )
- {
- if ((flags & LightUnwind) && (pCodeInfo != NULL))
- {
-#if !defined(DACCESS_COMPILE) && defined(HAS_LIGHTUNWIND)
- LightUnwindStackFrame(pRD, pCodeInfo, EnsureCallerStackFrameIsValid);
-#else
- // We need to make a copy here (instead of switching the pointers), in order to preserve the current context
- *(pRD->pCallerContext) = *(pRD->pCurrentContext);
- // Skip updating context registers for light unwind
- Thread::VirtualUnwindCallFrame(pRD->pCallerContext, NULL, pCodeInfo);
-#endif
- }
- else
- {
- // We need to make a copy here (instead of switching the pointers), in order to preserve the current context
- *(pRD->pCallerContext) = *(pRD->pCurrentContext);
- *(pRD->pCallerContextPointers) = *(pRD->pCurrentContextPointers);
- Thread::VirtualUnwindCallFrame(pRD->pCallerContext, pRD->pCallerContextPointers, pCodeInfo);
- }
-
- pRD->IsCallerContextValid = TRUE;
- }
-
- _ASSERTE( pRD->IsCallerContextValid );
-}
-
-size_t EECodeManager::GetCallerSp( PREGDISPLAY pRD )
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- SUPPORTS_DAC;
- } CONTRACTL_END;
-
- // Don't add usage of this field. This is only temporary.
- // See ExceptionTracker::InitializeCrawlFrame() for more information.
- if (!pRD->IsCallerSPValid)
- {
- EnsureCallerContextIsValid(pRD, NULL);
- }
-
- return GetSP(pRD->pCallerContext);
-}
-
-#endif // FEATURE_EH_FUNCLETS
-
-#ifdef HAS_LIGHTUNWIND
-/*
- * Light unwind the current stack frame, using provided cache entry.
- * pPC, Esp and pEbp of pContext are updated.
- */
-
-// static
-void EECodeManager::LightUnwindStackFrame(PREGDISPLAY pRD, EECodeInfo* pCodeInfo, LightUnwindFlag flag)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- } CONTRACTL_END;
-
-#ifdef TARGET_AMD64
- ULONG RBPOffset, RSPOffset;
- pCodeInfo->GetOffsetsFromUnwindInfo(&RSPOffset, &RBPOffset);
-
- if (pRD->IsCallerContextValid)
- {
- pRD->pCurrentContext->Rbp = pRD->pCallerContext->Rbp;
- pRD->pCurrentContext->Rsp = pRD->pCallerContext->Rsp;
- pRD->pCurrentContext->Rip = pRD->pCallerContext->Rip;
- }
- else
- {
- PCONTEXT pSourceCtx = NULL;
- PCONTEXT pTargetCtx = NULL;
- if (flag == UnwindCurrentStackFrame)
- {
- pTargetCtx = pRD->pCurrentContext;
- pSourceCtx = pRD->pCurrentContext;
- }
- else
- {
- pTargetCtx = pRD->pCallerContext;
- pSourceCtx = pRD->pCurrentContext;
- }
-
- // Unwind RBP. The offset is relative to the current sp.
- if (RBPOffset == 0)
- {
- pTargetCtx->Rbp = pSourceCtx->Rbp;
- }
- else
- {
- pTargetCtx->Rbp = *(UINT_PTR*)(pSourceCtx->Rsp + RBPOffset);
- }
-
- // Adjust the sp. From this pointer onwards pCurrentContext->Rsp is the caller sp.
- pTargetCtx->Rsp = pSourceCtx->Rsp + RSPOffset;
-
- // Retrieve the return address.
- pTargetCtx->Rip = *(UINT_PTR*)((pTargetCtx->Rsp) - sizeof(UINT_PTR));
- }
-
- if (flag == UnwindCurrentStackFrame)
- {
- SyncRegDisplayToCurrentContext(pRD);
- pRD->IsCallerContextValid = FALSE;
- pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary.
- }
-#else
- PORTABILITY_ASSERT("EECodeManager::LightUnwindStackFrame is not implemented on this platform.");
-#endif
-}
-#endif // HAS_LIGHTUNWIND
-
-/*****************************************************************************/
-#ifdef TARGET_X86 // UnwindStackFrame
-/*****************************************************************************/
-
-const RegMask CALLEE_SAVED_REGISTERS_MASK[] =
-{
- RM_EDI, // first register to be pushed
- RM_ESI,
- RM_EBX,
- RM_EBP // last register to be pushed
-};
-
-static void SetLocation(PREGDISPLAY pRD, int ind, PDWORD loc)
-{
-#ifdef FEATURE_EH_FUNCLETS
- static const SIZE_T OFFSET_OF_CALLEE_SAVED_REGISTERS[] =
- {
- offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Edi), // first register to be pushed
- offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Esi),
- offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Ebx),
- offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Ebp), // last register to be pushed
- };
-
- SIZE_T offsetOfRegPtr = OFFSET_OF_CALLEE_SAVED_REGISTERS[ind];
- *(LPVOID*)(PBYTE(pRD->pCurrentContextPointers) + offsetOfRegPtr) = loc;
-#else
- static const SIZE_T OFFSET_OF_CALLEE_SAVED_REGISTERS[] =
- {
- offsetof(REGDISPLAY, pEdi), // first register to be pushed
- offsetof(REGDISPLAY, pEsi),
- offsetof(REGDISPLAY, pEbx),
- offsetof(REGDISPLAY, pEbp), // last register to be pushed
- };
-
- SIZE_T offsetOfRegPtr = OFFSET_OF_CALLEE_SAVED_REGISTERS[ind];
- *(LPVOID*)(PBYTE(pRD) + offsetOfRegPtr) = loc;
-#endif
-}
-
-/*****************************************************************************/
-
-void UnwindEspFrameEpilog(
- PREGDISPLAY pContext,
- hdrInfo * info,
- PTR_CBYTE epilogBase,
- unsigned flags)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
- _ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
- _ASSERTE(!info->ebpFrame && !info->doubleAlign);
- _ASSERTE(info->epilogOffs > 0);
-
- int offset = 0;
- unsigned ESP = pContext->SP;
-
- if (info->rawStkSize)
- {
- if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
- {
- /* We have NOT executed the "ADD ESP, FrameSize",
- so manually adjust stack pointer */
- ESP += info->rawStkSize;
- }
-
- // We have already popped off the frame (excluding the callee-saved registers)
-
- if (epilogBase[0] == X86_INSTR_POP_ECX)
- {
- // We may use "POP ecx" for doing "ADD ESP, 4",
- // or we may not (in the case of JMP epilogs)
- _ASSERTE(info->rawStkSize == sizeof(void*));
- offset = SKIP_POP_REG(epilogBase, offset);
- }
- else
- {
- // "add esp, rawStkSize"
- offset = SKIP_ARITH_REG(info->rawStkSize, epilogBase, offset);
- }
- }
-
- /* Remaining callee-saved regs are at ESP. Need to update
- regsMask as well to exclude registers which have already been popped. */
-
- const RegMask regsMask = info->savedRegMask;
-
- /* Increment "offset" in steps to see which callee-saved
- registers have already been popped */
-
- for (unsigned i = ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--)
- {
- RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
-
- if (!(regMask & regsMask))
- continue;
-
- if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
- {
- /* We have NOT yet popped off the register.
- Get the value from the stack if needed */
- if ((flags & UpdateAllRegs) || (regMask == RM_EBP))
- {
- SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
- }
-
- /* Adjust ESP */
- ESP += sizeof(void*);
- }
-
- offset = SKIP_POP_REG(epilogBase, offset);
- }
-
- //CEE_JMP generates an epilog similar to a normal CEE_RET epilog except for the last instruction
- _ASSERTE(CheckInstrBytePattern(epilogBase[offset] & X86_INSTR_RET, X86_INSTR_RET, epilogBase[offset]) //ret
- || CheckInstrBytePattern(epilogBase[offset], X86_INSTR_JMP_NEAR_REL32, epilogBase[offset]) //jmp ret32
- || CheckInstrWord(*PTR_WORD(epilogBase + offset), X86_INSTR_w_JMP_FAR_IND_IMM)); //jmp [addr32]
-
- /* Finally we can set pPC */
- pContext->PCTAddr = (TADDR)ESP;
- pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
-
- pContext->SP = ESP;
-}
-
-/*****************************************************************************/
-
-void UnwindEbpDoubleAlignFrameEpilog(
- PREGDISPLAY pContext,
- hdrInfo * info,
- PTR_CBYTE epilogBase,
- unsigned flags)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
- _ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
- _ASSERTE(info->ebpFrame || info->doubleAlign);
-
- _ASSERTE(info->argSize < 0x10000); // "ret" only has a 2 byte operand
-
- /* See how many instructions we have executed in the
- epilog to determine which callee-saved registers
- have already been popped */
- int offset = 0;
-
- unsigned ESP = pContext->SP;
-
- bool needMovEspEbp = false;
-
- if (info->doubleAlign)
- {
- // add esp, rawStkSize
-
- if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
- ESP += info->rawStkSize;
- _ASSERTE(info->rawStkSize != 0);
- offset = SKIP_ARITH_REG(info->rawStkSize, epilogBase, offset);
-
- // We also need "mov esp, ebp" after popping the callee-saved registers
- needMovEspEbp = true;
- }
- else
- {
- bool needLea = false;
-
- if (info->localloc)
- {
- // ESP may be variable if a localloc was actually executed. We will reset it.
- // lea esp, [ebp-calleeSavedRegs]
-
- needLea = true;
- }
- else if (info->savedRegsCountExclFP == 0)
- {
- // We will just generate "mov esp, ebp" and be done with it.
-
- if (info->rawStkSize != 0)
- {
- needMovEspEbp = true;
- }
- }
- else if (info->rawStkSize == 0)
- {
- // do nothing before popping the callee-saved registers
- }
- else if (info->rawStkSize == sizeof(void*))
- {
- // "pop ecx" will make ESP point to the callee-saved registers
- if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
- ESP += sizeof(void*);
- offset = SKIP_POP_REG(epilogBase, offset);
- }
- else
- {
- // We need to make ESP point to the callee-saved registers
- // lea esp, [ebp-calleeSavedRegs]
-
- needLea = true;
- }
-
- if (needLea)
- {
- // lea esp, [ebp-calleeSavedRegs]
-
- unsigned calleeSavedRegsSize = info->savedRegsCountExclFP * sizeof(void*);
-
- if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
- ESP = GetRegdisplayFP(pContext) - calleeSavedRegsSize;
-
- offset = SKIP_LEA_ESP_EBP(-int(calleeSavedRegsSize), epilogBase, offset);
- }
- }
-
- for (unsigned i = STRING_LENGTH(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--)
- {
- RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
- _ASSERTE(regMask != RM_EBP);
-
- if ((info->savedRegMask & regMask) == 0)
- continue;
-
- if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
- {
- if (flags & UpdateAllRegs)
- {
- SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
- }
- ESP += sizeof(void*);
- }
-
- offset = SKIP_POP_REG(epilogBase, offset);
- }
-
- if (needMovEspEbp)
- {
- if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
- ESP = GetRegdisplayFP(pContext);
-
- offset = SKIP_MOV_REG_REG(epilogBase, offset);
- }
-
- // Have we executed the pop EBP?
- if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
- {
- pContext->SetEbpLocation(PTR_DWORD(TADDR(ESP)));
- ESP += sizeof(void*);
- }
- offset = SKIP_POP_REG(epilogBase, offset);
-
- pContext->PCTAddr = (TADDR)ESP;
- pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
-
- pContext->SP = ESP;
-}
-
-inline SIZE_T GetStackParameterSize(hdrInfo * info)
-{
- SUPPORTS_DAC;
- return (info->varargs ? 0 : info->argSize); // Note varargs is caller-popped
-}
-
-//****************************************************************************
-// This is the value ESP is incremented by on doing a "return"
-
-inline SIZE_T ESPIncrOnReturn(hdrInfo * info)
-{
- SUPPORTS_DAC;
- return sizeof(void *) + // pop off the return address
- GetStackParameterSize(info);
-}
-
-/*****************************************************************************/
-
-void UnwindEpilog(
- PREGDISPLAY pContext,
- hdrInfo * info,
- PTR_CBYTE epilogBase,
- unsigned flags)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
- _ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
- // _ASSERTE(flags & ActiveStackFrame); // Wont work for thread death
- _ASSERTE(info->epilogOffs > 0);
-
- if (info->ebpFrame || info->doubleAlign)
- {
- UnwindEbpDoubleAlignFrameEpilog(pContext, info, epilogBase, flags);
- }
- else
- {
- UnwindEspFrameEpilog(pContext, info, epilogBase, flags);
- }
-
-#ifdef _DEBUG
- if (flags & UpdateAllRegs)
- TRASH_CALLEE_UNSAVED_REGS(pContext);
-#endif
-
- /* Now adjust stack pointer */
-
- pContext->SP += ESPIncrOnReturn(info);
-}
-
-/*****************************************************************************/
+ // Strategy for zeroing out the frame on x64:
+ //
+ // The stack frame looks like this (stack grows up)
+ //
+ // =======================================
+ // <--- RSP == RBP (invariant: localalloc disallowed before remap)
+ // Arguments for next call (if there is one)
+ // PSPSym (optional)
+ // JIT temporaries (if any)
+ // Security object (if any)
+ // Local variables (if any)
+ // ---------------------------------------
+ // Frame header (stuff we must preserve, such as bool for synchronized
+ // methods, saved FP, saved callee-preserved registers, etc.)
+ // Return address (also included in frame header)
+ // ---------------------------------------
+ // Arguments for this frame (that's getting remapped). Will naturally be preserved
+ // since fixed-frame size doesn't include this.
+ // =======================================
+ //
+ // Goal: Zero out everything AFTER (above) frame header.
+ //
+ // How do we find this stuff?
+ //
+ // EECodeInfo::GetFixedStackSize() gives us the full size from the top ("Arguments
+ // for next call") all the way down to and including Return Address.
+ //
+ // GetSizeOfEditAndContinuePreservedArea() gives us the size in bytes of the
+ // frame header at the bottom.
+ //
+ // So we start at RSP, and zero out:
+ // GetFixedStackSize() - GetSizeOfEditAndContinuePreservedArea() bytes.
+ //
+ // We'll need to restore PSPSym; location gotten from GCInfo.
+ // We'll need to copy security object; location gotten from GCInfo.
+ //
+ // On ARM64 the JIT generates a slightly different frame and we do not have
+ // the invariant FP == SP, since the FP needs to point at the saved fp/lr
+ // pair for ETW stack walks. The frame there looks something like:
+ // =======================================
+ // Arguments for next call (if there is one) <- SP
+ // JIT temporaries
+ // Locals
+ // PSPSym
+ // --------------------------------------- ^ zeroed area
+ // MonitorAcquired (for synchronized methods)
+ // Saved FP <- FP
+ // Saved LR
+ // --------------------------------------- ^ preserved area
+ // Arguments
+ //
+ // The JIT reports the size of the "preserved" area, which includes
+ // MonitorAcquired when it is present. It could also include other local
+ // values that need to be preserved across EnC transitions, but no explicit
+ // treatment of these is necessary here beyond preserving the values in
+ // this region.
-void UnwindEspFrameProlog(
- PREGDISPLAY pContext,
- hdrInfo * info,
- PTR_CBYTE methodStart,
- unsigned flags)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
+ // GCInfo for old method
+ GcInfoDecoder oldGcDecoder(
+ pOldCodeInfo->GetGCInfoToken(),
+ GcInfoDecoderFlags(DECODE_SECURITY_OBJECT | DECODE_PSP_SYM | DECODE_EDIT_AND_CONTINUE),
+ 0 // Instruction offset (not needed)
+ );
- /* we are in the middle of the prolog */
- _ASSERTE(info->prologOffs != hdrInfo::NOT_IN_PROLOG);
- _ASSERTE(!info->ebpFrame && !info->doubleAlign);
+ // GCInfo for new method
+ GcInfoDecoder newGcDecoder(
+ pNewCodeInfo->GetGCInfoToken(),
+ GcInfoDecoderFlags(DECODE_SECURITY_OBJECT | DECODE_PSP_SYM | DECODE_EDIT_AND_CONTINUE),
+ 0 // Instruction offset (not needed)
+ );
- unsigned offset = 0;
+ UINT32 oldSizeOfPreservedArea = oldGcDecoder.GetSizeOfEditAndContinuePreservedArea();
+ UINT32 newSizeOfPreservedArea = newGcDecoder.GetSizeOfEditAndContinuePreservedArea();
-#ifdef _DEBUG
- // If the first two instructions are 'nop, int3', then we will
- // assume that is from a JitHalt operation and skip past it
- if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
+ LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC: Got old and new EnC preserved area sizes of %u and %u\n", oldSizeOfPreservedArea, newSizeOfPreservedArea));
+ // This ensures the JIT generated EnC compliant code.
+ if ((oldSizeOfPreservedArea == NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA) ||
+ (newSizeOfPreservedArea == NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA))
{
- offset += 2;
+ _ASSERTE(!"FixContextForEnC called on a non-EnC-compliant method frame");
+ return CORDBG_E_ENC_INFOLESS_METHOD;
}
-#endif
-
- const DWORD curOffs = info->prologOffs;
- unsigned ESP = pContext->SP;
- // Find out how many callee-saved regs have already been pushed
+ TADDR oldStackBase = GetSP(&oldCtx);
- unsigned regsMask = RM_NONE;
- PTR_DWORD savedRegPtr = PTR_DWORD((TADDR)ESP);
+ LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC: Old SP=%p, FP=%p\n", (void*)oldStackBase, (void*)GetFP(&oldCtx)));
- for (unsigned i = 0; i < ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i++)
- {
- RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
+#if defined(TARGET_AMD64)
+ // Note: we cannot assert anything about the relationship between oldFixedStackSize
+ // and newFixedStackSize. It's possible the edited frame grows (new locals) or
+ // shrinks (less temporaries).
+ DWORD oldFixedStackSize = pOldCodeInfo->GetFixedStackSize();
+ DWORD newFixedStackSize = pNewCodeInfo->GetFixedStackSize();
- if (!(info->savedRegMask & regMask))
- continue;
+ // This verifies no localallocs were used in the old method.
+ // JIT is required to emit frame register for EnC-compliant code
+ _ASSERTE(pOldCodeInfo->HasFrameRegister());
+ _ASSERTE(pNewCodeInfo->HasFrameRegister());
- if (InstructionAlreadyExecuted(offset, curOffs))
- {
- ESP += sizeof(void*);
- regsMask |= regMask;
- }
+#elif defined(TARGET_ARM64)
+ DWORD oldFixedStackSize = oldGcDecoder.GetSizeOfEditAndContinueFixedStackFrame();
+ DWORD newFixedStackSize = newGcDecoder.GetSizeOfEditAndContinueFixedStackFrame();
+#else
+ PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
+#endif
- offset = SKIP_PUSH_REG(methodStart, offset);
- }
+ LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC: Old and new fixed stack sizes are %u and %u\n", oldFixedStackSize, newFixedStackSize));
- if (info->rawStkSize)
+#if defined(TARGET_AMD64) && defined(TARGET_WINDOWS)
+ // win-x64: SP == FP before localloc
+ if (oldStackBase != GetFP(&oldCtx))
{
- offset = SKIP_ALLOC_FRAME(info->rawStkSize, methodStart, offset);
-
- // Note that this assumes that only the last instruction in SKIP_ALLOC_FRAME
- // actually updates ESP
- if (InstructionAlreadyExecuted(offset, curOffs + 1))
- {
- savedRegPtr += (info->rawStkSize / sizeof(DWORD));
- ESP += info->rawStkSize;
- }
+ return E_FAIL;
}
-
- //
- // Stack probe checks here
- //
-
- // Poison the value, we don't set it properly at the end of the prolog
- INDEBUG(offset = 0xCCCCCCCC);
-
-
- // Always restore EBP
- if (regsMask & RM_EBP)
- pContext->SetEbpLocation(savedRegPtr++);
-
- if (flags & UpdateAllRegs)
+#else
+ // All other 64-bit targets use frame chaining with the FP stored right below the
+ // return address (LR is always pushed on arm64). FP + 16 == SP + oldFixedStackSize
+ // gives the caller's SP before stack alloc.
+ if (GetFP(&oldCtx) + 16 != oldStackBase + oldFixedStackSize)
{
- if (regsMask & RM_EBX)
- pContext->SetEbxLocation(savedRegPtr++);
- if (regsMask & RM_ESI)
- pContext->SetEsiLocation(savedRegPtr++);
- if (regsMask & RM_EDI)
- pContext->SetEdiLocation(savedRegPtr++);
-
- TRASH_CALLEE_UNSAVED_REGS(pContext);
+ return E_FAIL;
}
-
-#if 0
-// NOTE:
-// THIS IS ONLY TRUE IF PROLOGSIZE DOES NOT INCLUDE REG-VAR INITIALIZATION !!!!
-//
- /* there is (potentially) only one additional
- instruction in the prolog, (push ebp)
- but if we would have been passed that instruction,
- info->prologOffs would be hdrInfo::NOT_IN_PROLOG!
- */
- _ASSERTE(offset == info->prologOffs);
#endif
- pContext->SP = ESP;
-}
-
-/*****************************************************************************/
-
-void UnwindEspFrame(
- PREGDISPLAY pContext,
- hdrInfo * info,
- PTR_CBYTE table,
- PTR_CBYTE methodStart,
- DWORD curOffs,
- unsigned flags)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
- _ASSERTE(!info->ebpFrame && !info->doubleAlign);
- _ASSERTE(info->epilogOffs == hdrInfo::NOT_IN_EPILOG);
-
- unsigned ESP = pContext->SP;
-
+ // EnC remap inside handlers is not supported
+ if (pOldCodeInfo->IsFunclet() || pNewCodeInfo->IsFunclet())
+ return CORDBG_E_ENC_IN_FUNCLET;
- if (info->prologOffs != hdrInfo::NOT_IN_PROLOG)
- {
- if (info->prologOffs != 0) // Do nothing for the very start of the method
- {
- UnwindEspFrameProlog(pContext, info, methodStart, flags);
- ESP = pContext->SP;
- }
- }
- else
+ if (oldSizeOfPreservedArea != newSizeOfPreservedArea)
{
- /* we are past the prolog, ESP has been set above */
-
- // Are there any arguments pushed on the stack?
-
- ESP += GetPushedArgSize(info, table, curOffs);
-
- ESP += info->rawStkSize;
-
- const RegMask regsMask = info->savedRegMask;
-
- for (unsigned i = ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--)
- {
- RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
-
- if ((regMask & regsMask) == 0)
- continue;
-
- SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
-
- ESP += sizeof(unsigned);
- }
+ _ASSERTE(!"FixContextForEnC called with method whose frame header size changed from old to new version.");
+ return E_FAIL;
}
- /* we can now set the (address of the) return address */
-
- pContext->PCTAddr = (TADDR)ESP;
- pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
-
- /* Now adjust stack pointer */
-
- pContext->SP = ESP + ESPIncrOnReturn(info);
-}
-
-
-/*****************************************************************************/
-
-void UnwindEbpDoubleAlignFrameProlog(
- PREGDISPLAY pContext,
- hdrInfo * info,
- PTR_CBYTE methodStart,
- unsigned flags)
-{
- LIMITED_METHOD_DAC_CONTRACT;
-
- _ASSERTE(info->prologOffs != hdrInfo::NOT_IN_PROLOG);
- _ASSERTE(info->ebpFrame || info->doubleAlign);
-
- DWORD offset = 0;
+ TADDR callerSP = oldStackBase + oldFixedStackSize;
#ifdef _DEBUG
- // If the first two instructions are 'nop, int3', then we will
- // assume that is from a JitHalt operation and skip past it
- if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
+ // If the old method has a PSPSym, then its value should == initial-SP (i.e.
+ // oldStackBase) for x64 and callerSP for arm64
+ INT32 nOldPspSymStackSlot = oldGcDecoder.GetPSPSymStackSlot();
+ if (nOldPspSymStackSlot != NO_PSP_SYM)
{
- offset += 2;
- }
+#if defined(TARGET_AMD64)
+ TADDR oldPSP = *PTR_TADDR(oldStackBase + nOldPspSymStackSlot);
+ _ASSERTE(oldPSP == oldStackBase);
+#else
+ TADDR oldPSP = *PTR_TADDR(callerSP + nOldPspSymStackSlot);
+ _ASSERTE(oldPSP == callerSP);
#endif
+ }
+#endif // _DEBUG
- /* Check for the case where EBP has not been updated yet. */
-
- const DWORD curOffs = info->prologOffs;
+#else
+ PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
+#endif
- // If we have still not excecuted "push ebp; mov ebp, esp", then we need to
- // report the frame relative to ESP
+ // 2) Get all the info about current variables, registers, etc
- if (!InstructionAlreadyExecuted(offset + 1, curOffs))
- {
- _ASSERTE(CheckInstrByte(methodStart [offset], X86_INSTR_PUSH_EBP) ||
- CheckInstrWord(*PTR_WORD(methodStart + offset), X86_INSTR_W_MOV_EBP_ESP) ||
- CheckInstrByte(methodStart [offset], X86_INSTR_JMP_NEAR_REL32)); // a rejit jmp-stamp
+ const ICorDebugInfo::NativeVarInfo * pOldVar;
- /* If we're past the "push ebp", adjust ESP to pop EBP off */
+ // sorted by varNumber
+ ICorDebugInfo::NativeVarInfo * oldMethodVarsSorted = NULL;
+ ICorDebugInfo::NativeVarInfo * oldMethodVarsSortedBase = NULL;
+ ICorDebugInfo::NativeVarInfo *newMethodVarsSorted = NULL;
+ ICorDebugInfo::NativeVarInfo *newMethodVarsSortedBase = NULL;
- if (curOffs == (offset + 1))
- pContext->SP += sizeof(TADDR);
+ SIZE_T *rgVal1 = NULL;
+ SIZE_T *rgVal2 = NULL;
- /* Stack pointer points to return address */
+ {
+ SIZE_T local;
- pContext->PCTAddr = (TADDR)pContext->SP;
- pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
+ // We'll need to sort the old native var info by variable number, since the
+ // order of them isn't necc. the same. We'll use the number as the key.
+ // We will assume we may have hidden arguments (which have negative values as the index)
- /* EBP and callee-saved registers still have the correct value */
+ unsigned oldNumVars = unsigned(-ICorDebugInfo::UNKNOWN_ILNUM);
+ for (pOldVar = oldMethodVars, local = 0;
+ local < oldMethodVarsCount;
+ local++, pOldVar++)
+ {
+ DWORD varNumber = pOldVar->varNumber;
+ if (signed(varNumber) >= 0)
+ {
+ // This is an explicit (not special) var, so add its varNumber + 1 to our
+ // max count ("+1" because varNumber is zero-based).
+ oldNumVars = max(oldNumVars, unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) + varNumber + 1);
+ }
+ }
- return;
- }
+ oldMethodVarsSortedBase = new (nothrow) ICorDebugInfo::NativeVarInfo[oldNumVars];
+ if (!oldMethodVarsSortedBase)
+ {
+ hr = E_FAIL;
+ goto ErrExit;
+ }
+ oldMethodVarsSorted = oldMethodVarsSortedBase + (-ICorDebugInfo::UNKNOWN_ILNUM);
- // We are atleast after the "push ebp; mov ebp, esp"
+ memset((void *)oldMethodVarsSortedBase, 0, oldNumVars * sizeof(ICorDebugInfo::NativeVarInfo));
- offset = SKIP_MOV_REG_REG(methodStart,
- SKIP_PUSH_REG(methodStart, offset));
+ for (local = 0; local < oldNumVars;local++)
+ oldMethodVarsSortedBase[local].loc.vlType = ICorDebugInfo::VLT_INVALID;
- /* At this point, EBP has been set up. The caller's ESP and the return value
- can be determined using EBP. Since we are still in the prolog,
- we need to know our exact location to determine the callee-saved registers */
+ BYTE **rgVCs = NULL;
+ DWORD oldMethodOffset = pOldCodeInfo->GetRelOffset();
- const unsigned curEBP = GetRegdisplayFP(pContext);
+ for (pOldVar = oldMethodVars, local = 0;
+ local < oldMethodVarsCount;
+ local++, pOldVar++)
+ {
+ DWORD varNumber = pOldVar->varNumber;
- if (flags & UpdateAllRegs)
- {
- PTR_DWORD pSavedRegs = PTR_DWORD((TADDR)curEBP);
+ _ASSERTE(varNumber + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < oldNumVars);
- /* make sure that we align ESP just like the method's prolog did */
- if (info->doubleAlign)
- {
- // "and esp,-8"
- offset = SKIP_ARITH_REG(-8, methodStart, offset);
- if (curEBP & 0x04)
+ // Only care about old local variables alive at oldMethodOffset
+ if (pOldVar->startOffset <= oldMethodOffset &&
+ pOldVar->endOffset > oldMethodOffset)
{
- pSavedRegs--;
-#ifdef _DEBUG
- if (dspPtr) printf("EnumRef: dblalign ebp: %08X\n", curEBP);
-#endif
+ // Indexing should be performed with a signed value - could be negative.
+ oldMethodVarsSorted[(int32_t)varNumber] = *pOldVar;
}
}
- /* Increment "offset" in steps to see which callee-saved
- registers have been pushed already */
+ // 3) Next sort the new var info by varNumber. We want to do this here, since
+ // we're allocating memory (which may fail) - do this before going to step 2
- for (unsigned i = 0; i < STRING_LENGTH(CALLEE_SAVED_REGISTERS_MASK); i++)
- {
- RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
- _ASSERTE(regMask != RM_EBP);
+ // First, count the new vars the same way we did the old vars above.
- if ((info->savedRegMask & regMask) == 0)
- continue;
+ const ICorDebugInfo::NativeVarInfo * pNewVar;
- if (InstructionAlreadyExecuted(offset, curOffs))
+ unsigned newNumVars = unsigned(-ICorDebugInfo::UNKNOWN_ILNUM);
+ for (pNewVar = newMethodVars, local = 0;
+ local < newMethodVarsCount;
+ local++, pNewVar++)
+ {
+ DWORD varNumber = pNewVar->varNumber;
+ if (signed(varNumber) >= 0)
{
- SetLocation(pContext, i, PTR_DWORD(--pSavedRegs));
+ // This is an explicit (not special) var, so add its varNumber + 1 to our
+ // max count ("+1" because varNumber is zero-based).
+ newNumVars = max(newNumVars, unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) + varNumber + 1);
}
-
- // "push reg"
- offset = SKIP_PUSH_REG(methodStart, offset) ;
}
- TRASH_CALLEE_UNSAVED_REGS(pContext);
- }
-
- /* The caller's saved EBP is pointed to by our EBP */
-
- pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP));
- pContext->SP = DWORD((TADDR)(curEBP + sizeof(void *)));
-
- /* Stack pointer points to return address */
-
- pContext->PCTAddr = (TADDR)pContext->SP;
- pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
-}
-
-/*****************************************************************************/
-
-bool UnwindEbpDoubleAlignFrame(
- PREGDISPLAY pContext,
- EECodeInfo *pCodeInfo,
- hdrInfo *info,
- PTR_CBYTE table,
- PTR_CBYTE methodStart,
- DWORD curOffs,
- unsigned flags)
-{
- LIMITED_METHOD_CONTRACT;
- SUPPORTS_DAC;
-
- _ASSERTE(info->ebpFrame || info->doubleAlign);
-
- const unsigned curESP = pContext->SP;
- const unsigned curEBP = GetRegdisplayFP(pContext);
+ // sorted by varNumber
+ newMethodVarsSortedBase = new (nothrow) ICorDebugInfo::NativeVarInfo[newNumVars];
+ if (!newMethodVarsSortedBase)
+ {
+ hr = E_FAIL;
+ goto ErrExit;
+ }
+ newMethodVarsSorted = newMethodVarsSortedBase + (-ICorDebugInfo::UNKNOWN_ILNUM);
- /* First check if we are in a filter (which is obviously after the prolog) */
+ memset(newMethodVarsSortedBase, 0, newNumVars * sizeof(ICorDebugInfo::NativeVarInfo));
+ for (local = 0; local < newNumVars;local++)
+ newMethodVarsSortedBase[local].loc.vlType = ICorDebugInfo::VLT_INVALID;
- if (info->handlers && info->prologOffs == hdrInfo::NOT_IN_PROLOG)
- {
- TADDR baseSP;
+ DWORD newMethodOffset = pNewCodeInfo->GetRelOffset();
-#ifdef FEATURE_EH_FUNCLETS
- // Funclets' frame pointers(EBP) are always restored so they can access to main function's local variables.
- // Therefore the value of EBP is invalid for unwinder so we should use ESP instead.
- // TODO If funclet frame layout is changed from CodeGen::genFuncletProlog() and genFuncletEpilog(),
- // we need to change here accordingly. It is likely to have changes when introducing PSPSym.
- // TODO Currently we assume that ESP of funclet frames is always fixed but actually it could change.
- if (pCodeInfo->IsFunclet())
+ for (pNewVar = newMethodVars, local = 0;
+ local < newMethodVarsCount;
+ local++, pNewVar++)
{
- baseSP = curESP;
- // Set baseSP as initial SP
- baseSP += GetPushedArgSize(info, table, curOffs);
-
-#ifdef UNIX_X86_ABI
- // 16-byte stack alignment padding (allocated in genFuncletProlog)
- // Current funclet frame layout (see CodeGen::genFuncletProlog() and genFuncletEpilog()):
- // prolog: sub esp, 12
- // epilog: add esp, 12
- // ret
- // SP alignment padding should be added for all instructions except the first one and the last one.
- // Epilog may not exist (unreachable), so we need to check the instruction code.
- const TADDR funcletStart = pCodeInfo->GetJitManager()->GetFuncletStartAddress(pCodeInfo);
- if (funcletStart != pCodeInfo->GetCodeAddress() && methodStart[pCodeInfo->GetRelOffset()] != X86_INSTR_RETN)
- baseSP += 12;
-#endif
-
- pContext->PCTAddr = baseSP;
- pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
+ DWORD varNumber = pNewVar->varNumber;
- pContext->SP = (DWORD)(baseSP + sizeof(TADDR));
+ _ASSERTE(varNumber + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < newNumVars);
- return true;
+ // Only care about new local variables alive at newMethodOffset
+ if (pNewVar->startOffset <= newMethodOffset &&
+ pNewVar->endOffset > newMethodOffset)
+ {
+ // Indexing should be performed with a signed valued - could be negative.
+ newMethodVarsSorted[(int32_t)varNumber] = *pNewVar;
+ }
}
-#else // FEATURE_EH_FUNCLETS
- FrameType frameType = GetHandlerFrameInfo(info, curEBP,
- curESP, (DWORD) IGNORE_VAL,
- &baseSP);
+ _ASSERTE(newNumVars >= oldNumVars ||
+ !"Not allowed to reduce the number of locals between versions!");
- /* If we are in a filter, we only need to unwind the funclet stack.
- For catches/finallies, the normal handling will
- cause the frame to be unwound all the way up to ebp skipping
- other frames above it. This is OK, as those frames will be
- dead. Also, the EE will detect that this has happened and it
- will handle any EE frames correctly.
- */
+ LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: gathered info!\n"));
- if (frameType == FR_INVALID)
+ rgVal1 = new (nothrow) SIZE_T[newNumVars];
+ if (rgVal1 == NULL)
{
- return false;
+ hr = E_FAIL;
+ goto ErrExit;
}
- if (frameType == FR_FILTER)
+ rgVal2 = new (nothrow) SIZE_T[newNumVars];
+ if (rgVal2 == NULL)
{
- pContext->PCTAddr = baseSP;
- pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
-
- pContext->SP = (DWORD)(baseSP + sizeof(TADDR));
-
- // pContext->pEbp = same as before;
+ hr = E_FAIL;
+ goto ErrExit;
+ }
-#ifdef _DEBUG
- /* The filter has to be called by the VM. So we dont need to
- update callee-saved registers.
- */
+ // 4) Next we'll zero them out, so any variables that aren't in scope
+ // in the old method, but are in scope in the new, will have the
+ // default, zero, value.
- if (flags & UpdateAllRegs)
- {
- static DWORD s_badData = 0xDEADBEEF;
+ memset(rgVal1, 0, sizeof(SIZE_T) * newNumVars);
+ memset(rgVal2, 0, sizeof(SIZE_T) * newNumVars);
- pContext->SetEaxLocation(&s_badData);
- pContext->SetEcxLocation(&s_badData);
- pContext->SetEdxLocation(&s_badData);
+ unsigned varsToGet = (oldNumVars > newNumVars)
+ ? newNumVars
+ : oldNumVars;
- pContext->SetEbxLocation(&s_badData);
- pContext->SetEsiLocation(&s_badData);
- pContext->SetEdiLocation(&s_badData);
- }
-#endif
+ // 2) Get all the info about current variables, registers, etc.
- return true;
+ hr = g_pDebugInterface->GetVariablesFromOffset(pOldCodeInfo->GetMethodDesc(),
+ varsToGet,
+ oldMethodVarsSortedBase,
+ oldMethodOffset,
+ &oldCtx,
+ rgVal1,
+ rgVal2,
+ newNumVars,
+ &rgVCs);
+ if (FAILED(hr))
+ {
+ goto ErrExit;
}
-#endif // !FEATURE_EH_FUNCLETS
- }
-
- //
- // Prolog of an EBP method
- //
- if (info->prologOffs != hdrInfo::NOT_IN_PROLOG)
- {
- UnwindEbpDoubleAlignFrameProlog(pContext, info, methodStart, flags);
- /* Now adjust stack pointer. */
+ LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: got vars!\n"));
- pContext->SP += ESPIncrOnReturn(info);
- return true;
- }
+ /*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
+ * IMPORTANT : Once we start munging on the context, we cannot return
+ * EnC_FAIL, as this should be a transacted commit,
+ **=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/
- if (flags & UpdateAllRegs)
- {
- // Get to the first callee-saved register
- PTR_DWORD pSavedRegs = PTR_DWORD((TADDR)curEBP);
+#if defined(TARGET_X86)
+ // Zero out all the registers as some may hold new variables.
+ pCtx->Eax = pCtx->Ecx = pCtx->Edx = pCtx->Ebx = pCtx->Esi = pCtx->Edi = 0;
- if (info->doubleAlign && (curEBP & 0x04))
- pSavedRegs--;
+ // 3) zero out the stack frame - this'll initialize _all_ variables
- for (unsigned i = 0; i < STRING_LENGTH(CALLEE_SAVED_REGISTERS_MASK); i++)
- {
- RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
- if ((info->savedRegMask & regMask) == 0)
- continue;
+ /*-------------------------------------------------------------------------
+ * Adjust the stack height
+ */
+ pCtx->Esp -= (newInfo.stackSize - oldInfo.stackSize);
- SetLocation(pContext, i, --pSavedRegs);
- }
- }
+ // Zero-init the local and tempory section of new stack frame being careful to avoid
+ // touching anything in the frame header.
+ // This is necessary to ensure that any JIT temporaries in the old version can't be mistaken
+ // for ObjRefs now.
+ size_t frameHeaderSize = GetSizeOfFrameHeaderForEnC( &newInfo );
+ _ASSERTE( frameHeaderSize <= oldInfo.stackSize );
+ _ASSERTE( GetSizeOfFrameHeaderForEnC( &oldInfo ) == frameHeaderSize );
- /* The caller's ESP will be equal to EBP + retAddrSize + argSize. */
+#elif defined(TARGET_AMD64) && !defined(UNIX_AMD64_ABI)
- pContext->SP = (DWORD)(curEBP + sizeof(curEBP) + ESPIncrOnReturn(info));
+ // Next few statements zero out all registers that may end up holding new variables.
- /* The caller's saved EIP is right after our EBP */
+ // volatile int registers (JIT may use these to enregister variables)
+ pCtx->Rax = pCtx->Rcx = pCtx->Rdx = pCtx->R8 = pCtx->R9 = pCtx->R10 = pCtx->R11 = 0;
- pContext->PCTAddr = (TADDR)curEBP + RETURN_ADDR_OFFS * sizeof(TADDR);
- pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
+ // volatile float registers
+ pCtx->Xmm1.High = pCtx->Xmm1.Low = 0;
+ pCtx->Xmm2.High = pCtx->Xmm2.Low = 0;
+ pCtx->Xmm3.High = pCtx->Xmm3.Low = 0;
+ pCtx->Xmm4.High = pCtx->Xmm4.Low = 0;
+ pCtx->Xmm5.High = pCtx->Xmm5.Low = 0;
- /* The caller's saved EBP is pointed to by our EBP */
+ // 3) zero out the stack frame - this'll initialize _all_ variables
- pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP));
- return true;
-}
+ /*-------------------------------------------------------------------------
+ * Adjust the stack height
+ */
-bool UnwindStackFrame(PREGDISPLAY pContext,
- EECodeInfo *pCodeInfo,
- unsigned flags,
- CodeManState *pState)
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- HOST_NOCALLS;
- SUPPORTS_DAC;
- } CONTRACTL_END;
+ TADDR newStackBase = callerSP - newFixedStackSize;
- // Address where the method has been interrupted
- PCODE breakPC = pContext->ControlPC;
- _ASSERTE(PCODEToPINSTR(breakPC) == pCodeInfo->GetCodeAddress());
+ SetSP(pCtx, newStackBase);
- PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode());
+ // We want to zero-out everything pushed after the frame header. This way we'll zero
+ // out locals (both old & new) and temporaries. This is necessary to ensure that any
+ // JIT temporaries in the old version can't be mistaken for ObjRefs now. (I am told
+ // this last point is less of an issue on x64 as it is on x86, but zeroing out the
+ // temporaries is still the cleanest, most robust way to go.)
+ size_t frameHeaderSize = newSizeOfPreservedArea;
+ _ASSERTE(frameHeaderSize <= oldFixedStackSize);
+ _ASSERTE(frameHeaderSize <= newFixedStackSize);
- GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
- PTR_VOID methodInfoPtr = gcInfoToken.Info;
- DWORD curOffs = pCodeInfo->GetRelOffset();
+ // For EnC-compliant x64 code, FP == SP. Since SP changed above, update FP now
+ pCtx->Rbp = newStackBase;
- _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
- CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
+#else
+#if defined(TARGET_ARM64)
+ // Zero out volatile part of stack frame
+ // x0-x17
+ memset(&pCtx->X[0], 0, sizeof(pCtx->X[0]) * 18);
+ // v0-v7
+ memset(&pCtx->V[0], 0, sizeof(pCtx->V[0]) * 8);
+ // v16-v31
+ memset(&pCtx->V[16], 0, sizeof(pCtx->V[0]) * 16);
+#elif defined(TARGET_AMD64)
+ // SysV ABI
+ pCtx->Rax = pCtx->Rdi = pCtx->Rsi = pCtx->Rdx = pCtx->Rcx = pCtx->R8 = pCtx->R9 = 0;
- if (pState->dwIsSet == 0)
- {
- /* Extract the necessary information from the info block header */
+ // volatile float registers
+ memset(&pCtx->Xmm0, 0, sizeof(pCtx->Xmm0) * 16);
+#else
+ PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
+#endif
- stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken,
- curOffs,
- &stateBuf->hdrInfoBody);
- }
+ TADDR newStackBase = callerSP - newFixedStackSize;
- PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf->hdrInfoSize;
+ SetSP(pCtx, newStackBase);
- hdrInfo * info = &stateBuf->hdrInfoBody;
+ size_t frameHeaderSize = newSizeOfPreservedArea;
+ _ASSERTE(frameHeaderSize <= oldFixedStackSize);
+ _ASSERTE(frameHeaderSize <= newFixedStackSize);
- info->isSpeculativeStackWalk = ((flags & SpeculativeStackwalk) != 0);
+ // EnC prolog saves only FP (and LR on arm64), and FP points to saved FP for frame chaining.
+ // These should already be set up from previous version.
+ _ASSERTE(GetFP(pCtx) == callerSP - 16);
+#endif
- if (info->epilogOffs != hdrInfo::NOT_IN_EPILOG)
- {
- /*---------------------------------------------------------------------
- * First, handle the epilog
- */
+ // Perform some debug-only sanity checks on stack variables. Some checks are
+ // performed differently between X86/AMD64.
- PTR_CBYTE epilogBase = methodStart + (curOffs - info->epilogOffs);
- UnwindEpilog(pContext, info, epilogBase, flags);
- }
- else if (!info->ebpFrame && !info->doubleAlign)
- {
- /*---------------------------------------------------------------------
- * Now handle ESP frames
- */
+#ifdef _DEBUG
+ for( unsigned i = 0; i < newNumVars; i++ )
+ {
+ // Make sure that stack variables existing in both old and new methods did not
+ // move. This matters if the address of a local is used in the remapped method.
+ // For example:
+ //
+ // static unsafe void Main(string[] args)
+ // {
+ // int x;
+ // int* p = &x;
+ // <- Edit made here - cannot move address of x
+ // *p = 5;
+ // }
+ //
+ if ((i + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < oldNumVars) && // Does variable exist in old method?
+ (oldMethodVarsSorted[i].loc.vlType == ICorDebugInfo::VLT_STK) && // Is the variable on the stack?
+ (newMethodVarsSorted[i].loc.vlType == ICorDebugInfo::VLT_STK))
+ {
+ SIZE_T * pOldVarStackLocation = NativeVarStackAddr(oldMethodVarsSorted[i].loc, &oldCtx);
+ SIZE_T * pNewVarStackLocation = NativeVarStackAddr(newMethodVarsSorted[i].loc, pCtx);
+ _ASSERTE(pOldVarStackLocation == pNewVarStackLocation);
+ }
- UnwindEspFrame(pContext, info, table, methodStart, curOffs, flags);
- return true;
- }
- else
- {
- /*---------------------------------------------------------------------
- * Now we know that have an EBP frame
- */
+ // Sanity-check that the range we're clearing contains all of the stack variables
- if (!UnwindEbpDoubleAlignFrame(pContext, pCodeInfo, info, table, methodStart, curOffs, flags))
- return false;
- }
+#if defined(TARGET_X86)
+ const ICorDebugInfo::VarLoc &varLoc = newMethodVarsSortedBase[i].loc;
+ if( varLoc.vlType == ICorDebugInfo::VLT_STK )
+ {
+ // This is an EBP frame, all stack variables should be EBP relative
+ _ASSERTE( varLoc.vlStk.vlsBaseReg == ICorDebugInfo::REGNUM_EBP );
+ // Generic special args may show up as locals with positive offset from EBP, so skip them
+ if( varLoc.vlStk.vlsOffset <= 0 )
+ {
+ // Normal locals must occur after the header on the stack
+ _ASSERTE( unsigned(-varLoc.vlStk.vlsOffset) >= frameHeaderSize );
+ // Value must occur before the top of the stack
+ _ASSERTE( unsigned(-varLoc.vlStk.vlsOffset) < newInfo.stackSize );
+ }
- // TODO [DAVBR]: For the full fix for VsWhidbey 450273, all the below
- // may be uncommented once isLegalManagedCodeCaller works properly
- // with non-return address inputs, and with non-DEBUG builds
- /*
- // Ensure isLegalManagedCodeCaller succeeds for speculative stackwalks.
- // (We just assert this below for non-speculative stackwalks.)
- //
- FAIL_IF_SPECULATIVE_WALK(isLegalManagedCodeCaller(GetControlPC(pContext)));
- */
+ // Ideally we'd like to verify that the stack locals (if any) start at exactly the end
+ // of the header. However, we can't easily determine the size of value classes here,
+ // and so (since the stack grows towards 0) can't easily determine where the end of
+ // the local lies.
+ }
+#elif defined(TARGET_AMD64) || defined(TARGET_ARM64)
+ switch(newMethodVarsSortedBase[i].loc.vlType)
+ {
+ default:
+ // No validation here for non-stack locals
+ break;
- return true;
-}
+ case ICorDebugInfo::VLT_STK_BYREF:
+ {
+ // For byrefs, verify that the ptr will be zeroed out
-#endif // TARGET_X86
+ SIZE_T regOffs = GetRegOffsInCONTEXT(newMethodVarsSortedBase[i].loc.vlStk.vlsBaseReg);
+ TADDR baseReg = *(TADDR *)(regOffs + (BYTE*)pCtx);
+ TADDR addrOfPtr = baseReg + newMethodVarsSortedBase[i].loc.vlStk.vlsOffset;
-#ifdef FEATURE_EH_FUNCLETS
-#ifdef TARGET_X86
-size_t EECodeManager::GetResumeSp( PCONTEXT pContext )
-{
- PCODE currentPc = PCODE(pContext->Eip);
+ _ASSERTE(
+ // The ref must exist in the portion we'll zero-out
+ (
+ (newStackBase <= addrOfPtr) &&
+ (addrOfPtr < newStackBase + (newFixedStackSize - frameHeaderSize))
+ ) ||
+ // OR in the caller's frame (for parameters)
+ (addrOfPtr >= newStackBase + newFixedStackSize));
- _ASSERTE(ExecutionManager::IsManagedCode(currentPc));
+ // Deliberately fall through, so that we also verify that the value that the ptr
+ // points to will be zeroed out
+ // ...
+ }
+ __fallthrough;
- EECodeInfo codeInfo(currentPc);
+ case ICorDebugInfo::VLT_STK:
+ case ICorDebugInfo::VLT_STK2:
+ case ICorDebugInfo::VLT_REG_STK:
+ case ICorDebugInfo::VLT_STK_REG:
+ SIZE_T * pVarStackLocation = NativeVarStackAddr(newMethodVarsSortedBase[i].loc, pCtx);
+ _ASSERTE (pVarStackLocation != NULL);
+ _ASSERTE(
+ // The value must exist in the portion we'll zero-out
+ (
+ (newStackBase <= (TADDR) pVarStackLocation) &&
+ ((TADDR) pVarStackLocation < newStackBase + (newFixedStackSize - frameHeaderSize))
+ ) ||
+ // OR in the caller's frame (for parameters)
+ ((TADDR) pVarStackLocation >= newStackBase + newFixedStackSize));
+ break;
+ }
+#else // !X86, !X64, !ARM64
+ PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
+#endif
+ }
- PTR_CBYTE methodStart = PTR_CBYTE(codeInfo.GetSavedMethodCode());
+#endif // _DEBUG
- GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken();
- PTR_VOID methodInfoPtr = gcInfoToken.Info;
- DWORD curOffs = codeInfo.GetRelOffset();
+ // Clear the local and temporary stack space
- CodeManStateBuf stateBuf;
+#if defined(TARGET_X86)
+ memset((void*)(size_t)(pCtx->Esp), 0, newInfo.stackSize - frameHeaderSize );
+#elif defined(TARGET_AMD64) || defined(TARGET_ARM64)
+ memset((void*)newStackBase, 0, newFixedStackSize - frameHeaderSize);
- stateBuf.hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken,
- curOffs,
- &stateBuf.hdrInfoBody);
+ // Restore PSPSym for the new function. Its value should be set to our new FP. But
+ // first, we gotta find PSPSym's location on the stack
+ INT32 nNewPspSymStackSlot = newGcDecoder.GetPSPSymStackSlot();
+ if (nNewPspSymStackSlot != NO_PSP_SYM)
+ {
+#if defined(TARGET_AMD64)
+ *PTR_TADDR(newStackBase + nNewPspSymStackSlot) = newStackBase;
+#elif defined(TARGET_ARM64)
+ *PTR_TADDR(callerSP + nNewPspSymStackSlot) = callerSP;
+#else
+ PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
+#endif
+ }
+#else // !X86, !X64, !ARM64
+ PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
+#endif
- PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf.hdrInfoSize;
+ // 4) Put the variables from step 3 into their new locations.
- hdrInfo *info = &stateBuf.hdrInfoBody;
+ LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: set vars!\n"));
- _ASSERTE(info->epilogOffs == hdrInfo::NOT_IN_EPILOG && info->prologOffs == hdrInfo::NOT_IN_PROLOG);
+ // Move the old variables into their new places.
- bool isESPFrame = !info->ebpFrame && !info->doubleAlign;
+ hr = g_pDebugInterface->SetVariablesAtOffset(pNewCodeInfo->GetMethodDesc(),
+ newNumVars,
+ newMethodVarsSortedBase,
+ newMethodOffset,
+ pCtx, // place them into the new context
+ rgVal1,
+ rgVal2,
+ rgVCs);
- if (codeInfo.IsFunclet())
- {
- // Treat funclet's frame as ESP frame
- isESPFrame = true;
+ /*-----------------------------------------------------------------------*/
}
+ErrExit:
+ if (oldMethodVarsSortedBase)
+ delete[] oldMethodVarsSortedBase;
+ if (newMethodVarsSortedBase)
+ delete[] newMethodVarsSortedBase;
+ if (rgVal1 != NULL)
+ delete[] rgVal1;
+ if (rgVal2 != NULL)
+ delete[] rgVal2;
- if (isESPFrame)
- {
- const size_t curESP = (size_t)(pContext->Esp);
- return curESP + GetPushedArgSize(info, table, curOffs);
- }
+ LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: exiting!\n"));
- const size_t curEBP = (size_t)(pContext->Ebp);
- return GetOutermostBaseFP(curEBP, info);
+ return hr;
}
-#endif // TARGET_X86
-#endif // FEATURE_EH_FUNCLETS
+#endif // !FEATURE_METADATA_UPDATER
-#ifndef FEATURE_EH_FUNCLETS
+#endif // #ifndef DACCESS_COMPILE
+#ifdef USE_GC_INFO_DECODER
/*****************************************************************************
*
- * Unwind the current stack frame, i.e. update the virtual register
- * set in pContext. This will be similar to the state after the function
- * returns back to caller (IP points to after the call, Frame and Stack
- * pointer has been reset, callee-saved registers restored (if UpdateAllRegs),
- * callee-unsaved registers are trashed.
- * Returns success of operation.
+ * Is the function currently at a "GC safe point" ?
*/
-
-bool EECodeManager::UnwindStackFrame(PREGDISPLAY pContext,
- EECodeInfo *pCodeInfo,
- unsigned flags,
- CodeManState *pState)
-{
-#ifdef TARGET_X86
- return ::UnwindStackFrame(pContext, pCodeInfo, flags, pState);
-#else // TARGET_X86
- PORTABILITY_ASSERT("EECodeManager::UnwindStackFrame");
- return false;
-#endif // _TARGET_???_
-}
-
-/*****************************************************************************/
-#else // !FEATURE_EH_FUNCLETS
-/*****************************************************************************/
-
-bool EECodeManager::UnwindStackFrame(PREGDISPLAY pContext,
- EECodeInfo *pCodeInfo,
- unsigned flags,
- CodeManState *pState)
+bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo,
+ DWORD dwRelOffset)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
- _ASSERTE(pCodeInfo != NULL);
+ GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
-#ifdef HAS_LIGHTUNWIND
- if (flags & LightUnwind)
- {
- LightUnwindStackFrame(pContext, pCodeInfo, UnwindCurrentStackFrame);
- return true;
- }
-#endif
+ GcInfoDecoder gcInfoDecoder(
+ gcInfoToken,
+ DECODE_INTERRUPTIBILITY,
+ dwRelOffset
+ );
- Thread::VirtualUnwindCallFrame(pContext, pCodeInfo);
- return true;
+ return gcInfoDecoder.IsInterruptible();
}
-/*****************************************************************************/
-#endif // FEATURE_EH_FUNCLETS
+#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
+bool EECodeManager::HasTailCalls( EECodeInfo *pCodeInfo)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
-/*****************************************************************************/
+ GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
-/* report args in 'msig' to the GC.
- 'argsStart' is start of the stack-based arguments
- 'varArgSig' describes the arguments
- 'ctx' has the GC reporting info
-*/
-void promoteVarArgs(PTR_BYTE argsStart, PTR_VASigCookie varArgSig, GCCONTEXT* ctx)
-{
- WRAPPER_NO_CONTRACT;
+ GcInfoDecoder gcInfoDecoder(
+ gcInfoToken,
+ DECODE_HAS_TAILCALLS,
+ 0
+ );
- SigTypeContext typeContext(varArgSig->classInst, varArgSig->methodInst);
- MetaSig msig(varArgSig->signature,
- varArgSig->pModule,
- &typeContext);
+ return gcInfoDecoder.HasTailCalls();
+}
+#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64
- PTR_BYTE pFrameBase = argsStart - TransitionBlock::GetOffsetOfArgs();
+#if defined(TARGET_AMD64) && defined(_DEBUG)
- ArgIterator argit(&msig);
+struct FindEndOfLastInterruptibleRegionState
+{
+ unsigned curOffset;
+ unsigned endOffset;
+ unsigned lastRangeOffset;
+};
-#ifdef TARGET_X86
- // For the X86 target the JIT does not report any of the fixed args for a varargs method
- // So we report the fixed args via the promoteArgs call below
- bool skipFixedArgs = false;
-#else
- // For other platforms the JITs do report the fixed args of a varargs method
- // So we must tell promoteArgs to skip to the end of the fixed args
- bool skipFixedArgs = true;
-#endif
+bool FindEndOfLastInterruptibleRegionCB (
+ UINT32 startOffset,
+ UINT32 stopOffset,
+ LPVOID hCallback)
+{
+ FindEndOfLastInterruptibleRegionState *pState = (FindEndOfLastInterruptibleRegionState*)hCallback;
- bool inVarArgs = false;
+ //
+ // If the current range doesn't overlap the given range, keep searching.
+ //
+ if ( startOffset >= pState->endOffset
+ || stopOffset < pState->curOffset)
+ {
+ return false;
+ }
- int argOffset;
- while ((argOffset = argit.GetNextOffset()) != TransitionBlock::InvalidOffset)
+ //
+ // If the range overlaps the end, then the last point is the end.
+ //
+ if ( stopOffset > pState->endOffset
+ /*&& startOffset < pState->endOffset*/)
{
- if (msig.GetArgProps().AtSentinel())
- inVarArgs = true;
+ // The ranges should be sorted in increasing order.
+ CONSISTENCY_CHECK(startOffset >= pState->lastRangeOffset);
- // if skipFixedArgs is false we report all arguments
- // otherwise we just report the varargs.
- if (!skipFixedArgs || inVarArgs)
- {
- ArgDestination argDest(pFrameBase, argOffset, argit.GetArgLocDescForStructInRegs());
- msig.GcScanRoots(&argDest, ctx->f, ctx->sc);
- }
+ pState->lastRangeOffset = pState->endOffset;
+ return true;
}
-}
-#ifndef DACCESS_COMPILE
-FCIMPL1(void, GCReporting::Register, GCFrame* frame)
-{
- FCALL_CONTRACT;
+ //
+ // See if the end of this range is the closet to the end that we've found
+ // so far.
+ //
+ if (stopOffset > pState->lastRangeOffset)
+ pState->lastRangeOffset = stopOffset;
- // Construct a GCFrame.
- _ASSERTE(frame != NULL);
- frame->Push(GetThread());
+ return false;
}
-FCIMPLEND
-FCIMPL1(void, GCReporting::Unregister, GCFrame* frame)
+/*
+ Locates the end of the last interruptible region in the given code range.
+ Returns 0 if the entire range is uninterruptible. Returns the end point
+ if the entire range is interruptible.
+*/
+unsigned EECodeManager::FindEndOfLastInterruptibleRegion(unsigned curOffset,
+ unsigned endOffset,
+ GCInfoToken gcInfoToken)
{
- FCALL_CONTRACT;
+#ifndef DACCESS_COMPILE
+ GcInfoDecoder gcInfoDecoder(
+ gcInfoToken,
+ DECODE_FOR_RANGES_CALLBACK
+ );
- // Destroy the GCFrame.
- _ASSERTE(frame != NULL);
- frame->Remove();
+ FindEndOfLastInterruptibleRegionState state;
+ state.curOffset = curOffset;
+ state.endOffset = endOffset;
+ state.lastRangeOffset = 0;
+
+ gcInfoDecoder.EnumerateInterruptibleRanges(&FindEndOfLastInterruptibleRegionCB, &state);
+
+ return state.lastRangeOffset;
+#else
+ DacNotImpl();
+ return NULL;
+#endif // #ifndef DACCESS_COMPILE
}
-FCIMPLEND
-#endif // !DACCESS_COMPILE
-#ifndef USE_GC_INFO_DECODER
+#endif // TARGET_AMD64 && _DEBUG
+
+
+#else // !USE_GC_INFO_DECODER
/*****************************************************************************
*
- * Enumerate all live object references in that function using
- * the virtual register set.
- * Returns success of operation.
+ * Is the function currently at a "GC safe point" ?
*/
-
-bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext,
- EECodeInfo *pCodeInfo,
- unsigned flags,
- GCEnumCallback pCallBack,
- LPVOID hCallBack,
- DWORD relOffsetOverride)
+bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo,
+ DWORD dwRelOffset)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
+ SUPPORTS_DAC;
} CONTRACTL_END;
-#ifdef FEATURE_EH_FUNCLETS
- if (flags & ParentOfFuncletStackFrame)
- {
- LOG((LF_GCROOTS, LL_INFO100000, "Not reporting this frame because it was already reported via another funclet.\n"));
- return true;
- }
-#endif // FEATURE_EH_FUNCLETS
+ hdrInfo info;
+ BYTE * table;
- GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
- unsigned curOffs = pCodeInfo->GetRelOffset();
+ /* Extract the necessary information from the info block header */
- unsigned EBP = GetRegdisplayFP(pContext);
- unsigned ESP = pContext->SP;
+ table = (BYTE *)DecodeGCHdrInfo(pCodeInfo->GetGCInfoToken(),
+ dwRelOffset,
+ &info);
- unsigned ptrOffs;
+ /* workaround: prevent interruption within prolog/epilog */
- unsigned count;
+ if (info.prologOffs != hdrInfo::NOT_IN_PROLOG || info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
+ return false;
- hdrInfo info;
- PTR_CBYTE table = PTR_CBYTE(gcInfoToken.Info);
-#if 0
- printf("EECodeManager::EnumGcRefs - EIP = %08x ESP = %08x offset = %x GC Info is at %08x\n", *pContext->pPC, ESP, curOffs, table);
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
#endif
+ return (info.interruptible);
+}
- /* Extract the necessary information from the info block header */
+#endif // !USE_GC_INFO_DECODER
- table += DecodeGCHdrInfo(gcInfoToken,
- curOffs,
- &info);
- _ASSERTE( curOffs <= info.methodSize);
+#if defined(FEATURE_EH_FUNCLETS)
-#ifdef _DEBUG
-// if ((gcInfoToken.Info == (void*)0x37760d0) && (curOffs == 0x264))
-// __asm int 3;
-
- if (trEnumGCRefs) {
- static unsigned lastESP = 0;
- unsigned diffESP = ESP - lastESP;
- if (diffESP > 0xFFFF) {
- printf("------------------------------------------------------\n");
- }
- lastESP = ESP;
- printf("EnumGCRefs [%s][%s] at %s.%s + 0x%03X:\n",
- info.ebpFrame?"ebp":" ",
- info.interruptible?"int":" ",
- "UnknownClass","UnknownMethod", curOffs);
- fflush(stdout);
+void EECodeManager::EnsureCallerContextIsValid( PREGDISPLAY pRD, EECodeInfo * pCodeInfo /*= NULL*/, unsigned flags /*= 0*/)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
}
-#endif
-
- /* Are we in the prolog or epilog of the method? */
+ CONTRACTL_END;
- if (info.prologOffs != hdrInfo::NOT_IN_PROLOG ||
- info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
+ if( !pRD->IsCallerContextValid )
{
-
-#if !DUMP_PTR_REFS
- // Under normal circumstances the system will not suspend a thread
- // if it is in the prolog or epilog of the function. However ThreadAbort
- // exception or stack overflows can cause EH to happen in a prolog.
- // Once in the handler, a GC can happen, so we can get to this code path.
- // However since we are tearing down this frame, we don't need to report
- // anything and we can simply return.
-
- _ASSERTE(flags & ExecutionAborted);
+ if ((flags & LightUnwind) && (pCodeInfo != NULL))
+ {
+#if !defined(DACCESS_COMPILE) && defined(HAS_LIGHTUNWIND)
+ LightUnwindStackFrame(pRD, pCodeInfo, EnsureCallerStackFrameIsValid);
+#else
+ // We need to make a copy here (instead of switching the pointers), in order to preserve the current context
+ *(pRD->pCallerContext) = *(pRD->pCurrentContext);
+ // Skip updating context registers for light unwind
+ Thread::VirtualUnwindCallFrame(pRD->pCallerContext, NULL, pCodeInfo);
#endif
- return true;
- }
-
-#ifdef _DEBUG
-#define CHK_AND_REPORT_REG(reg, doIt, iptr, regName) \
- if (doIt) \
- { \
- if (dspPtr) \
- printf(" Live pointer register %s: ", #regName); \
- pCallBack(hCallBack, \
- (OBJECTREF*)(pContext->Get##regName##Location()), \
- (iptr ? GC_CALL_INTERIOR : 0) \
- | CHECK_APP_DOMAIN \
- DAC_ARG(DacSlotLocation(reg, 0, false))); \
}
-#else // !_DEBUG
-#define CHK_AND_REPORT_REG(reg, doIt, iptr, regName) \
- if (doIt) \
- pCallBack(hCallBack, \
- (OBJECTREF*)(pContext->Get##regName##Location()), \
- (iptr ? GC_CALL_INTERIOR : 0) \
- | CHECK_APP_DOMAIN \
- DAC_ARG(DacSlotLocation(reg, 0, false)));
+ else
+ {
+ // We need to make a copy here (instead of switching the pointers), in order to preserve the current context
+ *(pRD->pCallerContext) = *(pRD->pCurrentContext);
+ *(pRD->pCallerContextPointers) = *(pRD->pCurrentContextPointers);
+ Thread::VirtualUnwindCallFrame(pRD->pCallerContext, pRD->pCallerContextPointers, pCodeInfo);
+ }
-#endif // _DEBUG
+ pRD->IsCallerContextValid = TRUE;
+ }
-#ifndef FEATURE_EH_FUNCLETS
- /* What kind of a frame is this ? */
+ _ASSERTE( pRD->IsCallerContextValid );
+}
- FrameType frameType = FR_NORMAL;
- TADDR baseSP = 0;
+size_t EECodeManager::GetCallerSp( PREGDISPLAY pRD )
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
- if (info.handlers)
+ // Don't add usage of this field. This is only temporary.
+ // See ExceptionTracker::InitializeCrawlFrame() for more information.
+ if (!pRD->IsCallerSPValid)
{
- _ASSERTE(info.ebpFrame);
-
- bool hasInnerFilter, hadInnerFilter;
- frameType = GetHandlerFrameInfo(&info, EBP,
- ESP, (DWORD) IGNORE_VAL,
- &baseSP, NULL,
- &hasInnerFilter, &hadInnerFilter);
- _ASSERTE(frameType != FR_INVALID);
-
- /* If this is the parent frame of a filter which is currently
- executing, then the filter would have enumerated the frame using
- the filter PC.
- */
+ EnsureCallerContextIsValid(pRD, NULL);
+ }
- if (hasInnerFilter)
- return true;
+ return GetSP(pRD->pCallerContext);
+}
- /* If are in a try and we had a filter execute, we may have reported
- GC refs from the filter (and not using the try's offset). So
- we had better use the filter's end offset, as the try is
- effectively dead and its GC ref's would be stale */
+#endif // FEATURE_EH_FUNCLETS
- if (hadInnerFilter)
- {
- PTR_TADDR pFirstBaseSPslot = GetFirstBaseSPslotPtr(EBP, &info);
- curOffs = (unsigned)pFirstBaseSPslot[1] - 1;
- _ASSERTE(curOffs < info.methodSize);
+#ifdef HAS_LIGHTUNWIND
+/*
+ * Light unwind the current stack frame, using provided cache entry.
+ * pPC, Esp and pEbp of pContext are updated.
+ */
- /* Extract the necessary information from the info block header */
+// static
+void EECodeManager::LightUnwindStackFrame(PREGDISPLAY pRD, EECodeInfo* pCodeInfo, LightUnwindFlag flag)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
- table = PTR_CBYTE(gcInfoToken.Info);
+#ifdef TARGET_AMD64
+ ULONG RBPOffset, RSPOffset;
+ pCodeInfo->GetOffsetsFromUnwindInfo(&RSPOffset, &RBPOffset);
- table += DecodeGCHdrInfo(gcInfoToken,
- curOffs,
- &info);
- }
+ if (pRD->IsCallerContextValid)
+ {
+ pRD->pCurrentContext->Rbp = pRD->pCallerContext->Rbp;
+ pRD->pCurrentContext->Rsp = pRD->pCallerContext->Rsp;
+ pRD->pCurrentContext->Rip = pRD->pCallerContext->Rip;
}
-#endif
-
- bool willContinueExecution = !(flags & ExecutionAborted);
- unsigned pushedSize = 0;
-
- /* if we have been interrupted we don't have to report registers/arguments
- * because we are about to lose this context anyway.
- * Alas, if we are in a ebp-less method we have to parse the table
- * in order to adjust ESP.
- *
- * Note that we report "this" for all methods, even if
- * noncontinuable, because of the off chance they may be
- * synchronized and we have to release the monitor on unwind. This
- * could conceivably be optimized, but it turns out to be more
- * expensive to check whether we're synchronized (which involves
- * consulting metadata) than to just report "this" all the time in
- * our most important scenarios.
- */
-
- if (info.interruptible)
+ else
{
- unsigned curOffsRegs = curOffs;
-
- // Don't decrement curOffsRegs when it is 0, as it is an unsigned and will wrap to MAX_UINT
- //
- if (curOffsRegs > 0)
+ PCONTEXT pSourceCtx = NULL;
+ PCONTEXT pTargetCtx = NULL;
+ if (flag == UnwindCurrentStackFrame)
{
- // If we are not on the active stack frame, we need to report gc registers
- // that are live before the call. The reason is that the liveness of gc registers
- // may change across a call to a method that does not return. In this case the instruction
- // after the call may be a jump target and a register that didn't have a live gc pointer
- // before the call may have a live gc pointer after the jump. To make sure we report the
- // registers that have live gc pointers before the call we subtract 1 from curOffs.
- if ((flags & ActiveStackFrame) == 0)
- {
- // We are not the top most stack frame (i.e. the ActiveStackFrame)
- curOffsRegs--; // decrement curOffsRegs
- }
+ pTargetCtx = pRD->pCurrentContext;
+ pSourceCtx = pRD->pCurrentContext;
}
-
- pushedSize = scanArgRegTableI(skipToArgReg(info, table), curOffsRegs, curOffs, &info);
-
- RegMask regs = info.regMaskResult;
- RegMask iregs = info.iregMaskResult;
- ptrArgTP args = info.argMaskResult;
- ptrArgTP iargs = info.iargMaskResult;
-
- _ASSERTE((isZero(args) || pushedSize != 0) || info.ebpFrame);
- _ASSERTE((args & iargs) == iargs);
- // Only synchronized methods and generic code that accesses
- // the type context via "this" need to report "this".
- // If its reported for other methods, its probably
- // done incorrectly. So flag such cases.
- _ASSERTE(info.thisPtrResult == REGI_NA ||
- pCodeInfo->GetMethodDesc()->IsSynchronized() ||
- pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis());
-
- /* now report registers and arguments if we are not interrupted */
-
- if (willContinueExecution)
+ else
{
+ pTargetCtx = pRD->pCallerContext;
+ pSourceCtx = pRD->pCurrentContext;
+ }
- /* Propagate unsafed registers only in "current" method */
- /* If this is not the active method, then the callee wil
- * trash these registers, and so we wont need to report them */
-
- if (flags & ActiveStackFrame)
- {
- CHK_AND_REPORT_REG(REGI_EAX, regs & RM_EAX, iregs & RM_EAX, Eax);
- CHK_AND_REPORT_REG(REGI_ECX, regs & RM_ECX, iregs & RM_ECX, Ecx);
- CHK_AND_REPORT_REG(REGI_EDX, regs & RM_EDX, iregs & RM_EDX, Edx);
- }
-
- CHK_AND_REPORT_REG(REGI_EBX, regs & RM_EBX, iregs & RM_EBX, Ebx);
- CHK_AND_REPORT_REG(REGI_EBP, regs & RM_EBP, iregs & RM_EBP, Ebp);
- CHK_AND_REPORT_REG(REGI_ESI, regs & RM_ESI, iregs & RM_ESI, Esi);
- CHK_AND_REPORT_REG(REGI_EDI, regs & RM_EDI, iregs & RM_EDI, Edi);
- _ASSERTE(!(regs & RM_ESP));
-
- /* Report any pending pointer arguments */
-
- DWORD * pPendingArgFirst; // points **AT** first parameter
- if (!info.ebpFrame)
- {
- // -sizeof(void*) because we want to point *AT* first parameter
- pPendingArgFirst = (DWORD *)(size_t)(ESP + pushedSize - sizeof(void*));
- }
- else
- {
- _ASSERTE(willContinueExecution);
-
-#ifdef FEATURE_EH_FUNCLETS
- // Funclets' frame pointers(EBP) are always restored so they can access to main function's local variables.
- // Therefore the value of EBP is invalid for unwinder so we should use ESP instead.
- // See UnwindStackFrame for details.
- if (pCodeInfo->IsFunclet())
- {
- PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode());
- TADDR baseSP = ESP;
- // Set baseSP as initial SP
- baseSP += GetPushedArgSize(&info, table, curOffs);
-
-#ifdef UNIX_X86_ABI
- // 16-byte stack alignment padding (allocated in genFuncletProlog)
- // Current funclet frame layout (see CodeGen::genFuncletProlog() and genFuncletEpilog()):
- // prolog: sub esp, 12
- // epilog: add esp, 12
- // ret
- // SP alignment padding should be added for all instructions except the first one and the last one.
- // Epilog may not exist (unreachable), so we need to check the instruction code.
- const PTR_CBYTE funcletStart = PTR_CBYTE(pCodeInfo->GetJitManager()->GetFuncletStartAddress(pCodeInfo));
- if (funcletStart != methodStart + curOffs && methodStart[curOffs] != X86_INSTR_RETN)
- baseSP += 12;
-#endif
-
- // -sizeof(void*) because we want to point *AT* first parameter
- pPendingArgFirst = (DWORD *)(size_t)(baseSP - sizeof(void*));
- }
-#else // FEATURE_EH_FUNCLETS
- if (info.handlers)
- {
- // -sizeof(void*) because we want to point *AT* first parameter
- pPendingArgFirst = (DWORD *)(size_t)(baseSP - sizeof(void*));
- }
-#endif
- else if (info.localloc)
- {
- TADDR locallocBaseSP = *(DWORD *)(size_t)(EBP - GetLocallocSPOffset(&info));
- // -sizeof(void*) because we want to point *AT* first parameter
- pPendingArgFirst = (DWORD *)(size_t) (locallocBaseSP - sizeof(void*));
- }
- else
- {
- // Note that 'info.stackSize includes the size for pushing EBP, but EBP is pushed
- // BEFORE EBP is set from ESP, thus (EBP - info.stackSize) actually points past
- // the frame by one DWORD, and thus points *AT* the first parameter
-
- pPendingArgFirst = (DWORD *)(size_t)(EBP - info.stackSize);
- }
- }
-
- if (!isZero(args))
- {
- unsigned i = 0;
- ptrArgTP b(1);
- for (; !isZero(args) && (i < MAX_PTRARG_OFS); i += 1, b <<= 1)
- {
- if (intersect(args,b))
- {
- unsigned argAddr = (unsigned)(size_t)(pPendingArgFirst - i);
- bool iptr = false;
-
- setDiff(args, b);
- if (intersect(iargs,b))
- {
- setDiff(iargs, b);
- iptr = true;
- }
-
-#ifdef _DEBUG
- if (dspPtr)
- {
- printf(" Pushed ptr arg [E");
- if (info.ebpFrame)
- printf("BP-%02XH]: ", EBP - argAddr);
- else
- printf("SP+%02XH]: ", argAddr - ESP);
- }
-#endif
- _ASSERTE(true == GC_CALL_INTERIOR);
- pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, (int)iptr | CHECK_APP_DOMAIN
- DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
- info.ebpFrame ? EBP - argAddr : argAddr - ESP,
- true)));
- }
- }
- }
+ // Unwind RBP. The offset is relative to the current sp.
+ if (RBPOffset == 0)
+ {
+ pTargetCtx->Rbp = pSourceCtx->Rbp;
}
else
{
- // Is "this" enregistered. If so, report it as we might need to
- // release the monitor for synchronized methods.
- // Else, it is on the stack and will be reported below.
-
- if (info.thisPtrResult != REGI_NA)
- {
- // Synchronized methods and methods satisfying
- // MethodDesc::AcquiresInstMethodTableFromThis (i.e. those
- // where "this" is reported in thisPtrResult) are
- // not supported on value types.
- _ASSERTE((regNumToMask(info.thisPtrResult) & info.iregMaskResult)== 0);
-
- void * thisReg = getCalleeSavedReg(pContext, info.thisPtrResult);
- pCallBack(hCallBack, (OBJECTREF *)thisReg, CHECK_APP_DOMAIN
- DAC_ARG(DacSlotLocation(info.thisPtrResult, 0, false)));
- }
+ pTargetCtx->Rbp = *(UINT_PTR*)(pSourceCtx->Rsp + RBPOffset);
}
- }
- else /* not interruptible */
- {
- pushedSize = scanArgRegTable(skipToArgReg(info, table), curOffs, &info);
-
- RegMask regMask = info.regMaskResult;
- RegMask iregMask = info.iregMaskResult;
- ptrArgTP argMask = info.argMaskResult;
- ptrArgTP iargMask = info.iargMaskResult;
- unsigned argHnum = info.argHnumResult;
- PTR_CBYTE argTab = info.argTabResult;
-
- // Only synchronized methods and generic code that accesses
- // the type context via "this" need to report "this".
- // If its reported for other methods, its probably
- // done incorrectly. So flag such cases.
- _ASSERTE(info.thisPtrResult == REGI_NA ||
- pCodeInfo->GetMethodDesc()->IsSynchronized() ||
- pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis());
-
-
- /* now report registers and arguments if we are not interrupted */
-
- if (willContinueExecution)
- {
-
- /* Report all live pointer registers */
-
- CHK_AND_REPORT_REG(REGI_EDI, regMask & RM_EDI, iregMask & RM_EDI, Edi);
- CHK_AND_REPORT_REG(REGI_ESI, regMask & RM_ESI, iregMask & RM_ESI, Esi);
- CHK_AND_REPORT_REG(REGI_EBX, regMask & RM_EBX, iregMask & RM_EBX, Ebx);
- CHK_AND_REPORT_REG(REGI_EBP, regMask & RM_EBP, iregMask & RM_EBP, Ebp);
-
- /* Esp cant be reported */
- _ASSERTE(!(regMask & RM_ESP));
- /* No callee-trashed registers */
- _ASSERTE(!(regMask & RM_CALLEE_TRASHED));
- /* EBP can't be reported unless we have an EBP-less frame */
- _ASSERTE(!(regMask & RM_EBP) || !(info.ebpFrame));
-
- /* Report any pending pointer arguments */
-
- if (argTab != 0)
- {
- unsigned lowBits, stkOffs, argAddr, val;
-
- // argMask does not fit in 32-bits
- // thus arguments are reported via a table
- // Both of these are very rare cases
-
- do
- {
- val = fastDecodeUnsigned(argTab);
-
- lowBits = val & OFFSET_MASK;
- stkOffs = val & ~OFFSET_MASK;
- _ASSERTE((lowBits == 0) || (lowBits == byref_OFFSET_FLAG));
-
- argAddr = ESP + stkOffs;
-#ifdef _DEBUG
- if (dspPtr)
- printf(" Pushed %sptr arg at [ESP+%02XH]",
- lowBits ? "iptr " : "", stkOffs);
-#endif
- _ASSERTE(byref_OFFSET_FLAG == GC_CALL_INTERIOR);
- pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, lowBits | CHECK_APP_DOMAIN
- DAC_ARG(DacSlotLocation(REGI_ESP, stkOffs, true)));
- }
- while(--argHnum);
-
- _ASSERTE(info.argTabResult + info.argTabBytes == argTab);
- }
- else
- {
- unsigned argAddr = ESP;
- while (!isZero(argMask))
- {
- _ASSERTE(argHnum-- > 0);
+ // Adjust the sp. From this pointer onwards pCurrentContext->Rsp is the caller sp.
+ pTargetCtx->Rsp = pSourceCtx->Rsp + RSPOffset;
- if (toUnsigned(argMask) & 1)
- {
- bool iptr = false;
+ // Retrieve the return address.
+ pTargetCtx->Rip = *(UINT_PTR*)((pTargetCtx->Rsp) - sizeof(UINT_PTR));
+ }
- if (toUnsigned(iargMask) & 1)
- iptr = true;
-#ifdef _DEBUG
- if (dspPtr)
- printf(" Pushed ptr arg at [ESP+%02XH]",
- argAddr - ESP);
+ if (flag == UnwindCurrentStackFrame)
+ {
+ SyncRegDisplayToCurrentContext(pRD);
+ pRD->IsCallerContextValid = FALSE;
+ pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary.
+ }
+#else
+ PORTABILITY_ASSERT("EECodeManager::LightUnwindStackFrame is not implemented on this platform.");
#endif
- _ASSERTE(true == GC_CALL_INTERIOR);
- pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, (int)iptr | CHECK_APP_DOMAIN
- DAC_ARG(DacSlotLocation(REGI_ESP, argAddr - ESP, true)));
- }
-
- argMask >>= 1;
- iargMask >>= 1;
- argAddr += 4;
- }
-
- }
-
- }
- else
- {
- // Is "this" enregistered. If so, report it as we will need to
- // release the monitor. Else, it is on the stack and will be
- // reported below.
+}
+#endif // HAS_LIGHTUNWIND
- // For partially interruptible code, info.thisPtrResult will be
- // the last known location of "this". So the compiler needs to
- // generate information which is correct at every point in the code,
- // not just at call sites.
+#ifdef FEATURE_EH_FUNCLETS
+#ifdef TARGET_X86
+size_t EECodeManager::GetResumeSp( PCONTEXT pContext )
+{
+ PCODE currentPc = PCODE(pContext->Eip);
- if (info.thisPtrResult != REGI_NA)
- {
- // Synchronized methods on value types are not supported
- _ASSERTE((regNumToMask(info.thisPtrResult) & info.iregMaskResult)== 0);
+ _ASSERTE(ExecutionManager::IsManagedCode(currentPc));
- void * thisReg = getCalleeSavedReg(pContext, info.thisPtrResult);
- pCallBack(hCallBack, (OBJECTREF *)thisReg, CHECK_APP_DOMAIN
- DAC_ARG(DacSlotLocation(info.thisPtrResult, 0, false)));
- }
- }
+ EECodeInfo codeInfo(currentPc);
- } //info.interruptible
+ PTR_CBYTE methodStart = PTR_CBYTE(codeInfo.GetSavedMethodCode());
- /* compute the argument base (reference point) */
+ GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken();
+ PTR_VOID methodInfoPtr = gcInfoToken.Info;
+ DWORD curOffs = codeInfo.GetRelOffset();
- unsigned argBase;
+ CodeManStateBuf stateBuf;
- if (info.ebpFrame)
- argBase = EBP;
- else
- argBase = ESP + pushedSize;
+ stateBuf.hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken,
+ curOffs,
+ &stateBuf.hdrInfoBody);
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
-#endif
+ PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf.hdrInfoSize;
- unsigned ptrAddr;
- unsigned lowBits;
+ hdrInfo *info = &stateBuf.hdrInfoBody;
+ _ASSERTE(info->epilogOffs == hdrInfo::NOT_IN_EPILOG && info->prologOffs == hdrInfo::NOT_IN_PROLOG);
- /* Process the untracked frame variable table */
+ bool isESPFrame = !info->ebpFrame && !info->doubleAlign;
-#if defined(FEATURE_EH_FUNCLETS) // funclets
- // Filters are the only funclet that run during the 1st pass, and must have
- // both the leaf and the parent frame reported. In order to avoid double
- // reporting of the untracked variables, do not report them for the filter.
- if (!pCodeInfo->GetJitManager()->IsFilterFunclet(pCodeInfo))
-#endif // FEATURE_EH_FUNCLETS
+ if (codeInfo.IsFunclet())
{
- count = info.untrackedCnt;
- int lastStkOffs = 0;
- while (count-- > 0)
- {
- int stkOffs = fastDecodeSigned(table);
- stkOffs = lastStkOffs - stkOffs;
- lastStkOffs = stkOffs;
-
- _ASSERTE(0 == ~OFFSET_MASK % sizeof(void*));
-
- lowBits = OFFSET_MASK & stkOffs;
- stkOffs &= ~OFFSET_MASK;
-
- ptrAddr = argBase + stkOffs;
- if (info.doubleAlign && stkOffs >= int(info.stackSize - sizeof(void*))) {
- // We encode the arguments as if they were ESP based variables even though they aren't
- // If this frame would have ben an ESP based frame, This fake frame is one DWORD
- // smaller than the real frame because it did not push EBP but the real frame did.
- // Thus to get the correct EBP relative offset we have to adjust by info.stackSize-sizeof(void*)
- ptrAddr = EBP + (stkOffs-(info.stackSize - sizeof(void*)));
- }
-
-#ifdef _DEBUG
- if (dspPtr)
- {
- printf(" Untracked %s%s local at [E",
- (lowBits & pinned_OFFSET_FLAG) ? "pinned " : "",
- (lowBits & byref_OFFSET_FLAG) ? "byref" : "");
+ // Treat funclet's frame as ESP frame
+ isESPFrame = true;
+ }
- int dspOffs = ptrAddr;
- char frameType;
+ if (isESPFrame)
+ {
+ const size_t curESP = (size_t)(pContext->Esp);
+ return curESP + GetPushedArgSize(info, table, curOffs);
+ }
- if (info.ebpFrame) {
- dspOffs -= EBP;
- frameType = 'B';
- }
- else {
- dspOffs -= ESP;
- frameType = 'S';
- }
+ const size_t curEBP = (size_t)(pContext->Ebp);
+ return GetOutermostBaseFP(curEBP, info);
+}
+#endif // TARGET_X86
+#endif // FEATURE_EH_FUNCLETS
- if (dspOffs < 0)
- printf("%cP-%02XH]: ", frameType, -dspOffs);
- else
- printf("%cP+%02XH]: ", frameType, +dspOffs);
- }
-#endif
+#ifndef FEATURE_EH_FUNCLETS
- _ASSERTE((pinned_OFFSET_FLAG == GC_CALL_PINNED) &&
- (byref_OFFSET_FLAG == GC_CALL_INTERIOR));
- pCallBack(hCallBack, (OBJECTREF*)(size_t)ptrAddr, lowBits | CHECK_APP_DOMAIN
- DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
- info.ebpFrame ? EBP - ptrAddr : ptrAddr - ESP,
- true)));
- }
+/*****************************************************************************
+ *
+ * Unwind the current stack frame, i.e. update the virtual register
+ * set in pContext. This will be similar to the state after the function
+ * returns back to caller (IP points to after the call, Frame and Stack
+ * pointer has been reset, callee-saved registers restored (if UpdateAllRegs),
+ * callee-unsaved registers are trashed.
+ * Returns success of operation.
+ */
- }
+bool EECodeManager::UnwindStackFrame(PREGDISPLAY pContext,
+ EECodeInfo *pCodeInfo,
+ unsigned flags,
+ CodeManState *pState)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ HOST_NOCALLS;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xCAFE);
-#endif
+#ifdef TARGET_X86
+ bool updateAllRegs = flags & UpdateAllRegs;
- /* Process the frame variable lifetime table */
- count = info.varPtrTableSize;
+ // Address where the method has been interrupted
+ PCODE breakPC = pContext->ControlPC;
+ _ASSERTE(PCODEToPINSTR(breakPC) == pCodeInfo->GetCodeAddress());
- /* If we are not in the active method, we are currently pointing
- * to the return address; at the return address stack variables
- * can become dead if the call the last instruction of a try block
- * and the return address is the jump around the catch block. Therefore
- * we simply assume an offset inside of call instruction.
- */
+ GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
+ PTR_VOID methodInfoPtr = gcInfoToken.Info;
+ DWORD curOffs = pCodeInfo->GetRelOffset();
- unsigned newCurOffs;
+ _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
+ CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
- if (willContinueExecution)
- {
- newCurOffs = (flags & ActiveStackFrame) ? curOffs // after "call"
- : curOffs-1; // inside "call"
- }
- else
+ if (pState->dwIsSet == 0)
{
- /* However if ExecutionAborted, then this must be one of the
- * ExceptionFrames. Handle accordingly
- */
- _ASSERTE(!(flags & AbortingCall) || !(flags & ActiveStackFrame));
+ /* Extract the necessary information from the info block header */
- newCurOffs = (flags & AbortingCall) ? curOffs-1 // inside "call"
- : curOffs; // at faulting instr, or start of "try"
+ stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken,
+ curOffs,
+ &stateBuf->hdrInfoBody);
}
- ptrOffs = 0;
-
- while (count-- > 0)
- {
- int stkOffs;
- unsigned begOffs;
- unsigned endOffs;
+ PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf->hdrInfoSize;
- stkOffs = fastDecodeUnsigned(table);
- begOffs = ptrOffs + fastDecodeUnsigned(table);
- endOffs = begOffs + fastDecodeUnsigned(table);
+ hdrInfo * info = &stateBuf->hdrInfoBody;
- _ASSERTE(0 == ~OFFSET_MASK % sizeof(void*));
+ info->isSpeculativeStackWalk = ((flags & SpeculativeStackwalk) != 0);
- lowBits = OFFSET_MASK & stkOffs;
- stkOffs &= ~OFFSET_MASK;
+ return UnwindStackFrameX86(pContext,
+ PTR_CBYTE(pCodeInfo->GetSavedMethodCode()),
+ curOffs,
+ info,
+ table,
+ IN_EH_FUNCLETS_COMMA(PTR_CBYTE(pCodeInfo->GetJitManager()->GetFuncletStartAddress(pCodeInfo)))
+ IN_EH_FUNCLETS_COMMA(pCodeInfo->IsFunclet())
+ updateAllRegs);
+#else // TARGET_X86
+ PORTABILITY_ASSERT("EECodeManager::UnwindStackFrame");
+ return false;
+#endif // _TARGET_???_
+}
- if (info.ebpFrame) {
- stkOffs = -stkOffs;
- _ASSERTE(stkOffs < 0);
- }
- else {
- _ASSERTE(stkOffs >= 0);
- }
+/*****************************************************************************/
+#else // !FEATURE_EH_FUNCLETS
+/*****************************************************************************/
- ptrAddr = argBase + stkOffs;
+bool EECodeManager::UnwindStackFrame(PREGDISPLAY pContext,
+ EECodeInfo *pCodeInfo,
+ unsigned flags,
+ CodeManState *pState)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
- /* Is this variable live right now? */
+ _ASSERTE(pCodeInfo != NULL);
- if (newCurOffs >= begOffs)
- {
- if (newCurOffs < endOffs)
- {
-#ifdef _DEBUG
- if (dspPtr) {
- printf(" Frame %s%s local at [E",
- (lowBits & byref_OFFSET_FLAG) ? "byref " : "",
-#ifndef FEATURE_EH_FUNCLETS
- (lowBits & this_OFFSET_FLAG) ? "this-ptr" : "");
-#else
- (lowBits & pinned_OFFSET_FLAG) ? "pinned" : "");
+#ifdef HAS_LIGHTUNWIND
+ if (flags & LightUnwind)
+ {
+ LightUnwindStackFrame(pContext, pCodeInfo, UnwindCurrentStackFrame);
+ return true;
+ }
#endif
+ Thread::VirtualUnwindCallFrame(pContext, pCodeInfo);
+ return true;
+}
- int dspOffs = ptrAddr;
- char frameType;
-
- if (info.ebpFrame) {
- dspOffs -= EBP;
- frameType = 'B';
- }
- else {
- dspOffs -= ESP;
- frameType = 'S';
- }
+/*****************************************************************************/
+#endif // FEATURE_EH_FUNCLETS
- if (dspOffs < 0)
- printf("%cP-%02XH]: ", frameType, -dspOffs);
- else
- printf("%cP+%02XH]: ", frameType, +dspOffs);
- }
-#endif
+/*****************************************************************************/
- unsigned flags = CHECK_APP_DOMAIN;
-#ifndef FEATURE_EH_FUNCLETS
- // First Bit : byref
- // Second Bit : this
- // The second bit means `this` not `pinned`. So we ignore it.
- flags |= lowBits & byref_OFFSET_FLAG;
-#else
- // First Bit : byref
- // Second Bit : pinned
- // Both bits are valid
- flags |= lowBits;
-#endif
+/* report args in 'msig' to the GC.
+ 'argsStart' is start of the stack-based arguments
+ 'varArgSig' describes the arguments
+ 'ctx' has the GC reporting info
+*/
+void promoteVarArgs(PTR_BYTE argsStart, PTR_VASigCookie varArgSig, GCCONTEXT* ctx)
+{
+ WRAPPER_NO_CONTRACT;
- _ASSERTE(byref_OFFSET_FLAG == GC_CALL_INTERIOR);
- pCallBack(hCallBack, (OBJECTREF*)(size_t)ptrAddr, flags
- DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
- info.ebpFrame ? EBP - ptrAddr : ptrAddr - ESP,
- true)));
- }
- }
- // exit loop early if start of live range is beyond PC, as ranges are sorted by lower bound
- else break;
+ SigTypeContext typeContext(varArgSig->classInst, varArgSig->methodInst);
+ MetaSig msig(varArgSig->signature,
+ varArgSig->pModule,
+ &typeContext);
- ptrOffs = begOffs;
- }
+ PTR_BYTE pFrameBase = argsStart - TransitionBlock::GetOffsetOfArgs();
+ ArgIterator argit(&msig);
-#if VERIFY_GC_TABLES
- _ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
+#ifdef TARGET_X86
+ // For the X86 target the JIT does not report any of the fixed args for a varargs method
+ // So we report the fixed args via the promoteArgs call below
+ bool skipFixedArgs = false;
+#else
+ // For other platforms the JITs do report the fixed args of a varargs method
+ // So we must tell promoteArgs to skip to the end of the fixed args
+ bool skipFixedArgs = true;
#endif
-#ifdef FEATURE_EH_FUNCLETS // funclets
- //
- // If we're in a funclet, we do not want to report the incoming varargs. This is
- // taken care of by the parent method and the funclet should access those arguments
- // by way of the parent method's stack frame.
- //
- if(pCodeInfo->IsFunclet())
+ bool inVarArgs = false;
+
+ int argOffset;
+ while ((argOffset = argit.GetNextOffset()) != TransitionBlock::InvalidOffset)
{
- return true;
+ if (msig.GetArgProps().AtSentinel())
+ inVarArgs = true;
+
+ // if skipFixedArgs is false we report all arguments
+ // otherwise we just report the varargs.
+ if (!skipFixedArgs || inVarArgs)
+ {
+ ArgDestination argDest(pFrameBase, argOffset, argit.GetArgLocDescForStructInRegs());
+ msig.GcScanRoots(&argDest, ctx->f, ctx->sc);
+ }
}
-#endif // FEATURE_EH_FUNCLETS
+}
- /* Are we a varargs function, if so we have to report all args
- except 'this' (note that the GC tables created by the x86 jit
- do not contain ANY arguments except 'this' (even if they
- were statically declared */
+#ifndef DACCESS_COMPILE
+FCIMPL1(void, GCReporting::Register, GCFrame* frame)
+{
+ FCALL_CONTRACT;
- if (info.varargs) {
- LOG((LF_GCINFO, LL_INFO100, "Reporting incoming vararg GC refs\n"));
+ // Construct a GCFrame.
+ _ASSERTE(frame != NULL);
+ frame->Push(GetThread());
+}
+FCIMPLEND
- PTR_BYTE argsStart;
+FCIMPL1(void, GCReporting::Unregister, GCFrame* frame)
+{
+ FCALL_CONTRACT;
- if (info.ebpFrame || info.doubleAlign)
- argsStart = PTR_BYTE((size_t)EBP) + 2* sizeof(void*); // pushed EBP and retAddr
- else
- argsStart = PTR_BYTE((size_t)argBase) + info.stackSize + sizeof(void*); // ESP + locals + retAddr
+ // Destroy the GCFrame.
+ _ASSERTE(frame != NULL);
+ frame->Remove();
+}
+FCIMPLEND
+#endif // !DACCESS_COMPILE
-#if defined(_DEBUG) && !defined(DACCESS_COMPILE)
- // Note that I really want to say hCallBack is a GCCONTEXT, but this is pretty close
- extern void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags);
- _ASSERTE((void*) GcEnumObject == pCallBack);
-#endif
- GCCONTEXT *pCtx = (GCCONTEXT *) hCallBack;
+#ifndef USE_GC_INFO_DECODER
- // For varargs, look up the signature using the varArgSig token passed on the stack
- PTR_VASigCookie varArgSig = *PTR_PTR_VASigCookie(argsStart);
+/*****************************************************************************
+ *
+ * Enumerate all live object references in that function using
+ * the virtual register set.
+ * Returns success of operation.
+ */
+
+bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext,
+ EECodeInfo *pCodeInfo,
+ unsigned flags,
+ GCEnumCallback pCallBack,
+ LPVOID hCallBack,
+ DWORD relOffsetOverride)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
+
+ PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode());
+ unsigned curOffs = pCodeInfo->GetRelOffset();
+ GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
- promoteVarArgs(argsStart, varArgSig, pCtx);
+ if (relOffsetOverride != NO_OVERRIDE_OFFSET)
+ {
+ curOffs = relOffsetOverride;
}
- return true;
+ return ::EnumGcRefsX86(pContext,
+ methodStart,
+ curOffs,
+ gcInfoToken,
+ IN_EH_FUNCLETS_COMMA(PTR_CBYTE(pCodeInfo->GetJitManager()->GetFuncletStartAddress(pCodeInfo)))
+ IN_EH_FUNCLETS_COMMA(pCodeInfo->IsFunclet())
+ IN_EH_FUNCLETS_COMMA(pCodeInfo->GetJitManager()->IsFilterFunclet(pCodeInfo))
+ flags,
+ pCallBack,
+ hCallBack);
}
#else // !USE_GC_INFO_DECODER
@@ -5747,8 +1954,6 @@ void * EECodeManager::GetGSCookieAddr(PREGDISPLAY pContext,
GC_NOTRIGGER;
} CONTRACTL_END;
- _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
-
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
unsigned relOffset = pCodeInfo->GetRelOffset();
@@ -5760,6 +1965,8 @@ void * EECodeManager::GetGSCookieAddr(PREGDISPLAY pContext,
#endif
#ifndef USE_GC_INFO_DECODER
+ _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
+
CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
/* Extract the necessary information from the info block header */
diff --git a/src/coreclr/vm/gc_unwind_x86.inl b/src/coreclr/vm/gc_unwind_x86.inl
new file mode 100644
index 00000000000000..8f981ed84cd609
--- /dev/null
+++ b/src/coreclr/vm/gc_unwind_x86.inl
@@ -0,0 +1,3837 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// This file is shared between CoreCLR and NativeAOT. Some of the differences are handled
+// with the FEATURE_NATIVEAOT and FEATURE_EH_FUNCLETS defines. There are three main methods
+// that are used by both runtimes - DecodeGCHdrInfo, UnwindStackFrameX86, and EnumGcRefsX86.
+
+#define RETURN_ADDR_OFFS 1 // in DWORDS
+
+#define X86_INSTR_TEST_ESP_SIB 0x24
+#define X86_INSTR_PUSH_0 0x6A // push 00, entire instruction is 0x6A00
+#define X86_INSTR_PUSH_IMM 0x68 // push NNNN,
+#define X86_INSTR_W_PUSH_IND_IMM 0x35FF // push [NNNN]
+#define X86_INSTR_CALL_REL32 0xE8 // call rel32
+#define X86_INSTR_W_CALL_IND_IMM 0x15FF // call [addr32]
+#define X86_INSTR_NOP 0x90 // nop
+#define X86_INSTR_NOP2 0x9090 // 2-byte nop
+#define X86_INSTR_NOP3_1 0x9090 // 1st word of 3-byte nop
+#define X86_INSTR_NOP3_3 0x90 // 3rd byte of 3-byte nop
+#define X86_INSTR_NOP4 0x90909090 // 4-byte nop
+#define X86_INSTR_NOP5_1 0x90909090 // 1st dword of 5-byte nop
+#define X86_INSTR_NOP5_5 0x90 // 5th byte of 5-byte nop
+#define X86_INSTR_INT3 0xCC // int3
+#define X86_INSTR_HLT 0xF4 // hlt
+#define X86_INSTR_PUSH_EAX 0x50 // push eax
+#define X86_INSTR_PUSH_EBP 0x55 // push ebp
+#define X86_INSTR_W_MOV_EBP_ESP 0xEC8B // mov ebp, esp
+#define X86_INSTR_POP_ECX 0x59 // pop ecx
+#define X86_INSTR_RET 0xC2 // ret imm16
+#define X86_INSTR_RETN 0xC3 // ret
+#define X86_INSTR_XOR 0x33 // xor
+#define X86_INSTR_w_TEST_ESP_EAX 0x0485 // test [esp], eax
+#define X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX 0x8485 // test [esp-dwOffset], eax
+#define X86_INSTR_w_LEA_ESP_EBP_BYTE_OFFSET 0x658d // lea esp, [ebp-bOffset]
+#define X86_INSTR_w_LEA_ESP_EBP_DWORD_OFFSET 0xa58d // lea esp, [ebp-dwOffset]
+#define X86_INSTR_w_LEA_EAX_ESP_BYTE_OFFSET 0x448d // lea eax, [esp-bOffset]
+#define X86_INSTR_w_LEA_EAX_ESP_DWORD_OFFSET 0x848d // lea eax, [esp-dwOffset]
+#define X86_INSTR_JMP_NEAR_REL32 0xE9 // near jmp rel32
+#define X86_INSTR_w_JMP_FAR_IND_IMM 0x25FF // far jmp [addr32]
+
+#ifdef _DEBUG
+// For dumping of verbose info.
+#ifndef DACCESS_COMPILE
+static bool trFixContext = false;
+#endif
+static bool trEnumGCRefs = false;
+static bool dspPtr = false; // prints the live ptrs as reported
+#endif
+
+__forceinline unsigned decodeUnsigned(PTR_CBYTE& src)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+#ifdef DACCESS_COMPILE
+ PTR_CBYTE begin = src;
+#endif
+
+ BYTE byte = *src++;
+ unsigned value = byte & 0x7f;
+ while (byte & 0x80)
+ {
+#ifdef DACCESS_COMPILE
+ // In DAC builds, the target data may be corrupt. Rather than return incorrect data
+ // and risk wasting time in a potentially long loop, we want to fail early and gracefully.
+ // The data is encoded with 7 value-bits per byte, and so we may need to read a maximum
+ // of 5 bytes (7*5=35) to read a full 32-bit integer.
+ if ((src - begin) > 5)
+ {
+ DacError(CORDBG_E_TARGET_INCONSISTENT);
+ }
+#endif
+
+ byte = *src++;
+ value <<= 7;
+ value += byte & 0x7f;
+ }
+ return value;
+}
+
+__forceinline int decodeSigned(PTR_CBYTE& src)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+#ifdef DACCESS_COMPILE
+ PTR_CBYTE begin = src;
+#endif
+
+ BYTE byte = *src++;
+ BYTE first = byte;
+ int value = byte & 0x3f;
+ while (byte & 0x80)
+ {
+#ifdef DACCESS_COMPILE
+ // In DAC builds, the target data may be corrupt. Rather than return incorrect data
+ // and risk wasting time in a potentially long loop, we want to fail early and gracefully.
+ // The data is encoded with 7 value-bits per byte, and so we may need to read a maximum
+ // of 5 bytes (7*5=35) to read a full 32-bit integer.
+ if ((src - begin) > 5)
+ {
+ DacError(CORDBG_E_TARGET_INCONSISTENT);
+ }
+#endif
+
+ byte = *src++;
+ value <<= 7;
+ value += byte & 0x7f;
+ }
+ if (first & 0x40)
+ value = -value;
+ return value;
+}
+
+// Fast versions of the above, with one iteration of the loop unrolled
+#define fastDecodeUnsigned(src) (((*(src) & 0x80) == 0) ? (unsigned) (*(src)++) : decodeUnsigned((src)))
+#define fastDecodeSigned(src) (((*(src) & 0xC0) == 0) ? (unsigned) (*(src)++) : decodeSigned((src)))
+
+// Fast skipping past encoded integers
+#ifndef DACCESS_COMPILE
+#define fastSkipUnsigned(src) { while ((*(src)++) & 0x80) { } }
+#define fastSkipSigned(src) { while ((*(src)++) & 0x80) { } }
+#else
+// In DAC builds we want to trade-off a little perf in the common case for reliaiblity against corrupt data.
+#define fastSkipUnsigned(src) (decodeUnsigned(src))
+#define fastSkipSigned(src) (decodeSigned(src))
+#endif
+
+
+/*****************************************************************************
+ *
+ * Decodes the X86 GcInfo header and returns the decoded information
+ * in the hdrInfo struct.
+ * curOffset is the code offset within the active method used in the
+ * computation of PrologOffs/EpilogOffs.
+ * Returns the size of the header (number of bytes decoded).
+ */
+size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken,
+ unsigned curOffset,
+ hdrInfo * infoPtr)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ HOST_NOCALLS;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ PTR_CBYTE table = (PTR_CBYTE) gcInfoToken.Info;
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xFEEF);
+#endif
+
+ infoPtr->methodSize = fastDecodeUnsigned(table);
+
+ _ASSERTE(curOffset >= 0);
+ _ASSERTE(curOffset <= infoPtr->methodSize);
+
+ /* Decode the InfoHdr */
+
+ InfoHdr header;
+ table = decodeHeader(table, gcInfoToken.Version, &header);
+
+ BOOL hasArgTabOffset = FALSE;
+ if (header.untrackedCnt == HAS_UNTRACKED)
+ {
+ hasArgTabOffset = TRUE;
+ header.untrackedCnt = fastDecodeUnsigned(table);
+ }
+
+ if (header.varPtrTableSize == HAS_VARPTR)
+ {
+ hasArgTabOffset = TRUE;
+ header.varPtrTableSize = fastDecodeUnsigned(table);
+ }
+
+ if (header.gsCookieOffset == HAS_GS_COOKIE_OFFSET)
+ {
+ header.gsCookieOffset = fastDecodeUnsigned(table);
+ }
+
+ if (header.syncStartOffset == HAS_SYNC_OFFSET)
+ {
+ header.syncStartOffset = decodeUnsigned(table);
+ header.syncEndOffset = decodeUnsigned(table);
+
+ _ASSERTE(header.syncStartOffset != INVALID_SYNC_OFFSET && header.syncEndOffset != INVALID_SYNC_OFFSET);
+ _ASSERTE(header.syncStartOffset < header.syncEndOffset);
+ }
+
+ if (header.revPInvokeOffset == HAS_REV_PINVOKE_FRAME_OFFSET)
+ {
+ header.revPInvokeOffset = fastDecodeUnsigned(table);
+ }
+
+ /* Some sanity checks on header */
+
+ _ASSERTE( header.prologSize +
+ (size_t)(header.epilogCount*header.epilogSize) <= infoPtr->methodSize);
+ _ASSERTE( header.epilogCount == 1 || !header.epilogAtEnd);
+
+ _ASSERTE( header.untrackedCnt <= header.argCount+header.frameSize);
+
+ _ASSERTE( header.ebpSaved || !(header.ebpFrame || header.doubleAlign));
+ _ASSERTE(!header.ebpFrame || !header.doubleAlign );
+ _ASSERTE( header.ebpFrame || !header.security );
+ _ASSERTE( header.ebpFrame || !header.handlers );
+ _ASSERTE( header.ebpFrame || !header.localloc );
+ _ASSERTE( header.ebpFrame || !header.editNcontinue); // : Esp frames NYI for EnC
+
+ /* Initialize the infoPtr struct */
+
+ infoPtr->argSize = header.argCount * 4;
+ infoPtr->ebpFrame = header.ebpFrame;
+ infoPtr->interruptible = header.interruptible;
+ infoPtr->returnKind = (ReturnKind) header.returnKind;
+
+ infoPtr->prologSize = header.prologSize;
+ infoPtr->epilogSize = header.epilogSize;
+ infoPtr->epilogCnt = header.epilogCount;
+ infoPtr->epilogEnd = header.epilogAtEnd;
+
+ infoPtr->untrackedCnt = header.untrackedCnt;
+ infoPtr->varPtrTableSize = header.varPtrTableSize;
+ infoPtr->gsCookieOffset = header.gsCookieOffset;
+
+ infoPtr->syncStartOffset = header.syncStartOffset;
+ infoPtr->syncEndOffset = header.syncEndOffset;
+ infoPtr->revPInvokeOffset = header.revPInvokeOffset;
+
+ infoPtr->doubleAlign = header.doubleAlign;
+ infoPtr->handlers = header.handlers;
+ infoPtr->localloc = header.localloc;
+ infoPtr->editNcontinue = header.editNcontinue;
+ infoPtr->varargs = header.varargs;
+ infoPtr->profCallbacks = header.profCallbacks;
+ infoPtr->genericsContext = header.genericsContext;
+ infoPtr->genericsContextIsMethodDesc = header.genericsContextIsMethodDesc;
+ infoPtr->isSpeculativeStackWalk = false;
+
+ /* Are we within the prolog of the method? */
+
+ if (curOffset < infoPtr->prologSize)
+ {
+ infoPtr->prologOffs = curOffset;
+ }
+ else
+ {
+ infoPtr->prologOffs = hdrInfo::NOT_IN_PROLOG;
+ }
+
+ /* Assume we're not in the epilog of the method */
+
+ infoPtr->epilogOffs = hdrInfo::NOT_IN_EPILOG;
+
+ /* Are we within an epilog of the method? */
+
+ if (infoPtr->epilogCnt)
+ {
+ unsigned epilogStart;
+
+ if (infoPtr->epilogCnt > 1 || !infoPtr->epilogEnd)
+ {
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xFACE);
+#endif
+ epilogStart = 0;
+ for (unsigned i = 0; i < infoPtr->epilogCnt; i++)
+ {
+ epilogStart += fastDecodeUnsigned(table);
+ if (curOffset > epilogStart &&
+ curOffset < epilogStart + infoPtr->epilogSize)
+ {
+ infoPtr->epilogOffs = curOffset - epilogStart;
+ }
+ }
+ }
+ else
+ {
+ epilogStart = infoPtr->methodSize - infoPtr->epilogSize;
+
+ if (curOffset > epilogStart &&
+ curOffset < epilogStart + infoPtr->epilogSize)
+ {
+ infoPtr->epilogOffs = curOffset - epilogStart;
+ }
+ }
+
+ infoPtr->syncEpilogStart = epilogStart;
+ }
+
+ unsigned argTabOffset = INVALID_ARGTAB_OFFSET;
+ if (hasArgTabOffset)
+ {
+ argTabOffset = fastDecodeUnsigned(table);
+ }
+ infoPtr->argTabOffset = argTabOffset;
+
+ size_t frameDwordCount = header.frameSize;
+
+ /* Set the rawStackSize to the number of bytes that it bumps ESP */
+
+ infoPtr->rawStkSize = (UINT)(frameDwordCount * sizeof(size_t));
+
+ /* Calculate the callee saves regMask and adjust stackSize to */
+ /* include the callee saves register spills */
+
+ unsigned savedRegs = RM_NONE;
+ unsigned savedRegsCount = 0;
+
+ if (header.ediSaved)
+ {
+ savedRegsCount++;
+ savedRegs |= RM_EDI;
+ }
+ if (header.esiSaved)
+ {
+ savedRegsCount++;
+ savedRegs |= RM_ESI;
+ }
+ if (header.ebxSaved)
+ {
+ savedRegsCount++;
+ savedRegs |= RM_EBX;
+ }
+ if (header.ebpSaved)
+ {
+ savedRegsCount++;
+ savedRegs |= RM_EBP;
+ }
+
+ infoPtr->savedRegMask = (RegMask)savedRegs;
+
+ infoPtr->savedRegsCountExclFP = savedRegsCount;
+ if (header.ebpFrame || header.doubleAlign)
+ {
+ _ASSERTE(header.ebpSaved);
+ infoPtr->savedRegsCountExclFP = savedRegsCount - 1;
+ }
+
+ frameDwordCount += savedRegsCount;
+
+ infoPtr->stackSize = (UINT)(frameDwordCount * sizeof(size_t));
+
+ _ASSERTE(infoPtr->gsCookieOffset == INVALID_GS_COOKIE_OFFSET ||
+ (infoPtr->gsCookieOffset < infoPtr->stackSize) &&
+ ((header.gsCookieOffset % sizeof(void*)) == 0));
+
+ return table - PTR_CBYTE(gcInfoToken.Info);
+}
+
+/*****************************************************************************/
+
+// We do a "pop eax; jmp eax" to return from a fault or finally handler
+const size_t END_FIN_POP_STACK = sizeof(TADDR);
+
+inline
+size_t GetLocallocSPOffset(hdrInfo * info)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ _ASSERTE(info->localloc && info->ebpFrame);
+
+ unsigned position = info->savedRegsCountExclFP +
+ 1;
+ return position * sizeof(TADDR);
+}
+
+#ifndef FEATURE_NATIVEAOT
+inline
+size_t GetParamTypeArgOffset(hdrInfo * info)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ _ASSERTE((info->genericsContext || info->handlers) && info->ebpFrame);
+
+ unsigned position = info->savedRegsCountExclFP +
+ info->localloc +
+ 1; // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
+ return position * sizeof(TADDR);
+}
+
+inline size_t GetStartShadowSPSlotsOffset(hdrInfo * info)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ _ASSERTE(info->handlers && info->ebpFrame);
+
+ return GetParamTypeArgOffset(info) +
+ sizeof(TADDR); // Slot for end-of-last-executed-filter
+}
+
+/*****************************************************************************
+ * Returns the start of the hidden slots for the shadowSP for functions
+ * with exception handlers. There is one slot per nesting level starting
+ * near Ebp and is zero-terminated after the active slots.
+ */
+
+inline
+PTR_TADDR GetFirstBaseSPslotPtr(TADDR ebp, hdrInfo * info)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ _ASSERTE(info->handlers && info->ebpFrame);
+
+ size_t offsetFromEBP = GetStartShadowSPSlotsOffset(info)
+ + sizeof(TADDR); // to get to the *start* of the next slot
+
+ return PTR_TADDR(ebp - offsetFromEBP);
+}
+
+inline size_t GetEndShadowSPSlotsOffset(hdrInfo * info, unsigned maxHandlerNestingLevel)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ _ASSERTE(info->handlers && info->ebpFrame);
+
+ unsigned numberOfShadowSPSlots = maxHandlerNestingLevel +
+ 1 + // For zero-termination
+ 1; // For a filter (which can be active at the same time as a catch/finally handler
+
+ return GetStartShadowSPSlotsOffset(info) +
+ (numberOfShadowSPSlots * sizeof(TADDR));
+}
+
+/*****************************************************************************
+ * returns the base frame pointer corresponding to the target nesting level.
+ */
+
+inline
+TADDR GetOutermostBaseFP(TADDR ebp, hdrInfo * info)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // we are not taking into account double alignment. We are
+ // safe because the jit currently bails on double alignment if there
+ // are handles or localalloc
+ _ASSERTE(!info->doubleAlign);
+ if (info->localloc)
+ {
+ // If the function uses localloc we will fetch the ESP from the localloc
+ // slot.
+ PTR_TADDR pLocalloc = PTR_TADDR(ebp - GetLocallocSPOffset(info));
+
+ return (*pLocalloc);
+ }
+ else
+ {
+ // Default, go back all the method's local stack size
+ return ebp - info->stackSize + sizeof(int);
+ }
+}
+
+/*****************************************************************************
+ *
+ * For functions with handlers, checks if it is currently in a handler.
+ * Either of unwindESP or unwindLevel will specify the target nesting level.
+ * If unwindLevel is specified, info about the funclet at that nesting level
+ * will be returned. (Use if you are interested in a specific nesting level.)
+ * If unwindESP is specified, info for nesting level invoked before the stack
+ * reached unwindESP will be returned. (Use if you have a specific ESP value
+ * during stack walking.)
+ *
+ * *pBaseSP is set to the base SP (base of the stack on entry to
+ * the current funclet) corresponding to the target nesting level.
+ * *pNestLevel is set to the nesting level of the target nesting level (useful
+ * if unwindESP!=IGNORE_VAL
+ * *pHasInnerFilter will be set to true (only when unwindESP!=IGNORE_VAL) if a filter
+ * is currently active, but the target nesting level is an outer nesting level.
+ * *pHadInnerFilter - was the last use of the frame to execute a filter.
+ * This mainly affects GC lifetime reporting.
+ */
+
+enum FrameType
+{
+ FR_NORMAL, // Normal method frame - no exceptions currently active
+ FR_FILTER, // Frame-let of a filter
+ FR_HANDLER, // Frame-let of a callable catch/fault/finally
+
+ FR_INVALID, // Invalid frame (for speculative stackwalks)
+};
+
+enum { IGNORE_VAL = -1 };
+
+FrameType GetHandlerFrameInfo(hdrInfo * info,
+ TADDR frameEBP,
+ TADDR unwindESP,
+ DWORD unwindLevel,
+ TADDR * pBaseSP = NULL, /* OUT */
+ DWORD * pNestLevel = NULL, /* OUT */
+ bool * pHasInnerFilter = NULL, /* OUT */
+ bool * pHadInnerFilter = NULL) /* OUT */
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ HOST_NOCALLS;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ _ASSERTE(info->ebpFrame && info->handlers);
+ // One and only one of them should be IGNORE_VAL
+ _ASSERTE((unwindESP == (TADDR) IGNORE_VAL) !=
+ (unwindLevel == (DWORD) IGNORE_VAL));
+ _ASSERTE(pHasInnerFilter == NULL || unwindESP != (TADDR) IGNORE_VAL);
+
+ // Many of the conditions that we'd like to assert cannot be asserted in the case that we're
+ // in the middle of a stackwalk seeded by a profiler, since such seeds can't be trusted
+ // (profilers are external, untrusted sources). So during profiler walks, we test the condition
+ // and throw an exception if it's not met. Otherwise, we just assert the condition.
+ #define FAIL_IF_SPECULATIVE_WALK(condition) \
+ if (info->isSpeculativeStackWalk) \
+ { \
+ if (!(condition)) \
+ { \
+ return FR_INVALID; \
+ } \
+ } \
+ else \
+ { \
+ _ASSERTE(condition); \
+ }
+
+ PTR_TADDR pFirstBaseSPslot = GetFirstBaseSPslotPtr(frameEBP, info);
+ TADDR baseSP = GetOutermostBaseFP(frameEBP, info);
+ bool nonLocalHandlers = false; // Are the funclets invoked by EE (instead of managed code itself)
+ bool hasInnerFilter = false;
+ bool hadInnerFilter = false;
+
+ /* Get the last non-zero slot >= unwindESP, or lvl curSlotVal ||
+ (baseSP == curSlotVal && pSlot == pFirstBaseSPslot));
+
+ if (curSlotVal == LCL_FINALLY_MARK)
+ {
+ // Locally called finally
+ baseSP -= sizeof(TADDR);
+ }
+ else
+ {
+ // Is this a funclet we unwound before (can only happen with filters) ?
+ // If unwindESP is specified, normally we expect it to be the last entry in the shadow slot array.
+ // Or, if there is a filter, we expect unwindESP to be the second last entry. However, this may
+ // not be the case in DAC builds. For example, the user can use .cxr in an EH clause to set a
+ // CONTEXT captured in the try clause. In this case, unwindESP will be the ESP of the parent
+ // function, but the shadow slot array will contain the SP of the EH clause, which is closer to
+ // the leaf than the parent method.
+
+ if (unwindESP != (TADDR) IGNORE_VAL &&
+ unwindESP > END_FIN_POP_STACK +
+ (curSlotVal & ~ICodeManager::SHADOW_SP_BITS))
+ {
+ // In non-DAC builds, the only time unwindESP is closer to the root than entries in the shadow
+ // slot array is when the last entry in the array is for a filter. Also, filters can't have
+ // nested handlers.
+ if ((pSlot[0] & ICodeManager::SHADOW_SP_IN_FILTER) &&
+ (pSlot[-1] == 0) &&
+ !(baseSP & ICodeManager::SHADOW_SP_IN_FILTER))
+ {
+ if (pSlot[0] & ICodeManager::SHADOW_SP_FILTER_DONE)
+ hadInnerFilter = true;
+ else
+ hasInnerFilter = true;
+ break;
+ }
+ else
+ {
+#if defined(DACCESS_COMPILE)
+ // In DAC builds, this could happen. We just need to bail out of this loop early.
+ break;
+#else // !DACCESS_COMPILE
+ // In non-DAC builds, this is an error.
+ FAIL_IF_SPECULATIVE_WALK(FALSE);
+#endif // DACCESS_COMPILE
+ }
+ }
+
+ nonLocalHandlers = true;
+ baseSP = curSlotVal;
+ }
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ if (unwindESP != (TADDR) IGNORE_VAL)
+ {
+ FAIL_IF_SPECULATIVE_WALK(baseSP >= unwindESP ||
+ baseSP == unwindESP - sizeof(TADDR)); // About to locally call a finally
+
+ if (baseSP < unwindESP) // About to locally call a finally
+ baseSP = unwindESP;
+ }
+ else
+ {
+ FAIL_IF_SPECULATIVE_WALK(lvl == unwindLevel); // unwindLevel must be currently active on stack
+ }
+
+ if (pBaseSP)
+ *pBaseSP = baseSP & ~ICodeManager::SHADOW_SP_BITS;
+
+ if (pNestLevel)
+ {
+ *pNestLevel = (DWORD)lvl;
+ }
+
+ if (pHasInnerFilter)
+ *pHasInnerFilter = hasInnerFilter;
+
+ if (pHadInnerFilter)
+ *pHadInnerFilter = hadInnerFilter;
+
+ if (baseSP & ICodeManager::SHADOW_SP_IN_FILTER)
+ {
+ FAIL_IF_SPECULATIVE_WALK(!hasInnerFilter); // nested filters not allowed
+ return FR_FILTER;
+ }
+ else if (nonLocalHandlers)
+ {
+ return FR_HANDLER;
+ }
+ else
+ {
+ return FR_NORMAL;
+ }
+
+ #undef FAIL_IF_SPECULATIVE_WALK
+}
+
+// Returns the number of bytes at the beginning of the stack frame that shouldn't be
+// modified by an EnC. This is everything except the space for locals and temporaries.
+inline size_t GetSizeOfFrameHeaderForEnC(hdrInfo * info)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // See comment above Compiler::lvaAssignFrameOffsets() in src\jit\il\lclVars.cpp
+ // for frame layout
+
+ // EnC supports increasing the maximum handler nesting level by always
+ // assuming that the max is MAX_EnC_HANDLER_NESTING_LEVEL. Methods with
+ // a higher max cannot be updated by EnC
+
+ // Take the offset (from EBP) of the last slot of the header, plus one for the EBP slot itself
+ // to get the total size of the header.
+ return sizeof(TADDR) +
+ GetEndShadowSPSlotsOffset(info, MAX_EnC_HANDLER_NESTING_LEVEL);
+}
+#endif
+
+/*****************************************************************************/
+static
+PTR_CBYTE skipToArgReg(const hdrInfo& info, PTR_CBYTE table)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+#ifdef _DEBUG
+ PTR_CBYTE tableStart = table;
+#else
+ if (info.argTabOffset != INVALID_ARGTAB_OFFSET)
+ {
+ return table + info.argTabOffset;
+ }
+#endif
+
+ unsigned count;
+
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
+#endif
+
+ /* Skip over the untracked frame variable table */
+
+ count = info.untrackedCnt;
+ while (count-- > 0) {
+ fastSkipSigned(table);
+ }
+
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xCAFE);
+#endif
+
+ /* Skip over the frame variable lifetime table */
+
+ count = info.varPtrTableSize;
+ while (count-- > 0) {
+ fastSkipUnsigned(table); fastSkipUnsigned(table); fastSkipUnsigned(table);
+ }
+
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *) == 0xBABE);
+#endif
+
+#if defined(_DEBUG) && defined(CONSISTENCY_CHECK_MSGF)
+ if (info.argTabOffset != INVALID_ARGTAB_OFFSET)
+ {
+ CONSISTENCY_CHECK_MSGF((info.argTabOffset == (unsigned) (table - tableStart)),
+ ("table = %p, tableStart = %p, info.argTabOffset = %d", table, tableStart, info.argTabOffset));
+ }
+#endif
+
+ return table;
+}
+
+/*****************************************************************************/
+
+#define regNumToMask(regNum) RegMask(1<<(regNum))
+
+/*****************************************************************************
+ Helper for scanArgRegTable() and scanArgRegTableI() for regMasks
+ */
+
+void * getCalleeSavedReg(PREGDISPLAY pContext, regNum reg)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ switch (reg)
+ {
+ case REGI_EBP: return pContext->GetEbpLocation();
+ case REGI_EBX: return pContext->GetEbxLocation();
+ case REGI_ESI: return pContext->GetEsiLocation();
+ case REGI_EDI: return pContext->GetEdiLocation();
+
+ default: _ASSERTE(!"bad info.thisPtrResult"); return NULL;
+ }
+}
+
+/*****************************************************************************
+ These functions converts the bits in the GC encoding to RegMask
+ */
+
+inline
+RegMask convertCalleeSavedRegsMask(unsigned inMask) // EBP,EBX,ESI,EDI
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ _ASSERTE((inMask & 0x0F) == inMask);
+
+ unsigned outMask = RM_NONE;
+ if (inMask & 0x1) outMask |= RM_EDI;
+ if (inMask & 0x2) outMask |= RM_ESI;
+ if (inMask & 0x4) outMask |= RM_EBX;
+ if (inMask & 0x8) outMask |= RM_EBP;
+
+ return (RegMask) outMask;
+}
+
+inline
+RegMask convertAllRegsMask(unsigned inMask) // EAX,ECX,EDX,EBX, EBP,ESI,EDI
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ _ASSERTE((inMask & 0xEF) == inMask);
+
+ unsigned outMask = RM_NONE;
+ if (inMask & 0x01) outMask |= RM_EAX;
+ if (inMask & 0x02) outMask |= RM_ECX;
+ if (inMask & 0x04) outMask |= RM_EDX;
+ if (inMask & 0x08) outMask |= RM_EBX;
+ if (inMask & 0x20) outMask |= RM_EBP;
+ if (inMask & 0x40) outMask |= RM_ESI;
+ if (inMask & 0x80) outMask |= RM_EDI;
+
+ return (RegMask)outMask;
+}
+
+/*****************************************************************************
+ * scan the register argument table for the not fully interruptible case.
+ this function is called to find all live objects (pushed arguments)
+ and to get the stack base for EBP-less methods.
+
+ NOTE: If info->argTabResult is NULL, info->argHnumResult indicates
+ how many bits in argMask are valid
+ If info->argTabResult is non-NULL, then the argMask field does
+ not fit in 32-bits and the value in argMask meaningless.
+ Instead argHnum specifies the number of (variable-length) elements
+ in the array, and argTabBytes specifies the total byte size of the
+ array. [ Note this is an extremely rare case ]
+ */
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+static
+unsigned scanArgRegTable(PTR_CBYTE table,
+ unsigned curOffs,
+ hdrInfo * info)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ regNum thisPtrReg = REGI_NA;
+#ifdef _DEBUG
+ bool isCall = false;
+#endif
+ unsigned regMask = 0; // EBP,EBX,ESI,EDI
+ unsigned argMask = 0;
+ unsigned argHnum = 0;
+ PTR_CBYTE argTab = 0;
+ unsigned argTabBytes = 0;
+ unsigned stackDepth = 0;
+
+ unsigned iregMask = 0; // EBP,EBX,ESI,EDI
+ unsigned iargMask = 0;
+ unsigned iptrMask = 0;
+
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
+#endif
+
+ unsigned scanOffs = 0;
+
+ _ASSERTE(scanOffs <= info->methodSize);
+
+ if (info->ebpFrame) {
+ /*
+ Encoding table for methods with an EBP frame and
+ that are not fully interruptible
+
+ The encoding used is as follows:
+
+ this pointer encodings:
+
+ 01000000 this pointer in EBX
+ 00100000 this pointer in ESI
+ 00010000 this pointer in EDI
+
+ tiny encoding:
+
+ 0bsdDDDD
+ requires code delta < 16 (4-bits)
+ requires pushed argmask == 0
+
+ where DDDD is code delta
+ b indicates that register EBX is a live pointer
+ s indicates that register ESI is a live pointer
+ d indicates that register EDI is a live pointer
+
+ small encoding:
+
+ 1DDDDDDD bsdAAAAA
+
+ requires code delta < 120 (7-bits)
+ requires pushed argmask < 64 (5-bits)
+
+ where DDDDDDD is code delta
+ AAAAA is the pushed args mask
+ b indicates that register EBX is a live pointer
+ s indicates that register ESI is a live pointer
+ d indicates that register EDI is a live pointer
+
+ medium encoding
+
+ 0xFD aaaaaaaa AAAAdddd bseDDDDD
+
+ requires code delta < 0x1000000000 (9-bits)
+ requires pushed argmask < 0x1000000000000 (12-bits)
+
+ where DDDDD is the upper 5-bits of the code delta
+ dddd is the low 4-bits of the code delta
+ AAAA is the upper 4-bits of the pushed arg mask
+ aaaaaaaa is the low 8-bits of the pushed arg mask
+ b indicates that register EBX is a live pointer
+ s indicates that register ESI is a live pointer
+ e indicates that register EDI is a live pointer
+
+ medium encoding with interior pointers
+
+ 0xF9 DDDDDDDD bsdAAAAAA iiiIIIII
+
+ requires code delta < (8-bits)
+ requires pushed argmask < (5-bits)
+
+ where DDDDDDD is the code delta
+ b indicates that register EBX is a live pointer
+ s indicates that register ESI is a live pointer
+ d indicates that register EDI is a live pointer
+ AAAAA is the pushed arg mask
+ iii indicates that EBX,EDI,ESI are interior pointers
+ IIIII indicates that bits is the arg mask are interior
+ pointers
+
+ large encoding
+
+ 0xFE [0BSD0bsd][32-bit code delta][32-bit argMask]
+
+ b indicates that register EBX is a live pointer
+ s indicates that register ESI is a live pointer
+ d indicates that register EDI is a live pointer
+ B indicates that register EBX is an interior pointer
+ S indicates that register ESI is an interior pointer
+ D indicates that register EDI is an interior pointer
+ requires pushed argmask < 32-bits
+
+ large encoding with interior pointers
+
+ 0xFA [0BSD0bsd][32-bit code delta][32-bit argMask][32-bit interior pointer mask]
+
+
+ b indicates that register EBX is a live pointer
+ s indicates that register ESI is a live pointer
+ d indicates that register EDI is a live pointer
+ B indicates that register EBX is an interior pointer
+ S indicates that register ESI is an interior pointer
+ D indicates that register EDI is an interior pointer
+ requires pushed argmask < 32-bits
+ requires pushed iArgmask < 32-bits
+
+ huge encoding This is the only encoding that supports
+ a pushed argmask which is greater than
+ 32-bits.
+
+ 0xFB [0BSD0bsd][32-bit code delta]
+ [32-bit table count][32-bit table size]
+ [pushed ptr offsets table...]
+
+ b indicates that register EBX is a live pointer
+ s indicates that register ESI is a live pointer
+ d indicates that register EDI is a live pointer
+ B indicates that register EBX is an interior pointer
+ S indicates that register ESI is an interior pointer
+ D indicates that register EDI is an interior pointer
+ the list count is the number of entries in the list
+ the list size gives the byte-length of the list
+ the offsets in the list are variable-length
+ */
+ while (scanOffs < curOffs)
+ {
+ iregMask = 0;
+ iargMask = 0;
+ argTab = NULL;
+#ifdef _DEBUG
+ isCall = true;
+#endif
+
+ /* Get the next byte and check for a 'special' entry */
+
+ unsigned encType = *table++;
+#if defined(DACCESS_COMPILE)
+ // In this scenario, it is invalid to have a zero byte in the GC info encoding (refer to the
+ // comments above). At least one bit has to be set. For example, a byte can represent which
+ // register is the "this" pointer, and this byte has to be 0x10, 0x20, or 0x40. Having a zero
+ // byte indicates there is most likely some sort of DAC error, and it may lead to problems such as
+ // infinite loops. So we bail out early instead.
+ if (encType == 0)
+ {
+ DacError(CORDBG_E_TARGET_INCONSISTENT);
+ UNREACHABLE();
+ }
+#endif // DACCESS_COMPILE
+
+ switch (encType)
+ {
+ unsigned val, nxt;
+
+ default:
+
+ /* A tiny or small call entry */
+ val = encType;
+ if ((val & 0x80) == 0x00) {
+ if (val & 0x0F) {
+ /* A tiny call entry */
+ scanOffs += (val & 0x0F);
+ regMask = (val & 0x70) >> 4;
+ argMask = 0;
+ argHnum = 0;
+ }
+ else {
+ /* This pointer liveness encoding */
+ regMask = (val & 0x70) >> 4;
+ if (regMask == 0x1)
+ thisPtrReg = REGI_EDI;
+ else if (regMask == 0x2)
+ thisPtrReg = REGI_ESI;
+ else if (regMask == 0x4)
+ thisPtrReg = REGI_EBX;
+ else
+ _ASSERTE(!"illegal encoding for 'this' pointer liveness");
+ }
+ }
+ else {
+ /* A small call entry */
+ scanOffs += (val & 0x7F);
+ val = *table++;
+ regMask = val >> 5;
+ argMask = val & 0x1F;
+ argHnum = 5;
+ }
+ break;
+
+ case 0xFD: // medium encoding
+
+ argMask = *table++;
+ val = *table++;
+ argMask |= ((val & 0xF0) << 4);
+ argHnum = 12;
+ nxt = *table++;
+ scanOffs += (val & 0x0F) + ((nxt & 0x1F) << 4);
+ regMask = nxt >> 5; // EBX,ESI,EDI
+
+ break;
+
+ case 0xF9: // medium encoding with interior pointers
+
+ scanOffs += *table++;
+ val = *table++;
+ argMask = val & 0x1F;
+ argHnum = 5;
+ regMask = val >> 5;
+ val = *table++;
+ iargMask = val & 0x1F;
+ iregMask = val >> 5;
+
+ break;
+
+ case 0xFE: // large encoding
+ case 0xFA: // large encoding with interior pointers
+
+ val = *table++;
+ regMask = val & 0x7;
+ iregMask = val >> 4;
+ scanOffs += *dac_cast(table); table += sizeof(DWORD);
+ argMask = *dac_cast(table); table += sizeof(DWORD);
+ argHnum = 31;
+ if (encType == 0xFA) // read iargMask
+ {
+ iargMask = *dac_cast(table); table += sizeof(DWORD);
+ }
+ break;
+
+ case 0xFB: // huge encoding This is the only partially interruptible
+ // encoding that supports a pushed ArgMask
+ // which is greater than 32-bits.
+ // The ArgMask is encoded using the argTab
+ val = *table++;
+ regMask = val & 0x7;
+ iregMask = val >> 4;
+ scanOffs += *dac_cast(table); table += sizeof(DWORD);
+ argHnum = *dac_cast(table); table += sizeof(DWORD);
+ argTabBytes = *dac_cast(table); table += sizeof(DWORD);
+ argTab = table; table += argTabBytes;
+
+ argMask = 0;
+ break;
+
+ case 0xFF:
+ scanOffs = curOffs + 1;
+ break;
+
+ } // end case
+
+ // iregMask & iargMask are subsets of regMask & argMask respectively
+
+ _ASSERTE((iregMask & regMask) == iregMask);
+ _ASSERTE((iargMask & argMask) == iargMask);
+
+ } // end while
+
+ }
+ else {
+
+/*
+ * Encoding table for methods with an ESP frame and are not fully interruptible
+ * This encoding does not support a pushed ArgMask greater than 32
+ *
+ * The encoding used is as follows:
+ *
+ * push 000DDDDD ESP push one item with 5-bit delta
+ * push 00100000 [pushCount] ESP push multiple items
+ * reserved 0011xxxx
+ * skip 01000000 [Delta] Skip Delta, arbitrary sized delta
+ * skip 0100DDDD Skip small Delta, for call (DDDD != 0)
+ * pop 01CCDDDD ESP pop CC items with 4-bit delta (CC != 00)
+ * call 1PPPPPPP Call Pattern, P=[0..79]
+ * call 1101pbsd DDCCCMMM Call RegMask=pbsd,ArgCnt=CCC,
+ * ArgMask=MMM Delta=commonDelta[DD]
+ * call 1110pbsd [ArgCnt] [ArgMask] Call ArgCnt,RegMask=pbsd,[32-bit ArgMask]
+ * call 11111000 [PBSDpbsd][32-bit delta][32-bit ArgCnt]
+ * [32-bit PndCnt][32-bit PndSize][PndOffs...]
+ * iptr 11110000 [IPtrMask] Arbitrary 32-bit Interior Pointer Mask
+ * thisptr 111101RR This pointer is in Register RR
+ * 00=EDI,01=ESI,10=EBX,11=EBP
+ * reserved 111100xx xx != 00
+ * reserved 111110xx xx != 00
+ * reserved 11111xxx xxx != 000 && xxx != 111(EOT)
+ *
+ * The value 11111111 [0xFF] indicates the end of the table.
+ *
+ * An offset (at which stack-walking is performed) without an explicit encoding
+ * is assumed to be a trivial call-site (no GC registers, stack empty before and
+ * after) to avoid having to encode all trivial calls.
+ *
+ * Note on the encoding used for interior pointers
+ *
+ * The iptr encoding must immediately precede a call encoding. It is used to
+ * transform a normal GC pointer addresses into an interior pointers for GC purposes.
+ * The mask supplied to the iptr encoding is read from the least signicant bit
+ * to the most signicant bit. (i.e the lowest bit is read first)
+ *
+ * p indicates that register EBP is a live pointer
+ * b indicates that register EBX is a live pointer
+ * s indicates that register ESI is a live pointer
+ * d indicates that register EDI is a live pointer
+ * P indicates that register EBP is an interior pointer
+ * B indicates that register EBX is an interior pointer
+ * S indicates that register ESI is an interior pointer
+ * D indicates that register EDI is an interior pointer
+ *
+ * As an example the following sequence indicates that EDI.ESI and the 2nd pushed pointer
+ * in ArgMask are really interior pointers. The pointer in ESI in a normal pointer:
+ *
+ * iptr 11110000 00010011 => read Interior Ptr, Interior Ptr, Normal Ptr, Normal Ptr, Interior Ptr
+ * call 11010011 DDCCC011 RRRR=1011 => read EDI is a GC-pointer, ESI is a GC-pointer. EBP is a GC-pointer
+ * MMM=0011 => read two GC-pointers arguments on the stack (nested call)
+ *
+ * Since the call instruction mentions 5 GC-pointers we list them in the required order:
+ * EDI, ESI, EBP, 1st-pushed pointer, 2nd-pushed pointer
+ *
+ * And we apply the Interior Pointer mask mmmm=10011 to the above five ordered GC-pointers
+ * we learn that EDI and ESI are interior GC-pointers and that the second push arg is an
+ * interior GC-pointer.
+ */
+
+#if defined(DACCESS_COMPILE)
+ DWORD cbZeroBytes = 0;
+#endif // DACCESS_COMPILE
+
+ while (scanOffs <= curOffs)
+ {
+ unsigned callArgCnt;
+ unsigned skip;
+ unsigned newRegMask, inewRegMask;
+ unsigned newArgMask, inewArgMask;
+ unsigned oldScanOffs = scanOffs;
+
+ if (iptrMask)
+ {
+ // We found this iptrMask in the previous iteration.
+ // This iteration must be for a call. Set these variables
+ // so that they are available at the end of the loop
+
+ inewRegMask = iptrMask & 0x0F; // EBP,EBX,ESI,EDI
+ inewArgMask = iptrMask >> 4;
+
+ iptrMask = 0;
+ }
+ else
+ {
+ // Zero out any stale values.
+
+ inewRegMask = 0;
+ inewArgMask = 0;
+ }
+
+ /* Get the next byte and decode it */
+
+ unsigned val = *table++;
+#if defined(DACCESS_COMPILE)
+ // In this scenario, a 0 means that there is a push at the current offset. For a struct with
+ // two double fields, the JIT may use two movq instructions to push the struct onto the stack, and
+ // the JIT will encode 4 pushes at the same code offset. This means that we can have up to 4
+ // consecutive bytes of 0 without changing the code offset. Having more than 4 consecutive bytes
+ // of zero indicates that there is most likely some sort of DAC error, and it may lead to problems
+ // such as infinite loops. So we bail out early instead.
+ if (val == 0)
+ {
+ cbZeroBytes += 1;
+ if (cbZeroBytes > 4)
+ {
+ DacError(CORDBG_E_TARGET_INCONSISTENT);
+ UNREACHABLE();
+ }
+ }
+ else
+ {
+ cbZeroBytes = 0;
+ }
+#endif // DACCESS_COMPILE
+
+#ifdef _DEBUG
+ if (scanOffs != curOffs)
+ isCall = false;
+#endif
+
+ /* Check pushes, pops, and skips */
+
+ if (!(val & 0x80)) {
+
+ // iptrMask can immediately precede only calls
+
+ _ASSERTE(inewRegMask == 0);
+ _ASSERTE(inewArgMask == 0);
+
+ if (!(val & 0x40)) {
+
+ unsigned pushCount;
+
+ if (!(val & 0x20))
+ {
+ //
+ // push 000DDDDD ESP push one item, 5-bit delta
+ //
+ pushCount = 1;
+ scanOffs += val & 0x1f;
+ }
+ else
+ {
+ //
+ // push 00100000 [pushCount] ESP push multiple items
+ //
+ _ASSERTE(val == 0x20);
+ pushCount = fastDecodeUnsigned(table);
+ }
+
+ if (scanOffs > curOffs)
+ {
+ scanOffs = oldScanOffs;
+ goto FINISHED;
+ }
+
+ stackDepth += pushCount;
+ }
+ else if ((val & 0x3f) != 0) {
+ //
+ // pop 01CCDDDD pop CC items, 4-bit delta
+ //
+ scanOffs += val & 0x0f;
+ if (scanOffs > curOffs)
+ {
+ scanOffs = oldScanOffs;
+ goto FINISHED;
+ }
+ stackDepth -= (val & 0x30) >> 4;
+
+ } else if (scanOffs < curOffs) {
+ //
+ // skip 01000000 [Delta] Skip arbitrary sized delta
+ //
+ skip = fastDecodeUnsigned(table);
+ scanOffs += skip;
+ }
+ else // don't process a skip if we are already at curOffs
+ goto FINISHED;
+
+ /* reset regs and args state since we advance past last call site */
+
+ regMask = 0;
+ iregMask = 0;
+ argMask = 0;
+ iargMask = 0;
+ argHnum = 0;
+
+ }
+ else /* It must be a call, thisptr, or iptr */
+ {
+ switch ((val & 0x70) >> 4) {
+ default: // case 0-4, 1000xxxx through 1100xxxx
+ //
+ // call 1PPPPPPP Call Pattern, P=[0..79]
+ //
+ decodeCallPattern((val & 0x7f), &callArgCnt,
+ &newRegMask, &newArgMask, &skip);
+ // If we've already reached curOffs and the skip amount
+ // is non-zero then we are done
+ if ((scanOffs == curOffs) && (skip > 0))
+ goto FINISHED;
+ // otherwise process this call pattern
+ scanOffs += skip;
+ if (scanOffs > curOffs)
+ goto FINISHED;
+#ifdef _DEBUG
+ isCall = true;
+#endif
+ regMask = newRegMask;
+ argMask = newArgMask; argTab = NULL;
+ iregMask = inewRegMask;
+ iargMask = inewArgMask;
+ stackDepth -= callArgCnt;
+ argHnum = 2; // argMask is known to be <= 3
+ break;
+
+ case 5:
+ //
+ // call 1101RRRR DDCCCMMM Call RegMask=RRRR,ArgCnt=CCC,
+ // ArgMask=MMM Delta=commonDelta[DD]
+ //
+ newRegMask = val & 0xf; // EBP,EBX,ESI,EDI
+ val = *table++; // read next byte
+ skip = callCommonDelta[val>>6];
+ // If we've already reached curOffs and the skip amount
+ // is non-zero then we are done
+ if ((scanOffs == curOffs) && (skip > 0))
+ goto FINISHED;
+ // otherwise process this call encoding
+ scanOffs += skip;
+ if (scanOffs > curOffs)
+ goto FINISHED;
+#ifdef _DEBUG
+ isCall = true;
+#endif
+ regMask = newRegMask;
+ iregMask = inewRegMask;
+ callArgCnt = (val >> 3) & 0x7;
+ stackDepth -= callArgCnt;
+ argMask = (val & 0x7); argTab = NULL;
+ iargMask = inewArgMask;
+ argHnum = 3;
+ break;
+
+ case 6:
+ //
+ // call 1110RRRR [ArgCnt] [ArgMask]
+ // Call ArgCnt,RegMask=RRR,ArgMask
+ //
+#ifdef _DEBUG
+ isCall = true;
+#endif
+ regMask = val & 0xf; // EBP,EBX,ESI,EDI
+ iregMask = inewRegMask;
+ callArgCnt = fastDecodeUnsigned(table);
+ stackDepth -= callArgCnt;
+ argMask = fastDecodeUnsigned(table); argTab = NULL;
+ iargMask = inewArgMask;
+ argHnum = sizeof(argMask) * 8; // The size of argMask in bits
+ break;
+
+ case 7:
+ switch (val & 0x0C)
+ {
+ case 0x00:
+ //
+ // 0xF0 iptr 11110000 [IPtrMask] Arbitrary Interior Pointer Mask
+ //
+ iptrMask = fastDecodeUnsigned(table);
+ break;
+
+ case 0x04:
+ //
+ // 0xF4 thisptr 111101RR This pointer is in Register RR
+ // 00=EDI,01=ESI,10=EBX,11=EBP
+ //
+ {
+ static const regNum calleeSavedRegs[] =
+ { REGI_EDI, REGI_ESI, REGI_EBX, REGI_EBP };
+ thisPtrReg = calleeSavedRegs[val&0x3];
+ }
+ break;
+
+ case 0x08:
+ //
+ // 0xF8 call 11111000 [PBSDpbsd][32-bit delta][32-bit ArgCnt]
+ // [32-bit PndCnt][32-bit PndSize][PndOffs...]
+ //
+ val = *table++;
+ skip = *dac_cast(table); table += sizeof(DWORD);
+// [VSUQFE 4670]
+ // If we've already reached curOffs and the skip amount
+ // is non-zero then we are done
+ if ((scanOffs == curOffs) && (skip > 0))
+ goto FINISHED;
+// [VSUQFE 4670]
+ scanOffs += skip;
+ if (scanOffs > curOffs)
+ goto FINISHED;
+#ifdef _DEBUG
+ isCall = true;
+#endif
+ regMask = val & 0xF;
+ iregMask = val >> 4;
+ callArgCnt = *dac_cast(table); table += sizeof(DWORD);
+ stackDepth -= callArgCnt;
+ argHnum = *dac_cast(table); table += sizeof(DWORD);
+ argTabBytes = *dac_cast(table); table += sizeof(DWORD);
+ argTab = table;
+ table += argTabBytes;
+ break;
+
+ case 0x0C:
+ //
+ // 0xFF end 11111111 End of table marker
+ //
+ _ASSERTE(val==0xff);
+ goto FINISHED;
+
+ default:
+ _ASSERTE(!"reserved GC encoding");
+ break;
+ }
+ break;
+
+ } // end switch
+
+ } // end else (!(val & 0x80))
+
+ // iregMask & iargMask are subsets of regMask & argMask respectively
+
+ _ASSERTE((iregMask & regMask) == iregMask);
+ _ASSERTE((iargMask & argMask) == iargMask);
+
+ } // end while
+
+ } // end else ebp-less frame
+
+FINISHED:
+
+ // iregMask & iargMask are subsets of regMask & argMask respectively
+
+ _ASSERTE((iregMask & regMask) == iregMask);
+ _ASSERTE((iargMask & argMask) == iargMask);
+
+ if (scanOffs != curOffs)
+ {
+ /* must have been a boring call */
+ info->regMaskResult = RM_NONE;
+ info->argMaskResult = ptrArgTP(0);
+ info->iregMaskResult = RM_NONE;
+ info->iargMaskResult = ptrArgTP(0);
+ info->argHnumResult = 0;
+ info->argTabResult = NULL;
+ info->argTabBytes = 0;
+ }
+ else
+ {
+ info->regMaskResult = convertCalleeSavedRegsMask(regMask);
+ info->argMaskResult = ptrArgTP(argMask);
+ info->argHnumResult = argHnum;
+ info->iregMaskResult = convertCalleeSavedRegsMask(iregMask);
+ info->iargMaskResult = ptrArgTP(iargMask);
+ info->argTabResult = argTab;
+ info->argTabBytes = argTabBytes;
+ }
+
+#ifdef _DEBUG
+ if (scanOffs != curOffs) {
+ isCall = false;
+ }
+ _ASSERTE(thisPtrReg == REGI_NA || (!isCall || (regNumToMask(thisPtrReg) & info->regMaskResult)));
+#endif
+ info->thisPtrResult = thisPtrReg;
+
+ _ASSERTE(int(stackDepth) < INT_MAX); // check that it did not underflow
+ return (stackDepth * sizeof(unsigned));
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+
+/*****************************************************************************
+ * scan the register argument table for the fully interruptible case.
+ this function is called to find all live objects (pushed arguments)
+ and to get the stack base for fully interruptible methods.
+ Returns size of things pushed on the stack for ESP frames
+
+ Arguments:
+ table - The pointer table
+ curOffsRegs - The current code offset that should be used for reporting registers
+ curOffsArgs - The current code offset that should be used for reporting args
+ info - Incoming arg used to determine if there's a frame, and to save results
+ */
+
+static
+unsigned scanArgRegTableI(PTR_CBYTE table,
+ unsigned curOffsRegs,
+ unsigned curOffsArgs,
+ hdrInfo * info)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ regNum thisPtrReg = REGI_NA;
+ unsigned ptrRegs = 0; // The mask of registers that contain pointers
+ unsigned iptrRegs = 0; // The subset of ptrRegs that are interior pointers
+ unsigned ptrOffs = 0; // The code offset of the table entry we are currently looking at
+ unsigned argCnt = 0; // The number of args that have been pushed
+
+ ptrArgTP ptrArgs(0); // The mask of stack values that contain pointers.
+ ptrArgTP iptrArgs(0); // The subset of ptrArgs that are interior pointers.
+ ptrArgTP argHigh(0); // The current mask position that corresponds to the top of the stack.
+
+ bool isThis = false;
+ bool iptr = false;
+
+ // The comment before the call to scanArgRegTableI in EnumGCRefs
+ // describes why curOffsRegs can be smaller than curOffsArgs.
+ _ASSERTE(curOffsRegs <= curOffsArgs);
+
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
+#endif
+
+ bool hasPartialArgInfo;
+
+#ifndef UNIX_X86_ABI
+ hasPartialArgInfo = info->ebpFrame;
+#else
+ // For x86/Linux, interruptible code always has full arg info
+ //
+ // This should be aligned with emitFullArgInfo setting at
+ // emitter::emitEndCodeGen (in JIT)
+ hasPartialArgInfo = false;
+#endif
+
+ /*
+ Encoding table for methods that are fully interruptible
+
+ The encoding used is as follows:
+
+ ptr reg dead 00RRRDDD [RRR != 100]
+ ptr reg live 01RRRDDD [RRR != 100]
+
+ non-ptr arg push 10110DDD [SSS == 110]
+ ptr arg push 10SSSDDD [SSS != 110] && [SSS != 111]
+ ptr arg pop 11CCCDDD [CCC != 000] && [CCC != 110] && [CCC != 111]
+ little delta skip 11000DDD [CCC == 000]
+ bigger delta skip 11110BBB [CCC == 110]
+
+ The values used in the encodings are as follows:
+
+ DDD code offset delta from previous entry (0-7)
+ BBB bigger delta 000=8,001=16,010=24,...,111=64
+ RRR register number (EAX=000,ECX=001,EDX=010,EBX=011,
+ EBP=101,ESI=110,EDI=111), ESP=100 is reserved
+ SSS argument offset from base of stack. This is
+ redundant for frameless methods as we can
+ infer it from the previous pushes+pops. However,
+ for EBP-methods, we only report GC pushes, and
+ so we need SSS
+ CCC argument count being popped (includes only ptrs for EBP methods)
+
+ The following are the 'large' versions:
+
+ large delta skip 10111000 [0xB8] , encodeUnsigned(delta)
+
+ large ptr arg push 11111000 [0xF8] , encodeUnsigned(pushCount)
+ large non-ptr arg push 11111001 [0xF9] , encodeUnsigned(pushCount)
+ large ptr arg pop 11111100 [0xFC] , encodeUnsigned(popCount)
+ large arg dead 11111101 [0xFD] , encodeUnsigned(popCount) for caller-pop args.
+ Any GC args go dead after the call,
+ but are still sitting on the stack
+
+ this pointer prefix 10111100 [0xBC] the next encoding is a ptr live
+ or a ptr arg push
+ and contains the this pointer
+
+ interior or by-ref 10111111 [0xBF] the next encoding is a ptr live
+ pointer prefix or a ptr arg push
+ and contains an interior
+ or by-ref pointer
+
+
+ The value 11111111 [0xFF] indicates the end of the table.
+ */
+
+#if defined(DACCESS_COMPILE)
+ bool fLastByteIsZero = false;
+#endif // DACCESS_COMPILE
+
+ /* Have we reached the instruction we're looking for? */
+
+ while (ptrOffs <= curOffsArgs)
+ {
+ unsigned val;
+
+ int isPop;
+ unsigned argOfs;
+
+ unsigned regMask;
+
+ // iptrRegs & iptrArgs are subsets of ptrRegs & ptrArgs respectively
+
+ _ASSERTE((iptrRegs & ptrRegs) == iptrRegs);
+ _ASSERTE((iptrArgs & ptrArgs) == iptrArgs);
+
+ /* Now find the next 'life' transition */
+
+ val = *table++;
+#if defined(DACCESS_COMPILE)
+ // In this scenario, a zero byte means that EAX is going dead at the current offset. Since EAX
+ // can't go dead more than once at any given offset, it's invalid to have two consecutive bytes
+ // of zero. If this were to happen, then it means that there is most likely some sort of DAC
+ // error, and it may lead to problems such as infinite loops. So we bail out early instead.
+ if ((val == 0) && fLastByteIsZero)
+ {
+ DacError(CORDBG_E_TARGET_INCONSISTENT);
+ UNREACHABLE();
+ }
+ fLastByteIsZero = (val == 0);
+#endif // DACCESS_COMPILE
+
+ if (!(val & 0x80))
+ {
+ /* A small 'regPtr' encoding */
+
+ regNum reg;
+
+ ptrOffs += (val ) & 0x7;
+ if (ptrOffs > curOffsArgs) {
+ iptr = isThis = false;
+ goto REPORT_REFS;
+ }
+ else if (ptrOffs > curOffsRegs) {
+ iptr = isThis = false;
+ continue;
+ }
+
+ reg = (regNum)((val >> 3) & 0x7);
+ regMask = 1 << reg; // EAX,ECX,EDX,EBX,---,EBP,ESI,EDI
+
+#if 0
+ printf("regMask = %04X -> %04X\n", ptrRegs,
+ (val & 0x40) ? (ptrRegs | regMask)
+ : (ptrRegs & ~regMask));
+#endif
+
+ /* The register is becoming live/dead here */
+
+ if (val & 0x40)
+ {
+ /* Becomes Live */
+ _ASSERTE((ptrRegs & regMask) == 0);
+
+ ptrRegs |= regMask;
+
+ if (isThis)
+ {
+ thisPtrReg = reg;
+ }
+ if (iptr)
+ {
+ iptrRegs |= regMask;
+ }
+ }
+ else
+ {
+ /* Becomes Dead */
+ _ASSERTE((ptrRegs & regMask) != 0);
+
+ ptrRegs &= ~regMask;
+
+ if (reg == thisPtrReg)
+ {
+ thisPtrReg = REGI_NA;
+ }
+ if (iptrRegs & regMask)
+ {
+ iptrRegs &= ~regMask;
+ }
+ }
+ iptr = isThis = false;
+ continue;
+ }
+
+ /* This is probably an argument push/pop */
+
+ argOfs = (val & 0x38) >> 3;
+
+ /* 6 [110] and 7 [111] are reserved for other encodings */
+ if (argOfs < 6)
+ {
+
+ /* A small argument encoding */
+
+ ptrOffs += (val & 0x07);
+ if (ptrOffs > curOffsArgs) {
+ iptr = isThis = false;
+ goto REPORT_REFS;
+ }
+ isPop = (val & 0x40);
+
+ ARG:
+
+ if (isPop)
+ {
+ if (argOfs == 0)
+ continue; // little skip encoding
+
+ /* We remove (pop) the top 'argOfs' entries */
+
+ _ASSERTE(argOfs || argOfs <= argCnt);
+
+ /* adjust # of arguments */
+
+ argCnt -= argOfs;
+ _ASSERTE(argCnt < MAX_PTRARG_OFS);
+
+// printf("[%04X] popping %u args: mask = %04X\n", ptrOffs, argOfs, (int)ptrArgs);
+
+ do
+ {
+ _ASSERTE(!isZero(argHigh));
+
+ /* Do we have an argument bit that's on? */
+
+ if (intersect(ptrArgs, argHigh))
+ {
+ /* Turn off the bit */
+
+ setDiff(ptrArgs, argHigh);
+ setDiff(iptrArgs, argHigh);
+
+ /* We've removed one more argument bit */
+
+ argOfs--;
+ }
+ else if (hasPartialArgInfo)
+ argCnt--;
+ else /* full arg info && not a ref */
+ argOfs--;
+
+ /* Continue with the next lower bit */
+
+ argHigh >>= 1;
+ }
+ while (argOfs);
+
+ _ASSERTE(!hasPartialArgInfo ||
+ isZero(argHigh) ||
+ (argHigh == CONSTRUCT_ptrArgTP(1, (argCnt-1))));
+
+ if (hasPartialArgInfo)
+ {
+ // We always leave argHigh pointing to the next ptr arg.
+ // So, while argHigh is non-zero, and not a ptrArg, we shift right (and subtract
+ // one arg from our argCnt) until it is a ptrArg.
+ while (!intersect(argHigh, ptrArgs) && (!isZero(argHigh)))
+ {
+ argHigh >>= 1;
+ argCnt--;
+ }
+ }
+
+ }
+ else
+ {
+ /* Add a new ptr arg entry at stack offset 'argOfs' */
+
+ if (argOfs >= MAX_PTRARG_OFS)
+ {
+ _ASSERTE_ALL_BUILDS(!"scanArgRegTableI: args pushed 'too deep'");
+ }
+ else
+ {
+ /* Full arg info reports all pushes, and thus
+ argOffs has to be consistent with argCnt */
+
+ _ASSERTE(hasPartialArgInfo || argCnt == argOfs);
+
+ /* store arg count */
+
+ argCnt = argOfs + 1;
+ _ASSERTE((argCnt < MAX_PTRARG_OFS));
+
+ /* Compute the appropriate argument offset bit */
+
+ ptrArgTP argMask = CONSTRUCT_ptrArgTP(1, argOfs);
+
+// printf("push arg at offset %02u --> mask = %04X\n", argOfs, (int)argMask);
+
+ /* We should never push twice at the same offset */
+
+ _ASSERTE(!intersect( ptrArgs, argMask));
+ _ASSERTE(!intersect(iptrArgs, argMask));
+
+ /* We should never push within the current highest offset */
+
+ // _ASSERTE(argHigh < argMask);
+
+ /* This is now the highest bit we've set */
+
+ argHigh = argMask;
+
+ /* Set the appropriate bit in the argument mask */
+
+ ptrArgs |= argMask;
+
+ if (iptr)
+ iptrArgs |= argMask;
+ }
+
+ iptr = isThis = false;
+ }
+ continue;
+ }
+ else if (argOfs == 6)
+ {
+ if (val & 0x40) {
+ /* Bigger delta 000=8,001=16,010=24,...,111=64 */
+ ptrOffs += (((val & 0x07) + 1) << 3);
+ }
+ else {
+ /* non-ptr arg push */
+ _ASSERTE(!hasPartialArgInfo);
+ ptrOffs += (val & 0x07);
+ if (ptrOffs > curOffsArgs) {
+ iptr = isThis = false;
+ goto REPORT_REFS;
+ }
+ argHigh = CONSTRUCT_ptrArgTP(1, argCnt);
+ argCnt++;
+ _ASSERTE(argCnt < MAX_PTRARG_OFS);
+ }
+ continue;
+ }
+
+ /* argOfs was 7 [111] which is reserved for the larger encodings */
+
+ _ASSERTE(argOfs==7);
+
+ switch (val)
+ {
+ case 0xFF:
+ iptr = isThis = false;
+ goto REPORT_REFS; // the method might loop !!!
+
+ case 0xB8:
+ val = fastDecodeUnsigned(table);
+ ptrOffs += val;
+ continue;
+
+ case 0xBC:
+ isThis = true;
+ break;
+
+ case 0xBF:
+ iptr = true;
+ break;
+
+ case 0xF8:
+ case 0xFC:
+ isPop = val & 0x04;
+ argOfs = fastDecodeUnsigned(table);
+ goto ARG;
+
+ case 0xFD: {
+ argOfs = fastDecodeUnsigned(table);
+ _ASSERTE(argOfs && argOfs <= argCnt);
+
+ // Kill the top "argOfs" pointers.
+
+ ptrArgTP argMask;
+ for(argMask = CONSTRUCT_ptrArgTP(1, argCnt); (argOfs != 0); argMask >>= 1)
+ {
+ _ASSERTE(!isZero(argMask) && !isZero(ptrArgs)); // there should be remaining pointers
+
+ if (intersect(ptrArgs, argMask))
+ {
+ setDiff(ptrArgs, argMask);
+ setDiff(iptrArgs, argMask);
+ argOfs--;
+ }
+ }
+
+ // For partial arg info, need to find the next highest pointer for argHigh
+
+ if (hasPartialArgInfo)
+ {
+ for(argHigh = ptrArgTP(0); !isZero(argMask); argMask >>= 1)
+ {
+ if (intersect(ptrArgs, argMask)) {
+ argHigh = argMask;
+ break;
+ }
+ }
+ }
+ } break;
+
+ case 0xF9:
+ argOfs = fastDecodeUnsigned(table);
+ argCnt += argOfs;
+ break;
+
+ default:
+ _ASSERTE(!"Unexpected special code %04X");
+ }
+ }
+
+ /* Report all live pointer registers */
+REPORT_REFS:
+
+ _ASSERTE((iptrRegs & ptrRegs) == iptrRegs); // iptrRegs is a subset of ptrRegs
+ _ASSERTE((iptrArgs & ptrArgs) == iptrArgs); // iptrArgs is a subset of ptrArgs
+
+ /* Save the current live register, argument set, and argCnt */
+
+ info->regMaskResult = convertAllRegsMask(ptrRegs);
+ info->argMaskResult = ptrArgs;
+ info->argHnumResult = 0;
+ info->iregMaskResult = convertAllRegsMask(iptrRegs);
+ info->iargMaskResult = iptrArgs;
+
+ info->thisPtrResult = thisPtrReg;
+ _ASSERTE(thisPtrReg == REGI_NA || (regNumToMask(thisPtrReg) & info->regMaskResult));
+
+ if (hasPartialArgInfo)
+ {
+ return 0;
+ }
+ else
+ {
+ _ASSERTE(int(argCnt) < INT_MAX); // check that it did not underflow
+ return (argCnt * sizeof(unsigned));
+ }
+}
+
+/*****************************************************************************/
+
+unsigned GetPushedArgSize(hdrInfo * info, PTR_CBYTE table, DWORD curOffs)
+{
+ SUPPORTS_DAC;
+
+ unsigned sz;
+
+ if (info->interruptible)
+ {
+ sz = scanArgRegTableI(skipToArgReg(*info, table),
+ curOffs,
+ curOffs,
+ info);
+ }
+ else
+ {
+ sz = scanArgRegTable(skipToArgReg(*info, table),
+ curOffs,
+ info);
+ }
+
+ return sz;
+}
+
+/*****************************************************************************/
+
+inline
+void TRASH_CALLEE_UNSAVED_REGS(PREGDISPLAY pContext)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+#ifdef _DEBUG
+ /* This is not completely correct as we lose the current value, but
+ it should not really be useful to anyone. */
+ static DWORD s_badData = 0xDEADBEEF;
+ pContext->SetEaxLocation(&s_badData);
+ pContext->SetEcxLocation(&s_badData);
+ pContext->SetEdxLocation(&s_badData);
+#endif //_DEBUG
+}
+
+/*****************************************************************************
+ * Sizes of certain i386 instructions which are used in the prolog/epilog
+ */
+
+// Can we use sign-extended byte to encode the imm value, or do we need a dword
+#define CAN_COMPRESS(val) ((INT8)(val) == (INT32)(val))
+
+#define SZ_ADD_REG(val) ( 2 + (CAN_COMPRESS(val) ? 1 : 4))
+#define SZ_AND_REG(val) SZ_ADD_REG(val)
+#define SZ_POP_REG 1
+#define SZ_LEA(offset) SZ_ADD_REG(offset)
+#define SZ_MOV_REG_REG 2
+
+bool IsMarkerInstr(BYTE val)
+{
+ SUPPORTS_DAC;
+
+#ifdef _DEBUG
+ if (val == X86_INSTR_INT3)
+ {
+ return true;
+ }
+#ifdef HAVE_GCCOVER
+ else // GcCover might have stomped on the instruction
+ {
+ if (GCStress::IsEnabled())
+ {
+ if (IsGcCoverageInterruptInstructionVal(val))
+ {
+ return true;
+ }
+ }
+ }
+#endif // HAVE_GCCOVER
+#endif // _DEBUG
+
+ return false;
+}
+
+/* Check if the given instruction opcode is the one we expect.
+ This is a "necessary" but not "sufficient" check as it ignores the check
+ if the instruction is one of our special markers (for debugging and GcStress) */
+
+bool CheckInstrByte(BYTE val, BYTE expectedValue)
+{
+ SUPPORTS_DAC;
+ return ((val == expectedValue) || IsMarkerInstr(val));
+}
+
+/* Similar to CheckInstrByte(). Use this to check a masked opcode (ignoring
+ optional bits in the opcode encoding).
+ valPattern is the masked out value.
+ expectedPattern is the mask value we expect.
+ val is the actual instruction opcode
+ */
+bool CheckInstrBytePattern(BYTE valPattern, BYTE expectedPattern, BYTE val)
+{
+ SUPPORTS_DAC;
+
+ _ASSERTE((valPattern & val) == valPattern);
+
+ return ((valPattern == expectedPattern) || IsMarkerInstr(val));
+}
+
+/* Similar to CheckInstrByte() */
+
+bool CheckInstrWord(WORD val, WORD expectedValue)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ return ((val == expectedValue) || IsMarkerInstr(val & 0xFF));
+}
+
+// Use this to check if the instruction at offset "walkOffset" has already
+// been executed
+// "actualHaltOffset" is the offset when the code was suspended
+// It is assumed that there is linear control flow from offset 0 to "actualHaltOffset".
+//
+// This has been factored out just so that the intent of the comparison
+// is clear (compared to the opposite intent)
+
+bool InstructionAlreadyExecuted(unsigned walkOffset, unsigned actualHaltOffset)
+{
+ SUPPORTS_DAC;
+ return (walkOffset < actualHaltOffset);
+}
+
+// skips past a "arith REG, IMM"
+inline unsigned SKIP_ARITH_REG(int val, PTR_CBYTE base, unsigned offset)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ unsigned delta = 0;
+ if (val != 0)
+ {
+#ifdef _DEBUG
+ // Confirm that arith instruction is at the correct place
+ _ASSERTE(CheckInstrBytePattern(base[offset ] & 0xFD, 0x81, base[offset]) &&
+ CheckInstrBytePattern(base[offset+1] & 0xC0, 0xC0, base[offset+1]));
+ // only use DWORD form if needed
+ _ASSERTE(((base[offset] & 2) != 0) == CAN_COMPRESS(val) ||
+ IsMarkerInstr(base[offset]));
+#endif
+ delta = 2 + (CAN_COMPRESS(val) ? 1 : 4);
+ }
+ return(offset + delta);
+}
+
+inline unsigned SKIP_PUSH_REG(PTR_CBYTE base, unsigned offset)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // Confirm it is a push instruction
+ _ASSERTE(CheckInstrBytePattern(base[offset] & 0xF8, 0x50, base[offset]));
+ return(offset + 1);
+}
+
+inline unsigned SKIP_POP_REG(PTR_CBYTE base, unsigned offset)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // Confirm it is a pop instruction
+ _ASSERTE(CheckInstrBytePattern(base[offset] & 0xF8, 0x58, base[offset]));
+ return(offset + 1);
+}
+
+inline unsigned SKIP_MOV_REG_REG(PTR_CBYTE base, unsigned offset)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // Confirm it is a move instruction
+ // Note that only the first byte may have been stomped on by IsMarkerInstr()
+ // So we can check the second byte directly
+ _ASSERTE(CheckInstrBytePattern(base[offset] & 0xFD, 0x89, base[offset]) &&
+ (base[offset+1] & 0xC0) == 0xC0);
+ return(offset + 2);
+}
+
+inline unsigned SKIP_LEA_ESP_EBP(int val, PTR_CBYTE base, unsigned offset)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+#ifdef _DEBUG
+ // Confirm it is the right instruction
+ // Note that only the first byte may have been stomped on by IsMarkerInstr()
+ // So we can check the second byte directly
+ WORD wOpcode = *(PTR_WORD)base;
+ _ASSERTE((CheckInstrWord(wOpcode, X86_INSTR_w_LEA_ESP_EBP_BYTE_OFFSET) &&
+ (val == *(PTR_SBYTE)(base+2)) &&
+ CAN_COMPRESS(val)) ||
+ (CheckInstrWord(wOpcode, X86_INSTR_w_LEA_ESP_EBP_DWORD_OFFSET) &&
+ (val == *(PTR_INT32)(base+2)) &&
+ !CAN_COMPRESS(val)));
+#endif
+
+ unsigned delta = 2 + (CAN_COMPRESS(val) ? 1 : 4);
+ return(offset + delta);
+}
+
+inline unsigned SKIP_LEA_EAX_ESP(int val, PTR_CBYTE base, unsigned offset)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+#ifdef _DEBUG
+ WORD wOpcode = *(PTR_WORD)(base + offset);
+ if (CheckInstrWord(wOpcode, X86_INSTR_w_LEA_EAX_ESP_BYTE_OFFSET))
+ {
+ _ASSERTE(val == *(PTR_SBYTE)(base + offset + 3));
+ _ASSERTE(CAN_COMPRESS(val));
+ }
+ else
+ {
+ _ASSERTE(CheckInstrWord(wOpcode, X86_INSTR_w_LEA_EAX_ESP_DWORD_OFFSET));
+ _ASSERTE(val == *(PTR_INT32)(base + offset + 3));
+ _ASSERTE(!CAN_COMPRESS(val));
+ }
+#endif
+
+ unsigned delta = 3 + (CAN_COMPRESS(-val) ? 1 : 4);
+ return(offset + delta);
+}
+
+inline unsigned SKIP_HELPER_CALL(PTR_CBYTE base, unsigned offset)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ unsigned delta;
+
+ if (CheckInstrByte(base[offset], X86_INSTR_CALL_REL32))
+ {
+ delta = 5;
+ }
+ else
+ {
+#ifdef _DEBUG
+ WORD wOpcode = *(PTR_WORD)(base+offset);
+ _ASSERTE(CheckInstrWord(wOpcode, X86_INSTR_W_CALL_IND_IMM));
+#endif
+ delta = 6;
+ }
+
+ return(offset+delta);
+}
+
+unsigned SKIP_ALLOC_FRAME(int size, PTR_CBYTE base, unsigned offset)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ _ASSERTE(size != 0);
+
+ if (size == sizeof(void*))
+ {
+ // JIT emits "push eax" instead of "sub esp,4"
+ return SKIP_PUSH_REG(base, offset);
+ }
+
+ const int STACK_PROBE_PAGE_SIZE_BYTES = 4096;
+ const int STACK_PROBE_BOUNDARY_THRESHOLD_BYTES = 1024;
+
+ int lastProbedLocToFinalSp = size;
+
+ if (size < STACK_PROBE_PAGE_SIZE_BYTES)
+ {
+ // sub esp, size
+ offset = SKIP_ARITH_REG(size, base, offset);
+ }
+ else
+ {
+ WORD wOpcode = *(PTR_WORD)(base + offset);
+
+ if (CheckInstrWord(wOpcode, X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX))
+ {
+ // In .NET 5.0 and earlier for frames that have size smaller than 0x3000 bytes
+ // JIT emits one or two 'test eax, [esp-dwOffset]' instructions before adjusting the stack pointer.
+ _ASSERTE(size < 0x3000);
+
+ // test eax, [esp-0x1000]
+ offset += 7;
+ lastProbedLocToFinalSp -= 0x1000;
+
+ if (size >= 0x2000)
+ {
+#ifdef _DEBUG
+ wOpcode = *(PTR_WORD)(base + offset);
+ _ASSERTE(CheckInstrWord(wOpcode, X86_INSTR_w_TEST_ESP_DWORD_OFFSET_EAX));
+#endif
+ //test eax, [esp-0x2000]
+ offset += 7;
+ lastProbedLocToFinalSp -= 0x1000;
+ }
+
+ // sub esp, size
+ offset = SKIP_ARITH_REG(size, base, offset);
+ }
+ else
+ {
+ bool pushedStubParam = false;
+
+ if (CheckInstrByte(base[offset], X86_INSTR_PUSH_EAX))
+ {
+ // push eax
+ offset = SKIP_PUSH_REG(base, offset);
+ pushedStubParam = true;
+ }
+
+ if (CheckInstrByte(base[offset], X86_INSTR_XOR))
+ {
+ // In .NET Core 3.1 and earlier for frames that have size greater than or equal to 0x3000 bytes
+ // JIT emits the following loop.
+ _ASSERTE(size >= 0x3000);
+
+ offset += 2;
+ // xor eax, eax 2
+ // [nop] 0-3
+ // loop:
+ // test [esp + eax], eax 3
+ // sub eax, 0x1000 5
+ // cmp eax, -size 5
+ // jge loop 2
+
+ // R2R images that support ReJIT may have extra nops we need to skip over.
+ while (offset < 5)
+ {
+ if (CheckInstrByte(base[offset], X86_INSTR_NOP))
+ {
+ offset++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ offset += 15;
+
+ if (pushedStubParam)
+ {
+ // pop eax
+ offset = SKIP_POP_REG(base, offset);
+ }
+
+ // sub esp, size
+ return SKIP_ARITH_REG(size, base, offset);
+ }
+ else
+ {
+ // In .NET 5.0 and later JIT emits a call to JIT_StackProbe helper.
+
+ if (pushedStubParam)
+ {
+ // lea eax, [esp-size+4]
+ offset = SKIP_LEA_EAX_ESP(-size + 4, base, offset);
+ // call JIT_StackProbe
+ offset = SKIP_HELPER_CALL(base, offset);
+ // pop eax
+ offset = SKIP_POP_REG(base, offset);
+ // sub esp, size
+ return SKIP_ARITH_REG(size, base, offset);
+ }
+ else
+ {
+ // lea eax, [esp-size]
+ offset = SKIP_LEA_EAX_ESP(-size, base, offset);
+ // call JIT_StackProbe
+ offset = SKIP_HELPER_CALL(base, offset);
+ // mov esp, eax
+ return SKIP_MOV_REG_REG(base, offset);
+ }
+ }
+ }
+ }
+
+ if (lastProbedLocToFinalSp + STACK_PROBE_BOUNDARY_THRESHOLD_BYTES > STACK_PROBE_PAGE_SIZE_BYTES)
+ {
+#ifdef _DEBUG
+ WORD wOpcode = *(PTR_WORD)(base + offset);
+ _ASSERTE(CheckInstrWord(wOpcode, X86_INSTR_w_TEST_ESP_EAX));
+#endif
+ // test [esp], eax
+ offset += 3;
+ }
+
+ return offset;
+}
+
+/*****************************************************************************/
+
+const RegMask CALLEE_SAVED_REGISTERS_MASK[] =
+{
+ RM_EDI, // first register to be pushed
+ RM_ESI,
+ RM_EBX,
+ RM_EBP // last register to be pushed
+};
+
+static void SetLocation(PREGDISPLAY pRD, int ind, PDWORD loc)
+{
+#if defined(FEATURE_NATIVEAOT)
+ static const SIZE_T OFFSET_OF_CALLEE_SAVED_REGISTERS[] =
+ {
+ offsetof(REGDISPLAY, pRdi), // first register to be pushed
+ offsetof(REGDISPLAY, pRsi),
+ offsetof(REGDISPLAY, pRbx),
+ offsetof(REGDISPLAY, pRbp), // last register to be pushed
+ };
+
+ SIZE_T offsetOfRegPtr = OFFSET_OF_CALLEE_SAVED_REGISTERS[ind];
+ *(LPVOID*)(PBYTE(pRD) + offsetOfRegPtr) = loc;
+#elif defined(FEATURE_EH_FUNCLETS)
+ static const SIZE_T OFFSET_OF_CALLEE_SAVED_REGISTERS[] =
+ {
+ offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Edi), // first register to be pushed
+ offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Esi),
+ offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Ebx),
+ offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Ebp), // last register to be pushed
+ };
+
+ SIZE_T offsetOfRegPtr = OFFSET_OF_CALLEE_SAVED_REGISTERS[ind];
+ *(LPVOID*)(PBYTE(pRD->pCurrentContextPointers) + offsetOfRegPtr) = loc;
+#else
+ static const SIZE_T OFFSET_OF_CALLEE_SAVED_REGISTERS[] =
+ {
+ offsetof(REGDISPLAY, pEdi), // first register to be pushed
+ offsetof(REGDISPLAY, pEsi),
+ offsetof(REGDISPLAY, pEbx),
+ offsetof(REGDISPLAY, pEbp), // last register to be pushed
+ };
+
+ SIZE_T offsetOfRegPtr = OFFSET_OF_CALLEE_SAVED_REGISTERS[ind];
+ *(LPVOID*)(PBYTE(pRD) + offsetOfRegPtr) = loc;
+#endif
+}
+
+/*****************************************************************************/
+
+void UnwindEspFrameEpilog(
+ PREGDISPLAY pContext,
+ hdrInfo * info,
+ PTR_CBYTE epilogBase,
+ bool updateAllRegs)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ _ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
+ _ASSERTE(!info->ebpFrame && !info->doubleAlign);
+ _ASSERTE(info->epilogOffs > 0);
+
+ int offset = 0;
+ unsigned ESP = pContext->SP;
+
+ if (info->rawStkSize)
+ {
+ if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
+ {
+ /* We have NOT executed the "ADD ESP, FrameSize",
+ so manually adjust stack pointer */
+ ESP += info->rawStkSize;
+ }
+
+ // We have already popped off the frame (excluding the callee-saved registers)
+
+ if (epilogBase[0] == X86_INSTR_POP_ECX)
+ {
+ // We may use "POP ecx" for doing "ADD ESP, 4",
+ // or we may not (in the case of JMP epilogs)
+ _ASSERTE(info->rawStkSize == sizeof(void*));
+ offset = SKIP_POP_REG(epilogBase, offset);
+ }
+ else
+ {
+ // "add esp, rawStkSize"
+ offset = SKIP_ARITH_REG(info->rawStkSize, epilogBase, offset);
+ }
+ }
+
+ /* Remaining callee-saved regs are at ESP. Need to update
+ regsMask as well to exclude registers which have already been popped. */
+
+ const RegMask regsMask = info->savedRegMask;
+
+ /* Increment "offset" in steps to see which callee-saved
+ registers have already been popped */
+
+ for (unsigned i = ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--)
+ {
+ RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
+
+ if (!(regMask & regsMask))
+ continue;
+
+ if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
+ {
+ /* We have NOT yet popped off the register.
+ Get the value from the stack if needed */
+ if (updateAllRegs || (regMask == RM_EBP))
+ {
+ SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
+ }
+
+ /* Adjust ESP */
+ ESP += sizeof(void*);
+ }
+
+ offset = SKIP_POP_REG(epilogBase, offset);
+ }
+
+ //CEE_JMP generates an epilog similar to a normal CEE_RET epilog except for the last instruction
+ _ASSERTE(CheckInstrBytePattern(epilogBase[offset] & X86_INSTR_RET, X86_INSTR_RET, epilogBase[offset]) //ret
+ || CheckInstrBytePattern(epilogBase[offset], X86_INSTR_JMP_NEAR_REL32, epilogBase[offset]) //jmp ret32
+ || CheckInstrWord(*PTR_WORD(epilogBase + offset), X86_INSTR_w_JMP_FAR_IND_IMM)); //jmp [addr32]
+
+ /* Finally we can set pPC */
+ SetRegdisplayPCTAddr(pContext, (TADDR)ESP);
+
+ pContext->SP = ESP;
+}
+
+/*****************************************************************************/
+
+void UnwindEbpDoubleAlignFrameEpilog(
+ PREGDISPLAY pContext,
+ hdrInfo * info,
+ PTR_CBYTE epilogBase,
+ bool updateAllRegs)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ _ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
+ _ASSERTE(info->ebpFrame || info->doubleAlign);
+
+ _ASSERTE(info->argSize < 0x10000); // "ret" only has a 2 byte operand
+
+ /* See how many instructions we have executed in the
+ epilog to determine which callee-saved registers
+ have already been popped */
+ int offset = 0;
+
+ unsigned ESP = pContext->SP;
+
+ bool needMovEspEbp = false;
+
+ if (info->doubleAlign)
+ {
+ // add esp, rawStkSize
+
+ if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
+ ESP += info->rawStkSize;
+ _ASSERTE(info->rawStkSize != 0);
+ offset = SKIP_ARITH_REG(info->rawStkSize, epilogBase, offset);
+
+ // We also need "mov esp, ebp" after popping the callee-saved registers
+ needMovEspEbp = true;
+ }
+ else
+ {
+ bool needLea = false;
+
+ if (info->localloc)
+ {
+ // ESP may be variable if a localloc was actually executed. We will reset it.
+ // lea esp, [ebp-calleeSavedRegs]
+
+ needLea = true;
+ }
+ else if (info->savedRegsCountExclFP == 0)
+ {
+ // We will just generate "mov esp, ebp" and be done with it.
+
+ if (info->rawStkSize != 0)
+ {
+ needMovEspEbp = true;
+ }
+ }
+ else if (info->rawStkSize == 0)
+ {
+ // do nothing before popping the callee-saved registers
+ }
+ else if (info->rawStkSize == sizeof(void*))
+ {
+ // "pop ecx" will make ESP point to the callee-saved registers
+ if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
+ ESP += sizeof(void*);
+ offset = SKIP_POP_REG(epilogBase, offset);
+ }
+ else
+ {
+ // We need to make ESP point to the callee-saved registers
+ // lea esp, [ebp-calleeSavedRegs]
+
+ needLea = true;
+ }
+
+ if (needLea)
+ {
+ // lea esp, [ebp-calleeSavedRegs]
+
+ unsigned calleeSavedRegsSize = info->savedRegsCountExclFP * sizeof(void*);
+
+ if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
+ ESP = GetRegdisplayFP(pContext) - calleeSavedRegsSize;
+
+ offset = SKIP_LEA_ESP_EBP(-int(calleeSavedRegsSize), epilogBase, offset);
+ }
+ }
+
+ for (unsigned i = STRING_LENGTH(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--)
+ {
+ RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
+ _ASSERTE(regMask != RM_EBP);
+
+ if ((info->savedRegMask & regMask) == 0)
+ continue;
+
+ if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
+ {
+ if (updateAllRegs)
+ {
+ SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
+ }
+ ESP += sizeof(void*);
+ }
+
+ offset = SKIP_POP_REG(epilogBase, offset);
+ }
+
+ if (needMovEspEbp)
+ {
+ if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
+ ESP = GetRegdisplayFP(pContext);
+
+ offset = SKIP_MOV_REG_REG(epilogBase, offset);
+ }
+
+ // Have we executed the pop EBP?
+ if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
+ {
+ pContext->SetEbpLocation(PTR_DWORD(TADDR(ESP)));
+ ESP += sizeof(void*);
+ }
+ offset = SKIP_POP_REG(epilogBase, offset);
+
+ SetRegdisplayPCTAddr(pContext, (TADDR)ESP);
+
+ pContext->SP = ESP;
+}
+
+inline SIZE_T GetStackParameterSize(hdrInfo * info)
+{
+ SUPPORTS_DAC;
+ return (info->varargs ? 0 : info->argSize); // Note varargs is caller-popped
+}
+
+//****************************************************************************
+// This is the value ESP is incremented by on doing a "return"
+
+inline SIZE_T ESPIncrOnReturn(hdrInfo * info)
+{
+ SUPPORTS_DAC;
+ return sizeof(void *) + // pop off the return address
+ GetStackParameterSize(info);
+}
+
+/*****************************************************************************/
+
+void UnwindEpilog(
+ PREGDISPLAY pContext,
+ hdrInfo * info,
+ PTR_CBYTE epilogBase,
+ bool updateAllRegs)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+ _ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
+ // _ASSERTE(flags & ActiveStackFrame); // Wont work for thread death
+ _ASSERTE(info->epilogOffs > 0);
+
+ if (info->ebpFrame || info->doubleAlign)
+ {
+ UnwindEbpDoubleAlignFrameEpilog(pContext, info, epilogBase, updateAllRegs);
+ }
+ else
+ {
+ UnwindEspFrameEpilog(pContext, info, epilogBase, updateAllRegs);
+ }
+
+#ifdef _DEBUG
+ if (updateAllRegs)
+ TRASH_CALLEE_UNSAVED_REGS(pContext);
+#endif
+
+ /* Now adjust stack pointer */
+
+ pContext->SP += ESPIncrOnReturn(info);
+}
+
+/*****************************************************************************/
+
+void UnwindEspFrameProlog(
+ PREGDISPLAY pContext,
+ hdrInfo * info,
+ PTR_CBYTE methodStart,
+ bool updateAllRegs)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ /* we are in the middle of the prolog */
+ _ASSERTE(info->prologOffs != hdrInfo::NOT_IN_PROLOG);
+ _ASSERTE(!info->ebpFrame && !info->doubleAlign);
+
+ unsigned offset = 0;
+
+#ifdef _DEBUG
+ // If the first two instructions are 'nop, int3', then we will
+ // assume that is from a JitHalt operation and skip past it
+ if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
+ {
+ offset += 2;
+ }
+#endif
+
+ const DWORD curOffs = info->prologOffs;
+ unsigned ESP = pContext->SP;
+
+ // Find out how many callee-saved regs have already been pushed
+
+ unsigned regsMask = RM_NONE;
+ PTR_DWORD savedRegPtr = PTR_DWORD((TADDR)ESP);
+
+ for (unsigned i = 0; i < ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i++)
+ {
+ RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
+
+ if (!(info->savedRegMask & regMask))
+ continue;
+
+ if (InstructionAlreadyExecuted(offset, curOffs))
+ {
+ ESP += sizeof(void*);
+ regsMask |= regMask;
+ }
+
+ offset = SKIP_PUSH_REG(methodStart, offset);
+ }
+
+ if (info->rawStkSize)
+ {
+ offset = SKIP_ALLOC_FRAME(info->rawStkSize, methodStart, offset);
+
+ // Note that this assumes that only the last instruction in SKIP_ALLOC_FRAME
+ // actually updates ESP
+ if (InstructionAlreadyExecuted(offset, curOffs + 1))
+ {
+ savedRegPtr += (info->rawStkSize / sizeof(DWORD));
+ ESP += info->rawStkSize;
+ }
+ }
+
+ //
+ // Stack probe checks here
+ //
+
+ // Poison the value, we don't set it properly at the end of the prolog
+#ifdef _DEBUG
+ offset = 0xCCCCCCCC;
+#endif
+
+ // Always restore EBP
+ if (regsMask & RM_EBP)
+ pContext->SetEbpLocation(savedRegPtr++);
+
+ if (updateAllRegs)
+ {
+ if (regsMask & RM_EBX)
+ pContext->SetEbxLocation(savedRegPtr++);
+ if (regsMask & RM_ESI)
+ pContext->SetEsiLocation(savedRegPtr++);
+ if (regsMask & RM_EDI)
+ pContext->SetEdiLocation(savedRegPtr++);
+
+ TRASH_CALLEE_UNSAVED_REGS(pContext);
+ }
+
+#if 0
+// NOTE:
+// THIS IS ONLY TRUE IF PROLOGSIZE DOES NOT INCLUDE REG-VAR INITIALIZATION !!!!
+//
+ /* there is (potentially) only one additional
+ instruction in the prolog, (push ebp)
+ but if we would have been passed that instruction,
+ info->prologOffs would be hdrInfo::NOT_IN_PROLOG!
+ */
+ _ASSERTE(offset == info->prologOffs);
+#endif
+
+ pContext->SP = ESP;
+}
+
+/*****************************************************************************/
+
+void UnwindEspFrame(
+ PREGDISPLAY pContext,
+ hdrInfo * info,
+ PTR_CBYTE table,
+ PTR_CBYTE methodStart,
+ DWORD curOffs,
+ unsigned flags)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ _ASSERTE(!info->ebpFrame && !info->doubleAlign);
+ _ASSERTE(info->epilogOffs == hdrInfo::NOT_IN_EPILOG);
+
+ unsigned ESP = pContext->SP;
+
+
+ if (info->prologOffs != hdrInfo::NOT_IN_PROLOG)
+ {
+ if (info->prologOffs != 0) // Do nothing for the very start of the method
+ {
+ UnwindEspFrameProlog(pContext, info, methodStart, flags);
+ ESP = pContext->SP;
+ }
+ }
+ else
+ {
+ /* we are past the prolog, ESP has been set above */
+
+ // Are there any arguments pushed on the stack?
+
+ ESP += GetPushedArgSize(info, table, curOffs);
+
+ ESP += info->rawStkSize;
+
+ const RegMask regsMask = info->savedRegMask;
+
+ for (unsigned i = ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--)
+ {
+ RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
+
+ if ((regMask & regsMask) == 0)
+ continue;
+
+ SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
+
+ ESP += sizeof(unsigned);
+ }
+ }
+
+ /* we can now set the (address of the) return address */
+
+ SetRegdisplayPCTAddr(pContext, (TADDR)ESP);
+
+ /* Now adjust stack pointer */
+
+ pContext->SP = ESP + ESPIncrOnReturn(info);
+}
+
+
+/*****************************************************************************/
+
+void UnwindEbpDoubleAlignFrameProlog(
+ PREGDISPLAY pContext,
+ hdrInfo * info,
+ PTR_CBYTE methodStart,
+ bool updateAllRegs)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ _ASSERTE(info->prologOffs != hdrInfo::NOT_IN_PROLOG);
+ _ASSERTE(info->ebpFrame || info->doubleAlign);
+
+ DWORD offset = 0;
+
+#ifdef _DEBUG
+ // If the first two instructions are 'nop, int3', then we will
+ // assume that is from a JitHalt operation and skip past it
+ if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
+ {
+ offset += 2;
+ }
+#endif
+
+ /* Check for the case where EBP has not been updated yet. */
+
+ const DWORD curOffs = info->prologOffs;
+
+ // If we have still not excecuted "push ebp; mov ebp, esp", then we need to
+ // report the frame relative to ESP
+
+ if (!InstructionAlreadyExecuted(offset + 1, curOffs))
+ {
+ _ASSERTE(CheckInstrByte(methodStart [offset], X86_INSTR_PUSH_EBP) ||
+ CheckInstrWord(*PTR_WORD(methodStart + offset), X86_INSTR_W_MOV_EBP_ESP) ||
+ CheckInstrByte(methodStart [offset], X86_INSTR_JMP_NEAR_REL32)); // a rejit jmp-stamp
+
+ /* If we're past the "push ebp", adjust ESP to pop EBP off */
+
+ if (curOffs == (offset + 1))
+ pContext->SP += sizeof(TADDR);
+
+ /* Stack pointer points to return address */
+
+ SetRegdisplayPCTAddr(pContext, (TADDR)pContext->SP);
+
+ /* EBP and callee-saved registers still have the correct value */
+
+ return;
+ }
+
+ // We are atleast after the "push ebp; mov ebp, esp"
+
+ offset = SKIP_MOV_REG_REG(methodStart,
+ SKIP_PUSH_REG(methodStart, offset));
+
+ /* At this point, EBP has been set up. The caller's ESP and the return value
+ can be determined using EBP. Since we are still in the prolog,
+ we need to know our exact location to determine the callee-saved registers */
+
+ const unsigned curEBP = GetRegdisplayFP(pContext);
+
+ if (updateAllRegs)
+ {
+ PTR_DWORD pSavedRegs = PTR_DWORD((TADDR)curEBP);
+
+ /* make sure that we align ESP just like the method's prolog did */
+ if (info->doubleAlign)
+ {
+ // "and esp,-8"
+ offset = SKIP_ARITH_REG(-8, methodStart, offset);
+ if (curEBP & 0x04)
+ {
+ pSavedRegs--;
+#ifdef _DEBUG
+ if (dspPtr) printf("EnumRef: dblalign ebp: %08X\n", curEBP);
+#endif
+ }
+ }
+
+ /* Increment "offset" in steps to see which callee-saved
+ registers have been pushed already */
+
+ for (unsigned i = 0; i < STRING_LENGTH(CALLEE_SAVED_REGISTERS_MASK); i++)
+ {
+ RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
+ _ASSERTE(regMask != RM_EBP);
+
+ if ((info->savedRegMask & regMask) == 0)
+ continue;
+
+ if (InstructionAlreadyExecuted(offset, curOffs))
+ {
+ SetLocation(pContext, i, PTR_DWORD(--pSavedRegs));
+ }
+
+ // "push reg"
+ offset = SKIP_PUSH_REG(methodStart, offset) ;
+ }
+
+ TRASH_CALLEE_UNSAVED_REGS(pContext);
+ }
+
+ /* The caller's saved EBP is pointed to by our EBP */
+
+ pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP));
+ pContext->SP = DWORD((TADDR)(curEBP + sizeof(void *)));
+
+ /* Stack pointer points to return address */
+
+ SetRegdisplayPCTAddr(pContext, (TADDR)pContext->SP);
+}
+
+/*****************************************************************************/
+
+bool UnwindEbpDoubleAlignFrame(
+ PREGDISPLAY pContext,
+ hdrInfo *info,
+ PTR_CBYTE table,
+ PTR_CBYTE methodStart,
+ DWORD curOffs,
+ IN_EH_FUNCLETS_COMMA(PTR_CBYTE funcletStart)
+ IN_EH_FUNCLETS_COMMA(bool isFunclet)
+ bool updateAllRegs)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ _ASSERTE(info->ebpFrame || info->doubleAlign);
+
+ const unsigned curESP = pContext->SP;
+ const unsigned curEBP = GetRegdisplayFP(pContext);
+
+ /* First check if we are in a filter (which is obviously after the prolog) */
+
+ if (info->handlers && info->prologOffs == hdrInfo::NOT_IN_PROLOG)
+ {
+ TADDR baseSP;
+
+#ifdef FEATURE_EH_FUNCLETS
+ // Funclets' frame pointers(EBP) are always restored so they can access to main function's local variables.
+ // Therefore the value of EBP is invalid for unwinder so we should use ESP instead.
+ // TODO If funclet frame layout is changed from CodeGen::genFuncletProlog() and genFuncletEpilog(),
+ // we need to change here accordingly. It is likely to have changes when introducing PSPSym.
+ // TODO Currently we assume that ESP of funclet frames is always fixed but actually it could change.
+ if (isFunclet)
+ {
+ baseSP = curESP;
+ // Set baseSP as initial SP
+ baseSP += GetPushedArgSize(info, table, curOffs);
+
+#ifdef UNIX_X86_ABI
+ // 16-byte stack alignment padding (allocated in genFuncletProlog)
+ // Current funclet frame layout (see CodeGen::genFuncletProlog() and genFuncletEpilog()):
+ // prolog: sub esp, 12
+ // epilog: add esp, 12
+ // ret
+ // SP alignment padding should be added for all instructions except the first one and the last one.
+ // Epilog may not exist (unreachable), so we need to check the instruction code.
+ if (funcletStart != methodStart + curOffs && methodStart[curOffs] != X86_INSTR_RETN)
+ baseSP += 12;
+#endif
+
+ SetRegdisplayPCTAddr(pContext, (TADDR)baseSP);
+
+ pContext->SP = (DWORD)(baseSP + sizeof(TADDR));
+
+ return true;
+ }
+#else // FEATURE_EH_FUNCLETS
+
+ FrameType frameType = GetHandlerFrameInfo(info, curEBP,
+ curESP, (DWORD) IGNORE_VAL,
+ &baseSP);
+
+ /* If we are in a filter, we only need to unwind the funclet stack.
+ For catches/finallies, the normal handling will
+ cause the frame to be unwound all the way up to ebp skipping
+ other frames above it. This is OK, as those frames will be
+ dead. Also, the EE will detect that this has happened and it
+ will handle any EE frames correctly.
+ */
+
+ if (frameType == FR_INVALID)
+ {
+ return false;
+ }
+
+ if (frameType == FR_FILTER)
+ {
+ SetRegdisplayPCTAddr(pContext, (TADDR)baseSP);
+
+ pContext->SP = (DWORD)(baseSP + sizeof(TADDR));
+
+ // pContext->pEbp = same as before;
+
+#ifdef _DEBUG
+ /* The filter has to be called by the VM. So we dont need to
+ update callee-saved registers.
+ */
+
+ if (updateAllRegs)
+ {
+ static DWORD s_badData = 0xDEADBEEF;
+
+ pContext->SetEaxLocation(&s_badData);
+ pContext->SetEcxLocation(&s_badData);
+ pContext->SetEdxLocation(&s_badData);
+
+ pContext->SetEbxLocation(&s_badData);
+ pContext->SetEsiLocation(&s_badData);
+ pContext->SetEdiLocation(&s_badData);
+ }
+#endif
+
+ return true;
+ }
+#endif // !FEATURE_EH_FUNCLETS
+ }
+
+ //
+ // Prolog of an EBP method
+ //
+
+ if (info->prologOffs != hdrInfo::NOT_IN_PROLOG)
+ {
+ UnwindEbpDoubleAlignFrameProlog(pContext, info, methodStart, updateAllRegs);
+
+ /* Now adjust stack pointer. */
+
+ pContext->SP += ESPIncrOnReturn(info);
+ return true;
+ }
+
+ if (updateAllRegs)
+ {
+ // Get to the first callee-saved register
+ PTR_DWORD pSavedRegs = PTR_DWORD((TADDR)curEBP);
+
+ if (info->doubleAlign && (curEBP & 0x04))
+ pSavedRegs--;
+
+ for (unsigned i = 0; i < STRING_LENGTH(CALLEE_SAVED_REGISTERS_MASK); i++)
+ {
+ RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
+ if ((info->savedRegMask & regMask) == 0)
+ continue;
+
+ SetLocation(pContext, i, --pSavedRegs);
+ }
+ }
+
+ /* The caller's ESP will be equal to EBP + retAddrSize + argSize. */
+
+ pContext->SP = (DWORD)(curEBP + sizeof(curEBP) + ESPIncrOnReturn(info));
+
+ /* The caller's saved EIP is right after our EBP */
+
+ SetRegdisplayPCTAddr(pContext, (TADDR)curEBP + RETURN_ADDR_OFFS * sizeof(TADDR));
+
+ /* The caller's saved EBP is pointed to by our EBP */
+
+ pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP));
+ return true;
+}
+
+bool UnwindStackFrameX86(PREGDISPLAY pContext,
+ PTR_CBYTE methodStart,
+ DWORD curOffs,
+ hdrInfo * info,
+ PTR_CBYTE table,
+ IN_EH_FUNCLETS_COMMA(PTR_CBYTE funcletStart)
+ IN_EH_FUNCLETS_COMMA(bool isFunclet)
+ bool updateAllRegs)
+{
+ if (info->epilogOffs != hdrInfo::NOT_IN_EPILOG)
+ {
+ /*---------------------------------------------------------------------
+ * First, handle the epilog
+ */
+
+ PTR_CBYTE epilogBase = methodStart + (curOffs - info->epilogOffs);
+ UnwindEpilog(pContext, info, epilogBase, updateAllRegs);
+ }
+ else if (!info->ebpFrame && !info->doubleAlign)
+ {
+ /*---------------------------------------------------------------------
+ * Now handle ESP frames
+ */
+
+ UnwindEspFrame(pContext, info, table, methodStart, curOffs, updateAllRegs);
+ return true;
+ }
+ else
+ {
+ /*---------------------------------------------------------------------
+ * Now we know that have an EBP frame
+ */
+
+ if (!UnwindEbpDoubleAlignFrame(pContext,
+ info,
+ table,
+ methodStart,
+ curOffs,
+ IN_EH_FUNCLETS_COMMA(funcletStart)
+ IN_EH_FUNCLETS_COMMA(isFunclet)
+ updateAllRegs))
+ return false;
+ }
+
+ // TODO [DAVBR]: For the full fix for VsWhidbey 450273, all the below
+ // may be uncommented once isLegalManagedCodeCaller works properly
+ // with non-return address inputs, and with non-DEBUG builds
+ /*
+ // Ensure isLegalManagedCodeCaller succeeds for speculative stackwalks.
+ // (We just assert this below for non-speculative stackwalks.)
+ //
+ FAIL_IF_SPECULATIVE_WALK(isLegalManagedCodeCaller(GetControlPC(pContext)));
+ */
+
+ return true;
+}
+
+bool EnumGcRefsX86(PREGDISPLAY pContext,
+ PTR_CBYTE methodStart,
+ DWORD curOffs,
+ GCInfoToken gcInfoToken,
+ IN_EH_FUNCLETS_COMMA(PTR_CBYTE funcletStart)
+ IN_EH_FUNCLETS_COMMA(bool isFunclet)
+ IN_EH_FUNCLETS_COMMA(bool isFilterFunclet)
+ unsigned flags,
+ GCEnumCallback pCallBack,
+ LPVOID hCallBack)
+{
+#ifdef FEATURE_EH_FUNCLETS
+ if (flags & ParentOfFuncletStackFrame)
+ {
+ LOG((LF_GCROOTS, LL_INFO100000, "Not reporting this frame because it was already reported via another funclet.\n"));
+ return true;
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ unsigned EBP = GetRegdisplayFP(pContext);
+ unsigned ESP = pContext->SP;
+
+ unsigned ptrOffs;
+
+ unsigned count;
+
+ hdrInfo info;
+ PTR_CBYTE table = PTR_CBYTE(gcInfoToken.Info);
+#if 0
+ printf("EECodeManager::EnumGcRefs - EIP = %08x ESP = %08x offset = %x GC Info is at %08x\n", *pContext->pPC, ESP, curOffs, table);
+#endif
+
+
+ /* Extract the necessary information from the info block header */
+
+ table += DecodeGCHdrInfo(gcInfoToken,
+ curOffs,
+ &info);
+
+ _ASSERTE( curOffs <= info.methodSize);
+
+#ifdef _DEBUG
+// if ((gcInfoToken.Info == (void*)0x37760d0) && (curOffs == 0x264))
+// __asm int 3;
+
+ if (trEnumGCRefs) {
+ static unsigned lastESP = 0;
+ unsigned diffESP = ESP - lastESP;
+ if (diffESP > 0xFFFF) {
+ printf("------------------------------------------------------\n");
+ }
+ lastESP = ESP;
+ printf("EnumGCRefs [%s][%s] at %s.%s + 0x%03X:\n",
+ info.ebpFrame?"ebp":" ",
+ info.interruptible?"int":" ",
+ "UnknownClass","UnknownMethod", curOffs);
+ fflush(stdout);
+ }
+#endif
+
+ /* Are we in the prolog or epilog of the method? */
+
+ if (info.prologOffs != hdrInfo::NOT_IN_PROLOG ||
+ info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
+ {
+
+#if !DUMP_PTR_REFS
+ // Under normal circumstances the system will not suspend a thread
+ // if it is in the prolog or epilog of the function. However ThreadAbort
+ // exception or stack overflows can cause EH to happen in a prolog.
+ // Once in the handler, a GC can happen, so we can get to this code path.
+ // However since we are tearing down this frame, we don't need to report
+ // anything and we can simply return.
+
+ _ASSERTE(flags & ExecutionAborted);
+#endif
+ return true;
+ }
+
+#ifdef _DEBUG
+#define CHK_AND_REPORT_REG(reg, doIt, iptr, regName) \
+ if (doIt) \
+ { \
+ if (dspPtr) \
+ printf(" Live pointer register %s: ", #regName); \
+ pCallBack(hCallBack, \
+ (OBJECTREF*)(pContext->Get##regName##Location()), \
+ (iptr ? GC_CALL_INTERIOR : 0) \
+ | CHECK_APP_DOMAIN \
+ DAC_ARG(DacSlotLocation(reg, 0, false))); \
+ }
+#else // !_DEBUG
+#define CHK_AND_REPORT_REG(reg, doIt, iptr, regName) \
+ if (doIt) \
+ pCallBack(hCallBack, \
+ (OBJECTREF*)(pContext->Get##regName##Location()), \
+ (iptr ? GC_CALL_INTERIOR : 0) \
+ | CHECK_APP_DOMAIN \
+ DAC_ARG(DacSlotLocation(reg, 0, false)));
+
+#endif // _DEBUG
+
+ /* What kind of a frame is this ? */
+
+#ifndef FEATURE_EH_FUNCLETS
+ FrameType frameType = FR_NORMAL;
+ TADDR baseSP = 0;
+
+ if (info.handlers)
+ {
+ _ASSERTE(info.ebpFrame);
+
+ bool hasInnerFilter, hadInnerFilter;
+ frameType = GetHandlerFrameInfo(&info, EBP,
+ ESP, (DWORD) IGNORE_VAL,
+ &baseSP, NULL,
+ &hasInnerFilter, &hadInnerFilter);
+ _ASSERTE(frameType != FR_INVALID);
+
+ /* If this is the parent frame of a filter which is currently
+ executing, then the filter would have enumerated the frame using
+ the filter PC.
+ */
+
+ if (hasInnerFilter)
+ return true;
+
+ /* If are in a try and we had a filter execute, we may have reported
+ GC refs from the filter (and not using the try's offset). So
+ we had better use the filter's end offset, as the try is
+ effectively dead and its GC ref's would be stale */
+
+ if (hadInnerFilter)
+ {
+ PTR_TADDR pFirstBaseSPslot = GetFirstBaseSPslotPtr(EBP, &info);
+ curOffs = (unsigned)pFirstBaseSPslot[1] - 1;
+ _ASSERTE(curOffs < info.methodSize);
+
+ /* Extract the necessary information from the info block header */
+
+ table = PTR_CBYTE(gcInfoToken.Info);
+
+ table += DecodeGCHdrInfo(gcInfoToken,
+ curOffs,
+ &info);
+ }
+ }
+#endif
+
+ bool willContinueExecution = !(flags & ExecutionAborted);
+ unsigned pushedSize = 0;
+
+ /* if we have been interrupted we don't have to report registers/arguments
+ * because we are about to lose this context anyway.
+ * Alas, if we are in a ebp-less method we have to parse the table
+ * in order to adjust ESP.
+ *
+ * Note that we report "this" for all methods, even if
+ * noncontinuable, because of the off chance they may be
+ * synchronized and we have to release the monitor on unwind. This
+ * could conceivably be optimized, but it turns out to be more
+ * expensive to check whether we're synchronized (which involves
+ * consulting metadata) than to just report "this" all the time in
+ * our most important scenarios.
+ */
+
+ if (info.interruptible)
+ {
+ unsigned curOffsRegs = curOffs;
+
+ // Don't decrement curOffsRegs when it is 0, as it is an unsigned and will wrap to MAX_UINT
+ //
+ if (curOffsRegs > 0)
+ {
+ // If we are not on the active stack frame, we need to report gc registers
+ // that are live before the call. The reason is that the liveness of gc registers
+ // may change across a call to a method that does not return. In this case the instruction
+ // after the call may be a jump target and a register that didn't have a live gc pointer
+ // before the call may have a live gc pointer after the jump. To make sure we report the
+ // registers that have live gc pointers before the call we subtract 1 from curOffs.
+ if ((flags & ActiveStackFrame) == 0)
+ {
+ // We are not the top most stack frame (i.e. the ActiveStackFrame)
+ curOffsRegs--; // decrement curOffsRegs
+ }
+ }
+
+ pushedSize = scanArgRegTableI(skipToArgReg(info, table), curOffsRegs, curOffs, &info);
+
+ RegMask regs = info.regMaskResult;
+ RegMask iregs = info.iregMaskResult;
+ ptrArgTP args = info.argMaskResult;
+ ptrArgTP iargs = info.iargMaskResult;
+
+ _ASSERTE((isZero(args) || pushedSize != 0) || info.ebpFrame);
+ _ASSERTE((args & iargs) == iargs);
+
+ /* now report registers and arguments if we are not interrupted */
+
+ if (willContinueExecution)
+ {
+
+ /* Propagate unsafed registers only in "current" method */
+ /* If this is not the active method, then the callee wil
+ * trash these registers, and so we wont need to report them */
+
+ if (flags & ActiveStackFrame)
+ {
+ CHK_AND_REPORT_REG(REGI_EAX, regs & RM_EAX, iregs & RM_EAX, Eax);
+ CHK_AND_REPORT_REG(REGI_ECX, regs & RM_ECX, iregs & RM_ECX, Ecx);
+ CHK_AND_REPORT_REG(REGI_EDX, regs & RM_EDX, iregs & RM_EDX, Edx);
+ }
+
+ CHK_AND_REPORT_REG(REGI_EBX, regs & RM_EBX, iregs & RM_EBX, Ebx);
+ CHK_AND_REPORT_REG(REGI_EBP, regs & RM_EBP, iregs & RM_EBP, Ebp);
+ CHK_AND_REPORT_REG(REGI_ESI, regs & RM_ESI, iregs & RM_ESI, Esi);
+ CHK_AND_REPORT_REG(REGI_EDI, regs & RM_EDI, iregs & RM_EDI, Edi);
+ _ASSERTE(!(regs & RM_ESP));
+
+ /* Report any pending pointer arguments */
+
+ DWORD * pPendingArgFirst; // points **AT** first parameter
+ if (!info.ebpFrame)
+ {
+ // -sizeof(void*) because we want to point *AT* first parameter
+ pPendingArgFirst = (DWORD *)(size_t)(ESP + pushedSize - sizeof(void*));
+ }
+ else
+ {
+ _ASSERTE(willContinueExecution);
+
+#ifdef FEATURE_EH_FUNCLETS
+ // Funclets' frame pointers(EBP) are always restored so they can access to main function's local variables.
+ // Therefore the value of EBP is invalid for unwinder so we should use ESP instead.
+ // See UnwindStackFrame for details.
+ if (isFunclet)
+ {
+ TADDR baseSP = ESP;
+ // Set baseSP as initial SP
+ baseSP += GetPushedArgSize(&info, table, curOffs);
+
+#ifdef UNIX_X86_ABI
+ // 16-byte stack alignment padding (allocated in genFuncletProlog)
+ // Current funclet frame layout (see CodeGen::genFuncletProlog() and genFuncletEpilog()):
+ // prolog: sub esp, 12
+ // epilog: add esp, 12
+ // ret
+ // SP alignment padding should be added for all instructions except the first one and the last one.
+ // Epilog may not exist (unreachable), so we need to check the instruction code.
+ if (funcletStart != methodStart + curOffs && methodStart[curOffs] != X86_INSTR_RETN)
+ baseSP += 12;
+#endif
+
+ // -sizeof(void*) because we want to point *AT* first parameter
+ pPendingArgFirst = (DWORD *)(size_t)(baseSP - sizeof(void*));
+ }
+#else // FEATURE_EH_FUNCLETS
+ if (info.handlers)
+ {
+ // -sizeof(void*) because we want to point *AT* first parameter
+ pPendingArgFirst = (DWORD *)(size_t)(baseSP - sizeof(void*));
+ }
+#endif
+ else if (info.localloc)
+ {
+ TADDR locallocBaseSP = *(DWORD *)(size_t)(EBP - GetLocallocSPOffset(&info));
+ // -sizeof(void*) because we want to point *AT* first parameter
+ pPendingArgFirst = (DWORD *)(size_t) (locallocBaseSP - sizeof(void*));
+ }
+ else
+ {
+ // Note that 'info.stackSize includes the size for pushing EBP, but EBP is pushed
+ // BEFORE EBP is set from ESP, thus (EBP - info.stackSize) actually points past
+ // the frame by one DWORD, and thus points *AT* the first parameter
+
+ pPendingArgFirst = (DWORD *)(size_t)(EBP - info.stackSize);
+ }
+ }
+
+ if (!isZero(args))
+ {
+ unsigned i = 0;
+ ptrArgTP b(1);
+ for (; !isZero(args) && (i < MAX_PTRARG_OFS); i += 1, b <<= 1)
+ {
+ if (intersect(args,b))
+ {
+ unsigned argAddr = (unsigned)(size_t)(pPendingArgFirst - i);
+ bool iptr = false;
+
+ setDiff(args, b);
+ if (intersect(iargs,b))
+ {
+ setDiff(iargs, b);
+ iptr = true;
+ }
+
+#ifdef _DEBUG
+ if (dspPtr)
+ {
+ printf(" Pushed ptr arg [E");
+ if (info.ebpFrame)
+ printf("BP-%02XH]: ", EBP - argAddr);
+ else
+ printf("SP+%02XH]: ", argAddr - ESP);
+ }
+#endif
+ _ASSERTE(true == GC_CALL_INTERIOR);
+ pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, (int)iptr | CHECK_APP_DOMAIN
+ DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
+ info.ebpFrame ? EBP - argAddr : argAddr - ESP,
+ true)));
+ }
+ }
+ }
+ }
+ else
+ {
+ // Is "this" enregistered. If so, report it as we might need to
+ // release the monitor for synchronized methods.
+ // Else, it is on the stack and will be reported below.
+
+ if (info.thisPtrResult != REGI_NA)
+ {
+ // Synchronized methods and methods satisfying
+ // MethodDesc::AcquiresInstMethodTableFromThis (i.e. those
+ // where "this" is reported in thisPtrResult) are
+ // not supported on value types.
+ _ASSERTE((regNumToMask(info.thisPtrResult) & info.iregMaskResult)== 0);
+
+ void * thisReg = getCalleeSavedReg(pContext, info.thisPtrResult);
+ pCallBack(hCallBack, (OBJECTREF *)thisReg, CHECK_APP_DOMAIN
+ DAC_ARG(DacSlotLocation(info.thisPtrResult, 0, false)));
+ }
+ }
+ }
+ else /* not interruptible */
+ {
+ pushedSize = scanArgRegTable(skipToArgReg(info, table), curOffs, &info);
+
+ RegMask regMask = info.regMaskResult;
+ RegMask iregMask = info.iregMaskResult;
+ ptrArgTP argMask = info.argMaskResult;
+ ptrArgTP iargMask = info.iargMaskResult;
+ unsigned argHnum = info.argHnumResult;
+ PTR_CBYTE argTab = info.argTabResult;
+
+ /* now report registers and arguments if we are not interrupted */
+
+ if (willContinueExecution)
+ {
+
+ /* Report all live pointer registers */
+
+ CHK_AND_REPORT_REG(REGI_EDI, regMask & RM_EDI, iregMask & RM_EDI, Edi);
+ CHK_AND_REPORT_REG(REGI_ESI, regMask & RM_ESI, iregMask & RM_ESI, Esi);
+ CHK_AND_REPORT_REG(REGI_EBX, regMask & RM_EBX, iregMask & RM_EBX, Ebx);
+ CHK_AND_REPORT_REG(REGI_EBP, regMask & RM_EBP, iregMask & RM_EBP, Ebp);
+
+ /* Esp cant be reported */
+ _ASSERTE(!(regMask & RM_ESP));
+ /* No callee-trashed registers */
+ _ASSERTE(!(regMask & RM_CALLEE_TRASHED));
+ /* EBP can't be reported unless we have an EBP-less frame */
+ _ASSERTE(!(regMask & RM_EBP) || !(info.ebpFrame));
+
+ /* Report any pending pointer arguments */
+
+ if (argTab != 0)
+ {
+ unsigned lowBits, stkOffs, argAddr, val;
+
+ // argMask does not fit in 32-bits
+ // thus arguments are reported via a table
+ // Both of these are very rare cases
+
+ do
+ {
+ val = fastDecodeUnsigned(argTab);
+
+ lowBits = val & OFFSET_MASK;
+ stkOffs = val & ~OFFSET_MASK;
+ _ASSERTE((lowBits == 0) || (lowBits == byref_OFFSET_FLAG));
+
+ argAddr = ESP + stkOffs;
+#ifdef _DEBUG
+ if (dspPtr)
+ printf(" Pushed %sptr arg at [ESP+%02XH]",
+ lowBits ? "iptr " : "", stkOffs);
+#endif
+ _ASSERTE(byref_OFFSET_FLAG == GC_CALL_INTERIOR);
+ pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, lowBits | CHECK_APP_DOMAIN
+ DAC_ARG(DacSlotLocation(REGI_ESP, stkOffs, true)));
+ }
+ while(--argHnum);
+
+ _ASSERTE(info.argTabResult + info.argTabBytes == argTab);
+ }
+ else
+ {
+ unsigned argAddr = ESP;
+
+ while (!isZero(argMask))
+ {
+ _ASSERTE(argHnum-- > 0);
+
+ if (toUnsigned(argMask) & 1)
+ {
+ bool iptr = false;
+
+ if (toUnsigned(iargMask) & 1)
+ iptr = true;
+#ifdef _DEBUG
+ if (dspPtr)
+ printf(" Pushed ptr arg at [ESP+%02XH]",
+ argAddr - ESP);
+#endif
+ _ASSERTE(true == GC_CALL_INTERIOR);
+ pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, (int)iptr | CHECK_APP_DOMAIN
+ DAC_ARG(DacSlotLocation(REGI_ESP, argAddr - ESP, true)));
+ }
+
+ argMask >>= 1;
+ iargMask >>= 1;
+ argAddr += 4;
+ }
+
+ }
+
+ }
+ else
+ {
+ // Is "this" enregistered. If so, report it as we will need to
+ // release the monitor. Else, it is on the stack and will be
+ // reported below.
+
+ // For partially interruptible code, info.thisPtrResult will be
+ // the last known location of "this". So the compiler needs to
+ // generate information which is correct at every point in the code,
+ // not just at call sites.
+
+ if (info.thisPtrResult != REGI_NA)
+ {
+ // Synchronized methods on value types are not supported
+ _ASSERTE((regNumToMask(info.thisPtrResult) & info.iregMaskResult)== 0);
+
+ void * thisReg = getCalleeSavedReg(pContext, info.thisPtrResult);
+ pCallBack(hCallBack, (OBJECTREF *)thisReg, CHECK_APP_DOMAIN
+ DAC_ARG(DacSlotLocation(info.thisPtrResult, 0, false)));
+ }
+ }
+
+ } //info.interruptible
+
+ /* compute the argument base (reference point) */
+
+ unsigned argBase;
+
+ if (info.ebpFrame)
+ argBase = EBP;
+ else
+ argBase = ESP + pushedSize;
+
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
+#endif
+
+ unsigned ptrAddr;
+ unsigned lowBits;
+
+
+ /* Process the untracked frame variable table */
+
+#if defined(FEATURE_EH_FUNCLETS) // funclets
+ // Filters are the only funclet that run during the 1st pass, and must have
+ // both the leaf and the parent frame reported. In order to avoid double
+ // reporting of the untracked variables, do not report them for the filter.
+ if (!isFilterFunclet)
+#endif // FEATURE_EH_FUNCLETS
+ {
+ count = info.untrackedCnt;
+ int lastStkOffs = 0;
+ while (count-- > 0)
+ {
+ int stkOffs = fastDecodeSigned(table);
+ stkOffs = lastStkOffs - stkOffs;
+ lastStkOffs = stkOffs;
+
+ _ASSERTE(0 == ~OFFSET_MASK % sizeof(void*));
+
+ lowBits = OFFSET_MASK & stkOffs;
+ stkOffs &= ~OFFSET_MASK;
+
+ ptrAddr = argBase + stkOffs;
+ if (info.doubleAlign && stkOffs >= int(info.stackSize - sizeof(void*))) {
+ // We encode the arguments as if they were ESP based variables even though they aren't
+ // If this frame would have ben an ESP based frame, This fake frame is one DWORD
+ // smaller than the real frame because it did not push EBP but the real frame did.
+ // Thus to get the correct EBP relative offset we have to adjust by info.stackSize-sizeof(void*)
+ ptrAddr = EBP + (stkOffs-(info.stackSize - sizeof(void*)));
+ }
+
+#ifdef _DEBUG
+ if (dspPtr)
+ {
+ printf(" Untracked %s%s local at [E",
+ (lowBits & pinned_OFFSET_FLAG) ? "pinned " : "",
+ (lowBits & byref_OFFSET_FLAG) ? "byref" : "");
+
+ int dspOffs = ptrAddr;
+ char frameType;
+
+ if (info.ebpFrame) {
+ dspOffs -= EBP;
+ frameType = 'B';
+ }
+ else {
+ dspOffs -= ESP;
+ frameType = 'S';
+ }
+
+ if (dspOffs < 0)
+ printf("%cP-%02XH]: ", frameType, -dspOffs);
+ else
+ printf("%cP+%02XH]: ", frameType, +dspOffs);
+ }
+#endif
+
+ _ASSERTE((pinned_OFFSET_FLAG == GC_CALL_PINNED) &&
+ (byref_OFFSET_FLAG == GC_CALL_INTERIOR));
+ pCallBack(hCallBack, (OBJECTREF*)(size_t)ptrAddr, lowBits | CHECK_APP_DOMAIN
+ DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
+ info.ebpFrame ? EBP - ptrAddr : ptrAddr - ESP,
+ true)));
+ }
+
+ }
+
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xCAFE);
+#endif
+
+ /* Process the frame variable lifetime table */
+ count = info.varPtrTableSize;
+
+ /* If we are not in the active method, we are currently pointing
+ * to the return address; at the return address stack variables
+ * can become dead if the call the last instruction of a try block
+ * and the return address is the jump around the catch block. Therefore
+ * we simply assume an offset inside of call instruction.
+ */
+
+ unsigned newCurOffs;
+
+ if (willContinueExecution)
+ {
+ newCurOffs = (flags & ActiveStackFrame) ? curOffs // after "call"
+ : curOffs-1; // inside "call"
+ }
+ else
+ {
+ /* However if ExecutionAborted, then this must be one of the
+ * ExceptionFrames. Handle accordingly
+ */
+ _ASSERTE(!(flags & AbortingCall) || !(flags & ActiveStackFrame));
+
+ newCurOffs = (flags & AbortingCall) ? curOffs-1 // inside "call"
+ : curOffs; // at faulting instr, or start of "try"
+ }
+
+ ptrOffs = 0;
+
+ while (count-- > 0)
+ {
+ int stkOffs;
+ unsigned begOffs;
+ unsigned endOffs;
+
+ stkOffs = fastDecodeUnsigned(table);
+ begOffs = ptrOffs + fastDecodeUnsigned(table);
+ endOffs = begOffs + fastDecodeUnsigned(table);
+
+ _ASSERTE(0 == ~OFFSET_MASK % sizeof(void*));
+
+ lowBits = OFFSET_MASK & stkOffs;
+ stkOffs &= ~OFFSET_MASK;
+
+ if (info.ebpFrame) {
+ stkOffs = -stkOffs;
+ _ASSERTE(stkOffs < 0);
+ }
+ else {
+ _ASSERTE(stkOffs >= 0);
+ }
+
+ ptrAddr = argBase + stkOffs;
+
+ /* Is this variable live right now? */
+
+ if (newCurOffs >= begOffs)
+ {
+ if (newCurOffs < endOffs)
+ {
+#ifdef _DEBUG
+ if (dspPtr) {
+ printf(" Frame %s%s local at [E",
+ (lowBits & byref_OFFSET_FLAG) ? "byref " : "",
+#ifndef FEATURE_EH_FUNCLETS
+ (lowBits & this_OFFSET_FLAG) ? "this-ptr" : "");
+#else
+ (lowBits & pinned_OFFSET_FLAG) ? "pinned" : "");
+#endif
+
+
+ int dspOffs = ptrAddr;
+ char frameType;
+
+ if (info.ebpFrame) {
+ dspOffs -= EBP;
+ frameType = 'B';
+ }
+ else {
+ dspOffs -= ESP;
+ frameType = 'S';
+ }
+
+ if (dspOffs < 0)
+ printf("%cP-%02XH]: ", frameType, -dspOffs);
+ else
+ printf("%cP+%02XH]: ", frameType, +dspOffs);
+ }
+#endif
+
+ unsigned flags = CHECK_APP_DOMAIN;
+#ifndef FEATURE_EH_FUNCLETS
+ // First Bit : byref
+ // Second Bit : this
+ // The second bit means `this` not `pinned`. So we ignore it.
+ flags |= lowBits & byref_OFFSET_FLAG;
+#else
+ // First Bit : byref
+ // Second Bit : pinned
+ // Both bits are valid
+ flags |= lowBits;
+#endif
+
+ _ASSERTE(byref_OFFSET_FLAG == GC_CALL_INTERIOR);
+ pCallBack(hCallBack, (OBJECTREF*)(size_t)ptrAddr, flags
+ DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
+ info.ebpFrame ? EBP - ptrAddr : ptrAddr - ESP,
+ true)));
+ }
+ }
+ // exit loop early if start of live range is beyond PC, as ranges are sorted by lower bound
+ else break;
+
+ ptrOffs = begOffs;
+ }
+
+
+#if VERIFY_GC_TABLES
+ _ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
+#endif
+
+#ifdef FEATURE_EH_FUNCLETS // funclets
+ //
+ // If we're in a funclet, we do not want to report the incoming varargs. This is
+ // taken care of by the parent method and the funclet should access those arguments
+ // by way of the parent method's stack frame.
+ //
+ if (isFunclet)
+ {
+ return true;
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ /* Are we a varargs function, if so we have to report all args
+ except 'this' (note that the GC tables created by the x86 jit
+ do not contain ANY arguments except 'this' (even if they
+ were statically declared */
+
+ if (info.varargs) {
+#ifdef FEATURE_NATIVEAOT
+ PORTABILITY_ASSERT("EnumGCRefs: VarArgs");
+#else
+ LOG((LF_GCINFO, LL_INFO100, "Reporting incoming vararg GC refs\n"));
+
+ PTR_BYTE argsStart;
+
+ if (info.ebpFrame || info.doubleAlign)
+ argsStart = PTR_BYTE((size_t)EBP) + 2* sizeof(void*); // pushed EBP and retAddr
+ else
+ argsStart = PTR_BYTE((size_t)argBase) + info.stackSize + sizeof(void*); // ESP + locals + retAddr
+
+#if defined(_DEBUG) && !defined(DACCESS_COMPILE)
+ // Note that I really want to say hCallBack is a GCCONTEXT, but this is pretty close
+ extern void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags);
+ _ASSERTE((void*) GcEnumObject == pCallBack);
+#endif
+ GCCONTEXT *pCtx = (GCCONTEXT *) hCallBack;
+
+ // For varargs, look up the signature using the varArgSig token passed on the stack
+ PTR_VASigCookie varArgSig = *PTR_PTR_VASigCookie(argsStart);
+
+ promoteVarArgs(argsStart, varArgSig, pCtx);
+#endif
+ }
+
+ return true;
+}