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; +}