Skip to content
73 changes: 73 additions & 0 deletions docs/design/datacontracts/Loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TargetPointer> GetAvailableTypeParams(ModuleHandle handle);
IEnumerable<TargetPointer> GetInstantiatedMethods(ModuleHandle handle);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<uint>(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<uint>(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<uint>(fileHeaderPtr + /* ImageFileHeader::NumberOfSectionsOffset */);

// Calculate first section address (after NT headers and optional header)
uint imageFileHeaderSize = target.Read<ushort>(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<uint>(currentSectionPtr + /* ImageSectionHeader::VirtualAddressOffset */);
uint sizeOfRawData = target.Read<uint>(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<uint>(sectionPtr + /* ImageSectionHeader::VirtualAddressOffset */);
uint sectionPointerToRawData = target.Read<uint>(sectionPtr + /* ImageSectionHeader::PointerToRawDataOffset */);
offset = ((rva - sectionVirtualAddress) + sectionPointerToRawData);
}
}
return baseAddress + offset;
}

bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size)
{
buffer = TargetPointer.Null;
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TargetPointer> GetAvailableTypeParams(ModuleHandle handle) => throw new NotImplementedException();
IEnumerable<TargetPointer> GetInstantiatedMethods(ModuleHandle handle) => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ public abstract class Target
/// <exception cref="VirtualReadException">Thrown when the read operation fails</exception>
public abstract T Read<T>(ulong address) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>;

/// <summary>
/// Read a value from the target in little endianness
/// </summary>
/// <typeparam name="T">Type of value to read</typeparam>
/// <param name="address">Address to start reading from</param>
/// <returns>Value read from the target</returns>
public abstract T ReadLittleEndian<T>(ulong address) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>;

/// <summary>
/// Read a value from the target in target endianness
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<Data.ImageDosHeader>(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<Data.ImageNTHeaders>(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<Data.ImageSectionHeader>(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<Data.ImageSectionHeader>(section);
uint offset = (uint)(rva - sectionHeader.VirtualAddress) + sectionHeader.PointerToRawData;
return offset;
}

TargetPointer ILoader.GetILAddr(TargetPointer peAssemblyPtr, int rva)
{
Data.PEAssembly assembly = _target.ProcessedData.GetOrAdd<Data.PEAssembly>(peAssemblyPtr);
if (assembly.PEImage == TargetPointer.Null)
throw new InvalidOperationException("PEAssembly does not have a PEImage associated with it.");
Data.PEImage peImage = _target.ProcessedData.GetOrAdd<Data.PEImage>(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<Data.PEImageLayout>(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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ImageDosHeader>
{
static ImageDosHeader IData<ImageDosHeader>.Create(Target target, TargetPointer address)
=> new ImageDosHeader(target, address);
private const int LfanewOffset = 60;

public ImageDosHeader(Target target, TargetPointer address)
{
Lfanew = target.ReadLittleEndian<int>(address + LfanewOffset);
}
public int Lfanew { get; init; }
}
Original file line number Diff line number Diff line change
@@ -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<ImageFileHeader>
{
static ImageFileHeader IData<ImageFileHeader>.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<ushort>(address + NumberOfSectionsOffset);
SizeOfOptionalHeader = target.ReadLittleEndian<ushort>(address + SizeOfOptionalHeaderOffset);
}
public ushort NumberOfSections { get; init; }
public ushort SizeOfOptionalHeader { get; init; }
}
Original file line number Diff line number Diff line change
@@ -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<ImageNTHeaders>
{
static ImageNTHeaders IData<ImageNTHeaders>.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<ImageFileHeader>(address + FileHeaderOffset);
OptionalHeader = target.ProcessedData.GetOrAdd<ImageOptionalHeader>(address + OptionalHeaderOffset);
}
public ImageFileHeader FileHeader { get; init; }
public ImageOptionalHeader OptionalHeader { get; init; }
}
Original file line number Diff line number Diff line change
@@ -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<ImageOptionalHeader>
{
static ImageOptionalHeader IData<ImageOptionalHeader>.Create(Target target, TargetPointer address) => new ImageOptionalHeader(target, address);
private const int SectionAlignmentOffset = 32;
public ImageOptionalHeader(Target target, TargetPointer address)
{
SectionAlignment = target.ReadLittleEndian<uint>(address + SectionAlignmentOffset);
}
public uint SectionAlignment { get; init; }
}
Original file line number Diff line number Diff line change
@@ -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<ImageSectionHeader>
{
static ImageSectionHeader IData<ImageSectionHeader>.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<uint>(address + VirtualSizeOffset);
VirtualAddress = target.ReadLittleEndian<uint>(address + VirtualAddressOffset);
SizeOfRawData = target.ReadLittleEndian<uint>(address + SizeOfRawDataOffset);
PointerToRawData = target.ReadLittleEndian<uint>(address + PointerToRawDataOffset);
}

public uint VirtualSize { get; init; }
public uint VirtualAddress { get; init; }
public uint SizeOfRawData { get; init; }
public uint PointerToRawData { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,20 @@ public override T Read<T>(ulong address)
return value;
}

/// <summary>
/// Read a value from the target in little endianness
/// </summary>
/// <typeparam name="T">Type of value to read</typeparam>
/// <param name="address">Address to start reading from</param>
/// <returns>Value read from the target</returns>
public override T ReadLittleEndian<T>(ulong address)
{
if (!TryRead(address, true, _dataTargetDelegates, out T value))
throw new VirtualReadException($"Failed to read {typeof(T)} at 0x{address:x8}.");

return value;
}

/// <summary>
/// Read a value from the target in target endianness
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions src/native/managed/cdac/tests/TestPlaceholderTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,20 @@ public override bool TryReadGlobalString(string name, [NotNullWhen(true)] out st

public override T Read<T>(ulong address) => DefaultRead<T>(address);

public override T ReadLittleEndian<T>(ulong address)
{
T value = default;
unsafe
{
Span<byte> 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<T>(), out value);
}
return value;
}

public override bool TryRead<T>(ulong address, out T value)
{
value = default;
Expand Down
Loading