Skip to content
17 changes: 17 additions & 0 deletions TestProj/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

struct ClrDataAddress
{
public ulong Value;
public ClrDataAddress(ulong value) => Value = value;
public override string ToString() => $"0x{Value:x}";
}

class Program
{
static void Main()
{
ClrDataAddress addr = new ClrDataAddress(255);
Console.WriteLine($"cDAC: {addr:x}");
}
}
11 changes: 11 additions & 0 deletions TestProj/Program2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

interface ITest {
void Foo([In, Out, MarshalUsing(CountElementName = "len")] int[] arr, int len);
}

class Test : ITest {
void ITest.Foo([In, Out, MarshalUsing(CountElementName = "len")] int[] arr, int len) { }
}
10 changes: 10 additions & 0 deletions TestProj/TestProj.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net11.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks></PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,41 @@ int IXCLRDataMethodInstance.GetTypeInstance(DacComNullableByRef<IXCLRDataTypeIns
=> HResults.E_NOTIMPL;

int IXCLRDataMethodInstance.GetDefinition(DacComNullableByRef<IXCLRDataMethodDefinition> methodDefinition)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetDefinition(methodDefinition) : HResults.E_NOTIMPL;
{
int hr = HResults.S_OK;
int hrLocal = HResults.S_OK;
IXCLRDataMethodDefinition? legacyMethodDefinition = null;
bool canFallback = LegacyFallbackHelper.CanFallback();

try
{
if (canFallback && _legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataMethodDefinition> legacyMethodDefinitionOut = new(isNullRef: methodDefinition.IsNullRef);
hrLocal = _legacyImpl.GetDefinition(legacyMethodDefinitionOut);
legacyMethodDefinition = legacyMethodDefinitionOut.Interface;
}

IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
uint token = rts.GetMethodToken(_methodDesc);
TargetPointer methodTable = rts.GetMethodTable(_methodDesc);
TargetPointer module = rts.GetModule(rts.GetTypeHandle(methodTable));
methodDefinition.Interface = new ClrDataMethodDefinition(_target, module, token, legacyMethodDefinition);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (canFallback && _legacyImpl is not null)
{
Debug.ValidateHResult(hr, hrLocal);
}
#endif

return hr;
}

int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, DacComNullableByRef<IXCLRDataModule> mod)
{
Expand Down Expand Up @@ -221,7 +255,7 @@ int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint o
uint hits = 0;
for (int i = 0; i < map.Count; i++)
{
bool isEpilog = map[i].ILOffset == unchecked((uint)-3); // -3 is used to indicate an epilog
bool isEpilog = map[i].ILOffset == (uint)CLRDataILOffsetMarker.CLRDATA_IL_OFFSET_EPILOG;
bool lastValue = i == map.Count - 1;
uint nativeEndOffset = lastValue ? 0 : map[i + 1].NativeOffset;
if (codeOffset >= map[i].NativeOffset && (((isEpilog || lastValue) && nativeEndOffset == 0) || codeOffset < nativeEndOffset))
Expand Down Expand Up @@ -289,8 +323,100 @@ int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint o
return hr;
}

int IXCLRDataMethodInstance.GetAddressRangesByILOffset(uint ilOffset, uint rangesLen, uint* rangesNeeded, void* addressRanges)
=> HResults.E_NOTIMPL;
int IXCLRDataMethodInstance.GetAddressRangesByILOffset(
uint ilOffset,
uint rangesLen,
uint* rangesNeeded,
[In, Out, MarshalUsing(CountElementName = nameof(rangesLen))] ClrDataAddressRange[]? addressRanges)
{
int hr = HResults.S_OK;
bool canFallback = LegacyFallbackHelper.CanFallback();

try
{
TargetCodePointer nativeCode = _target.Contracts.RuntimeTypeSystem.GetNativeCode(_methodDesc);
TargetCodePointer pCode = _target.Contracts.PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(nativeCode);
TargetPointer codeStart = pCode.ToAddress(_target);

if (!_target.Contracts.DebugInfo.HasDebugInfo(pCode))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;

IEnumerable<OffsetMapping> mapEnumerable = _target.Contracts.DebugInfo.GetMethodNativeMap(
pCode,
preferUninstrumented: false,
out uint _);

List<OffsetMapping> map = [.. mapEnumerable];
uint hits = 0;

for (int i = 0; i < map.Count; i++)
{
OffsetMapping entry = map[i];
if (entry.ILOffset != ilOffset)
continue;

if (addressRanges is not null && hits < addressRanges.Length)
{
uint nativeEndOffset = i == map.Count - 1 ? 0 : map[i + 1].NativeOffset;
addressRanges[hits].startAddress = new TargetPointer(codeStart + entry.NativeOffset).ToClrDataAddress(_target);
addressRanges[hits].endAddress = entry.ILOffset == (uint)CLRDataILOffsetMarker.CLRDATA_IL_OFFSET_EPILOG && nativeEndOffset == 0
? default
: new TargetPointer(codeStart + nativeEndOffset).ToClrDataAddress(_target);
}

hits++;
}

if (rangesNeeded is not null)
{
*rangesNeeded = hits;
}

if (hits == 0)
throw new InvalidCastException();
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (canFallback && _legacyImpl is not null)
{
bool validateRangesNeeded = rangesNeeded is not null;
uint rangesNeededLocal = 0;
ClrDataAddressRange[]? addressRangesLocal = rangesLen > 0 ? new ClrDataAddressRange[rangesLen] : null;

int hrLocal = _legacyImpl.GetAddressRangesByILOffset(
ilOffset,
rangesLen,
validateRangesNeeded ? &rangesNeededLocal : null,
addressRangesLocal);

Debug.ValidateHResult(hr, hrLocal);

if (hr == HResults.S_OK && hrLocal == HResults.S_OK)
{
if (validateRangesNeeded)
{
Debug.Assert(rangesNeededLocal == *rangesNeeded, $"cDAC: {*rangesNeeded:x}, DAC: {rangesNeededLocal:x}");
}

if (addressRangesLocal is not null && addressRanges is not null)
{
int countToCheck = Math.Min(addressRangesLocal.Length, (int)rangesNeededLocal);
for (int i = 0; i < countToCheck; i++)
{
Debug.Assert(addressRangesLocal[i].startAddress == addressRanges[i].startAddress, $"StartAddress - cDAC: {addressRanges[i].startAddress:x}, DAC: {addressRangesLocal[i].startAddress:x}");
Debug.Assert(addressRangesLocal[i].endAddress == addressRanges[i].endAddress, $"EndAddress - cDAC: {addressRanges[i].endAddress:x}, DAC: {addressRangesLocal[i].endAddress:x}");
}
}
}
}
#endif

return hr;
}

int IXCLRDataMethodInstance.GetILAddressMap(uint mapLen, uint* mapNeeded, [In, Out, MarshalUsing(CountElementName = "mapLen")] ClrDataILAddressMap[]? maps)
{
Expand Down Expand Up @@ -345,7 +471,8 @@ int IXCLRDataMethodInstance.GetILAddressMap(uint mapLen, uint* mapNeeded, [In, O
*mapNeeded = (uint)map.Count;
}

hr = map.Count > 0 ? HResults.S_OK : HResults.COR_E_INVALIDCAST /*E_NOINTERFACE*/;
if (map.Count == 0)
throw new InvalidCastException();
}
catch (System.Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,13 @@ public enum ClrDataSourceType : uint
CLRDATA_SOURCE_TYPE_INVALID = 0,
}

public enum CLRDataILOffsetMarker : uint
{
CLRDATA_IL_OFFSET_NO_MAPPING = unchecked((uint)-1),
CLRDATA_IL_OFFSET_PROLOG = unchecked((uint)-2),
CLRDATA_IL_OFFSET_EPILOG = unchecked((uint)-3),
}

// CLRDATA_IL_ADDRESS_MAP
public struct ClrDataILAddressMap
{
Expand Down Expand Up @@ -611,7 +618,7 @@ int GetAddressRangesByILOffset(
uint ilOffset,
uint rangesLen,
uint* rangesNeeded,
/*CLRDATA_ADDRESS_RANGE* */ void* addressRanges);
[In, Out, MarshalUsing(CountElementName = nameof(rangesLen))] ClrDataAddressRange[]? addressRanges);

[PreserveSig]
int GetILAddressMap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ private static unsafe int CreateDacDbiInterface(IntPtr handle, IntPtr legacyImpl
if (legacyObj is not Legacy.IDacDbiInterface)
{
*obj = IntPtr.Zero;
return HResults.COR_E_INVALIDCAST; // E_NOINTERFACE
throw new InvalidCastException(); // E_NOINTERFACE
}
}

Expand Down
99 changes: 99 additions & 0 deletions src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,84 @@ public unsafe void MethodInstance_EnumExtents_ReturnsSingleRangeContainingInstru
Assert.Fail("MethodC not found on the crashing thread's stack");
}

[ConditionalTheory]
[MemberData(nameof(TestConfigurations))]
[SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")]
public unsafe void MethodInstance_GetDefinition_ReturnsMatchingDefinition(TestConfiguration config)
{
InitializeDumpTest(config);
IXCLRDataMethodInstance methodInstance = GetMethodInstanceFromStack("MethodC");

DacComNullableByRef<IXCLRDataMethodDefinition> definitionOut = new(isNullRef: false);
int hr = methodInstance.GetDefinition(definitionOut);
AssertHResult(HResults.S_OK, hr);

IXCLRDataMethodDefinition methodDefinition = Assert.IsAssignableFrom<IXCLRDataMethodDefinition>(definitionOut.Interface);
DacComNullableByRef<IXCLRDataModule> nullModule = new(isNullRef: true);
uint instanceToken;
hr = methodInstance.GetTokenAndScope(&instanceToken, nullModule);
AssertHResult(HResults.S_OK, hr);

uint definitionToken;
hr = methodDefinition.GetTokenAndScope(&definitionToken, nullModule);
AssertHResult(HResults.S_OK, hr);
Assert.Equal(instanceToken, definitionToken);

uint nameLength;
hr = methodDefinition.GetName(0, 0, &nameLength, null);
AssertHResult(HResults.S_OK, hr);

char[] name = new char[nameLength];
fixed (char* namePtr = name)
{
hr = methodDefinition.GetName(0, nameLength, &nameLength, namePtr);
}
AssertHResult(HResults.S_OK, hr);
Assert.Contains("MethodC", new string(name, 0, (int)nameLength - 1));
}

[ConditionalTheory]
[MemberData(nameof(TestConfigurations))]
[SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")]
public unsafe void MethodInstance_GetAddressRangesByILOffset_ReturnsMatchingMapEntries(TestConfiguration config)
{
InitializeDumpTest(config);
IXCLRDataMethodInstance methodInstance = GetMethodInstanceFromStack("MethodC");

uint mapNeeded;
int hr = methodInstance.GetILAddressMap(0, &mapNeeded, null);
AssertHResult(HResults.S_OK, hr);
Assert.NotEqual(0u, mapNeeded);

ClrDataILAddressMap[] maps = new ClrDataILAddressMap[mapNeeded];
hr = methodInstance.GetILAddressMap(mapNeeded, &mapNeeded, maps);
AssertHResult(HResults.S_OK, hr);

ClrDataILAddressMap selectedMap = maps.First(map => unchecked((int)map.ilOffset) >= 0);
ClrDataILAddressMap[] expectedMaps = maps.Where(map => map.ilOffset == selectedMap.ilOffset).ToArray();

uint rangesNeeded;
hr = methodInstance.GetAddressRangesByILOffset(selectedMap.ilOffset, 0, &rangesNeeded, null);
AssertHResult(HResults.S_OK, hr);
Assert.Equal((uint)expectedMaps.Length, rangesNeeded);

ClrDataAddressRange[] ranges = new ClrDataAddressRange[rangesNeeded];
hr = methodInstance.GetAddressRangesByILOffset(selectedMap.ilOffset, rangesNeeded, &rangesNeeded, ranges);
AssertHResult(HResults.S_OK, hr);
Assert.Equal((uint)expectedMaps.Length, rangesNeeded);

for (int i = 0; i < expectedMaps.Length; i++)
{
Assert.Equal(expectedMaps[i].startAddress, ranges[i].startAddress);
Assert.Equal(expectedMaps[i].endAddress, ranges[i].endAddress);
}

rangesNeeded = uint.MaxValue;
hr = methodInstance.GetAddressRangesByILOffset(int.MaxValue, 0, &rangesNeeded, null);
AssertHResult(HResults.COR_E_INVALIDCAST, hr);
Assert.Equal(0u, rangesNeeded);
}

// ========== PInvokeStub debuggee ==========

[ConditionalTheory]
Expand Down Expand Up @@ -466,4 +544,25 @@ public void GetContext_ReturnsNonEmptyContext(TestConfiguration config)
ctx.FillFromBuffer(context);
Assert.NotEqual(TargetCodePointer.Null, ctx.InstructionPointer);
}

private IXCLRDataMethodInstance GetMethodInstanceFromStack(string methodName)
{
IStackWalk stackWalk = Target.Contracts.StackWalk;
IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem;
ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target);

foreach (IStackDataFrameHandle frame in DumpTestStackWalker.LegacyVisibleFrames(stackWalk, crashingThread))
{
TargetPointer methodDescPtr = stackWalk.GetMethodDescPtr(frame);
if (methodDescPtr == TargetPointer.Null)
continue;

MethodDescHandle methodDesc = rts.GetMethodDescHandle(methodDescPtr);
if (DumpTestHelpers.GetMethodName(Target, methodDesc) == methodName)
return new ClrDataMethodInstance(Target, methodDesc, TargetPointer.Null, legacyImpl: null);
}

Assert.Fail($"{methodName} not found on the crashing thread's stack");
return null!;
}
}
24 changes: 24 additions & 0 deletions test_marshal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

partial class Program
{
[LibraryImport("kernel32.dll", EntryPoint = "SetLastError")]
public static partial void Dummy(
uint rangesLen,
[In, Out, MarshalUsing(CountElementName = nameof(rangesLen))] int[]? addressRanges);

static void Main()
{
try
{
Dummy(5, null);
Console.WriteLine("Success");
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.GetType().Name + " - " + ex.Message);
}
}
}