[cDAC] Implement simple DacDbi APIs with Loader contract#126774
[cDAC] Implement simple DacDbi APIs with Loader contract#126774
Conversation
…nAppDomain, EnumerateModulesInAssembly, GetModuleForDomainAssembly, GetDomainAssemblyFromModule, GetModuleData, IsModuleMapped Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e122b97b-0569-4722-b7d7-584f09848bcd Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Implements additional cDAC-backed IDacDbiInterface loader-related APIs by wiring them to the ILoader contract, and extends the Loader contract/data model to support DomainAssembly↔Module navigation and “is mapped” queries.
Changes:
- Implemented
GetModuleData,GetModuleForDomainAssembly,GetDomainAssemblyFromModule,IsModuleMapped, and basic enumeration APIs inDacDbiImplusing theILoadercontract. - Extended Loader contract + data descriptors with
Module.DomainAssembly, plus newILoaderAPIs for DomainAssembly↔Module mapping and module mapping status. - Added/updated mock descriptors and unit tests for the new Loader contract behaviors.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs | Enhances mock module creation with Assembly.Module back-pointer and optional Module.DomainAssembly; adds helper to allocate a mock DomainAssembly. |
| src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs | Adds DomainAssembly to mock Module field layout. |
| src/native/managed/cdac/tests/LoaderTests.cs | Adds unit tests for GetModuleForDomainAssembly and GetDomainAssemblyFromModule. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs | Updates enumeration callbacks to function pointer types and fixes IsModuleMapped out-parameter type to BOOL*. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs | Implements the new/updated DacDbi APIs via Loader contract and adds DEBUG cross-validation with legacy DAC. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs | Adds DomainAssembly field parsing to the Module data model. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs | Adds GetModuleForDomainAssembly, GetDomainAssemblyFromModule, and IsModuleMapped; refactors PE image retrieval. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs | Adds new Loader contract surface for DomainAssembly↔Module mapping and module mapping status. |
| src/coreclr/vm/datadescriptor/datadescriptor.inc | Publishes Module.DomainAssembly in the cDAC type descriptor. |
| src/coreclr/vm/ceeload.h | Adds cdac_data<Module>::DomainAssembly offset definition. |
| docs/design/datacontracts/Loader.md | Documents the new Loader APIs/field and updates pseudocode. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Outdated
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Outdated
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Outdated
Show resolved
Hide resolved
...native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Outdated
Show resolved
Hide resolved
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
docs/design/datacontracts/Loader.md
Outdated
| TargetPointer GetILHeader(ModuleHandle handle, uint token); | ||
| TargetPointer GetDynamicIL(ModuleHandle handle, uint token); | ||
| IReadOnlyDictionary<string, TargetPointer> GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer); | ||
| // Gets the module handle for the module owned by the given DomainAssembly. |
There was a problem hiding this comment.
What is pulling in DomainAssembly into the data contract? What would it take to avoid it?
For historic reasons, the loader implementation was split into number of types. This split does not make sense anymore. @elinor-fung has been cleaning it up by merging and deleting the split types. I expect DomainAssembly is going to be deleted once we get to it.
There was a problem hiding this comment.
It is GetModuleForDomainAssembly; there's lots of places in the DBI that pass a DomainAssembly into GetModuleForDomainAssembly.
There was a problem hiding this comment.
In fact, there are pointers to DomainAssembly embedded in the IPC event structures that get sent between DBI and runtime. I had run into these structures when attempting to implement other APIs; the issue is that the structures are currently shared between DBI and runtime and would have to be stabilized and separated to prepare for the splitting off of the DBI. Removing references to DomainAssembly would be another thing to keep in mind for the new API design.
|
Note This review was generated by Copilot (Claude Opus 4.6). 🤖 Copilot Code Review — PR #126774Holistic AssessmentMotivation: This PR implements several cDAC DacDbi APIs (EnumerateAppDomains, EnumerateAssembliesInAppDomain, EnumerateModulesInAssembly, GetModuleForDomainAssembly, GetDomainAssemblyFromModule, GetModuleData, IsModuleMapped) that were previously delegating to the legacy DAC. Migrating these APIs to the cDAC path is a well-understood incremental goal for the diagnostics infrastructure. Approach: The approach is sound — each API follows the established cDAC pattern (implement via Summary: Detailed Findings❌ Missing null check for
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs:200
TryGetLoadedImageContentsno longer checkspeImage.LoadedImageLayoutfor null before callingGetOrAddon it. If the PE image exists but hasn’t been laid out yet, this will attempt to read a descriptor at address 0 and throw instead of returningfalse(as the method name/contract implies). Please restore theLoadedImageLayout == TargetPointer.Nullcheck and returnfalsein that case.
if (!TryGetPEImage(handle, out Data.PEImage? peImage))
return false; // no PE image
Data.PEImageLayout peImageLayout = _target.ProcessedData.GetOrAdd<Data.PEImageLayout>(peImage.LoadedImageLayout);
baseAddress = peImageLayout.Base;
size = peImageLayout.Size;
imageFlags = peImageLayout.Flags;
return true;
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Outdated
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Show resolved
Hide resolved
db43968 to
a33627f
Compare
a33627f to
ca5ca84
Compare
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Outdated
Show resolved
Hide resolved
| public int IsModuleMapped(ulong pModule, Interop.BOOL* isModuleMapped) | ||
| { | ||
| int hr = HResults.S_FALSE; | ||
| try |
There was a problem hiding this comment.
Argument validation conflates an invalid module pointer (pModule == 0) with a null output pointer, and throws ArgumentNullException(nameof(isModuleMapped)) in both cases. Split the checks so pModule==0 yields an invalid-argument HRESULT, and only treat isModuleMapped==null as a null-pointer case.
| try | |
| if (pModule == 0) | |
| throw new ArgumentException("Module pointer must not be zero.", nameof(pModule)); | |
| if (isModuleMapped == null) |
| public int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData) | ||
| => _legacy is not null ? _legacy.GetModuleData(vmModule, pData) : HResults.E_NOTIMPL; | ||
| { | ||
| int hr = HResults.S_OK; | ||
| try | ||
| { | ||
| if (vmModule == 0 || pData == null) | ||
| throw new ArgumentNullException(nameof(pData)); | ||
|
|
||
| Contracts.ILoader loader = _target.Contracts.Loader; | ||
| Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); | ||
|
|
||
| *pData = default; | ||
| pData->vmAssembly = loader.GetAssembly(handle).Value; | ||
| pData->vmPEAssembly = loader.GetPEAssembly(handle).Value; | ||
| pData->fIsDynamic = loader.IsDynamic(handle) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; | ||
| string path = loader.GetPath(handle); | ||
| pData->fInMemory = string.IsNullOrEmpty(path) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; | ||
| if (!loader.IsDynamic(handle) | ||
| && loader.TryGetLoadedImageContents(handle, out TargetPointer baseAddress, out uint size, out uint _)) | ||
| { |
There was a problem hiding this comment.
GetModuleData is newly implemented using the Loader contract, but there are no tests exercising it (unlike other DacDbiImpl methods that have dump-based coverage). Consider adding a dump-based integration test that calls GetModuleData for at least one module and validates key fields (e.g., vmAssembly, vmPEAssembly, fIsDynamic/fInMemory) against Loader contract values.
| => _legacy is not null ? _legacy.GetLoaderHeapMemoryRanges(pRanges) : HResults.E_NOTIMPL; | ||
|
|
||
| public int IsModuleMapped(ulong pModule, int* isModuleMapped) | ||
| => _legacy is not null ? _legacy.IsModuleMapped(pModule, isModuleMapped) : HResults.E_NOTIMPL; | ||
| public int IsModuleMapped(ulong pModule, Interop.BOOL* isModuleMapped) | ||
| { | ||
| int hr = HResults.S_FALSE; | ||
| try | ||
| { | ||
| if (pModule == 0 || isModuleMapped == null) | ||
| throw new ArgumentNullException(nameof(isModuleMapped)); | ||
|
|
||
| *isModuleMapped = Interop.BOOL.FALSE; | ||
| Contracts.ILoader loader = _target.Contracts.Loader; | ||
| Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(pModule)); | ||
| if (loader.TryGetLoadedImageContents(handle, out _, out _, out _)) | ||
| { | ||
| *isModuleMapped = loader.IsModuleMapped(handle) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; |
There was a problem hiding this comment.
IsModuleMapped is newly implemented via Loader contract behavior (including S_FALSE when no loaded image), but there is no test coverage validating the HRESULT and BOOL output across common cases (mapped vs not mapped, and Webcil). Consider adding a dump-based test that locates a real module from the dump and verifies the result against Loader.IsModuleMapped/TryGetLoadedImageContents.
ca5ca84 to
426b0ac
Compare
Implement cDAC DacDbi APIs: GetModuleData, IsModuleMapped