From 595d3f2f3b0fc8a2b0610f64a0b2aff387058089 Mon Sep 17 00:00:00 2001
From: Adeel <3840695+am11@users.noreply.github.com>
Date: Mon, 20 Oct 2025 23:33:36 +0300
Subject: [PATCH] Add loongarch64 managed unwinder for cDAC
---
eng/Subsets.props | 2 -
.../vm/datadescriptor/datadescriptor.inc | 30 +
.../LoongArch64/LoongArch64Unwinder.cs | 603 ++++++++++++++++++
.../StackWalk/Context/LoongArch64Context.cs | 226 +++++++
.../StackWalk/FrameHandling/FrameIterator.cs | 1 +
.../FrameHandling/LoongArch64FrameHandler.cs | 39 ++
6 files changed, 899 insertions(+), 2 deletions(-)
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64/LoongArch64Unwinder.cs
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs
create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/LoongArch64FrameHandler.cs
diff --git a/eng/Subsets.props b/eng/Subsets.props
index e4cc340001ee3a..53ff9f5a95310b 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -669,8 +669,6 @@
<_BuildMonoRuntimePack Condition="'$(RuntimeFlavor)' == 'Mono' and '$(MonoSupported)' == 'true'">true
<_BuildHostPack Condition="'$(RuntimeFlavor)' == '$(PrimaryRuntimeFlavor)' and '$(TargetsMobile)' != 'true'">true
<_BuildCdacPack Condition="'$(_CDacToolsBuilt)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR' and '$(TargetsMobile)' != 'true' and ('$(TargetOS)' == 'windows' or '$(TargetOS)' == 'osx' or '$(TargetOS)' == 'linux')">true
-
- <_BuildCdacPack Condition="'$(TargetArchitecture)' == 'loongarch64'">false
<_BuildBundle Condition="'$(RuntimeFlavor)' == '$(PrimaryRuntimeFlavor)' and '$(TargetsMobile)' != 'true'">true
diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc
index 85d6df4ecaa043..42f6872a0643cb 100644
--- a/src/coreclr/vm/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc
@@ -865,6 +865,22 @@ CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Tp, offsetof(HijackArgs, Tp))
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, A0, offsetof(HijackArgs, A0))
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, A1, offsetof(HijackArgs, A1))
+#elif defined(TARGET_LOONGARCH64)
+
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Fp, offsetof(HijackArgs, Fp))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Ra, offsetof(HijackArgs, Ra))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S0, offsetof(HijackArgs, S0))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S1, offsetof(HijackArgs, S1))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S2, offsetof(HijackArgs, S2))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S3, offsetof(HijackArgs, S3))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S4, offsetof(HijackArgs, S4))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S5, offsetof(HijackArgs, S5))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S6, offsetof(HijackArgs, S6))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S7, offsetof(HijackArgs, S7))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, S8, offsetof(HijackArgs, S8))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, A0, offsetof(HijackArgs, A0))
+CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, A1, offsetof(HijackArgs, A1))
+
#endif // Platform switch
CDAC_TYPE_END(HijackArgs)
#endif // FEATURE_HIJACK
@@ -952,6 +968,20 @@ CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S11, offsetof(CalleeSavedRegist
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Tp, offsetof(CalleeSavedRegisters, tp))
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Gp, offsetof(CalleeSavedRegisters, gp))
+#elif defined(TARGET_LOONGARCH64)
+
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Fp, offsetof(CalleeSavedRegisters, fp))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Ra, offsetof(CalleeSavedRegisters, ra))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S0, offsetof(CalleeSavedRegisters, s0))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S1, offsetof(CalleeSavedRegisters, s1))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S2, offsetof(CalleeSavedRegisters, s2))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S3, offsetof(CalleeSavedRegisters, s3))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S4, offsetof(CalleeSavedRegisters, s4))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S5, offsetof(CalleeSavedRegisters, s5))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S6, offsetof(CalleeSavedRegisters, s6))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S7, offsetof(CalleeSavedRegisters, s7))
+CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, S8, offsetof(CalleeSavedRegisters, s8))
+
#endif // Platform switch
CDAC_TYPE_END(CalleeSavedRegisters)
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64/LoongArch64Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64/LoongArch64Unwinder.cs
new file mode 100644
index 00000000000000..611fa0c6689e8c
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64/LoongArch64Unwinder.cs
@@ -0,0 +1,603 @@
+// 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.LoongArch64Context;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.LoongArch64;
+
+internal class LoongArch64Unwinder(Target target)
+{
+ #region Constants
+
+ ///
+ /// This table describes the size of each unwind code, in bytes.
+ ///
+ private static ReadOnlySpan UnwindCodeSizeTable =>
+ [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 00-1F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 20-3F
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40-5F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60-7F
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80-9F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A0-BF
+ 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 2, // C0-DF
+ 4, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // E0-FF
+ ];
+
+ #endregion
+
+ private readonly Target _target = target;
+ private readonly IExecutionManager _eman = target.Contracts.ExecutionManager;
+
+ #region Entrypoint
+
+ public bool Unwind(ref LoongArch64Context context)
+ {
+ if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh)
+ return false;
+
+ TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh);
+ Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_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 LoongArch64Context 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);
+
+ return VirtualUnwindFull(ref context, controlPcRva, imageBase, functionEntry);
+ }
+
+ private bool VirtualUnwindFull(
+ ref LoongArch64Context 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(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(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(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(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(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(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 LoongArch64Context context, byte curCode, ref TargetPointer unwindCodePtr, TargetPointer unwindCodesEndPtr, ref uint accumulatedSaveNexts)
+ {
+ try
+ {
+ //
+ // alloc_s (000xxxxx): allocate small stack with size < 1024 (2^5 * 16)
+ //
+
+ if (curCode <= 0x1f)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+ context.Sp += 16u * (uint)(curCode & 0x1f);
+ }
+
+ //
+ // alloc_m (11000xxx|xxxxxxxx): allocate large stack with size < 32k (2^11 * 16)
+ //
+
+ else if (curCode <= 0xc7)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+ if (unwindCodePtr >= unwindCodesEndPtr)
+ {
+ return false;
+ }
+ byte nextCode = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ context.Sp += 16u * ((curCode & 7u) << 8);
+ context.Sp += 16u * nextCode;
+ }
+
+ //
+ // save_reg (11010000|000xxxxx|zzzzzzzz): save reg r(1+#X) at [sp+#Z*8], offset <= 2047
+ //
+
+ else if (curCode == 0xd0)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+ if (unwindCodePtr + 1 >= unwindCodesEndPtr)
+ {
+ return false;
+ }
+ byte nextCode = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ byte nextCode1 = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+
+ uint regNum = 1u + nextCode;
+ uint offset = 8u * nextCode1;
+ SetRegisterFromOffset(ref context, regNum, context.Sp + offset);
+ }
+
+ //
+ // save_freg (11011100|0xxxzzzz|zzzzzzzz): save reg f(24+#X) at [sp+#Z*8], offset <= 32767
+ //
+
+ else if (curCode == 0xdc)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+ if (unwindCodePtr + 1 >= unwindCodesEndPtr)
+ {
+ return false;
+ }
+ byte nextCode = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ byte nextCode1 = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ }
+
+ //
+ // alloc_l (11100000|xxxxxxxx|xxxxxxxx|xxxxxxxx): allocate large stack with size < 256M
+ //
+
+ else if (curCode == 0xe0)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+ if (unwindCodePtr + 2 >= unwindCodesEndPtr)
+ {
+ return false;
+ }
+ byte nextCode1 = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ byte nextCode2 = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ byte nextCode3 = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ context.Sp += 16u * (uint)((nextCode1 << 16) | (nextCode2 << 8) | nextCode3);
+ }
+
+ //
+ // set_fp (11100001): set up fp: with: ori fp,sp,0
+ //
+
+ else if (curCode == 0xe1)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+ context.Sp = context.Fp;
+ }
+
+ //
+ // add_fp (11100010|000xxxxx|xxxxxxxx): set up fp with: addi.d fp,sp,#x*8
+ //
+
+ else if (curCode == 0xe2)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+ if (unwindCodePtr + 1 >= unwindCodesEndPtr)
+ {
+ return false;
+ }
+ byte nextCode = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ byte nextCode1 = _target.Read(unwindCodePtr);
+ unwindCodePtr += 1;
+ context.Sp = context.Fp - 8u * ((uint)((nextCode << 8) | nextCode1));
+ }
+
+ //
+ // nop (11100011): no unwind operation is required
+ //
+
+ else if (curCode == 0xe3)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+ }
+
+ //
+ // end (11100100): end of unwind code
+ //
+
+ else if (curCode == 0xe4)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+
+ return true;
+ }
+
+ //
+ // end_c (11100101): end of unwind code in current chained scope.
+ // Continue unwinding parent scope.
+ //
+
+ else if (curCode == 0xe5)
+ {
+ }
+
+ //
+ // custom_0 (111010xx): restore custom structure
+ //
+
+ else if (curCode >= 0xe8 && curCode <= 0xeb)
+ {
+ if (accumulatedSaveNexts != 0)
+ {
+ // invalid sequence
+ return false;
+ }
+
+ return false;
+ }
+
+ //
+ // Anything else is invalid
+ //
+
+ else
+ {
+ // invalid sequence
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private void SetRegisterFromOffset(ref LoongArch64Context 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 LoongArch64Context context, uint regNum, ulong value)
+ {
+ switch (regNum)
+ {
+ case 0: context.R0 = value; break;
+ case 1: context.Ra = value; break;
+ case 2: context.Tp = value; break;
+ case 3: context.Sp = value; break;
+ case 4: context.A0 = value; break;
+ case 5: context.A1 = value; break;
+ case 6: context.A2 = value; break;
+ case 7: context.A3 = value; break;
+ case 8: context.A4 = value; break;
+ case 9: context.A5 = value; break;
+ case 10: context.A6 = value; break;
+ case 11: context.A7 = value; break;
+ case 12: context.T0 = value; break;
+ case 13: context.T1 = value; break;
+ case 14: context.T2 = value; break;
+ case 15: context.T3 = value; break;
+ case 16: context.T4 = value; break;
+ case 17: context.T5 = value; break;
+ case 18: context.T6 = value; break;
+ case 19: context.T7 = value; break;
+ case 20: context.T8 = value; break;
+ case 21: context.X0 = value; break;
+ case 22: context.Fp = value; break;
+ case 23: context.S0 = value; break;
+ case 24: context.S1 = value; break;
+ case 25: context.S2 = value; break;
+ case 26: context.S3 = value; break;
+ case 27: context.S4 = value; break;
+ case 28: context.S5 = value; break;
+ case 29: context.S6 = value; break;
+ case 30: context.S7 = value; break;
+ case 31: context.S8 = value; break;
+ }
+ }
+
+ #endregion
+}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs
new file mode 100644
index 00000000000000..35b89d0a7a5c4b
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs
@@ -0,0 +1,226 @@
+// 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.LoongArch64;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
+
+///
+/// LoongArch64-specific thread context.
+///
+[StructLayout(LayoutKind.Explicit, Pack = 1)]
+internal struct LoongArch64Context : IPlatformContext
+{
+ [Flags]
+ public enum ContextFlagsValues : uint
+ {
+ CONTEXT_LOONGARCH64 = 0x00800000,
+ CONTEXT_CONTROL = CONTEXT_LOONGARCH64 | 0x1,
+ CONTEXT_INTEGER = CONTEXT_LOONGARCH64 | 0x2,
+ CONTEXT_FLOATING_POINT = CONTEXT_LOONGARCH64 | 0x4,
+ CONTEXT_DEBUG_REGISTERS = CONTEXT_LOONGARCH64 | 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 => 0x320;
+
+ public readonly uint DefaultContextFlags => (uint)(ContextFlagsValues.CONTEXT_CONTROL |
+ ContextFlagsValues.CONTEXT_INTEGER |
+ ContextFlagsValues.CONTEXT_FLOATING_POINT |
+ ContextFlagsValues.CONTEXT_DEBUG_REGISTERS);
+
+ 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)
+ {
+ LoongArch64Unwinder unwinder = new(target);
+ unwinder.Unwind(ref this);
+ }
+
+ // Control flags
+
+ [FieldOffset(0x0)]
+ public uint ContextFlags;
+
+ #region General registers
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x8)]
+ public ulong R0;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x10)]
+ public ulong Ra;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x18)]
+ public ulong Tp;
+
+ [Register(RegisterType.General | RegisterType.StackPointer)]
+ [FieldOffset(0x20)]
+ public ulong Sp;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x28)]
+ public ulong A0;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x30)]
+ public ulong A1;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x38)]
+ public ulong A2;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x40)]
+ public ulong A3;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x48)]
+ public ulong A4;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x50)]
+ public ulong A5;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x58)]
+ public ulong A6;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x60)]
+ public ulong A7;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x68)]
+ public ulong T0;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x70)]
+ public ulong T1;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x78)]
+ public ulong T2;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x80)]
+ public ulong T3;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x88)]
+ public ulong T4;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x90)]
+ public ulong T5;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x98)]
+ public ulong T6;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xa0)]
+ public ulong T7;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xa8)]
+ public ulong T8;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xb0)]
+ public ulong X0;
+
+ [Register(RegisterType.General | RegisterType.FramePointer)]
+ [FieldOffset(0xb8)]
+ public ulong Fp;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xc0)]
+ public ulong S0;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xc8)]
+ public ulong S1;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xd0)]
+ public ulong S2;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xd8)]
+ public ulong S3;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xe0)]
+ public ulong S4;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xe8)]
+ public ulong S5;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xf0)]
+ public ulong S6;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0xf8)]
+ public ulong S7;
+
+ [Register(RegisterType.General)]
+ [FieldOffset(0x100)]
+ public ulong S8;
+
+ #endregion
+
+ #region Control Registers
+
+ [Register(RegisterType.Control | RegisterType.ProgramCounter)]
+ [FieldOffset(0x108)]
+ 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 Fcc;
+
+ [Register(RegisterType.FloatingPoint)]
+ [FieldOffset(0x214)]
+ public uint Fcsr;
+
+ #endregion
+}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs
index 2a8fbd431f5e59..41134a2fd4c1d3 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs
@@ -190,6 +190,7 @@ private IPlatformFrameHandler GetFrameHandler(IPlatformAgnosticContext context)
ContextHolder contextHolder => new ARMFrameHandler(target, contextHolder),
ContextHolder contextHolder => new ARM64FrameHandler(target, contextHolder),
ContextHolder contextHolder => new RISCV64FrameHandler(target, contextHolder),
+ ContextHolder contextHolder => new LoongArch64FrameHandler(target, contextHolder),
_ => throw new InvalidOperationException("Unsupported context type"),
};
}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/LoongArch64FrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/LoongArch64FrameHandler.cs
new file mode 100644
index 00000000000000..9e565d18cc414b
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/LoongArch64FrameHandler.cs
@@ -0,0 +1,39 @@
+// 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.LoongArch64Context;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
+
+internal class LoongArch64FrameHandler(Target target, ContextHolder contextHolder) : BaseFrameHandler(target, contextHolder), IPlatformFrameHandler
+{
+ private readonly ContextHolder _holder = contextHolder;
+
+ public void HandleHijackFrame(HijackFrame frame)
+ {
+ HijackArgs args = _target.ProcessedData.GetOrAdd(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 LoongArch64)");
+ // 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);
+ }
+
+ public override void HandleFaultingExceptionFrame(FaultingExceptionFrame frame)
+ {
+ base.HandleFaultingExceptionFrame(frame);
+
+ // Clear the CONTEXT_UNWOUND_TO_CALL flag
+ const uint CONTEXT_UNWOUND_TO_CALL = (uint)ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL;
+ _holder.Context.ContextFlags &= ~CONTEXT_UNWOUND_TO_CALL;
+ }
+}