From 21acdcd2a2c345e53e685a672e90d1918ddbb007 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 29 Feb 2024 14:38:05 +0100 Subject: [PATCH 1/7] Verbatim code extraction from eetwain.cpp into gc_unwind_x86.inl --- src/coreclr/vm/eetwain.cpp | 4794 ++++++------------------------ src/coreclr/vm/gc_unwind_x86.inl | 3866 ++++++++++++++++++++++++ 2 files changed, 4694 insertions(+), 3966 deletions(-) create mode 100644 src/coreclr/vm/gc_unwind_x86.inl diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 366bf86af3d024..d955a6b3b5fe7c 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 @@ -21,4233 +19,1097 @@ #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 - // NOTE: enabling compiler optimizations, even for debug builds. // Comment this out in order to be able to fully debug methods here. #if defined(_MSC_VER) #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 - - 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))) +#endif // !USE_GC_INFO_DECODER -// 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; - } - } - - infoPtr->syncEpilogStart = epilogStart; - } + case ICorDebugInfo::VLT_REG: + case ICorDebugInfo::VLT_REG_REG: + case ICorDebugInfo::VLT_REG_STK: + return true; - unsigned argTabOffset = INVALID_ARGTAB_OFFSET; - if (hasArgTabOffset) - { - argTabOffset = fastDecodeUnsigned(table); + default: + return false; } - infoPtr->argTabOffset = argTabOffset; - - size_t frameDwordCount = header.frameSize; +} - /* Set the rawStackSize to the number of bytes that it bumps ESP */ +#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. + */ - infoPtr->rawStkSize = (UINT)(frameDwordCount * sizeof(size_t)); +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; - /* Calculate the callee saves regMask and adjust stackSize to */ - /* include the callee saves register spills */ + HRESULT hr = S_OK; - unsigned savedRegs = RM_NONE; - unsigned savedRegsCount = 0; + // Grab a copy of the context before the EnC update. + T_CONTEXT oldCtx = *pCtx; - 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; - } +#if defined(TARGET_X86) - infoPtr->savedRegMask = (RegMask)savedRegs; + /* Extract the necessary information from the info block header */ - infoPtr->savedRegsCountExclFP = savedRegsCount; - if (header.ebpFrame || header.doubleAlign) - { - _ASSERTE(header.ebpSaved); - infoPtr->savedRegsCountExclFP = savedRegsCount - 1; - } + hdrInfo oldInfo, newInfo; - frameDwordCount += savedRegsCount; + DecodeGCHdrInfo(pOldCodeInfo->GetGCInfoToken(), + pOldCodeInfo->GetRelOffset(), + &oldInfo); - infoPtr->stackSize = (UINT)(frameDwordCount * sizeof(size_t)); + DecodeGCHdrInfo(pNewCodeInfo->GetGCInfoToken(), + pNewCodeInfo->GetRelOffset(), + &newInfo); - _ASSERTE(infoPtr->gsCookieOffset == INVALID_GS_COOKIE_OFFSET || - (infoPtr->gsCookieOffset < infoPtr->stackSize) && - ((header.gsCookieOffset % sizeof(void*)) == 0)); + //1) Error checking up front. If we get through here, everything + // else should work - return table - PTR_CBYTE(gcInfoToken.Info); -} + 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 + } -// 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 (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 + } -inline -size_t GetLocallocSPOffset(hdrInfo * info) -{ - LIMITED_METHOD_DAC_CONTRACT; + 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 - _ASSERTE(info->localloc && info->ebpFrame); + // If the method is in a fuclet, and if the framesize grows, we are in trouble. - unsigned position = info->savedRegsCountExclFP + - 1; - return position * sizeof(TADDR); -} + 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 */ -inline -size_t GetParamTypeArgOffset(hdrInfo * info) -{ - LIMITED_METHOD_DAC_CONTRACT; + if (oldInfo.stackSize != newInfo.stackSize) { + LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC stack size mismatch\n")); + return CORDBG_E_ENC_IN_FUNCLET; + } + } + } - _ASSERTE((info->genericsContext || info->handlers) && info->ebpFrame); + /* @TODO: Check if we have grown out of space for locals, in the face of localloc */ + _ASSERTE(!oldInfo.localloc && !newInfo.localloc); - unsigned position = info->savedRegsCountExclFP + - info->localloc + - 1; // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG - return position * sizeof(TADDR); -} + // @TODO: If nesting level grows above the MAX_EnC_HANDLER_NESTING_LEVEL, + // we should return EnC_NESTED_HANLDERS + _ASSERTE(oldInfo.handlers && newInfo.handlers); -inline size_t GetStartShadowSPSlotsOffset(hdrInfo * info) -{ - LIMITED_METHOD_DAC_CONTRACT; + LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: Checks out\n")); - _ASSERTE(info->handlers && info->ebpFrame); +#elif defined(TARGET_AMD64) || defined(TARGET_ARM64) - 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); + // 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. - if (size == sizeof(void*)) - { - // JIT emits "push eax" instead of "sub esp,4" - return SKIP_PUSH_REG(base, offset); - } + // GCInfo for old method + GcInfoDecoder oldGcDecoder( + pOldCodeInfo->GetGCInfoToken(), + GcInfoDecoderFlags(DECODE_SECURITY_OBJECT | DECODE_PSP_SYM | DECODE_EDIT_AND_CONTINUE), + 0 // Instruction offset (not needed) + ); - const int STACK_PROBE_PAGE_SIZE_BYTES = 4096; - const int STACK_PROBE_BOUNDARY_THRESHOLD_BYTES = 1024; + // GCInfo for new method + GcInfoDecoder newGcDecoder( + pNewCodeInfo->GetGCInfoToken(), + GcInfoDecoderFlags(DECODE_SECURITY_OBJECT | DECODE_PSP_SYM | DECODE_EDIT_AND_CONTINUE), + 0 // Instruction offset (not needed) + ); - int lastProbedLocToFinalSp = size; + UINT32 oldSizeOfPreservedArea = oldGcDecoder.GetSizeOfEditAndContinuePreservedArea(); + UINT32 newSizeOfPreservedArea = newGcDecoder.GetSizeOfEditAndContinuePreservedArea(); - if (size < STACK_PROBE_PAGE_SIZE_BYTES) + 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)) { - // sub esp, size - offset = SKIP_ARITH_REG(size, base, offset); + _ASSERTE(!"FixContextForEnC called on a non-EnC-compliant method frame"); + return CORDBG_E_ENC_INFOLESS_METHOD; } - 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; + TADDR oldStackBase = GetSP(&oldCtx); - if (pushedStubParam) - { - // pop eax - offset = SKIP_POP_REG(base, offset); - } + LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC: Old SP=%p, FP=%p\n", (void*)oldStackBase, (void*)GetFP(&oldCtx))); - // 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 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 (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); - } - } - } - } + // 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 (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)); +#elif defined(TARGET_ARM64) + DWORD oldFixedStackSize = oldGcDecoder.GetSizeOfEditAndContinueFixedStackFrame(); + DWORD newFixedStackSize = newGcDecoder.GetSizeOfEditAndContinueFixedStackFrame(); +#else + PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform."); #endif - // test [esp], eax - offset += 3; - } - - return offset; -} -#endif // !USE_GC_INFO_DECODER - - -#if defined(FEATURE_EH_FUNCLETS) + LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC: Old and new fixed stack sizes are %u and %u\n", oldFixedStackSize, newFixedStackSize)); -void EECodeManager::EnsureCallerContextIsValid( PREGDISPLAY pRD, EECodeInfo * pCodeInfo /*= NULL*/, unsigned flags /*= 0*/) -{ - CONTRACTL +#if defined(TARGET_AMD64) && defined(TARGET_WINDOWS) + // win-x64: SP == FP before localloc + if (oldStackBase != GetFP(&oldCtx)) { - NOTHROW; - GC_NOTRIGGER; - SUPPORTS_DAC; + return E_FAIL; } - 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) + // 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) { - EnsureCallerContextIsValid(pRD, NULL); + return E_FAIL; } +#endif - 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); + // EnC remap inside handlers is not supported + if (pOldCodeInfo->IsFunclet() || pNewCodeInfo->IsFunclet()) + return CORDBG_E_ENC_IN_FUNCLET; - if (pRD->IsCallerContextValid) + if (oldSizeOfPreservedArea != newSizeOfPreservedArea) { - pRD->pCurrentContext->Rbp = pRD->pCallerContext->Rbp; - pRD->pCurrentContext->Rsp = pRD->pCallerContext->Rsp; - pRD->pCurrentContext->Rip = pRD->pCallerContext->Rip; + _ASSERTE(!"FixContextForEnC called with method whose frame header size changed from old to new version."); + return E_FAIL; } - 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)); - } + TADDR callerSP = oldStackBase + oldFixedStackSize; - if (flag == UnwindCurrentStackFrame) +#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) { - SyncRegDisplayToCurrentContext(pRD); - pRD->IsCallerContextValid = FALSE; - pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. - } +#if defined(TARGET_AMD64) + TADDR oldPSP = *PTR_TADDR(oldStackBase + nOldPspSymStackSlot); + _ASSERTE(oldPSP == oldStackBase); #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 -}; + TADDR oldPSP = *PTR_TADDR(callerSP + nOldPspSymStackSlot); + _ASSERTE(oldPSP == callerSP); +#endif + } +#endif // _DEBUG -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; + PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform."); #endif -} -/*****************************************************************************/ + // 2) Get all the info about current variables, registers, etc -void UnwindEspFrameEpilog( - PREGDISPLAY pContext, - hdrInfo * info, - PTR_CBYTE epilogBase, - unsigned flags) -{ - LIMITED_METHOD_CONTRACT; - SUPPORTS_DAC; + const ICorDebugInfo::NativeVarInfo * pOldVar; - _ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG); - _ASSERTE(!info->ebpFrame && !info->doubleAlign); - _ASSERTE(info->epilogOffs > 0); + // sorted by varNumber + ICorDebugInfo::NativeVarInfo * oldMethodVarsSorted = NULL; + ICorDebugInfo::NativeVarInfo * oldMethodVarsSortedBase = NULL; + ICorDebugInfo::NativeVarInfo *newMethodVarsSorted = NULL; + ICorDebugInfo::NativeVarInfo *newMethodVarsSortedBase = NULL; - int offset = 0; - unsigned ESP = pContext->SP; + SIZE_T *rgVal1 = NULL; + SIZE_T *rgVal2 = NULL; - if (info->rawStkSize) { - if (!InstructionAlreadyExecuted(offset, info->epilogOffs)) - { - /* We have NOT executed the "ADD ESP, FrameSize", - so manually adjust stack pointer */ - ESP += info->rawStkSize; - } + SIZE_T local; - // We have already popped off the frame (excluding the callee-saved registers) + // 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) - if (epilogBase[0] == X86_INSTR_POP_ECX) + unsigned oldNumVars = unsigned(-ICorDebugInfo::UNKNOWN_ILNUM); + for (pOldVar = oldMethodVars, local = 0; + local < oldMethodVarsCount; + local++, pOldVar++) { - // 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); + 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); + } } - else + + oldMethodVarsSortedBase = new (nothrow) ICorDebugInfo::NativeVarInfo[oldNumVars]; + if (!oldMethodVarsSortedBase) { - // "add esp, rawStkSize" - offset = SKIP_ARITH_REG(info->rawStkSize, epilogBase, offset); + hr = E_FAIL; + goto ErrExit; } - } + oldMethodVarsSorted = oldMethodVarsSortedBase + (-ICorDebugInfo::UNKNOWN_ILNUM); - /* Remaining callee-saved regs are at ESP. Need to update - regsMask as well to exclude registers which have already been popped. */ + memset((void *)oldMethodVarsSortedBase, 0, oldNumVars * sizeof(ICorDebugInfo::NativeVarInfo)); - const RegMask regsMask = info->savedRegMask; + for (local = 0; local < oldNumVars;local++) + oldMethodVarsSortedBase[local].loc.vlType = ICorDebugInfo::VLT_INVALID; - /* Increment "offset" in steps to see which callee-saved - registers have already been popped */ + BYTE **rgVCs = NULL; + DWORD oldMethodOffset = pOldCodeInfo->GetRelOffset(); - for (unsigned i = ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--) - { - RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1]; + for (pOldVar = oldMethodVars, local = 0; + local < oldMethodVarsCount; + local++, pOldVar++) + { + DWORD varNumber = pOldVar->varNumber; - if (!(regMask & regsMask)) - continue; + _ASSERTE(varNumber + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < oldNumVars); - 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)) + // Only care about old local variables alive at oldMethodOffset + if (pOldVar->startOffset <= oldMethodOffset && + pOldVar->endOffset > oldMethodOffset) { - SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP)); + // Indexing should be performed with a signed value - could be negative. + oldMethodVarsSorted[(int32_t)varNumber] = *pOldVar; } - - /* 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); + // 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 - // We also need "mov esp, ebp" after popping the callee-saved registers - needMovEspEbp = true; - } - else - { - bool needLea = false; + // First, count the new vars the same way we did the old vars above. - if (info->localloc) - { - // ESP may be variable if a localloc was actually executed. We will reset it. - // lea esp, [ebp-calleeSavedRegs] + const ICorDebugInfo::NativeVarInfo * pNewVar; - needLea = true; - } - else if (info->savedRegsCountExclFP == 0) + unsigned newNumVars = unsigned(-ICorDebugInfo::UNKNOWN_ILNUM); + for (pNewVar = newMethodVars, local = 0; + local < newMethodVarsCount; + local++, pNewVar++) { - // We will just generate "mov esp, ebp" and be done with it. - - if (info->rawStkSize != 0) + DWORD varNumber = pNewVar->varNumber; + if (signed(varNumber) >= 0) { - needMovEspEbp = true; + // 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); } } - 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) + // sorted by varNumber + newMethodVarsSortedBase = new (nothrow) ICorDebugInfo::NativeVarInfo[newNumVars]; + if (!newMethodVarsSortedBase) { - // 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); + hr = E_FAIL; + goto ErrExit; } - } + newMethodVarsSorted = newMethodVarsSortedBase + (-ICorDebugInfo::UNKNOWN_ILNUM); - for (unsigned i = STRING_LENGTH(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--) - { - RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1]; - _ASSERTE(regMask != RM_EBP); + memset(newMethodVarsSortedBase, 0, newNumVars * sizeof(ICorDebugInfo::NativeVarInfo)); + for (local = 0; local < newNumVars;local++) + newMethodVarsSortedBase[local].loc.vlType = ICorDebugInfo::VLT_INVALID; - if ((info->savedRegMask & regMask) == 0) - continue; + DWORD newMethodOffset = pNewCodeInfo->GetRelOffset(); - if (!InstructionAlreadyExecuted(offset, info->epilogOffs)) + for (pNewVar = newMethodVars, local = 0; + local < newMethodVarsCount; + local++, pNewVar++) { - if (flags & UpdateAllRegs) + 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) { - SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP)); + // Indexing should be performed with a signed valued - could be negative. + newMethodVarsSorted[(int32_t)varNumber] = *pNewVar; } - 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); -} - -/*****************************************************************************/ - -void UnwindEspFrameProlog( - PREGDISPLAY pContext, - hdrInfo * info, - PTR_CBYTE methodStart, - unsigned flags) -{ - 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); + _ASSERTE(newNumVars >= oldNumVars || + !"Not allowed to reduce the number of locals between versions!"); - unsigned offset = 0; + LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: gathered info!\n")); -#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 + rgVal1 = new (nothrow) SIZE_T[newNumVars]; + if (rgVal1 == NULL) + { + hr = E_FAIL; + goto ErrExit; + } - const DWORD curOffs = info->prologOffs; - unsigned ESP = pContext->SP; + rgVal2 = new (nothrow) SIZE_T[newNumVars]; + if (rgVal2 == NULL) + { + hr = E_FAIL; + goto ErrExit; + } - // Find out how many callee-saved regs have already been pushed + // 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. - unsigned regsMask = RM_NONE; - PTR_DWORD savedRegPtr = PTR_DWORD((TADDR)ESP); + memset(rgVal1, 0, sizeof(SIZE_T) * newNumVars); + memset(rgVal2, 0, sizeof(SIZE_T) * newNumVars); - for (unsigned i = 0; i < ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i++) - { - RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i]; + unsigned varsToGet = (oldNumVars > newNumVars) + ? newNumVars + : oldNumVars; - if (!(info->savedRegMask & regMask)) - continue; + // 2) Get all the info about current variables, registers, etc. - if (InstructionAlreadyExecuted(offset, curOffs)) + hr = g_pDebugInterface->GetVariablesFromOffset(pOldCodeInfo->GetMethodDesc(), + varsToGet, + oldMethodVarsSortedBase, + oldMethodOffset, + &oldCtx, + rgVal1, + rgVal2, + newNumVars, + &rgVCs); + if (FAILED(hr)) { - ESP += sizeof(void*); - regsMask |= regMask; + goto ErrExit; } - 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; - } - } + LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: got vars!\n")); - // - // Stack probe checks here - // + /*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* + * IMPORTANT : Once we start munging on the context, we cannot return + * EnC_FAIL, as this should be a transacted commit, + **=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/ - // Poison the value, we don't set it properly at the end of the prolog - INDEBUG(offset = 0xCCCCCCCC); +#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 - // Always restore EBP - if (regsMask & RM_EBP) - pContext->SetEbpLocation(savedRegPtr++); + /*------------------------------------------------------------------------- + * Adjust the stack height + */ + pCtx->Esp -= (newInfo.stackSize - oldInfo.stackSize); - if (flags & 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); - } + // 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 ); -#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 +#elif defined(TARGET_AMD64) && !defined(UNIX_AMD64_ABI) - pContext->SP = ESP; -} + // 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; -void UnwindEspFrame( - PREGDISPLAY pContext, - hdrInfo * info, - PTR_CBYTE table, - PTR_CBYTE methodStart, - DWORD curOffs, - unsigned flags) -{ - LIMITED_METHOD_CONTRACT; - SUPPORTS_DAC; + // 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; - _ASSERTE(!info->ebpFrame && !info->doubleAlign); - _ASSERTE(info->epilogOffs == hdrInfo::NOT_IN_EPILOG); + // 3) zero out the stack frame - this'll initialize _all_ variables - unsigned ESP = pContext->SP; + /*------------------------------------------------------------------------- + * Adjust the stack height + */ + TADDR newStackBase = callerSP - newFixedStackSize; - 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 */ + SetSP(pCtx, newStackBase); - // Are there any arguments pushed on the stack? + // 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); - ESP += GetPushedArgSize(info, table, curOffs); + // For EnC-compliant x64 code, FP == SP. Since SP changed above, update FP now + pCtx->Rbp = newStackBase; - ESP += info->rawStkSize; +#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; - const RegMask regsMask = info->savedRegMask; + // volatile float registers + memset(&pCtx->Xmm0, 0, sizeof(pCtx->Xmm0) * 16); +#else + PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform."); +#endif - for (unsigned i = ARRAY_SIZE(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--) - { - RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1]; + TADDR newStackBase = callerSP - newFixedStackSize; - if ((regMask & regsMask) == 0) - continue; + SetSP(pCtx, newStackBase); - SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP)); + size_t frameHeaderSize = newSizeOfPreservedArea; + _ASSERTE(frameHeaderSize <= oldFixedStackSize); + _ASSERTE(frameHeaderSize <= newFixedStackSize); - ESP += sizeof(unsigned); - } - } + // 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 - /* we can now set the (address of the) return address */ + // Perform some debug-only sanity checks on stack variables. Some checks are + // performed differently between X86/AMD64. - pContext->PCTAddr = (TADDR)ESP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); +#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); + } - /* Now adjust stack pointer */ + // Sanity-check that the range we're clearing contains all of the stack variables - pContext->SP = ESP + ESPIncrOnReturn(info); -} +#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 -void UnwindEbpDoubleAlignFrameProlog( - PREGDISPLAY pContext, - hdrInfo * info, - PTR_CBYTE methodStart, - unsigned flags) -{ - LIMITED_METHOD_DAC_CONTRACT; + SIZE_T regOffs = GetRegOffsInCONTEXT(newMethodVarsSortedBase[i].loc.vlStk.vlsBaseReg); + TADDR baseReg = *(TADDR *)(regOffs + (BYTE*)pCtx); + TADDR addrOfPtr = baseReg + newMethodVarsSortedBase[i].loc.vlStk.vlsOffset; - _ASSERTE(info->prologOffs != hdrInfo::NOT_IN_PROLOG); - _ASSERTE(info->ebpFrame || info->doubleAlign); + _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)); - DWORD offset = 0; + // Deliberately fall through, so that we also verify that the value that the ptr + // points to will be zeroed out + // ... + } + __fallthrough; -#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; - } + 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 + } - /* Check for the case where EBP has not been updated yet. */ - - const DWORD curOffs = info->prologOffs; +#endif // _DEBUG - // If we have still not excecuted "push ebp; mov ebp, esp", then we need to - // report the frame relative to ESP + // Clear the local and temporary stack space - 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 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); - /* If we're past the "push ebp", adjust ESP to pop EBP off */ + // 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 - if (curOffs == (offset + 1)) - pContext->SP += sizeof(TADDR); + // 4) Put the variables from step 3 into their new locations. - /* Stack pointer points to return address */ + LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: set vars!\n")); - pContext->PCTAddr = (TADDR)pContext->SP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + // Move the old variables into their new places. - /* EBP and callee-saved registers still have the correct value */ + hr = g_pDebugInterface->SetVariablesAtOffset(pNewCodeInfo->GetMethodDesc(), + newNumVars, + newMethodVarsSortedBase, + newMethodOffset, + pCtx, // place them into the new context + rgVal1, + rgVal2, + rgVCs); - return; + /*-----------------------------------------------------------------------*/ } +ErrExit: + if (oldMethodVarsSortedBase) + delete[] oldMethodVarsSortedBase; + if (newMethodVarsSortedBase) + delete[] newMethodVarsSortedBase; + if (rgVal1 != NULL) + delete[] rgVal1; + if (rgVal2 != NULL) + delete[] rgVal2; - // 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 (flags & 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 - } - } + LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: exiting!\n")); - /* Increment "offset" in steps to see which callee-saved - registers have been pushed already */ + return hr; +} +#endif // !FEATURE_METADATA_UPDATER - for (unsigned i = 0; i < STRING_LENGTH(CALLEE_SAVED_REGISTERS_MASK); i++) - { - RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i]; - _ASSERTE(regMask != RM_EBP); +#endif // #ifndef DACCESS_COMPILE - if ((info->savedRegMask & regMask) == 0) - continue; +#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; - if (InstructionAlreadyExecuted(offset, curOffs)) - { - SetLocation(pContext, i, PTR_DWORD(--pSavedRegs)); - } + GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); - // "push reg" - offset = SKIP_PUSH_REG(methodStart, offset) ; - } + GcInfoDecoder gcInfoDecoder( + gcInfoToken, + DECODE_INTERRUPTIBILITY, + dwRelOffset + ); - TRASH_CALLEE_UNSAVED_REGS(pContext); - } + return gcInfoDecoder.IsInterruptible(); +} - /* The caller's saved EBP is pointed to by our EBP */ +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) +bool EECodeManager::HasTailCalls( EECodeInfo *pCodeInfo) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; - pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP)); - pContext->SP = DWORD((TADDR)(curEBP + sizeof(void *))); + GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); - /* Stack pointer points to return address */ + GcInfoDecoder gcInfoDecoder( + gcInfoToken, + DECODE_HAS_TAILCALLS, + 0 + ); - pContext->PCTAddr = (TADDR)pContext->SP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + return gcInfoDecoder.HasTailCalls(); } +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64 -/*****************************************************************************/ +#if defined(TARGET_AMD64) && defined(_DEBUG) -bool UnwindEbpDoubleAlignFrame( - PREGDISPLAY pContext, - EECodeInfo *pCodeInfo, - hdrInfo *info, - PTR_CBYTE table, - PTR_CBYTE methodStart, - DWORD curOffs, - unsigned flags) +struct FindEndOfLastInterruptibleRegionState { - LIMITED_METHOD_CONTRACT; - SUPPORTS_DAC; - - _ASSERTE(info->ebpFrame || info->doubleAlign); + unsigned curOffset; + unsigned endOffset; + unsigned lastRangeOffset; +}; - const unsigned curESP = pContext->SP; - const unsigned curEBP = GetRegdisplayFP(pContext); +bool FindEndOfLastInterruptibleRegionCB ( + UINT32 startOffset, + UINT32 stopOffset, + LPVOID hCallback) +{ + FindEndOfLastInterruptibleRegionState *pState = (FindEndOfLastInterruptibleRegionState*)hCallback; - /* First check if we are in a filter (which is obviously after the prolog) */ + // + // If the current range doesn't overlap the given range, keep searching. + // + if ( startOffset >= pState->endOffset + || stopOffset < pState->curOffset) + { + return false; + } - if (info->handlers && info->prologOffs == hdrInfo::NOT_IN_PROLOG) + // + // If the range overlaps the end, then the last point is the end. + // + if ( stopOffset > pState->endOffset + /*&& startOffset < pState->endOffset*/) { - TADDR baseSP; + // The ranges should be sorted in increasing order. + CONSISTENCY_CHECK(startOffset >= pState->lastRangeOffset); -#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()) - { - baseSP = curESP; - // Set baseSP as initial SP - baseSP += GetPushedArgSize(info, table, curOffs); + pState->lastRangeOffset = pState->endOffset; + return true; + } -#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 + // + // 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 + ); - pContext->PCTAddr = baseSP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + FindEndOfLastInterruptibleRegionState state; + state.curOffset = curOffset; + state.endOffset = endOffset; + state.lastRangeOffset = 0; - pContext->SP = (DWORD)(baseSP + sizeof(TADDR)); + gcInfoDecoder.EnumerateInterruptibleRanges(&FindEndOfLastInterruptibleRegionCB, &state); - return true; - } -#else // FEATURE_EH_FUNCLETS + return state.lastRangeOffset; +#else + DacNotImpl(); + return NULL; +#endif // #ifndef DACCESS_COMPILE +} - FrameType frameType = GetHandlerFrameInfo(info, curEBP, - curESP, (DWORD) IGNORE_VAL, - &baseSP); +#endif // TARGET_AMD64 && _DEBUG - /* 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; - } +#else // !USE_GC_INFO_DECODER - if (frameType == FR_FILTER) - { - pContext->PCTAddr = baseSP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); +/***************************************************************************** + * + * Is the function currently at a "GC safe point" ? + */ +bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo, + DWORD dwRelOffset) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; - pContext->SP = (DWORD)(baseSP + sizeof(TADDR)); + hdrInfo info; + BYTE * table; - // pContext->pEbp = same as before; + /* Extract the necessary information from the info block header */ -#ifdef _DEBUG - /* The filter has to be called by the VM. So we dont need to - update callee-saved registers. - */ + table = (BYTE *)DecodeGCHdrInfo(pCodeInfo->GetGCInfoToken(), + dwRelOffset, + &info); - if (flags & UpdateAllRegs) - { - static DWORD s_badData = 0xDEADBEEF; + /* workaround: prevent interruption within prolog/epilog */ - pContext->SetEaxLocation(&s_badData); - pContext->SetEcxLocation(&s_badData); - pContext->SetEdxLocation(&s_badData); + if (info.prologOffs != hdrInfo::NOT_IN_PROLOG || info.epilogOffs != hdrInfo::NOT_IN_EPILOG) + return false; - pContext->SetEbxLocation(&s_badData); - pContext->SetEsiLocation(&s_badData); - pContext->SetEdiLocation(&s_badData); - } +#if VERIFY_GC_TABLES + _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF); #endif - return true; - } -#endif // !FEATURE_EH_FUNCLETS - } + return (info.interruptible); +} - // - // Prolog of an EBP method - // +#endif // !USE_GC_INFO_DECODER - if (info->prologOffs != hdrInfo::NOT_IN_PROLOG) - { - UnwindEbpDoubleAlignFrameProlog(pContext, info, methodStart, flags); - /* Now adjust stack pointer. */ +#if defined(FEATURE_EH_FUNCLETS) - pContext->SP += ESPIncrOnReturn(info); - return true; +void EECodeManager::EnsureCallerContextIsValid( PREGDISPLAY pRD, EECodeInfo * pCodeInfo /*= NULL*/, unsigned flags /*= 0*/) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; } + CONTRACTL_END; - if (flags & UpdateAllRegs) + if( !pRD->IsCallerContextValid ) { - // 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++) + if ((flags & LightUnwind) && (pCodeInfo != NULL)) { - RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i]; - if ((info->savedRegMask & regMask) == 0) - continue; - - SetLocation(pContext, i, --pSavedRegs); +#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); } - } - - /* 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 */ - - pContext->PCTAddr = (TADDR)curEBP + RETURN_ADDR_OFFS * sizeof(TADDR); - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); - /* The caller's saved EBP is pointed to by our EBP */ + pRD->IsCallerContextValid = TRUE; + } - pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP)); - return true; + _ASSERTE( pRD->IsCallerContextValid ); } -bool UnwindStackFrame(PREGDISPLAY pContext, - EECodeInfo *pCodeInfo, - unsigned flags, - CodeManState *pState) +size_t EECodeManager::GetCallerSp( PREGDISPLAY pRD ) { CONTRACTL { NOTHROW; GC_NOTRIGGER; - HOST_NOCALLS; SUPPORTS_DAC; } CONTRACTL_END; - // Address where the method has been interrupted - PCODE breakPC = pContext->ControlPC; - _ASSERTE(PCODEToPINSTR(breakPC) == pCodeInfo->GetCodeAddress()); - - PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode()); - - GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); - PTR_VOID methodInfoPtr = gcInfoToken.Info; - DWORD curOffs = pCodeInfo->GetRelOffset(); - - _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf)); - CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf; - - if (pState->dwIsSet == 0) + // Don't add usage of this field. This is only temporary. + // See ExceptionTracker::InitializeCrawlFrame() for more information. + if (!pRD->IsCallerSPValid) { - /* Extract the necessary information from the info block header */ - - stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, - curOffs, - &stateBuf->hdrInfoBody); + EnsureCallerContextIsValid(pRD, NULL); } - PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf->hdrInfoSize; + return GetSP(pRD->pCallerContext); +} - hdrInfo * info = &stateBuf->hdrInfoBody; +#endif // FEATURE_EH_FUNCLETS - info->isSpeculativeStackWalk = ((flags & SpeculativeStackwalk) != 0); +#ifdef HAS_LIGHTUNWIND +/* + * Light unwind the current stack frame, using provided cache entry. + * pPC, Esp and pEbp of pContext are updated. + */ - if (info->epilogOffs != hdrInfo::NOT_IN_EPILOG) - { - /*--------------------------------------------------------------------- - * First, handle the epilog - */ +// static +void EECodeManager::LightUnwindStackFrame(PREGDISPLAY pRD, EECodeInfo* pCodeInfo, LightUnwindFlag flag) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; - PTR_CBYTE epilogBase = methodStart + (curOffs - info->epilogOffs); - UnwindEpilog(pContext, info, epilogBase, flags); - } - else if (!info->ebpFrame && !info->doubleAlign) - { - /*--------------------------------------------------------------------- - * Now handle ESP frames - */ +#ifdef TARGET_AMD64 + ULONG RBPOffset, RSPOffset; + pCodeInfo->GetOffsetsFromUnwindInfo(&RSPOffset, &RBPOffset); - UnwindEspFrame(pContext, info, table, methodStart, curOffs, flags); - return true; + if (pRD->IsCallerContextValid) + { + pRD->pCurrentContext->Rbp = pRD->pCallerContext->Rbp; + pRD->pCurrentContext->Rsp = pRD->pCallerContext->Rsp; + pRD->pCurrentContext->Rip = pRD->pCallerContext->Rip; } else { - /*--------------------------------------------------------------------- - * Now we know that have an EBP frame - */ + PCONTEXT pSourceCtx = NULL; + PCONTEXT pTargetCtx = NULL; + if (flag == UnwindCurrentStackFrame) + { + pTargetCtx = pRD->pCurrentContext; + pSourceCtx = pRD->pCurrentContext; + } + else + { + pTargetCtx = pRD->pCallerContext; + pSourceCtx = pRD->pCurrentContext; + } - if (!UnwindEbpDoubleAlignFrame(pContext, pCodeInfo, info, table, methodStart, curOffs, flags)) - return false; - } + // 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); + } - // 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))); - */ + // Adjust the sp. From this pointer onwards pCurrentContext->Rsp is the caller sp. + pTargetCtx->Rsp = pSourceCtx->Rsp + RSPOffset; - return true; + // 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 #endif // TARGET_X86 diff --git a/src/coreclr/vm/gc_unwind_x86.inl b/src/coreclr/vm/gc_unwind_x86.inl new file mode 100644 index 00000000000000..d61d66bcab332f --- /dev/null +++ b/src/coreclr/vm/gc_unwind_x86.inl @@ -0,0 +1,3866 @@ +#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); +} + +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); +} + +/*****************************************************************************/ +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; +} + +/*****************************************************************************/ + +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); +} + +/*****************************************************************************/ + +void UnwindEspFrameProlog( + PREGDISPLAY pContext, + hdrInfo * info, + PTR_CBYTE methodStart, + unsigned flags) +{ + 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 + INDEBUG(offset = 0xCCCCCCCC); + + + // Always restore EBP + if (regsMask & RM_EBP) + pContext->SetEbpLocation(savedRegPtr++); + + if (flags & 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 */ + + 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; + +#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 */ + + pContext->PCTAddr = (TADDR)pContext->SP; + pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + + /* 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 (flags & 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 */ + + 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); + + /* 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 (pCodeInfo->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. + 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); + + 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) + { + pContext->PCTAddr = baseSP; + pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + + 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 (flags & 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, flags); + + /* Now adjust stack pointer. */ + + pContext->SP += ESPIncrOnReturn(info); + return true; + } + + if (flags & 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 */ + + pContext->PCTAddr = (TADDR)curEBP + RETURN_ADDR_OFFS * sizeof(TADDR); + pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + + /* The caller's saved EBP is pointed to by our EBP */ + + pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP)); + return true; +} + +bool UnwindStackFrame(PREGDISPLAY pContext, + EECodeInfo *pCodeInfo, + unsigned flags, + CodeManState *pState) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + HOST_NOCALLS; + SUPPORTS_DAC; + } CONTRACTL_END; + + // Address where the method has been interrupted + PCODE breakPC = pContext->ControlPC; + _ASSERTE(PCODEToPINSTR(breakPC) == pCodeInfo->GetCodeAddress()); + + PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode()); + + GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); + PTR_VOID methodInfoPtr = gcInfoToken.Info; + DWORD curOffs = pCodeInfo->GetRelOffset(); + + _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf)); + CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf; + + if (pState->dwIsSet == 0) + { + /* Extract the necessary information from the info block header */ + + stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, + curOffs, + &stateBuf->hdrInfoBody); + } + + PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf->hdrInfoSize; + + hdrInfo * info = &stateBuf->hdrInfoBody; + + info->isSpeculativeStackWalk = ((flags & SpeculativeStackwalk) != 0); + + if (info->epilogOffs != hdrInfo::NOT_IN_EPILOG) + { + /*--------------------------------------------------------------------- + * First, handle the epilog + */ + + PTR_CBYTE epilogBase = methodStart + (curOffs - info->epilogOffs); + UnwindEpilog(pContext, info, epilogBase, flags); + } + else if (!info->ebpFrame && !info->doubleAlign) + { + /*--------------------------------------------------------------------- + * Now handle ESP frames + */ + + UnwindEspFrame(pContext, info, table, methodStart, curOffs, flags); + return true; + } + else + { + /*--------------------------------------------------------------------- + * Now we know that have an EBP frame + */ + + if (!UnwindEbpDoubleAlignFrame(pContext, pCodeInfo, info, table, methodStart, curOffs, flags)) + 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 EECodeManager::EnumGcRefs( PREGDISPLAY pContext, + EECodeInfo *pCodeInfo, + unsigned flags, + GCEnumCallback pCallBack, + LPVOID hCallBack, + DWORD relOffsetOverride) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } 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 + + GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); + unsigned curOffs = pCodeInfo->GetRelOffset(); + + 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 + +#ifndef FEATURE_EH_FUNCLETS + /* What kind of a frame is this ? */ + + 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); + // 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) + { + + /* 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))); + } + } + } + } + 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; + + // 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); + + 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 (!pCodeInfo->GetJitManager()->IsFilterFunclet(pCodeInfo)) +#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(pCodeInfo->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) { + 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); + } + + return true; +} From d10ff51cc8d50440bdfe1bee38f8f8b51dab259e Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 28 Feb 2024 16:26:20 +0100 Subject: [PATCH 2/7] 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. --- 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 | 148 ++++ src/coreclr/inc/gcinfo.h | 14 +- src/coreclr/inc/gcinfodecoder.h | 25 +- src/coreclr/inc/regdisp.h | 6 + .../nativeaot/Runtime/StackFrameIterator.cpp | 39 +- .../nativeaot/Runtime/i386/AsmMacros.inc | 10 +- .../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 | 790 ++---------------- src/coreclr/vm/gc_unwind_x86.inl | 211 +++-- 20 files changed, 662 insertions(+), 1087 deletions(-) create mode 100644 src/coreclr/inc/gc_unwind_x86.h diff --git a/src/coreclr/inc/bitvector.h b/src/coreclr/inc/bitvector.h index df06b4c75c66ec..0f17697dddce74 100644 --- a/src/coreclr/inc/bitvector.h +++ b/src/coreclr/inc/bitvector.h @@ -32,7 +32,9 @@ #define UNDEF_ASSERTE #endif +#ifndef FEATURE_NATIVEAOT #define USE_BITVECTOR 1 +#endif #if USE_BITVECTOR /* The bitvector class is meant to be a drop in replacement for an integer diff --git a/src/coreclr/inc/daccess.h b/src/coreclr/inc/daccess.h index 7783fde153f942..ce5dcb8916dd06 100644 --- a/src/coreclr/inc/daccess.h +++ b/src/coreclr/inc/daccess.h @@ -2374,6 +2374,7 @@ typedef DPTR(int32_t) PTR_int32_t; typedef DPTR(uint32_t) PTR_uint32_t; typedef DPTR(uint64_t) PTR_uint64_t; typedef DPTR(uintptr_t) PTR_uintptr_t; +typedef DPTR(TADDR) PTR_TADDR; #ifndef NATIVEAOT typedef ArrayDPTR(BYTE) PTR_BYTE; @@ -2395,7 +2396,6 @@ typedef DPTR(ULONG64) PTR_ULONG64; typedef DPTR(INT64) PTR_INT64; typedef DPTR(UINT64) PTR_UINT64; typedef DPTR(SIZE_T) PTR_SIZE_T; -typedef DPTR(TADDR) PTR_TADDR; typedef DPTR(int) PTR_int; typedef DPTR(BOOL) PTR_BOOL; typedef DPTR(unsigned) PTR_unsigned; diff --git a/src/coreclr/inc/eetwain.h b/src/coreclr/inc/eetwain.h index e9c16514aa375b..71729a9182f3a1 100644 --- a/src/coreclr/inc/eetwain.h +++ b/src/coreclr/inc/eetwain.h @@ -629,123 +629,7 @@ HRESULT FixContextForEnC(PCONTEXT pCtx, }; #ifdef TARGET_X86 -bool UnwindStackFrame(PREGDISPLAY pContext, - EECodeInfo *pCodeInfo, - unsigned flags, - CodeManState *pState); - -size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken, - unsigned curOffset, - hdrInfo * infoPtr); -#endif - -/***************************************************************************** - ToDo: Do we want to include JIT/IL/target.h? - */ - -enum regNum -{ - REGI_EAX, REGI_ECX, REGI_EDX, REGI_EBX, - REGI_ESP, REGI_EBP, REGI_ESI, REGI_EDI, - REGI_COUNT, - REGI_NA = REGI_COUNT -}; - -/***************************************************************************** - Register masks - */ - -enum RegMask -{ - RM_EAX = 0x01, - RM_ECX = 0x02, - RM_EDX = 0x04, - RM_EBX = 0x08, - RM_ESP = 0x10, - RM_EBP = 0x20, - RM_ESI = 0x40, - RM_EDI = 0x80, - - RM_NONE = 0x00, - RM_ALL = (RM_EAX|RM_ECX|RM_EDX|RM_EBX|RM_ESP|RM_EBP|RM_ESI|RM_EDI), - RM_CALLEE_SAVED = (RM_EBP|RM_EBX|RM_ESI|RM_EDI), - RM_CALLEE_TRASHED = (RM_ALL & ~RM_CALLEE_SAVED), -}; - -/***************************************************************************** - * - * Helper to extract basic info from a method info block. - */ - -struct hdrInfo -{ - unsigned int methodSize; // native code bytes - unsigned int argSize; // in bytes - unsigned int stackSize; // including callee saved registers - unsigned int rawStkSize; // excluding callee saved registers - ReturnKind returnKind; // The ReturnKind for this method. - - unsigned int prologSize; - - // Size of the epilogs in the method. - // For methods which use CEE_JMP, some epilogs may end with a "ret" instruction - // and some may end with a "jmp". The epilogSize reported should be for the - // epilog with the smallest size. - unsigned int epilogSize; - - unsigned char epilogCnt; - bool epilogEnd; // is the epilog at the end of the method - - bool ebpFrame; // locals and arguments addressed relative to EBP - bool doubleAlign; // is the stack double-aligned? locals addressed relative to ESP, and arguments relative to EBP - bool interruptible; // intr. at all times (excluding prolog/epilog), not just call sites - - bool handlers; // has callable handlers - bool localloc; // uses localloc - bool editNcontinue; // has been compiled in EnC mode - bool varargs; // is this a varargs routine - bool profCallbacks; // does the method have Enter-Leave callbacks - bool genericsContext;// has a reported generic context parameter - bool genericsContextIsMethodDesc;// reported generic context parameter is methoddesc - bool isSpeculativeStackWalk; // is the stackwalk seeded by an untrusted source (e.g., sampling profiler)? - - // These always includes EBP for EBP-frames and double-aligned-frames - RegMask savedRegMask:8; // which callee-saved regs are saved on stack - - // Count of the callee-saved registers, excluding the frame pointer. - // This does not include EBP for EBP-frames and double-aligned-frames. - unsigned int savedRegsCountExclFP; - - unsigned int untrackedCnt; - unsigned int varPtrTableSize; - unsigned int argTabOffset; // INVALID_ARGTAB_OFFSET if argtab must be reached by stepping through ptr tables - unsigned int gsCookieOffset; // INVALID_GS_COOKIE_OFFSET if there is no GuardStack cookie - - unsigned int syncStartOffset; // start/end code offset of the protected region in synchronized methods. - unsigned int syncEndOffset; // INVALID_SYNC_OFFSET if there not synchronized method - unsigned int syncEpilogStart; // The start of the epilog. Synchronized methods are guaranteed to have no more than one epilog. - unsigned int revPInvokeOffset; // INVALID_REV_PINVOKE_OFFSET if there is no Reverse PInvoke frame - - enum { NOT_IN_PROLOG = -1, NOT_IN_EPILOG = -1 }; - - int prologOffs; // NOT_IN_PROLOG if not in prolog - int epilogOffs; // NOT_IN_EPILOG if not in epilog. It is never 0 - - // - // Results passed back from scanArgRegTable - // - regNum thisPtrResult; // register holding "this" - RegMask regMaskResult; // registers currently holding GC ptrs - RegMask iregMaskResult; // iptr qualifier for regMaskResult - unsigned argHnumResult; - PTR_CBYTE argTabResult; // Table of encoded offsets of pending ptr args - unsigned argTabBytes; // Number of bytes in argTabResult[] - - // These next two are now large structs (i.e 132 bytes each) - - ptrArgTP argMaskResult; // pending arguments mask - ptrArgTP iargMaskResult; // iptr qualifier for argMaskResult -}; +#include "gc_unwind_x86.h" /***************************************************************************** How the stackwalkers buffer will be interpreted @@ -756,6 +640,9 @@ struct CodeManStateBuf DWORD hdrInfoSize; hdrInfo hdrInfoBody; }; + +#endif + //***************************************************************************** #endif // _EETWAIN_H //***************************************************************************** diff --git a/src/coreclr/inc/gc_unwind_x86.h b/src/coreclr/inc/gc_unwind_x86.h new file mode 100644 index 00000000000000..d29adfaf223a01 --- /dev/null +++ b/src/coreclr/inc/gc_unwind_x86.h @@ -0,0 +1,148 @@ +// 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, IN_EH_FUNCLETS_COMMA, and NOT_IN_NATIVEAOT_PRE_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 +#ifdef FEATURE_NATIVEAOT +#define NOT_IN_NATIVEAOT_PRE_COMMA(a) +#else +#define NOT_IN_NATIVEAOT_PRE_COMMA(a) ,a +#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 +}; + +bool UnwindStackFrameX86(PREGDISPLAY pContext, + PTR_CBYTE methodStart, + DWORD curOffs, + hdrInfo * info, + PTR_CBYTE table, + IN_EH_FUNCLETS_COMMA(PTR_CBYTE funcletStart) + IN_EH_FUNCLETS_COMMA(bool isFunclet) + bool updateAllRegs); + +size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken, + unsigned curOffset, + hdrInfo * infoPtr); + +#endif // _UNWIND_X86_H diff --git a/src/coreclr/inc/gcinfo.h b/src/coreclr/inc/gcinfo.h index 2a6ba1914f0b31..841a616caf8619 100644 --- a/src/coreclr/inc/gcinfo.h +++ b/src/coreclr/inc/gcinfo.h @@ -13,7 +13,11 @@ /*****************************************************************************/ #include "daccess.h" -#include "windef.h" // For BYTE +#ifndef FEATURE_NATIVEAOT +#include "windef.h" // For UINT32 +#else +#define UINT32 uint32_t +#endif // Use the lower 2 bits of the offsets stored in the tables // to encode properties @@ -58,6 +62,14 @@ struct GCInfoToken PTR_VOID Info; UINT32 Version; +#ifdef FEATURE_NATIVEAOT + GCInfoToken(PTR_VOID info) + { + Info = info; + Version = GCINFO_VERSION; + } +#endif + static UINT32 ReadyToRunVersionToGcInfoVersion(UINT32 readyToRunMajorVersion) { // GcInfo version is current from ReadyToRun version 2.0 diff --git a/src/coreclr/inc/gcinfodecoder.h b/src/coreclr/inc/gcinfodecoder.h index 34af8c53055687..eb60728af5b1f7 100644 --- a/src/coreclr/inc/gcinfodecoder.h +++ b/src/coreclr/inc/gcinfodecoder.h @@ -31,7 +31,17 @@ #ifdef FEATURE_NATIVEAOT +#include "gcinfo.h" + typedef ArrayDPTR(const uint8_t) PTR_CBYTE; +#ifdef TARGET_X86 +// Bridge few additional pointer types used in x86 unwinding code +typedef DPTR(DWORD) PTR_DWORD; +typedef DPTR(WORD) PTR_WORD; +typedef DPTR(BYTE) PTR_BYTE; +typedef DPTR(signed char) PTR_SBYTE; +typedef DPTR(INT32) PTR_INT32; +#endif #define LIMITED_METHOD_CONTRACT #define SUPPORTS_DAC @@ -50,22 +60,12 @@ typedef ArrayDPTR(const uint8_t) PTR_CBYTE; #define SSIZE_T intptr_t #define LPVOID void* +#define CHECK_APP_DOMAIN 0 + typedef void * OBJECTREF; #define GET_CALLER_SP(pREGDISPLAY) ((TADDR)0) -struct GCInfoToken -{ - PTR_VOID Info; - UINT32 Version; - - GCInfoToken(PTR_VOID info) - { - Info = info; - Version = 2; - } -}; - #else // FEATURE_NATIVEAOT // Stuff from cgencpu.h: @@ -179,6 +179,7 @@ enum ICodeManagerFlags ExecutionAborted = 0x0002, // execution of this function has been aborted // (i.e. it will not continue execution at the // current location) + AbortingCall = 0x0004, // The current call will never return ParentOfFuncletStackFrame = 0x0040, // A funclet for this frame was previously reported diff --git a/src/coreclr/inc/regdisp.h b/src/coreclr/inc/regdisp.h index 6bfa083beddae6..ec47b9019dbc02 100644 --- a/src/coreclr/inc/regdisp.h +++ b/src/coreclr/inc/regdisp.h @@ -131,6 +131,12 @@ inline LPVOID GetRegdisplayFPAddress(REGDISPLAY *display) { return (LPVOID)display->GetEbpLocation(); } +inline void SetRegdisplayPCTAddr(REGDISPLAY *display, TADDR addr) +{ + display->PCTAddr = addr; + display->ControlPC = *PTR_PCODE(addr); +} + // This function tells us if the given stack pointer is in one of the frames of the functions called by the given frame inline BOOL IsInCalleesFrames(REGDISPLAY *display, LPVOID stackPointer) { diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index 63da57ba78c3c7..6ab3377f5e5d10 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -41,9 +41,6 @@ EXTERN_C CODE_LOCATION ReturnFromUniversalTransition; EXTERN_C CODE_LOCATION ReturnFromUniversalTransition_DebugStepTailCall; #endif -#ifdef TARGET_X86 -EXTERN_C CODE_LOCATION RhpCallFunclet2; -#endif EXTERN_C CODE_LOCATION RhpCallCatchFunclet2; EXTERN_C CODE_LOCATION RhpCallFinallyFunclet2; EXTERN_C CODE_LOCATION RhpCallFilterFunclet2; @@ -779,20 +776,6 @@ void StackFrameIterator::UnwindFuncletInvokeThunk() PTR_uintptr_t SP; -#ifdef TARGET_X86 - // First, unwind RhpCallFunclet - SP = (PTR_uintptr_t)(m_RegDisplay.SP + 0x4); // skip the saved assembly-routine-EBP - m_RegDisplay.SetIP(*SP++); - m_RegDisplay.SetSP((uintptr_t)dac_cast(SP)); - SetControlPC(dac_cast(m_RegDisplay.GetIP())); - - ASSERT( - EQUALS_RETURN_ADDRESS(m_ControlPC, RhpCallCatchFunclet2) || - EQUALS_RETURN_ADDRESS(m_ControlPC, RhpCallFinallyFunclet2) || - EQUALS_RETURN_ADDRESS(m_ControlPC, RhpCallFilterFunclet2) - ); -#endif - bool isFilterInvoke = EQUALS_RETURN_ADDRESS(m_ControlPC, RhpCallFilterFunclet2); #if defined(UNIX_AMD64_ABI) @@ -876,7 +859,7 @@ void StackFrameIterator::UnwindFuncletInvokeThunk() m_RegDisplay.pR15 = SP++; #elif defined(TARGET_X86) - SP = (PTR_uintptr_t)(m_RegDisplay.SP); + SP = (PTR_uintptr_t)(m_RegDisplay.SP + 0x4); // skip the saved assembly-routine-EBP if (!isFilterInvoke) { @@ -1809,25 +1792,6 @@ StackFrameIterator::ReturnAddressCategory StackFrameIterator::CategorizeUnadjust return InThrowSiteThunk; } -#ifdef TARGET_X86 - if (EQUALS_RETURN_ADDRESS(returnAddress, RhpCallFunclet2)) - { - PORTABILITY_ASSERT("CategorizeUnadjustedReturnAddress"); -#if 0 - // See if it is a filter funclet based on the caller of RhpCallFunclet - PTR_uintptr_t SP = (PTR_uintptr_t)(m_RegDisplay.SP + 0x4); // skip the saved assembly-routine-EBP - PTR_uintptr_t ControlPC = *SP++; - if (EQUALS_RETURN_ADDRESS(ControlPC, RhpCallFilterFunclet2)) - { - return InFilterFuncletInvokeThunk; - } - else -#endif - { - return InFuncletInvokeThunk; - } - } -#else // TARGET_X86 if (EQUALS_RETURN_ADDRESS(returnAddress, RhpCallCatchFunclet2) || EQUALS_RETURN_ADDRESS(returnAddress, RhpCallFinallyFunclet2)) { @@ -1838,7 +1802,6 @@ StackFrameIterator::ReturnAddressCategory StackFrameIterator::CategorizeUnadjust { return InFilterFuncletInvokeThunk; } -#endif // TARGET_X86 return InManagedCode; #endif // defined(USE_PORTABLE_HELPERS) } diff --git a/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc b/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc index b7f6554993cd19..f25ef65585496f 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,10 @@ 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 +RhHandleGet equ @RhHandleGet@4 +RhpGcSafeZeroMemory equ @RhpGcSafeZeroMemory@8 ifdef FEATURE_GC_STRESS THREAD__HIJACKFORGCSTRESS equ ?HijackForGcStress@Thread@@SGXPAUPAL_LIMITED_CONTEXT@@@Z @@ -178,6 +182,10 @@ EXTERN RhExceptionHandling_FailedAllocation : PROC EXTERN RhThrowHwEx : PROC EXTERN RhThrowEx : PROC EXTERN RhRethrow : PROC +EXTERN RhpGcPoll2 : PROC + +EXTERN RhHandleGet : PROC +EXTERN RhpGcSafeZeroMemory : PROC ifdef FEATURE_GC_STRESS EXTERN THREAD__HIJACKFORGCSTRESS : PROC diff --git a/src/coreclr/nativeaot/Runtime/i386/AsmOffsetsCpu.h b/src/coreclr/nativeaot/Runtime/i386/AsmOffsetsCpu.h index 7e19e77b7e885c..ad428db1250cef 100644 --- a/src/coreclr/nativeaot/Runtime/i386/AsmOffsetsCpu.h +++ b/src/coreclr/nativeaot/Runtime/i386/AsmOffsetsCpu.h @@ -7,7 +7,7 @@ // // NOTE: the offsets MUST be in hex notation WITHOUT the 0x prefix -PLAT_ASM_SIZEOF(bc, ExInfo) +PLAT_ASM_SIZEOF(c0, ExInfo) PLAT_ASM_OFFSET(0, ExInfo, m_pPrevExInfo) PLAT_ASM_OFFSET(4, ExInfo, m_pExContext) PLAT_ASM_OFFSET(8, ExInfo, m_exception) @@ -15,7 +15,7 @@ PLAT_ASM_OFFSET(0c, ExInfo, m_kind) PLAT_ASM_OFFSET(0d, ExInfo, m_passNumber) PLAT_ASM_OFFSET(10, ExInfo, m_idxCurClause) PLAT_ASM_OFFSET(14, ExInfo, m_frameIter) -PLAT_ASM_OFFSET(b8, ExInfo, m_notifyDebuggerSP) +PLAT_ASM_OFFSET(bc, ExInfo, m_notifyDebuggerSP) PLAT_ASM_OFFSET(0, PInvokeTransitionFrame, m_RIP) PLAT_ASM_OFFSET(4, PInvokeTransitionFrame, m_FramePointer) @@ -23,12 +23,12 @@ PLAT_ASM_OFFSET(8, PInvokeTransitionFrame, m_pThread) PLAT_ASM_OFFSET(0c, PInvokeTransitionFrame, m_Flags) PLAT_ASM_OFFSET(10, PInvokeTransitionFrame, m_PreservedRegs) -PLAT_ASM_SIZEOF(a4, StackFrameIterator) +PLAT_ASM_SIZEOF(a8, StackFrameIterator) PLAT_ASM_OFFSET(08, StackFrameIterator, m_FramePointer) PLAT_ASM_OFFSET(0c, StackFrameIterator, m_ControlPC) PLAT_ASM_OFFSET(10, StackFrameIterator, m_RegDisplay) -PLAT_ASM_OFFSET(9c, StackFrameIterator, m_OriginalControlPC) -PLAT_ASM_OFFSET(a0, StackFrameIterator, m_pPreviousTransitionFrame) +PLAT_ASM_OFFSET(a0, StackFrameIterator, m_OriginalControlPC) +PLAT_ASM_OFFSET(a4, StackFrameIterator, m_pPreviousTransitionFrame) PLAT_ASM_SIZEOF(1c, PAL_LIMITED_CONTEXT) PLAT_ASM_OFFSET(0, PAL_LIMITED_CONTEXT, IP) @@ -40,7 +40,7 @@ PLAT_ASM_OFFSET(10, PAL_LIMITED_CONTEXT, Rsi) PLAT_ASM_OFFSET(14, PAL_LIMITED_CONTEXT, Rax) PLAT_ASM_OFFSET(18, PAL_LIMITED_CONTEXT, Rbx) -PLAT_ASM_SIZEOF(24, REGDISPLAY) +PLAT_ASM_SIZEOF(28, REGDISPLAY) PLAT_ASM_OFFSET(1c, REGDISPLAY, SP) PLAT_ASM_OFFSET(0c, REGDISPLAY, pRbx) diff --git a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm index 127c1b617b8f81..2172d3982d1821 100644 --- a/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm +++ b/src/coreclr/nativeaot/Runtime/i386/ExceptionHandling.asm @@ -10,10 +10,18 @@ include AsmMacros.inc -RhpCallFunclet equ @RhpCallFunclet@0 -RhpThrowHwEx equ @RhpThrowHwEx@0 +;; input: ECX: possible exception object +;; EDX: funclet IP +;; EAX: funclet EBP +CALL_FUNCLET macro SUFFIX + push ebp + mov ebp, eax + mov eax, ecx + call edx +ALTERNATE_ENTRY _RhpCall&SUFFIX&Funclet2 + pop ebp +endm -extern RhpCallFunclet : proc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -25,7 +33,7 @@ extern RhpCallFunclet : proc ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -FASTCALL_FUNC RhpThrowHwEx, 0 +FASTCALL_FUNC RhpThrowHwEx, 8 esp_offsetof_ExInfo textequ %0 esp_offsetof_Context textequ %SIZEOF__ExInfo @@ -74,7 +82,7 @@ FASTCALL_FUNC RhpThrowHwEx, 0 ;; edx contains the address of the ExInfo call RhThrowHwEx -ALTERNATE_ENTRY RhpThrowHwEx2 +ALTERNATE_ENTRY _RhpThrowHwEx2 ;; no return int 3 @@ -90,7 +98,7 @@ FASTCALL_ENDFUNC ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -FASTCALL_FUNC RhpThrowEx, 0 +FASTCALL_FUNC RhpThrowEx, 4 esp_offsetof_ExInfo textequ %0 esp_offsetof_Context textequ %SIZEOF__ExInfo @@ -151,7 +159,7 @@ FASTCALL_FUNC RhpThrowEx, 0 ;; edx contains the address of the ExInfo call RhThrowEx -ALTERNATE_ENTRY RhpThrowEx2 +ALTERNATE_ENTRY _RhpThrowEx2 ;; no return int 3 @@ -171,7 +179,6 @@ FASTCALL_ENDFUNC ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; FASTCALL_FUNC RhpRethrow, 0 - esp_offsetof_ExInfo textequ %0 esp_offsetof_Context textequ %SIZEOF__ExInfo @@ -266,13 +273,14 @@ endm ;; ;; INPUT: ECX: exception object ;; EDX: handler funclet address -;; [ESP + 4]: REGDISPLAY* -;; [ESP + 8]: ExInfo* +;; [ESP + 4]: ExInfo* +;; [ESP + 8]: REGDISPLAY* +;; (CLR calling convention switches the last two parameters!) ;; ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -FASTCALL_FUNC RhpCallCatchFunclet, 0 +FASTCALL_FUNC RhpCallCatchFunclet, 16 FUNCLET_CALL_PROLOGUE 2 @@ -283,8 +291,8 @@ FASTCALL_FUNC RhpCallCatchFunclet, 0 ;; [esp + 10h]: ebx save esp_offsetof_PrevEBP textequ %14h ;; [esp + 14h]: prev ebp esp_offsetof_RetAddr textequ %18h ;; [esp + 18h]: return address - esp_offsetof_RegDisplay textequ %1ch ;; [esp + 1Ch]: REGDISPLAY* - esp_offsetof_ExInfo textequ %20h ;; [esp + 20h]: ExInfo* + esp_offsetof_RegDisplay textequ %20h ;; [esp + 20h]: REGDISPLAY* + esp_offsetof_ExInfo textequ %1ch ;; [esp + 1ch]: ExInfo* ;; Clear the DoNotTriggerGc state before calling out to our managed catch funclet. INLINE_GETTHREAD eax, ebx ;; eax <- Thread*, ebx is trashed @@ -313,9 +321,7 @@ FASTCALL_FUNC RhpCallCatchFunclet, 0 ;; ECX still contains the exception object ;; EDX: funclet IP ;; EAX: funclet EBP - call RhpCallFunclet - -ALTERNATE_ENTRY RhpCallCatchFunclet2 + CALL_FUNCLET Catch ;; eax: resume IP mov [esp + esp_offsetof_ResumeIP], eax ;; save for later @@ -379,7 +385,7 @@ FASTCALL_ENDFUNC ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -FASTCALL_FUNC RhpCallFinallyFunclet, 0 +FASTCALL_FUNC RhpCallFinallyFunclet, 8 FUNCLET_CALL_PROLOGUE 0 @@ -409,9 +415,7 @@ FASTCALL_FUNC RhpCallFinallyFunclet, 0 ;; ECX: not used ;; EDX: funclet IP ;; EAX: funclet EBP - call RhpCallFunclet - -ALTERNATE_ENTRY RhpCallFinallyFunclet2 + CALL_FUNCLET Finally pop edx ;; restore REGDISPLAY* @@ -446,7 +450,7 @@ FASTCALL_ENDFUNC ;; OUTPUT: ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -FASTCALL_FUNC RhpCallFilterFunclet, 0 +FASTCALL_FUNC RhpCallFilterFunclet, 12 FUNCLET_CALL_PROLOGUE 0 @@ -463,9 +467,7 @@ FASTCALL_FUNC RhpCallFilterFunclet, 0 ;; EAX contains the funclet EBP value mov edx, [esp + 0] ;; reload filter funclet address - call RhpCallFunclet - -ALTERNATE_ENTRY RhpCallFilterFunclet2 + CALL_FUNCLET Filter ;; EAX contains the result of the filter execution mov edx, [ebp + 8] diff --git a/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm b/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm index b5876f059f6a40..7e2715d3dd7685 100644 --- a/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm +++ b/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm @@ -11,8 +11,6 @@ include AsmMacros.inc DEFAULT_PROBE_SAVE_FLAGS equ PTFF_SAVE_ALL_PRESERVED + PTFF_SAVE_RSP -PROBE_SAVE_FLAGS_EVERYTHING equ DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_ALL_SCRATCH -PROBE_SAVE_FLAGS_RAX_IS_GCREF equ DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX + PTFF_RAX_IS_GCREF ;; ;; The prolog for all GC suspension hijackes (normal and stress). Sets up an EBP frame, @@ -25,7 +23,7 @@ PROBE_SAVE_FLAGS_RAX_IS_GCREF equ DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX + P ;; EAX: not trashed or saved ;; EBP: new EBP frame with correct return address ;; ESP: points to saved scratch registers (ECX & EDX) -;; ECX: trashed +;; ECX: return value flags ;; EDX: thread pointer ;; HijackFixupProlog macro @@ -44,11 +42,15 @@ HijackFixupProlog macro mov ecx, [edx + OFFSETOF__Thread__m_pvHijackedReturnAddress] mov [ebp + 4], ecx + ;; Fetch the return address flags + mov ecx, [edx + OFFSETOF__Thread__m_uHijackedReturnValueFlags] + ;; ;; Clear hijack state ;; mov dword ptr [edx + OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation], 0 mov dword ptr [edx + OFFSETOF__Thread__m_pvHijackedReturnAddress], 0 + mov dword ptr [edx + OFFSETOF__Thread__m_uHijackedReturnValueFlags], 0 endm @@ -136,7 +138,7 @@ PopProbeFrame macro pop eax endm -RhpThrowHwEx equ @RhpThrowHwEx@0 +RhpThrowHwEx equ @RhpThrowHwEx@8 extern RhpThrowHwEx : proc ;; @@ -179,6 +181,25 @@ Abort: RhpWaitForGC endp +RhpGcPoll proc + cmp [RhpTrapThreads], TrapThreadsFlags_None + jne @F ; forward branch - predicted not taken + ret +@@: + jmp RhpGcPollRare + +RhpGcPoll endp + +RhpGcPollRare proc + push ebp + mov ebp, esp + PUSH_COOP_PINVOKE_FRAME ecx + call RhpGcPoll2 + POP_COOP_PINVOKE_FRAME + pop ebp + ret +RhpGcPollRare endp + ifdef FEATURE_GC_STRESS ;; ;; Set the Thread state and invoke RhpStressGC(). @@ -237,7 +258,7 @@ FASTCALL_FUNC RhpGcProbeHijack, 0 HijackFixupEpilog WaitForGC: - mov ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX + or ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX jmp RhpWaitForGC FASTCALL_ENDFUNC @@ -246,7 +267,7 @@ ifdef FEATURE_GC_STRESS FASTCALL_FUNC RhpGcStressHijack, 0 HijackFixupProlog - mov ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX + or ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX jmp RhpGcStressProbe FASTCALL_ENDFUNC diff --git a/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm b/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm index 246f4297900646..953ea11b74de6d 100644 --- a/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm +++ b/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm @@ -99,15 +99,16 @@ DEFINE_WRITE_BARRIER macro DESTREG, REFREG ;; Define a helper with a name of the form RhpAssignRefEAX etc. (along with suitable calling standard ;; decoration). The location to be updated is in DESTREG. The object reference that will be assigned into that ;; location is in one of the other general registers determined by the value of REFREG. -FASTCALL_FUNC RhpAssignRef&REFREG&, 0 +FASTCALL_FUNC RhpAssignRef&REFREG&, 8 ;; Export the canonical write barrier under unqualified name as well ifidni , - @RhpAssignRef@0 label proc - PUBLIC @RhpAssignRef@0 - ALTERNATE_ENTRY RhpAssignRefAVLocation + ALTERNATE_ENTRY RhpAssignRef + ALTERNATE_ENTRY _RhpAssignRefAVLocation endif + ALTERNATE_ENTRY _RhpAssignRef&REFREG&AVLocation + ;; Write the reference into the location. Note that we rely on the fact that no GC can occur between here ;; and the card table update we may perform below. mov dword ptr [DESTREG], REFREG @@ -196,15 +197,16 @@ DEFINE_CHECKED_WRITE_BARRIER macro DESTREG, REFREG ;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular: ;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen on the first instruction ;; - Function "UnwindSimpleHelperToCaller" assumes the stack contains just the pushed return address -FASTCALL_FUNC RhpCheckedAssignRef&REFREG&, 0 +FASTCALL_FUNC RhpCheckedAssignRef&REFREG&, 8 ;; Export the canonical write barrier under unqualified name as well ifidni , - @RhpCheckedAssignRef@0 label proc - PUBLIC @RhpCheckedAssignRef@0 - ALTERNATE_ENTRY RhpCheckedAssignRefAVLocation + ALTERNATE_ENTRY RhpCheckedAssignRef + ALTERNATE_ENTRY _RhpCheckedAssignRefAVLocation endif + ALTERNATE_ENTRY _RhpCheckedAssignRef&REFREG&AVLocation + ;; Write the reference into the location. Note that we rely on the fact that no GC can occur between here ;; and the card table update we may perform below. mov dword ptr [DESTREG], REFREG @@ -238,29 +240,76 @@ DEFINE_CHECKED_WRITE_BARRIER EDX, EBP ;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular: ;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen at @RhpCheckedLockCmpXchgAVLocation@0 ;; - Function "UnwindSimpleHelperToCaller" assumes the stack contains just the pushed return address -;; pass third argument in EAX -FASTCALL_FUNC RhpCheckedLockCmpXchg -ALTERNATE_ENTRY RhpCheckedLockCmpXchgAVLocation +FASTCALL_FUNC RhpCheckedLockCmpXchg, 12 + mov eax, [esp+4] +ALTERNATE_ENTRY _RhpCheckedLockCmpXchgAVLocation lock cmpxchg [ecx], edx - jne RhpCheckedLockCmpXchg_NoBarrierRequired_ECX_EDX + jne RhpCheckedLockCmpXchg_NoBarrierRequired_ECX_EDX - DEFINE_CHECKED_WRITE_BARRIER_CORE RhpCheckedLockCmpXchg, ECX, EDX, ret + DEFINE_CHECKED_WRITE_BARRIER_CORE RhpCheckedLockCmpXchg, ECX, EDX, ret 4 FASTCALL_ENDFUNC ;; WARNING: Code in EHHelpers.cpp makes assumptions about write barrier code, in particular: ;; - Function "InWriteBarrierHelper" assumes an AV due to passed in null pointer will happen at @RhpCheckedXchgAVLocation@0 ;; - Function "UnwindSimpleHelperToCaller" assumes the stack contains just the pushed return address -FASTCALL_FUNC RhpCheckedXchg, 0 +FASTCALL_FUNC RhpCheckedXchg, 8 ;; Setup eax with the new object for the exchange, that way it will automatically hold the correct result ;; afterwards and we can leave edx unaltered ready for the GC write barrier below. mov eax, edx -ALTERNATE_ENTRY RhpCheckedXchgAVLocation +ALTERNATE_ENTRY _RhpCheckedXchgAVLocation xchg [ecx], eax DEFINE_CHECKED_WRITE_BARRIER_CORE RhpCheckedXchg, ECX, EDX, ret FASTCALL_ENDFUNC +;; +;; RhpByRefAssignRef simulates movs instruction for object references. +;; +;; On entry: +;; edi: address of ref-field (assigned to) +;; esi: address of the data (source) +;; +;; On exit: +;; edi, esi are incremented by 4, +;; ecx: trashed +;; +FASTCALL_FUNC RhpByRefAssignRef, 8 +ALTERNATE_ENTRY _RhpByRefAssignRefAVLocation1 + mov ecx, [esi] +ALTERNATE_ENTRY _RhpByRefAssignRefAVLocation2 + mov [edi], ecx + + ;; Check whether the writes were even into the heap. If not there's no card update required. + cmp edi, [G_LOWEST_ADDRESS] + jb RhpByRefAssignRef_NoBarrierRequired + cmp edi, [G_HIGHEST_ADDRESS] + jae RhpByRefAssignRef_NoBarrierRequired + + UPDATE_GC_SHADOW BASENAME, ecx, edi + + ;; If the reference is to an object that's not in an ephemeral generation we have no need to track it + ;; (since the object won't be collected or moved by an ephemeral collection). + cmp ecx, [G_EPHEMERAL_LOW] + jb RhpByRefAssignRef_NoBarrierRequired + cmp ecx, [G_EPHEMERAL_HIGH] + jae RhpByRefAssignRef_NoBarrierRequired + + mov ecx, edi + shr ecx, 10 + add ecx, [G_CARD_TABLE] + cmp byte ptr [ecx], 0FFh + je RhpByRefAssignRef_NoBarrierRequired + + mov byte ptr [ecx], 0FFh + +RhpByRefAssignRef_NoBarrierRequired: + ;; Increment the pointers before leaving + add esi,4 + add edi,4 + ret +FASTCALL_ENDFUNC + end diff --git a/src/coreclr/nativeaot/Runtime/regdisplay.h b/src/coreclr/nativeaot/Runtime/regdisplay.h index b9c0175210578a..d5082fb9efbe74 100644 --- a/src/coreclr/nativeaot/Runtime/regdisplay.h +++ b/src/coreclr/nativeaot/Runtime/regdisplay.h @@ -46,8 +46,41 @@ struct REGDISPLAY inline void SetIP(PCODE IP) { this->IP = IP; } inline void SetSP(uintptr_t SP) { this->SP = SP; } + +#ifdef TARGET_X86 + TADDR PCTAddr; + + inline unsigned long *GetEaxLocation() { return (unsigned long *)pRax; } + inline unsigned long *GetEcxLocation() { return (unsigned long *)pRcx; } + inline unsigned long *GetEdxLocation() { return (unsigned long *)pRdx; } + inline unsigned long *GetEbpLocation() { return (unsigned long *)pRbp; } + inline unsigned long *GetEbxLocation() { return (unsigned long *)pRbx; } + inline unsigned long *GetEsiLocation() { return (unsigned long *)pRsi; } + inline unsigned long *GetEdiLocation() { return (unsigned long *)pRdi; } + + inline void SetEaxLocation(unsigned long *loc) { pRax = (PTR_uintptr_t)loc; } + inline void SetEcxLocation(unsigned long *loc) { pRcx = (PTR_uintptr_t)loc; } + inline void SetEdxLocation(unsigned long *loc) { pRdx = (PTR_uintptr_t)loc; } + inline void SetEbxLocation(unsigned long *loc) { pRbx = (PTR_uintptr_t)loc; } + inline void SetEsiLocation(unsigned long *loc) { pRsi = (PTR_uintptr_t)loc; } + inline void SetEdiLocation(unsigned long *loc) { pRdi = (PTR_uintptr_t)loc; } + inline void SetEbpLocation(unsigned long *loc) { pRbp = (PTR_uintptr_t)loc; } +#endif }; +#ifdef TARGET_X86 +inline TADDR GetRegdisplayFP(REGDISPLAY *display) +{ + return (TADDR)*display->GetEbpLocation(); +} + +inline void SetRegdisplayPCTAddr(REGDISPLAY *display, TADDR addr) +{ + display->PCTAddr = addr; + display->SetIP(*PTR_PCODE(addr)); +} +#endif + #elif defined(TARGET_ARM) struct REGDISPLAY diff --git a/src/coreclr/nativeaot/Runtime/rhassert.h b/src/coreclr/nativeaot/Runtime/rhassert.h index ecf0297980ae19..34403e216f5b16 100644 --- a/src/coreclr/nativeaot/Runtime/rhassert.h +++ b/src/coreclr/nativeaot/Runtime/rhassert.h @@ -44,6 +44,10 @@ void Assert(const char * expr, const char * file, unsigned int line_num, const c #define _ASSERTE(_expr) ASSERT(_expr) #endif +#ifndef _ASSERTE_ALL_BUILDS +#define _ASSERTE_ALL_BUILDS(_expr) ASSERT(_expr) +#endif + #define PORTABILITY_ASSERT(message) \ ASSERT_UNCONDITIONALLY(message); \ ASSUME(0); \ diff --git a/src/coreclr/nativeaot/Runtime/threadstore.cpp b/src/coreclr/nativeaot/Runtime/threadstore.cpp index 6ec8a44ed2e7bd..8357bca1051574 100644 --- a/src/coreclr/nativeaot/Runtime/threadstore.cpp +++ b/src/coreclr/nativeaot/Runtime/threadstore.cpp @@ -477,7 +477,6 @@ GVAL_IMPL(uint32_t, SECTIONREL__tls_CurrentThread); // // This routine supports the !Thread debugger extension routine // -typedef DPTR(TADDR) PTR_TADDR; // static PTR_Thread ThreadStore::GetThreadFromTEB(TADDR pTEB) { diff --git a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp index 6bb6cbcf00945c..8e7abcebd8548d 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(); + hdrInfo infoBuf; + size_t infoSize = DecodeGCHdrInfo(GCInfoToken(p), 0, &infoBuf); + + // TODO: Hijack with saving the return value in FP stack + if (infoBuf.returnKind == RT_Float) + { + return false; + } + + PTR_uint8_t gcInfo; + uint32_t codeOffset = GetCodeOffset(pMethodInfo, (PTR_VOID)pRegisterSet->IP, &gcInfo); + REGDISPLAY registerSet = *pRegisterSet; + + if (!::UnwindStackFrameX86(®isterSet, + (PTR_CBYTE)(m_moduleBase + pNativeMethodInfo->mainRuntimeFunction->BeginAddress), + codeOffset, + &infoBuf, + gcInfo + infoSize, + (PTR_CBYTE)(m_moduleBase + pNativeMethodInfo->runtimeFunction->BeginAddress), + (unwindBlockFlags & UBF_FUNC_KIND_MASK) != UBF_FUNC_KIND_ROOT, + false)) + { + return false; + } + + *ppvRetAddrLocation = (PTR_PTR_VOID)registerSet.PCTAddr; + *pRetValueKind = GetGcRefKind(infoBuf.returnKind); + + return true; #endif } diff --git a/src/coreclr/unwinder/i386/unwinder.cpp b/src/coreclr/unwinder/i386/unwinder.cpp index 45781ef0ff6b02..57d1268a129a0f 100644 --- a/src/coreclr/unwinder/i386/unwinder.cpp +++ b/src/coreclr/unwinder/i386/unwinder.cpp @@ -21,15 +21,23 @@ BOOL OOPStackUnwinderX86::Unwind(T_CONTEXT* pContextRecord, T_KNONVOLATILE_CONTE rd.pCurrentContextPointers = pContextPointers; } - CodeManState codeManState; - codeManState.dwIsSet = 0; - DWORD ControlPc = pContextRecord->Eip; EECodeInfo codeInfo; codeInfo.Init((PCODE) ControlPc); - if (!UnwindStackFrame(&rd, &codeInfo, UpdateAllRegs, &codeManState)) + GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken(); + hdrInfo hdrInfoBody; + DWORD hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, codeInfo.GetRelOffset(), &hdrInfoBody); + + if (!UnwindStackFrameX86(&rd, + PTR_CBYTE(codeInfo.GetSavedMethodCode()), + codeInfo.GetRelOffset(), + &hdrInfoBody, + dac_cast(gcInfoToken.Info) + hdrInfoSize, + PTR_CBYTE(codeInfo.GetJitManager()->GetFuncletStartAddress(&codeInfo)), + codeInfo.IsFunclet(), + UpdateAllRegs)) { return FALSE; } diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index d955a6b3b5fe7c..44a8eb3319abfe 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -17,9 +17,7 @@ #include "gccover.h" #endif // HAVE_GCCOVER -#include "argdestination.h" - -#ifndef USE_GC_INFO_DECODER +#ifdef TARGET_X86 // NOTE: enabling compiler optimizations, even for debug builds. // Comment this out in order to be able to fully debug methods here. @@ -27,7 +25,13 @@ #pragma optimize("tg", on) #endif -#endif // !USE_GC_INFO_DECODER +void promoteVarArgs(PTR_BYTE argsStart, PTR_VASigCookie varArgSig, GCCONTEXT* ctx); + +#include "gc_unwind_x86.inl" + +#endif // TARGET_X86 + +#include "argdestination.h" #ifndef DACCESS_COMPILE #ifndef FEATURE_EH_FUNCLETS @@ -1108,11 +1112,6 @@ void EECodeManager::LightUnwindStackFrame(PREGDISPLAY pRD, EECodeInfo* pCodeInfo } #endif // HAS_LIGHTUNWIND -/*****************************************************************************/ -#ifdef TARGET_X86 // UnwindStackFrame - -#endif // TARGET_X86 - #ifdef FEATURE_EH_FUNCLETS #ifdef TARGET_X86 size_t EECodeManager::GetResumeSp( PCONTEXT pContext ) @@ -1178,8 +1177,50 @@ bool EECodeManager::UnwindStackFrame(PREGDISPLAY pContext, unsigned flags, CodeManState *pState) { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + HOST_NOCALLS; + SUPPORTS_DAC; + } CONTRACTL_END; + #ifdef TARGET_X86 - return ::UnwindStackFrame(pContext, pCodeInfo, flags, pState); + bool updateAllRegs = flags & UpdateAllRegs; + + // Address where the method has been interrupted + PCODE breakPC = pContext->ControlPC; + _ASSERTE(PCODEToPINSTR(breakPC) == pCodeInfo->GetCodeAddress()); + + GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); + PTR_VOID methodInfoPtr = gcInfoToken.Info; + DWORD curOffs = pCodeInfo->GetRelOffset(); + + _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf)); + CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf; + + if (pState->dwIsSet == 0) + { + /* Extract the necessary information from the info block header */ + + stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, + curOffs, + &stateBuf->hdrInfoBody); + } + + PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf->hdrInfoSize; + + hdrInfo * info = &stateBuf->hdrInfoBody; + + info->isSpeculativeStackWalk = ((flags & SpeculativeStackwalk) != 0); + + 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; @@ -1308,721 +1349,26 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, GC_NOTRIGGER; } 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 - - GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); + PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode()); unsigned curOffs = pCodeInfo->GetRelOffset(); + GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); - 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 - -#ifndef FEATURE_EH_FUNCLETS - /* What kind of a frame is this ? */ - - 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); - // 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) - { - - /* 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))); - } - } - } - } - 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; - - // 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); - - 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 (!pCodeInfo->GetJitManager()->IsFilterFunclet(pCodeInfo)) -#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(pCodeInfo->IsFunclet()) + if (relOffsetOverride != NO_OVERRIDE_OFFSET) { - 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) { - 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); + 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, + pCodeInfo->GetMethodDesc()); } #else // !USE_GC_INFO_DECODER @@ -2609,8 +1955,6 @@ void * EECodeManager::GetGSCookieAddr(PREGDISPLAY pContext, GC_NOTRIGGER; } CONTRACTL_END; - _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf)); - GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); unsigned relOffset = pCodeInfo->GetRelOffset(); @@ -2622,6 +1966,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 index d61d66bcab332f..9d77f6d4144d3c 100644 --- a/src/coreclr/vm/gc_unwind_x86.inl +++ b/src/coreclr/vm/gc_unwind_x86.inl @@ -1,3 +1,10 @@ +// 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 @@ -359,6 +366,7 @@ size_t GetLocallocSPOffset(hdrInfo * info) return position * sizeof(TADDR); } +#ifndef FEATURE_NATIVEAOT inline size_t GetParamTypeArgOffset(hdrInfo * info) { @@ -654,6 +662,7 @@ inline size_t GetSizeOfFrameHeaderForEnC(hdrInfo * info) return sizeof(TADDR) + GetEndShadowSPSlotsOffset(info, MAX_EnC_HANDLER_NESTING_LEVEL); } +#endif /*****************************************************************************/ static @@ -702,7 +711,7 @@ PTR_CBYTE skipToArgReg(const hdrInfo& info, PTR_CBYTE table) _ASSERTE(*castto(table, unsigned short *) == 0xBABE); #endif -#ifdef _DEBUG +#if defined(_DEBUG) && defined(CONSISTENCY_CHECK_MSGF) if (info.argTabOffset != INVALID_ARGTAB_OFFSET) { CONSISTENCY_CHECK_MSGF((info.argTabOffset == (unsigned) (table - tableStart)), @@ -1758,7 +1767,7 @@ unsigned scanArgRegTableI(PTR_CBYTE table, if (argOfs >= MAX_PTRARG_OFS) { - _ASSERTE_ALL_BUILDS(!"scanArgRegTableI: args pushed 'too deep'"); + _ASSERTE_ALL_BUILDS(!"scanArgRegTableI: args pushed 'too deep'"); } else { @@ -2328,7 +2337,18 @@ const RegMask CALLEE_SAVED_REGISTERS_MASK[] = static void SetLocation(PREGDISPLAY pRD, int ind, PDWORD loc) { -#ifdef FEATURE_EH_FUNCLETS +#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 @@ -2359,7 +2379,7 @@ void UnwindEspFrameEpilog( PREGDISPLAY pContext, hdrInfo * info, PTR_CBYTE epilogBase, - unsigned flags) + bool updateAllRegs) { LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; @@ -2415,7 +2435,7 @@ void UnwindEspFrameEpilog( { /* We have NOT yet popped off the register. Get the value from the stack if needed */ - if ((flags & UpdateAllRegs) || (regMask == RM_EBP)) + if (updateAllRegs || (regMask == RM_EBP)) { SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP)); } @@ -2433,8 +2453,7 @@ void UnwindEspFrameEpilog( || 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); + SetRegdisplayPCTAddr(pContext, (TADDR)ESP); pContext->SP = ESP; } @@ -2445,7 +2464,7 @@ void UnwindEbpDoubleAlignFrameEpilog( PREGDISPLAY pContext, hdrInfo * info, PTR_CBYTE epilogBase, - unsigned flags) + bool updateAllRegs) { LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; @@ -2538,7 +2557,7 @@ void UnwindEbpDoubleAlignFrameEpilog( if (!InstructionAlreadyExecuted(offset, info->epilogOffs)) { - if (flags & UpdateAllRegs) + if (updateAllRegs) { SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP)); } @@ -2564,8 +2583,7 @@ void UnwindEbpDoubleAlignFrameEpilog( } offset = SKIP_POP_REG(epilogBase, offset); - pContext->PCTAddr = (TADDR)ESP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + SetRegdisplayPCTAddr(pContext, (TADDR)ESP); pContext->SP = ESP; } @@ -2592,7 +2610,7 @@ void UnwindEpilog( PREGDISPLAY pContext, hdrInfo * info, PTR_CBYTE epilogBase, - unsigned flags) + bool updateAllRegs) { LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; @@ -2602,15 +2620,15 @@ void UnwindEpilog( if (info->ebpFrame || info->doubleAlign) { - UnwindEbpDoubleAlignFrameEpilog(pContext, info, epilogBase, flags); + UnwindEbpDoubleAlignFrameEpilog(pContext, info, epilogBase, updateAllRegs); } else { - UnwindEspFrameEpilog(pContext, info, epilogBase, flags); + UnwindEspFrameEpilog(pContext, info, epilogBase, updateAllRegs); } #ifdef _DEBUG - if (flags & UpdateAllRegs) + if (updateAllRegs) TRASH_CALLEE_UNSAVED_REGS(pContext); #endif @@ -2625,7 +2643,7 @@ void UnwindEspFrameProlog( PREGDISPLAY pContext, hdrInfo * info, PTR_CBYTE methodStart, - unsigned flags) + bool updateAllRegs) { LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; @@ -2687,14 +2705,15 @@ void UnwindEspFrameProlog( // // Poison the value, we don't set it properly at the end of the prolog - INDEBUG(offset = 0xCCCCCCCC); - +#ifdef _DEBUG + offset = 0xCCCCCCCC; +#endif // Always restore EBP if (regsMask & RM_EBP) pContext->SetEbpLocation(savedRegPtr++); - if (flags & UpdateAllRegs) + if (updateAllRegs) { if (regsMask & RM_EBX) pContext->SetEbxLocation(savedRegPtr++); @@ -2775,8 +2794,7 @@ void UnwindEspFrame( /* we can now set the (address of the) return address */ - pContext->PCTAddr = (TADDR)ESP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + SetRegdisplayPCTAddr(pContext, (TADDR)ESP); /* Now adjust stack pointer */ @@ -2790,7 +2808,7 @@ void UnwindEbpDoubleAlignFrameProlog( PREGDISPLAY pContext, hdrInfo * info, PTR_CBYTE methodStart, - unsigned flags) + bool updateAllRegs) { LIMITED_METHOD_DAC_CONTRACT; @@ -2828,8 +2846,7 @@ void UnwindEbpDoubleAlignFrameProlog( /* Stack pointer points to return address */ - pContext->PCTAddr = (TADDR)pContext->SP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + SetRegdisplayPCTAddr(pContext, (TADDR)pContext->SP); /* EBP and callee-saved registers still have the correct value */ @@ -2847,7 +2864,7 @@ void UnwindEbpDoubleAlignFrameProlog( const unsigned curEBP = GetRegdisplayFP(pContext); - if (flags & UpdateAllRegs) + if (updateAllRegs) { PTR_DWORD pSavedRegs = PTR_DWORD((TADDR)curEBP); @@ -2895,20 +2912,20 @@ void UnwindEbpDoubleAlignFrameProlog( /* Stack pointer points to return address */ - pContext->PCTAddr = (TADDR)pContext->SP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + SetRegdisplayPCTAddr(pContext, (TADDR)pContext->SP); } /*****************************************************************************/ bool UnwindEbpDoubleAlignFrame( PREGDISPLAY pContext, - EECodeInfo *pCodeInfo, hdrInfo *info, PTR_CBYTE table, PTR_CBYTE methodStart, DWORD curOffs, - unsigned flags) + IN_EH_FUNCLETS_COMMA(PTR_CBYTE funcletStart) + IN_EH_FUNCLETS_COMMA(bool isFunclet) + bool updateAllRegs) { LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; @@ -2930,7 +2947,7 @@ bool UnwindEbpDoubleAlignFrame( // 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()) + if (isFunclet) { baseSP = curESP; // Set baseSP as initial SP @@ -2944,13 +2961,11 @@ bool UnwindEbpDoubleAlignFrame( // 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) + if (funcletStart != methodStart + curOffs && methodStart[curOffs] != X86_INSTR_RETN) baseSP += 12; #endif - pContext->PCTAddr = baseSP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + SetRegdisplayPCTAddr(pContext, (TADDR)baseSP); pContext->SP = (DWORD)(baseSP + sizeof(TADDR)); @@ -2977,8 +2992,7 @@ bool UnwindEbpDoubleAlignFrame( if (frameType == FR_FILTER) { - pContext->PCTAddr = baseSP; - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + SetRegdisplayPCTAddr(pContext, (TADDR)baseSP); pContext->SP = (DWORD)(baseSP + sizeof(TADDR)); @@ -2989,7 +3003,7 @@ bool UnwindEbpDoubleAlignFrame( update callee-saved registers. */ - if (flags & UpdateAllRegs) + if (updateAllRegs) { static DWORD s_badData = 0xDEADBEEF; @@ -3014,7 +3028,7 @@ bool UnwindEbpDoubleAlignFrame( if (info->prologOffs != hdrInfo::NOT_IN_PROLOG) { - UnwindEbpDoubleAlignFrameProlog(pContext, info, methodStart, flags); + UnwindEbpDoubleAlignFrameProlog(pContext, info, methodStart, updateAllRegs); /* Now adjust stack pointer. */ @@ -3022,7 +3036,7 @@ bool UnwindEbpDoubleAlignFrame( return true; } - if (flags & UpdateAllRegs) + if (updateAllRegs) { // Get to the first callee-saved register PTR_DWORD pSavedRegs = PTR_DWORD((TADDR)curEBP); @@ -3046,8 +3060,7 @@ bool UnwindEbpDoubleAlignFrame( /* The caller's saved EIP is right after our EBP */ - pContext->PCTAddr = (TADDR)curEBP + RETURN_ADDR_OFFS * sizeof(TADDR); - pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr); + SetRegdisplayPCTAddr(pContext, (TADDR)curEBP + RETURN_ADDR_OFFS * sizeof(TADDR)); /* The caller's saved EBP is pointed to by our EBP */ @@ -3055,45 +3068,22 @@ bool UnwindEbpDoubleAlignFrame( return true; } -bool UnwindStackFrame(PREGDISPLAY pContext, - EECodeInfo *pCodeInfo, - unsigned flags, - CodeManState *pState) +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) { - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - HOST_NOCALLS; - SUPPORTS_DAC; - } CONTRACTL_END; - - // Address where the method has been interrupted - PCODE breakPC = pContext->ControlPC; - _ASSERTE(PCODEToPINSTR(breakPC) == pCodeInfo->GetCodeAddress()); - - PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode()); - - GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); - PTR_VOID methodInfoPtr = gcInfoToken.Info; - DWORD curOffs = pCodeInfo->GetRelOffset(); - - _ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf)); - CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf; - - if (pState->dwIsSet == 0) +#ifndef FEATURE_NATIVEAOT + if (pUnwindInfo != NULL) { - /* Extract the necessary information from the info block header */ - - stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, - curOffs, - &stateBuf->hdrInfoBody); + pUnwindInfo->fUseEbpAsFrameReg = info->ebpFrame; + pUnwindInfo->fUseEbp = ((info->savedRegMask & RM_EBP) != 0); } - - PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf->hdrInfoSize; - - hdrInfo * info = &stateBuf->hdrInfoBody; - - info->isSpeculativeStackWalk = ((flags & SpeculativeStackwalk) != 0); +#endif if (info->epilogOffs != hdrInfo::NOT_IN_EPILOG) { @@ -3102,7 +3092,7 @@ bool UnwindStackFrame(PREGDISPLAY pContext, */ PTR_CBYTE epilogBase = methodStart + (curOffs - info->epilogOffs); - UnwindEpilog(pContext, info, epilogBase, flags); + UnwindEpilog(pContext, info, epilogBase, updateAllRegs); } else if (!info->ebpFrame && !info->doubleAlign) { @@ -3110,7 +3100,7 @@ bool UnwindStackFrame(PREGDISPLAY pContext, * Now handle ESP frames */ - UnwindEspFrame(pContext, info, table, methodStart, curOffs, flags); + UnwindEspFrame(pContext, info, table, methodStart, curOffs, updateAllRegs); return true; } else @@ -3119,7 +3109,14 @@ bool UnwindStackFrame(PREGDISPLAY pContext, * Now we know that have an EBP frame */ - if (!UnwindEbpDoubleAlignFrame(pContext, pCodeInfo, info, table, methodStart, curOffs, flags)) + if (!UnwindEbpDoubleAlignFrame(pContext, + info, + table, + methodStart, + curOffs, + IN_EH_FUNCLETS_COMMA(funcletStart) + IN_EH_FUNCLETS_COMMA(isFunclet) + updateAllRegs)) return false; } @@ -3136,18 +3133,18 @@ bool UnwindStackFrame(PREGDISPLAY pContext, return true; } -bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, - EECodeInfo *pCodeInfo, - unsigned flags, - GCEnumCallback pCallBack, - LPVOID hCallBack, - DWORD relOffsetOverride) +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 + NOT_IN_NATIVEAOT_PRE_COMMA(MethodDesc * methodDesc)) { - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - } CONTRACTL_END; - #ifdef FEATURE_EH_FUNCLETS if (flags & ParentOfFuncletStackFrame) { @@ -3156,9 +3153,6 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, } #endif // FEATURE_EH_FUNCLETS - GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); - unsigned curOffs = pCodeInfo->GetRelOffset(); - unsigned EBP = GetRegdisplayFP(pContext); unsigned ESP = pContext->SP; @@ -3242,9 +3236,9 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, #endif // _DEBUG -#ifndef FEATURE_EH_FUNCLETS /* What kind of a frame is this ? */ +#ifndef FEATURE_EH_FUNCLETS FrameType frameType = FR_NORMAL; TADDR baseSP = 0; @@ -3340,11 +3334,13 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, // the type context via "this" need to report "this". // If its reported for other methods, its probably // done incorrectly. So flag such cases. +#ifndef FEATURE_NATIVEAOT _ASSERTE(info.thisPtrResult == REGI_NA || - pCodeInfo->GetMethodDesc()->IsSynchronized() || - pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis()); + methodDesc->IsSynchronized() || + methodDesc->AcquiresInstMethodTableFromThis()); +#endif - /* now report registers and arguments if we are not interrupted */ + /* now report registers and arguments if we are not interrupted */ if (willContinueExecution) { @@ -3382,9 +3378,8 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, // 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()) + if (isFunclet) { - PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode()); TADDR baseSP = ESP; // Set baseSP as initial SP baseSP += GetPushedArgSize(&info, table, curOffs); @@ -3397,7 +3392,6 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, // 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 @@ -3500,10 +3494,11 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, // the type context via "this" need to report "this". // If its reported for other methods, its probably // done incorrectly. So flag such cases. +#ifndef FEATURE_NATIVEAOT _ASSERTE(info.thisPtrResult == REGI_NA || - pCodeInfo->GetMethodDesc()->IsSynchronized() || - pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis()); - + methodDesc->IsSynchronized() || + methodDesc->AcquiresInstMethodTableFromThis()); +#endif /* now report registers and arguments if we are not interrupted */ @@ -3635,7 +3630,7 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, // 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)) + if (!isFilterFunclet) #endif // FEATURE_EH_FUNCLETS { count = info.untrackedCnt; @@ -3828,7 +3823,7 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, // 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()) + if (isFunclet) { return true; } @@ -3840,6 +3835,9 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, 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; @@ -3860,6 +3858,7 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, PTR_VASigCookie varArgSig = *PTR_PTR_VASigCookie(argsStart); promoteVarArgs(argsStart, varArgSig, pCtx); +#endif } return true; From 0c94dc10ce3149d59afa5f9a9973018ebd229cf3 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 1 Mar 2024 09:38:11 +0100 Subject: [PATCH 3/7] PR feedback --- src/coreclr/inc/gc_unwind_x86.h | 4 ---- src/coreclr/inc/gcinfo.h | 9 ++------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/coreclr/inc/gc_unwind_x86.h b/src/coreclr/inc/gc_unwind_x86.h index d29adfaf223a01..0508c8499c080d 100644 --- a/src/coreclr/inc/gc_unwind_x86.h +++ b/src/coreclr/inc/gc_unwind_x86.h @@ -24,10 +24,6 @@ #define NOT_IN_NATIVEAOT_PRE_COMMA(a) ,a #endif -/***************************************************************************** - ToDo: Do we want to include JIT/IL/target.h? - */ - enum regNum { REGI_EAX, REGI_ECX, REGI_EDX, REGI_EBX, diff --git a/src/coreclr/inc/gcinfo.h b/src/coreclr/inc/gcinfo.h index 841a616caf8619..f334b099f2578e 100644 --- a/src/coreclr/inc/gcinfo.h +++ b/src/coreclr/inc/gcinfo.h @@ -13,11 +13,6 @@ /*****************************************************************************/ #include "daccess.h" -#ifndef FEATURE_NATIVEAOT -#include "windef.h" // For UINT32 -#else -#define UINT32 uint32_t -#endif // Use the lower 2 bits of the offsets stored in the tables // to encode properties @@ -60,7 +55,7 @@ const unsigned this_OFFSET_FLAG = 0x2; // the offset is "this" struct GCInfoToken { PTR_VOID Info; - UINT32 Version; + uint32_t Version; #ifdef FEATURE_NATIVEAOT GCInfoToken(PTR_VOID info) @@ -70,7 +65,7 @@ struct GCInfoToken } #endif - static UINT32 ReadyToRunVersionToGcInfoVersion(UINT32 readyToRunMajorVersion) + static uint32_t ReadyToRunVersionToGcInfoVersion(uint32_t readyToRunMajorVersion) { // GcInfo version is current from ReadyToRun version 2.0 return GCINFO_VERSION; From 15543edfc85d026ada818b9d4cbc1f0d1c41e702 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 6 Mar 2024 09:12:44 +0100 Subject: [PATCH 4/7] Remove extra EXTERNs from asm code --- src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc b/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc index f25ef65585496f..7e147cad9c1316 100644 --- a/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc +++ b/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc @@ -165,8 +165,6 @@ RhpWaitForGC2 equ @RhpWaitForGC2@4 RhpTrapThreads equ _RhpTrapThreads RhpStressGc equ @RhpStressGc@0 RhpGcPoll2 equ @RhpGcPoll2@4 -RhHandleGet equ @RhHandleGet@4 -RhpGcSafeZeroMemory equ @RhpGcSafeZeroMemory@8 ifdef FEATURE_GC_STRESS THREAD__HIJACKFORGCSTRESS equ ?HijackForGcStress@Thread@@SGXPAUPAL_LIMITED_CONTEXT@@@Z @@ -184,9 +182,6 @@ EXTERN RhThrowEx : PROC EXTERN RhRethrow : PROC EXTERN RhpGcPoll2 : PROC -EXTERN RhHandleGet : PROC -EXTERN RhpGcSafeZeroMemory : PROC - ifdef FEATURE_GC_STRESS EXTERN THREAD__HIJACKFORGCSTRESS : PROC EXTERN RhpStressGc : PROC From ab0b9f2858ab071396aa52982c8f53e08c358cb0 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 6 Mar 2024 10:09:31 +0100 Subject: [PATCH 5/7] Fix rebase --- src/coreclr/vm/gc_unwind_x86.inl | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/coreclr/vm/gc_unwind_x86.inl b/src/coreclr/vm/gc_unwind_x86.inl index 9d77f6d4144d3c..62da74e1994d42 100644 --- a/src/coreclr/vm/gc_unwind_x86.inl +++ b/src/coreclr/vm/gc_unwind_x86.inl @@ -3077,14 +3077,6 @@ bool UnwindStackFrameX86(PREGDISPLAY pContext, IN_EH_FUNCLETS_COMMA(bool isFunclet) bool updateAllRegs) { -#ifndef FEATURE_NATIVEAOT - if (pUnwindInfo != NULL) - { - pUnwindInfo->fUseEbpAsFrameReg = info->ebpFrame; - pUnwindInfo->fUseEbp = ((info->savedRegMask & RM_EBP) != 0); - } -#endif - if (info->epilogOffs != hdrInfo::NOT_IN_EPILOG) { /*--------------------------------------------------------------------- From aeef9dd0ce985ab424b0944bb0b7077f320a3adf Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 6 Mar 2024 13:49:48 +0100 Subject: [PATCH 6/7] Fix passing code offset for unwinding --- .../nativeaot/Runtime/windows/CoffNativeCodeManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp index 8e7abcebd8548d..1ff934919fd199 100644 --- a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp @@ -905,8 +905,10 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn return false; #endif // defined(TARGET_AMD64) #else // defined(USE_GC_INFO_DECODER) + PTR_uint8_t gcInfo; + uint32_t codeOffset = GetCodeOffset(pMethodInfo, (PTR_VOID)pRegisterSet->IP, &gcInfo); hdrInfo infoBuf; - size_t infoSize = DecodeGCHdrInfo(GCInfoToken(p), 0, &infoBuf); + size_t infoSize = DecodeGCHdrInfo(GCInfoToken(gcInfo), codeOffset, &infoBuf); // TODO: Hijack with saving the return value in FP stack if (infoBuf.returnKind == RT_Float) @@ -914,8 +916,6 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn return false; } - PTR_uint8_t gcInfo; - uint32_t codeOffset = GetCodeOffset(pMethodInfo, (PTR_VOID)pRegisterSet->IP, &gcInfo); REGDISPLAY registerSet = *pRegisterSet; if (!::UnwindStackFrameX86(®isterSet, From cf35f92d397e5f84773e24368affac777033f7f2 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Wed, 6 Mar 2024 16:08:20 +0100 Subject: [PATCH 7/7] Remove assert --- src/coreclr/inc/gc_unwind_x86.h | 10 ++-------- src/coreclr/vm/eetwain.cpp | 3 +-- src/coreclr/vm/gc_unwind_x86.inl | 22 +--------------------- 3 files changed, 4 insertions(+), 31 deletions(-) diff --git a/src/coreclr/inc/gc_unwind_x86.h b/src/coreclr/inc/gc_unwind_x86.h index 0508c8499c080d..e5be6b2e4aa43f 100644 --- a/src/coreclr/inc/gc_unwind_x86.h +++ b/src/coreclr/inc/gc_unwind_x86.h @@ -8,9 +8,8 @@ // 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, IN_EH_FUNCLETS_COMMA, and NOT_IN_NATIVEAOT_PRE_COMMA macros are used -// to specify some parameters for the above methods that are specific for a certain runtime -// or configuration. +// 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, @@ -18,11 +17,6 @@ #define IN_EH_FUNCLETS(a) #define IN_EH_FUNCLETS_COMMA(a) #endif -#ifdef FEATURE_NATIVEAOT -#define NOT_IN_NATIVEAOT_PRE_COMMA(a) -#else -#define NOT_IN_NATIVEAOT_PRE_COMMA(a) ,a -#endif enum regNum { diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 44a8eb3319abfe..323dec316a8a83 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -1367,8 +1367,7 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext, IN_EH_FUNCLETS_COMMA(pCodeInfo->GetJitManager()->IsFilterFunclet(pCodeInfo)) flags, pCallBack, - hCallBack, - pCodeInfo->GetMethodDesc()); + hCallBack); } #else // !USE_GC_INFO_DECODER diff --git a/src/coreclr/vm/gc_unwind_x86.inl b/src/coreclr/vm/gc_unwind_x86.inl index 62da74e1994d42..8f981ed84cd609 100644 --- a/src/coreclr/vm/gc_unwind_x86.inl +++ b/src/coreclr/vm/gc_unwind_x86.inl @@ -3134,8 +3134,7 @@ bool EnumGcRefsX86(PREGDISPLAY pContext, IN_EH_FUNCLETS_COMMA(bool isFilterFunclet) unsigned flags, GCEnumCallback pCallBack, - LPVOID hCallBack - NOT_IN_NATIVEAOT_PRE_COMMA(MethodDesc * methodDesc)) + LPVOID hCallBack) { #ifdef FEATURE_EH_FUNCLETS if (flags & ParentOfFuncletStackFrame) @@ -3322,15 +3321,6 @@ bool EnumGcRefsX86(PREGDISPLAY pContext, _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. -#ifndef FEATURE_NATIVEAOT - _ASSERTE(info.thisPtrResult == REGI_NA || - methodDesc->IsSynchronized() || - methodDesc->AcquiresInstMethodTableFromThis()); -#endif /* now report registers and arguments if we are not interrupted */ @@ -3482,16 +3472,6 @@ bool EnumGcRefsX86(PREGDISPLAY pContext, 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. -#ifndef FEATURE_NATIVEAOT - _ASSERTE(info.thisPtrResult == REGI_NA || - methodDesc->IsSynchronized() || - methodDesc->AcquiresInstMethodTableFromThis()); -#endif - /* now report registers and arguments if we are not interrupted */ if (willContinueExecution)