Skip to content
Open
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 @@ -2971,7 +2971,121 @@ int ISOSDacInterface.GetRCWData(ClrDataAddress addr, void* data)
int ISOSDacInterface.GetRCWInterfaces(ClrDataAddress rcw, uint count, void* interfaces, uint* pNeeded)
=> _legacyImpl is not null ? _legacyImpl.GetRCWInterfaces(rcw, count, interfaces, pNeeded) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetRegisterName(int regName, uint count, char* buffer, uint* pNeeded)
=> _legacyImpl is not null ? _legacyImpl.GetRegisterName(regName, count, buffer, pNeeded) : HResults.E_NOTIMPL;
{
int hr = HResults.S_OK;
try
{
if (buffer is null && pNeeded is null)
throw new NullReferenceException();

string[] regs = _target.Contracts.RuntimeInfo.GetTargetArchitecture() switch
{
RuntimeInfoArchitecture.X64 => s_amd64Registers,
RuntimeInfoArchitecture.Arm => s_armRegisters,
RuntimeInfoArchitecture.Arm64 => s_arm64Registers,
RuntimeInfoArchitecture.X86 => s_x86Registers,
RuntimeInfoArchitecture.LoongArch64 => s_loongArch64Registers,
RuntimeInfoArchitecture.RiscV64 => s_riscV64Registers,
_ => throw new InvalidOperationException(),
};

// Caller frame registers are encoded as "-(reg+1)".
bool callerFrame = regName < 0;
int regIndex = callerFrame ? -regName - 1 : regName;

if ((uint)regIndex >= (uint)regs.Length)
return unchecked((int)0x8000FFFF); // E_UNEXPECTED

string name = callerFrame ? $"caller.{regs[regIndex]}" : regs[regIndex];

uint needed = (uint)(name.Length + 1);
if (pNeeded is not null)
*pNeeded = needed;

if (buffer is not null)
{
OutputBufferHelpers.CopyStringToBuffer(buffer, count, neededBufferSize: null, name);

if (count < needed)
hr = HResults.S_FALSE;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (_legacyImpl is not null)
{
char[] bufferLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = bufferLocal)
{
hrLocal = _legacyImpl.GetRegisterName(regName, count, ptr, &neededLocal);
}
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(pNeeded is null || *pNeeded == neededLocal);
Debug.Assert(buffer is null || new ReadOnlySpan<char>(bufferLocal, 0, (int)Math.Min(count, neededLocal)).SequenceEqual(new ReadOnlySpan<char>(buffer, (int)Math.Min(count, neededLocal))));
}
}
#endif

return hr;
}

private static readonly string[] s_amd64Registers =
[
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
];

private static readonly string[] s_armRegisters =
[
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11", "r12", "sp", "lr",
];

private static readonly string[] s_arm64Registers =
[
"X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
"X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15", "X16", "X17",
"X18", "X19", "X20", "X21", "X22", "X23", "X24", "X25", "X26", "X27",
"X28", "Fp", "Lr", "Sp",
];

private static readonly string[] s_x86Registers =
[
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi",
];

private static readonly string[] s_loongArch64Registers =
[
"R0", "RA", "TP", "SP",
"A0", "A1", "A2", "A3",
"A4", "A5", "A6", "A7",
"T0", "T1", "T2", "T3",
"T4", "T5", "T6", "T7",
"T8", "R21", "FP", "S0",
"S1", "S2", "S3", "S4",
"S5", "S6", "S7", "S8",
];

private static readonly string[] s_riscV64Registers =
[
"R0", "RA", "SP", "GP",
"TP", "T0", "T1", "T2",
"FP", "S1", "A0", "A1",
"A2", "A3", "A4", "A5",
"A6", "A7", "S2", "S3",
"S4", "S5", "S6", "S7",
"S8", "S9", "S10", "S11",
"T3", "T4", "T5", "T6",
];

int ISOSDacInterface.GetStackLimits(ClrDataAddress threadPtr, ClrDataAddress* lower, ClrDataAddress* upper, ClrDataAddress* fp)
=> _legacyImpl is not null ? _legacyImpl.GetStackLimits(threadPtr, lower, upper, fp) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetStackReferences(int osThreadID, void** ppEnum)
Expand Down
205 changes: 205 additions & 0 deletions src/native/managed/cdac/tests/GetRegisterNameTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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

namespace Microsoft.Diagnostics.DataContractReader.Tests;

public unsafe class GetRegisterNameTests
{
private static SOSDacImpl CreateSosDacImpl(
MockTarget.Architecture arch,
RuntimeInfoArchitecture targetArch)
{
MockMemorySpace.Builder builder = new MockMemorySpace.Builder(new TargetTestHelpers(arch));
TestPlaceholderTarget target = new TestPlaceholderTarget(
arch,
builder.GetMemoryContext().ReadFromTarget,
[],
[],
[(Constants.Globals.Architecture, targetArch.ToString().ToLowerInvariant())]);

IContractFactory<IRuntimeInfo> runtimeInfoFactory = new RuntimeInfoFactory();
ContractRegistry reg = Mock.Of<ContractRegistry>(
c => c.RuntimeInfo == runtimeInfoFactory.CreateContract(target, 1));
target.SetContracts(reg);

return new SOSDacImpl(target, legacyObj: null);
}

public static IEnumerable<object[]> BasicRegisterNameData()
{
// AMD64 registers
yield return [RuntimeInfoArchitecture.X64, 0, "rax"];
yield return [RuntimeInfoArchitecture.X64, 1, "rcx"];
yield return [RuntimeInfoArchitecture.X64, 5, "rbp"];
yield return [RuntimeInfoArchitecture.X64, 15, "r15"];

// x86 registers
yield return [RuntimeInfoArchitecture.X86, 0, "eax"];
yield return [RuntimeInfoArchitecture.X86, 7, "edi"];

// ARM registers
yield return [RuntimeInfoArchitecture.Arm, 0, "r0"];
yield return [RuntimeInfoArchitecture.Arm, 13, "sp"];
yield return [RuntimeInfoArchitecture.Arm, 14, "lr"];

// ARM64 registers
yield return [RuntimeInfoArchitecture.Arm64, 0, "X0"];
yield return [RuntimeInfoArchitecture.Arm64, 29, "Fp"];
yield return [RuntimeInfoArchitecture.Arm64, 30, "Lr"];
yield return [RuntimeInfoArchitecture.Arm64, 31, "Sp"];

// LoongArch64 registers
yield return [RuntimeInfoArchitecture.LoongArch64, 0, "R0"];
yield return [RuntimeInfoArchitecture.LoongArch64, 1, "RA"];
yield return [RuntimeInfoArchitecture.LoongArch64, 3, "SP"];

// RiscV64 registers
yield return [RuntimeInfoArchitecture.RiscV64, 0, "R0"];
yield return [RuntimeInfoArchitecture.RiscV64, 1, "RA"];
yield return [RuntimeInfoArchitecture.RiscV64, 2, "SP"];
}

[Theory]
[MemberData(nameof(BasicRegisterNameData))]
public void GetRegisterName_ReturnsCorrectName(
RuntimeInfoArchitecture targetArch,
int regNum,
string expectedName)
{
SOSDacImpl impl = CreateSosDacImpl(new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, targetArch);
ISOSDacInterface sos = impl;

char[] buffer = new char[256];
uint needed;
int hr;
fixed (char* pBuffer = buffer)
{
hr = sos.GetRegisterName(regNum, (uint)buffer.Length, pBuffer, &needed);
}

Assert.Equal(HResults.S_OK, hr);
Assert.Equal((uint)(expectedName.Length + 1), needed);
Assert.Equal(expectedName, new string(buffer, 0, (int)needed - 1));
}

[Theory]
[InlineData(RuntimeInfoArchitecture.X64, 0, "rax")]
[InlineData(RuntimeInfoArchitecture.X86, 3, "ebx")]
[InlineData(RuntimeInfoArchitecture.Arm64, 0, "X0")]
public void GetRegisterName_CallerFrame_PrependsCaller(
RuntimeInfoArchitecture targetArch,
int regNum,
string expectedRegName)
{
SOSDacImpl impl = CreateSosDacImpl(new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, targetArch);
ISOSDacInterface sos = impl;

// Caller frame registers are encoded as "-(reg+1)"
int callerRegNum = -(regNum + 1);
string expectedName = $"caller.{expectedRegName}";

char[] buffer = new char[256];
uint needed;
int hr;
fixed (char* pBuffer = buffer)
{
hr = sos.GetRegisterName(callerRegNum, (uint)buffer.Length, pBuffer, &needed);
}

Assert.Equal(HResults.S_OK, hr);
Assert.Equal((uint)(expectedName.Length + 1), needed);
Assert.Equal(expectedName, new string(buffer, 0, (int)needed - 1));
}

[Fact]
public void GetRegisterName_NullBufferAndNullNeeded_ReturnsEPointer()
{
SOSDacImpl impl = CreateSosDacImpl(new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, RuntimeInfoArchitecture.X64);
ISOSDacInterface sos = impl;

int hr = sos.GetRegisterName(0, 0, null, null);

Assert.Equal(HResults.E_POINTER, hr);
}

[Fact]
public void GetRegisterName_NullBuffer_SetsNeeded()
{
SOSDacImpl impl = CreateSosDacImpl(new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, RuntimeInfoArchitecture.X64);
ISOSDacInterface sos = impl;

uint needed;
int hr = sos.GetRegisterName(0, 0, null, &needed);

Assert.Equal(HResults.S_OK, hr);
Assert.Equal((uint)("rax".Length + 1), needed);
}

[Theory]
[InlineData(RuntimeInfoArchitecture.X64, 16)]
[InlineData(RuntimeInfoArchitecture.X86, 8)]
[InlineData(RuntimeInfoArchitecture.Arm, 15)]
[InlineData(RuntimeInfoArchitecture.Arm64, 32)]
[InlineData(RuntimeInfoArchitecture.LoongArch64, 32)]
[InlineData(RuntimeInfoArchitecture.RiscV64, 32)]
public void GetRegisterName_OutOfRange_ReturnsEUnexpected(
RuntimeInfoArchitecture targetArch,
int regNum)
{
SOSDacImpl impl = CreateSosDacImpl(new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, targetArch);
ISOSDacInterface sos = impl;

uint needed;
int hr;
char[] buffer = new char[256];
fixed (char* pBuffer = buffer)
{
hr = sos.GetRegisterName(regNum, (uint)buffer.Length, pBuffer, &needed);
}

Assert.Equal(unchecked((int)0x8000FFFF), hr); // E_UNEXPECTED
}

[Fact]
public void GetRegisterName_SmallBuffer_ReturnsSFalse()
{
SOSDacImpl impl = CreateSosDacImpl(new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, RuntimeInfoArchitecture.X64);
ISOSDacInterface sos = impl;

// "rax" needs 4 chars (3 + null), provide only 2
char[] buffer = new char[2];
uint needed;
int hr;
fixed (char* pBuffer = buffer)
{
hr = sos.GetRegisterName(0, (uint)buffer.Length, pBuffer, &needed);
}

Assert.Equal(HResults.S_FALSE, hr);
Assert.Equal((uint)("rax".Length + 1), needed);
}

[Fact]
public void GetRegisterName_UnsupportedArchitecture_ReturnsError()
{
SOSDacImpl impl = CreateSosDacImpl(new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, RuntimeInfoArchitecture.Wasm);
ISOSDacInterface sos = impl;

uint needed;
char[] buffer = new char[256];
int hr;
fixed (char* pBuffer = buffer)
{
hr = sos.GetRegisterName(0, (uint)buffer.Length, pBuffer, &needed);
}

Assert.NotEqual(HResults.S_OK, hr);
}
}
Loading