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
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,54 @@ 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;

public int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, int* pSymbolFormat)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetSymbolsBuffer(vmModule, pTargetBuffer, pSymbolFormat) : HResults.E_NOTIMPL;
public int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, SymbolFormat* pSymbolFormat)
{
int hr = HResults.S_OK;
try
{
if (pTargetBuffer == null)
throw new ArgumentNullException(nameof(pTargetBuffer));

if (pSymbolFormat == null)
throw new ArgumentNullException(nameof(pSymbolFormat));

*pTargetBuffer = default;
*pSymbolFormat = SymbolFormat.None;

if (vmModule == 0)
throw new ArgumentException("Module pointer must be non-zero.", nameof(vmModule));

Contracts.ILoader loader = _target.Contracts.Loader;
Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule));

if (loader.TryGetSymbolStream(handle, out TargetPointer buffer, out uint size) && size != 0)
{
pTargetBuffer->pAddress = buffer.Value;
pTargetBuffer->cbSize = size;
*pSymbolFormat = SymbolFormat.Pdb;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacy is not null)
{
DacDbiTargetBuffer bufferLocal;
SymbolFormat formatLocal;
int hrLocal = _legacy.GetSymbolsBuffer(vmModule, &bufferLocal, &formatLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pTargetBuffer->pAddress == bufferLocal.pAddress, $"pAddress: cDAC: {pTargetBuffer->pAddress:x}, DAC: {bufferLocal.pAddress:x}");
Debug.Assert(pTargetBuffer->cbSize == bufferLocal.cbSize, $"cbSize: cDAC: {pTargetBuffer->cbSize}, DAC: {bufferLocal.cbSize}");
Debug.Assert(*pSymbolFormat == formatLocal, $"pSymbolFormat: cDAC: {*pSymbolFormat}, DAC: {formatLocal}");
}
}
#endif
return hr;
}

public int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ public enum CorDebugUserState
USER_THREADPOOL = 0x100,
}

public enum SymbolFormat
{
None = 0,
Pdb = 1,
}

public enum CorDebugGenerationTypes
{
CorDebug_Gen0 = 0,
Expand Down Expand Up @@ -231,7 +237,7 @@ public unsafe partial interface IDacDbiInterface
int GetMetadata(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer);

[PreserveSig]
int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, int* pSymbolFormat);
int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, SymbolFormat* pSymbolFormat);

[PreserveSig]
int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData);
Expand Down
65 changes: 65 additions & 0 deletions src/native/managed/cdac/tests/DacDbiImplTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,69 @@ public void EnumerateAssembliesInAppDomain_NoAssemblies(MockTarget.Architecture
Assert.Equal(System.HResults.S_OK, hr);
Assert.Empty(assemblies);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetSymbolsBuffer_NoStream(MockTarget.Architecture arch)
{
TargetPointer moduleAddr = TargetPointer.Null;
var (dacDbi, _) = CreateDacDbiWithLoader(arch, (loader, _) =>
{
moduleAddr = new TargetPointer(loader.AddModule().Address);
});

DacDbiTargetBuffer targetBuffer;
SymbolFormat symbolFormat;
int hr = dacDbi.GetSymbolsBuffer(moduleAddr, &targetBuffer, &symbolFormat);
Comment thread
rcj1 marked this conversation as resolved.
Assert.Equal(System.HResults.S_OK, hr);
Assert.Equal(0UL, targetBuffer.pAddress);
Assert.Equal(0u, targetBuffer.cbSize);
Assert.Equal(SymbolFormat.None, symbolFormat);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetSymbolsBuffer_WithSymbols(MockTarget.Architecture arch)
{
byte[] symbolBytes = [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE];
TargetPointer moduleAddr = TargetPointer.Null;
ulong expectedBufferAddr = 0;
var (dacDbi, _) = CreateDacDbiWithLoader(arch, (loader, _) =>
{
MockLoaderModule module = loader.AddModule();
MockCGrowableSymbolStream stream = loader.AddInMemorySymbolStream(module, symbolBytes);
moduleAddr = new TargetPointer(module.Address);
expectedBufferAddr = stream.Buffer;
});

DacDbiTargetBuffer targetBuffer;
SymbolFormat symbolFormat;
int hr = dacDbi.GetSymbolsBuffer(moduleAddr, &targetBuffer, &symbolFormat);
Comment thread
rcj1 marked this conversation as resolved.
Assert.Equal(System.HResults.S_OK, hr);
Assert.Equal(expectedBufferAddr, targetBuffer.pAddress);
Assert.Equal((uint)symbolBytes.Length, targetBuffer.cbSize);
Assert.Equal(SymbolFormat.Pdb, symbolFormat);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetSymbolsBuffer_EmptyStream(MockTarget.Architecture arch)
{
// Stream object exists but contains no bytes - treated like no symbols.
TargetPointer moduleAddr = TargetPointer.Null;
var (dacDbi, _) = CreateDacDbiWithLoader(arch, (loader, _) =>
{
MockLoaderModule module = loader.AddModule();
loader.AddInMemorySymbolStream(module, symbols: null);
moduleAddr = new TargetPointer(module.Address);
});

DacDbiTargetBuffer targetBuffer;
SymbolFormat symbolFormat;
int hr = dacDbi.GetSymbolsBuffer(moduleAddr, &targetBuffer, &symbolFormat);
Comment thread
rcj1 marked this conversation as resolved.
Assert.Equal(System.HResults.S_OK, hr);
Assert.Equal(0UL, targetBuffer.pAddress);
Assert.Equal(0u, targetBuffer.cbSize);
Assert.Equal(SymbolFormat.None, symbolFormat);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Legacy;
using Xunit;

namespace Microsoft.Diagnostics.DataContractReader.DumpTests;

/// <summary>
/// Dump-based integration tests for DacDbiImpl methods that need a debuggee with
/// extra loader features. Uses the MultiModule debuggee (heap dump), which
/// additionally loads an assembly from in-memory bytes with an in-memory PDB
/// so that the <c>GetSymbolsBuffer</c> / <c>TryGetSymbolStream</c> code path can
/// be exercised against an actual module with in-memory symbols.
/// </summary>
public class DacDbiMultiModuleDumpTests : DumpTestBase
{
protected override string DebuggeeName => "MultiModule";

private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null);

private IEnumerable<ModuleHandle> GetAllModules()
{
ILoader loader = Target.Contracts.Loader;
TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain);
ulong appDomain = Target.ReadPointer(appDomainPtr);
return loader.GetModuleHandles(new TargetPointer(appDomain),
AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution);
}

[ConditionalTheory]
[MemberData(nameof(TestConfigurations))]
public unsafe void GetSymbolsBuffer_FindsInMemorySymbols(TestConfiguration config)
{
InitializeDumpTest(config);
DacDbiImpl dbi = CreateDacDbi();
ILoader loader = Target.Contracts.Loader;

bool foundInMemorySymbols = false;
foreach (ModuleHandle module in GetAllModules())
{
TargetPointer moduleAddr = loader.GetModule(module);

DacDbiTargetBuffer targetBuffer;
SymbolFormat symbolFormat;
int hr = dbi.GetSymbolsBuffer(moduleAddr.Value, &targetBuffer, &symbolFormat);
Comment thread
rcj1 marked this conversation as resolved.
Assert.Equal(System.HResults.S_OK, hr);

if (symbolFormat == SymbolFormat.Pdb)
{
// When PDB symbols are reported, the buffer must be non-empty.
Assert.NotEqual(0UL, targetBuffer.pAddress);
Assert.NotEqual(0u, targetBuffer.cbSize);
foundInMemorySymbols = true;
}
else
{
Assert.Equal(0UL, targetBuffer.pAddress);
Assert.Equal(0u, targetBuffer.cbSize);
}
}

Assert.True(foundInMemorySymbols,
"Expected at least one module in the MultiModule debuggee dump to have an in-memory PDB symbol stream.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;

/// <summary>
/// Debuggee for cDAC dump tests — exercises the Loader contract.
/// Loads assemblies from multiple AssemblyLoadContexts then crashes.
/// Also loads an assembly from in-memory bytes with an in-memory PDB
/// stream so the Loader contract's <c>TryGetSymbolStream</c> /
/// DacDbi <c>GetSymbolsBuffer</c> path can be exercised against a real module.
/// Also includes metadata features for MetaDataImport dump tests:
/// - Non-const fields (for ELEMENT_TYPE_VOID default value testing)
/// - String literals (user strings in #US heap)
Expand Down Expand Up @@ -40,6 +44,16 @@ private static void Main()
// Also load System.Xml to have another module present
var xmlAssembly = Assembly.Load("System.Private.Xml");

// Load this debuggee's own assembly from in-memory bytes alongside its
// portable PDB. The runtime stores the PDB bytes on the resulting
// Module as a CGrowableSymbolStream, which is what the DacDbi
// GetSymbolsBuffer / Loader contract TryGetSymbolStream APIs read.
string asmPath = typeof(Program).Assembly.Location;
string pdbPath = Path.ChangeExtension(asmPath, ".pdb");
byte[] inMemoryAssemblyBytes = File.ReadAllBytes(asmPath);
byte[] inMemorySymbolBytes = File.ReadAllBytes(pdbPath);
Assembly inMemoryAssembly = Assembly.Load(inMemoryAssemblyBytes, inMemorySymbolBytes);

// Use the non-const field so it doesn't get optimized away
s_nonConstField = contexts.Length;

Expand All @@ -51,6 +65,9 @@ private static void Main()
GC.KeepAlive(contexts);
GC.KeepAlive(loadedAssemblies);
GC.KeepAlive(xmlAssembly);
GC.KeepAlive(inMemoryAssembly);
GC.KeepAlive(inMemoryAssemblyBytes);
GC.KeepAlive(inMemorySymbolBytes);
GC.KeepAlive(userString);
GC.KeepAlive(s_nonConstField);

Expand Down
62 changes: 62 additions & 0 deletions src/native/managed/cdac/tests/LoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public unsafe class LoaderTests
[DataType.Module] = TargetTestHelpers.CreateTypeInfo(loader.ModuleLayout),
[DataType.Assembly] = TargetTestHelpers.CreateTypeInfo(loader.AssemblyLayout),
[DataType.EEConfig] = TargetTestHelpers.CreateTypeInfo(loader.EEConfigLayout),
[DataType.CGrowableSymbolStream] = TargetTestHelpers.CreateTypeInfo(loader.CGrowableSymbolStreamLayout),
};

private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action<MockLoaderBuilder> configure)
Expand Down Expand Up @@ -1021,4 +1022,65 @@ public void GetCompilerFlags(uint rawFlags, Interop.BOOL expectedAllowJITOpts, I
Assert.Equal(expectedAllowJITOpts, allowJITOpts);
Assert.Equal(expectedEnableEnC, enableEnC);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TryGetSymbolStream_NoStream(MockTarget.Architecture arch)
{
TargetPointer moduleAddr = TargetPointer.Null;
ILoader contract = CreateLoaderContract(arch, loader =>
{
// Module with no GrowableSymbolStream (left as null pointer).
moduleAddr = loader.AddModule().Address;
});

Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr);
bool result = contract.TryGetSymbolStream(handle, out TargetPointer buffer, out uint size);
Assert.False(result);
Assert.Equal(TargetPointer.Null, buffer);
Assert.Equal(0u, size);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TryGetSymbolStream_WithSymbols(MockTarget.Architecture arch)
{
byte[] symbolBytes = [1, 2, 3, 4, 5, 6, 7, 8];
TargetPointer moduleAddr = TargetPointer.Null;
TargetPointer expectedBuffer = TargetPointer.Null;
ILoader contract = CreateLoaderContract(arch, loader =>
{
MockLoaderModule module = loader.AddModule();
MockCGrowableSymbolStream stream = loader.AddInMemorySymbolStream(module, symbolBytes);
moduleAddr = module.Address;
expectedBuffer = new TargetPointer(stream.Buffer);
});

Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr);
bool result = contract.TryGetSymbolStream(handle, out TargetPointer buffer, out uint size);
Assert.True(result);
Assert.Equal(expectedBuffer, buffer);
Assert.Equal((uint)symbolBytes.Length, size);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void TryGetSymbolStream_EmptyStream(MockTarget.Architecture arch)
{
TargetPointer moduleAddr = TargetPointer.Null;
ILoader contract = CreateLoaderContract(arch, loader =>
{
MockLoaderModule module = loader.AddModule();
// Stream object is present but has no bytes.
loader.AddInMemorySymbolStream(module, symbols: null);
moduleAddr = module.Address;
});

Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr);
bool result = contract.TryGetSymbolStream(handle, out TargetPointer buffer, out uint size);
// The stream pointer is non-null so the API returns true even though the buffer is empty.
Assert.True(result);
Assert.Equal(TargetPointer.Null, buffer);
Assert.Equal(0u, size);
}
}
Loading
Loading