Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/design/datacontracts/Loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ IEnumerable<TargetPointer> GetInstantiatedMethods(ModuleHandle handle);

bool IsProbeExtensionResultValid(ModuleHandle handle);
ModuleFlags GetFlags(ModuleHandle handle);
bool TryGetSimpleName(ModuleHandle handle, out string simpleName);
string GetPath(ModuleHandle handle);
string GetFileName(ModuleHandle handle);
Comment thread
rcj1 marked this conversation as resolved.
TargetPointer GetLoaderAllocator(ModuleHandle handle);
Expand Down Expand Up @@ -102,6 +103,7 @@ IReadOnlyDictionary<string, TargetPointer> GetLoaderAllocatorHeaps(TargetPointer
| `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) |
Comment thread
rcj1 marked this conversation as resolved.
| `Module` | `GrowableSymbolStream` | Pointer to the in memory symbol stream |
Comment thread
rcj1 marked this conversation as resolved.
| `Module` | `AvailableTypeParams` | Pointer to an EETypeHashTable |
| `Module` | `InstMethodHashTable` | Pointer to an InstMethodHashTable |
Expand Down Expand Up @@ -575,6 +577,16 @@ ModuleFlags GetFlags(ModuleHandle handle)
return GetFlags(target.Read<uint>(handle.Address + /* Module::Flags offset */));
}

bool TryGetSimpleName(ModuleHandle handle, out string simpleName)
{
TargetPointer simpleNameStart = target.ReadPointer(handle.Address + /* Module::SimpleName offset */);
if (simpleNameStart == TargetPointer.Null)
return false;
byte[] simpleNameBytes = // Read<byte> from target starting at simpleNameStart until null terminator
simpleName = // convert to string, throw on invalid UTF-8
return true;
}

string GetPath(ModuleHandle handle)
{
TargetPointer pathStart = target.ReadPointer(handle.Address + /* Module::Path offset */);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/ceeload.h
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 +1717,7 @@ struct cdac_data<Module>
static constexpr size_t Flags = offsetof(Module, m_dwTransientFlags);
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);
Comment thread
rcj1 marked this conversation as resolved.
static constexpr size_t FileName = offsetof(Module, m_fileName);
static constexpr size_t ReadyToRunInfo = offsetof(Module, m_pReadyToRunInfo);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, Base, cdac_data<Module>::Base)
CDAC_TYPE_FIELD(Module, /*uint32*/, Flags, cdac_data<Module>::Flags)
CDAC_TYPE_FIELD(Module, /*pointer*/, LoaderAllocator, cdac_data<Module>::LoaderAllocator)
CDAC_TYPE_FIELD(Module, /*pointer*/, DynamicMetadata, cdac_data<Module>::DynamicMetadata)
CDAC_TYPE_FIELD(Module, /*pointer*/, SimpleName, cdac_data<Module>::SimpleName)
CDAC_TYPE_FIELD(Module, /*pointer*/, Path, cdac_data<Module>::Path)
CDAC_TYPE_FIELD(Module, /*pointer*/, FileName, cdac_data<Module>::FileName)
CDAC_TYPE_FIELD(Module, /*pointer*/, ReadyToRunInfo, cdac_data<Module>::ReadyToRunInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public interface ILoader : IContract

bool IsProbeExtensionResultValid(ModuleHandle handle) => throw new NotImplementedException();
ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException();
bool TryGetSimpleName(ModuleHandle handle, out string simpleName) => throw new NotImplementedException();
string GetPath(ModuleHandle handle) => throw new NotImplementedException();
string GetFileName(ModuleHandle handle) => throw new NotImplementedException();
TargetPointer GetLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,10 @@ public abstract class Target
/// Read a null-terminated UTF-8 string from the target
/// </summary>
/// <param name="address">Address to start reading from</param>
/// <param name="strict">If true, throw if the string is not valid UTF-8. If false, replace invalid sequences with the replacement character.</param>
/// <returns>String read from the target</returns>
/// <exception cref="VirtualReadException">Thrown when the read operation fails</exception>
public abstract string ReadUtf8String(ulong address);
public abstract string ReadUtf8String(ulong address, bool strict = false);
Comment thread
rcj1 marked this conversation as resolved.

/// <summary>
/// Read a null-terminated UTF-16 string from the target in target endianness
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,19 @@ ModuleFlags ILoader.GetFlags(ModuleHandle handle)
return GetFlags(module);
}

bool ILoader.TryGetSimpleName(ModuleHandle handle, out string simpleName)
{
simpleName = string.Empty;
Data.Module module = _target.ProcessedData.GetOrAdd<Data.Module>(handle.Address);
if (module.SimpleName != TargetPointer.Null)
{
simpleName = _target.ReadUtf8String(module.SimpleName, strict: true);
return true;
}
else
return false;
}

string ILoader.GetPath(ModuleHandle handle)
{
Data.Module module = _target.ProcessedData.GetOrAdd<Data.Module>(handle.Address);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public Module(Target target, TargetPointer address)
Base = target.ReadPointer(address + (ulong)type.Fields[nameof(Base)].Offset);
LoaderAllocator = target.ReadPointer(address + (ulong)type.Fields[nameof(LoaderAllocator)].Offset);
DynamicMetadata = target.ReadPointer(address + (ulong)type.Fields[nameof(DynamicMetadata)].Offset);
SimpleName = target.ReadPointer(address + (ulong)type.Fields[nameof(SimpleName)].Offset);
Path = target.ReadPointer(address + (ulong)type.Fields[nameof(Path)].Offset);
Comment thread
rcj1 marked this conversation as resolved.
FileName = target.ReadPointer(address + (ulong)type.Fields[nameof(FileName)].Offset);
ReadyToRunInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(ReadyToRunInfo)].Offset);
Expand All @@ -43,6 +44,7 @@ public Module(Target target, TargetPointer address)
public TargetPointer Base { get; init; }
public TargetPointer LoaderAllocator { get; init; }
public TargetPointer DynamicMetadata { get; init; }
public TargetPointer SimpleName { get; init; }
public TargetPointer Path { get; init; }
public TargetPointer FileName { get; init; }
public TargetPointer ReadyToRunInfo { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,51 @@ int IXCLRDataModule.EndEnumDataByName(ulong handle)
=> _legacyModule is not null ? _legacyModule.EndEnumDataByName(handle) : HResults.E_NOTIMPL;

int IXCLRDataModule.GetName(uint bufLen, uint* nameLen, char* name)
=> _legacyModule is not null ? _legacyModule.GetName(bufLen, nameLen, name) : HResults.E_NOTIMPL;
{
int hr = HResults.S_OK;
int E_INSUFFICIENT_BUFFER = unchecked((int)0x8007007A);
try
{
if (nameLen != null)
*nameLen = 0;
Contracts.ILoader loader = _target.Contracts.Loader;
Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(_address);
if (!loader.TryGetSimpleName(handle, out string result))
throw new ArgumentException("Module does not have a simple name");

uint nameLenLocal = 0;
OutputBufferHelpers.CopyStringToBuffer(name, bufLen, &nameLenLocal, result);
if (nameLen != null)
*nameLen = nameLenLocal;
// throw on insufficient buffer
if (nameLenLocal > bufLen)
Comment thread
rcj1 marked this conversation as resolved.
throw Marshal.GetExceptionForHR(E_INSUFFICIENT_BUFFER)!;
Comment thread
rcj1 marked this conversation as resolved.
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (_legacyModule is not null)
{
char[] nameLocal = new char[bufLen];
uint nameLenLocal;
int hrLocal;
fixed (char* ptr = nameLocal)
{
hrLocal = _legacyModule.GetName(bufLen, &nameLenLocal, ptr);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(nameLen == null || *nameLen == nameLenLocal);
Debug.Assert(name == null || new ReadOnlySpan<char>(nameLocal, 0, (int)nameLenLocal - 1).SequenceEqual(new string(name)));
}
}
#endif
return hr;
}
int IXCLRDataModule.GetFileName(uint bufLen, uint* nameLen, char* name)
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ private readonly struct Configuration
public delegate int ReadFromTargetDelegate(ulong address, Span<byte> bufferToFill);
public delegate int WriteToTargetDelegate(ulong address, Span<byte> bufferToWrite);
public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, Span<byte> bufferToFill);
private static readonly UTF8Encoding strictUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private static readonly UTF8Encoding looseUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);

/// <summary>
/// Create a new target instance from a contract descriptor embedded in the target memory.
Expand Down Expand Up @@ -595,8 +597,9 @@ public void ReadPointers(ulong address, Span<TargetPointer> buffer)
/// Read a null-terminated UTF-8 string from the target
/// </summary>
/// <param name="address">Address to start reading from</param>
/// <param name="strict">Whether to throw on invalid UTF-8 sequences. If false, invalid sequences will be replaced with the replacement character.</param>
/// <returns>String read from the target</returns>
public override string ReadUtf8String(ulong address)
public override string ReadUtf8String(ulong address, bool strict = false)
{
// Read characters until we find the null terminator
ulong end = address;
Expand All @@ -613,7 +616,7 @@ public override string ReadUtf8String(ulong address)
? stackalloc byte[length]
: new byte[length];
ReadBuffer(address, span);
return Encoding.UTF8.GetString(span);
return strict ? strictUTF8Encoding.GetString(span) : looseUTF8Encoding.GetString(span);
}

/// <summary>
Expand Down
60 changes: 60 additions & 0 deletions src/native/managed/cdac/tests/LoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Legacy;
using Moq;
Expand Down Expand Up @@ -84,6 +85,65 @@ public void GetFileName(MockTarget.Architecture arch)
}
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TryGetSimpleName(MockTarget.Architecture arch)
{
// Set up the target
TargetTestHelpers helpers = new(arch);
MockMemorySpace.Builder builder = new(helpers);
MockLoader loader = new(builder);

string expected = "TestModule";

// Add the modules
TargetPointer moduleAddr = loader.AddModule(simpleName: expected);
TargetPointer moduleAddrEmptyName = loader.AddModule();

var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, loader.Types);
target.SetContracts(Mock.Of<ContractRegistry>(
c => c.Loader == ((IContractFactory<ILoader>)new LoaderFactory()).CreateContract(target, 1)));

// Validate the expected module data
Contracts.ILoader contract = target.Contracts.Loader;
Assert.NotNull(contract);
{
Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr);
bool result = contract.TryGetSimpleName(handle, out string actual);
Assert.True(result);
Assert.Equal(expected, actual);
}
{
Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddrEmptyName);
bool result = contract.TryGetSimpleName(handle, out string actual);
Assert.False(result);
Assert.Equal(string.Empty, actual);
}
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TryGetSimpleName_InvalidUtf8(MockTarget.Architecture arch)
{
// Set up the target
TargetTestHelpers helpers = new(arch);
MockMemorySpace.Builder builder = new(helpers);
MockLoader loader = new(builder);

// 0xFF is not valid UTF-8
byte[] invalidUtf8 = [0xFF, 0xFE];
TargetPointer moduleAddr = loader.AddModule(simpleNameBytes: invalidUtf8);

var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, loader.Types);
target.SetContracts(Mock.Of<ContractRegistry>(
c => c.Loader == ((IContractFactory<ILoader>)new LoaderFactory()).CreateContract(target, 1)));

Contracts.ILoader contract = target.Contracts.Loader;
Assert.NotNull(contract);
Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr);
Assert.Throws<DecoderFallbackException>(() => contract.TryGetSimpleName(handle, out _));
}

private static readonly Dictionary<string, TargetPointer> MockHeapDictionary = new()
{
["LowFrequencyHeap"] = new(0x1000),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,30 @@ public Loader(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati
]);
}

internal TargetPointer AddModule(string? path = null, string? fileName = null)
internal TargetPointer AddModule(string? path = null, string? fileName = null, string? simpleName = null, byte[]? simpleNameBytes = null)
{
TargetTestHelpers helpers = _builder.TargetTestHelpers;
Target.TypeInfo typeInfo = Types[DataType.Module];
uint size = typeInfo.Size.Value;
MockMemorySpace.HeapFragment module = _allocator.Allocate(size, "Module");
_builder.AddHeapFragment(module);

byte[]? rawSimpleName = simpleName is not null ? Encoding.UTF8.GetBytes(simpleName) : simpleNameBytes;
if (rawSimpleName != null)
{
// Simple name data (UTF-8, null-terminated)
ulong simpleNameSize = (ulong)rawSimpleName.Length + 1;
MockMemorySpace.HeapFragment simpleNameFragment = _allocator.Allocate(simpleNameSize, "Module simple name");
rawSimpleName.AsSpan().CopyTo(simpleNameFragment.Data);
simpleNameFragment.Data[^1] = 0;
_builder.AddHeapFragment(simpleNameFragment);

// Pointer to simple name
helpers.WritePointer(
module.Data.AsSpan().Slice(typeInfo.Fields[nameof(Data.Module.SimpleName)].Offset, helpers.PointerSize),
simpleNameFragment.Address);
}

if (path != null)
{
// Path data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ internal record TypeFields
new(nameof(Data.Module.Flags), DataType.uint32),
new(nameof(Data.Module.LoaderAllocator), DataType.pointer),
new(nameof(Data.Module.DynamicMetadata), DataType.pointer),
new(nameof(Data.Module.SimpleName), DataType.pointer),
new(nameof(Data.Module.Path), DataType.pointer),
new(nameof(Data.Module.FileName), DataType.pointer),
new(nameof(Data.Module.ReadyToRunInfo), DataType.pointer),
Expand Down
20 changes: 19 additions & 1 deletion src/native/managed/cdac/tests/TestPlaceholderTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ internal class TestPlaceholderTarget : Target
internal delegate int ReadFromTargetDelegate(ulong address, Span<byte> buffer);

private readonly ReadFromTargetDelegate _dataReader;
private static readonly UTF8Encoding strictUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private static readonly UTF8Encoding looseUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);

public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary<DataType, Target.TypeInfo> types = null, (string Name, ulong Value)[] globals = null, (string Name, string Value)[] globalStrings = null)
{
Expand Down Expand Up @@ -159,7 +161,23 @@ public override void ReadBuffer(ulong address, Span<byte> buffer)
}
public override void WriteBuffer(ulong address, Span<byte> buffer) => throw new NotImplementedException();

public override string ReadUtf8String(ulong address) => throw new NotImplementedException();
public override string ReadUtf8String(ulong address, bool strict = false)
{
Comment thread
rcj1 marked this conversation as resolved.
// Read bytes until we find the null terminator
ulong end = address;
while (Read<byte>(end) != 0)
{
end += sizeof(byte);
}

int length = (int)(end - address);
if (length == 0)
return string.Empty;

Span<byte> span = new byte[length];
ReadBuffer(address, span);
return strict ? strictUTF8Encoding.GetString(span) : looseUTF8Encoding.GetString(span);
}
public override string ReadUtf16String(ulong address)
{
// Read characters until we find the null terminator
Expand Down
Loading