From 91e6a2b847a3049ba8a2ede895d55a77bda354bc Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 6 Mar 2024 18:55:43 +0100 Subject: [PATCH] Reuse x86 GC and unwinding code in NativeAOT (#99109) * Verbatim code extraction from eetwain.cpp into gc_unwind_x86.inl * Reuse x86 GC and unwinding code in NativeAOT Extracts x86 unwinding code and EnumGCRefs from eetwain.h/cpp into gc_unwind_x86.h/inl files. The bulk of the code remains unchanged with the methods UnwindStackFrameX86 and EnumGcRefsX86 extracted from the original EECodeManager methods. CoffNativeCodeManager includes all the x86 GC decoders and includes gc_unwind_x86.inl for TARGET_X86. Missing methods are implemented in terms of UnwindStackFrameX86, EnumGcRefsX86, and DecodeGCHdrInfo. Funclet calling assembly helpers are fixed up to follow the same pattern as on other platform. * Remove assert --- src/coreclr/inc/bitvector.h | 2 + src/coreclr/inc/daccess.h | 2 +- src/coreclr/inc/eetwain.h | 121 +- src/coreclr/inc/gc_unwind_x86.h | 138 + src/coreclr/inc/gcinfo.h | 13 +- src/coreclr/inc/gcinfodecoder.h | 25 +- src/coreclr/inc/regdisp.h | 6 + .../nativeaot/Runtime/StackFrameIterator.cpp | 39 +- .../nativeaot/Runtime/i386/AsmMacros.inc | 5 +- .../nativeaot/Runtime/i386/AsmOffsetsCpu.h | 12 +- .../Runtime/i386/ExceptionHandling.asm | 50 +- .../nativeaot/Runtime/i386/GcProbe.asm | 33 +- .../nativeaot/Runtime/i386/WriteBarriers.asm | 79 +- src/coreclr/nativeaot/Runtime/regdisplay.h | 33 + src/coreclr/nativeaot/Runtime/rhassert.h | 4 + src/coreclr/nativeaot/Runtime/threadstore.cpp | 1 - .../Runtime/windows/CoffNativeCodeManager.cpp | 153 +- src/coreclr/unwinder/i386/unwinder.cpp | 16 +- src/coreclr/vm/eetwain.cpp | 5839 +++-------------- src/coreclr/vm/gc_unwind_x86.inl | 3837 +++++++++++ 20 files changed, 5331 insertions(+), 5077 deletions(-) create mode 100644 src/coreclr/inc/gc_unwind_x86.h create mode 100644 src/coreclr/vm/gc_unwind_x86.inl diff --git a/src/coreclr/inc/bitvector.h b/src/coreclr/inc/bitvector.h index df06b4c75c66e..0f17697dddce7 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 7783fde153f94..ce5dcb8916dd0 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 e9c16514aa375..71729a9182f3a 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 0000000000000..e5be6b2e4aa43 --- /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 2a6ba1914f0b3..f334b099f2578 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 34af8c5305568..eb60728af5b1f 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 6bfa083beddae..ec47b9019dbc0 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 63da57ba78c3c..6ab3377f5e5d1 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 b7f6554993cd1..7e147cad9c131 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 7e19e77b7e885..ad428db1250ce 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 127c1b617b8f8..2172d3982d182 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 b5876f059f6a4..7e2715d3dd768 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 246f429790064..953ea11b74de6 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 b9c0175210578..d5082fb9efbe7 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 ecf0297980ae1..34403e216f5b1 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 6ec8a44ed2e7b..8357bca105157 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 6bb6cbcf00945..1ff934919fd19 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 45781ef0ff6b0..57d1268a129a0 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 366bf86af3d02..323dec316a8a8 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 0000000000000..8f981ed84cd60 --- /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; +}