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; + } +}