From 352ebcc106e510aa472df4bc657af987b56b9278 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 15 May 2026 16:06:38 -0700 Subject: [PATCH 01/13] [cDAC] Implement DacDbi GetNativeCodeInfo / GetNativeCodeInfoForAddr Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/EnC.md | 89 +++++++ docs/design/datacontracts/Loader.md | 11 + .../design/datacontracts/RuntimeTypeSystem.md | 47 +++- src/coreclr/debug/daccess/dacdbiimpl.cpp | 65 +---- src/coreclr/debug/daccess/dacdbiimpl.h | 7 +- src/coreclr/debug/di/divalue.cpp | 2 +- src/coreclr/debug/di/module.cpp | 4 +- src/coreclr/debug/ee/functioninfo.cpp | 9 + src/coreclr/debug/inc/dacdbistructures.h | 2 +- src/coreclr/vm/ceeload.h | 56 +++- .../vm/datadescriptor/datadescriptor.inc | 17 ++ .../ContractRegistry.cs | 4 + .../Contracts/IEnC.cs | 17 ++ .../Contracts/ILoader.cs | 1 + .../Contracts/IRuntimeTypeSystem.cs | 5 + .../Constants.cs | 1 + .../Contracts/EnC_1.cs | 69 +++++ .../Contracts/Loader_1.cs | 11 + .../Contracts/RuntimeTypeSystem_1.cs | 42 ++- .../CoreCLRContracts.cs | 2 + .../Data/EnCData.cs | 25 ++ .../Data/Module.cs | 6 + .../DataType.cs | 1 + .../EcmaMetadataUtils.cs | 1 + .../Dbi/DacDbiImpl.cs | 241 +++++++++++++++++- .../Dbi/IDacDbiInterface.cs | 16 +- 26 files changed, 680 insertions(+), 71 deletions(-) create mode 100644 docs/design/datacontracts/EnC.md create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEnC.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EnC_1.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCData.cs diff --git a/docs/design/datacontracts/EnC.md b/docs/design/datacontracts/EnC.md new file mode 100644 index 00000000000000..a05e3d2da373e4 --- /dev/null +++ b/docs/design/datacontracts/EnC.md @@ -0,0 +1,89 @@ +# Contract EnC + +This contract reports Edit and Continue (EnC) function version numbers for jitted +managed methods. EnC function versions are 1-based monotonically increasing counters +that the runtime assigns to each `EnC`-emitted instance of a method body. + +## APIs of contract + +``` csharp +// Returns the latest EnC version number associated with the method identified by +// (module, methodDef). If no EnC-jitted instance exists for that method, returns +// the default EnC function version (1). +TargetNUInt GetLatestEnCVersion(TargetPointer module, uint methodDef); + +// Returns the EnC version number for the specific jitted instance of the method +// identified by (module, methodDef) whose hot region starts at the given native +// code address. If no matching jitted instance exists (for example, the method +// was never EnC-edited), returns the default EnC function version (1). +TargetNUInt GetEnCVersion(TargetPointer module, uint methodDef, TargetCodePointer nativeCodeAddress); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Type | Purpose | +| --- | --- | --- | --- | +| `Module` | `EnCDataList` | nuint | Head of the singly linked list of `EnCData` entries for jitted EnC-versioned methods in this module | +| `EnCData` | `AddrOfCode` | nuint | Native code start (TADDR) for the jitted instance | +| `EnCData` | `Token` | uint32 | `mdMethodDef` token of the method | +| `EnCData` | `EnCVersion` | nuint | EnC function version number for this jitted instance | +| `EnCData` | `Next` | nuint | Next entry in the module's `EnCData` list, or null | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| `CorDBDefaultEnCFunctionVersion` | nuint | Default EnC function version reported for methods that have never been EnC-edited (matches `CorDB_DEFAULT_ENC_FUNCTION_VERSION` in `src/coreclr/inc/cordbpriv.h`) | + +Contracts used: none + +``` csharp +// Returns the address of the first EnCData entry on module's EnCDataList whose Token +// matches methodDef and (when addrOrZero is non-null) whose AddrOfCode matches +// addrOrZero. Returns TargetPointer.Null if no entry matches. +TargetPointer FindEnCDataEntry(TargetPointer module, uint methodDef, + TargetPointer addrOrZero) +{ + TargetPointer cur = _target.ReadPointer(module + /* Module::EnCDataList offset */); + while (cur != TargetPointer.Null) + { + uint token = _target.Read(cur + /* EnCData::Token offset */); + TargetPointer addrOfCode = _target.ReadPointer(cur + /* EnCData::AddrOfCode offset */); + if (token == methodDef && + (addrOrZero == TargetPointer.Null || addrOfCode == addrOrZero)) + { + return cur; + } + cur = _target.ReadPointer(cur + /* EnCData::Next offset */); + } + return TargetPointer.Null; +} +``` + +``` csharp +TargetNUInt GetLatestEnCVersion(TargetPointer module, uint methodDef) +{ + TargetPointer entry = FindEnCDataEntry(module, methodDef, TargetPointer.Null); + if (entry == TargetPointer.Null) + return new TargetNUInt(/* CorDBDefaultEnCFunctionVersion global */); + + return _target.ReadNUInt(entry + /* EnCData::EnCVersion offset */); +} +``` + +``` csharp +TargetNUInt GetEnCVersion(TargetPointer module, uint methodDef, + TargetCodePointer nativeCodeAddress) +{ + if (nativeCodeAddress.Value == 0) + return new TargetNUInt(/* CorDBDefaultEnCFunctionVersion global */); + + TargetPointer entry = FindEnCDataEntry(module, methodDef, + new TargetPointer(nativeCodeAddress.Value)); + if (entry == TargetPointer.Null) + return new TargetNUInt(/* CorDBDefaultEnCFunctionVersion global */); + + return _target.ReadNUInt(entry + /* EnCData::EnCVersion offset */); +} +``` + diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 8e9d6ccb6f98d9..54addba164cc98 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -103,6 +103,7 @@ TargetPointer GetILBase(ModuleHandle handle); TargetPointer GetAssemblyLoadContext(ModuleHandle handle); ModuleLookupTables GetLookupTables(ModuleHandle handle); TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags); +TargetPointer LookupMemberRefAsMethod(ModuleHandle handle, uint memberRefToken); IEnumerable<(TargetPointer, uint)> EnumerateModuleLookupMap(TargetPointer table); bool IsCollectible(ModuleHandle handle); bool IsDynamic(ModuleHandle handle); @@ -254,6 +255,7 @@ enum ClrModifiableAssemblies : uint | `MaxWebcilSections` | ushort | Maximum number of COFF sections supported in a Webcil image (must stay in sync with native `WEBCIL_MAX_SECTIONS`) | `16` | | `DebuggerInfoMask` | uint | Mask for the debugger info bits within the Module's transient flags | `0x0000FC00` | | `DebuggerInfoShift` | int | Bit shift for the debugger info bits within the Module's transient flags | `10` | +| `IS_FIELD_MEMBER_REF` | TADDR (target pointer-sized unsigned int) | Flag on `MemberRefToDescMap` entries indicating the entry is a FieldDesc, not a MethodDesc | `0x00000002` | | `DEBUGGER_ALLOW_JIT_OPTS_PRIV` | uint | Debugger allows JIT optimizations (shifted in transient flags) | `0x00000800` | Contracts used: @@ -717,6 +719,15 @@ TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out Tar return TargetPointer.Null; } +// Returns the MethodDesc pointer for the given mdMemberRef token, or TargetPointer.Null +// if the entry is a FieldDesc (flagged with IS_FIELD_MEMBER_REF) or not present. +TargetPointer LookupMemberRefAsMethod(ModuleHandle handle, uint memberRefToken); +{ + ModuleLookupTables tables = GetLookupTables(handle); + TargetPointer ptr = GetModuleLookupMapElement(tables.MemberRefToDesc, memberRefToken, out TargetNUInt flags); + return (flags.Value & /* IS_FIELD_MEMBER_REF */) != 0 ? TargetPointer.Null : ptr; +} + IEnumerable<(TargetPointer, uint)> EnumerateModuleLookupMap(TargetPointer table) { Data.ModuleLookupMap lookupMap = new Data.ModuleLookupMap(table); diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 0ce6cca2e5a141..d605d7cc88ce86 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -244,6 +244,10 @@ partial interface IRuntimeTypeSystem : IContract // Return true if the method is an async thunk method. public virtual bool IsAsyncThunkMethod(MethodDescHandle methodDesc); + // Return the async variant MethodDesc for the given method, or null if not found. + // Mirrors the no-create path of native MethodDesc::GetAsyncVariantNoCreate. + public virtual MethodDescHandle? GetAsyncVariant(MethodDescHandle methodDesc); + // Return true if the method is a wrapper stub (unboxing or instantiating). public virtual bool IsWrapperStub(MethodDescHandle methodDesc); @@ -1212,8 +1216,11 @@ And the following enumeration definitions [Flags] internal enum AsyncMethodFlags : uint { - None = 0, - Thunk = 16, + None = 0x0, + AsyncCall = 0x1, + IsAsyncVariant = 0x4, + Thunk = 0x10, + ReturnDroppingThunk = 0x20, } [Flags] @@ -1698,6 +1705,42 @@ Determining if a method is a wrapper stub (unboxing or instantiating): } ``` +Resolving the async variant of an async thunk method (no-create): + +```csharp + // Helper: matches native MatchesAsyncVariantLookup(AsyncVariantLookup::Async). + private bool IsAsyncVariantMethod(MethodDesc md) + { + if (!md.HasAsyncMethodData) + return false; + + Data.AsyncMethodData asyncData = // Read AsyncMethodData from the async method data optional slot + AsyncMethodFlags flags = (AsyncMethodFlags)asyncData.Flags; + return flags.HasFlag(AsyncMethodFlags.IsAsyncVariant) && !flags.HasFlag(AsyncMethodFlags.ReturnDroppingThunk); + } + + public MethodDescHandle? GetAsyncVariant(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + uint token = md.Token; + TypeHandle typeHandle = GetTypeHandle(GetMethodTable(methodDescHandle)); + + // For typical method definitions (non-generic or generic-with-formal-vars), native's + // FindOrCreateAssociatedMethodDesc returns the result of MethodTable::GetParallelMethodDesc + // directly. The canonical MethodTable holds the chunk of sibling MethodDescs that share + // a token; one of them carries the async-variant flag combination. + TypeHandle canonMT = GetTypeHandle(GetCanonicalMethodTable(typeHandle)); + foreach (MethodDescHandle candidate in GetIntroducedMethods(canonMT)) + { + MethodDesc candidateMd = _methodDescs[candidate.Address]; + if (candidateMd.Token == token && IsAsyncVariantMethod(candidateMd)) + return candidate; + } + + return null; + } +``` + Extracting a pointer to the `MethodDescVersioningState` data for a given method ```csharp diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index c4d079ac828453..15c07ceb173f45 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -1255,8 +1255,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeInfo(VMPTR_Assembly if (pCodeInfo->m_rgCodeRegions[kHot].pAddress != (CORDB_ADDRESS)NULL) { pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation(); - LookupEnCVersions(pModule, - pCodeInfo->vmNativeCodeMethodDescToken, + LookupEnCVersions(pCodeInfo->vmNativeCodeMethodDescToken, functionToken, pCodeInfo->m_rgCodeRegions[kHot].pAddress, &(pCodeInfo->encVersion)); @@ -1337,11 +1336,10 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeInfoForAddr(CORDB_AD pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation(); pCodeInfo->vmNativeCodeMethodDescToken = vmMethodDesc; - SIZE_T unusedLatestEncVersion; + ULONG64 unusedLatestEncVersion; Module * pModule = pMethodDesc->GetModule(); _ASSERTE(pModule != NULL); - LookupEnCVersions(pModule, - vmMethodDesc, + LookupEnCVersions(vmMethodDesc, pMethodDesc->GetMemberDef(), codeStartAddr, &unusedLatestEncVersion, //unused by caller @@ -5602,60 +5600,23 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetPartialUserState(VMPTR_Thread // thinking is that some of the RS data structures will remain, most likely in a reduced form. // -void DacDbiInterfaceImpl::LookupEnCVersions(Module* pModule, - VMPTR_MethodDesc vmMethodDesc, +void DacDbiInterfaceImpl::LookupEnCVersions(VMPTR_MethodDesc vmMethodDesc, mdMethodDef mdMethod, CORDB_ADDRESS pNativeStartAddress, - SIZE_T * pLatestEnCVersion, - SIZE_T * pJittedInstanceEnCVersion /* = NULL */) + ULONG64 * pLatestEnCVersion, + ULONG64 * pJittedInstanceEnCVersion /* = NULL */) { - MethodDesc * pMD = vmMethodDesc.GetDacPtr(); - - // make sure the vmMethodDesc and mdMethod match - _ASSERTE(pMD->GetMemberDef() == mdMethod); - + MethodDesc * pMD = vmMethodDesc.GetDacPtr(); _ASSERTE(pLatestEnCVersion != NULL); - // @dbgtodo inspection - once we do EnC, stop using DMIs. - // If the method wasn't EnCed, DMIs may not exist. And since this is DAC, we can't create them. - - // We may not have the memory for the DebuggerMethodInfos in a minidump. - // When dump debugging EnC information isn't very useful so just fallback - // to default version. - DebuggerMethodInfo * pDMI = NULL; - DebuggerJitInfo * pDJI = NULL; - EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY + Module* pLoaderModule = pMD->GetLoaderModule(); + PTR_EnCData pEnCData = pLoaderModule->FindEncData(mdMethod, CORDB_ADDRESS_TO_TADDR(pNativeStartAddress)); + PTR_EnCData pLatestEncData = pLoaderModule->FindLatestEncData(mdMethod); + if (pJittedInstanceEnCVersion != NULL) { - if (g_pDebugger != NULL) - { - pDMI = g_pDebugger->GetOrCreateMethodInfo(pModule, mdMethod); - if (pDMI != NULL) - { - pDJI = pDMI->FindJitInfo(pMD, CORDB_ADDRESS_TO_TADDR(pNativeStartAddress)); - } - } - } - EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY; - if (pDJI != NULL) - { - if (pJittedInstanceEnCVersion != NULL) - { - *pJittedInstanceEnCVersion = pDJI->m_encVersion; - } - *pLatestEnCVersion = pDMI->GetCurrentEnCVersion(); - } - else - { - // If we have no DMI/DJI, then we must never have EnCed. So we can use default EnC info - // Several cases where we don't have a DMI/DJI: - // - LCG methods - // - method was never "touched" by debugger. (DJIs are created lazily). - if (pJittedInstanceEnCVersion != NULL) - { - *pJittedInstanceEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; - } - *pLatestEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; + *pJittedInstanceEnCVersion = (pEnCData != NULL) ? pEnCData->encVersion : CorDB_DEFAULT_ENC_FUNCTION_VERSION; } + *pLatestEnCVersion = (pLatestEncData != NULL) ? pLatestEncData->encVersion : CorDB_DEFAULT_ENC_FUNCTION_VERSION; } // Get the address of the Debugger control block on the helper thread diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index 171b067e02afe4..6b11cea6a06af6 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -883,12 +883,11 @@ class DacDbiInterfaceImpl : BOOL UnwindRuntimeStackFrame(StackFrameIterator * pIter); // Look up the EnC version number of a particular jitted instance of a managed method. - void LookupEnCVersions(Module* pModule, - VMPTR_MethodDesc vmMethodDesc, + void LookupEnCVersions(VMPTR_MethodDesc vmMethodDesc, mdMethodDef mdMethod, CORDB_ADDRESS pNativeStartAddress, - SIZE_T * pLatestEnCVersion, - SIZE_T * pJittedInstanceEnCVersion = NULL); + ULONG64 * pLatestEnCVersion, + ULONG64 * pJittedInstanceEnCVersion = NULL); // @dbgtodo - This method should be removed once CordbFunctionBreakpoint and SetIP are moved OOP and // no longer use nativeCodeJITInfoToken. diff --git a/src/coreclr/debug/di/divalue.cpp b/src/coreclr/debug/di/divalue.cpp index 709b33281d8736..7f873508b2e1e1 100644 --- a/src/coreclr/debug/di/divalue.cpp +++ b/src/coreclr/debug/di/divalue.cpp @@ -2718,7 +2718,7 @@ HRESULT CordbObjectValue::GetFunctionHelper(ICorDebugFunction **ppFunction) IfFailThrow(pDAC->GetNativeCodeInfo(functionAssembly, functionMethodDef, &nativeCodeForDelFunc)); RSSmartPtr funcModule(GetAppDomain()->LookupOrCreateModule(functionAssembly)); - func.Assign(funcModule->LookupOrCreateFunction(functionMethodDef, nativeCodeForDelFunc.encVersion)); + func.Assign(funcModule->LookupOrCreateFunction(functionMethodDef, (SIZE_T)nativeCodeForDelFunc.encVersion)); } *ppFunction = static_cast (func.GetValue()); diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 5dac6157145ab8..97d299cce098cf 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -3902,7 +3902,7 @@ HRESULT CordbVariableHome::GetOffset(LONG *pOffset) CordbNativeCode::CordbNativeCode(CordbFunction * pFunction, const NativeCodeFunctionData * pJitData, BOOL fIsInstantiatedGeneric) - : CordbCode(pFunction, (UINT_PTR)pJitData->m_rgCodeRegions[kHot].pAddress, pJitData->encVersion, FALSE), + : CordbCode(pFunction, (UINT_PTR)pJitData->m_rgCodeRegions[kHot].pAddress, (SIZE_T)pJitData->encVersion, FALSE), m_vmNativeCodeMethodDescToken(pJitData->vmNativeCodeMethodDescToken), m_fCodeAvailable(TRUE), m_fIsInstantiatedGeneric(fIsInstantiatedGeneric != FALSE) @@ -5093,7 +5093,7 @@ CordbNativeCode * CordbModule::LookupOrCreateNativeCode(mdMethodDef methodToken, codeInfo.m_rgCodeRegions[kHot].cbSize)); // Lookup the function object that this code should be bound to - CordbFunction* pFunction = CordbModule::LookupOrCreateFunction(methodToken, codeInfo.encVersion); + CordbFunction* pFunction = CordbModule::LookupOrCreateFunction(methodToken, (SIZE_T)codeInfo.encVersion); _ASSERTE(pFunction != NULL); // There are bugs with the on-demand class load performed by CordbFunction in some cases. The old stack diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index 3ee9d46810e911..4c3efbab9df2c3 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -1226,6 +1226,15 @@ void DebuggerJitInfo::Init(TADDR newAddress) this->m_sizeOfCode = this->m_codeRegionInfo.getSizeOfTotalCode(); this->m_encVersion = this->m_methodInfo->GetCurrentEnCVersion(); + if (this->m_encVersion != CorDB_DEFAULT_ENC_FUNCTION_VERSION) + { + Module* pModule = this->m_pLoaderModule; + EnCData* pEnCData = (EnCData*)(void*)pModule->GetLoaderAllocator()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(EnCData))); + pEnCData->addrOfCode = this->m_addrOfCode; + pEnCData->token = this->m_methodInfo->m_token; + pEnCData->encVersion = this->m_encVersion; + pModule->AddEncData(pEnCData); + } this->InitFuncletAddress(); diff --git a/src/coreclr/debug/inc/dacdbistructures.h b/src/coreclr/debug/inc/dacdbistructures.h index aff80317c59b32..2a723a990ab85e 100644 --- a/src/coreclr/debug/inc/dacdbistructures.h +++ b/src/coreclr/debug/inc/dacdbistructures.h @@ -509,7 +509,7 @@ class MSLAYOUT NativeCodeFunctionData VMPTR_MethodDesc vmNativeCodeMethodDescToken; // EnC version number of the function - SIZE_T encVersion; + ULONG64 encVersion; }; //---------------------------------------------------------------------------------- diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 24737cb71c8576..247ad9916cf89d 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -62,6 +62,15 @@ class JITInlineTrackingMap; #ifdef FEATURE_METADATA_UPDATER class EnCEEClassData; +struct EnCData; +typedef DPTR(struct EnCData) PTR_EnCData; +struct EnCData +{ + TADDR addrOfCode; + mdMethodDef token; + SIZE_T encVersion; + PTR_EnCData pNext; +}; #endif // FEATURE_METADATA_UPDATER // Hash table parameter of available classes (name -> module/class) hash @@ -326,7 +335,7 @@ typedef DPTR(class MemberRef) PTR_MemberRef; // flag used to mark member ref pointers to field descriptors in the member ref cache -#define IS_FIELD_MEMBER_REF ((TADDR)0x00000002) +#define IS_FIELD_MEMBER_REF ((TADDR)0x00000002) // [cDAC] [Loader]: Contract depends on this value. // @@ -949,6 +958,48 @@ class Module : public ModuleBase #ifdef FEATURE_METADATA_UPDATER // Holds a table of EnCEEClassData object for classes in this module that have been modified CUnorderedArray m_ClassList; + + PTR_EnCData m_pEnCDataList = nullptr; + +public: + void AddEncData(EnCData* pData) + { + LIMITED_METHOD_CONTRACT; + pData->pNext = m_pEnCDataList; + m_pEnCDataList = dac_cast(pData); + } + + PTR_EnCData FindEncData(mdMethodDef token, TADDR addrOfCode) + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + for (PTR_EnCData pCur = m_pEnCDataList; pCur != nullptr; pCur = pCur->pNext) + { + if (pCur->token == token && pCur->addrOfCode == addrOfCode) + { + return pCur; + } + } + + return nullptr; + } + + PTR_EnCData FindLatestEncData(mdMethodDef token) + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + for (PTR_EnCData pCur = m_pEnCDataList; pCur != nullptr; pCur = pCur->pNext) + { + if (pCur->token == token) + { + return pCur; + } + } + + return nullptr; + } + +protected: #endif // FEATURE_METADATA_UPDATER private: @@ -1728,6 +1779,9 @@ struct cdac_data static constexpr size_t MethodDefToILCodeVersioningStateMap = offsetof(Module, m_ILCodeVersioningStateMap); #endif // FEATURE_CODE_VERSIONING static constexpr size_t DynamicILBlobTable = offsetof(Module, m_debuggerSpecificData.m_pDynamicILBlobTable); +#ifdef FEATURE_METADATA_UPDATER + static constexpr size_t EnCDataList = offsetof(Module, m_pEnCDataList); +#endif // FEATURE_METADATA_UPDATER }; // diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index fb94ca4218e96f..5d5ac0b27534ff 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -277,8 +277,21 @@ CDAC_TYPE_FIELD(Module, T_POINTER, TypeRefToMethodTableMap, cdac_data::T CDAC_TYPE_FIELD(Module, T_POINTER, MethodDefToILCodeVersioningStateMap, cdac_data::MethodDefToILCodeVersioningStateMap) #endif // FEATURE_CODE_VERSIONING CDAC_TYPE_FIELD(Module, T_POINTER, DynamicILBlobTable, cdac_data::DynamicILBlobTable) +#ifdef FEATURE_METADATA_UPDATER +CDAC_TYPE_FIELD(Module, T_POINTER, EnCDataList, cdac_data::EnCDataList) +#endif // FEATURE_METADATA_UPDATER CDAC_TYPE_END(Module) +#ifdef FEATURE_METADATA_UPDATER +CDAC_TYPE_BEGIN(EnCData) +CDAC_TYPE_INDETERMINATE(EnCData) +CDAC_TYPE_FIELD(EnCData, T_POINTER, AddrOfCode, offsetof(EnCData, addrOfCode)) +CDAC_TYPE_FIELD(EnCData, T_UINT32, Token, offsetof(EnCData, token)) +CDAC_TYPE_FIELD(EnCData, T_NUINT, EnCVersion, offsetof(EnCData, encVersion)) +CDAC_TYPE_FIELD(EnCData, T_POINTER, Next, offsetof(EnCData, pNext)) +CDAC_TYPE_END(EnCData) +#endif // FEATURE_METADATA_UPDATER + CDAC_TYPE_BEGIN(ModuleLookupMap) CDAC_TYPE_FIELD(ModuleLookupMap, T_POINTER, TableData, offsetof(LookupMapBase, pTable)) CDAC_TYPE_FIELD(ModuleLookupMap, T_POINTER, Next, offsetof(LookupMapBase, pNext)) @@ -1520,6 +1533,7 @@ CDAC_GLOBAL(DirectorySeparator, T_UINT8, (uint8_t)DIRECTORY_SEPARATOR_CHAR_A) CDAC_GLOBAL(HashMapSlotsPerBucket, T_UINT32, SLOTS_PER_BUCKET) CDAC_GLOBAL(HashMapValueMask, T_UINT64, VALUE_MASK) CDAC_GLOBAL(MethodDescAlignment, T_UINT64, MethodDesc::ALIGNMENT) +CDAC_GLOBAL(CorDBDefaultEnCFunctionVersion, T_NUINT, CorDB_DEFAULT_ENC_FUNCTION_VERSION) CDAC_GLOBAL(ArrayBaseSize, T_UINT32, ARRAYBASE_BASESIZE) CDAC_GLOBAL(SyncBlockValueToObjectOffset, T_UINT16, OBJHEADER_SIZE - cdac_data::SyncBlockValue) CDAC_GLOBAL(StubCodeBlockLast, T_UINT8, STUB_CODE_BLOCK_LAST) @@ -1622,6 +1636,9 @@ CDAC_GLOBAL_CONTRACT(Debugger, c1) #endif // DEBUGGING_SUPPORTED && !TARGET_WASM CDAC_GLOBAL_CONTRACT(DebugInfo, c2) CDAC_GLOBAL_CONTRACT(EcmaMetadata, c1) +#ifdef FEATURE_METADATA_UPDATER +CDAC_GLOBAL_CONTRACT(EnC, c1) +#endif // FEATURE_METADATA_UPDATER CDAC_GLOBAL_CONTRACT(Exception, c1) CDAC_GLOBAL_CONTRACT(ExecutionManager, c2) CDAC_GLOBAL_CONTRACT(GCInfo, c1) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 17cfcd1000ee19..70b9faed26d7a5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -62,6 +62,10 @@ public abstract class ContractRegistry /// public virtual IReJIT ReJIT => GetContract(); /// + /// Gets an instance of the EnC contract for the target. + /// + public virtual IEnC EnC => GetContract(); + /// /// Gets an instance of the StackWalk contract for the target. /// public virtual IStackWalk StackWalk => GetContract(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEnC.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEnC.cs new file mode 100644 index 00000000000000..f3103b7c336f8c --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEnC.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public interface IEnC : IContract +{ + static string IContract.Name { get; } = nameof(EnC); + TargetNUInt GetLatestEnCVersion(TargetPointer module, uint methodDef) => throw new NotImplementedException(); + TargetNUInt GetEnCVersion(TargetPointer module, uint methodDef, TargetCodePointer nativeCodeAddress) => throw new NotImplementedException(); +} + +public readonly struct EnC : IEnC +{ +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index cdf2b16e1d5899..f17a05c7fc8bbd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -126,6 +126,7 @@ public interface ILoader : IContract TargetPointer GetAssemblyLoadContext(ModuleHandle handle) => throw new NotImplementedException(); ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags) => throw new NotImplementedException(); + TargetPointer LookupMemberRefAsMethod(ModuleHandle handle, uint memberRefToken) => throw new NotImplementedException(); IEnumerable<(TargetPointer, uint)> EnumerateModuleLookupMap(TargetPointer table) => throw new NotImplementedException(); bool IsCollectible(ModuleHandle handle) => throw new NotImplementedException(); bool IsDynamic(ModuleHandle handle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 3d1c8cf60fbaaf..361a6950bfabe8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -251,6 +251,11 @@ public interface IRuntimeTypeSystem : IContract bool IsAsyncThunkMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); + // Given an async thunk method, find its async variant counterpart (the MethodDesc with the real async body). + // Returns null if the variant is not found (not yet loaded). + // Mirrors native GetAsyncVariantNoCreate(). + MethodDescHandle? GetAsyncVariant(MethodDescHandle methodDesc) => throw new NotImplementedException(); + bool IsWrapperStub(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs #region FieldDesc inspection APIs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 808b28a8b44de6..8d752cfe3a8e3c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -30,6 +30,7 @@ public static class Globals public const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask); public const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); public const string RecommendedReaderVersion = nameof(RecommendedReaderVersion); + public const string CorDBDefaultEnCFunctionVersion = nameof(CorDBDefaultEnCFunctionVersion); public const string ContinuationMethodTable = nameof(ContinuationMethodTable); public const string ExceptionMethodTable = nameof(ExceptionMethodTable); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EnC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EnC_1.cs new file mode 100644 index 00000000000000..598fe3478df5c0 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EnC_1.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct EnC_1 : IEnC +{ + private readonly Target _target; + private readonly ulong _defaultEnCFunctionVersion; + + public EnC_1(Target target) + { + _target = target; + _defaultEnCFunctionVersion = target.ReadGlobal(Constants.Globals.CorDBDefaultEnCFunctionVersion); + } + + TargetNUInt IEnC.GetLatestEnCVersion(TargetPointer module, uint methodDef) + { + Data.EnCData? entry = FindFirstByToken(module, methodDef); + return entry is null + ? new TargetNUInt(_defaultEnCFunctionVersion) + : entry.EnCVersion; + } + + TargetNUInt IEnC.GetEnCVersion(TargetPointer module, uint methodDef, TargetCodePointer nativeCodeAddress) + { + if (nativeCodeAddress.Value == 0) + { + return new TargetNUInt(_defaultEnCFunctionVersion); + } + + // EnCData entries store native code start addresses as TADDR (already stripped of any thumb bit). + TargetPointer addr = new TargetPointer(nativeCodeAddress.Value); + + Data.Module moduleData = _target.ProcessedData.GetOrAdd(module); + TargetPointer cur = moduleData.EnCDataList; + while (cur != TargetPointer.Null) + { + Data.EnCData entry = _target.ProcessedData.GetOrAdd(cur); + if (entry.Token == methodDef && entry.AddrOfCode == addr) + { + return entry.EnCVersion; + } + cur = entry.Next; + } + + return new TargetNUInt(_defaultEnCFunctionVersion); + } + + private Data.EnCData? FindFirstByToken(TargetPointer module, uint methodDef) + { + Data.Module moduleData = _target.ProcessedData.GetOrAdd(module); + TargetPointer cur = moduleData.EnCDataList; + while (cur != TargetPointer.Null) + { + Data.EnCData entry = _target.ProcessedData.GetOrAdd(cur); + if (entry.Token == methodDef) + { + return entry; + } + cur = entry.Next; + } + + return null; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 36c59a7829972f..8d0f866e9abe04 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -31,6 +31,9 @@ private enum ModuleFlags_1 : uint private const uint DEBUGGER_ALLOW_JIT_OPTS_PRIV = 0x00000800; + // Flag on MemberRefToDescMap entries indicating the entry is a FieldDesc, not a MethodDesc. + private const ulong IS_FIELD_MEMBER_REF = 0x00000002; + private enum PEImageFlags : uint { FLAG_MAPPED = 0x01, // the file is mapped/hydrated (vs. the raw disk layout) @@ -546,6 +549,14 @@ TargetPointer ILoader.GetModuleLookupMapElement(TargetPointer table, uint token, return rval & ~supportedFlagsMask; } + TargetPointer ILoader.LookupMemberRefAsMethod(ModuleHandle handle, uint memberRefToken) + { + ILoader self = this; + ModuleLookupTables tables = self.GetLookupTables(handle); + TargetPointer ptr = self.GetModuleLookupMapElement(tables.MemberRefToDesc, memberRefToken, out TargetNUInt flags); + return (flags.Value & IS_FIELD_MEMBER_REF) != 0 ? TargetPointer.Null : ptr; + } + IEnumerable<(TargetPointer, uint)> ILoader.EnumerateModuleLookupMap(TargetPointer table) { if (table == TargetPointer.Null) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index cd9fb666c51962..5f098cfd9d2faf 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -154,9 +154,11 @@ internal enum DynamicMethodDescExtendedFlags : uint [Flags] internal enum AsyncMethodFlags : uint { - None = 0, + None = 0x0, AsyncCall = 0x1, - Thunk = 16, + IsAsyncVariant = 0x4, + Thunk = 0x10, + ReturnDroppingThunk = 0x20, } [Flags] @@ -1923,6 +1925,42 @@ public bool IsAsyncThunkMethod(MethodDescHandle methodDescHandle) return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.Thunk); } + /// + /// Returns true if the method matches MatchesAsyncVariantLookup(AsyncVariantLookup::Async), + /// i.e. it has the IsAsyncVariant flag set and is not a ReturnDroppingThunk. + /// + private bool IsAsyncVariantMethod(MethodDesc md) + { + if (!md.HasAsyncMethodData) + return false; + + Data.AsyncMethodData asyncData = _target.ProcessedData.GetOrAdd(md.GetAddressOfAsyncMethodData()); + AsyncMethodFlags flags = (AsyncMethodFlags)asyncData.Flags; + return flags.HasFlag(AsyncMethodFlags.IsAsyncVariant) && !flags.HasFlag(AsyncMethodFlags.ReturnDroppingThunk); + } + + public MethodDescHandle? GetAsyncVariant(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + uint token = md.Token; + TargetPointer mtAddr = GetMethodTable(methodDescHandle); + TypeHandle typeHandle = GetTypeHandle(mtAddr); + + // Iterate the introduced methods on the canonical MethodTable looking for a sibling + // with the same token that matches AsyncVariantLookup::Async (IsAsyncVariant set and + // ReturnDroppingThunk clear). Mirrors MethodTable::GetParallelMethodDesc's slow-path + // IntroducedMethodIterator loop. + TypeHandle canonMT = GetTypeHandle(GetCanonicalMethodTable(typeHandle)); + foreach (MethodDescHandle candidate in GetIntroducedMethods(canonMT)) + { + MethodDesc candidateMd = _methodDescs[candidate.Address]; + if (candidateMd.Token == token && IsAsyncVariantMethod(candidateMd)) + return candidate; + } + + return null; + } + public bool IsWrapperStub(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index 83996e38ef1d42..cfff4a824aeaac 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -51,6 +51,8 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new ReJIT_1(t)); + registry.Register("c1", static t => new EnC_1(t)); + registry.Register("c1", static t => new GC_1(t)); registry.Register("c1", static t => diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCData.cs new file mode 100644 index 00000000000000..b5ec80f124f824 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCData.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class EnCData : IData +{ + static EnCData IData.Create(Target target, TargetPointer address) + => new EnCData(target, address); + + public EnCData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EnCData); + + AddrOfCode = target.ReadPointerField(address, type, nameof(AddrOfCode)); + Token = target.ReadField(address, type, nameof(Token)); + EnCVersion = target.ReadNUInt(address + (ulong)type.Fields[nameof(EnCVersion)].Offset); + Next = target.ReadPointerField(address, type, nameof(Next)); + } + + public TargetPointer AddrOfCode { get; init; } + public uint Token { get; init; } + public TargetNUInt EnCVersion { get; init; } + public TargetPointer Next { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index 8e16e53b190183..115cd371ae6e5e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -37,6 +37,10 @@ public Module(Target target, TargetPointer address) TypeRefToMethodTableMap = address + (ulong)type.Fields[nameof(TypeRefToMethodTableMap)].Offset; MethodDefToILCodeVersioningStateMap = address + (ulong)type.Fields[nameof(MethodDefToILCodeVersioningStateMap)].Offset; DynamicILBlobTable = target.ReadPointerField(address, type, nameof(DynamicILBlobTable)); + + EnCDataList = type.Fields.ContainsKey(nameof(EnCDataList)) + ? target.ReadPointerField(address, type, nameof(EnCDataList)) + : TargetPointer.Null; } private readonly TargetPointer _address; @@ -71,4 +75,6 @@ public void WriteFlags(Target target, uint flags) public TargetPointer TypeRefToMethodTableMap { get; init; } public TargetPointer MethodDefToILCodeVersioningStateMap { get; init; } public TargetPointer DynamicILBlobTable { get; init; } + + public TargetPointer EnCDataList { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs index 5399a9b7edba72..e33b50581be5f0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -145,6 +145,7 @@ public enum DataType EETypeHashTable, InstMethodHashTable, DynamicILBlobTable, + EnCData, EEJitManager, PatchpointInfo, PortableEntryPoint, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs index 6ecbff7791bfad..17af88755e6c7d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs @@ -22,6 +22,7 @@ public enum TokenType : uint mdtTypeDef = 0x02 << 24, mdtFieldDef = 0x04 << 24, mdtMethodDef = 0x06 << 24, + mdtMemberRef = 0x0a << 24, } public const uint TokenTypeMask = 0xff000000; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 3bbdcc28f90eb7..3c077f7786cd08 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -18,6 +18,7 @@ public sealed unsafe partial class DacDbiImpl : IDacDbiInterface { private readonly Target _target; private readonly IDacDbiInterface? _legacy; + private readonly ulong _corDBDefaultEnCFunctionVersion; // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: // virtual HRESULT AssignCopy(const WCHAR* psz) = 0; @@ -54,6 +55,7 @@ public DacDbiImpl(Target target, object? legacyObj) { _target = target; _legacy = legacyObj as IDacDbiInterface; + _corDBDefaultEnCFunctionVersion = target.ReadGlobal(Constants.Globals.CorDBDefaultEnCFunctionVersion); } public int CheckDbiVersion(DbiVersion* pVersion) @@ -1275,11 +1277,242 @@ public int ResolveExactGenericArgsToken(uint dwExactGenericArgsTokenIndex, ulong public int GetILCodeAndSig(ulong vmAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetILCodeAndSig(vmAssembly, functionToken, pTargetBuffer, pLocalSigToken) : HResults.E_NOTIMPL; - public int GetNativeCodeInfo(ulong vmAssembly, uint functionToken, nint pJitManagerList) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetNativeCodeInfo(vmAssembly, functionToken, pJitManagerList) : HResults.E_NOTIMPL; + public int GetNativeCodeInfo(ulong vmAssembly, uint functionToken, NativeCodeFunctionData* pCodeInfo) + { + int hr = HResults.S_OK; + try + { + if (pCodeInfo == null) + throw new ArgumentException("Output buffer cannot be null.", nameof(pCodeInfo)); + + *pCodeInfo = default; + pCodeInfo->encVersion = _corDBDefaultEnCFunctionVersion; + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); + Contracts.ModuleLookupTables tables = loader.GetLookupTables(moduleHandle); + + TargetPointer mdAddr; + uint tokenType = functionToken & EcmaMetadataUtils.TokenTypeMask; + if (tokenType == (uint)EcmaMetadataUtils.TokenType.mdtMethodDef) + { + mdAddr = loader.GetModuleLookupMapElement(tables.MethodDefToDesc, functionToken, out _); + } + else if (tokenType == (uint)EcmaMetadataUtils.TokenType.mdtMemberRef) + { + mdAddr = loader.LookupMemberRefAsMethod(moduleHandle, functionToken); + } + else + throw new ArgumentException("Invalid function token type.", nameof(functionToken)); + + if (mdAddr != TargetPointer.Null) + { + Contracts.MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); + + // If this is an async thunk, unwrap to the real async variant. + // Mirrors native: pMethodDesc->GetAsyncVariantNoCreate(). + if (rts.IsAsyncThunkMethod(md)) + { + Contracts.MethodDescHandle? asyncVariant = rts.GetAsyncVariant(md); + if (asyncVariant is not null) + { + md = asyncVariant.Value; + mdAddr = md.Address; + } + } + + TargetCodePointer nativeCode = rts.GetNativeCode(md); + if (nativeCode.Value != 0) + { + FillRegionInfoAndGenericInstantiation(rts, md, nativeCode, pCodeInfo); + + // Look up EnC version on the method desc's loader module. + TargetPointer loaderModule = GetLoaderModule(rts, md); + if (_target.Contracts.TryGetContract(out Contracts.IEnC enc)) + { + pCodeInfo->encVersion = enc.GetEnCVersion(loaderModule, functionToken, nativeCode).Value; + } + } + } + pCodeInfo->vmNativeCodeMethodDescToken = mdAddr.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + NativeCodeFunctionData dataLocal = default; + int hrLocal = _legacy.GetNativeCodeInfo(vmAssembly, functionToken, &dataLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + AssertNativeCodeFunctionDataEqual(pCodeInfo, &dataLocal); + } + } +#endif + return hr; + } + + public int GetNativeCodeInfoForAddr(ulong codeAddress, NativeCodeFunctionData* pCodeInfo, ulong* pVmModule, uint* pFunctionToken) + { + int hr = HResults.S_OK; + try + { + if (pCodeInfo == null) + throw new ArgumentException("Output buffer cannot be null.", nameof(pCodeInfo)); + + *pCodeInfo = default; + if (pVmModule != null) + *pVmModule = 0; + if (pFunctionToken != null) + *pFunctionToken = 0; + + // codeAddress == 0 is not an error: clear output and report success + // (matches legacy DacDbiInterfaceImpl::GetNativeCodeInfoForAddr). + if (codeAddress != 0) + { + Contracts.IExecutionManager em = _target.Contracts.ExecutionManager; + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + Contracts.CodeBlockHandle? cbh = em.GetCodeBlockHandle(new TargetCodePointer(codeAddress)); + if (cbh is null) + throw new ArgumentException("No code block found for the given address.", nameof(codeAddress)); + TargetPointer mdAddr = em.GetMethodDesc(cbh.Value); - public int GetNativeCodeInfoForAddr(ulong codeAddress, nint pCodeInfo, ulong* pVmModule, uint* pFunctionToken) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetNativeCodeInfoForAddr(codeAddress, pCodeInfo, pVmModule, pFunctionToken) : HResults.E_NOTIMPL; + Contracts.MethodDescHandle md = rts.GetMethodDescHandle(mdAddr); + TargetCodePointer hotStart = new TargetCodePointer(em.GetStartAddress(cbh.Value).Value); + + FillRegionInfoAndGenericInstantiation(rts, md, hotStart, pCodeInfo); + pCodeInfo->vmNativeCodeMethodDescToken = mdAddr.Value; + + uint methodToken = rts.GetMethodToken(md); + + TargetPointer loaderModule = GetLoaderModule(rts, md); + if (_target.Contracts.TryGetContract(out Contracts.IEnC enc)) + { + pCodeInfo->encVersion = enc.GetEnCVersion(loaderModule, methodToken, hotStart).Value; + } + else + { + pCodeInfo->encVersion = _corDBDefaultEnCFunctionVersion; + } + + if (pFunctionToken != null) + *pFunctionToken = methodToken; + + if (pVmModule != null) + { + TargetPointer mt = rts.GetMethodTable(md); + Contracts.TypeHandle th = rts.GetTypeHandle(mt); + *pVmModule = rts.GetModule(th).Value; + } + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + NativeCodeFunctionData dataLocal = default; + ulong moduleLocal = 0; + uint tokenLocal = 0; + int hrLocal = _legacy.GetNativeCodeInfoForAddr( + codeAddress, + &dataLocal, + pVmModule != null ? &moduleLocal : null, + pFunctionToken != null ? &tokenLocal : null); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + AssertNativeCodeFunctionDataEqual(pCodeInfo, &dataLocal); + if (pVmModule != null) + Debug.Assert(*pVmModule == moduleLocal, $"pVmModule: cDAC: {*pVmModule:x}, DAC: {moduleLocal:x}"); + if (pFunctionToken != null) + Debug.Assert(*pFunctionToken == tokenLocal, $"pFunctionToken: cDAC: {*pFunctionToken:x}, DAC: {tokenLocal:x}"); + } + } +#endif + return hr; + } + + // Helper: fill the hot/cold region info and the isInstantiatedGeneric flag. + // pOut->hotRegion.pAddress == 0 when the method has no jitted code; in that case + // the cold region and isInstantiatedGeneric remain zero (cleared on entry). + private void FillRegionInfoAndGenericInstantiation( + Contracts.IRuntimeTypeSystem rts, + Contracts.MethodDescHandle md, + TargetCodePointer hotStart, + NativeCodeFunctionData* pCodeInfo) + { + if (hotStart.Value == 0) + return; + + Contracts.IExecutionManager em = _target.Contracts.ExecutionManager; + + // Look up the code block to obtain hot region size and any cold region. + Contracts.CodeBlockHandle? cbh = em.GetCodeBlockHandle(hotStart); + uint hotSize = 0; + TargetPointer coldStart = TargetPointer.Null; + uint coldSize = 0; + if (cbh is not null) + { + em.GetMethodRegionInfo(cbh.Value, out hotSize, out coldStart, out coldSize); + } + + pCodeInfo->hotRegion.pAddress = hotStart.Value; + pCodeInfo->hotRegion.cbSize = hotSize; + pCodeInfo->coldRegion.pAddress = coldStart.Value; + pCodeInfo->coldRegion.cbSize = coldSize; + + // isInstantiatedGeneric mirrors MethodDesc::HasClassOrMethodInstantiation(): + // either the owning type is instantiated, or the method has its own instantiation. + bool hasClassInst = false; + TargetPointer mt = rts.GetMethodTable(md); + if (mt != TargetPointer.Null) + { + Contracts.TypeHandle th = rts.GetTypeHandle(mt); + hasClassInst = rts.GetInstantiation(th).Length > 0; + } + bool hasMethodInst = rts.GetGenericMethodInstantiation(md).Length > 0; + pCodeInfo->isInstantiatedGeneric = (hasClassInst || hasMethodInst) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + + // Returns MethodDesc::GetLoaderModule(): the module that owns the loader heap + // for this method desc. For non-generic methods this equals the metadata + // module; for instantiations they may differ. + private static TargetPointer GetLoaderModule(Contracts.IRuntimeTypeSystem rts, Contracts.MethodDescHandle md) + { + TargetPointer mt = rts.GetMethodTable(md); + if (mt == TargetPointer.Null) + return TargetPointer.Null; + Contracts.TypeHandle th = rts.GetTypeHandle(mt); + return rts.GetLoaderModule(th); + } + +#if DEBUG + private static void AssertNativeCodeFunctionDataEqual(NativeCodeFunctionData* cdac, NativeCodeFunctionData* legacy) + { + Debug.Assert(cdac->hotRegion.pAddress == legacy->hotRegion.pAddress, + $"hotRegion.pAddress: cDAC: {cdac->hotRegion.pAddress:x}, DAC: {legacy->hotRegion.pAddress:x}"); + Debug.Assert(cdac->hotRegion.cbSize == legacy->hotRegion.cbSize, + $"hotRegion.cbSize: cDAC: {cdac->hotRegion.cbSize}, DAC: {legacy->hotRegion.cbSize}"); + Debug.Assert(cdac->coldRegion.pAddress == legacy->coldRegion.pAddress, + $"coldRegion.pAddress: cDAC: {cdac->coldRegion.pAddress:x}, DAC: {legacy->coldRegion.pAddress:x}"); + Debug.Assert(cdac->coldRegion.cbSize == legacy->coldRegion.cbSize, + $"coldRegion.cbSize: cDAC: {cdac->coldRegion.cbSize}, DAC: {legacy->coldRegion.cbSize}"); + Debug.Assert(cdac->isInstantiatedGeneric == legacy->isInstantiatedGeneric, + $"isInstantiatedGeneric: cDAC: {cdac->isInstantiatedGeneric}, DAC: {legacy->isInstantiatedGeneric}"); + Debug.Assert(cdac->vmNativeCodeMethodDescToken == legacy->vmNativeCodeMethodDescToken, + $"vmNativeCodeMethodDescToken: cDAC: {cdac->vmNativeCodeMethodDescToken:x}, DAC: {legacy->vmNativeCodeMethodDescToken:x}"); + Debug.Assert(cdac->encVersion == legacy->encVersion, + $"encVersion: cDAC: {cdac->encVersion}, DAC: {legacy->encVersion}"); + } +#endif public int IsValueType(ulong vmTypeHandle, Interop.BOOL* pResult) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index 4636ad3161b665..e61d338c9b47c5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -33,6 +33,18 @@ public struct DacDbiTargetBuffer public uint cbSize; } +// Mirrors the native NativeCodeFunctionData class defined in +// src/coreclr/debug/inc/dacdbistructures.h. +[StructLayout(LayoutKind.Sequential)] +public struct NativeCodeFunctionData +{ + public DacDbiTargetBuffer hotRegion; + public DacDbiTargetBuffer coldRegion; + public Interop.BOOL isInstantiatedGeneric; + public ulong vmNativeCodeMethodDescToken; + public ulong encVersion; +} + [StructLayout(LayoutKind.Sequential)] public struct DacDbiAssemblyInfo { @@ -372,10 +384,10 @@ public unsafe partial interface IDacDbiInterface int GetILCodeAndSig(ulong vmAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken); [PreserveSig] - int GetNativeCodeInfo(ulong vmAssembly, uint functionToken, nint pJitManagerList); + int GetNativeCodeInfo(ulong vmAssembly, uint functionToken, NativeCodeFunctionData* pCodeInfo); [PreserveSig] - int GetNativeCodeInfoForAddr(ulong codeAddress, nint pCodeInfo, ulong* pVmModule, uint* pFunctionToken); + int GetNativeCodeInfoForAddr(ulong codeAddress, NativeCodeFunctionData* pCodeInfo, ulong* pVmModule, uint* pFunctionToken); [PreserveSig] int IsValueType(ulong vmTypeHandle, Interop.BOOL* pResult); From 794e87e47e9eec50136bc03bdf55bb8473c2898a Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Mon, 18 May 2026 13:35:09 -0700 Subject: [PATCH 02/13] Update IDacDbiInterface.cs --- .../Dbi/IDacDbiInterface.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index e61d338c9b47c5..4939a47b7700d5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -33,8 +33,6 @@ public struct DacDbiTargetBuffer public uint cbSize; } -// Mirrors the native NativeCodeFunctionData class defined in -// src/coreclr/debug/inc/dacdbistructures.h. [StructLayout(LayoutKind.Sequential)] public struct NativeCodeFunctionData { From 1a185ec80c4c1b3eb48295f3b088d2cc12367f90 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Mon, 18 May 2026 13:47:26 -0700 Subject: [PATCH 03/13] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/Loader.md | 2 +- .../Dbi/DacDbiImpl.cs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 54addba164cc98..3d47ee3c6b3e6c 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -721,7 +721,7 @@ TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out Tar // Returns the MethodDesc pointer for the given mdMemberRef token, or TargetPointer.Null // if the entry is a FieldDesc (flagged with IS_FIELD_MEMBER_REF) or not present. -TargetPointer LookupMemberRefAsMethod(ModuleHandle handle, uint memberRefToken); +TargetPointer LookupMemberRefAsMethod(ModuleHandle handle, uint memberRefToken) { ModuleLookupTables tables = GetLookupTables(handle); TargetPointer ptr = GetModuleLookupMapElement(tables.MemberRefToDesc, memberRefToken, out TargetNUInt flags); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 3c077f7786cd08..d843ca69ae9a23 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1325,7 +1325,16 @@ public int GetNativeCodeInfo(ulong vmAssembly, uint functionToken, NativeCodeFun TargetCodePointer nativeCode = rts.GetNativeCode(md); if (nativeCode.Value != 0) { - FillRegionInfoAndGenericInstantiation(rts, md, nativeCode, pCodeInfo); + // GetNativeCode can return an interpreter precode. Resolve to the + // actual interpreter/jitted code address before asking for region info, + // matching native GetMethodRegionInfo's GetCodeForInterpreterOrJitted path. + TargetCodePointer regionCode = rts.GetCodeForInterpreterOrJitted(md); + if (regionCode.Value == 0) + { + regionCode = nativeCode; + } + + FillRegionInfoAndGenericInstantiation(rts, md, regionCode, pCodeInfo); // Look up EnC version on the method desc's loader module. TargetPointer loaderModule = GetLoaderModule(rts, md); @@ -1377,7 +1386,10 @@ public int GetNativeCodeInfoForAddr(ulong codeAddress, NativeCodeFunctionData* p Contracts.IExecutionManager em = _target.Contracts.ExecutionManager; Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - Contracts.CodeBlockHandle? cbh = em.GetCodeBlockHandle(new TargetCodePointer(codeAddress)); + TargetCodePointer lookupAddress = new TargetCodePointer(codeAddress); + lookupAddress = PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(_target, lookupAddress); + + Contracts.CodeBlockHandle? cbh = em.GetCodeBlockHandle(lookupAddress); if (cbh is null) throw new ArgumentException("No code block found for the given address.", nameof(codeAddress)); TargetPointer mdAddr = em.GetMethodDesc(cbh.Value); From 51fe617aedd50fb19134621a3615ba5168743eda Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Mon, 18 May 2026 13:50:05 -0700 Subject: [PATCH 04/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Dbi/DacDbiImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index d843ca69ae9a23..0ec2fcd8f51d87 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1336,11 +1336,11 @@ public int GetNativeCodeInfo(ulong vmAssembly, uint functionToken, NativeCodeFun FillRegionInfoAndGenericInstantiation(rts, md, regionCode, pCodeInfo); - // Look up EnC version on the method desc's loader module. + // Look up the latest EnC version on the method desc's loader module. TargetPointer loaderModule = GetLoaderModule(rts, md); if (_target.Contracts.TryGetContract(out Contracts.IEnC enc)) { - pCodeInfo->encVersion = enc.GetEnCVersion(loaderModule, functionToken, nativeCode).Value; + pCodeInfo->encVersion = enc.GetLatestEnCVersion(loaderModule, functionToken).Value; } } } From 109f48fb5a0a08a79795855e07c7515a0ff248a8 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 18 May 2026 14:02:50 -0700 Subject: [PATCH 05/13] ccr --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 12 +++++++++++- src/coreclr/debug/ee/functioninfo.cpp | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 15c07ceb173f45..e79c278a976bc3 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -5608,7 +5608,16 @@ void DacDbiInterfaceImpl::LookupEnCVersions(VMPTR_MethodDesc vmMethodDesc, { MethodDesc * pMD = vmMethodDesc.GetDacPtr(); _ASSERTE(pLatestEnCVersion != NULL); - +#ifdef FEATURE_METADATA_UPDATER + if (pJittedInstanceEnCVersion != NULL) + { + *pJittedInstanceEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; + } + if (pLatestEnCVersion != NULL) + { + *pLatestEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; + } +#else Module* pLoaderModule = pMD->GetLoaderModule(); PTR_EnCData pEnCData = pLoaderModule->FindEncData(mdMethod, CORDB_ADDRESS_TO_TADDR(pNativeStartAddress)); PTR_EnCData pLatestEncData = pLoaderModule->FindLatestEncData(mdMethod); @@ -5617,6 +5626,7 @@ void DacDbiInterfaceImpl::LookupEnCVersions(VMPTR_MethodDesc vmMethodDesc, *pJittedInstanceEnCVersion = (pEnCData != NULL) ? pEnCData->encVersion : CorDB_DEFAULT_ENC_FUNCTION_VERSION; } *pLatestEnCVersion = (pLatestEncData != NULL) ? pLatestEncData->encVersion : CorDB_DEFAULT_ENC_FUNCTION_VERSION; +#endif // FEATURE_METADATA_UPDATER } // Get the address of the Debugger control block on the helper thread diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index 4c3efbab9df2c3..e797a6d7537f4b 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -1226,6 +1226,7 @@ void DebuggerJitInfo::Init(TADDR newAddress) this->m_sizeOfCode = this->m_codeRegionInfo.getSizeOfTotalCode(); this->m_encVersion = this->m_methodInfo->GetCurrentEnCVersion(); +#ifdef FEATURE_METADATA_UPDATER if (this->m_encVersion != CorDB_DEFAULT_ENC_FUNCTION_VERSION) { Module* pModule = this->m_pLoaderModule; @@ -1235,6 +1236,7 @@ void DebuggerJitInfo::Init(TADDR newAddress) pEnCData->encVersion = this->m_encVersion; pModule->AddEncData(pEnCData); } +#endif // FEATURE_METADATA_UPDATER this->InitFuncletAddress(); From efc62f07c866b8a9f7d49379bca314e2684e3a34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 21:11:11 +0000 Subject: [PATCH 06/13] Add EnC unit tests for cDAC DacDbi native code APIs Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../Dbi/DacDbiImpl.cs | 8 +- .../managed/cdac/tests/DacDbiImplTests.cs | 146 +++++++++++++++++- 2 files changed, 147 insertions(+), 7 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 0ec2fcd8f51d87..de0677eda87dc1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1328,11 +1328,7 @@ public int GetNativeCodeInfo(ulong vmAssembly, uint functionToken, NativeCodeFun // GetNativeCode can return an interpreter precode. Resolve to the // actual interpreter/jitted code address before asking for region info, // matching native GetMethodRegionInfo's GetCodeForInterpreterOrJitted path. - TargetCodePointer regionCode = rts.GetCodeForInterpreterOrJitted(md); - if (regionCode.Value == 0) - { - regionCode = nativeCode; - } + TargetCodePointer regionCode = _target.Contracts.PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(nativeCode); FillRegionInfoAndGenericInstantiation(rts, md, regionCode, pCodeInfo); @@ -1387,7 +1383,7 @@ public int GetNativeCodeInfoForAddr(ulong codeAddress, NativeCodeFunctionData* p Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; TargetCodePointer lookupAddress = new TargetCodePointer(codeAddress); - lookupAddress = PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(_target, lookupAddress); + lookupAddress = _target.Contracts.PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(lookupAddress); Contracts.CodeBlockHandle? cbh = em.GetCodeBlockHandle(lookupAddress); if (cbh is null) diff --git a/src/native/managed/cdac/tests/DacDbiImplTests.cs b/src/native/managed/cdac/tests/DacDbiImplTests.cs index 0f9268acbeb255..78e9145b10941f 100644 --- a/src/native/managed/cdac/tests/DacDbiImplTests.cs +++ b/src/native/managed/cdac/tests/DacDbiImplTests.cs @@ -23,7 +23,11 @@ private static (DacDbiImpl DacDbi, TestPlaceholderTarget Target) CreateDacDbiWit MockTarget.Architecture arch, Action configure) { - var (_, target) = LoaderTests.CreateLoaderContractWithTarget(arch, configure); + var (_, target) = LoaderTests.CreateLoaderContractWithTarget(arch, (loader, builder) => + { + builder.AddGlobals((Constants.Globals.CorDBDefaultEnCFunctionVersion, 1)); + configure(loader, builder); + }); var dacDbi = new DacDbiImpl(target, legacyObj: null); return (dacDbi, target); } @@ -262,6 +266,7 @@ private static DacDbiImpl CreateDacDbiWithMockLoader( { var target = new TestPlaceholderTarget.Builder(arch) .UseReader((_, _) => -1) + .AddGlobals((Constants.Globals.CorDBDefaultEnCFunctionVersion, 1)) .AddMockContract(mockLoader) .Build(); return new DacDbiImpl(target, legacyObj: null); @@ -397,4 +402,143 @@ public void EnumerateAssembliesInAppDomain_NoAssemblies(MockTarget.Architecture Assert.Equal(System.HResults.S_OK, hr); Assert.Empty(assemblies); } + + private static DacDbiImpl CreateDacDbiWithMocks( + MockTarget.Architecture arch, + Mock mockLoader, + Mock mockRuntimeTypeSystem, + Mock mockExecutionManager, + Mock mockPrecodeStubs, + Mock? mockEnc = null, + ulong defaultEncVersion = 1) + { + var builder = new TestPlaceholderTarget.Builder(arch) + .UseReader((_, _) => -1) + .AddGlobals((Constants.Globals.CorDBDefaultEnCFunctionVersion, defaultEncVersion)) + .AddMockContract(mockLoader) + .AddMockContract(mockRuntimeTypeSystem) + .AddMockContract(mockExecutionManager) + .AddMockContract(mockPrecodeStubs); + + if (mockEnc is not null) + builder.AddMockContract(mockEnc); + + var target = builder.Build(); + return new DacDbiImpl(target, legacyObj: null); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetNativeCodeInfo_UsesLatestEnCVersion(MockTarget.Architecture arch) + { + const ulong vmAssembly = 0x1000; + uint functionToken = EcmaMetadataUtils.CreateMethodDef(0x123); + TargetPointer moduleAddr = new(0x2000); + TargetPointer methodDescAddr = new(0x3000); + TargetPointer methodLoaderModuleAddr = TargetPointer.Null; + TargetCodePointer nativeCodeAddr = new(0x6000); + TargetCodePointer resolvedCodeAddr = TargetCodePointer.Null; + const ulong expectedEncVersion = 42; + + var mockLoader = new Mock(); + var mockRuntimeTypeSystem = new Mock(); + var mockExecutionManager = new Mock(); + var mockPrecodeStubs = new Mock(); + var mockEnc = new Mock(); + + Contracts.ModuleHandle moduleHandle = new(moduleAddr); + TargetPointer methodDefToDescTable = new(0x9000); + TargetNUInt mapFlags = default; + MethodDescHandle methodDescHandle = new(methodDescAddr); + + mockLoader.Setup(l => l.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly))).Returns(moduleHandle); + mockLoader.Setup(l => l.GetLookupTables(moduleHandle)).Returns(new ModuleLookupTables { MethodDefToDesc = methodDefToDescTable }); + mockLoader.Setup(l => l.GetModuleLookupMapElement(methodDefToDescTable, functionToken, out mapFlags)).Returns(methodDescAddr); + + mockRuntimeTypeSystem.Setup(r => r.GetMethodDescHandle(methodDescAddr)).Returns(methodDescHandle); + mockRuntimeTypeSystem.Setup(r => r.IsAsyncThunkMethod(methodDescHandle)).Returns(false); + mockRuntimeTypeSystem.Setup(r => r.GetNativeCode(methodDescHandle)).Returns(nativeCodeAddr); + mockRuntimeTypeSystem.Setup(r => r.GetMethodTable(methodDescHandle)).Returns(TargetPointer.Null); + + mockPrecodeStubs.Setup(p => p.GetInterpreterCodeFromInterpreterPrecodeIfPresent(nativeCodeAddr)).Returns(resolvedCodeAddr); + + mockEnc.Setup(e => e.GetLatestEnCVersion(methodLoaderModuleAddr, functionToken)).Returns(new TargetNUInt(expectedEncVersion)); + + DacDbiImpl dacDbi = CreateDacDbiWithMocks( + arch, + mockLoader, + mockRuntimeTypeSystem, + mockExecutionManager, + mockPrecodeStubs, + mockEnc); + + NativeCodeFunctionData codeInfo = default; + int hr = dacDbi.GetNativeCodeInfo(vmAssembly, functionToken, &codeInfo); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(expectedEncVersion, codeInfo.encVersion); + Assert.Equal(methodDescAddr.Value, codeInfo.vmNativeCodeMethodDescToken); + Assert.Equal(0ul, codeInfo.hotRegion.pAddress); + Assert.Equal(0u, codeInfo.hotRegion.cbSize); + Assert.Equal(0ul, codeInfo.coldRegion.pAddress); + Assert.Equal(0u, codeInfo.coldRegion.cbSize); + mockEnc.Verify(e => e.GetLatestEnCVersion(methodLoaderModuleAddr, functionToken), Times.Once); + mockEnc.Verify(e => e.GetEnCVersion(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetNativeCodeInfoForAddr_UsesAddressSpecificEnCVersion(MockTarget.Architecture arch) + { + TargetCodePointer requestedAddress = new(0x1000); + TargetCodePointer resolvedAddress = new(0x1008); + CodeBlockHandle codeBlockHandle = new(new TargetPointer(0x2000)); + TargetPointer methodDescAddr = new(0x3000); + MethodDescHandle methodDescHandle = new(methodDescAddr); + TargetCodePointer hotStartAddr = TargetCodePointer.Null; + TargetPointer loaderModuleAddr = TargetPointer.Null; + uint methodToken = EcmaMetadataUtils.CreateMethodDef(0x111); + const ulong expectedEncVersion = 97; + + var mockLoader = new Mock(); + var mockRuntimeTypeSystem = new Mock(); + var mockExecutionManager = new Mock(); + var mockPrecodeStubs = new Mock(); + var mockEnc = new Mock(); + + mockPrecodeStubs.Setup(p => p.GetInterpreterCodeFromInterpreterPrecodeIfPresent(requestedAddress)).Returns(resolvedAddress); + + mockExecutionManager.Setup(e => e.GetCodeBlockHandle(resolvedAddress)).Returns(codeBlockHandle); + mockExecutionManager.Setup(e => e.GetMethodDesc(codeBlockHandle)).Returns(methodDescAddr); + mockExecutionManager.Setup(e => e.GetStartAddress(codeBlockHandle)).Returns(hotStartAddr.AsTargetPointer); + + mockRuntimeTypeSystem.Setup(r => r.GetMethodDescHandle(methodDescAddr)).Returns(methodDescHandle); + mockRuntimeTypeSystem.Setup(r => r.GetMethodToken(methodDescHandle)).Returns(methodToken); + mockRuntimeTypeSystem.Setup(r => r.GetMethodTable(methodDescHandle)).Returns(TargetPointer.Null); + + mockEnc.Setup(e => e.GetEnCVersion(loaderModuleAddr, methodToken, hotStartAddr)).Returns(new TargetNUInt(expectedEncVersion)); + + DacDbiImpl dacDbi = CreateDacDbiWithMocks( + arch, + mockLoader, + mockRuntimeTypeSystem, + mockExecutionManager, + mockPrecodeStubs, + mockEnc); + + NativeCodeFunctionData codeInfo = default; + uint functionToken = 0; + int hr = dacDbi.GetNativeCodeInfoForAddr(requestedAddress.Value, &codeInfo, null, &functionToken); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(expectedEncVersion, codeInfo.encVersion); + Assert.Equal(methodDescAddr.Value, codeInfo.vmNativeCodeMethodDescToken); + Assert.Equal(0ul, codeInfo.hotRegion.pAddress); + Assert.Equal(0u, codeInfo.hotRegion.cbSize); + Assert.Equal(0ul, codeInfo.coldRegion.pAddress); + Assert.Equal(0u, codeInfo.coldRegion.cbSize); + Assert.Equal(methodToken, functionToken); + mockEnc.Verify(e => e.GetEnCVersion(loaderModuleAddr, methodToken, hotStartAddr), Times.Once); + mockEnc.Verify(e => e.GetLatestEnCVersion(It.IsAny(), It.IsAny()), Times.Never); + } } From 7f0ded63469112064358fe68c33dc51b41a75989 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 18 May 2026 14:47:38 -0700 Subject: [PATCH 07/13] fix buidl break --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 2 +- src/coreclr/debug/ee/functioninfo.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index e79c278a976bc3..4fef4e3121af46 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -5608,7 +5608,7 @@ void DacDbiInterfaceImpl::LookupEnCVersions(VMPTR_MethodDesc vmMethodDesc, { MethodDesc * pMD = vmMethodDesc.GetDacPtr(); _ASSERTE(pLatestEnCVersion != NULL); -#ifdef FEATURE_METADATA_UPDATER +#ifndef FEATURE_METADATA_UPDATER if (pJittedInstanceEnCVersion != NULL) { *pJittedInstanceEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index e797a6d7537f4b..433d0a72bdd768 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -1231,7 +1231,7 @@ void DebuggerJitInfo::Init(TADDR newAddress) { Module* pModule = this->m_pLoaderModule; EnCData* pEnCData = (EnCData*)(void*)pModule->GetLoaderAllocator()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(EnCData))); - pEnCData->addrOfCode = this->m_addrOfCode; + pEnCData->addrOfCode = (TADDR)this->m_addrOfCode; pEnCData->token = this->m_methodInfo->m_token; pEnCData->encVersion = this->m_encVersion; pModule->AddEncData(pEnCData); From a948fbc6b4031f0f7c0c79a9cbf24fcfb8a5fa03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 22:33:45 +0000 Subject: [PATCH 08/13] Add EnC contract unit tests Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../managed/cdac/tests/EnCContractTests.cs | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/native/managed/cdac/tests/EnCContractTests.cs diff --git a/src/native/managed/cdac/tests/EnCContractTests.cs b/src/native/managed/cdac/tests/EnCContractTests.cs new file mode 100644 index 00000000000000..f918d98fd209fb --- /dev/null +++ b/src/native/managed/cdac/tests/EnCContractTests.cs @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class EnCContractTests +{ + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetLatestEnCVersion_ReturnsFirstMatchingEntryAndDefault(MockTarget.Architecture arch) + { + const ulong defaultVersion = 1; + const uint methodToken = 0x06000042; + const uint missingToken = 0x06000043; + const ulong latestVersion = 7; + + var (enc, module) = CreateEnCContract( + arch, + defaultVersion, + [ + (methodToken, 0x1000ul, latestVersion), + (methodToken, 0x2000ul, 6ul), + (missingToken, 0x3000ul, 9ul), + ]); + + Assert.Equal(latestVersion, enc.GetLatestEnCVersion(module, methodToken).Value); + Assert.Equal(defaultVersion, enc.GetLatestEnCVersion(module, 0x06000044).Value); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetEnCVersion_ReturnsAddressMatchAndDefault(MockTarget.Architecture arch) + { + const ulong defaultVersion = 1; + const uint methodToken = 0x06000042; + const ulong matchingAddress = 0x2000; + const ulong expectedVersion = 6; + + var (enc, module) = CreateEnCContract( + arch, + defaultVersion, + [ + (methodToken, 0x1000ul, 7ul), + (methodToken, matchingAddress, expectedVersion), + (0x06000043u, 0x3000ul, 9ul), + ]); + + Assert.Equal(expectedVersion, enc.GetEnCVersion(module, methodToken, new TargetCodePointer(matchingAddress)).Value); + Assert.Equal(defaultVersion, enc.GetEnCVersion(module, methodToken, new TargetCodePointer(0x4000)).Value); + Assert.Equal(defaultVersion, enc.GetEnCVersion(module, methodToken, TargetCodePointer.Null).Value); + } + + private static (IEnC Contract, TargetPointer ModuleAddress) CreateEnCContract( + MockTarget.Architecture arch, + ulong defaultVersion, + IReadOnlyList<(uint Token, ulong AddrOfCode, ulong EnCVersion)> entries) + { + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockMemorySpace.BumpAllocator allocator = targetBuilder.MemoryBuilder.CreateAllocator(0x0010_0000, 0x0011_0000); + + Layout moduleLayout = MockLoaderModuleWithEnCDataList.CreateLayout(arch); + Layout encDataLayout = MockEnCDataNode.CreateLayout(arch); + + MockLoaderModuleWithEnCDataList module = moduleLayout.Create(allocator.Allocate((ulong)moduleLayout.Size, "Module")); + + ulong next = 0; + for (int i = entries.Count - 1; i >= 0; i--) + { + var entry = entries[i]; + MockEnCDataNode node = encDataLayout.Create(allocator.Allocate((ulong)encDataLayout.Size, "EnCData")); + node.AddrOfCode = entry.AddrOfCode; + node.Token = entry.Token; + node.EnCVersion = entry.EnCVersion; + node.Next = next; + next = node.Address; + } + + module.EnCDataList = next; + + targetBuilder + .AddGlobals((Constants.Globals.CorDBDefaultEnCFunctionVersion, defaultVersion)) + .AddTypes(new Dictionary + { + [DataType.Module] = TargetTestHelpers.CreateTypeInfo(moduleLayout), + [DataType.EnCData] = TargetTestHelpers.CreateTypeInfo(encDataLayout), + }) + .AddContract("c1"); + + TestPlaceholderTarget target = targetBuilder.Build(); + return (target.Contracts.EnC, new TargetPointer(module.Address)); + } + + private sealed class MockLoaderModuleWithEnCDataList : TypedView + { + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("Module", architecture) + .AddPointerField("Assembly") + .AddPointerField("PEAssembly") + .AddPointerField("Base") + .AddUInt32Field("Flags") + .AddPointerField("LoaderAllocator") + .AddPointerField("DynamicMetadata") + .AddPointerField("SimpleName") + .AddPointerField("Path") + .AddPointerField("FileName") + .AddPointerField("ReadyToRunInfo") + .AddPointerField("GrowableSymbolStream") + .AddPointerField("AvailableTypeParams") + .AddPointerField("InstMethodHashTable") + .AddPointerField("FieldDefToDescMap") + .AddPointerField("ManifestModuleReferencesMap") + .AddPointerField("MemberRefToDescMap") + .AddPointerField("MethodDefToDescMap") + .AddPointerField("TypeDefToMethodTableMap") + .AddPointerField("TypeRefToMethodTableMap") + .AddPointerField("MethodDefToILCodeVersioningStateMap") + .AddPointerField("DynamicILBlobTable") + .AddPointerField("EnCDataList") + .Build(); + + public ulong EnCDataList + { + set => WritePointerField("EnCDataList", value); + } + } + + private sealed class MockEnCDataNode : TypedView + { + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("EnCData", architecture) + .AddPointerField("AddrOfCode") + .AddUInt32Field("Token") + .AddNUIntField("EnCVersion") + .AddPointerField("Next") + .Build(); + + public ulong AddrOfCode + { + set => WritePointerField("AddrOfCode", value); + } + + public uint Token + { + set => WriteUInt32Field("Token", value); + } + + public ulong EnCVersion + { + set => WritePointerField("EnCVersion", value); + } + + public ulong Next + { + set => WritePointerField("Next", value); + } + } +} From 5d7dd6f6a06f9d41955717653c15abd8782d530f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 22:34:29 +0000 Subject: [PATCH 09/13] Refine EnC contract test names Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/native/managed/cdac/tests/EnCContractTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/tests/EnCContractTests.cs b/src/native/managed/cdac/tests/EnCContractTests.cs index f918d98fd209fb..d645c43ddef151 100644 --- a/src/native/managed/cdac/tests/EnCContractTests.cs +++ b/src/native/managed/cdac/tests/EnCContractTests.cs @@ -11,7 +11,7 @@ public class EnCContractTests { [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetLatestEnCVersion_ReturnsFirstMatchingEntryAndDefault(MockTarget.Architecture arch) + public void GetLatestEnCVersion_ReturnsFirstEntryForTokenAndDefaultForMissingToken(MockTarget.Architecture arch) { const ulong defaultVersion = 1; const uint methodToken = 0x06000042; @@ -33,7 +33,7 @@ public void GetLatestEnCVersion_ReturnsFirstMatchingEntryAndDefault(MockTarget.A [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetEnCVersion_ReturnsAddressMatchAndDefault(MockTarget.Architecture arch) + public void GetEnCVersion_ReturnsVersionForMatchingAddressOrDefaultForMismatch(MockTarget.Architecture arch) { const ulong defaultVersion = 1; const uint methodToken = 0x06000042; From dfaa609d8204fe0800a0291ccf9a93f00bba5b94 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 22:35:14 +0000 Subject: [PATCH 10/13] Polish EnC contract unit test scaffolding Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/native/managed/cdac/tests/EnCContractTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/EnCContractTests.cs b/src/native/managed/cdac/tests/EnCContractTests.cs index d645c43ddef151..f3c92005f41252 100644 --- a/src/native/managed/cdac/tests/EnCContractTests.cs +++ b/src/native/managed/cdac/tests/EnCContractTests.cs @@ -134,7 +134,7 @@ public static Layout CreateLayout(MockTarget.Architecture archi => new SequentialLayoutBuilder("EnCData", architecture) .AddPointerField("AddrOfCode") .AddUInt32Field("Token") - .AddNUIntField("EnCVersion") + .AddPointerField("EnCVersion") .AddPointerField("Next") .Build(); From 575085a25d5b19d6a4ffb2044ad08a44bd2f3dbc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 22:35:49 +0000 Subject: [PATCH 11/13] Rename EnC contract tests for clarity Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/native/managed/cdac/tests/EnCContractTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/tests/EnCContractTests.cs b/src/native/managed/cdac/tests/EnCContractTests.cs index f3c92005f41252..f7673ee66df052 100644 --- a/src/native/managed/cdac/tests/EnCContractTests.cs +++ b/src/native/managed/cdac/tests/EnCContractTests.cs @@ -11,7 +11,7 @@ public class EnCContractTests { [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetLatestEnCVersion_ReturnsFirstEntryForTokenAndDefaultForMissingToken(MockTarget.Architecture arch) + public void GetLatestEnCVersion_ReturnsLatestVersionForKnownToken_DefaultForUnknownToken(MockTarget.Architecture arch) { const ulong defaultVersion = 1; const uint methodToken = 0x06000042; @@ -33,7 +33,7 @@ public void GetLatestEnCVersion_ReturnsFirstEntryForTokenAndDefaultForMissingTok [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetEnCVersion_ReturnsVersionForMatchingAddressOrDefaultForMismatch(MockTarget.Architecture arch) + public void GetEnCVersion_ReturnsVersionForMatchingAddress_DefaultForNonMatchingOrNullAddress(MockTarget.Architecture arch) { const ulong defaultVersion = 1; const uint methodToken = 0x06000042; From e7644f113f21ae987b27abdcbb11eaf9c30def52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 22:36:31 +0000 Subject: [PATCH 12/13] Tidy EnC unit test naming Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/native/managed/cdac/tests/EnCContractTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/native/managed/cdac/tests/EnCContractTests.cs b/src/native/managed/cdac/tests/EnCContractTests.cs index f7673ee66df052..fb48437114113c 100644 --- a/src/native/managed/cdac/tests/EnCContractTests.cs +++ b/src/native/managed/cdac/tests/EnCContractTests.cs @@ -11,7 +11,7 @@ public class EnCContractTests { [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetLatestEnCVersion_ReturnsLatestVersionForKnownToken_DefaultForUnknownToken(MockTarget.Architecture arch) + public void GetLatestEnCVersion_ReturnsLatestVersion_DefaultsForUnregisteredToken(MockTarget.Architecture arch) { const ulong defaultVersion = 1; const uint methodToken = 0x06000042; @@ -33,7 +33,7 @@ public void GetLatestEnCVersion_ReturnsLatestVersionForKnownToken_DefaultForUnkn [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetEnCVersion_ReturnsVersionForMatchingAddress_DefaultForNonMatchingOrNullAddress(MockTarget.Architecture arch) + public void GetEnCVersion_ReturnsMatchingVersion_DefaultsForMismatchOrNull(MockTarget.Architecture arch) { const ulong defaultVersion = 1; const uint methodToken = 0x06000042; @@ -67,7 +67,7 @@ private static (IEnC Contract, TargetPointer ModuleAddress) CreateEnCContract( MockLoaderModuleWithEnCDataList module = moduleLayout.Create(allocator.Allocate((ulong)moduleLayout.Size, "Module")); - ulong next = 0; + ulong headAddress = 0; for (int i = entries.Count - 1; i >= 0; i--) { var entry = entries[i]; @@ -75,11 +75,11 @@ private static (IEnC Contract, TargetPointer ModuleAddress) CreateEnCContract( node.AddrOfCode = entry.AddrOfCode; node.Token = entry.Token; node.EnCVersion = entry.EnCVersion; - node.Next = next; - next = node.Address; + node.Next = headAddress; + headAddress = node.Address; } - module.EnCDataList = next; + module.EnCDataList = headAddress; targetBuilder .AddGlobals((Constants.Globals.CorDBDefaultEnCFunctionVersion, defaultVersion)) From 2b2c1a8a6c1136111c67f725e673a69fb0b567f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 22:37:05 +0000 Subject: [PATCH 13/13] Clarify EnC unit test locals Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/native/managed/cdac/tests/EnCContractTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdac/tests/EnCContractTests.cs b/src/native/managed/cdac/tests/EnCContractTests.cs index fb48437114113c..fe2db9f479bf1e 100644 --- a/src/native/managed/cdac/tests/EnCContractTests.cs +++ b/src/native/managed/cdac/tests/EnCContractTests.cs @@ -65,9 +65,9 @@ private static (IEnC Contract, TargetPointer ModuleAddress) CreateEnCContract( Layout moduleLayout = MockLoaderModuleWithEnCDataList.CreateLayout(arch); Layout encDataLayout = MockEnCDataNode.CreateLayout(arch); - MockLoaderModuleWithEnCDataList module = moduleLayout.Create(allocator.Allocate((ulong)moduleLayout.Size, "Module")); + MockLoaderModuleWithEnCDataList moduleInstance = moduleLayout.Create(allocator.Allocate((ulong)moduleLayout.Size, "Module")); - ulong headAddress = 0; + ulong headAddress = TargetPointer.Null.Value; for (int i = entries.Count - 1; i >= 0; i--) { var entry = entries[i]; @@ -79,7 +79,7 @@ private static (IEnC Contract, TargetPointer ModuleAddress) CreateEnCContract( headAddress = node.Address; } - module.EnCDataList = headAddress; + moduleInstance.EnCDataList = headAddress; targetBuilder .AddGlobals((Constants.Globals.CorDBDefaultEnCFunctionVersion, defaultVersion)) @@ -91,7 +91,7 @@ private static (IEnC Contract, TargetPointer ModuleAddress) CreateEnCContract( .AddContract("c1"); TestPlaceholderTarget target = targetBuilder.Build(); - return (target.Contracts.EnC, new TargetPointer(module.Address)); + return (target.Contracts.EnC, new TargetPointer(moduleInstance.Address)); } private sealed class MockLoaderModuleWithEnCDataList : TypedView