diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 00d9745e177e7f..3097ff9f9c8914 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -507,7 +507,6 @@ CONFIG_DWORD_INFO(INTERNAL_DiagnosticSuspend, W("DiagnosticSuspend"), 0, "") CONFIG_DWORD_INFO(INTERNAL_SuspendDeadlockTimeout, W("SuspendDeadlockTimeout"), 40000, "") CONFIG_DWORD_INFO(INTERNAL_SuspendThreadDeadlockTimeoutMs, W("SuspendThreadDeadlockTimeoutMs"), 2000, "") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadSuspendInjection, W("INTERNAL_ThreadSuspendInjection"), 1, "Specifies whether to inject activations for thread suspension on Unix") -RETAIL_CONFIG_DWORD_INFO(INTERNAL_InterruptibleCallSites, W("InterruptibleCallSites"), 1, "Specifies whether to allow asynchronous thread interruptions at call sites (requires GCInfo v3)") /// /// Thread (miscellaneous) diff --git a/src/coreclr/inc/eetwain.h b/src/coreclr/inc/eetwain.h index c7b1be02e5c638..bee2f658ee7c08 100644 --- a/src/coreclr/inc/eetwain.h +++ b/src/coreclr/inc/eetwain.h @@ -457,9 +457,6 @@ virtual bool IsGcSafe( EECodeInfo *pCodeInfo, DWORD dwRelOffset); -static -bool InterruptibleSafePointsEnabled(); - #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) virtual bool HasTailCalls(EECodeInfo *pCodeInfo); diff --git a/src/coreclr/inc/gcinfo.h b/src/coreclr/inc/gcinfo.h index d526405c9f2cff..a400ecfbf5c9e8 100644 --- a/src/coreclr/inc/gcinfo.h +++ b/src/coreclr/inc/gcinfo.h @@ -67,14 +67,6 @@ struct GCInfoToken static uint32_t ReadyToRunVersionToGcInfoVersion(uint32_t readyToRunMajorVersion, uint32_t readyToRunMinorVersion) { - // Once MINIMUM_READYTORUN_MAJOR_VERSION is bumped to 10+ - // delete the following and just return GCINFO_VERSION - // - // R2R 9.0 and 9.1 use GCInfo v2 - // R2R 9.2 uses GCInfo v3 - if (readyToRunMajorVersion == 9 && readyToRunMinorVersion < 2) - return 2; - return GCINFO_VERSION; } }; diff --git a/src/coreclr/inc/gcinfodecoder.h b/src/coreclr/inc/gcinfodecoder.h index 6f62a3f8741998..d91e10bc081d21 100644 --- a/src/coreclr/inc/gcinfodecoder.h +++ b/src/coreclr/inc/gcinfodecoder.h @@ -528,11 +528,9 @@ class GcInfoDecoder #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED bool IsSafePoint(); - bool AreSafePointsInterruptible(); - bool IsInterruptibleSafePoint(); - bool CouldBeInterruptibleSafePoint(); + bool CouldBeSafePoint(); - // This is used for gccoverage + // This is used for gcinfodumper bool IsSafePoint(UINT32 codeOffset); typedef void EnumerateSafePointsCallback (GcInfoDecoder* decoder, UINT32 offset, void * hCallback); diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp index b12d63bf726129..9f982f630bcec7 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp @@ -196,7 +196,7 @@ bool UnixNativeCodeManager::IsSafePoint(PTR_VOID pvAddress) if (decoder.IsInterruptible()) return true; - if (decoder.IsInterruptibleSafePoint()) + if (decoder.IsSafePoint()) return true; return false; @@ -246,7 +246,7 @@ void UnixNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, codeOffset - 1 ); - assert(decoder.IsInterruptibleSafePoint()); + assert(decoder.IsSafePoint()); } } diff --git a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp index b8c2310d644004..66c775e0e2c05e 100644 --- a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp @@ -418,7 +418,7 @@ bool CoffNativeCodeManager::IsSafePoint(PTR_VOID pvAddress) if (decoder.IsInterruptible()) return true; - if (decoder.IsInterruptibleSafePoint()) + if (decoder.IsSafePoint()) return true; return false; @@ -479,7 +479,7 @@ void CoffNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, codeOffset - 1 ); - assert(decoder.IsInterruptibleSafePoint()); + assert(decoder.IsSafePoint()); } } diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index d940acf6603930..c66c4f2779d67e 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -2570,10 +2570,6 @@ class EECodeInfo void GetOffsetsFromUnwindInfo(ULONG* pRSPOffset, ULONG* pRBPOffset); ULONG GetFrameOffsetFromUnwindInfo(); -#if defined(_DEBUG) && defined(HAVE_GCCOVER) - // Find first funclet inside (pvFuncletStart, pvFuncletStart + cbCode) - static LPVOID findNextFunclet (LPVOID pvFuncletStart, SIZE_T cbCode, LPVOID *ppvFuncletEnd); -#endif // _DEBUG && HAVE_GCCOVER #endif // TARGET_AMD64 private: diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 5746c44de4a770..2176c9f28b8285 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -846,21 +846,12 @@ bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo, if (gcInfoDecoder.IsInterruptible()) return true; - if (InterruptibleSafePointsEnabled() && gcInfoDecoder.IsInterruptibleSafePoint()) + if (gcInfoDecoder.IsSafePoint()) return true; return false; } -bool EECodeManager::InterruptibleSafePointsEnabled() -{ - LIMITED_METHOD_CONTRACT; - - // zero initialized - static ConfigDWORD interruptibleCallSitesEnabled; - return interruptibleCallSitesEnabled.val(CLRConfig::INTERNAL_InterruptibleCallSites) != 0; -} - #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) bool EECodeManager::HasTailCalls( EECodeInfo *pCodeInfo) { @@ -1431,8 +1422,7 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, DECODE_INTERRUPTIBILITY, curOffs ); - _ASSERTE(_gcInfoDecoder.IsInterruptible() || - (InterruptibleSafePointsEnabled() && _gcInfoDecoder.CouldBeInterruptibleSafePoint())); + _ASSERTE(_gcInfoDecoder.IsInterruptible() || _gcInfoDecoder.CouldBeSafePoint()); } #endif @@ -1535,7 +1525,7 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, curOffs - 1 ); - _ASSERTE((InterruptibleSafePointsEnabled() && gcInfoDecoder.CouldBeInterruptibleSafePoint())); + _ASSERTE(gcInfoDecoder.CouldBeSafePoint()); } } diff --git a/src/coreclr/vm/gccover.cpp b/src/coreclr/vm/gccover.cpp index 8403ab0837174c..3565582cd1fce7 100644 --- a/src/coreclr/vm/gccover.cpp +++ b/src/coreclr/vm/gccover.cpp @@ -34,73 +34,6 @@ /****************************************************************************/ -MethodDesc* AsMethodDesc(size_t addr); -static PBYTE getTargetOfCall(PBYTE instrPtr, PCONTEXT regs, PBYTE*nextInstr); -#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) -static void replaceSafePointInstructionWithGcStressInstr(GcInfoDecoder* decoder, UINT32 safePointOffset, LPVOID codeStart); -static bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 stopOffset, LPVOID codeStart); -#endif - -// There is a call target instruction, try to find the MethodDesc for where target points to. -// Returns nullptr if it can't find it. -static MethodDesc* getTargetMethodDesc(PCODE target) -{ - MethodDesc* targetMD = ExecutionManager::GetCodeMethodDesc(target); - if (targetMD != nullptr) - { - // It is JIT/NGened call. - return targetMD; - } - - auto stubKind = RangeSectionStubManager::GetStubKind(target); - - if ((stubKind == STUB_CODE_BLOCK_VSD_DISPATCH_STUB) || - (stubKind == STUB_CODE_BLOCK_VSD_RESOLVE_STUB) || - (stubKind == STUB_CODE_BLOCK_VSD_LOOKUP_STUB) || - (stubKind == STUB_CODE_BLOCK_VSD_VTABLE_STUB)) - { - VirtualCallStubManager *pVSDStubManager = VirtualCallStubManager::FindStubManager(target, &stubKind); - if (pVSDStubManager != NULL) - { - // It is a VSD stub manager. - DispatchToken token(VirtualCallStubManager::GetTokenFromStubQuick(pVSDStubManager, target, stubKind)); - _ASSERTE(token.IsValid()); - return VirtualCallStubManager::GetInterfaceMethodDescFromToken(token); - } - } - - if (stubKind == STUB_CODE_BLOCK_PRECODE) - { - // The address looks like a value stub, try to get the method descriptor. - return MethodDesc::GetMethodDescFromStubAddr(target, TRUE); - } - - if (stubKind == STUB_CODE_BLOCK_STUBPRECODE) - { - return (MethodDesc*)((StubPrecode*)PCODEToPINSTR(target))->GetMethodDesc(); - } - - if (stubKind == STUB_CODE_BLOCK_FIXUPPRECODE) - { - if (!FixupPrecode::IsFixupPrecodeByASM(target)) - { - // If the target slot points to the fixup part of the stub, the actual - // stub starts FixupPrecode::FixupCodeOffset bytes below the target, - // so we need to compensate for it. - target -= FixupPrecode::FixupCodeOffset; - if (!FixupPrecode::IsFixupPrecodeByASM(target)) - { - _ASSERTE(!"Invalid FixupPrecode address"); // We should never get other precode type here - return nullptr; - } - } - - return (MethodDesc*)((FixupPrecode*)PCODEToPINSTR(target))->GetMethodDesc(); - } - - return nullptr; -} - bool IsGcCoverageInterruptInstruction(PBYTE instrPtr) { UINT32 instrVal; @@ -162,6 +95,9 @@ void SetupAndSprinkleBreakpoints( { _ASSERTE(!nativeCodeVersion.IsNull()); + // CONSIDER: does anyone call this with fZapped == true ? are there plans? + _ASSERTE(!fZapped); + // Allocate room for the GCCoverageInfo and copy of the method instructions MethodDesc *pMD = nativeCodeVersion.GetMethodDesc(); size_t memSize = sizeof(GCCoverageInfo) + methodRegionInfo.hotSize + methodRegionInfo.coldSize; @@ -181,8 +117,8 @@ void SetupAndSprinkleBreakpoints( 0, fZapped); - // This is not required for ARM* as the above call does the work for both hot & cold regions -#if !defined(TARGET_ARM) && !defined(TARGET_ARM64) && !defined(TARGET_LOONGARCH64) && !defined(TARGET_RISCV64) + // This is only required for X86, since otherwise the above call does the work for both hot & cold regions +#if defined(TARGET_X86) if (gcCover->methodRegion.coldSize != 0) { gcCover->SprinkleBreakpoints(gcCover->savedCode + gcCover->methodRegion.hotSize, @@ -261,235 +197,326 @@ void SetupGcCoverage(NativeCodeVersion nativeCodeVersion, BYTE* methodStartPtr) SetupAndSprinkleBreakpointsForJittedMethod(nativeCodeVersion, codeStart); } -void ReplaceInstrAfterCall(PBYTE instrToReplace, MethodDesc* callMD) +// There are some code path in DoGcStress to return without doing a GC but +// now relies on EE suspension to update the GC STRESS instruction. +// We need to do a extra EE suspension/resume even without GC. +FORCEINLINE void UpdateGCStressInstructionWithoutGC () { - ReturnKind returnKind = callMD->GetReturnKind(true); - if (!IsValidReturnKind(returnKind)) - { -#if defined(TARGET_AMD64) && defined(TARGET_UNIX) - _ASSERTE(!"Unexpected return kind for x64 Unix."); -#else - // SKip GC coverage after the call. - return; -#endif - } - _ASSERTE(IsValidReturnKind(returnKind)); - - bool ispointerKind = IsPointerReturnKind(returnKind); -#ifdef TARGET_ARM - size_t instrLen = GetARMInstructionLength(instrToReplace); - bool protectReturn = ispointerKind; - if (protectReturn) - if (instrLen == 2) - *(WORD*)instrToReplace = INTERRUPT_INSTR_PROTECT_RET; - else - *(DWORD*)instrToReplace = INTERRUPT_INSTR_PROTECT_RET_32; - else - if (instrLen == 2) - *(WORD*)instrToReplace = INTERRUPT_INSTR; - else - *(DWORD*)instrToReplace = INTERRUPT_INSTR_32; -#elif defined(TARGET_ARM64) - bool protectReturn = ispointerKind; - if (protectReturn) - *(DWORD*)instrToReplace = INTERRUPT_INSTR_PROTECT_RET; - else - *(DWORD*)instrToReplace = INTERRUPT_INSTR; -#elif defined(TARGET_AMD64) || defined(TARGET_X86) - - - if (ispointerKind) - { - bool protectRegister[2] = { false, false }; - - bool moreRegisters = false; - - ReturnKind fieldKind1 = ExtractRegReturnKind(returnKind, 0, moreRegisters); - if (IsPointerFieldReturnKind(fieldKind1)) - { - protectRegister[0] = true; - } - if (moreRegisters) - { - ReturnKind fieldKind2 = ExtractRegReturnKind(returnKind, 1, moreRegisters); - if (IsPointerFieldReturnKind(fieldKind2)) - { - protectRegister[1] = true; - } - } - _ASSERTE(!moreRegisters); - - if (protectRegister[0] && !protectRegister[1]) - { - *instrToReplace = INTERRUPT_INSTR_PROTECT_FIRST_RET; - } - else - { -#if !defined(TARGET_AMD64) || !defined(TARGET_UNIX) - _ASSERTE(!"Not expected multi reg return with pointers."); -#endif // !TARGET_AMD64 || !TARGET_UNIX - if (!protectRegister[0] && protectRegister[1]) - { - *instrToReplace = INTERRUPT_INSTR_PROTECT_SECOND_RET; - } - else - { - _ASSERTE(protectRegister[0] && protectRegister[1]); - *instrToReplace = INTERRUPT_INSTR_PROTECT_BOTH_RET; - } - } - } - else - { - *instrToReplace = INTERRUPT_INSTR; - } -#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - bool protectReturn = ispointerKind; - if (protectReturn) - *(DWORD*)instrToReplace = INTERRUPT_INSTR_PROTECT_RET; - else - *(DWORD*)instrToReplace = INTERRUPT_INSTR; -#else - _ASSERTE(!"not implemented for platform"); -#endif + ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_OTHER); + ThreadSuspend::RestartEE(TRUE, TRUE); } -#ifdef TARGET_AMD64 +#if defined(TARGET_X86) +///////////////////////////////////////////////////////////////////////////// +////////////////////////////// x86-specific ///////////////////////////////// +///////////////////////////////////////////////////////////////////////////// -class GCCoverageRangeEnumerator +static size_t getRegVal(unsigned regNum, PCONTEXT regs) { -private: + return *getRegAddr(regNum, regs); +} - ICodeManager *m_pCodeManager; - GCInfoToken m_pvGCTable; - BYTE *m_codeStart; - BYTE *m_codeEnd; - BYTE *m_curFuncletEnd; - BYTE *m_nextFunclet; +/****************************************************************************/ +static PBYTE getTargetOfCall(PBYTE instrPtr, PCONTEXT regs, PBYTE* nextInstr) { + BYTE sibindexadj = 0; + BYTE baseadj = 0; + WORD displace = 0; - BYTE* GetNextFunclet () - { - if (m_nextFunclet == NULL) - return m_codeEnd; - - BYTE *pCurFunclet = (BYTE*)EECodeInfo::findNextFunclet(m_nextFunclet, m_codeEnd - m_nextFunclet, (LPVOID*)&m_curFuncletEnd); - m_nextFunclet = (pCurFunclet != NULL) ? m_curFuncletEnd : NULL; - - if (pCurFunclet == NULL) - return m_codeEnd; - - LOG((LF_JIT, LL_INFO1000, "funclet range %p-%p\n", pCurFunclet, m_curFuncletEnd)); - - // - // workaround - adjust the funclet end address to exclude uninterruptible - // code at the end of each funclet. The jit currently puts data like - // jump tables in the code portion of the allocation, instead of the - // read-only portion. - // - // TODO: If the entire range is uninterruptible, we should skip the - // entire funclet. - // - unsigned ofsLastInterruptible = m_pCodeManager->FindEndOfLastInterruptibleRegion( - static_cast(pCurFunclet - m_codeStart), - static_cast(m_curFuncletEnd - m_codeStart), - m_pvGCTable); - - if (ofsLastInterruptible) - { - m_curFuncletEnd = m_codeStart + ofsLastInterruptible; - LOG((LF_JIT, LL_INFO1000, "adjusted end to %p\n", m_curFuncletEnd)); - } + // In certain situations, the instruction bytes are read from a different + // location than the actual bytes being executed. + // When decoding the instructions of a method which is sprinkled with + // TRAP instructions for GCStress, we decode the bytes from a copy + // of the instructions stored before the traps-for-gc were inserted. + // However, the PC-relative addressing/displacement of the CALL-target + // will still be with respect to the currently executing PC. + // So, if a register context is available, we pick the PC from it + // (for address calculation purposes only). - return pCurFunclet; - } + PBYTE PC = (regs) ? (PBYTE)GetIP(regs) : instrPtr; + if (instrPtr[0] == 0xE8) { // Direct Relative Near + *nextInstr = instrPtr + 5; -public: + size_t base = (size_t) PC + 5; - GCCoverageRangeEnumerator (ICodeManager *pCodeManager, GCInfoToken pvGCTable, BYTE *codeStart, SIZE_T codeSize) - { - m_pCodeManager = pCodeManager; - m_pvGCTable = pvGCTable; - m_codeStart = codeStart; - m_codeEnd = codeStart + codeSize; - m_nextFunclet = codeStart; + INT32 displacement = (INT32) ( + ((UINT32)instrPtr[1]) + + (((UINT32)instrPtr[2]) << 8) + + (((UINT32)instrPtr[3]) << 16) + + (((UINT32)instrPtr[4]) << 24) + ); - GetNextFunclet(); + // Note that the signed displacement is sign-extended + // to 64-bit on AMD64 + return((PBYTE)(base + (SSIZE_T)displacement)); } - // Checks that the given pointer is inside of a range where gc should be - // tested. If not, increments the pointer until it is, and returns the - // new pointer. - BYTE *EnsureInRange (BYTE *cur) - { - if (cur >= m_curFuncletEnd) - { - cur = GetNextFunclet(); - } + if (instrPtr[0] == 0xFF) { // Indirect Absolute Near - return cur; - } + _ASSERTE(regs); - BYTE *SkipToNextRange () - { - return GetNextFunclet(); - } -}; + BYTE mod = (instrPtr[1] & 0xC0) >> 6; + BYTE rm = (instrPtr[1] & 0x7); + PBYTE result; -#endif // TARGET_AMD64 + switch (mod) { + case 0: + case 1: + case 2: -/****************************************************************************/ -/* sprinkle interrupt instructions that will stop on every GCSafe location - regionOffsetAdj - Represents the offset of the current region - from the beginning of the method (is 0 for hot region) -*/ + if (rm == 4) { -void GCCoverageInfo::SprinkleBreakpoints( - BYTE * saveAddr, - PCODE pCode, - size_t codeSize, - size_t regionOffsetAdj, - BOOL fZapped) -{ -#if (defined(TARGET_X86) || defined(TARGET_AMD64)) && USE_DISASSEMBLER + // + // Get values from the SIB byte + // + BYTE ss = (instrPtr[2] & 0xC0) >> 6; + BYTE index = (instrPtr[2] & 0x38) >> 3; + BYTE base = (instrPtr[2] & 0x7); - BYTE * codeStart = (BYTE *)pCode; - ExecutableWriterHolderNoLog codeWriterHolder; - size_t writeableOffset; + // + // Get starting value + // + if ((mod == 0) && (base == 5)) { + result = 0; + } else { + result = (BYTE *)getRegVal(baseadj + base, regs); + } - memcpy(saveAddr, codeStart, codeSize); + // + // Add in the [index] + // + if (index != 0x4) { + result = result + (getRegVal(sibindexadj + index, regs) << ss); + } - // For prejitted code we have to remove the write-protect on the code page - if (fZapped) - { - DWORD oldProtect; - ClrVirtualProtect(codeStart, codeSize, PAGE_EXECUTE_READWRITE, &oldProtect); - writeableOffset = 0; - } - else - { - codeWriterHolder.AssignExecutableWriterHolder(codeStart, codeSize); - writeableOffset = codeWriterHolder.GetRW() - codeStart; - } + // + // Finally add in the offset + // + if (mod == 0) { - PBYTE cur; - BYTE* codeEnd = codeStart + codeSize; + if (base == 5) { + result = result + *((int *)&instrPtr[3]); + displace += 7; + } else { + displace += 3; + } - EECodeInfo codeInfo((PCODE)codeStart); + } else if (mod == 1) { - static ConfigDWORD fGcStressOnDirectCalls; // ConfigDWORD must be a static variable + result = result + *((char *)&instrPtr[3]); + displace += 4; + } else { // == 2 -#ifdef TARGET_AMD64 - GCCoverageRangeEnumerator rangeEnum(codeMan, gcInfoToken, codeStart, codeSize); + result = result + *((int *)&instrPtr[3]); + displace += 7; - GcInfoDecoder safePointDecoder(gcInfoToken, (GcInfoDecoderFlags)0, 0); - bool fSawPossibleSwitch = false; -#endif + } - cur = codeStart; - Disassembler disassembler; + } else { + + // + // Get the value we need from the register. + // + + if ((mod == 0) && (rm == 5)) { + result = 0; + } else { + result = (PBYTE)getRegVal(baseadj + rm, regs); + } + + if (mod == 0) { + + if (rm == 5) { + result = result + *((int *)&instrPtr[2]); + displace += 6; + } else { + displace += 2; + } + + } else if (mod == 1) { + + result = result + *((char *)&instrPtr[2]); + displace += 3; + + } else { // == 2 + + result = result + *((int *)&instrPtr[2]); + displace += 6; + + } + + } + + // + // Now dereference thru the result to get the resulting IP. + // + result = (PBYTE)(*((PBYTE *)result)); + + break; + + case 3: + default: + + result = (PBYTE)getRegVal(baseadj + rm, regs); + displace += 2; + break; + + } + + *nextInstr = instrPtr + displace; + return result; + + } + + return(0); // Fail +} + +// There is a call target instruction, try to find the MethodDesc for where target points to. +// Returns nullptr if it can't find it. +static MethodDesc* getTargetMethodDesc(PCODE target) +{ + MethodDesc* targetMD = ExecutionManager::GetCodeMethodDesc(target); + if (targetMD != nullptr) + { + // It is JIT/NGened call. + return targetMD; + } + + auto stubKind = RangeSectionStubManager::GetStubKind(target); + + if ((stubKind == STUB_CODE_BLOCK_VSD_DISPATCH_STUB) || + (stubKind == STUB_CODE_BLOCK_VSD_RESOLVE_STUB) || + (stubKind == STUB_CODE_BLOCK_VSD_LOOKUP_STUB) || + (stubKind == STUB_CODE_BLOCK_VSD_VTABLE_STUB)) + { + VirtualCallStubManager *pVSDStubManager = VirtualCallStubManager::FindStubManager(target, &stubKind); + if (pVSDStubManager != NULL) + { + // It is a VSD stub manager. + DispatchToken token(VirtualCallStubManager::GetTokenFromStubQuick(pVSDStubManager, target, stubKind)); + _ASSERTE(token.IsValid()); + return VirtualCallStubManager::GetInterfaceMethodDescFromToken(token); + } + } + + if (stubKind == STUB_CODE_BLOCK_PRECODE) + { + // The address looks like a value stub, try to get the method descriptor. + return MethodDesc::GetMethodDescFromStubAddr(target, TRUE); + } + + if (stubKind == STUB_CODE_BLOCK_STUBPRECODE) + { + return (MethodDesc*)((StubPrecode*)PCODEToPINSTR(target))->GetMethodDesc(); + } + + if (stubKind == STUB_CODE_BLOCK_FIXUPPRECODE) + { + if (!FixupPrecode::IsFixupPrecodeByASM(target)) + { + // If the target slot points to the fixup part of the stub, the actual + // stub starts FixupPrecode::FixupCodeOffset bytes below the target, + // so we need to compensate for it. + target -= FixupPrecode::FixupCodeOffset; + if (!FixupPrecode::IsFixupPrecodeByASM(target)) + { + _ASSERTE(!"Invalid FixupPrecode address"); // We should never get other precode type here + return nullptr; + } + } + + return (MethodDesc*)((FixupPrecode*)PCODEToPINSTR(target))->GetMethodDesc(); + } + + return nullptr; +} + +void ReplaceInstrAfterCall(PBYTE instrToReplace, MethodDesc* callMD) +{ + ReturnKind returnKind = callMD->GetReturnKind(true); + if (!IsValidReturnKind(returnKind)) + { + // SKip GC coverage after the call. + return; + } + _ASSERTE(IsValidReturnKind(returnKind)); + + bool ispointerKind = IsPointerReturnKind(returnKind); + if (ispointerKind) + { + bool protectRegister[2] = { false, false }; + + bool moreRegisters = false; + + ReturnKind fieldKind1 = ExtractRegReturnKind(returnKind, 0, moreRegisters); + if (IsPointerFieldReturnKind(fieldKind1)) + { + protectRegister[0] = true; + } + if (moreRegisters) + { + ReturnKind fieldKind2 = ExtractRegReturnKind(returnKind, 1, moreRegisters); + if (IsPointerFieldReturnKind(fieldKind2)) + { + protectRegister[1] = true; + } + } + _ASSERTE(!moreRegisters); + + if (protectRegister[0] && !protectRegister[1]) + { + *instrToReplace = INTERRUPT_INSTR_PROTECT_FIRST_RET; + } + else + { + _ASSERTE(!"Not expected multi reg return with pointers."); + } + } + else + { + *instrToReplace = INTERRUPT_INSTR; + } +} + +void GCCoverageInfo::SprinkleBreakpoints( + BYTE * saveAddr, + PCODE pCode, + size_t codeSize, + size_t regionOffsetAdj, + BOOL fZapped) +{ +#if USE_DISASSEMBLER + + BYTE * codeStart = (BYTE *)pCode; + ExecutableWriterHolderNoLog codeWriterHolder; + size_t writeableOffset; + + memcpy(saveAddr, codeStart, codeSize); + + // For prejitted code we have to remove the write-protect on the code page + if (fZapped) + { + DWORD oldProtect; + ClrVirtualProtect(codeStart, codeSize, PAGE_EXECUTE_READWRITE, &oldProtect); + writeableOffset = 0; + } + else + { + codeWriterHolder.AssignExecutableWriterHolder(codeStart, codeSize); + writeableOffset = codeWriterHolder.GetRW() - codeStart; + } + + PBYTE cur; + BYTE* codeEnd = codeStart + codeSize; + + EECodeInfo codeInfo((PCODE)codeStart); + + static ConfigDWORD fGcStressOnDirectCalls; // ConfigDWORD must be a static variable + + cur = codeStart; + Disassembler disassembler; // When we find a direct call instruction and we are partially-interruptible // we determine the target and place a breakpoint after the call @@ -523,24 +550,6 @@ void GCCoverageInfo::SprinkleBreakpoints( InstructionType instructionType; size_t len = disassembler.DisassembleInstruction(cur, codeEnd - cur, &instructionType); -#ifdef TARGET_AMD64 - // REVISIT_TODO apparently the jit does not use the entire RUNTIME_FUNCTION range - // for code. It uses some for switch tables. Because the first few offsets - // may be decodable as instructions, we can't reason about where we should - // encounter invalid instructions. However, we do not want to silently skip - // large chunks of methods just because the JIT started emitting a new - // instruction, so only assume it is a switch table if we've seen the switch - // code (an indirect unconditional jump) - if ((len == 0) && fSawPossibleSwitch) - { - LOG((LF_JIT, LL_WARNING, "invalid instruction at %p (possibly start of switch table)\n", cur)); - cur = rangeEnum.SkipToNextRange(); - prevDirectCallTargetMD = NULL; - fSawPossibleSwitch = false; - continue; - } -#endif - _ASSERTE(len > 0); _ASSERTE(len <= (size_t)(codeEnd-cur)); @@ -551,40 +560,23 @@ void GCCoverageInfo::SprinkleBreakpoints( switch(instructionType) { case InstructionType::Call_IndirectUnconditional: -#ifdef TARGET_AMD64 - if(!(EECodeManager::InterruptibleSafePointsEnabled() && safePointDecoder.AreSafePointsInterruptible()) && - safePointDecoder.IsSafePoint((UINT32)(cur + len - codeStart + regionOffsetAdj))) -#endif - { - *(cur + writeableOffset) = INTERRUPT_INSTR_CALL; // return value. May need to protect - } + *(cur + writeableOffset) = INTERRUPT_INSTR_CALL; // return value. May need to protect break; case InstructionType::Call_DirectUnconditional: + // NB: turned off by default if(fGcStressOnDirectCalls.val(CLRConfig::INTERNAL_GcStressOnDirectCalls)) { -#ifdef TARGET_AMD64 - if(!(EECodeManager::InterruptibleSafePointsEnabled() && safePointDecoder.AreSafePointsInterruptible()) && - safePointDecoder.IsSafePoint((UINT32)(cur + len - codeStart + regionOffsetAdj))) -#endif - { - PBYTE nextInstr; - PBYTE target = getTargetOfCall(cur, NULL, &nextInstr); + PBYTE nextInstr; + PBYTE target = getTargetOfCall(cur, NULL, &nextInstr); - if (target != 0) - { - targetMD = getTargetMethodDesc((PCODE)target); - } + if (target != 0) + { + targetMD = getTargetMethodDesc((PCODE)target); } } break; -#ifdef TARGET_AMD64 - case InstructionType::Branch_IndirectUnconditional: - fSawPossibleSwitch = true; - break; -#endif - default: // Clang issues an error saying that some enum values are not handled in the switch, that's intended break; @@ -604,29 +596,17 @@ void GCCoverageInfo::SprinkleBreakpoints( *(cur + writeableOffset) = INTERRUPT_INSTR; } -#ifdef TARGET_X86 // we will whack every instruction in the prolog and epilog to make certain // our unwinding logic works there. if (codeMan->IsInPrologOrEpilog((cur - codeStart) + (DWORD)regionOffsetAdj, gcInfoToken, NULL)) { *(cur + writeableOffset) = INTERRUPT_INSTR; } -#endif // If we couldn't find the method desc targetMD is zero prevDirectCallTargetMD = targetMD; cur += len; - -#ifdef TARGET_AMD64 - PBYTE newCur = rangeEnum.EnsureInRange(cur); - if(newCur != cur) - { - prevDirectCallTargetMD = NULL; - cur = newCur; - fSawPossibleSwitch = false; - } -#endif } // If we are not able to place an interrupt at the first instruction, this means that @@ -636,839 +616,23 @@ void GCCoverageInfo::SprinkleBreakpoints( if ((regionOffsetAdj==0) && (*codeStart != INTERRUPT_INSTR)) doingEpilogChecks = false; -#elif defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - //Save the method code from hotRegion - memcpy(saveAddr, (BYTE*)methodRegion.hotStartAddress, methodRegion.hotSize); - - if (methodRegion.coldSize > 0) - { - //Save the method code from coldRegion - memcpy(saveAddr+methodRegion.hotSize, (BYTE*)methodRegion.coldStartAddress, methodRegion.coldSize); - } +#endif // USE_DISASSEMBLER +} - // For prejitted code we have to remove the write-protect on the code page - if (fZapped) - { - DWORD oldProtect; - ClrVirtualProtect((BYTE*)methodRegion.hotStartAddress, methodRegion.hotSize, PAGE_EXECUTE_READWRITE, &oldProtect); +void checkAndUpdateReg(DWORD& origVal, DWORD curVal, bool gcHappened) { + if (origVal == curVal) + return; - if (methodRegion.coldSize > 0) - { - ClrVirtualProtect((BYTE*)methodRegion.coldStartAddress, methodRegion.coldSize, PAGE_EXECUTE_READWRITE, &oldProtect); - } - } + // If these asserts go off, they indicate either that unwinding out of a epilog is wrong or that + // the validation infrastructure has got a bug. - GcInfoDecoder safePointDecoder(gcInfoToken, (GcInfoDecoderFlags)0, 0); + _ASSERTE(gcHappened); // If the register values are different, a GC must have happened + _ASSERTE(GCHeapUtilities::GetGCHeap()->IsHeapPointer((BYTE*) size_t(origVal))); // And the pointers involved are on the GCHeap + _ASSERTE(GCHeapUtilities::GetGCHeap()->IsHeapPointer((BYTE*) size_t(curVal))); + origVal = curVal; // this is now the best estimate of what should be returned. +} - assert(methodRegion.hotSize > 0); - -#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED - safePointDecoder.EnumerateSafePoints(&replaceSafePointInstructionWithGcStressInstr,this); -#endif // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED - - safePointDecoder.EnumerateInterruptibleRanges(&replaceInterruptibleRangesWithGcStressInstr, this); - - FlushInstructionCache(GetCurrentProcess(), (BYTE*)methodRegion.hotStartAddress, methodRegion.hotSize); - - if (methodRegion.coldSize > 0) - { - FlushInstructionCache(GetCurrentProcess(), (BYTE*)methodRegion.coldStartAddress, methodRegion.coldSize); - } - -#else - _ASSERTE(!"not implemented for platform"); -#endif // TARGET_X86 -} - -#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - -#ifdef TARGET_RISCV64 -enum -{ - REG_RA = 1, - JAL = 0x6f, - JALR = 0x67, -}; -#endif - -#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED - -void replaceSafePointInstructionWithGcStressInstr(GcInfoDecoder* decoder, UINT32 safePointOffset, LPVOID pGCCover) -{ - PCODE pCode = (PCODE)NULL; - IJitManager::MethodRegionInfo *ptr = &(((GCCoverageInfo*)pGCCover)->methodRegion); - - //Get code address from offset - if (safePointOffset < ptr->hotSize) - pCode = ptr->hotStartAddress + safePointOffset; - else if(safePointOffset - ptr->hotSize < ptr->coldSize) - { - SIZE_T coldOffset = safePointOffset - ptr->hotSize; - pCode = ptr->coldStartAddress + coldOffset; - } - else - { - //For some methods( eg MCCTest.MyClass.GetSum2 in test file jit\jit64\mcc\interop\mcc_i07.il) gcinfo points to a safepoint - //beyond the length of the method. So commenting the below assert. - //_ASSERTE(safePointOffset - ptr->hotSize < ptr->coldSize); - return; - } - - PBYTE instrPtr = (BYTE*)PCODEToPINSTR(pCode); - - // if this is an interruptible safe point, just replace it with an interrupt instr and we are done. - if (EECodeManager::InterruptibleSafePointsEnabled() && decoder->AreSafePointsInterruptible()) - { - // The instruction about to be replaced cannot already be a gcstress instruction - _ASSERTE(!IsGcCoverageInterruptInstruction(instrPtr)); - - ExecutableWriterHolder instrPtrWriterHolder(instrPtr, sizeof(DWORD)); -#if defined(TARGET_ARM) - size_t instrLen = GetARMInstructionLength(instrPtr); - - if (instrLen == 2) - *((WORD*)instrPtrWriterHolder.GetRW()) = INTERRUPT_INSTR; - else - { - *((DWORD*)instrPtrWriterHolder.GetRW()) = INTERRUPT_INSTR_32; - } -#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - *((DWORD*)instrPtrWriterHolder.GetRW()) = INTERRUPT_INSTR; -#endif // TARGET_XXXX_ - return; - } - - // For code sequences of the type - // BL func1 - // BL func2 // Safe point 1 - // mov r1 r0 // Safe point 2 - // Both the above safe points instruction must be replaced with gcStress instruction. - // However as the first safe point is already replaced with gcstress instruction, decoding of the call - // instruction will fail when processing for the 2nd safe point. Therefore saved instruction must be used instead of - // instrPtr for decoding the call instruction. - PBYTE savedInstrPtr = ((GCCoverageInfo*)pGCCover)->savedCode + safePointOffset; - - //Determine if instruction before the safe point is call using immediate (BLX Imm) or call by register (BLX Rm) - BOOL instructionIsACallThroughRegister = FALSE; - BOOL instructionIsACallThroughImmediate = FALSE; - -#if defined(TARGET_ARM) - - // POSSIBLE BUG: Note that we are looking backwards by 2 or 4 bytes, looking for particular call instruction encodings. - // However, we don't know if the previous instruction is 2 bytes or 4 bytes. Looking back 2 bytes could be looking into - // the middle of a 4-byte instruction. The only safe way to do this is by walking forward from the first instruction of - // the function. - - // call by register instruction is two bytes (BL Reg T1 encoding) - WORD instr = *((WORD*)savedInstrPtr - 1); - instr = instr & 0xff87; - if ((instr ^ 0x4780) == 0) - { - // It is call by register - instructionIsACallThroughRegister = TRUE; - } - else - { - // call using immediate instructions are 4 bytes (BL