diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64/ARM64Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64/ARM64Unwinder.cs new file mode 100644 index 00000000000000..4f102c4a1931ff --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64/ARM64Unwinder.cs @@ -0,0 +1,1254 @@ +// 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.ARM64Context; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.ARM64; + +internal class ARM64Unwinder(Target target) +{ + #region Constants + + /// + /// This table describes the size of each unwind code, in bytes, for unwind codes + /// in the range 0xE0-0xFF. + /// + private static ReadOnlySpan UnwindCodeSizeTable => + [ + 4, 1, 2, 1, 1, 1, 1, 3, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 3, 4, 5, 1, 1, 1, 1, + ]; + + /// + // This table describes the number of instructions represented by each unwind + // code in the range 0xE0-0xFF. + /// + private static ReadOnlySpan UnwindCodeInstructionCountTable => + [ + 1, 1, 1, 1, 1, 1, 1, 1, // 0xE0-0xE7 + 0, // 0xE8 - MSFT_OP_TRAP_FRAME + 0, // 0xE9 - MSFT_OP_MACHINE_FRAME + 0, // 0xEA - MSFT_OP_CONTEXT + 0, // 0xEB - MSFT_OP_EC_CONTEXT / MSFT_OP_RET_TO_GUEST (unused) + 0, // 0xEC - MSFT_OP_CLEAR_UNWOUND_TO_CALL + 0, // 0XED - MSFT_OP_RET_TO_GUEST_LEAF (unused) + 0, 0, // 0xEE-0xEF + 0, 0, 0, 0, 0, 0, 0, 0, // 0xF0-0xF7 + 1, 1, 1, 1, 1, 1, 1, 1, // 0xF8-0xFF + ]; + + #endregion + + private readonly Target _target = target; + private readonly IExecutionManager _eman = target.Contracts.ExecutionManager; + + #region Entrypoint + + public bool Unwind(ref ARM64Context 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 RtlVirtualUnwind 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 ARM64Context 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. + + bool status = true; + uint controlPcRva; + uint unwindType = functionEntry.UnwindData & 0x3u; + + // + // Unwind type 3 refers to a chained record. The top 30 bits of the + // unwind data contains the RVA of the parent pdata record. + // + if (unwindType == 3) + { + if ((functionEntry.UnwindData & 4) == 0) + { + functionEntry = _target.ProcessedData.GetOrAdd(imageBase + functionEntry.UnwindData - 3); + unwindType = functionEntry.UnwindData & 3u; + + UnwinderAssert(unwindType != 3); + + controlPcRva = functionEntry.BeginAddress; + + } + else + { + // unsupported version + return false; + } + + } + else + { + controlPcRva = (uint)(context.Pc - imageBase); + } + + // + // Identify the compact .pdata format versus the full .pdata+.xdata format. + // + if (unwindType != 0) + { + // managed code does not use compact .pdata format. + UnwinderAssert(false, "Compact .pdata format is not currently supported."); + } + else + { + + status = VirtualUnwindFull(ref context, controlPcRva, imageBase, functionEntry); + } + + return status; + } + + private bool VirtualUnwindFull( + ref ARM64Context 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 LR, then copying + // that LR to the PC. However, some special opcodes require different + // behavior. + // + bool finalPcFromLr = 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 & 0x3ffff; + 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, isEpilog: false); + + 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, isEpilog: true); + 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 & 0x3ffff; + if (offsetInFunction < scopeStart) + break; + + unwindIndex = headerWord >> 22; + if (offsetInFunction < scopeStart + (4 * unwindWords - unwindIndex)) + { + scopeSize = ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr, isEpilog: true); + + 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 (OPCODE_IS_END(curCode)) + break; + + unwindCodePtr += GetUnwindCodeSize(curCode); + skipWords--; + } + + // + // Now execute codes until we hit the end. + // + bool status = true; + uint accumulatedSaveNexts = 0; + while (unwindCodePtr < unwindCodesEndPtr && status) + { + byte curCode = _target.Read(unwindCodePtr); + unwindCodePtr += 1; + + // + // 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 * (curCode & 0x1fu); + } + + // + // save_r19r20_x (001zzzzz): save pair at [sp-#Z*8]!, pre-indexed offset >= -248 + // + else if (curCode <= 0x3f) + { + status = RestoreRegisterRange( + ref context, + -8 * (curCode & 0x1f), + 19, + 2 + (2 * accumulatedSaveNexts)); + accumulatedSaveNexts = 0; + } + + // + // save_fplr (01zzzzzz): save pair at [sp+#Z*8], offset <= 504 + // + else if (curCode <= 0x7f) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + status = RestoreRegisterRange( + ref context, + 8 * (curCode & 0x3f), + 29, + 2); + } + + // + // save_fplr_x (10zzzzzz): save pair at [sp-(#Z+1)*8]!, pre-indexed offset >= -512 + // + else if (curCode <= 0xbf) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + status = RestoreRegisterRange( + ref context, + -8 * ((curCode & 0x3f) + 1), + 29, + 2); + } + + // + // alloc_m (11000xxx|xxxxxxxx): allocate large stack with size < 32k (2^11 * 16). + // + else if (curCode <= 0xc7) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + context.Sp += 16u * ((curCode & 7u) << 8); + context.Sp += 16u * _target.Read(unwindCodePtr); + unwindCodePtr++; + } + + // + // save_regp (110010xx|xxzzzzzz): save r(19+#X) pair at [sp+#Z*8], offset <= 504 + // + else if (curCode <= 0xcb) + { + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreRegisterRange( + ref context, + 8 * (nextCode & 0x3f), + 19u + ((curCode & 3u) << 2) + (uint)(nextCode >>> 6), + 2 + (2 * accumulatedSaveNexts)); + accumulatedSaveNexts = 0; + } + + // + // save_regp_x (110011xx|xxzzzzzz): save pair r(19+#X) at [sp-(#Z+1)*8]!, pre-indexed offset >= -512 + // + else if (curCode <= 0xcf) + { + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreRegisterRange( + ref context, + -8 * ((nextCode & 0x3f) + 1), + 19u + ((curCode & 3u) << 2) + (uint)(nextCode >>> 6), + 2 + (2 * accumulatedSaveNexts)); + accumulatedSaveNexts = 0; + } + + // + // save_reg (110100xx|xxzzzzzz): save reg r(19+#X) at [sp+#Z*8], offset <= 504 + // + else if (curCode <= 0xd3) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreRegisterRange( + ref context, + 8 * (nextCode & 0x3f), + 19u + ((curCode & 3u) << 2) + (uint)(nextCode >> 6), + 1); + } + + // + // save_reg_x (1101010x|xxxzzzzz): save reg r(19+#X) at [sp-(#Z+1)*8]!, pre-indexed offset >= -256 + // + else if (curCode <= 0xd5) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreRegisterRange( + ref context, + -8 * ((nextCode & 0x1f) + 1), + 19u + ((curCode & 1u) << 3) + (uint)(nextCode >>> 5), + 1); + } + + // + // save_lrpair (1101011x|xxzzzzzz): save pair at [sp+#Z*8], offset <= 504 + // + else if (curCode <= 0xd7) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreRegisterRange( + ref context, + 8 * (nextCode & 0x3f), + 19u + 2 * (((curCode & 1u) << 2) + (uint)(nextCode >>> 6)), + 1); + if (status) + { + RestoreRegisterRange( + ref context, + 8 * (nextCode & 0x3f) + 8, + 30, + 1); + } + } + + // + // save_fregp (1101100x|xxzzzzzz): save pair d(8+#X) at [sp+#Z*8], offset <= 504 + // + else if (curCode <= 0xd9) + { + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreFpRegisterRange( + ref context, + 8 * (nextCode & 0x3f), + 8u + ((curCode & 1u) << 2) + (uint)(nextCode >>> 6), + 2 + (2 * accumulatedSaveNexts)); + accumulatedSaveNexts = 0; + } + + // + // save_fregp_x (1101101x|xxzzzzzz): save pair d(8+#X), at [sp-(#Z+1)*8]!, pre-indexed offset >= -512 + // + else if (curCode <= 0xdb) + { + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreFpRegisterRange( + ref context, + -8 * ((nextCode & 0x3f) + 1), + 8u + ((curCode & 1u) << 2) + (uint)(nextCode >>> 6), + 2 + (2 * accumulatedSaveNexts)); + accumulatedSaveNexts = 0; + } + + // + // save_freg (1101110x|xxzzzzzz): save reg d(9+#X) at [sp+#Z*8], offset <= 504 + // + else if (curCode <= 0xdd) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreFpRegisterRange( + ref context, + 8 * (nextCode & 0x3f), + 8u + ((curCode & 1u) << 2) + (uint)(nextCode >>> 6), + 1); + } + + // + // save_freg_x (11011110|xxxzzzzz): save reg d(8+#X) at [sp-(#Z+1)*8]!, pre-indexed offset >= -256 + // + else if (curCode == 0xde) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + byte nextCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = RestoreFpRegisterRange( + ref context, + -8 * ((nextCode & 0x1f) + 1), + 8 + (uint)(nextCode >>> 5), + 1); + } + + // + // alloc_l (11100000|xxxxxxxx|xxxxxxxx|xxxxxxxx): allocate large stack with size < 256M + // + else if (curCode == 0xe0) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + context.Sp += 16u * ((uint)_target.Read(unwindCodePtr) << 16); + unwindCodePtr++; + context.Sp += 16 * ((uint)_target.Read(unwindCodePtr) << 8); + unwindCodePtr++; + context.Sp += 16 * (uint)_target.Read(unwindCodePtr); + unwindCodePtr++; + } + + // + // set_fp (11100001): set up r29: with: mov r29,sp + // + else if (curCode == 0xe1) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + context.Sp = context.Fp; + } + + // + // add_fp (11100010|xxxxxxxx): set up r29 with: add r29,sp,#x*8 + // + else if (curCode == 0xe2) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + context.Sp = context.Fp - 8u * _target.Read(unwindCodePtr); + unwindCodePtr++; + } + + // + // 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; + } + + break; + } + + // + // end_c (11100101): end of unwind code in current chained scope. + // Continue unwinding parent scope. + // + else if (curCode == 0xe5) + { + // no-op + } + + // + // save_next_pair (11100110): save next non-volatile Int or FP register pair. + // + else if (curCode == 0xe6) + { + accumulatedSaveNexts += 1; + } + + // + // 11100111 ' 0pxrrrrr ' ffoooooo + // p: 0/1 - single/pair + // x: 0/1 - positive offset / negative offset with writeback + // r: register number + // f: 00/01/10 - X / D / Q + // o: offset * 16 for x=1 or p=1 or f=Q / else offset * 8 + // + else if (curCode == 0xe7) + { + byte val2 = _target.Read(unwindCodePtr); + unwindCodePtr += 1; + byte val1 = _target.Read(unwindCodePtr); + unwindCodePtr += 1; + SaveAnyUnwindCode op = new SaveAnyUnwindCode(val1, val2); + + // + // save_next_pair only permited for pairs. + // + if ((op.p == 0) && accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + + if (op.fixedOp != 0) + { + // invalid sequence + return false; + } + + int spOffset = op.o + op.x; + spOffset *= ((op.x == 1) || (op.f == 2) || (op.p == 1)) ? 16 : 8; + spOffset *= op.x == 1 ? -1 : 1; + uint regCount = 1u + op.p + (2u * accumulatedSaveNexts); + switch (op.f) + { + case 0: + status = RestoreRegisterRange( + ref context, + spOffset, + op.r, + regCount); + break; + + case 1: + status = RestoreFpRegisterRange( + ref context, + spOffset, + op.r, + regCount); + break; + + case 2: + status = RestoreSimdRegisterRange( + ref context, + spOffset, + op.r, + regCount); + break; + + default: + // invalid sequence + return false; + } + + accumulatedSaveNexts = 0; + } + + // + // custom_0 (111010xx): restore custom structure + // + else if (curCode >= 0xe8 && curCode <= 0xec) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + + status = UnwindCustom(ref context, curCode); + finalPcFromLr = false; + } + + // + // pac (11111100): function has pointer authentication + // + else if (curCode == 0xfc) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + + // + // TODO: Implement support for UnwindFlags RTL_VIRTUAL_UNWIND2_VALIDATE_PAC. + // + } + + // + // future/nop: the following ranges represent encodings reserved for + // future extension. They are treated as a nop and, therefore, no + // unwind action is taken. + // + // 11111000|yyyyyyyy + // 11111001|yyyyyyyy|yyyyyyyy + // 11111010|yyyyyyyy|yyyyyyyy|yyyyyyyy + // 11111011|yyyyyyyy|yyyyyyyy|yyyyyyyy|yyyyyyyy + // 111111xx + // + else if (curCode >= 0xf8) + { + if (accumulatedSaveNexts != 0) + { + // invalid sequence + return false; + } + + if (curCode <= 0xfb) + { + unwindCodePtr += 1u + (curCode & 0x3u); + } + } + + // + // Anything else is invalid + // + else + { + // invalid sequence + return false; + } + } + + // + // If we succeeded, post-process the results a bit + // + if (status) + { + + // + // Since we always POP to the LR, recover the final PC from there, unless + // it was overwritten due to a special case custom unwinding operation. + // Also set the establisher frame equal to the final stack pointer. + // + if (finalPcFromLr) + { + context.Pc = context.Lr; + } + } + + return status; + } + + private unsafe bool UnwindCustom( + ref ARM64Context context, + byte customCode) + { + ulong startingSp = context.Sp; + + switch (customCode) + { + // + // Trap frame case + // + case 0xE8: // MSFT_OP_TRAP_FRAME: + { + // + // Restore X0-X18, and D0-D7 + // + TargetPointer sourceAddress = startingSp + /*offsetof(ARM64_KTRAP_FRAME, X)*/ 0x0A0; + for (uint regIndex = 0; regIndex < 19; regIndex++) + { + SetRegister(ref context, regIndex, _target.Read(sourceAddress)); + sourceAddress += sizeof(ulong); + } + + sourceAddress = startingSp + /*offsetof(ARM64_KTRAP_FRAME, VfpState)*/ 0x010; + TargetPointer vfpStateAddress = _target.Read(sourceAddress); + if (vfpStateAddress != 0) + { + sourceAddress = vfpStateAddress + /*offsetof(KARM64_VFP_STATE, Fpcr)*/ 0x08; + uint Fpcr = _target.Read(sourceAddress); + sourceAddress = vfpStateAddress + /*offsetof(KARM64_VFP_STATE, Fpcr)*/ 0x0C; + uint Fpsr = _target.Read(sourceAddress); + if (Fpcr != uint.MaxValue && Fpsr != uint.MaxValue) + { + context.Fpcr = Fpcr; + context.Fpsr = Fpsr; + + sourceAddress = vfpStateAddress + /*offsetof(KARM64_VFP_STATE, V)*/ 0x10; + for (uint regIndex = 0; regIndex < 32; regIndex++) + { + context.V[regIndex * 2] = _target.Read(sourceAddress); + context.V[(regIndex * 2) + 1] = _target.Read(sourceAddress + 8); + sourceAddress += 2 * sizeof(ulong); + } + } + } + + // + // Restore R11, R12, SP, LR, PC, and the status registers + // + sourceAddress = startingSp + /*offsetof(ARM64_KTRAP_FRAME, Spsr)*/ 0x090; + context.Cpsr = _target.Read(sourceAddress); + + sourceAddress = startingSp + /*offsetof(ARM64_KTRAP_FRAME, Sp)*/ 0x098; + context.Sp = _target.Read(sourceAddress); + + sourceAddress = startingSp + /*offsetof(ARM64_KTRAP_FRAME, Lr)*/ 0x138; + context.Lr = _target.Read(sourceAddress); + + sourceAddress = startingSp + /*offsetof(ARM64_KTRAP_FRAME, Fp)*/ 0x140; + context.Fp = _target.Read(sourceAddress); + + sourceAddress = startingSp + /*offsetof(ARM64_KTRAP_FRAME, Pc)*/ 0x148; + context.Pc = _target.Read(sourceAddress); + + // + // Clear the unwound-to-call flag + // + context.ContextFlags &= (uint)~ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL; + break; + } + + // + // Machine frame case + // + case 0xE9: // MSFT_OP_MACHINE_FRAME: + { + // + // Restore the SP and PC, and clear the unwound-to-call flag + // + context.Sp = _target.Read(startingSp + 0); + context.Pc = _target.Read(startingSp + 8); + context.ContextFlags &= (uint)~ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL; + break; + } + + // + // Context case + // + case 0xEA: // MSFT_OP_CONTEXT: + { + // + // Restore X0-X28, and D0-D31 + // + TargetPointer sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.X0)); + for (uint regIndex = 0; regIndex < 29; regIndex++) + { + SetRegister(ref context, regIndex, _target.Read(sourceAddress)); + sourceAddress += sizeof(ulong); + } + + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.V)); + for (uint regIndex = 0; regIndex < 32; regIndex++) + { + context.V[regIndex * 2] = _target.Read(sourceAddress); + context.V[(regIndex * 2) + 1] = _target.Read(sourceAddress + 8); + sourceAddress += 2 * sizeof(ulong); + } + + // + // Restore SP, LR, PC, and the status registers + // + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.Cpsr)); + context.Cpsr = _target.Read(sourceAddress); + + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.Fp)); + context.Fp = _target.Read(sourceAddress); + + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.Lr)); + context.Lr = _target.Read(sourceAddress); + + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.Sp)); + context.Sp = _target.Read(sourceAddress); + + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.Pc)); + context.Pc = _target.Read(sourceAddress); + + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.Fpcr)); + context.Fpcr = _target.Read(sourceAddress); + + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.Fpsr)); + context.Fpsr = _target.Read(sourceAddress); + + // + // Inherit the unwound-to-call flag from this context + // + sourceAddress = startingSp + (uint)Marshal.OffsetOf(nameof(ARM64Context.ContextFlags)); + context.ContextFlags &= (uint)~ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL; + context.ContextFlags |= + _target.Read(sourceAddress) & (uint)ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL; + break; + } + + case 0xEB: // MSFT_OP_EC_CONTEXT: + // NOTE: for .NET, the arm64ec context restoring is not implemented + UnwinderAssert(false); + return false; + + case 0xec: // MSFT_OP_CLEAR_UNWOUND_TO_CALL + context.ContextFlags &= (uint)~ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL; + context.Pc = context.Lr; + break; + + default: + return false; + } + + return true; + } + + #endregion + #region Helpers + + private uint ComputeScopeSize( + TargetPointer unwindCodePtr, + TargetPointer unwindCodesEndPtr, + bool isEpilog) + { + uint scopeSize = 0; + byte opcode; + + // + // Iterate through the unwind codes until we hit an end marker. + // While iterating, accumulate the total scope size. + // + + while (unwindCodePtr < unwindCodesEndPtr) + { + opcode = _target.Read(unwindCodePtr); + if (OPCODE_IS_END(opcode)) + break; + + unwindCodePtr += GetUnwindCodeSize(opcode); + scopeSize += GetUnwindCodeScopeSize(opcode); + } + + // + // Epilogs have one extra instruction at the end that needs to be + // accounted for. + // + if (isEpilog) + scopeSize++; + + return scopeSize; + } + + private static uint GetUnwindCodeSize(byte unwindCode) + { + if (unwindCode < 0xC0) + return 1; + + if (unwindCode < 0xE0) + return 2; + + return UnwindCodeSizeTable[unwindCode - 0xE0]; + } + + private static uint GetUnwindCodeScopeSize(byte unwindCode) + { + if (unwindCode < 0xE0) + return 1; + + return UnwindCodeInstructionCountTable[unwindCode - 0xE0]; + } + + /// + /// Restores a series of integer registers from the stack. + /// + /// ref of the context. + /// + /// Specifies a stack offset. Positive values are simply used + /// as a base offset. Negative values assume a pre-decrement behavior: + /// a 0 offset is used for restoration, but the absolute value of the + /// offset is added to the final Sp. + /// + /// Specifies the index of the first register to restore. + /// Specifies the number of registers to restore. + private bool RestoreRegisterRange( + ref ARM64Context context, + int spOffset, + uint firstRegister /* in range (0, 30) */, + uint registerCount /* in range (1, 31 - firstRegister) */) + { + if (firstRegister + registerCount > 31) + { + // invalid register range + return false; + } + + // + // Compute the source address + // + TargetPointer curAddress = context.Sp; + if (spOffset >= 0) + { + curAddress += (uint)spOffset; + } + + // + // Restore the registers + // + for (uint regIndex = 0; regIndex < registerCount; regIndex++) + { + SetRegister(ref context, firstRegister + regIndex, _target.Read(curAddress)); + curAddress += 8; + } + if (spOffset < 0) + { + context.Sp -= (ulong)spOffset; + } + + return true; + } + + /// + /// Restores a series of floating-point registers from the stack. + /// + /// ref of the context. + /// + /// Specifies a stack offset. Positive values are simply used + /// as a base offset. Negative values assume a pre-decrement behavior: + /// a 0 offset is used for restoration, but the absolute value of the + /// offset is added to the final Sp. + /// + /// Specifies the index of the first register to restore. + /// Specifies the number of registers to restore. + private unsafe bool RestoreFpRegisterRange( + ref ARM64Context context, + int spOffset, + uint firstRegister, + uint registerCount) + { + if (firstRegister + registerCount > 32) + { + // invalid register range + return false; + } + + // + // Compute the source address + // + TargetPointer curAddress = context.Sp; + if (spOffset >= 0) + { + curAddress += (uint)spOffset; + } + + // + // Restore the registers + // + for (uint regIndex = 0; regIndex < registerCount; regIndex++) + { + // double register values to only index into the low 64 bits of each 128-bit register + context.V[(firstRegister + regIndex) * 2] = _target.Read(curAddress); + curAddress += 8; + } + if (spOffset < 0) + { + context.Sp -= (ulong)spOffset; + } + + return true; + } + + /// + /// Restores a series of full SIMD (Q) registers from the stack. + /// + /// ref of the context. + /// + /// Specifies a stack offset. Positive values are simply used + /// as a base offset. Negative values assume a pre-decrement behavior: + /// a 0 offset is used for restoration, but the absolute value of the + /// offset is added to the final Sp. + /// + /// Specifies the index of the first register to restore. + /// Specifies the number of registers to restore. + private unsafe bool RestoreSimdRegisterRange( + ref ARM64Context context, + int spOffset, + uint firstRegister, + uint registerCount) + { + if (firstRegister + registerCount > 32) + { + // invalid register range + return false; + } + + // + // Compute the source address + // + TargetPointer curAddress = context.Sp; + if (spOffset >= 0) + { + curAddress += (uint)spOffset; + } + + // + // Restore the registers + // + for (uint regIndex = 0; regIndex < registerCount; regIndex++) + { + // V indexes are 64-bit values of the 128-bit registers + // double register values to write the low 64 bits of the 128-bit register + context.V[(firstRegister + regIndex) * 2] = _target.Read(curAddress); + curAddress += 8; + // double register values + 1 to write the high 64 bits of the 128-bit register + context.V[((firstRegister + regIndex) * 2) + 1] = _target.Read(curAddress); + curAddress += 8; + } + if (spOffset < 0) + { + context.Sp -= (ulong)spOffset; + } + + return true; + } + + private static void SetRegister(ref ARM64Context context, uint regIndex, ulong value) + { + switch (regIndex) + { + case 0: context.X0 = value; break; + case 1: context.X1 = value; break; + case 2: context.X2 = value; break; + case 3: context.X3 = value; break; + case 4: context.X4 = value; break; + case 5: context.X5 = value; break; + case 6: context.X6 = value; break; + case 7: context.X7 = value; break; + case 8: context.X8 = value; break; + case 9: context.X9 = value; break; + case 10: context.X10 = value; break; + case 11: context.X11 = value; break; + case 12: context.X12 = value; break; + case 13: context.X13 = value; break; + case 14: context.X14 = value; break; + case 15: context.X15 = value; break; + case 16: context.X16 = value; break; + case 17: context.X17 = value; break; + case 18: context.X18 = value; break; + case 19: context.X19 = value; break; + case 20: context.X20 = value; break; + case 21: context.X21 = value; break; + case 22: context.X22 = value; break; + case 23: context.X23 = value; break; + case 24: context.X24 = value; break; + case 25: context.X25 = value; break; + case 26: context.X26 = value; break; + case 27: context.X27 = value; break; + case 28: context.X28 = value; break; + case 29: context.Fp = value; break; + case 30: context.Lr = value; break; + default: throw new ArgumentOutOfRangeException(nameof(regIndex)); + } + } + + private static bool OPCODE_IS_END(byte opcode) + { + return (opcode & 0xFE) == 0xE4; + } + + private static void UnwinderAssert([DoesNotReturnIf(false)] bool condition, string? message = null) + { + if (!condition) + { + throw new InvalidOperationException(message); + } + } + + #endregion + #region Structs + + private struct SaveAnyUnwindCode(byte val1, byte val2) + { + public byte o = (byte)(val1 & 0x3f); + public byte f = (byte)(val1 >> 6); + public byte r = (byte)(val2 & 0x1f); + public byte x = (byte)((val1 >> 5) & 0x1); + public byte p = (byte)((val1 >> 6) & 0x1); + public byte fixedOp = (byte)((val1 >> 7) & 0x1); + } + + #endregion +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs index 5ab9258755d00d..3d0fe1c2cc69b6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.ARM64; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; @@ -25,6 +26,13 @@ public enum ContextFlagsValues : uint CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18, + // + // 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, } @@ -50,7 +58,8 @@ public TargetPointer FramePointer public void Unwind(Target target) { - Unwinder.ARM64Unwind(ref this, target); + ARM64Unwinder unwinder = new(target); + unwinder.Unwind(ref this); } // Control flags