This commit is contained in:
Adeel Mujahid 2025-07-30 15:56:23 +02:00 committed by GitHub
commit deb3e1b231
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 964 additions and 14 deletions

View File

@ -657,8 +657,6 @@
<_BuildMonoRuntimePack Condition="'$(RuntimeFlavor)' == 'Mono' and '$(MonoSupported)' == 'true'">true</_BuildMonoRuntimePack>
<_BuildHostPack Condition="'$(RuntimeFlavor)' == '$(PrimaryRuntimeFlavor)' and '$(TargetsMobile)' != 'true'">true</_BuildHostPack>
<_BuildCdacPack Condition="'$(_CDacToolsBuilt)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '$(TargetsMobile)' != 'true' and ('$(TargetOS)' == 'windows' or '$(TargetOS)' == 'osx' or '$(TargetOS)' == 'linux')">true</_BuildCdacPack>
<!-- Some source build configurations and musl need to work out packaging errors before they can be enabled -->
<_BuildCdacPack Condition="'$(DotNetBuildSourceOnly)' == 'true' or '$(TargetsLinuxMusl)' == 'true'">false</_BuildCdacPack>
<_BuildBundle Condition="'$(RuntimeFlavor)' == '$(PrimaryRuntimeFlavor)' and '$(TargetsMobile)' != 'true'">true</_BuildBundle>
</PropertyGroup>

View File

@ -521,7 +521,7 @@ extends:
jobParameters:
testScope: innerloop
nameSuffix: CoreCLR_Bootstrapped
buildArgs: -s clr+libs+host+packs -c $(_BuildConfig) -rc Checked --bootstrap
buildArgs: -s clr+libs+host+packs+tools.cdac -c $(_BuildConfig) -rc Checked --bootstrap
timeoutInMinutes: 120
condition: >-
or(

View File

@ -1,4 +1,6 @@
<Project>
<Import Project=".\native-library.targets" Condition="'$(IsSourceProject)' == 'true' and '$(Language)' == 'C#'" />
<Import Project="..\..\..\Directory.Build.targets" />
<Import Project="$(RepositoryEngineeringDir)liveILLink.targets" Condition="'$(UseBootstrapLayout)' == 'true'" />
<Import Project="$(RepositoryEngineeringDir)targetingpacks.targets" Condition="'$(UseBootstrapLayout)' == 'true'" />
</Project>

View File

@ -32,6 +32,7 @@ public interface IPlatformAgnosticContext
RuntimeInfoArchitecture.X64 => new ContextHolder<AMD64Context>(),
RuntimeInfoArchitecture.Arm => new ContextHolder<ARMContext>(),
RuntimeInfoArchitecture.Arm64 => new ContextHolder<ARM64Context>(),
RuntimeInfoArchitecture.RiscV64 => new ContextHolder<RISCV64Context>(),
RuntimeInfoArchitecture.Unknown => throw new InvalidOperationException($"Processor architecture is required for creating a platform specific context and is not provided by the target"),
_ => throw new InvalidOperationException($"Unsupported architecture {runtimeInfo.GetTargetArchitecture()}"),
};

View File

@ -0,0 +1,698 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.RISCV64Context;
namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.RISCV64;
internal class RISCV64Unwinder(Target target)
{
#region Constants
/// <summary>
/// This table describes the size of each unwind code, in bytes.
/// </summary>
private static ReadOnlySpan<byte> UnwindCodeSizeTable =>
[
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 00-0F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10-1F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 20-2F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 30-3F
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 40-4F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 50-5F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60-6F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 70-7F
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 80-8F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 90-9F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A0-AF
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B0-BF
2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, // C0-CF
3, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 2, 3, 3, 2, 1, // D0-DF
4, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E0-EF
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // F0-FF
];
#endregion
private readonly Target _target = target;
private readonly IExecutionManager _eman = target.Contracts.ExecutionManager;
#region Entrypoint
public bool Unwind(ref RISCV64Context context)
{
if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh)
return false;
TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh);
Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd<Data.RuntimeFunction>(_eman.GetUnwindInfo(cbh));
ulong startingPc = context.Pc;
ulong startingSp = context.Sp;
bool status = VirtualUnwind(ref context, imageBase, functionEntry);
//
// If we fail the unwind, clear the PC to 0. This is recognized by
// many callers as a failure, given that VirtualUnwind does not
// return a status code.
//
if (!status)
{
context.Pc = 0;
}
// PC == 0 means unwinding is finished.
// Same if no forward progress is made
if (context.Pc == 0 || (startingPc == context.Pc && startingSp == context.Sp))
return false;
return true;
}
#endregion
#region Unwinder
private bool VirtualUnwind(ref RISCV64Context context, TargetPointer imageBase, Data.RuntimeFunction functionEntry)
{
// FunctionEntry could be null if the function is a pure leaf/trivial function.
// This is not a valid case for managed code and not handled here.
uint controlPcRva = (uint)(context.Pc - imageBase.Value);
//
// Identify the compact .pdata format versus the full .pdata+.xdata format.
//
// RISC-V64 does not have a compact format like ARM64, so we only handle the full format.
//
return VirtualUnwindFull(ref context, controlPcRva, imageBase, functionEntry);
}
private bool VirtualUnwindFull(
ref RISCV64Context context,
uint controlPcRva,
TargetPointer imageBase,
Data.RuntimeFunction functionEntry)
{
//
// Unless a special frame is encountered, assume that any unwinding
// will return us to the return address of a call and set the flag
// appropriately (it will be cleared again if the special cases apply).
//
context.ContextFlags |= (uint)ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL;
//
// By default, unwinding is done by popping to the RA, then copying
// that RA to the PC. However, some special opcodes require different
// behavior.
//
bool finalPcFromRa = true;
//
// Fetch the header word from the .xdata blob
//
TargetPointer unwindDataPtr = imageBase + functionEntry.UnwindData;
uint headerWord = _target.Read<uint>(unwindDataPtr);
unwindDataPtr += 4;
//
// Verify the version before we do anything else
//
if (((headerWord >> 18) & 3) != 0)
{
// unsupported version
return false;
}
uint functionLength = headerWord & 0x3ffffu;
uint offsetInFunction = (controlPcRva - functionEntry.BeginAddress) / 4;
//
// Determine the number of epilog scope records and the maximum number
// of unwind codes.
//
uint unwindWords = (headerWord >> 27) & 31;
uint epilogScopeCount = (headerWord >> 22) & 31;
if (epilogScopeCount == 0 && unwindWords == 0)
{
epilogScopeCount = _target.Read<uint>(unwindDataPtr);
unwindDataPtr += 4;
unwindWords = (epilogScopeCount >> 16) & 0xff;
epilogScopeCount &= 0xffff;
}
uint unwindIndex = 0;
if ((headerWord & (1 << 21)) != 0)
{
unwindIndex = epilogScopeCount;
epilogScopeCount = 0;
}
//
// Exception data is not supported in this implementation and is not used by managed code.
// If it were, it should be extracted here.
//
//
// Unless we are in a prolog/epilog, we execute the unwind codes
// that immediately follow the epilog scope list.
//
TargetPointer unwindCodePtr = unwindDataPtr + 4 * epilogScopeCount;
TargetPointer unwindCodesEndPtr = unwindCodePtr + 4 * unwindWords;
uint skipWords = 0;
//
// If we're near the start of the function, and this function has a prolog,
// compute the size of the prolog from the unwind codes. If we're in the
// midst of it, we still execute starting at unwind code index 0, but we may
// need to skip some to account for partial execution of the prolog.
//
// N.B. As an optimization here, note that each byte of unwind codes can
// describe at most one 32-bit instruction. Thus, the largest prologue
// that could possibly be described by UnwindWords (which is 4 * the
// number of unwind code bytes) is 4 * UnwindWords words. If
// OffsetInFunction is larger than this value, it is guaranteed to be
// in the body of the function.
//
uint scopeSize;
if (offsetInFunction < 4 * unwindWords)
{
scopeSize = ComputeScopeSize(unwindCodePtr, unwindCodesEndPtr);
if (offsetInFunction < scopeSize)
{
skipWords = scopeSize - offsetInFunction;
}
}
if (skipWords > 0)
{
// Found that we are in the middle of a prolog, no need to check for epilog scopes
}
//
// We're not in the prolog, now check to see if we are in the epilog.
// In the simple case, the 'E' bit is set indicating there is a single
// epilog that lives at the end of the function. If we're near the end
// of the function, compute the actual size of the epilog from the
// unwind codes. If we're in the midst of it, adjust the unwind code
// pointer to the start of the codes and determine how many we need to skip.
//
// N.B. Similar to the prolog case above, the maximum number of halfwords
// that an epilog can cover is limited by UnwindWords. In the epilog
// case, however, the starting index within the unwind code table is
// non-zero, and so the maximum number of unwind codes that can pertain
// to an epilog is (UnwindWords * 4 - UnwindIndex), thus further
// constraining the bounds of the epilog.
//
else if ((headerWord & (1 << 21)) != 0)
{
if (offsetInFunction + (4 * unwindWords - unwindIndex) >= functionLength)
{
scopeSize = ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr);
uint scopeStart = functionLength - scopeSize;
//
// N.B. This code assumes that no handleable exceptions can occur in
// the prolog or in a chained shrink-wrapping prolog region.
//
if (offsetInFunction >= scopeStart)
{
unwindCodePtr += unwindIndex;
skipWords = offsetInFunction - scopeStart;
}
}
}
//
// In the multiple-epilog case, we scan forward to see if we are within
// shooting distance of any of the epilogs. If we are, we compute the
// actual size of the epilog from the unwind codes and proceed like the
// simple case above.
//
else
{
for (uint scopeNum = 0; scopeNum < epilogScopeCount; scopeNum++)
{
headerWord = _target.Read<uint>(unwindDataPtr);
unwindDataPtr += 4;
//
// The scope records are stored in order. If we hit a record that
// starts after our current position, we must not be in an epilog.
//
uint scopeStart = headerWord & 0x3ffffu;
if (offsetInFunction < scopeStart)
break;
unwindIndex = headerWord >> 22;
if (offsetInFunction < scopeStart + (4 * unwindWords - unwindIndex))
{
scopeSize = ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr);
if (offsetInFunction < scopeStart + scopeSize)
{
unwindCodePtr += unwindIndex;
skipWords = offsetInFunction - scopeStart;
break;
}
}
}
}
//
// Skip over unwind codes until we account for the number of halfwords
// to skip.
//
while (unwindCodePtr < unwindCodesEndPtr && skipWords > 0)
{
byte curCode = _target.Read<byte>(unwindCodePtr);
if (OpcodeIsEnd(curCode))
{
break;
}
unwindCodePtr += UnwindCodeSizeTable[curCode];
skipWords--;
}
//
// Now execute codes until we hit the end.
//
uint accumulatedSaveNexts = 0;
while (unwindCodePtr < unwindCodesEndPtr)
{
byte curCode = _target.Read<byte>(unwindCodePtr);
unwindCodePtr += 1;
bool isEndCode = OpcodeIsEnd(curCode);
if (!ProcessUnwindCode(ref context, curCode, ref unwindCodePtr, unwindCodesEndPtr, ref accumulatedSaveNexts))
{
return false;
}
if (isEndCode)
{
break;
}
}
//
// If we succeeded, post-process the final state.
//
if (finalPcFromRa)
{
context.Pc = context.Ra;
}
return true;
}
private uint ComputeScopeSize(TargetPointer unwindCodePtr, TargetPointer unwindCodesEndPtr)
{
//
// Iterate through the unwind codes until we hit an end marker.
// While iterating, accumulate the total scope size.
//
uint scopeSize = 0;
while (unwindCodePtr < unwindCodesEndPtr)
{
byte opcode = _target.Read<byte>(unwindCodePtr);
if (OpcodeIsEnd(opcode))
{
break;
}
unwindCodePtr += UnwindCodeSizeTable[opcode];
scopeSize++;
}
return scopeSize;
}
private static bool OpcodeIsEnd(byte opcode)
{
return (opcode & 0xfe) == 0xe4;
}
private bool ProcessUnwindCode(ref RISCV64Context context, byte curCode, ref TargetPointer unwindCodePtr, TargetPointer unwindCodesEndPtr, ref uint accumulatedSaveNexts)
{
// Note: finalPcFromRa parameter is reserved for future use when different final PC handling is needed
try
{
//
// alloc_s (000xxxxx): allocate small stack with size < 1024 (2^5 * 16)
//
if (curCode <= 0x1f)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
context.Sp += 16u * (uint)(curCode & 0x1f);
}
//
// save_r19r20_x (001zzzzz): save register pair r19,r20 at [sp-#Z*8]!, pre-indexed offset >= -248
//
else if (curCode <= 0x3f)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
uint offset = (uint)(curCode & 0x1f) * 8;
context.S3 = _target.ReadPointer(context.Sp + offset).Value;
context.S4 = _target.ReadPointer(context.Sp + offset + 8).Value;
}
//
// save_fplr (01zzzzzz): save <r29,r30> pair at [sp+#Z*8], offset <= 504
//
else if (curCode <= 0x7f)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
uint offset = (uint)(curCode & 0x3f) * 8;
context.Fp = _target.ReadPointer(context.Sp + offset).Value;
context.Ra = _target.ReadPointer(context.Sp + offset + 8).Value;
}
//
// save_fplr_x (10zzzzzz): save <r29,r30> pair at [sp-(#Z+1)*8]!, pre-indexed offset >= -512
//
else if (curCode <= 0xbf)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
uint offset = (uint)((curCode & 0x3f) + 1) * 8;
context.Sp += offset;
context.Fp = _target.ReadPointer(context.Sp - offset).Value;
context.Ra = _target.ReadPointer(context.Sp - offset + 8).Value;
}
//
// alloc_m (11000xxx|xxxxxxxx): allocate large stack with size < 32k (2^11 * 16)
//
else if (curCode <= 0xc7)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
if (unwindCodePtr >= unwindCodesEndPtr)
{
return false;
}
byte nextCode = _target.Read<byte>(unwindCodePtr);
unwindCodePtr += 1;
uint offset = (uint)(((curCode & 0x07) << 8) | nextCode) * 16;
context.Sp += offset;
}
//
// save_regp (110010xx|xxzzzzzz): save r(19+#X) pair at [sp+#Z*8], offset <= 504
//
else if (curCode <= 0xcb)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
if (unwindCodePtr >= unwindCodesEndPtr)
{
return false;
}
byte nextCode = _target.Read<byte>(unwindCodePtr);
unwindCodePtr += 1;
uint regPair = (uint)((curCode & 0x03) << 2) | (uint)((nextCode & 0xc0) >> 6);
uint offset = (uint)(nextCode & 0x3f) * 8;
SetRegisterFromOffset(ref context, 19 + regPair, context.Sp + offset);
SetRegisterFromOffset(ref context, 20 + regPair, context.Sp + offset + 8);
}
//
// save_regp_x (110011xx|xxzzzzzz): save pair r(19+#X) at [sp-(#Z+1)*8]!, pre-indexed offset >= -512
//
else if (curCode <= 0xcf)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
if (unwindCodePtr >= unwindCodesEndPtr)
{
return false;
}
byte nextCode = _target.Read<byte>(unwindCodePtr);
unwindCodePtr += 1;
uint regPair = (uint)((curCode & 0x03) << 2) | (uint)((nextCode & 0xc0) >> 6);
uint offset = (uint)((nextCode & 0x3f) + 1) * 8;
context.Sp += offset;
SetRegisterFromOffset(ref context, 19 + regPair, context.Sp - offset);
SetRegisterFromOffset(ref context, 20 + regPair, context.Sp - offset + 8);
}
//
// save_reg (1101xxxx|xxzzzzzz): save reg r(19+#X) at [sp+#Z*8], offset <= 504
//
else if (curCode <= 0xd7)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
if (unwindCodePtr >= unwindCodesEndPtr)
{
return false;
}
byte nextCode = _target.Read<byte>(unwindCodePtr);
unwindCodePtr += 1;
uint reg = (uint)((curCode & 0x0f) << 2) | (uint)((nextCode & 0xc0) >> 6);
uint offset = (uint)(nextCode & 0x3f) * 8;
SetRegisterFromOffset(ref context, 19 + reg, context.Sp + offset);
}
//
// save_reg_x (1101xxxx|xxzzzzzz): save reg r(19+#X) at [sp-(#Z+1)*8]!, pre-indexed offset >= -512
//
else if (curCode <= 0xdf)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
if (unwindCodePtr >= unwindCodesEndPtr)
{
return false;
}
byte nextCode = _target.Read<byte>(unwindCodePtr);
unwindCodePtr += 1;
uint reg = (uint)((curCode & 0x0f) << 2) | (uint)((nextCode & 0xc0) >> 6);
uint offset = (uint)((nextCode & 0x3f) + 1) * 8;
context.Sp += offset;
SetRegisterFromOffset(ref context, 19 + reg, context.Sp - offset);
}
//
// alloc_l (11100000|xxxxxxxx|xxxxxxxx|xxxxxxxx): allocate large stack with size < 256M
//
else if (curCode == 0xe0)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
if (unwindCodePtr + 3 > unwindCodesEndPtr)
{
return false;
}
byte nextCode1 = _target.Read<byte>(unwindCodePtr);
byte nextCode2 = _target.Read<byte>(unwindCodePtr + 1);
byte nextCode3 = _target.Read<byte>(unwindCodePtr + 2);
unwindCodePtr += 3;
uint size = (uint)((nextCode1 << 16) | (nextCode2 << 8) | nextCode3);
context.Sp += size * 16;
}
//
// set_fp (11100001): set up r29: with: mov r29, sp
//
else if (curCode == 0xe1)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
// No action needed for unwind
}
//
// add_fp (11100010|xxxxxxxx): set up r29 with: add r29, sp, #x*8
//
else if (curCode == 0xe2)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
if (unwindCodePtr >= unwindCodesEndPtr)
{
return false;
}
byte nextCode = _target.Read<byte>(unwindCodePtr);
unwindCodePtr += 1;
// For unwinding, we need to adjust SP relative to FP
// This is a nop in terms of register restoration
}
//
// nop (11100011): no unwind operation is required
//
else if (curCode == 0xe3)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
// No operation
}
//
// end (11100100): end of unwind code
//
else if (curCode == 0xe4)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
return true; // End marker found
}
//
// end_c (11100101): end of unwind code in current chained scope
//
else if (curCode == 0xe5)
{
if (accumulatedSaveNexts != 0)
{
return false;
}
return true; // End marker found
}
//
// save_next (11100110): save next non-volatile Int or FP register pair
//
else if (curCode == 0xe6)
{
accumulatedSaveNexts++;
}
//
// For unsupported opcodes, just fail
//
else
{
return false;
}
return true;
}
catch
{
return false;
}
}
private void SetRegisterFromOffset(ref RISCV64Context context, uint regNum, ulong address)
{
try
{
ulong value = _target.ReadPointer(address).Value;
SetRegisterValue(ref context, regNum, value);
}
catch
{
// Ignore read failures
}
}
private static void SetRegisterValue(ref RISCV64Context context, uint regNum, ulong value)
{
switch (regNum)
{
case 1: context.Ra = value; break;
case 2: context.Sp = value; break;
case 3: context.Gp = value; break;
case 4: context.Tp = value; break;
case 5: context.T0 = value; break;
case 6: context.T1 = value; break;
case 7: context.T2 = value; break;
case 8: context.Fp = value; break;
case 9: context.S1 = value; break;
case 10: context.A0 = value; break;
case 11: context.A1 = value; break;
case 12: context.A2 = value; break;
case 13: context.A3 = value; break;
case 14: context.A4 = value; break;
case 15: context.A5 = value; break;
case 16: context.A6 = value; break;
case 17: context.A7 = value; break;
case 18: context.S2 = value; break;
case 19: context.S3 = value; break;
case 20: context.S4 = value; break;
case 21: context.S5 = value; break;
case 22: context.S6 = value; break;
case 23: context.S7 = value; break;
case 24: context.S8 = value; break;
case 25: context.S9 = value; break;
case 26: context.S10 = value; break;
case 27: context.S11 = value; break;
case 28: context.T3 = value; break;
case 29: context.T4 = value; break;
case 30: context.T5 = value; break;
case 31: context.T6 = value; break;
}
}
#endregion
}

View File

@ -0,0 +1,215 @@
// 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.Runtime.InteropServices;
using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.RISCV64;
namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
/// <summary>
/// RISC-V 64-bit specific thread context.
/// </summary>
[StructLayout(LayoutKind.Explicit, Pack = 1)]
internal struct RISCV64Context : IPlatformContext
{
[Flags]
public enum ContextFlagsValues : uint
{
CONTEXT_RISCV64 = 0x01000000,
CONTEXT_CONTROL = CONTEXT_RISCV64 | 0x1,
CONTEXT_INTEGER = CONTEXT_RISCV64 | 0x2,
CONTEXT_FLOATING_POINT = CONTEXT_RISCV64 | 0x4,
CONTEXT_DEBUG_REGISTERS = CONTEXT_RISCV64 | 0x8,
CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT,
CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS,
//
// This flag is set by the unwinder if it has unwound to a call
// site, and cleared whenever it unwinds through a trap frame.
// It is used by language-specific exception handlers to help
// differentiate exception scopes during dispatching.
//
CONTEXT_UNWOUND_TO_CALL = 0x20000000,
CONTEXT_AREA_MASK = 0xFFFF,
}
public readonly uint Size => 0x220;
public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL;
public TargetPointer StackPointer
{
readonly get => new(Sp);
set => Sp = value.Value;
}
public TargetPointer InstructionPointer
{
readonly get => new(Pc);
set => Pc = value.Value;
}
public TargetPointer FramePointer
{
readonly get => new(Fp);
set => Fp = value.Value;
}
public void Unwind(Target target)
{
RISCV64Unwinder unwinder = new(target);
unwinder.Unwind(ref this);
}
// Control flags
[FieldOffset(0x0)]
public uint ContextFlags;
#region General registers
[Register(RegisterType.General)]
[FieldOffset(0x8)]
public ulong Ra;
[Register(RegisterType.General | RegisterType.StackPointer)]
[FieldOffset(0x10)]
public ulong Sp;
[Register(RegisterType.General)]
[FieldOffset(0x18)]
public ulong Gp;
[Register(RegisterType.General)]
[FieldOffset(0x20)]
public ulong Tp;
[Register(RegisterType.General)]
[FieldOffset(0x28)]
public ulong T0;
[Register(RegisterType.General)]
[FieldOffset(0x30)]
public ulong T1;
[Register(RegisterType.General)]
[FieldOffset(0x38)]
public ulong T2;
[Register(RegisterType.General | RegisterType.FramePointer)]
[FieldOffset(0x40)]
public ulong Fp;
[Register(RegisterType.General)]
[FieldOffset(0x48)]
public ulong S1;
[Register(RegisterType.General)]
[FieldOffset(0x50)]
public ulong A0;
[Register(RegisterType.General)]
[FieldOffset(0x58)]
public ulong A1;
[Register(RegisterType.General)]
[FieldOffset(0x60)]
public ulong A2;
[Register(RegisterType.General)]
[FieldOffset(0x68)]
public ulong A3;
[Register(RegisterType.General)]
[FieldOffset(0x70)]
public ulong A4;
[Register(RegisterType.General)]
[FieldOffset(0x78)]
public ulong A5;
[Register(RegisterType.General)]
[FieldOffset(0x80)]
public ulong A6;
[Register(RegisterType.General)]
[FieldOffset(0x88)]
public ulong A7;
[Register(RegisterType.General)]
[FieldOffset(0x90)]
public ulong S2;
[Register(RegisterType.General)]
[FieldOffset(0x98)]
public ulong S3;
[Register(RegisterType.General)]
[FieldOffset(0xa0)]
public ulong S4;
[Register(RegisterType.General)]
[FieldOffset(0xa8)]
public ulong S5;
[Register(RegisterType.General)]
[FieldOffset(0xb0)]
public ulong S6;
[Register(RegisterType.General)]
[FieldOffset(0xb8)]
public ulong S7;
[Register(RegisterType.General)]
[FieldOffset(0xc0)]
public ulong S8;
[Register(RegisterType.General)]
[FieldOffset(0xc8)]
public ulong S9;
[Register(RegisterType.General)]
[FieldOffset(0xd0)]
public ulong S10;
[Register(RegisterType.General)]
[FieldOffset(0xd8)]
public ulong S11;
[Register(RegisterType.General)]
[FieldOffset(0xe0)]
public ulong T3;
[Register(RegisterType.General)]
[FieldOffset(0xe8)]
public ulong T4;
[Register(RegisterType.General)]
[FieldOffset(0xf0)]
public ulong T5;
[Register(RegisterType.General)]
[FieldOffset(0xf8)]
public ulong T6;
#endregion
#region Control Registers
[Register(RegisterType.Control | RegisterType.ProgramCounter)]
[FieldOffset(0x100)]
public ulong Pc;
#endregion
#region Floating Point Registers
[Register(RegisterType.FloatingPoint)]
[FieldOffset(0x110)]
public unsafe fixed ulong F[32];
[Register(RegisterType.FloatingPoint)]
[FieldOffset(0x210)]
public uint Fcsr;
#endregion
}

View File

@ -0,0 +1,30 @@
// 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.Diagnostics;
using Microsoft.Diagnostics.DataContractReader.Data;
using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.RISCV64Context;
namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
internal class RISCV64FrameHandler(Target target, ContextHolder<RISCV64Context> contextHolder) : BaseFrameHandler(target, contextHolder), IPlatformFrameHandler
{
private readonly ContextHolder<RISCV64Context> _holder = contextHolder;
public void HandleHijackFrame(HijackFrame frame)
{
HijackArgs args = _target.ProcessedData.GetOrAdd<Data.HijackArgs>(frame.HijackArgsPtr);
_holder.InstructionPointer = frame.ReturnAddress;
// The stack pointer is the address immediately following HijackArgs
uint hijackArgsSize = _target.GetTypeInfo(DataType.HijackArgs).Size ?? throw new InvalidOperationException("HijackArgs size is not set");
Debug.Assert(hijackArgsSize % 8 == 0, "HijackArgs contains register values and should be a multiple of the pointer size (8 bytes for RISC-V64)");
// The stack must be multiple of 16. So if hijackArgsSize is not multiple of 16 then there must be padding of 8 bytes
hijackArgsSize += hijackArgsSize % 16;
_holder.StackPointer = frame.HijackArgsPtr + hijackArgsSize;
UpdateFromRegisterDict(args.Registers);
}
}

View File

@ -117,6 +117,16 @@ public class PrecodeStubsTests
StubPrecodeSize = 12,
};
internal static PrecodeTestDescriptor RiscV64TestDescriptor = new PrecodeTestDescriptor("RiscV64") {
Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true },
ReadWidthOfPrecodeType = 1,
ShiftOfPrecodeType = 0,
OffsetOfPrecodeType = 0,
StubCodePageSize = 0x4000u, // 16KiB
StubPrecode = 0x17,
StubPrecodeSize = 24,
};
public static IEnumerable<object[]> PrecodeTestDescriptorData()
{
var arch32le = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = false };
@ -125,6 +135,7 @@ public class PrecodeStubsTests
yield return new object[] { X64TestDescriptor };
yield return new object[] { Arm64TestDescriptor };
yield return new object[] { RiscV64TestDescriptor };
yield return new object[] { LoongArch64TestDescriptor };
yield return new object[] { Arm32Thumb };
// FIXME: maybe make these a little more exotic
@ -329,11 +340,11 @@ public class PrecodeStubsTests
Builder.AddHeapFragment(stubCodeFragment);
Span<byte> thisPtrStubData = Builder.BorrowAddressRange(thisPtrRetBufStubDataFragment.Address, (int)thisPtrRetBufDataTypeInfo.Size);
Builder.TargetTestHelpers.WritePointer(thisPtrStubData.Slice(thisPtrRetBufDataTypeInfo.Fields[nameof(Data.ThisPtrRetBufPrecodeData.MethodDesc)].Offset, Builder.TargetTestHelpers.PointerSize), methodDesc);
Span<byte> stubData = Builder.BorrowAddressRange(stubDataFragment.Address, (int)stubDataTypeInfo.Size);
Builder.TargetTestHelpers.Write(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData_2.Type)].Offset, sizeof(byte)), test.ThisPtrRetBufPrecode);
Builder.TargetTestHelpers.WritePointer(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData_2.SecretParam)].Offset, Builder.TargetTestHelpers.PointerSize), thisPtrRetBufStubDataFragment.Address);

View File

@ -1,4 +1,3 @@
<Project>
<Import Project="..\Directory.Build.targets" />
<Import Project="$(RepositoryEngineeringDir)targetingpacks.targets" Condition="'$(UseBootstrapLayout)' == 'true'" />
</Project>

View File

@ -19,13 +19,8 @@
<SupportsNativeAotComponents Condition="'$(SupportsNativeAotComponents)' == '' and '$(RuntimeFlavor)' == 'Mono'">false</SupportsNativeAotComponents>
<!-- disable on linux-bionic, for now -->
<SupportsNativeAotComponents Condition="'$(SupportsNativeAotComponents)' == '' and '$(TargetsLinuxBionic)' == 'true'">false</SupportsNativeAotComponents>
<!-- NativeAOT doesn't support cross-OS compilation. disable for crossdac-->
<SupportsNativeAotComponents Condition="'$(SupportsNativeAotComponents)' == '' and '$(HostOS)' != '$(TargetOS)'">false</SupportsNativeAotComponents>
<!-- unsupported targets -->
<SupportsNativeAotComponents Condition="'$(SupportsNativeAotComponents)' == '' and '$(DotNetBuildSourceOnly)' == 'true'">false</SupportsNativeAotComponents>
<SupportsNativeAotComponents Condition="'$(SupportsNativeAotComponents)' == '' and ('$(TargetArchitecture)' == 'armel' or '$(TargetArchitecture)' == 'riscv64')">false</SupportsNativeAotComponents>
<SupportsNativeAotComponents Condition="'$(SupportsNativeAotComponents)' == '' and ('$(TargetsWindows)' == 'true' or '$(TargetsOSX)' == 'true' or ('$(TargetsLinux)' == 'true' and '$(TargetsAndroid)' != 'true' and '$(TargetsLinuxMusl)' != 'true'))">true</SupportsNativeAotComponents>
<SupportsNativeAotComponents Condition="'$(SupportsNativeAotComponents)' == ''">false</SupportsNativeAotComponents>
<!-- use global property for the rest -->
<SupportsNativeAotComponents Condition="'$(SupportsNativeAotComponents)' == ''">$(NativeAotSupported)</SupportsNativeAotComponents>
</PropertyGroup>
<ItemGroup>

View File

@ -4,7 +4,8 @@
<SubprojectProps Include="Configuration" Value="$(Configuration)" />
<SubprojectProps Include="RuntimeConfiguration" Value="$(RuntimeConfiguration)" />
<SubprojectProps Include="LibrariesConfiguration" Value="$(LibrariesConfiguration)" />
<SubprojectProps Include="RuntimeIdentifier" Value="$(TargetRid)" />
<SubprojectProps Include="RuntimeIdentifier" Value="$(PortableTargetRid)" />
<SubprojectProps Include="UseBootstrapLayout" Value="$(UseBootstrap)" />
</ItemGroup>
<PropertyGroup>