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
1 change: 1 addition & 0 deletions docs/design/datacontracts/EcmaMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This contract provides methods to get a view of the ECMA-335 metadata for a give

```csharp
TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle);
TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle);
System.Reflection.Metadata.MetadataReader? GetMetadata(ModuleHandle handle);
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public interface IEcmaMetadata : IContract
{
static string IContract.Name { get; } = nameof(EcmaMetadata);
TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) => throw new NotImplementedException();
TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) => throw new NotImplementedException();

MetadataReader? GetMetadata(ModuleHandle module) => throw new NotImplementedException();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ public static class CorDbgHResults
public const int CORDBG_E_UNSUPPORTED_DELEGATE = unchecked((int)0x80131c68);
public const int CORDBG_E_ENC_HANGING_FIELD = unchecked((int)0x80131342);
public const int CLDB_E_FILE_CORRUPT = unchecked((int)0x8013110e);
public const int CORDBG_E_MISSING_METADATA = unchecked((int)0x80131c35);
}
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ private uint GetMetadataGeneration(ModuleHandle handle)
return module.MetadataGeneration;
}

private TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle)
public TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle)
{
Data.Module module = target.ProcessedData.GetOrAdd<Data.Module>(handle.Address);
Data.DynamicMetadata dynamicMetadata = target.ProcessedData.GetOrAdd<Data.DynamicMetadata>(module.DynamicMetadata);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,48 @@ public int GetModulePath(ulong vmModule, nint pStrFilename, Interop.BOOL* pResul
}

public int GetMetadata(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetMetadata(vmModule, pTargetBuffer) : HResults.E_NOTIMPL;
{
int hr = HResults.S_OK;
try
{
*pTargetBuffer = default;
Contracts.ILoader loader = _target.Contracts.Loader;
Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule));
Contracts.ModuleFlags flags = loader.GetFlags(handle);
Contracts.IEcmaMetadata ecmaMetadata = _target.Contracts.EcmaMetadata;

// Dynamic modules keep an eagerly-serialized metadata buffer, while non-dynamic modules read metadata from the loaded PE image.
TargetSpan targetSpan = flags.HasFlag(Contracts.ModuleFlags.ReflectionEmit)
? ecmaMetadata.GetReadWriteSavedMetadataAddress(handle)
: ecmaMetadata.GetReadOnlyMetadataAddress(handle);

pTargetBuffer->pAddress = targetSpan.Address.Value;
pTargetBuffer->cbSize = checked((uint)targetSpan.Size);

if (pTargetBuffer->cbSize == 0)
{
throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_MISSING_METADATA)!;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacy is not null)
{
DacDbiTargetBuffer pTargetBufferLocal = default;
int hrLocal = _legacy.GetMetadata(vmModule, pTargetBuffer == null ? null : &pTargetBufferLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pTargetBuffer->pAddress == pTargetBufferLocal.pAddress, $"pAddress: cDAC: {pTargetBuffer->pAddress:x}, DAC: {pTargetBufferLocal.pAddress:x}");
Debug.Assert(pTargetBuffer->cbSize == pTargetBufferLocal.cbSize, $"cbSize: cDAC: {pTargetBuffer->cbSize}, DAC: {pTargetBufferLocal.cbSize}");
}
}
#endif
return hr;
}

public int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, SymbolFormat* pSymbolFormat)
{
Expand Down
99 changes: 99 additions & 0 deletions src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1415,4 +1415,103 @@ public void ConvertToNativeVarInfo_MapsAllFields()
Assert.Equal(5u, nvi.loc.vlsBaseReg);
Assert.Equal(-0x28, nvi.loc.vlsOffset);
}

private static DacDbiImpl CreateDacDbiForModule(
MockTarget.Architecture arch,
ulong vmModule,
Contracts.ModuleHandle handle,
ModuleFlags flags,
Mock<IEcmaMetadata> mockEcmaMetadata)
{
var mockLoader = new Mock<ILoader>();
mockLoader.Setup(l => l.GetModuleHandleFromModulePtr(new TargetPointer(vmModule))).Returns(handle);
mockLoader.Setup(l => l.GetFlags(handle)).Returns(flags);

var target = new TestPlaceholderTarget.Builder(arch)
.UseReader((_, _) => -1)
.AddMockContract(mockLoader)
.AddMockContract(mockEcmaMetadata)
.Build();
return new DacDbiImpl(target, legacyObj: null);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetMetadata_NonDynamicModule(MockTarget.Architecture arch)
{
ulong vmModule = 0x1000;
TargetPointer moduleAddr = new(0x1000);
TargetPointer metadataAddr = new(0xabc0);
uint metadataSize = 0x200;

Contracts.ModuleHandle handle = new(moduleAddr);
var mockEcmaMetadata = new Mock<IEcmaMetadata>();
mockEcmaMetadata.Setup(e => e.GetReadOnlyMetadataAddress(handle)).Returns(new TargetSpan(metadataAddr, metadataSize));

DacDbiImpl dacDbi = CreateDacDbiForModule(arch, vmModule, handle, (ModuleFlags)0, mockEcmaMetadata);

DacDbiTargetBuffer buffer;
int hr = dacDbi.GetMetadata(vmModule, &buffer);

Assert.Equal(System.HResults.S_OK, hr);
Assert.Equal(metadataAddr.Value, buffer.pAddress);
Assert.Equal(metadataSize, buffer.cbSize);
mockEcmaMetadata.Verify(e => e.GetReadWriteSavedMetadataAddress(It.IsAny<Contracts.ModuleHandle>()), Times.Never);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetMetadata_DynamicModule(MockTarget.Architecture arch)
{
ulong vmModule = 0x1000;
TargetPointer moduleAddr = new(0x1000);
TargetPointer metadataAddr = new(0xdef0);
uint metadataSize = 0x80;

Contracts.ModuleHandle handle = new(moduleAddr);
var mockEcmaMetadata = new Mock<IEcmaMetadata>();
mockEcmaMetadata.Setup(e => e.GetReadWriteSavedMetadataAddress(handle)).Returns(new TargetSpan(metadataAddr, metadataSize));

DacDbiImpl dacDbi = CreateDacDbiForModule(arch, vmModule, handle, ModuleFlags.ReflectionEmit, mockEcmaMetadata);

DacDbiTargetBuffer buffer;
int hr = dacDbi.GetMetadata(vmModule, &buffer);

Assert.Equal(System.HResults.S_OK, hr);
Assert.Equal(metadataAddr.Value, buffer.pAddress);
Assert.Equal(metadataSize, buffer.cbSize);
mockEcmaMetadata.Verify(e => e.GetReadOnlyMetadataAddress(It.IsAny<Contracts.ModuleHandle>()), Times.Never);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetMetadata_EmptyMetadata(MockTarget.Architecture arch)
{
ulong vmModule = 0x1000;
TargetPointer moduleAddr = new(0x1000);

Contracts.ModuleHandle handle = new(moduleAddr);
var mockEcmaMetadata = new Mock<IEcmaMetadata>();
mockEcmaMetadata.Setup(e => e.GetReadOnlyMetadataAddress(handle)).Returns(new TargetSpan(TargetPointer.Null, 0));

DacDbiImpl dacDbi = CreateDacDbiForModule(arch, vmModule, handle, (ModuleFlags)0, mockEcmaMetadata);

DacDbiTargetBuffer buffer;
int hr = dacDbi.GetMetadata(vmModule, &buffer);

Assert.Equal(CorDbgHResults.CORDBG_E_MISSING_METADATA, hr);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetMetadata_NullBuffer(MockTarget.Architecture arch)
{
ulong vmModule = 0x1000;
Contracts.ModuleHandle handle = new(new TargetPointer(vmModule));
var mockEcmaMetadata = new Mock<IEcmaMetadata>();
DacDbiImpl dacDbi = CreateDacDbiForModule(arch, vmModule, handle, (ModuleFlags)0, mockEcmaMetadata);

int hr = dacDbi.GetMetadata(vmModule, null);
Assert.Equal(System.HResults.E_POINTER, hr);
}
}