[cDAC] Implement DacDbi GetNativeCodeInfo / GetNativeCodeInfoForAddr#128338
[cDAC] Implement DacDbi GetNativeCodeInfo / GetNativeCodeInfoForAddr#128338rcj1 wants to merge 13 commits into
Conversation
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR adds cDAC support for DacDbi native-code lookup APIs and introduces a lightweight EnC data contract/list so debugger-side code can retrieve native code region and EnC version information without walking debugger method-info tables.
Changes:
- Implements managed cDAC
GetNativeCodeInfo/GetNativeCodeInfoForAddrand related interop struct shape. - Adds EnC data descriptors, contract registration, native
ModuleEnC data storage, and DAC lookup changes. - Extends loader/runtime-type-system contracts for member refs and async variant resolution, with design docs.
Show a summary per file
| File | Description |
|---|---|
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs |
Updates DacDbi signatures and mirrors native code-info data. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs |
Implements native-code-info lookup in cDAC. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs |
Adds mdtMemberRef token type. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs |
Adds EnCData descriptor type. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs |
Reads optional EnC data list from modules. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EnCData.cs |
Adds managed view over native EnC data nodes. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs |
Registers the EnC contract implementation. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs |
Adds async variant resolution support. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs |
Adds member-ref-to-method lookup helper. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EnC_1.cs |
Implements EnC version lookup contract. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs |
Adds default EnC version global name. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs |
Exposes async variant contract API. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs |
Exposes member-ref lookup contract API. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEnC.cs |
Adds public EnC contract abstraction. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs |
Adds EnC contract accessor. |
src/coreclr/vm/datadescriptor/datadescriptor.inc |
Publishes EnC descriptors, global, and contract. |
src/coreclr/vm/ceeload.h |
Adds native EnC data list and lookup helpers to Module. |
src/coreclr/debug/inc/dacdbistructures.h |
Widens code-info EnC version field. |
src/coreclr/debug/ee/functioninfo.cpp |
Records EnC data when JIT info is initialized. |
src/coreclr/debug/di/module.cpp |
Casts widened EnC version for DI objects. |
src/coreclr/debug/di/divalue.cpp |
Casts widened EnC version for function lookup. |
src/coreclr/debug/daccess/dacdbiimpl.h |
Updates EnC lookup helper signature. |
src/coreclr/debug/daccess/dacdbiimpl.cpp |
Switches native DAC EnC lookup to new module list. |
docs/design/datacontracts/RuntimeTypeSystem.md |
Documents async variant contract behavior. |
docs/design/datacontracts/Loader.md |
Documents member-ref lookup helper. |
docs/design/datacontracts/EnC.md |
Adds EnC contract design documentation. |
Copilot's findings
- Files reviewed: 26/26 changed files
- Comments generated: 11
| Module* pLoaderModule = pMD->GetLoaderModule(); | ||
| PTR_EnCData pEnCData = pLoaderModule->FindEncData(mdMethod, CORDB_ADDRESS_TO_TADDR(pNativeStartAddress)); | ||
| PTR_EnCData pLatestEncData = pLoaderModule->FindLatestEncData(mdMethod); |
There was a problem hiding this comment.
I think this is fine now because heap dumps will include this as it is off the loader allocator
There was a problem hiding this comment.
What about mini or triage dumps? I would not expect them to contain all the memory from the loader allocator.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
| 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; | ||
| EnCData* pEnCData = (EnCData*)(void*)pModule->GetLoaderAllocator()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(EnCData))); | ||
| pEnCData->addrOfCode = (TADDR)this->m_addrOfCode; | ||
| pEnCData->token = this->m_methodInfo->m_token; | ||
| pEnCData->encVersion = this->m_encVersion; | ||
| pModule->AddEncData(pEnCData); |
There was a problem hiding this comment.
good catch. Only possibly relevant in
runtime/src/coreclr/debug/di/divalue.cpp
Line 2748 in 3206a8e
| uint coldSize = 0; | ||
| if (cbh is not null) | ||
| { | ||
| em.GetMethodRegionInfo(cbh.Value, out hotSize, out coldStart, out coldSize); |
829f44a to
7f0ded6
Compare
|
Caution Security scanning requires review for Code Review DetailsThe threat detection results could not be parsed. The workflow output should be reviewed before merging. Review the workflow run logs for details. Note This review was generated by Copilot. 🤖 Copilot Code Review — PR #128338Holistic AssessmentMotivation: The PR is well-motivated. It restructures debugger-side EnC version lookup to avoid walking all Approach: The approach is sound — a simple linked list on Summary: Detailed Findings
|
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
| void AddEncData(EnCData* pData) | ||
| { | ||
| LIMITED_METHOD_CONTRACT; | ||
| pData->pNext = m_pEnCDataList; |
| return nullptr; | ||
| } | ||
|
|
||
| PTR_EnCData FindLatestEncData(mdMethodDef token) |
There was a problem hiding this comment.
How does this work with generic methods? Generic methods can have multiple instantiations.
There was a problem hiding this comment.
Hm, the thing is this doesn’t look like it ever worked to distinguish generic instantiations when getting the latest EnC version. I wonder if we even need/use this?
There was a problem hiding this comment.
Currently I see FindLatestEncData getting used in CordbObjectValue::GetFunctionHelper but it doesn't have to be. Instead of calling CordbModule::LookupOrCreateFunction(token,encVersion) where encVersion ultimately gets calculated in this call you can instead call CordbModule::LookupOrCreateFunctionLatestVersion(token). That LatestVersion variant of the API already has another independent path to discover the latest (potentially unjitted) ENC version based on Debugger::UpdateFunction sending the new ENC version in updates.
Aside from CordbObjectValue::GetFunctionHelper I only see one other callsite for GetNativeCodeInfo and that one doesn't look at the ENC version information that is returned. You could modify GetNativeCodeInfo to only return exactly what that caller needs (MethodDesc+CodeStartAddr) which will eliminate the only callsite that cared about the pLatestEnCVersion param on LookupEnCVersions(). Then you can remove that parameter and this method.
| SUPPORTS_DAC; | ||
| for (PTR_EnCData pCur = m_pEnCDataList; pCur != nullptr; pCur = pCur->pNext) | ||
| { | ||
| if (pCur->token == token && pCur->addrOfCode == addrOfCode) |
There was a problem hiding this comment.
token match should be unnecessary. The code should be enough to identify the method.
| return nullptr; | ||
| } | ||
|
|
||
| PTR_EnCData FindLatestEncData(mdMethodDef token) |
There was a problem hiding this comment.
This will find the most recently JITed method body. It may not be the one with the highest EnC version. My guess is that you expect this is to find the highest EnC version. Is that right?
There was a problem hiding this comment.
Yes, copilot pointed this discrepancy out here #128338 (comment). I think I’ll have to bring the mutation of the linked list up next to SetCurrentEnCVersion
There was a problem hiding this comment.
We can refactor to remove the dependency on this method entirely (https://github.com/dotnet/runtime/pull/128338/changes#r3265486372)
| return nullptr; | ||
| } | ||
|
|
||
| PTR_EnCData FindLatestEncData(mdMethodDef token) |
There was a problem hiding this comment.
Currently I see FindLatestEncData getting used in CordbObjectValue::GetFunctionHelper but it doesn't have to be. Instead of calling CordbModule::LookupOrCreateFunction(token,encVersion) where encVersion ultimately gets calculated in this call you can instead call CordbModule::LookupOrCreateFunctionLatestVersion(token). That LatestVersion variant of the API already has another independent path to discover the latest (potentially unjitted) ENC version based on Debugger::UpdateFunction sending the new ENC version in updates.
Aside from CordbObjectValue::GetFunctionHelper I only see one other callsite for GetNativeCodeInfo and that one doesn't look at the ENC version information that is returned. You could modify GetNativeCodeInfo to only return exactly what that caller needs (MethodDesc+CodeStartAddr) which will eliminate the only callsite that cared about the pLatestEnCVersion param on LookupEnCVersions(). Then you can remove that parameter and this method.
| return nullptr; | ||
| } | ||
|
|
||
| PTR_EnCData FindLatestEncData(mdMethodDef token) |
There was a problem hiding this comment.
We can refactor to remove the dependency on this method entirely (https://github.com/dotnet/runtime/pull/128338/changes#r3265486372)
| { | ||
| LIMITED_METHOD_CONTRACT; | ||
| SUPPORTS_DAC; | ||
| for (PTR_EnCData pCur = m_pEnCDataList; pCur != nullptr; pCur = pCur->pNext) |
There was a problem hiding this comment.
I'm worried about the performance overhead of an O(N) linked list search. Although I suspect many uses of ENC are small I'm not confident that all of them are and this method can get called a lot.
Rather than a new linked list, how about we add the ENC version to the method DebugInfo? We could add an ENCVersion as the 7th item in the FAT header. This would have no cost for most methods which use the slim header or 1 nibble per method for those using the FAT header.
| Module* pLoaderModule = pMD->GetLoaderModule(); | ||
| PTR_EnCData pEnCData = pLoaderModule->FindEncData(mdMethod, CORDB_ADDRESS_TO_TADDR(pNativeStartAddress)); | ||
| PTR_EnCData pLatestEncData = pLoaderModule->FindLatestEncData(mdMethod); |
There was a problem hiding this comment.
What about mini or triage dumps? I would not expect them to contain all the memory from the loader allocator.
DebuggerMethodInfos and then theDebuggerJitInfos to get this data. It was noted that this is quite heavy just to get this one piece of data. We don't have that many EnC versions, so we now have a linked list of these stored on theModule.GetNativeCodeInfoandGetNativeCodeInfoForAddr.note to ccr: do not talk about changing com interface compatibility or compatibility with older datadescriptors.