diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 46fa6b716fb651..2f4c6f1233788e 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -61,6 +61,7 @@ TargetPointer GetModule(ModuleHandle handle); TargetPointer GetAssembly(ModuleHandle handle); TargetPointer GetPEAssembly(ModuleHandle handle); bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags); +TargetPointer ILoader.GetILAddr(TargetPointer peAssemblyPtr, int rva); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size); IEnumerable GetAvailableTypeParams(ModuleHandle handle); IEnumerable GetInstantiatedMethods(ModuleHandle handle); @@ -170,6 +171,11 @@ private enum ModuleFlags_1 : uint EditAndContinue = 0x00000008, // Edit and Continue is enabled for this module ReflectionEmit = 0x00000040, // Reflection.Emit was used to create this module } + +private enum PEImageFlags : uint +{ + FLAG_MAPPED = 0x01, // the file is mapped/hydrated (vs. the raw disk layout) +}; ``` ### Method Implementations @@ -333,6 +339,73 @@ bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddres return true; } +TargetPointer ILoader.GetILAddr(TargetPointer peAssemblyPtr, int rva) +{ + TargetPointer peImage = target.ReadPointer(peAssemblyPtr + /* PEAssembly::PEImage offset */); + if(peImage == TargetPointer.Null) + throw new InvalidOperationException("PEAssembly does not have a PEImage associated with it."); + + TargetPointer peImageLayout = target.ReadPointer(peImage + /* PEImage::LoadedImageLayout offset */); + if(peImageLayout == TargetPointer.Null) + throw new InvalidOperationException("PEImage does not have a LoadedImageLayout associated with it."); + + // Get base address and flags from PEImageLayout + TargetPointer baseAddress = target.ReadPointer(peImageLayout + /* PEImageLayout::Base offset */); + uint imageFlags = target.Read(peImageLayout + /* PEImageLayout::Flags offset */); + + bool isMapped = (imageFlags & (uint)PEImageFlags.FLAG_MAPPED) != 0; + + uint offset; + if (isMapped) + { + offset = (uint)rva; + } + else + { + // find NT headers using DOS header + uint dosHeaderLfanew = target.Read(baseAddress + /* ImageDosHeader::LfanewOffset */); + TargetPointer ntHeadersPtr = baseAddress + dosHeaderLfanew; + + TargetPointer optionalHeaderPtr = ntHeadersPtr + /* ImageNTHeaders::OptionalHeaderOffset */; + + // Get number of sections from file header + TargetPointer fileHeaderPtr = ntHeadersPtr + /* ImageNTHeaders::FileHeaderOffset */; + uint numberOfSections = target.Read(fileHeaderPtr + /* ImageFileHeader::NumberOfSectionsOffset */); + + // Calculate first section address (after NT headers and optional header) + uint imageFileHeaderSize = target.Read(fileHeaderPtr + /* ImageFileHeader::SizeOfOptionalHeaderOffset */); + TargetPointer firstSectionPtr = ntHeadersPtr + /* ImageNTHeaders::OptionalHeaderOffset */ + imageFileHeaderSize; + + // Find the section containing this RVA + TargetPointer sectionPtr = TargetPointer.Null; + uint sectionHeaderSize = /* sizeof(ImageSectionHeader native struct) */; + + for (uint i = 0; i < numberOfSections; i++) + { + TargetPointer currentSectionPtr = firstSectionPtr + (i * sectionHeaderSize); + uint virtualAddress = target.Read(currentSectionPtr + /* ImageSectionHeader::VirtualAddressOffset */); + uint sizeOfRawData = target.Read(currentSectionPtr + /* ImageSectionHeader::SizeOfRawDataOffset */); + + if (rva >= VirtualAddress && rva < VirtualAddress + SizeOfRawData) + { + sectionPtr = currentSectionPtr; + } + } + if (sectionPtr == TargetPointer.Null) + { + throw new InvalidOperationException("Failed to read from image."); + } + else + { + // Convert RVA to file offset using section information + uint sectionVirtualAddress = target.Read(sectionPtr + /* ImageSectionHeader::VirtualAddressOffset */); + uint sectionPointerToRawData = target.Read(sectionPtr + /* ImageSectionHeader::PointerToRawDataOffset */); + offset = ((rva - sectionVirtualAddress) + sectionPointerToRawData); + } + } + return baseAddress + offset; +} + bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size) { buffer = TargetPointer.Null; diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.h b/src/coreclr/vm/datadescriptor/datadescriptor.h index 84adff2a49b4d3..49a52cdd0e23f3 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.h +++ b/src/coreclr/vm/datadescriptor/datadescriptor.h @@ -1,6 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#ifndef HOST_WINDOWS +#include "../pal/inc/pal.h" +#include "../pal/inc/rt/ntimage.h" +#endif // HOST_WINDOWS #include "common.h" #include 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 8822dfee7ba384..8151ebe20a65d7 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 @@ -84,6 +84,7 @@ public interface ILoader : IContract TargetPointer GetAssembly(ModuleHandle handle) => throw new NotImplementedException(); TargetPointer GetPEAssembly(ModuleHandle handle) => throw new NotImplementedException(); 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(); 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(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index d691dd0eedd23e..e6bb21976d84b3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -147,6 +147,14 @@ public abstract class Target /// Thrown when the read operation fails public abstract T Read(ulong address) where T : unmanaged, IBinaryInteger, IMinMaxValue; + /// + /// Read a value from the target in little endianness + /// + /// Type of value to read + /// Address to start reading from + /// Value read from the target + public abstract T ReadLittleEndian(ulong address) where T : unmanaged, IBinaryInteger, IMinMaxValue; + /// /// Read a value from the target in target endianness /// 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 2d2562625e4688..8cd47ec7a4cadd 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 @@ -37,6 +37,10 @@ private enum ModuleFlags_1 : uint BeingUnloaded = 0x100000, } + private enum PEImageFlags : uint + { + FLAG_MAPPED = 0x01, // the file is mapped/hydrated (vs. the raw disk layout) + }; private readonly Target _target; internal Loader_1(Target target) @@ -196,6 +200,64 @@ bool ILoader.TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer ba return true; } + private static bool IsMapped(Data.PEImageLayout peImageLayout) + { + return (peImageLayout.Flags & (uint)PEImageFlags.FLAG_MAPPED) != 0; + } + + private TargetPointer FindNTHeaders(Data.PEImageLayout imageLayout) + { + Data.ImageDosHeader dosHeader = _target.ProcessedData.GetOrAdd(imageLayout.Base); + return imageLayout.Base + (uint)dosHeader.Lfanew; + } + + private TargetPointer RvaToSection(int rva, Data.PEImageLayout imageLayout) + { + TargetPointer ntHeadersPtr = FindNTHeaders(imageLayout); + Data.ImageNTHeaders ntHeaders = _target.ProcessedData.GetOrAdd(ntHeadersPtr); + int offset = Data.ImageNTHeaders.OptionalHeaderOffset; + TargetPointer section = ntHeadersPtr + (uint)offset + ntHeaders.FileHeader.SizeOfOptionalHeader; + TargetPointer sectionEnd = section + Data.ImageSectionHeader.Size * ntHeaders.FileHeader.NumberOfSections; + while (section < sectionEnd) + { + Data.ImageSectionHeader sectionHeader = _target.ProcessedData.GetOrAdd(section); + if (rva >= sectionHeader.VirtualAddress && rva < sectionHeader.VirtualAddress + sectionHeader.SizeOfRawData) + { + return section; + } + section += Data.ImageSectionHeader.Size; + } + return TargetPointer.Null; + } + + private uint RvaToOffset(int rva, Data.PEImageLayout imageLayout) + { + TargetPointer section = RvaToSection(rva, imageLayout); + if (section == TargetPointer.Null) + throw new InvalidOperationException("Failed to read from image."); + + Data.ImageSectionHeader sectionHeader = _target.ProcessedData.GetOrAdd(section); + uint offset = (uint)(rva - sectionHeader.VirtualAddress) + sectionHeader.PointerToRawData; + return offset; + } + + TargetPointer ILoader.GetILAddr(TargetPointer peAssemblyPtr, int rva) + { + Data.PEAssembly assembly = _target.ProcessedData.GetOrAdd(peAssemblyPtr); + if (assembly.PEImage == TargetPointer.Null) + throw new InvalidOperationException("PEAssembly does not have a PEImage associated with it."); + Data.PEImage peImage = _target.ProcessedData.GetOrAdd(assembly.PEImage); + if (peImage.LoadedImageLayout == TargetPointer.Null) + throw new InvalidOperationException("PEImage does not have a LoadedImageLayout associated with it."); + Data.PEImageLayout peImageLayout = _target.ProcessedData.GetOrAdd(peImage.LoadedImageLayout); + uint offset; + if (IsMapped(peImageLayout)) + offset = (uint)rva; + else + offset = RvaToOffset(rva, peImageLayout); + return peImageLayout.Base + offset; + } + bool ILoader.TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size) { buffer = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDosHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDosHeader.cs new file mode 100644 index 00000000000000..3b071c3600bab7 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageDosHeader.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ImageDosHeader : IData +{ + static ImageDosHeader IData.Create(Target target, TargetPointer address) + => new ImageDosHeader(target, address); + private const int LfanewOffset = 60; + + public ImageDosHeader(Target target, TargetPointer address) + { + Lfanew = target.ReadLittleEndian(address + LfanewOffset); + } + public int Lfanew { get; init; } +} 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 new file mode 100644 index 00000000000000..e65a7aa83d4753 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageFileHeader.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ImageFileHeader : IData +{ + static ImageFileHeader IData.Create(Target target, TargetPointer address) => new ImageFileHeader(target, address); + private const int NumberOfSectionsOffset = 2; + private const int SizeOfOptionalHeaderOffset = 16; + public ImageFileHeader(Target target, TargetPointer address) + { + NumberOfSections = target.ReadLittleEndian(address + NumberOfSectionsOffset); + SizeOfOptionalHeader = target.ReadLittleEndian(address + SizeOfOptionalHeaderOffset); + } + public ushort NumberOfSections { get; init; } + public ushort SizeOfOptionalHeader { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageNTHeaders.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageNTHeaders.cs new file mode 100644 index 00000000000000..3a28647549183c --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageNTHeaders.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ImageNTHeaders : IData +{ + static ImageNTHeaders IData.Create(Target target, TargetPointer address) => new ImageNTHeaders(target, address); + public const int FileHeaderOffset = 4; + public const int OptionalHeaderOffset = 24; + public ImageNTHeaders(Target target, TargetPointer address) + { + FileHeader = target.ProcessedData.GetOrAdd(address + FileHeaderOffset); + OptionalHeader = target.ProcessedData.GetOrAdd(address + OptionalHeaderOffset); + } + public ImageFileHeader FileHeader { get; init; } + public ImageOptionalHeader OptionalHeader { get; init; } +} 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 new file mode 100644 index 00000000000000..fae2d10eebd178 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageOptionalHeader.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ImageOptionalHeader : IData +{ + static ImageOptionalHeader IData.Create(Target target, TargetPointer address) => new ImageOptionalHeader(target, address); + private const int SectionAlignmentOffset = 32; + public ImageOptionalHeader(Target target, TargetPointer address) + { + SectionAlignment = target.ReadLittleEndian(address + SectionAlignmentOffset); + } + public uint SectionAlignment { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageSectionHeader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageSectionHeader.cs new file mode 100644 index 00000000000000..85c80cf0ab3feb --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ImageSectionHeader.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ImageSectionHeader : IData +{ + static ImageSectionHeader IData.Create(Target target, TargetPointer address) => new ImageSectionHeader(target, address); + private const int VirtualSizeOffset = 8; + private const int VirtualAddressOffset = 12; + private const int SizeOfRawDataOffset = 16; + private const int PointerToRawDataOffset = 20; + public const uint Size = 40; + public ImageSectionHeader(Target target, TargetPointer address) + { + VirtualSize = target.ReadLittleEndian(address + VirtualSizeOffset); + VirtualAddress = target.ReadLittleEndian(address + VirtualAddressOffset); + SizeOfRawData = target.ReadLittleEndian(address + SizeOfRawDataOffset); + PointerToRawData = target.ReadLittleEndian(address + PointerToRawDataOffset); + } + + public uint VirtualSize { get; init; } + public uint VirtualAddress { get; init; } + public uint SizeOfRawData { get; init; } + public uint PointerToRawData { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index e5ae46693bc20e..ece156f6dfbbca 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -302,6 +302,20 @@ public override T Read(ulong address) return value; } + /// + /// Read a value from the target in little endianness + /// + /// Type of value to read + /// Address to start reading from + /// Value read from the target + public override T ReadLittleEndian(ulong address) + { + if (!TryRead(address, true, _dataTargetDelegates, out T value)) + throw new VirtualReadException($"Failed to read {typeof(T)} at 0x{address:x8}."); + + return value; + } + /// /// Read a value from the target in target endianness /// diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index 387faa35766fa5..bb726a52164188 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -660,7 +660,43 @@ int ISOSDacInterface.GetHillClimbingLogEntry(ClrDataAddress addr, void* data) return hr; } int ISOSDacInterface.GetILForModule(ClrDataAddress moduleAddr, int rva, ClrDataAddress* il) - => _legacyImpl is not null ? _legacyImpl.GetILForModule(moduleAddr, rva, il) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + if (moduleAddr == 0 || il == null) + { + hr = HResults.E_INVALIDARG; + } + else if (rva == 0) + *il = 0; + else + { + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + TargetPointer module = moduleAddr.ToTargetPointer(_target); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(module); + TargetPointer peAssemblyPtr = loader.GetPEAssembly(moduleHandle); + *il = loader.GetILAddr(peAssemblyPtr, rva).ToClrDataAddress(_target); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + } +#if DEBUG + if (_legacyImpl is not null) + { + ClrDataAddress ilLocal; + int hrLocal = _legacyImpl.GetILForModule(moduleAddr, rva, &ilLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(*il == ilLocal, $"cDAC: {*il:x}, DAC: {ilLocal:x}"); + } + } +#endif + return hr; + } int ISOSDacInterface.GetJitHelperFunctionName(ClrDataAddress ip, uint count, byte* name, uint* pNeeded) => _legacyImpl is not null ? _legacyImpl.GetJitHelperFunctionName(ip, count, name, pNeeded) : HResults.E_NOTIMPL; int ISOSDacInterface.GetJitManagerList(uint count, void* managers, uint* pNeeded) diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index c05125b483a4fc..289873f6222ca2 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -162,6 +162,20 @@ public override bool TryReadGlobalString(string name, [NotNullWhen(true)] out st public override T Read(ulong address) => DefaultRead(address); + public override T ReadLittleEndian(ulong address) + { + T value = default; + unsafe + { + Span buffer = stackalloc byte[sizeof(T)]; + if (_dataReader(address, buffer) < 0) + throw new VirtualReadException($"Failed to read {typeof(T)} at 0x{address:x8}."); + + T.TryReadLittleEndian(buffer, !IsSigned(), out value); + } + return value; + } + public override bool TryRead(ulong address, out T value) { value = default;