diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 8e9d6ccb6f98d9..4d7e27bacebfbc 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -98,6 +98,7 @@ bool IsReadyToRun(ModuleHandle handle); string GetSimpleName(ModuleHandle handle); string GetPath(ModuleHandle handle); 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); @@ -658,6 +659,19 @@ string GetFileName(ModuleHandle handle) return new string(fileName); } +bool GetFileHeadersInfo(ModuleHandle handle, out uint timeStamp, out uint imageSize) +{ + timeStamp = 0; + imageSize = 0; + + if (!TryGetLoadedImageContents(handle, out TargetPointer baseAddress, out _, out _)) + return false; + 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..f721578d9d9396 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -5936,6 +5936,27 @@ 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; + + 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 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/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 81af07a4463e3a..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 @@ -113,6 +113,7 @@ public interface ILoader : IContract string GetSimpleName(ModuleHandle handle) => 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(); 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 06f4acffd46479..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 @@ -471,6 +471,29 @@ 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; + + 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; + 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/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.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 8fc9d53e7df765..6e194d4b19155c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -508,8 +508,7 @@ int IXCLRDataModule.GetFileName(uint bufLen, uint* nameLen, char* name) } 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 b63a939a45de19..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); + 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; @@ -3398,8 +3411,71 @@ public int GetAttachStateFlags(int* pRetVal) return hr; } - 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; + public int GetModuleMetaDataFileInfo(ulong vmModule, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult) + { + 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; + *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) + { + *dwTimeStamp = timeStamp; + *dwImageSize = imageSize; + } + 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; + } + 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.GetModuleMetaDataFileInfo(vmModule, &timeStampLocal, &imageSizeLocal, legacyHolder.Ptr, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(*pResult == resultLocal, $"GetModuleMetaDataFileInfo result mismatch - cDAC: {*pResult}, DAC: {resultLocal}"); + if (*pResult == Interop.BOOL.TRUE) + { + 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), + $"GetModuleMetaDataFileInfo 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/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/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 8843a4d5161011..7b0464e190e4ef 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,12 @@ 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; - - ILoader contract = CreateLoaderContract(arch, loader => - { - moduleAddr = loader.AddModule(path: expected).Address; - moduleAddrEmptyPath = loader.AddModule().Address; - }); + const string expected = @"C:\some\path\TestModule.dll"; + var (target, moduleAddr) = CreatePETarget(arch, timeStamp: 1, imageSize: 2, path: expected); + ILoader contract = target.Contracts.Loader; - { - 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); - } + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + Assert.Equal(expected, contract.GetPath(handle)); } [Theory] @@ -1084,4 +1069,133 @@ public void TryGetSymbolStream_EmptyStream(MockTarget.Architecture arch) Assert.Equal(TargetPointer.Null, buffer); Assert.Equal(0u, size); } + + private static (TestPlaceholderTarget Target, TargetPointer ModuleAddr) CreatePETarget( + MockTarget.Architecture arch, + uint timeStamp, + uint imageSize, + string? path, + uint format = 0, + bool nullImageLayout = false) + { + const uint Lfanew = 0x80; + 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([ + 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), + ]); + var peImageLayout = helpers.LayoutFields([ + new(nameof(Data.PEImage.LoadedImageLayout), DataType.pointer), + new(nameof(Data.PEImage.ProbeExtensionResult), DataType.ProbeExtensionResult, probeExtLayout.Stride), + ]); + 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)), format); + + var peImageFrag = allocator.Allocate(peImageLayout.Stride, "PEImage"); + 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. + 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(module.Address)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetFileHeadersInfo_ReturnsTimeStampAndImageSize(MockTarget.Architecture arch) + { + 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); + ILoader contract = target.Contracts.Loader; + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + bool result = contract.GetFileHeadersInfo(handle, out uint timeStamp, out uint imageSize); + + Assert.True(result); + 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); + } } 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..0333d5e80bea9e 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs @@ -272,10 +272,10 @@ internal MockLoaderHeapBlock AddLoaderHeapBlock(ulong virtualAddress, ulong virt } internal MockLoaderModule AddModule( - string? path = null, 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")); @@ -285,15 +285,15 @@ internal MockLoaderModule AddModule( module.Flags = flags; } - byte[]? rawSimpleName = simpleName is not null ? Encoding.UTF8.GetBytes(simpleName) : simpleNameBytes; - if (rawSimpleName is not null) + if (path is not null) { - module.SimpleName = AddNullTerminatedUtf8(rawSimpleName, "Module simple name"); + module.Path = AddUtf16String(path, $"Module path = {path}"); } - if (path is not null) + byte[]? rawSimpleName = simpleName is not null ? Encoding.UTF8.GetBytes(simpleName) : simpleNameBytes; + if (rawSimpleName is not null) { - module.Path = AddUtf16String(path, $"Module path = {path}"); + module.SimpleName = AddNullTerminatedUtf8(rawSimpleName, "Module simple name"); } if (fileName is not null)