From b57f13785ff957d1c18eb2ff4a117f008d3c26b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:44:42 +0000 Subject: [PATCH 01/11] Implement DacDbi cDAC API GetMetaDataFileInfoFromPEFile Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- docs/design/datacontracts/Loader.md | 57 +++++-- src/coreclr/inc/sbuffer.h | 5 + src/coreclr/inc/sstring.h | 6 + src/coreclr/vm/ceeload.h | 1 - .../vm/datadescriptor/datadescriptor.inc | 3 +- src/coreclr/vm/peimage.h | 2 + .../Contracts/ILoader.cs | 3 +- .../Contracts/Loader_1.cs | 60 +++++++- .../Data/ImageFileHeader.cs | 4 + .../Data/ImageOptionalHeader.cs | 4 + .../Data/Module.cs | 1 - .../Data/PEImage.cs | 2 + .../ClrDataModule.cs | 4 +- .../Dbi/DacDbiImpl.cs | 57 ++++++- .../SOSDacImpl.cs | 7 +- .../cdac/tests/DumpTests/LoaderDumpTests.cs | 2 +- .../cdac/tests/UnitTests/LoaderTests.cs | 144 +++++++++++++++--- .../MockDescriptors/MockDescriptors.Loader.cs | 14 -- ...ockDescriptors.RuntimeMutableTypeSystem.cs | 1 - 19 files changed, 312 insertions(+), 65 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 8e9d6ccb6f98d9..a2dc6bc55e944c 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -88,6 +88,8 @@ TargetPointer GetPEAssembly(ModuleHandle handle); bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags); TargetPointer GetILAddr(TargetPointer peAssemblyPtr, int rva); TargetPointer GetFieldAddressFromRva(TargetPointer peAssemblyPtr, int rva); +string GetPath(TargetPointer peAssemblyPtr, bool fallbackToHint = false); +bool GetFileHeadersInfo(TargetPointer peAssemblyPtr, out uint timeStamp, out uint imageSize); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size); IEnumerable GetAvailableTypeParams(ModuleHandle handle); IEnumerable GetInstantiatedMethods(ModuleHandle handle); @@ -96,7 +98,6 @@ bool IsProbeExtensionResultValid(ModuleHandle handle); ModuleFlags GetFlags(ModuleHandle handle); bool IsReadyToRun(ModuleHandle handle); string GetSimpleName(ModuleHandle handle); -string GetPath(ModuleHandle handle); string GetFileName(ModuleHandle handle); TargetPointer GetLoaderAllocator(ModuleHandle handle); TargetPointer GetILBase(ModuleHandle handle); @@ -159,7 +160,6 @@ enum ClrModifiableAssemblies : uint | `Module` | `Base` | Pointer to start of PE file in memory | | `Module` | `Flags` | Assembly of the Module | | `Module` | `LoaderAllocator` | LoaderAllocator of the Module | -| `Module` | `Path` | Path of the Module (UTF-16, null-terminated) | | `Module` | `FileName` | File name of the Module (UTF-16, null-terminated) | | `Module` | `SimpleName` | Simple name of the Module (UTF-8, null-terminated) | | `Module` | `GrowableSymbolStream` | Pointer to the in memory symbol stream | @@ -187,6 +187,8 @@ enum ClrModifiableAssemblies : uint | `AssemblyBinder` | `AssemblyLoadContext` | Pointer to the AssemblyBinder's AssemblyLoadContext | | `PEImage` | `LoadedImageLayout` | Pointer to the PEImage's loaded PEImageLayout | | `PEImage` | `ProbeExtensionResult` | PEImage's ProbeExtensionResult | +| `PEImage` | `Path` | Pointer to the PEImage's on-disk path (UTF-16, null-terminated) | +| `PEImage` | `ModuleFileNameHint` | Pointer to a diagnostic file name hint for in-memory modules | | `ProbeExtensionResult` | `Type` | Type of ProbeExtensionResult | | `PEImageLayout` | `Base` | Base address of the image layout | | `PEImageLayout` | `Size` | Size of the image layout | @@ -461,6 +463,50 @@ TargetPointer ILoader.GetFieldAddressFromRva(TargetPointer peAssemblyPtr, int rv return GetRvaData(peAssemblyPtr, rva, isNullOk: true); } +string GetPath(TargetPointer peAssemblyPtr, bool fallbackToHint = false) +{ + TargetPointer peImagePtr = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); + if (peImagePtr == TargetPointer.Null) + return string.Empty; + + TargetPointer pathPtr = target.ReadPointer(peImagePtr + /* PEImage::Path offset */); + string path = pathPtr != TargetPointer.Null + ? target.ReadUtf16String(pathPtr) + : string.Empty; + + if (fallbackToHint && string.IsNullOrEmpty(path)) + { + TargetPointer hintPtr = target.ReadPointer(peImagePtr + /* PEImage::ModuleFileNameHint offset */); + if (hintPtr != TargetPointer.Null) + path = target.ReadUtf16String(hintPtr); + } + + return path; +} + +bool GetFileHeadersInfo(TargetPointer peAssemblyPtr, out uint timeStamp, out uint imageSize) +{ + timeStamp = 0; + imageSize = 0; + + if (peAssemblyPtr == TargetPointer.Null) + return false; + + TargetPointer peImagePtr = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); + if (peImagePtr == TargetPointer.Null) + return false; + + TargetPointer layoutPtr = target.ReadPointer(peImagePtr + /* PEImage::LoadedImageLayout offset */); + if (layoutPtr == TargetPointer.Null) + return false; + + TargetPointer baseAddress = target.ReadPointer(layoutPtr + /* PEImageLayout::Base offset */); + TargetPointer ntHeadersPtr = baseAddress + // offset to NT headers + timeStamp = // read from NT header + imageSize = // read from NT header + return true; +} + private TargetPointer GetRvaData(TargetPointer peAssemblyPtr, int rva, bool isNullOk) { if (rva == 0 && !isNullOk) @@ -644,13 +690,6 @@ string GetSimpleName(ModuleHandle handle) return // convert to string, throw on invalid UTF-8 } -string GetPath(ModuleHandle handle) -{ - TargetPointer pathStart = target.ReadPointer(handle.Address + /* Module::Path offset */); - char[] path = // Read from target starting at pathStart until null terminator - return new string(path); -} - string GetFileName(ModuleHandle handle) { TargetPointer fileNameStart = target.ReadPointer(handle.Address + /* Module::FileName offset */); diff --git a/src/coreclr/inc/sbuffer.h b/src/coreclr/inc/sbuffer.h index 3573a798db92c5..c01e0093f266ba 100644 --- a/src/coreclr/inc/sbuffer.h +++ b/src/coreclr/inc/sbuffer.h @@ -59,6 +59,9 @@ typedef DPTR(class SBuffer) PTR_SBuffer; +class SString; +template struct cdac_data; + class SBuffer { public: @@ -87,6 +90,8 @@ class SBuffer class Iterator; friend class Iterator; + friend struct ::cdac_data; + //-------------------------------------------------------------------- // Initializers and constructors //-------------------------------------------------------------------- diff --git a/src/coreclr/inc/sstring.h b/src/coreclr/inc/sstring.h index bf2c7ab80a4d87..1b206d0caf0c06 100644 --- a/src/coreclr/inc/sstring.h +++ b/src/coreclr/inc/sstring.h @@ -894,6 +894,12 @@ typedef InlineSString<2 * 260> LongPathString; #define FAULTS_UNLESS_BOTH_NORMALIZED(s, stmt) \ if (IsNormalized() && s.IsNormalized()) FORBID_FAULT; else INJECT_FAULT(stmt) +template<> +struct cdac_data +{ + static constexpr size_t Buffer = offsetof(SBuffer, m_buffer); +}; + // ================================================================================ // Inline definitions // ================================================================================ diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 8a8349272025bc..8cfca82bcde197 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1713,7 +1713,6 @@ struct cdac_data static constexpr size_t LoaderAllocator = offsetof(Module, m_loaderAllocator); static constexpr size_t DynamicMetadata = offsetof(Module, m_pDynamicMetadata); static constexpr size_t SimpleName = offsetof(Module, m_pSimpleName); - static constexpr size_t Path = offsetof(Module, m_path); static constexpr size_t FileName = offsetof(Module, m_fileName); static constexpr size_t ReadyToRunInfo = offsetof(Module, m_pReadyToRunInfo); static constexpr size_t GrowableSymbolStream = offsetof(Module, m_pIStreamSym); diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index c0dbba1a0e6c19..8f2891f6399f86 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -287,7 +287,6 @@ CDAC_TYPE_FIELD(Module, T_UINT32, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(Module, T_POINTER, LoaderAllocator, cdac_data::LoaderAllocator) CDAC_TYPE_FIELD(Module, T_POINTER, DynamicMetadata, cdac_data::DynamicMetadata) CDAC_TYPE_FIELD(Module, T_POINTER, SimpleName, cdac_data::SimpleName) -CDAC_TYPE_FIELD(Module, T_POINTER, Path, cdac_data::Path) CDAC_TYPE_FIELD(Module, T_POINTER, FileName, cdac_data::FileName) CDAC_TYPE_FIELD(Module, T_POINTER, ReadyToRunInfo, cdac_data::ReadyToRunInfo) CDAC_TYPE_FIELD(Module, T_POINTER, GrowableSymbolStream, cdac_data::GrowableSymbolStream) @@ -406,6 +405,8 @@ CDAC_TYPE_BEGIN(PEImage) CDAC_TYPE_INDETERMINATE(PEImage) CDAC_TYPE_FIELD(PEImage, T_POINTER, LoadedImageLayout, cdac_data::LoadedImageLayout) CDAC_TYPE_FIELD(PEImage, TYPE(ProbeExtensionResult), ProbeExtensionResult, cdac_data::ProbeExtensionResult) +CDAC_TYPE_FIELD(PEImage, T_POINTER, Path, cdac_data::Path) +CDAC_TYPE_FIELD(PEImage, T_POINTER, ModuleFileNameHint, cdac_data::ModuleFileNameHint) CDAC_TYPE_END(PEImage) CDAC_TYPE_BEGIN(PEImageLayout) diff --git a/src/coreclr/vm/peimage.h b/src/coreclr/vm/peimage.h index ea56c0d8d594c7..bc90f36fed9851 100644 --- a/src/coreclr/vm/peimage.h +++ b/src/coreclr/vm/peimage.h @@ -329,6 +329,8 @@ struct cdac_data // The loaded PEImageLayout is m_pLayouts[IMAGE_LOADED] static constexpr size_t LoadedImageLayout = offsetof(PEImage, m_pLayouts) + sizeof(PTR_PEImageLayout); static constexpr size_t ProbeExtensionResult = offsetof(PEImage, m_probeExtensionResult); + static constexpr size_t Path = offsetof(PEImage, m_path) + cdac_data::Buffer; + static constexpr size_t ModuleFileNameHint = offsetof(PEImage, m_sModuleFileNameHintUsedByDac) + cdac_data::Buffer; }; struct PEImageHolderTraits final 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 81af07a4463e3a..30b04917281fee 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 @@ -103,6 +103,8 @@ public interface ILoader : IContract bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags) => throw new NotImplementedException(); TargetPointer GetILAddr(TargetPointer peAssemblyPtr, int rva) => throw new NotImplementedException(); TargetPointer GetFieldAddressFromRva(TargetPointer peAssemblyPtr, int rva) => throw new NotImplementedException(); + string GetPath(TargetPointer peAssemblyPtr, bool fallbackToHint = false) => throw new NotImplementedException(); + bool GetFileHeadersInfo(TargetPointer peAssemblyPtr, out uint timeStamp, out uint imageSize) => throw new NotImplementedException(); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size) => throw new NotImplementedException(); IEnumerable GetAvailableTypeParams(ModuleHandle handle) => throw new NotImplementedException(); IEnumerable GetInstantiatedMethods(ModuleHandle handle) => throw new NotImplementedException(); @@ -111,7 +113,6 @@ public interface ILoader : IContract ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); bool IsReadyToRun(ModuleHandle handle) => throw new NotImplementedException(); string GetSimpleName(ModuleHandle handle) => throw new NotImplementedException(); - string GetPath(ModuleHandle handle) => throw new NotImplementedException(); string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); 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 06f4acffd46479..6dbc3af770aff8 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 @@ -327,6 +327,58 @@ private TargetPointer GetRvaData(TargetPointer peAssemblyPtr, int rva, bool isNu TargetPointer ILoader.GetFieldAddressFromRva(TargetPointer peAssemblyPtr, int rva) => GetRvaData(peAssemblyPtr, rva, true); + string ILoader.GetPath(TargetPointer peAssemblyPtr, bool fallbackToHint) + { + Data.PEAssembly peAssembly = _target.ProcessedData.GetOrAdd(peAssemblyPtr); + if (peAssembly.PEImage == TargetPointer.Null) + return string.Empty; + + Data.PEImage peImage = _target.ProcessedData.GetOrAdd(peAssembly.PEImage); + + string path = string.Empty; + if (peImage.Path != TargetPointer.Null) + { + try + { + path = _target.ReadUtf16String(peImage.Path); + } + catch (VirtualReadException) + { + // Ignore virtual read exceptions + } + } + + if (fallbackToHint && string.IsNullOrEmpty(path) && peImage.ModuleFileNameHint != TargetPointer.Null) + path = _target.ReadUtf16String(peImage.ModuleFileNameHint); + + return path; + } + + bool ILoader.GetFileHeadersInfo(TargetPointer peAssemblyPtr, out uint timeStamp, out uint imageSize) + { + timeStamp = 0; + imageSize = 0; + + if (peAssemblyPtr == TargetPointer.Null) + return false; + + Data.PEAssembly peAssembly = _target.ProcessedData.GetOrAdd(peAssemblyPtr); + if (peAssembly.PEImage == TargetPointer.Null) + return false; + + Data.PEImage peImage = _target.ProcessedData.GetOrAdd(peAssembly.PEImage); + if (peImage.LoadedImageLayout == TargetPointer.Null) + return false; + + Data.PEImageLayout peImageLayout = _target.ProcessedData.GetOrAdd(peImage.LoadedImageLayout); + + TargetPointer ntHeadersPtr = FindNTHeaders(peImageLayout); + Data.ImageNTHeaders ntHeaders = _target.ProcessedData.GetOrAdd(ntHeadersPtr); + timeStamp = ntHeaders.FileHeader.TimeDateStamp; + imageSize = ntHeaders.OptionalHeader.SizeOfImage; + return true; + } + bool ILoader.TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size) { buffer = TargetPointer.Null; @@ -455,14 +507,6 @@ string ILoader.GetSimpleName(ModuleHandle handle) : string.Empty; } - string ILoader.GetPath(ModuleHandle handle) - { - Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - return module.Path != TargetPointer.Null - ? _target.ReadUtf16String(module.Path) - : string.Empty; - } - string ILoader.GetFileName(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageFileHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageFileHeader.cs index f07611edc3ff4f..03d10ec95f1501 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageFileHeader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageFileHeader.cs @@ -7,11 +7,15 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed partial class ImageFileHeader : IData { private const int NumberOfSectionsOffset = 2; + private const int TimeDateStampOffset = 4; private const int SizeOfOptionalHeaderOffset = 16; [RawOffset(NumberOfSectionsOffset, LittleEndian = true)] public ushort NumberOfSections { get; } + [RawOffset(TimeDateStampOffset, LittleEndian = true)] + public uint TimeDateStamp { get; } + [RawOffset(SizeOfOptionalHeaderOffset, LittleEndian = true)] public ushort SizeOfOptionalHeader { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageOptionalHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageOptionalHeader.cs index e30dcbc4037b44..a1defee15f0281 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageOptionalHeader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageOptionalHeader.cs @@ -7,7 +7,11 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed partial class ImageOptionalHeader : IData { private const int SectionAlignmentOffset = 32; + private const int SizeOfImageOffset = 56; [RawOffset(SectionAlignmentOffset, LittleEndian = true)] public uint SectionAlignment { get; } + + [RawOffset(SizeOfImageOffset, LittleEndian = true)] + public uint SizeOfImage { get; } } 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 58d29e1d81ea2e..0f13f0f20e0dc2 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 @@ -16,7 +16,6 @@ internal sealed partial class Module : IData [Field] public TargetPointer LoaderAllocator { get; } [Field] public TargetPointer DynamicMetadata { get; } [Field] public TargetPointer SimpleName { get; } - [Field] public TargetPointer Path { get; } [Field] public TargetPointer FileName { get; } [Field] public TargetPointer ReadyToRunInfo { get; } [Field] public TargetPointer GrowableSymbolStream { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs index d16ad4fa5b931d..e8653467b5b82f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs @@ -8,4 +8,6 @@ internal sealed partial class PEImage : IData { [Field] public TargetPointer LoadedImageLayout { get; } [Field] public ProbeExtensionResult ProbeExtensionResult { get; } + [Field] public TargetPointer Path { get; } + [Field] public TargetPointer ModuleFileNameHint { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 8fc9d53e7df765..6e5588a154ef8c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -504,7 +504,7 @@ int IXCLRDataModule.GetFileName(uint bufLen, uint* nameLen, char* name) string result = string.Empty; try { - result = contract.GetPath(handle); + result = contract.GetPath(contract.GetPEAssembly(handle), true); } catch (VirtualReadException) { @@ -772,7 +772,7 @@ private void PopulateModuleData(DacpGetModuleData* getModuleData) } else { - getModuleData->IsInMemory = contract.GetPath(moduleHandle).Length == 0 ? 1u : 0u; + getModuleData->IsInMemory = contract.GetPath(contract.GetPEAssembly(moduleHandle)).Length == 0 ? 1u : 0u; } contract.TryGetLoadedImageContents(moduleHandle, out TargetPointer baseAddress, out uint size, out uint 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 b63a939a45de19..d14fa48a2d7ef3 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 @@ -189,7 +189,7 @@ public int GetAssemblyPath(ulong vmAssembly, nint pStrFilename, Interop.BOOL* pR { Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); - string path = loader.GetPath(handle); + string path = loader.GetPath(loader.GetPEAssembly(handle)); if (string.IsNullOrEmpty(path)) { *pResult = Interop.BOOL.FALSE; @@ -228,7 +228,7 @@ public int GetModulePath(ulong vmModule, nint pStrFilename, Interop.BOOL* pResul { Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); - string path = loader.GetPath(handle); + string path = loader.GetPath(loader.GetPEAssembly(handle), true); if (string.IsNullOrEmpty(path)) { *pResult = Interop.BOOL.FALSE; @@ -327,7 +327,7 @@ public int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData) pData->vmPEAssembly = loader.GetPEAssembly(handle).Value; bool isDynamic = loader.IsDynamic(handle); pData->fIsDynamic = isDynamic ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; - string path = loader.GetPath(handle); + string path = loader.GetPath(loader.GetPEAssembly(handle)); pData->fInMemory = string.IsNullOrEmpty(path) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; if (!isDynamic && loader.TryGetLoadedImageContents(handle, out TargetPointer baseAddress, out uint size, out uint _)) { @@ -3399,7 +3399,56 @@ public int GetAttachStateFlags(int* pRetVal) } public int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetMetaDataFileInfoFromPEFile(vmPEAssembly, dwTimeStamp, dwImageSize, pStrFilename, pResult) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + string path = string.Empty; + try + { + if (dwTimeStamp is null || dwImageSize is null || pStrFilename == 0 || pResult is null) + throw new NullReferenceException("One or more parameters are null"); + *pResult = Interop.BOOL.FALSE; + if (vmPEAssembly == 0) + throw Marshal.GetExceptionForHR(HResults.E_FAIL)!; + Contracts.ILoader loader = _target.Contracts.Loader; + bool result = loader.GetFileHeadersInfo(vmPEAssembly, out uint timeStamp, out uint imageSize); + if (result) + { + *dwTimeStamp = timeStamp; + *dwImageSize = imageSize; + } + path = loader.GetPath(vmPEAssembly, fallbackToHint: true); + hr = StringHolderAssignCopy(pStrFilename, path); + *pResult = result ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint timeStampLocal; + uint imageSizeLocal; + Interop.BOOL resultLocal; + using var legacyHolder = new NativeStringHolder(); + int hrLocal = _legacy.GetMetaDataFileInfoFromPEFile(vmPEAssembly, &timeStampLocal, &imageSizeLocal, legacyHolder.Ptr, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(*pResult == resultLocal, $"GetMetaDataFileInfoFromPEFile result mismatch - cDAC: {*pResult}, DAC: {resultLocal}"); + if (*pResult == Interop.BOOL.TRUE) + { + Debug.Assert(*dwTimeStamp == timeStampLocal, $"GetMetaDataFileInfoFromPEFile timestamp mismatch - cDAC: {*dwTimeStamp}, DAC: {timeStampLocal}"); + Debug.Assert(*dwImageSize == imageSizeLocal, $"GetMetaDataFileInfoFromPEFile image size mismatch - cDAC: {*dwImageSize}, DAC: {imageSizeLocal}"); + Debug.Assert( + string.Equals(path, legacyHolder.Value, System.StringComparison.Ordinal), + $"GetMetaDataFileInfoFromPEFile path mismatch - cDAC: '{path}', DAC: '{legacyHolder.Value}'"); + } + } + } +#endif + return hr; + } public int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsThreadSuspendedOrHijacked(vmThread, pResult) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 08b0eb2b382acd..917d546f9899cd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -544,7 +544,7 @@ int ISOSDacInterface.GetAssemblyName(ClrDataAddress assembly, uint count, char* name[0] = '\0'; Contracts.ILoader contract = _target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromAssemblyPtr(assembly.ToTargetPointer(_target)); - string path = contract.GetPath(handle); + string path = contract.GetPath(contract.GetPEAssembly(handle)); // Return not implemented for empty paths for non-reflection emit assemblies (for example, loaded from memory) if (string.IsNullOrEmpty(path)) @@ -2645,7 +2645,8 @@ int ISOSDacInterface.GetMethodDescName(ClrDataAddress addr, uint count, char* na { TargetPointer modulePtr = rtsContract.GetModule(rtsContract.GetTypeHandle(rtsContract.GetMethodTable(methodDescHandle))); Contracts.ModuleHandle module = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr); - string modulePath = _target.Contracts.Loader.GetPath(module); + ILoader loader = _target.Contracts.Loader; + string modulePath = loader.GetPath(loader.GetPEAssembly(module)); ReadOnlySpan moduleSpan = modulePath.AsSpan(); char directorySeparator = (char)_target.ReadGlobal(Constants.Globals.DirectorySeparator); @@ -3631,7 +3632,7 @@ int ISOSDacInterface.GetPEFileName(ClrDataAddress addr, uint count, char* fileNa Contracts.ILoader contract = _target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(addr.ToTargetPointer(_target)); - string path = contract.GetPath(handle); + string path = contract.GetPath(contract.GetPEAssembly(handle)); // Return not implemented for empty paths for non-reflection emit assemblies (for example, loaded from memory) if (string.IsNullOrEmpty(path)) diff --git a/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs index 41744e00a37422..bb6ec6a0f1229b 100644 --- a/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs @@ -53,7 +53,7 @@ public void Loader_CanGetModulePath(TestConfiguration config) TargetPointer rootAssembly = loader.GetRootAssembly(); ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly); - string path = loader.GetPath(moduleHandle); + string path = loader.GetPath(loader.GetPEAssembly(moduleHandle)); Assert.NotNull(path); Assert.NotEmpty(path); } diff --git a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 8843a4d5161011..8b40a5a04979fb 100644 --- a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Runtime.InteropServices; using System.Text; using Microsoft.Diagnostics.DataContractReader.Contracts; @@ -64,26 +63,22 @@ internal static (ILoader Contract, TestPlaceholderTarget Target) CreateLoaderCon [ClassData(typeof(MockTarget.StdArch))] public void GetPath(MockTarget.Architecture arch) { - string expected = $"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}TestModule.dll"; - TargetPointer moduleAddr = TargetPointer.Null; - TargetPointer moduleAddrEmptyPath = TargetPointer.Null; + const string expected = @"C:\some\path\TestModule.dll"; + var (target, peAssemblyAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: expected, moduleFileNameHint: null); + ILoader contract = target.Contracts.Loader; - ILoader contract = CreateLoaderContract(arch, loader => - { - moduleAddr = loader.AddModule(path: expected).Address; - moduleAddrEmptyPath = loader.AddModule().Address; - }); + Assert.Equal(expected, contract.GetPath(peAssemblyAddr)); + } - { - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); - string actual = contract.GetPath(handle); - Assert.Equal(expected, actual); - } - { - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddrEmptyPath); - string actual = contract.GetFileName(handle); - Assert.Equal(string.Empty, actual); - } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetPath_EmptyPathFallsBackToModuleFileNameHint(MockTarget.Architecture arch) + { + const string expectedHint = @"InMemoryModule.dll"; + var (target, peAssemblyAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: null, moduleFileNameHint: expectedHint); + ILoader contract = target.Contracts.Loader; + + Assert.Equal(expectedHint, contract.GetPath(peAssemblyAddr, fallbackToHint: true)); } [Theory] @@ -1084,4 +1079,115 @@ public void TryGetSymbolStream_EmptyStream(MockTarget.Architecture arch) Assert.Equal(TargetPointer.Null, buffer); Assert.Equal(0u, size); } + + private static (TestPlaceholderTarget Target, TargetPointer PEAssemblyAddr) CreatePETarget( + MockTarget.Architecture arch, + uint timeStamp, + uint imageSize, + string? path, + string? moduleFileNameHint) + { + const uint Lfanew = 0x80; + TargetTestHelpers helpers = new(arch); + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockMemorySpace.Builder builder = targetBuilder.MemoryBuilder; + var allocator = builder.CreateAllocator(0x0010_0000, 0x0020_0000); + + var probeExtLayout = helpers.LayoutFields([ + new(nameof(Data.ProbeExtensionResult.Type), DataType.int32), + ]); + var peAssemblyLayout = helpers.LayoutFields([ + new(nameof(Data.PEAssembly.PEImage), DataType.pointer), + new(nameof(Data.PEAssembly.AssemblyBinder), DataType.pointer), + ]); + // PEImage.Path and PEImage.ModuleFileNameHint are modeled as pointers directly to the + // SString's null-terminated UTF-16 character buffer. + var peImageLayout = helpers.LayoutFields([ + new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), + new(nameof(Data.PEImage.ProbeExtensionResult), DataType.ProbeExtensionResult, probeExtLayout.Stride), + new(nameof(Data.PEImage.Path), DataType.pointer), + new(nameof(Data.PEImage.ModuleFileNameHint), DataType.pointer), + ]); + var imageLayoutLayout = helpers.LayoutFields([ + new(nameof(Data.PEImageLayout.Base), DataType.pointer), + new(nameof(Data.PEImageLayout.Size), DataType.uint32), + new(nameof(Data.PEImageLayout.Flags), DataType.uint32), + new(nameof(Data.PEImageLayout.Format), DataType.uint32), + ]); + + var types = new Dictionary + { + [DataType.PEAssembly] = new() { Fields = peAssemblyLayout.Fields, Size = peAssemblyLayout.Stride }, + [DataType.PEImage] = new() { Fields = peImageLayout.Fields, Size = peImageLayout.Stride }, + [DataType.PEImageLayout] = new() { Fields = imageLayoutLayout.Fields, Size = imageLayoutLayout.Stride }, + [DataType.ProbeExtensionResult] = new() { Fields = probeExtLayout.Fields, Size = probeExtLayout.Stride }, + }; + + // Build a minimal PE image containing just the DOS header lfanew, the FileHeader + // TimeDateStamp and the OptionalHeader SizeOfImage at their real PE format offsets. + const uint imageBufferSize = 0x100; + const int DosHeaderLfanewOffset = 60; // IMAGE_DOS_HEADER.e_lfanew + const int NtFileHeaderOffset = 4; // IMAGE_NT_HEADERS.FileHeader (after the 4-byte signature) + const int FileHeaderTimeDateStampOffset = 4; // IMAGE_FILE_HEADER.TimeDateStamp + const int NtOptionalHeaderOffset = 24; // IMAGE_NT_HEADERS.OptionalHeader + const int OptionalHeaderSizeOfImageOffset = 56; // IMAGE_OPTIONAL_HEADER.SizeOfImage (same for PE32/PE32+) + var image = allocator.Allocate(imageBufferSize, "PEImageBytes"); + // PE headers are always little-endian on disk, regardless of target architecture. + System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(image.Data.AsSpan().Slice(DosHeaderLfanewOffset, sizeof(uint)), Lfanew); + System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(image.Data.AsSpan().Slice((int)Lfanew + NtFileHeaderOffset + FileHeaderTimeDateStampOffset, sizeof(uint)), timeStamp); + System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(image.Data.AsSpan().Slice((int)Lfanew + NtOptionalHeaderOffset + OptionalHeaderSizeOfImageOffset, sizeof(uint)), imageSize); + + var layoutFrag = allocator.Allocate(imageLayoutLayout.Stride, "PEImageLayout"); + helpers.WritePointer(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Base)].Offset, helpers.PointerSize), image.Address); + helpers.Write(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Size)].Offset, sizeof(uint)), imageBufferSize); + helpers.Write(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Flags)].Offset, sizeof(uint)), 0u); + helpers.Write(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Format)].Offset, sizeof(uint)), 0u); + + var peImageFrag = allocator.Allocate(peImageLayout.Stride, "PEImage"); + helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.LoadedImageLayout)].Offset, helpers.PointerSize), layoutFrag.Address); + helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.Path)].Offset, helpers.PointerSize), AllocateUtf16String(helpers, allocator, path)); + helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.ModuleFileNameHint)].Offset, helpers.PointerSize), AllocateUtf16String(helpers, allocator, moduleFileNameHint)); + + var peAssemblyFrag = allocator.Allocate(peAssemblyLayout.Stride, "PEAssembly"); + helpers.WritePointer(peAssemblyFrag.Data.AsSpan().Slice(peAssemblyLayout.Fields[nameof(Data.PEAssembly.PEImage)].Offset, helpers.PointerSize), peImageFrag.Address); + + var target = targetBuilder + .AddTypes(types) + .AddContract(version: "c1") + .Build(); + + return (target, new TargetPointer(peAssemblyFrag.Address)); + } + + // Allocates a null-terminated UTF-16 buffer (mirroring an SString's UNICODE character buffer, + // where an empty string still points at a static zero-terminated buffer) and returns its address. + private static ulong AllocateUtf16String( + TargetTestHelpers helpers, + MockMemorySpace.BumpAllocator allocator, + string? value) + { + value ??= string.Empty; + // WriteUtf16String writes the characters plus a 2-byte null terminator in target endianness. + ulong bufferSize = (ulong)((value.Length + 1) * sizeof(char)); + var bufferFrag = allocator.Allocate(bufferSize, "Utf16StringBuffer"); + helpers.WriteUtf16String(bufferFrag.Data.AsSpan(), value); + return bufferFrag.Address; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetMetaDataFileInfoFromPEFile_ReturnsPathTimestampAndSize(MockTarget.Architecture arch) + { + const uint expectedTimeStamp = 0x1234_5678; + const uint expectedImageSize = 0x0009_A000; + const string expectedPath = @"C:\some\path\Test.dll"; + var (target, peAssemblyAddr) = CreatePETarget(arch, expectedTimeStamp, expectedImageSize, expectedPath, moduleFileNameHint: null); + ILoader contract = target.Contracts.Loader; + + bool result = contract.GetFileHeadersInfo(peAssemblyAddr, out uint timeStamp, out uint imageSize); + + Assert.True(result); + Assert.Equal(expectedTimeStamp, timeStamp); + Assert.Equal(expectedImageSize, imageSize); + } } diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs index 09ad3d25260a58..34dbad1284b74f 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs @@ -65,7 +65,6 @@ internal sealed class MockLoaderModule : TypedView private const string LoaderAllocatorFieldName = "LoaderAllocator"; private const string DynamicMetadataFieldName = "DynamicMetadata"; private const string SimpleNameFieldName = "SimpleName"; - private const string PathFieldName = "Path"; private const string FileNameFieldName = "FileName"; private const string ReadyToRunInfoFieldName = "ReadyToRunInfo"; private const string GrowableSymbolStreamFieldName = "GrowableSymbolStream"; @@ -89,7 +88,6 @@ public static Layout CreateLayout(MockTarget.Architecture arch .AddPointerField(LoaderAllocatorFieldName) .AddPointerField(DynamicMetadataFieldName) .AddPointerField(SimpleNameFieldName) - .AddPointerField(PathFieldName) .AddPointerField(FileNameFieldName) .AddPointerField(ReadyToRunInfoFieldName) .AddPointerField(GrowableSymbolStreamFieldName) @@ -123,12 +121,6 @@ public ulong SimpleName set => WritePointerField(SimpleNameFieldName, value); } - public ulong Path - { - get => ReadPointerField(PathFieldName); - set => WritePointerField(PathFieldName, value); - } - public ulong FileName { get => ReadPointerField(FileNameFieldName); @@ -272,7 +264,6 @@ internal MockLoaderHeapBlock AddLoaderHeapBlock(ulong virtualAddress, ulong virt } internal MockLoaderModule AddModule( - string? path = null, string? fileName = null, string? simpleName = null, byte[]? simpleNameBytes = null, @@ -291,11 +282,6 @@ internal MockLoaderModule AddModule( module.SimpleName = AddNullTerminatedUtf8(rawSimpleName, "Module simple name"); } - if (path is not null) - { - module.Path = AddUtf16String(path, $"Module path = {path}"); - } - if (fileName is not null) { module.FileName = AddUtf16String(fileName, $"Module file name = {fileName}"); diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs index 992957fdc947c7..c6e4381bf40512 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs @@ -33,7 +33,6 @@ public static Layout CreateLayout(MockTarget.Architecture archite .AddPointerField(nameof(Data.Module.LoaderAllocator)) .AddPointerField(nameof(Data.Module.DynamicMetadata)) .AddPointerField(nameof(Data.Module.SimpleName)) - .AddPointerField(nameof(Data.Module.Path)) .AddPointerField(nameof(Data.Module.FileName)) .AddPointerField(nameof(Data.Module.ReadyToRunInfo)) .AddPointerField(nameof(Data.Module.GrowableSymbolStream)) From d80a28934f28b30c0d2d59f972d7cc322f250f17 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 18 Jun 2026 12:52:54 -0700 Subject: [PATCH 02/11] code review --- docs/design/datacontracts/Loader.md | 102 ++++++++++-------- src/coreclr/debug/daccess/daccess.cpp | 13 +++ src/coreclr/debug/daccess/dacdbiimpl.cpp | 10 +- src/coreclr/debug/daccess/dacdbiimpl.h | 2 +- src/coreclr/debug/daccess/dacimpl.h | 8 ++ src/coreclr/debug/di/module.cpp | 10 +- src/coreclr/debug/di/process.cpp | 8 +- src/coreclr/debug/di/rspriv.h | 3 +- src/coreclr/debug/inc/dacdbiinterface.h | 2 +- src/coreclr/inc/dacdbi.idl | 4 +- src/coreclr/vm/ceeload.h | 1 + .../vm/datadescriptor/datadescriptor.inc | 2 +- src/coreclr/vm/peimage.h | 1 - .../Contracts/ILoader.cs | 4 +- .../Contracts/Loader_1.cs | 88 +++++++-------- .../Data/Module.cs | 1 + .../Data/PEImage.cs | 1 - .../ClrDataModule.cs | 4 +- .../Dbi/DacDbiImpl.cs | 25 ++--- .../Dbi/IDacDbiInterface.cs | 2 +- .../SOSDacImpl.cs | 7 +- .../cdac/tests/DumpTests/LoaderDumpTests.cs | 2 +- .../cdac/tests/UnitTests/LoaderTests.cs | 30 ++++-- .../MockDescriptors/MockDescriptors.Loader.cs | 14 +++ ...ockDescriptors.RuntimeMutableTypeSystem.cs | 1 + 25 files changed, 189 insertions(+), 156 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index a2dc6bc55e944c..baca456d93ccbe 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -88,8 +88,6 @@ TargetPointer GetPEAssembly(ModuleHandle handle); bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags); TargetPointer GetILAddr(TargetPointer peAssemblyPtr, int rva); TargetPointer GetFieldAddressFromRva(TargetPointer peAssemblyPtr, int rva); -string GetPath(TargetPointer peAssemblyPtr, bool fallbackToHint = false); -bool GetFileHeadersInfo(TargetPointer peAssemblyPtr, out uint timeStamp, out uint imageSize); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size); IEnumerable GetAvailableTypeParams(ModuleHandle handle); IEnumerable GetInstantiatedMethods(ModuleHandle handle); @@ -98,7 +96,9 @@ bool IsProbeExtensionResultValid(ModuleHandle handle); ModuleFlags GetFlags(ModuleHandle handle); bool IsReadyToRun(ModuleHandle handle); string GetSimpleName(ModuleHandle handle); +string GetPath(ModuleHandle handle, bool fallbackToHint = false); string GetFileName(ModuleHandle handle); +bool GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uint imageSize); TargetPointer GetLoaderAllocator(ModuleHandle handle); TargetPointer GetILBase(ModuleHandle handle); TargetPointer GetAssemblyLoadContext(ModuleHandle handle); @@ -160,6 +160,7 @@ enum ClrModifiableAssemblies : uint | `Module` | `Base` | Pointer to start of PE file in memory | | `Module` | `Flags` | Assembly of the Module | | `Module` | `LoaderAllocator` | LoaderAllocator of the Module | +| `Module` | `Path` | Path of the Module (UTF-16, null-terminated) | | `Module` | `FileName` | File name of the Module (UTF-16, null-terminated) | | `Module` | `SimpleName` | Simple name of the Module (UTF-8, null-terminated) | | `Module` | `GrowableSymbolStream` | Pointer to the in memory symbol stream | @@ -187,7 +188,6 @@ enum ClrModifiableAssemblies : uint | `AssemblyBinder` | `AssemblyLoadContext` | Pointer to the AssemblyBinder's AssemblyLoadContext | | `PEImage` | `LoadedImageLayout` | Pointer to the PEImage's loaded PEImageLayout | | `PEImage` | `ProbeExtensionResult` | PEImage's ProbeExtensionResult | -| `PEImage` | `Path` | Pointer to the PEImage's on-disk path (UTF-16, null-terminated) | | `PEImage` | `ModuleFileNameHint` | Pointer to a diagnostic file name hint for in-memory modules | | `ProbeExtensionResult` | `Type` | Type of ProbeExtensionResult | | `PEImageLayout` | `Base` | Base address of the image layout | @@ -463,50 +463,6 @@ TargetPointer ILoader.GetFieldAddressFromRva(TargetPointer peAssemblyPtr, int rv return GetRvaData(peAssemblyPtr, rva, isNullOk: true); } -string GetPath(TargetPointer peAssemblyPtr, bool fallbackToHint = false) -{ - TargetPointer peImagePtr = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); - if (peImagePtr == TargetPointer.Null) - return string.Empty; - - TargetPointer pathPtr = target.ReadPointer(peImagePtr + /* PEImage::Path offset */); - string path = pathPtr != TargetPointer.Null - ? target.ReadUtf16String(pathPtr) - : string.Empty; - - if (fallbackToHint && string.IsNullOrEmpty(path)) - { - TargetPointer hintPtr = target.ReadPointer(peImagePtr + /* PEImage::ModuleFileNameHint offset */); - if (hintPtr != TargetPointer.Null) - path = target.ReadUtf16String(hintPtr); - } - - return path; -} - -bool GetFileHeadersInfo(TargetPointer peAssemblyPtr, out uint timeStamp, out uint imageSize) -{ - timeStamp = 0; - imageSize = 0; - - if (peAssemblyPtr == TargetPointer.Null) - return false; - - TargetPointer peImagePtr = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); - if (peImagePtr == TargetPointer.Null) - return false; - - TargetPointer layoutPtr = target.ReadPointer(peImagePtr + /* PEImage::LoadedImageLayout offset */); - if (layoutPtr == TargetPointer.Null) - return false; - - TargetPointer baseAddress = target.ReadPointer(layoutPtr + /* PEImageLayout::Base offset */); - TargetPointer ntHeadersPtr = baseAddress + // offset to NT headers - timeStamp = // read from NT header - imageSize = // read from NT header - return true; -} - private TargetPointer GetRvaData(TargetPointer peAssemblyPtr, int rva, bool isNullOk) { if (rva == 0 && !isNullOk) @@ -690,6 +646,33 @@ string GetSimpleName(ModuleHandle handle) return // convert to string, throw on invalid UTF-8 } +string GetPath(ModuleHandle handle, bool fallbackToHint = false) +{ + TargetPointer pathStart = target.ReadPointer(handle.Address + /* Module::Path offset */); + string path = pathStart != TargetPointer.Null + ? /* Read from target starting at pathStart until null terminator */ + : string.Empty; + + // For in-memory modules the path is empty; optionally fall back to the + // PEImage's diagnostic file name hint (reached through the Module's PEAssembly). + if (fallbackToHint && string.IsNullOrEmpty(path)) + { + TargetPointer peAssemblyPtr = target.ReadPointer(handle.Address + /* Module::PEAssembly offset */); + if (peAssemblyPtr != TargetPointer.Null) + { + TargetPointer peImagePtr = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); + if (peImagePtr != TargetPointer.Null) + { + TargetPointer hintPtr = target.ReadPointer(peImagePtr + /* PEImage::ModuleFileNameHint offset */); + if (hintPtr != TargetPointer.Null) + path = target.ReadUtf16String(hintPtr); + } + } + } + + return path; +} + string GetFileName(ModuleHandle handle) { TargetPointer fileNameStart = target.ReadPointer(handle.Address + /* Module::FileName offset */); @@ -697,6 +680,31 @@ string GetFileName(ModuleHandle handle) return new string(fileName); } +bool GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uint imageSize) +{ + timeStamp = 0; + imageSize = 0; + + // Resolve the PEImage through the Module's PEAssembly. + TargetPointer peAssemblyPtr = target.ReadPointer(handle.Address + /* Module::PEAssembly offset */); + if (peAssemblyPtr == TargetPointer.Null) + return false; + + TargetPointer peImagePtr = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); + if (peImagePtr == TargetPointer.Null) + return false; + + TargetPointer layoutPtr = target.ReadPointer(peImagePtr + /* PEImage::LoadedImageLayout offset */); + if (layoutPtr == TargetPointer.Null) + return false; + + TargetPointer baseAddress = target.ReadPointer(layoutPtr + /* PEImageLayout::Base offset */); + TargetPointer ntHeadersPtr = baseAddress + // offset to NT headers + timeStamp = // read from NT header + imageSize = // read from NT header + return true; +} + TargetPointer GetLoaderAllocator(ModuleHandle handle) { return target.ReadPointer(handle.Address + /* Module::LoaderAllocator offset */); diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 8271d1c9b3bbf4..3f4a95c9c601cf 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -5936,6 +5936,19 @@ ClrDataAccess::GetHostJitNotificationTable() return m_jitNotificationTable; } +/* static */ bool +ClrDataAccess::GetMetaDataFileInfoFromModule(Module *pModule, + DWORD &dwTimeStamp, + DWORD &dwSize, + DWORD &dwDataSize, + DWORD &dwRvaHint, + _Out_writes_(cchFilePath) LPWSTR wszFilePath, + const DWORD cchFilePath) +{ + SUPPORTS_DAC_HOST_ONLY; + return ClrDataAccess::GetMetaDataFileInfoFromPEFile(pModule->GetPEAssembly(), dwTimeStamp, dwSize, dwDataSize, dwRvaHint, wszFilePath, cchFilePath); +} + /* static */ bool ClrDataAccess::GetMetaDataFileInfoFromPEFile(PEAssembly *pPEAssembly, DWORD &dwTimeStamp, diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 383914db7e1e1f..e9730866b1b048 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -1125,7 +1125,7 @@ mdSignature DacDbiInterfaceImpl::GetILCodeAndSigHelper(Module * pModule, } -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult) +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetModuleMetaDataFileInfo(VMPTR_Module vmModule, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult) { if (pTimeStamp == NULL || pImageSize == NULL || pStrFilename == NULL || pResult == NULL) return E_POINTER; @@ -1138,9 +1138,9 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMP DWORD dwDataSize; DWORD dwRvaHint; - PEAssembly * pPEAssembly = vmPEAssembly.GetDacPtr(); - _ASSERTE(pPEAssembly != NULL); - if (pPEAssembly == NULL) + Module * pModule = vmModule.GetDacPtr(); + _ASSERTE(pModule != NULL); + if (pModule == NULL) { *pResult = FALSE; return E_FAIL; @@ -1149,7 +1149,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMP { WCHAR wszFilePath[MAX_LONGPATH] = {0}; DWORD cchFilePath = MAX_LONGPATH; - bool ret = ClrDataAccess::GetMetaDataFileInfoFromPEFile(pPEAssembly, + bool ret = ClrDataAccess::GetMetaDataFileInfoFromModule(pModule, *pTimeStamp, *pImageSize, dwDataSize, diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index 9c5f64de8d9c8c..fb73666289e3a3 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -895,7 +895,7 @@ class DacDbiInterfaceImpl : public: // API for picking up the info needed for a debugger to look up an image from its search path. - HRESULT STDMETHODCALLTYPE GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult); + HRESULT STDMETHODCALLTYPE GetModuleMetaDataFileInfo(VMPTR_Module vmModule, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult); }; diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index a9cf4abcaed257..2b7fb52385ddde 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1487,6 +1487,14 @@ class ClrDataAccess DWORD &dwRvaHint, _Out_writes_(cchFilePath) LPWSTR wszFilePath, DWORD cchFilePath); + + static bool GetMetaDataFileInfoFromModule(Module *pModule, + DWORD &dwTimeStamp, + DWORD &dwSize, + DWORD &dwDataSize, + DWORD &dwRvaHint, + _Out_writes_(cchFilePath) LPWSTR wszFilePath, + const DWORD cchFilePath); }; extern ClrDataAccess* g_dacImpl; diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 3bd2ea636603b3..6df21283f57529 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -296,7 +296,7 @@ IMetaDataImport * CordbModule::GetMetaDataImporter() // Since we've already done everything possible from the Module anyhow, just call the // stuff that talks to the debugger. // Don't do anything with the ptr returned here, since it's really m_pInternalMetaDataImport. - pProcess->LookupMetaDataFromDebugger(m_vmPEFile, this); + pProcess->LookupMetaDataFromDebugger(this); } // If we still can't get it, throw. @@ -788,10 +788,10 @@ HRESULT CordbModule::InitPublicMetaDataFromFile(const WCHAR * pszFullPathName, StringCopyHolder filePath; - _ASSERTE(!m_vmPEFile.IsNull()); + _ASSERTE(!m_vmModule.IsNull()); // MetaData lookup favors the NGEN image, which is what we want here. BOOL _mdFileInfoResult; - IfFailThrow(this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile, + IfFailThrow(this->GetProcess()->GetDAC()->GetModuleMetaDataFileInfo(m_vmModule, &dwImageTimeStamp, &dwImageSize, &filePath, @@ -1175,9 +1175,9 @@ HRESULT CordbModule::GetName(ULONG32 cchName, ULONG32 *pcchName, _Out_writes_to_ DWORD dwImageSize = 0; // unused StringCopyHolder filePath; - _ASSERTE(!m_vmPEFile.IsNull()); + _ASSERTE(!m_vmModule.IsNull()); BOOL _mdFileInfoResult; - IfFailThrow(this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile, + IfFailThrow(this->GetProcess()->GetDAC()->GetModuleMetaDataFileInfo(m_vmModule, &dwImageTimeStamp, &dwImageSize, &filePath, diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index 4a6a95006f4b71..5e399e84a837ac 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -387,7 +387,7 @@ IMDInternalImport * CordbProcess::LookupMetaData(VMPTR_PEAssembly vmPEAssembly) // debugger if it can find the metadata elsewhere. // If this was live debugging, we should have just gotten the memory contents. // Thus this code is for dump debugging, when you don't have the metadata in the dump. - pMDII = LookupMetaDataFromDebugger(vmPEAssembly, pModule); + pMDII = LookupMetaDataFromDebugger(pModule); } return pMDII; } @@ -398,9 +398,7 @@ IMDInternalImport * CordbProcess::LookupMetaData(VMPTR_PEAssembly vmPEAssembly) } -IMDInternalImport * CordbProcess::LookupMetaDataFromDebugger( - VMPTR_PEAssembly vmPEAssembly, - CordbModule * pModule) +IMDInternalImport * CordbProcess::LookupMetaDataFromDebugger(CordbModule * pModule) { DWORD dwImageTimeStamp = 0; DWORD dwImageSize = 0; @@ -409,7 +407,7 @@ IMDInternalImport * CordbProcess::LookupMetaDataFromDebugger( // First, see if the debugger can locate the exact metadata we want. BOOL _metaDataFileInfoResult; - IfFailThrow(this->GetDAC()->GetMetaDataFileInfoFromPEFile(vmPEAssembly, &dwImageTimeStamp, &dwImageSize, &filePath, &_metaDataFileInfoResult)); + IfFailThrow(this->GetDAC()->GetModuleMetaDataFileInfo(pModule->GetRuntimeModule(), &dwImageTimeStamp, &dwImageSize, &filePath, &_metaDataFileInfoResult)); if (_metaDataFileInfoResult) { _ASSERTE(filePath.IsSet()); diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 84c9b8ce1ced51..f47fe1f6c69dc7 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -2993,8 +2993,7 @@ class CordbProcess : IMDInternalImport * LookupMetaData(VMPTR_PEAssembly vmPEAssembly); // Helper functions for LookupMetaData implementation - IMDInternalImport * LookupMetaDataFromDebugger(VMPTR_PEAssembly vmPEAssembly, - CordbModule * pModule); + IMDInternalImport * LookupMetaDataFromDebugger(CordbModule * pModule); IMDInternalImport * LookupMetaDataFromDebuggerForSingleFile(CordbModule * pModule, LPCWSTR pwszImagePath, diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 56abc73cfc22a0..67929b46cfaecd 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1933,7 +1933,7 @@ IDacDbiInterface : public IUnknown // to terminate the process when the attach is canceled. virtual HRESULT STDMETHODCALLTYPE GetAttachStateFlags(OUT CLR_DEBUGGING_PROCESS_FLAGS * pRetVal) = 0; - virtual HRESULT STDMETHODCALLTYPE GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetModuleMetaDataFileInfo(VMPTR_Module vmModule, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult) = 0; virtual HRESULT STDMETHODCALLTYPE IsThreadSuspendedOrHijacked(VMPTR_Thread vmThread, OUT BOOL * pResult) = 0; diff --git a/src/coreclr/inc/dacdbi.idl b/src/coreclr/inc/dacdbi.idl index b017222be7a8c1..4219e94955b6b8 100644 --- a/src/coreclr/inc/dacdbi.idl +++ b/src/coreclr/inc/dacdbi.idl @@ -367,8 +367,8 @@ interface IDacDbiInterface : IUnknown HRESULT GetAttachStateFlags([out] CLR_DEBUGGING_PROCESS_FLAGS * pRetVal); // Metadata - HRESULT GetMetaDataFileInfoFromPEFile( - [in] VMPTR_PEAssembly vmPEAssembly, + HRESULT GetModuleMetaDataFileInfo( + [in] VMPTR_Module vmModule, [out] DWORD * pTimeStamp, [out] DWORD * pImageSize, [in] IDacDbiStringHolder pStrFilename, diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 8cfca82bcde197..8a8349272025bc 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1713,6 +1713,7 @@ struct cdac_data static constexpr size_t LoaderAllocator = offsetof(Module, m_loaderAllocator); static constexpr size_t DynamicMetadata = offsetof(Module, m_pDynamicMetadata); static constexpr size_t SimpleName = offsetof(Module, m_pSimpleName); + static constexpr size_t Path = offsetof(Module, m_path); static constexpr size_t FileName = offsetof(Module, m_fileName); static constexpr size_t ReadyToRunInfo = offsetof(Module, m_pReadyToRunInfo); static constexpr size_t GrowableSymbolStream = offsetof(Module, m_pIStreamSym); diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 8f2891f6399f86..0ae45ac574f6a6 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -287,6 +287,7 @@ CDAC_TYPE_FIELD(Module, T_UINT32, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(Module, T_POINTER, LoaderAllocator, cdac_data::LoaderAllocator) CDAC_TYPE_FIELD(Module, T_POINTER, DynamicMetadata, cdac_data::DynamicMetadata) CDAC_TYPE_FIELD(Module, T_POINTER, SimpleName, cdac_data::SimpleName) +CDAC_TYPE_FIELD(Module, T_POINTER, Path, cdac_data::Path) CDAC_TYPE_FIELD(Module, T_POINTER, FileName, cdac_data::FileName) CDAC_TYPE_FIELD(Module, T_POINTER, ReadyToRunInfo, cdac_data::ReadyToRunInfo) CDAC_TYPE_FIELD(Module, T_POINTER, GrowableSymbolStream, cdac_data::GrowableSymbolStream) @@ -405,7 +406,6 @@ CDAC_TYPE_BEGIN(PEImage) CDAC_TYPE_INDETERMINATE(PEImage) CDAC_TYPE_FIELD(PEImage, T_POINTER, LoadedImageLayout, cdac_data::LoadedImageLayout) CDAC_TYPE_FIELD(PEImage, TYPE(ProbeExtensionResult), ProbeExtensionResult, cdac_data::ProbeExtensionResult) -CDAC_TYPE_FIELD(PEImage, T_POINTER, Path, cdac_data::Path) CDAC_TYPE_FIELD(PEImage, T_POINTER, ModuleFileNameHint, cdac_data::ModuleFileNameHint) CDAC_TYPE_END(PEImage) diff --git a/src/coreclr/vm/peimage.h b/src/coreclr/vm/peimage.h index bc90f36fed9851..2dbcbf548bbf3e 100644 --- a/src/coreclr/vm/peimage.h +++ b/src/coreclr/vm/peimage.h @@ -329,7 +329,6 @@ struct cdac_data // The loaded PEImageLayout is m_pLayouts[IMAGE_LOADED] static constexpr size_t LoadedImageLayout = offsetof(PEImage, m_pLayouts) + sizeof(PTR_PEImageLayout); static constexpr size_t ProbeExtensionResult = offsetof(PEImage, m_probeExtensionResult); - static constexpr size_t Path = offsetof(PEImage, m_path) + cdac_data::Buffer; static constexpr size_t ModuleFileNameHint = offsetof(PEImage, m_sModuleFileNameHintUsedByDac) + cdac_data::Buffer; }; 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 30b04917281fee..2e5aeed906bab1 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 @@ -103,8 +103,6 @@ public interface ILoader : IContract bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags) => throw new NotImplementedException(); TargetPointer GetILAddr(TargetPointer peAssemblyPtr, int rva) => throw new NotImplementedException(); TargetPointer GetFieldAddressFromRva(TargetPointer peAssemblyPtr, int rva) => throw new NotImplementedException(); - string GetPath(TargetPointer peAssemblyPtr, bool fallbackToHint = false) => throw new NotImplementedException(); - bool GetFileHeadersInfo(TargetPointer peAssemblyPtr, out uint timeStamp, out uint imageSize) => throw new NotImplementedException(); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size) => throw new NotImplementedException(); IEnumerable GetAvailableTypeParams(ModuleHandle handle) => throw new NotImplementedException(); IEnumerable GetInstantiatedMethods(ModuleHandle handle) => throw new NotImplementedException(); @@ -113,7 +111,9 @@ public interface ILoader : IContract ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); bool IsReadyToRun(ModuleHandle handle) => throw new NotImplementedException(); string GetSimpleName(ModuleHandle handle) => throw new NotImplementedException(); + string GetPath(ModuleHandle handle, bool fallbackToHint = false) => throw new NotImplementedException(); string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); + bool GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uint imageSize) => throw new NotImplementedException(); TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetAssemblyLoadContext(ModuleHandle handle) => throw new NotImplementedException(); 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 6dbc3af770aff8..d7f5d015cac8ff 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 @@ -327,58 +327,6 @@ private TargetPointer GetRvaData(TargetPointer peAssemblyPtr, int rva, bool isNu TargetPointer ILoader.GetFieldAddressFromRva(TargetPointer peAssemblyPtr, int rva) => GetRvaData(peAssemblyPtr, rva, true); - string ILoader.GetPath(TargetPointer peAssemblyPtr, bool fallbackToHint) - { - Data.PEAssembly peAssembly = _target.ProcessedData.GetOrAdd(peAssemblyPtr); - if (peAssembly.PEImage == TargetPointer.Null) - return string.Empty; - - Data.PEImage peImage = _target.ProcessedData.GetOrAdd(peAssembly.PEImage); - - string path = string.Empty; - if (peImage.Path != TargetPointer.Null) - { - try - { - path = _target.ReadUtf16String(peImage.Path); - } - catch (VirtualReadException) - { - // Ignore virtual read exceptions - } - } - - if (fallbackToHint && string.IsNullOrEmpty(path) && peImage.ModuleFileNameHint != TargetPointer.Null) - path = _target.ReadUtf16String(peImage.ModuleFileNameHint); - - return path; - } - - bool ILoader.GetFileHeadersInfo(TargetPointer peAssemblyPtr, out uint timeStamp, out uint imageSize) - { - timeStamp = 0; - imageSize = 0; - - if (peAssemblyPtr == TargetPointer.Null) - return false; - - Data.PEAssembly peAssembly = _target.ProcessedData.GetOrAdd(peAssemblyPtr); - if (peAssembly.PEImage == TargetPointer.Null) - return false; - - Data.PEImage peImage = _target.ProcessedData.GetOrAdd(peAssembly.PEImage); - if (peImage.LoadedImageLayout == TargetPointer.Null) - return false; - - Data.PEImageLayout peImageLayout = _target.ProcessedData.GetOrAdd(peImage.LoadedImageLayout); - - TargetPointer ntHeadersPtr = FindNTHeaders(peImageLayout); - Data.ImageNTHeaders ntHeaders = _target.ProcessedData.GetOrAdd(ntHeadersPtr); - timeStamp = ntHeaders.FileHeader.TimeDateStamp; - imageSize = ntHeaders.OptionalHeader.SizeOfImage; - return true; - } - bool ILoader.TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size) { buffer = TargetPointer.Null; @@ -507,6 +455,25 @@ string ILoader.GetSimpleName(ModuleHandle handle) : string.Empty; } + string ILoader.GetPath(ModuleHandle handle, bool fallbackToHint) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + string path = string.Empty; + try + { + path = module.Path != TargetPointer.Null ? _target.ReadUtf16String(module.Path) : string.Empty; + } + catch (VirtualReadException) + { + // Ignore virtual read exceptions + } + + if (fallbackToHint && string.IsNullOrEmpty(path) && TryGetPEImage(handle, out Data.PEImage? peImage) && peImage.ModuleFileNameHint != TargetPointer.Null) + path = _target.ReadUtf16String(peImage.ModuleFileNameHint); + + return path; + } + string ILoader.GetFileName(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); @@ -515,6 +482,23 @@ string ILoader.GetFileName(ModuleHandle handle) : string.Empty; } + bool ILoader.GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uint imageSize) + { + timeStamp = 0; + imageSize = 0; + + if (!TryGetPEImage(handle, out Data.PEImage? peImage)) + return false; + + Data.PEImageLayout peImageLayout = _target.ProcessedData.GetOrAdd(peImage.LoadedImageLayout); + + TargetPointer ntHeadersPtr = FindNTHeaders(peImageLayout); + Data.ImageNTHeaders ntHeaders = _target.ProcessedData.GetOrAdd(ntHeadersPtr); + timeStamp = ntHeaders.FileHeader.TimeDateStamp; + imageSize = ntHeaders.OptionalHeader.SizeOfImage; + return true; + } + TargetPointer ILoader.GetLoaderAllocator(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); 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 0f13f0f20e0dc2..58d29e1d81ea2e 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 @@ -16,6 +16,7 @@ internal sealed partial class Module : IData [Field] public TargetPointer LoaderAllocator { get; } [Field] public TargetPointer DynamicMetadata { get; } [Field] public TargetPointer SimpleName { get; } + [Field] public TargetPointer Path { get; } [Field] public TargetPointer FileName { get; } [Field] public TargetPointer ReadyToRunInfo { get; } [Field] public TargetPointer GrowableSymbolStream { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs index e8653467b5b82f..683f04f781b7e1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs @@ -8,6 +8,5 @@ internal sealed partial class PEImage : IData { [Field] public TargetPointer LoadedImageLayout { get; } [Field] public ProbeExtensionResult ProbeExtensionResult { get; } - [Field] public TargetPointer Path { get; } [Field] public TargetPointer ModuleFileNameHint { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 6e5588a154ef8c..59843a9d7f69c6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -504,7 +504,7 @@ int IXCLRDataModule.GetFileName(uint bufLen, uint* nameLen, char* name) string result = string.Empty; try { - result = contract.GetPath(contract.GetPEAssembly(handle), true); + result = contract.GetPath(handle, true); } catch (VirtualReadException) { @@ -772,7 +772,7 @@ private void PopulateModuleData(DacpGetModuleData* getModuleData) } else { - getModuleData->IsInMemory = contract.GetPath(contract.GetPEAssembly(moduleHandle)).Length == 0 ? 1u : 0u; + getModuleData->IsInMemory = contract.GetPath(moduleHandle).Length == 0 ? 1u : 0u; } contract.TryGetLoadedImageContents(moduleHandle, out TargetPointer baseAddress, out uint size, out uint 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 d14fa48a2d7ef3..ecb2df1d94e442 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 @@ -189,7 +189,7 @@ public int GetAssemblyPath(ulong vmAssembly, nint pStrFilename, Interop.BOOL* pR { Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); - string path = loader.GetPath(loader.GetPEAssembly(handle)); + string path = loader.GetPath(handle); if (string.IsNullOrEmpty(path)) { *pResult = Interop.BOOL.FALSE; @@ -228,7 +228,7 @@ public int GetModulePath(ulong vmModule, nint pStrFilename, Interop.BOOL* pResul { Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); - string path = loader.GetPath(loader.GetPEAssembly(handle), true); + string path = loader.GetPath(handle, true); if (string.IsNullOrEmpty(path)) { *pResult = Interop.BOOL.FALSE; @@ -327,7 +327,7 @@ public int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData) pData->vmPEAssembly = loader.GetPEAssembly(handle).Value; bool isDynamic = loader.IsDynamic(handle); pData->fIsDynamic = isDynamic ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; - string path = loader.GetPath(loader.GetPEAssembly(handle)); + string path = loader.GetPath(handle); pData->fInMemory = string.IsNullOrEmpty(path) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; if (!isDynamic && loader.TryGetLoadedImageContents(handle, out TargetPointer baseAddress, out uint size, out uint _)) { @@ -3398,7 +3398,7 @@ public int GetAttachStateFlags(int* pRetVal) return hr; } - public int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult) + public int GetModuleMetaDataFileInfo(ulong vmModule, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult) { int hr = HResults.S_OK; string path = string.Empty; @@ -3407,16 +3407,17 @@ public int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, if (dwTimeStamp is null || dwImageSize is null || pStrFilename == 0 || pResult is null) throw new NullReferenceException("One or more parameters are null"); *pResult = Interop.BOOL.FALSE; - if (vmPEAssembly == 0) + if (vmModule == 0) throw Marshal.GetExceptionForHR(HResults.E_FAIL)!; Contracts.ILoader loader = _target.Contracts.Loader; - bool result = loader.GetFileHeadersInfo(vmPEAssembly, out uint timeStamp, out uint imageSize); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(vmModule); + bool result = loader.GetFileHeadersInfo(moduleHandle, out uint timeStamp, out uint imageSize); if (result) { *dwTimeStamp = timeStamp; *dwImageSize = imageSize; } - path = loader.GetPath(vmPEAssembly, fallbackToHint: true); + path = loader.GetPath(moduleHandle, fallbackToHint: true); hr = StringHolderAssignCopy(pStrFilename, path); *pResult = result ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; } @@ -3431,18 +3432,18 @@ public int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, uint imageSizeLocal; Interop.BOOL resultLocal; using var legacyHolder = new NativeStringHolder(); - int hrLocal = _legacy.GetMetaDataFileInfoFromPEFile(vmPEAssembly, &timeStampLocal, &imageSizeLocal, legacyHolder.Ptr, &resultLocal); + int hrLocal = _legacy.GetModuleMetaDataFileInfo(vmModule, &timeStampLocal, &imageSizeLocal, legacyHolder.Ptr, &resultLocal); Debug.ValidateHResult(hr, hrLocal); if (hr == HResults.S_OK) { - Debug.Assert(*pResult == resultLocal, $"GetMetaDataFileInfoFromPEFile result mismatch - cDAC: {*pResult}, DAC: {resultLocal}"); + Debug.Assert(*pResult == resultLocal, $"GetModuleMetaDataFileInfo result mismatch - cDAC: {*pResult}, DAC: {resultLocal}"); if (*pResult == Interop.BOOL.TRUE) { - Debug.Assert(*dwTimeStamp == timeStampLocal, $"GetMetaDataFileInfoFromPEFile timestamp mismatch - cDAC: {*dwTimeStamp}, DAC: {timeStampLocal}"); - Debug.Assert(*dwImageSize == imageSizeLocal, $"GetMetaDataFileInfoFromPEFile image size mismatch - cDAC: {*dwImageSize}, DAC: {imageSizeLocal}"); + Debug.Assert(*dwTimeStamp == timeStampLocal, $"GetModuleMetaDataFileInfo timestamp mismatch - cDAC: {*dwTimeStamp}, DAC: {timeStampLocal}"); + Debug.Assert(*dwImageSize == imageSizeLocal, $"GetModuleMetaDataFileInfo image size mismatch - cDAC: {*dwImageSize}, DAC: {imageSizeLocal}"); Debug.Assert( string.Equals(path, legacyHolder.Value, System.StringComparison.Ordinal), - $"GetMetaDataFileInfoFromPEFile path mismatch - cDAC: '{path}', DAC: '{legacyHolder.Value}'"); + $"GetModuleMetaDataFileInfo path mismatch - cDAC: '{path}', DAC: '{legacyHolder.Value}'"); } } } 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 754a848e2e8d5a..80953f85f66acc 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 @@ -659,7 +659,7 @@ int EnumerateTypeHandleParams(ulong vmTypeHandle, int GetAttachStateFlags(int* pRetVal); [PreserveSig] - int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult); + int GetModuleMetaDataFileInfo(ulong vmModule, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult); [PreserveSig] int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 917d546f9899cd..08b0eb2b382acd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -544,7 +544,7 @@ int ISOSDacInterface.GetAssemblyName(ClrDataAddress assembly, uint count, char* name[0] = '\0'; Contracts.ILoader contract = _target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromAssemblyPtr(assembly.ToTargetPointer(_target)); - string path = contract.GetPath(contract.GetPEAssembly(handle)); + string path = contract.GetPath(handle); // Return not implemented for empty paths for non-reflection emit assemblies (for example, loaded from memory) if (string.IsNullOrEmpty(path)) @@ -2645,8 +2645,7 @@ int ISOSDacInterface.GetMethodDescName(ClrDataAddress addr, uint count, char* na { TargetPointer modulePtr = rtsContract.GetModule(rtsContract.GetTypeHandle(rtsContract.GetMethodTable(methodDescHandle))); Contracts.ModuleHandle module = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr); - ILoader loader = _target.Contracts.Loader; - string modulePath = loader.GetPath(loader.GetPEAssembly(module)); + string modulePath = _target.Contracts.Loader.GetPath(module); ReadOnlySpan moduleSpan = modulePath.AsSpan(); char directorySeparator = (char)_target.ReadGlobal(Constants.Globals.DirectorySeparator); @@ -3632,7 +3631,7 @@ int ISOSDacInterface.GetPEFileName(ClrDataAddress addr, uint count, char* fileNa Contracts.ILoader contract = _target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(addr.ToTargetPointer(_target)); - string path = contract.GetPath(contract.GetPEAssembly(handle)); + string path = contract.GetPath(handle); // Return not implemented for empty paths for non-reflection emit assemblies (for example, loaded from memory) if (string.IsNullOrEmpty(path)) diff --git a/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs index bb6ec6a0f1229b..41744e00a37422 100644 --- a/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs @@ -53,7 +53,7 @@ public void Loader_CanGetModulePath(TestConfiguration config) TargetPointer rootAssembly = loader.GetRootAssembly(); ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly); - string path = loader.GetPath(loader.GetPEAssembly(moduleHandle)); + string path = loader.GetPath(moduleHandle); Assert.NotNull(path); Assert.NotEmpty(path); } diff --git a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 8b40a5a04979fb..2dda8cab73fc3b 100644 --- a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs @@ -64,10 +64,11 @@ internal static (ILoader Contract, TestPlaceholderTarget Target) CreateLoaderCon public void GetPath(MockTarget.Architecture arch) { const string expected = @"C:\some\path\TestModule.dll"; - var (target, peAssemblyAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: expected, moduleFileNameHint: null); + var (target, moduleAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: expected, moduleFileNameHint: null); ILoader contract = target.Contracts.Loader; - Assert.Equal(expected, contract.GetPath(peAssemblyAddr)); + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + Assert.Equal(expected, contract.GetPath(handle)); } [Theory] @@ -75,10 +76,11 @@ public void GetPath(MockTarget.Architecture arch) public void GetPath_EmptyPathFallsBackToModuleFileNameHint(MockTarget.Architecture arch) { const string expectedHint = @"InMemoryModule.dll"; - var (target, peAssemblyAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: null, moduleFileNameHint: expectedHint); + var (target, moduleAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: null, moduleFileNameHint: expectedHint); ILoader contract = target.Contracts.Loader; - Assert.Equal(expectedHint, contract.GetPath(peAssemblyAddr, fallbackToHint: true)); + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + Assert.Equal(expectedHint, contract.GetPath(handle, fallbackToHint: true)); } [Theory] @@ -1080,7 +1082,7 @@ public void TryGetSymbolStream_EmptyStream(MockTarget.Architecture arch) Assert.Equal(0u, size); } - private static (TestPlaceholderTarget Target, TargetPointer PEAssemblyAddr) CreatePETarget( + private static (TestPlaceholderTarget Target, TargetPointer ModuleAddr) CreatePETarget( MockTarget.Architecture arch, uint timeStamp, uint imageSize, @@ -1091,6 +1093,7 @@ private static (TestPlaceholderTarget Target, TargetPointer PEAssemblyAddr) Crea TargetTestHelpers helpers = new(arch); var targetBuilder = new TestPlaceholderTarget.Builder(arch); MockMemorySpace.Builder builder = targetBuilder.MemoryBuilder; + MockLoaderBuilder loader = new(builder); var allocator = builder.CreateAllocator(0x0010_0000, 0x0020_0000); var probeExtLayout = helpers.LayoutFields([ @@ -1100,12 +1103,11 @@ private static (TestPlaceholderTarget Target, TargetPointer PEAssemblyAddr) Crea new(nameof(Data.PEAssembly.PEImage), DataType.pointer), new(nameof(Data.PEAssembly.AssemblyBinder), DataType.pointer), ]); - // PEImage.Path and PEImage.ModuleFileNameHint are modeled as pointers directly to the + // PEImage.ModuleFileNameHint is modeled as a pointer directly to the // SString's null-terminated UTF-16 character buffer. var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), new(nameof(Data.PEImage.ProbeExtensionResult), DataType.ProbeExtensionResult, probeExtLayout.Stride), - new(nameof(Data.PEImage.Path), DataType.pointer), new(nameof(Data.PEImage.ModuleFileNameHint), DataType.pointer), ]); var imageLayoutLayout = helpers.LayoutFields([ @@ -1145,18 +1147,23 @@ private static (TestPlaceholderTarget Target, TargetPointer PEAssemblyAddr) Crea var peImageFrag = allocator.Allocate(peImageLayout.Stride, "PEImage"); helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.LoadedImageLayout)].Offset, helpers.PointerSize), layoutFrag.Address); - helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.Path)].Offset, helpers.PointerSize), AllocateUtf16String(helpers, allocator, path)); helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.ModuleFileNameHint)].Offset, helpers.PointerSize), AllocateUtf16String(helpers, allocator, moduleFileNameHint)); var peAssemblyFrag = allocator.Allocate(peAssemblyLayout.Stride, "PEAssembly"); helpers.WritePointer(peAssemblyFrag.Data.AsSpan().Slice(peAssemblyLayout.Fields[nameof(Data.PEAssembly.PEImage)].Offset, helpers.PointerSize), peImageFrag.Address); + // Build a Module that owns the PEAssembly and carries the module path. GetPath reads the + // path from the Module, falling back to the PEImage's ModuleFileNameHint when it is empty. + MockLoaderModule module = loader.AddModule(path: path); + module.PEAssembly = peAssemblyFrag.Address; + var target = targetBuilder + .AddTypes(CreateContractTypes(loader)) .AddTypes(types) .AddContract(version: "c1") .Build(); - return (target, new TargetPointer(peAssemblyFrag.Address)); + return (target, new TargetPointer(module.Address)); } // Allocates a null-terminated UTF-16 buffer (mirroring an SString's UNICODE character buffer, @@ -1181,10 +1188,11 @@ public void GetMetaDataFileInfoFromPEFile_ReturnsPathTimestampAndSize(MockTarget const uint expectedTimeStamp = 0x1234_5678; const uint expectedImageSize = 0x0009_A000; const string expectedPath = @"C:\some\path\Test.dll"; - var (target, peAssemblyAddr) = CreatePETarget(arch, expectedTimeStamp, expectedImageSize, expectedPath, moduleFileNameHint: null); + var (target, moduleAddr) = CreatePETarget(arch, expectedTimeStamp, expectedImageSize, expectedPath, moduleFileNameHint: null); ILoader contract = target.Contracts.Loader; - bool result = contract.GetFileHeadersInfo(peAssemblyAddr, out uint timeStamp, out uint imageSize); + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + bool result = contract.GetFileHeadersInfo(handle, out uint timeStamp, out uint imageSize); Assert.True(result); Assert.Equal(expectedTimeStamp, timeStamp); diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs index 34dbad1284b74f..0333d5e80bea9e 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs @@ -65,6 +65,7 @@ internal sealed class MockLoaderModule : TypedView private const string LoaderAllocatorFieldName = "LoaderAllocator"; private const string DynamicMetadataFieldName = "DynamicMetadata"; private const string SimpleNameFieldName = "SimpleName"; + private const string PathFieldName = "Path"; private const string FileNameFieldName = "FileName"; private const string ReadyToRunInfoFieldName = "ReadyToRunInfo"; private const string GrowableSymbolStreamFieldName = "GrowableSymbolStream"; @@ -88,6 +89,7 @@ public static Layout CreateLayout(MockTarget.Architecture arch .AddPointerField(LoaderAllocatorFieldName) .AddPointerField(DynamicMetadataFieldName) .AddPointerField(SimpleNameFieldName) + .AddPointerField(PathFieldName) .AddPointerField(FileNameFieldName) .AddPointerField(ReadyToRunInfoFieldName) .AddPointerField(GrowableSymbolStreamFieldName) @@ -121,6 +123,12 @@ public ulong SimpleName set => WritePointerField(SimpleNameFieldName, value); } + public ulong Path + { + get => ReadPointerField(PathFieldName); + set => WritePointerField(PathFieldName, value); + } + public ulong FileName { get => ReadPointerField(FileNameFieldName); @@ -267,6 +275,7 @@ internal MockLoaderModule AddModule( string? fileName = null, string? simpleName = null, byte[]? simpleNameBytes = null, + string? path = null, uint flags = 0) { MockLoaderModule module = ModuleLayout.Create(_allocator.Allocate((ulong)ModuleLayout.Size, "Module")); @@ -276,6 +285,11 @@ internal MockLoaderModule AddModule( module.Flags = flags; } + if (path is not null) + { + module.Path = AddUtf16String(path, $"Module path = {path}"); + } + byte[]? rawSimpleName = simpleName is not null ? Encoding.UTF8.GetBytes(simpleName) : simpleNameBytes; if (rawSimpleName is not null) { diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs index c6e4381bf40512..992957fdc947c7 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeMutableTypeSystem.cs @@ -33,6 +33,7 @@ public static Layout CreateLayout(MockTarget.Architecture archite .AddPointerField(nameof(Data.Module.LoaderAllocator)) .AddPointerField(nameof(Data.Module.DynamicMetadata)) .AddPointerField(nameof(Data.Module.SimpleName)) + .AddPointerField(nameof(Data.Module.Path)) .AddPointerField(nameof(Data.Module.FileName)) .AddPointerField(nameof(Data.Module.ReadyToRunInfo)) .AddPointerField(nameof(Data.Module.GrowableSymbolStream)) From 1c2e83e1eeac07739b904486fba357e2d5a863f4 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Thu, 18 Jun 2026 14:32:31 -0700 Subject: [PATCH 03/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/native/managed/cdac/tests/UnitTests/LoaderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 2dda8cab73fc3b..752cc9fe4ae297 100644 --- a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs @@ -1183,7 +1183,7 @@ private static ulong AllocateUtf16String( [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetMetaDataFileInfoFromPEFile_ReturnsPathTimestampAndSize(MockTarget.Architecture arch) + public void GetFileHeadersInfo_ReturnsTimeStampAndImageSize(MockTarget.Architecture arch) { const uint expectedTimeStamp = 0x1234_5678; const uint expectedImageSize = 0x0009_A000; From ea3bf3f61fa48af94cb79b88984e047fa6b2ea00 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 18 Jun 2026 15:19:58 -0700 Subject: [PATCH 04/11] fix build --- src/native/managed/cdac/tests/UnitTests/LoaderTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 752cc9fe4ae297..7921ddefafe95b 100644 --- a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs @@ -537,6 +537,7 @@ private static (TestPlaceholderTarget Target, TargetPointer PEAssemblyAddr, Targ var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), new(nameof(Data.PEImage.ProbeExtensionResult), DataType.ProbeExtensionResult, probeExtLayout.Stride), + new(nameof(Data.PEImage.ModuleFileNameHint), DataType.pointer), ]); var imageLayoutLayout = helpers.LayoutFields([ new(nameof(Data.PEImageLayout.Base), DataType.pointer), @@ -755,6 +756,7 @@ public void IsModuleMapped_ReturnsExpected(MockTarget.Architecture arch, uint fo var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), new(nameof(Data.PEImage.ProbeExtensionResult), DataType.ProbeExtensionResult, probeExtLayout.Stride), + new(nameof(Data.PEImage.ModuleFileNameHint), DataType.pointer), ]); var imageLayoutLayout = helpers.LayoutFields([ new(nameof(Data.PEImageLayout.Base), DataType.pointer), From 7bd20874685e000c447f9615f97aef5b6a29dd38 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 22 Jun 2026 16:03:47 -0700 Subject: [PATCH 05/11] code review --- src/coreclr/inc/sstring.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/inc/sstring.h b/src/coreclr/inc/sstring.h index 1b206d0caf0c06..a40205cd5566a1 100644 --- a/src/coreclr/inc/sstring.h +++ b/src/coreclr/inc/sstring.h @@ -897,7 +897,7 @@ typedef InlineSString<2 * 260> LongPathString; template<> struct cdac_data { - static constexpr size_t Buffer = offsetof(SBuffer, m_buffer); + static constexpr size_t Buffer = offsetof(SString, m_buffer); }; // ================================================================================ From 8ba3a5b5b05cfd7c8054a471d0ffbeaf7f1ab352 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Tue, 23 Jun 2026 09:03:44 -0700 Subject: [PATCH 06/11] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/coreclr/debug/daccess/daccess.cpp | 10 +++++++++- .../Dbi/DacDbiImpl.cs | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 3f4a95c9c601cf..f721578d9d9396 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -5946,7 +5946,15 @@ ClrDataAccess::GetMetaDataFileInfoFromModule(Module *pModule, const DWORD cchFilePath) { SUPPORTS_DAC_HOST_ONLY; - return ClrDataAccess::GetMetaDataFileInfoFromPEFile(pModule->GetPEAssembly(), dwTimeStamp, dwSize, dwDataSize, dwRvaHint, wszFilePath, cchFilePath); + + if (pModule == NULL) + return false; + + PEAssembly *pPEAssembly = pModule->GetPEAssembly(); + if (pPEAssembly == NULL) + return false; + + return ClrDataAccess::GetMetaDataFileInfoFromPEFile(pPEAssembly, dwTimeStamp, dwSize, dwDataSize, dwRvaHint, wszFilePath, cchFilePath); } /* static */ bool 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 ecb2df1d94e442..34c8c5032facd1 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 @@ -3407,9 +3407,10 @@ public int GetModuleMetaDataFileInfo(ulong vmModule, uint* dwTimeStamp, uint* dw if (dwTimeStamp is null || dwImageSize is null || pStrFilename == 0 || pResult is null) throw new NullReferenceException("One or more parameters are null"); *pResult = Interop.BOOL.FALSE; + *dwTimeStamp = 0; + *dwImageSize = 0; if (vmModule == 0) throw Marshal.GetExceptionForHR(HResults.E_FAIL)!; - Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(vmModule); bool result = loader.GetFileHeadersInfo(moduleHandle, out uint timeStamp, out uint imageSize); if (result) From f39b736b7c244dd8be04571579cda5619ec7b693 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 23 Jun 2026 09:04:26 -0700 Subject: [PATCH 07/11] copilot comments --- src/coreclr/inc/sstring.h | 1 + .../Dbi/DacDbiImpl.cs | 3 ++- src/native/managed/cdac/tests/UnitTests/LoaderTests.cs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/inc/sstring.h b/src/coreclr/inc/sstring.h index a40205cd5566a1..c396b470a29db6 100644 --- a/src/coreclr/inc/sstring.h +++ b/src/coreclr/inc/sstring.h @@ -73,6 +73,7 @@ typedef DPTR(class SString) PTR_SString; class EMPTY_BASES SString : private SBuffer { friend struct _DacGlobals; + friend struct ::cdac_data; private: enum Representation 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 34c8c5032facd1..0782556f9fecb5 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 @@ -3411,6 +3411,7 @@ public int GetModuleMetaDataFileInfo(ulong vmModule, uint* dwTimeStamp, uint* dw *dwImageSize = 0; if (vmModule == 0) throw Marshal.GetExceptionForHR(HResults.E_FAIL)!; + Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(vmModule); bool result = loader.GetFileHeadersInfo(moduleHandle, out uint timeStamp, out uint imageSize); if (result) @@ -3418,7 +3419,7 @@ public int GetModuleMetaDataFileInfo(ulong vmModule, uint* dwTimeStamp, uint* dw *dwTimeStamp = timeStamp; *dwImageSize = imageSize; } - path = loader.GetPath(moduleHandle, fallbackToHint: true); + path = loader.GetPath(moduleHandle, true); hr = StringHolderAssignCopy(pStrFilename, path); *pResult = result ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; } diff --git a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 7921ddefafe95b..387019e637eeee 100644 --- a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs @@ -80,7 +80,7 @@ public void GetPath_EmptyPathFallsBackToModuleFileNameHint(MockTarget.Architectu ILoader contract = target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); - Assert.Equal(expectedHint, contract.GetPath(handle, fallbackToHint: true)); + Assert.Equal(expectedHint, contract.GetPath(handle, true)); } [Theory] From 2adb30f7c99eec78280eaf8e943870a8ac4143e5 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 23 Jun 2026 09:43:57 -0700 Subject: [PATCH 08/11] try to get github to work --- .../Dbi/DacDbiImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0782556f9fecb5..00d79cc38e4d8b 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 @@ -4379,7 +4379,7 @@ public int GetILCodeVersionNode(ulong vmNativeCodeVersionNode, ulong* pVmILCodeV if (pVmILCodeVersionNode is null) throw new ArgumentException("Output pointer cannot be null.", nameof(pVmILCodeVersionNode)); - *pVmILCodeVersionNode = 0; + // *pVmILCodeVersionNode = 0; ICodeVersions codeVersions = _target.Contracts.CodeVersions; NativeCodeVersionHandle nativeCodeVersion = NativeCodeVersionHandle.CreateExplicit(new TargetPointer(vmNativeCodeVersionNode)); From 53080e59a811aa928fbb125e52348c38780eb13b Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 23 Jun 2026 09:44:37 -0700 Subject: [PATCH 09/11] Revert "try to get github to work" This reverts commit 2adb30f7c99eec78280eaf8e943870a8ac4143e5. --- .../Dbi/DacDbiImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 00d79cc38e4d8b..0782556f9fecb5 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 @@ -4379,7 +4379,7 @@ public int GetILCodeVersionNode(ulong vmNativeCodeVersionNode, ulong* pVmILCodeV if (pVmILCodeVersionNode is null) throw new ArgumentException("Output pointer cannot be null.", nameof(pVmILCodeVersionNode)); - // *pVmILCodeVersionNode = 0; + *pVmILCodeVersionNode = 0; ICodeVersions codeVersions = _target.Contracts.CodeVersions; NativeCodeVersionHandle nativeCodeVersion = NativeCodeVersionHandle.CreateExplicit(new TargetPointer(vmNativeCodeVersionNode)); From 5408cb135e6fc4bb5117e8095f4481c9093ee3af Mon Sep 17 00:00:00 2001 From: rcj1 Date: Wed, 24 Jun 2026 09:34:45 -0700 Subject: [PATCH 10/11] code review --- docs/design/datacontracts/Loader.md | 42 ++-------- src/coreclr/inc/sbuffer.h | 5 -- src/coreclr/inc/sstring.h | 7 -- .../vm/datadescriptor/datadescriptor.inc | 1 - src/coreclr/vm/peimage.h | 1 - .../Contracts/ILoader.cs | 2 +- .../Contracts/Loader_1.cs | 25 +++--- .../Data/PEImage.cs | 1 - .../ClrDataModule.cs | 5 +- .../Dbi/DacDbiImpl.cs | 28 ++++++- .../cdac/tests/UnitTests/LoaderTests.cs | 78 +++++++++---------- 11 files changed, 82 insertions(+), 113 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index baca456d93ccbe..be6c5e2b55bc53 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -96,7 +96,7 @@ bool IsProbeExtensionResultValid(ModuleHandle handle); ModuleFlags GetFlags(ModuleHandle handle); bool IsReadyToRun(ModuleHandle handle); string GetSimpleName(ModuleHandle handle); -string GetPath(ModuleHandle handle, bool fallbackToHint = false); +string GetPath(ModuleHandle handle); string GetFileName(ModuleHandle handle); bool GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uint imageSize); TargetPointer GetLoaderAllocator(ModuleHandle handle); @@ -646,31 +646,11 @@ string GetSimpleName(ModuleHandle handle) return // convert to string, throw on invalid UTF-8 } -string GetPath(ModuleHandle handle, bool fallbackToHint = false) +string GetPath(ModuleHandle handle) { TargetPointer pathStart = target.ReadPointer(handle.Address + /* Module::Path offset */); - string path = pathStart != TargetPointer.Null - ? /* Read from target starting at pathStart until null terminator */ - : string.Empty; - - // For in-memory modules the path is empty; optionally fall back to the - // PEImage's diagnostic file name hint (reached through the Module's PEAssembly). - if (fallbackToHint && string.IsNullOrEmpty(path)) - { - TargetPointer peAssemblyPtr = target.ReadPointer(handle.Address + /* Module::PEAssembly offset */); - if (peAssemblyPtr != TargetPointer.Null) - { - TargetPointer peImagePtr = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); - if (peImagePtr != TargetPointer.Null) - { - TargetPointer hintPtr = target.ReadPointer(peImagePtr + /* PEImage::ModuleFileNameHint offset */); - if (hintPtr != TargetPointer.Null) - path = target.ReadUtf16String(hintPtr); - } - } - } - - return path; + char[] path = // Read from target starting at pathStart until null terminator + return new string(path); } string GetFileName(ModuleHandle handle) @@ -685,20 +665,8 @@ bool GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uint imageS timeStamp = 0; imageSize = 0; - // Resolve the PEImage through the Module's PEAssembly. - TargetPointer peAssemblyPtr = target.ReadPointer(handle.Address + /* Module::PEAssembly offset */); - if (peAssemblyPtr == TargetPointer.Null) - return false; - - TargetPointer peImagePtr = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); - if (peImagePtr == TargetPointer.Null) + if (!TryGetLoadedImageContents(handle, out TargetPointer baseAddress, out _, out _)) return false; - - TargetPointer layoutPtr = target.ReadPointer(peImagePtr + /* PEImage::LoadedImageLayout offset */); - if (layoutPtr == TargetPointer.Null) - return false; - - TargetPointer baseAddress = target.ReadPointer(layoutPtr + /* PEImageLayout::Base offset */); TargetPointer ntHeadersPtr = baseAddress + // offset to NT headers timeStamp = // read from NT header imageSize = // read from NT header diff --git a/src/coreclr/inc/sbuffer.h b/src/coreclr/inc/sbuffer.h index c01e0093f266ba..3573a798db92c5 100644 --- a/src/coreclr/inc/sbuffer.h +++ b/src/coreclr/inc/sbuffer.h @@ -59,9 +59,6 @@ typedef DPTR(class SBuffer) PTR_SBuffer; -class SString; -template struct cdac_data; - class SBuffer { public: @@ -90,8 +87,6 @@ class SBuffer class Iterator; friend class Iterator; - friend struct ::cdac_data; - //-------------------------------------------------------------------- // Initializers and constructors //-------------------------------------------------------------------- diff --git a/src/coreclr/inc/sstring.h b/src/coreclr/inc/sstring.h index c396b470a29db6..bf2c7ab80a4d87 100644 --- a/src/coreclr/inc/sstring.h +++ b/src/coreclr/inc/sstring.h @@ -73,7 +73,6 @@ typedef DPTR(class SString) PTR_SString; class EMPTY_BASES SString : private SBuffer { friend struct _DacGlobals; - friend struct ::cdac_data; private: enum Representation @@ -895,12 +894,6 @@ typedef InlineSString<2 * 260> LongPathString; #define FAULTS_UNLESS_BOTH_NORMALIZED(s, stmt) \ if (IsNormalized() && s.IsNormalized()) FORBID_FAULT; else INJECT_FAULT(stmt) -template<> -struct cdac_data -{ - static constexpr size_t Buffer = offsetof(SString, m_buffer); -}; - // ================================================================================ // Inline definitions // ================================================================================ diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 0ae45ac574f6a6..c0dbba1a0e6c19 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -406,7 +406,6 @@ CDAC_TYPE_BEGIN(PEImage) CDAC_TYPE_INDETERMINATE(PEImage) CDAC_TYPE_FIELD(PEImage, T_POINTER, LoadedImageLayout, cdac_data::LoadedImageLayout) CDAC_TYPE_FIELD(PEImage, TYPE(ProbeExtensionResult), ProbeExtensionResult, cdac_data::ProbeExtensionResult) -CDAC_TYPE_FIELD(PEImage, T_POINTER, ModuleFileNameHint, cdac_data::ModuleFileNameHint) CDAC_TYPE_END(PEImage) CDAC_TYPE_BEGIN(PEImageLayout) diff --git a/src/coreclr/vm/peimage.h b/src/coreclr/vm/peimage.h index 2dbcbf548bbf3e..ea56c0d8d594c7 100644 --- a/src/coreclr/vm/peimage.h +++ b/src/coreclr/vm/peimage.h @@ -329,7 +329,6 @@ struct cdac_data // The loaded PEImageLayout is m_pLayouts[IMAGE_LOADED] static constexpr size_t LoadedImageLayout = offsetof(PEImage, m_pLayouts) + sizeof(PTR_PEImageLayout); static constexpr size_t ProbeExtensionResult = offsetof(PEImage, m_probeExtensionResult); - static constexpr size_t ModuleFileNameHint = offsetof(PEImage, m_sModuleFileNameHintUsedByDac) + cdac_data::Buffer; }; struct PEImageHolderTraits final 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 2e5aeed906bab1..3428a406fabe7a 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 @@ -111,7 +111,7 @@ public interface ILoader : IContract ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); bool IsReadyToRun(ModuleHandle handle) => throw new NotImplementedException(); string GetSimpleName(ModuleHandle handle) => throw new NotImplementedException(); - string GetPath(ModuleHandle handle, bool fallbackToHint = false) => throw new NotImplementedException(); + string GetPath(ModuleHandle handle) => throw new NotImplementedException(); string GetFileName(ModuleHandle handle) => throw new NotImplementedException(); bool GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uint imageSize) => throw new NotImplementedException(); TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); 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 d7f5d015cac8ff..54b8c857bc8589 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 @@ -455,23 +455,12 @@ string ILoader.GetSimpleName(ModuleHandle handle) : string.Empty; } - string ILoader.GetPath(ModuleHandle handle, bool fallbackToHint) + string ILoader.GetPath(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); - string path = string.Empty; - try - { - path = module.Path != TargetPointer.Null ? _target.ReadUtf16String(module.Path) : string.Empty; - } - catch (VirtualReadException) - { - // Ignore virtual read exceptions - } - - if (fallbackToHint && string.IsNullOrEmpty(path) && TryGetPEImage(handle, out Data.PEImage? peImage) && peImage.ModuleFileNameHint != TargetPointer.Null) - path = _target.ReadUtf16String(peImage.ModuleFileNameHint); - - return path; + return module.Path != TargetPointer.Null + ? _target.ReadUtf16String(module.Path) + : string.Empty; } string ILoader.GetFileName(ModuleHandle handle) @@ -490,8 +479,14 @@ bool ILoader.GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uin if (!TryGetPEImage(handle, out Data.PEImage? peImage)) return false; + if (peImage.LoadedImageLayout == TargetPointer.Null) + return false; // no loaded image layout + Data.PEImageLayout peImageLayout = _target.ProcessedData.GetOrAdd(peImage.LoadedImageLayout); + if (peImageLayout.Format == (uint)ImageFormat.Webcil) + return false; // Webcil images do not have NT headers + TargetPointer ntHeadersPtr = FindNTHeaders(peImageLayout); Data.ImageNTHeaders ntHeaders = _target.ProcessedData.GetOrAdd(ntHeadersPtr); timeStamp = ntHeaders.FileHeader.TimeDateStamp; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs index 683f04f781b7e1..d16ad4fa5b931d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs @@ -8,5 +8,4 @@ internal sealed partial class PEImage : IData { [Field] public TargetPointer LoadedImageLayout { get; } [Field] public ProbeExtensionResult ProbeExtensionResult { get; } - [Field] public TargetPointer ModuleFileNameHint { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 59843a9d7f69c6..6e194d4b19155c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -504,12 +504,11 @@ int IXCLRDataModule.GetFileName(uint bufLen, uint* nameLen, char* name) string result = string.Empty; try { - result = contract.GetPath(handle, true); + result = contract.GetPath(handle); } catch (VirtualReadException) { - // The memory for the path may not be enumerated - for example, in triage dumps - // In this case, GetPath will throw VirtualReadException + result = contract.GetFileName(handle); } if (string.IsNullOrEmpty(result)) 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 0782556f9fecb5..18c23917f086c9 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 @@ -228,7 +228,20 @@ public int GetModulePath(ulong vmModule, nint pStrFilename, Interop.BOOL* pResul { Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); - string path = loader.GetPath(handle, true); + string path = string.Empty; + try + { + path = loader.GetPath(handle); + } + catch (VirtualReadException) + { + path = loader.GetFileName(handle); + } + + if (string.IsNullOrEmpty(path)) + { + path = loader.GetFileName(handle); + } if (string.IsNullOrEmpty(path)) { *pResult = Interop.BOOL.FALSE; @@ -3419,7 +3432,18 @@ public int GetModuleMetaDataFileInfo(ulong vmModule, uint* dwTimeStamp, uint* dw *dwTimeStamp = timeStamp; *dwImageSize = imageSize; } - path = loader.GetPath(moduleHandle, true); + try + { + path = loader.GetPath(moduleHandle); + } + catch (VirtualReadException) + { + path = loader.GetFileName(moduleHandle); + } + if (string.IsNullOrEmpty(path)) + { + path = loader.GetFileName(moduleHandle); + } hr = StringHolderAssignCopy(pStrFilename, path); *pResult = result ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; } diff --git a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 387019e637eeee..7b0464e190e4ef 100644 --- a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs @@ -64,25 +64,13 @@ internal static (ILoader Contract, TestPlaceholderTarget Target) CreateLoaderCon public void GetPath(MockTarget.Architecture arch) { const string expected = @"C:\some\path\TestModule.dll"; - var (target, moduleAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: expected, moduleFileNameHint: null); + var (target, moduleAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: expected); ILoader contract = target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); Assert.Equal(expected, contract.GetPath(handle)); } - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void GetPath_EmptyPathFallsBackToModuleFileNameHint(MockTarget.Architecture arch) - { - const string expectedHint = @"InMemoryModule.dll"; - var (target, moduleAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: null, moduleFileNameHint: expectedHint); - ILoader contract = target.Contracts.Loader; - - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); - Assert.Equal(expectedHint, contract.GetPath(handle, true)); - } - [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetFileName(MockTarget.Architecture arch) @@ -537,7 +525,6 @@ private static (TestPlaceholderTarget Target, TargetPointer PEAssemblyAddr, Targ var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), new(nameof(Data.PEImage.ProbeExtensionResult), DataType.ProbeExtensionResult, probeExtLayout.Stride), - new(nameof(Data.PEImage.ModuleFileNameHint), DataType.pointer), ]); var imageLayoutLayout = helpers.LayoutFields([ new(nameof(Data.PEImageLayout.Base), DataType.pointer), @@ -756,7 +743,6 @@ public void IsModuleMapped_ReturnsExpected(MockTarget.Architecture arch, uint fo var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), new(nameof(Data.PEImage.ProbeExtensionResult), DataType.ProbeExtensionResult, probeExtLayout.Stride), - new(nameof(Data.PEImage.ModuleFileNameHint), DataType.pointer), ]); var imageLayoutLayout = helpers.LayoutFields([ new(nameof(Data.PEImageLayout.Base), DataType.pointer), @@ -1089,7 +1075,8 @@ private static (TestPlaceholderTarget Target, TargetPointer ModuleAddr) CreatePE uint timeStamp, uint imageSize, string? path, - string? moduleFileNameHint) + uint format = 0, + bool nullImageLayout = false) { const uint Lfanew = 0x80; TargetTestHelpers helpers = new(arch); @@ -1105,12 +1092,9 @@ private static (TestPlaceholderTarget Target, TargetPointer ModuleAddr) CreatePE new(nameof(Data.PEAssembly.PEImage), DataType.pointer), new(nameof(Data.PEAssembly.AssemblyBinder), DataType.pointer), ]); - // PEImage.ModuleFileNameHint is modeled as a pointer directly to the - // SString's null-terminated UTF-16 character buffer. var peImageLayout = helpers.LayoutFields([ new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), new(nameof(Data.PEImage.ProbeExtensionResult), DataType.ProbeExtensionResult, probeExtLayout.Stride), - new(nameof(Data.PEImage.ModuleFileNameHint), DataType.pointer), ]); var imageLayoutLayout = helpers.LayoutFields([ new(nameof(Data.PEImageLayout.Base), DataType.pointer), @@ -1145,17 +1129,15 @@ private static (TestPlaceholderTarget Target, TargetPointer ModuleAddr) CreatePE helpers.WritePointer(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Base)].Offset, helpers.PointerSize), image.Address); helpers.Write(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Size)].Offset, sizeof(uint)), imageBufferSize); helpers.Write(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Flags)].Offset, sizeof(uint)), 0u); - helpers.Write(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Format)].Offset, sizeof(uint)), 0u); + helpers.Write(layoutFrag.Data.AsSpan().Slice(imageLayoutLayout.Fields[nameof(Data.PEImageLayout.Format)].Offset, sizeof(uint)), format); var peImageFrag = allocator.Allocate(peImageLayout.Stride, "PEImage"); - helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.LoadedImageLayout)].Offset, helpers.PointerSize), layoutFrag.Address); - helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.ModuleFileNameHint)].Offset, helpers.PointerSize), AllocateUtf16String(helpers, allocator, moduleFileNameHint)); + helpers.WritePointer(peImageFrag.Data.AsSpan().Slice(peImageLayout.Fields[nameof(Data.PEImage.LoadedImageLayout)].Offset, helpers.PointerSize), nullImageLayout ? TargetPointer.Null : layoutFrag.Address); var peAssemblyFrag = allocator.Allocate(peAssemblyLayout.Stride, "PEAssembly"); helpers.WritePointer(peAssemblyFrag.Data.AsSpan().Slice(peAssemblyLayout.Fields[nameof(Data.PEAssembly.PEImage)].Offset, helpers.PointerSize), peImageFrag.Address); - // Build a Module that owns the PEAssembly and carries the module path. GetPath reads the - // path from the Module, falling back to the PEImage's ModuleFileNameHint when it is empty. + // Build a Module that owns the PEAssembly and carries the module path. MockLoaderModule module = loader.AddModule(path: path); module.PEAssembly = peAssemblyFrag.Address; @@ -1168,21 +1150,6 @@ private static (TestPlaceholderTarget Target, TargetPointer ModuleAddr) CreatePE return (target, new TargetPointer(module.Address)); } - // Allocates a null-terminated UTF-16 buffer (mirroring an SString's UNICODE character buffer, - // where an empty string still points at a static zero-terminated buffer) and returns its address. - private static ulong AllocateUtf16String( - TargetTestHelpers helpers, - MockMemorySpace.BumpAllocator allocator, - string? value) - { - value ??= string.Empty; - // WriteUtf16String writes the characters plus a 2-byte null terminator in target endianness. - ulong bufferSize = (ulong)((value.Length + 1) * sizeof(char)); - var bufferFrag = allocator.Allocate(bufferSize, "Utf16StringBuffer"); - helpers.WriteUtf16String(bufferFrag.Data.AsSpan(), value); - return bufferFrag.Address; - } - [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetFileHeadersInfo_ReturnsTimeStampAndImageSize(MockTarget.Architecture arch) @@ -1190,7 +1157,7 @@ public void GetFileHeadersInfo_ReturnsTimeStampAndImageSize(MockTarget.Architect const uint expectedTimeStamp = 0x1234_5678; const uint expectedImageSize = 0x0009_A000; const string expectedPath = @"C:\some\path\Test.dll"; - var (target, moduleAddr) = CreatePETarget(arch, expectedTimeStamp, expectedImageSize, expectedPath, moduleFileNameHint: null); + var (target, moduleAddr) = CreatePETarget(arch, expectedTimeStamp, expectedImageSize, expectedPath); ILoader contract = target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); @@ -1200,4 +1167,35 @@ public void GetFileHeadersInfo_ReturnsTimeStampAndImageSize(MockTarget.Architect Assert.Equal(expectedTimeStamp, timeStamp); Assert.Equal(expectedImageSize, imageSize); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetFileHeadersInfo_NoLoadedImageLayoutReturnsFalse(MockTarget.Architecture arch) + { + var (target, moduleAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: @"C:\some\path\Test.dll", nullImageLayout: true); + ILoader contract = target.Contracts.Loader; + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + bool result = contract.GetFileHeadersInfo(handle, out uint timeStamp, out uint imageSize); + + Assert.False(result); + Assert.Equal(0u, timeStamp); + Assert.Equal(0u, imageSize); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetFileHeadersInfo_WebcilImageReturnsFalse(MockTarget.Architecture arch) + { + // ImageFormat.Webcil == 1 (Loader_1.ImageFormat is private to the contract). + var (target, moduleAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: @"C:\some\path\Test.dll", format: 1u); + ILoader contract = target.Contracts.Loader; + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + bool result = contract.GetFileHeadersInfo(handle, out uint timeStamp, out uint imageSize); + + Assert.False(result); + Assert.Equal(0u, timeStamp); + Assert.Equal(0u, imageSize); + } } From a730bd66666b1b561a154a99c717dbd786c3a23b Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Wed, 24 Jun 2026 09:38:07 -0700 Subject: [PATCH 11/11] Update Loader.md --- docs/design/datacontracts/Loader.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index be6c5e2b55bc53..4d7e27bacebfbc 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -188,7 +188,6 @@ enum ClrModifiableAssemblies : uint | `AssemblyBinder` | `AssemblyLoadContext` | Pointer to the AssemblyBinder's AssemblyLoadContext | | `PEImage` | `LoadedImageLayout` | Pointer to the PEImage's loaded PEImageLayout | | `PEImage` | `ProbeExtensionResult` | PEImage's ProbeExtensionResult | -| `PEImage` | `ModuleFileNameHint` | Pointer to a diagnostic file name hint for in-memory modules | | `ProbeExtensionResult` | `Type` | Type of ProbeExtensionResult | | `PEImageLayout` | `Base` | Base address of the image layout | | `PEImageLayout` | `Size` | Size of the image layout |