From 12ebe345c36088278850dff0729469d2575226d9 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:03:00 -0400 Subject: [PATCH 01/20] initial work --- .../StackWalk/Context/ARM/ARMUnwinder.cs | 967 ++++++++++++++++++ .../StackWalk/Context/ARM/LookupValues.cs | 245 +++++ .../Contracts/StackWalk/Context/ARMContext.cs | 250 +++++ .../Context/IPlatformAgnosticContext.cs | 1 + .../Contracts/StackWalk/Context/M128.cs | 30 + .../FrameHandling/ARMFrameHandler.cs | 30 + .../StackWalk/FrameHandling/FrameIterator.cs | 1 + .../Legacy/ClrDataStackWalk.cs | 6 +- src/native/managed/compile-native.proj | 2 +- 9 files changed, 1528 insertions(+), 4 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/LookupValues.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs new file mode 100644 index 00000000000000..597eafd2e02c04 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs @@ -0,0 +1,967 @@ +// 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 static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.ARM.LookupValues; +using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.ARMContext; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.ARM; + +internal class ARMUnwinder(Target target) +{ + private const uint MAX_PROLOG_SIZE = 16; + private const uint MAX_EPILOG_SIZE = 16; + + private readonly Target _target = target; + private readonly IExecutionManager _eman = target.Contracts.ExecutionManager; + + #region Entrypoint + + public bool Unwind(ref ARMContext context) + { + if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh) + { + throw new InvalidOperationException("Unwind failed, unable to find code block for the instruction pointer."); + } + + uint startingPc = context.Pc; + uint startingSp = context.Sp; + + TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); + Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh)); + if (functionEntry.EndAddress is null) + return false; + + if ((functionEntry.UnwindData & 0x3) != 0) + { + if (!UnwindCompact(ref context, imageBase, functionEntry)) + return false; + } + else + { + if (!UnwindFull(ref context, imageBase, functionEntry)) + return false; + } + + // PC == 0 means unwinding is finished. + // Same if no forward progress is made + if (context.Pc == 0 || (context.Pc == startingPc && context.Sp == startingSp)) + return false; + + return true; + } + + #endregion + #region Unwind Logic + + private bool UnwindFull( + ref ARMContext context, + TargetPointer imageBase, + Data.RuntimeFunction functionEntry) + { + uint controlPcRva = (uint)(context.Pc - imageBase.Value); + bool status = true; + + // + // Unless we encounter a special frame, 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; + + // + // 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) + return false; + + uint functionLength = headerWord & 0x3ffff; + uint offsetInFunction = (controlPcRva - (functionEntry.BeginAddress & ~1u)) / 2u; + + if (offsetInFunction >= functionLength) + return false; + + // + // Determine the number of epilog scope records and the maximum number + // of unwind codes. + // + + uint unwindWords = (headerWord >> 28) & 15; + uint epilogScopeCount = (headerWord >> 23) & 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; + } + + // + // 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 skipHalfwords = 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. + // + + if (offsetInFunction < MAX_PROLOG_SIZE && ((headerWord & (1 << 22)) == 0)) + { + uint scopeSize = ComputeScopeSize(unwindCodePtr, unwindCodesEndPtr, isEpilog: false); + + if (offsetInFunction < scopeSize) + { + skipHalfwords = scopeSize - offsetInFunction; + } + } + + // + // 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. + // + if (skipHalfwords != 0) + { + // We found the prolog above, do nothing here. + } + else if ((headerWord & (1 << 21)) != 0) + { + if (offsetInFunction + MAX_EPILOG_SIZE >= functionLength) + { + uint scopeSize = ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr, isEpilog: true); + uint scopeStart = functionLength - scopeSize; + + if (offsetInFunction >= scopeStart) + { + unwindCodePtr += unwindIndex; + skipHalfwords = 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; + } + + if (offsetInFunction < scopeStart + MAX_EPILOG_SIZE) + { + unwindIndex = headerWord >> 24; + uint scopeSize = ComputeScopeSize(unwindCodePtr + unwindIndex, unwindCodesEndPtr, isEpilog: true); + + if (CheckCondition(ref context, headerWord >> 20) && + offsetInFunction < scopeStart + scopeSize) + { + + unwindCodePtr += unwindIndex; + skipHalfwords = offsetInFunction - scopeStart; + break; + } + } + } + } + + // + // Skip over unwind codes until we account for the number of halfwords + // to skip. + // + while (unwindCodePtr < unwindCodesEndPtr && skipHalfwords > 0) + { + uint curCode = _target.Read(unwindCodePtr); + if (curCode >= 0xfd) + break; + + byte tableValue = UnwindOpTable[(int)curCode]; + skipHalfwords -= (uint)tableValue >> 4; + unwindCodePtr += tableValue & 0xfu; + } + + // + // Now execute codes until we hit the end. + // + while (unwindCodePtr < unwindCodesEndPtr && status) + { + + byte curCode = _target.Read(unwindCodePtr); + unwindCodePtr++; + + // + // 0x00-0x7f: 2-byte stack adjust ... add sp, sp, #0xval + // + if (curCode < 0x80) + { + context.Sp += (curCode & 0x7fu) * 4u; + } + + // + // 0x80-0xbf: 4-byte bitmasked pop ... pop {r0-r12, lr} + // + else if (curCode < 0xc0) + { + if (unwindCodePtr >= unwindCodesEndPtr) + { + status = false; + } + else + { + uint param = (uint)(((curCode & 0x20) << 9) | + ((curCode & 0x1f) << 8) | + _target.Read(unwindCodePtr)); + unwindCodePtr++; + status = PopRegisterMask(ref context, (ushort)param); + } + } + + // + // 0xc0-0xcf: 2-byte stack restore ... mov sp, rX + // + else if (curCode < 0xd0) + { + context.Sp = GetRegister(ref context, curCode & 0x0f); + } + + else + { + uint param; + switch (curCode) + { + + // + // 0xd0-0xd7: 2-byte range pop ... pop {r4-r7, lr} + // + + case 0xd0: + case 0xd1: + case 0xd2: + case 0xd3: + case 0xd4: + case 0xd5: + case 0xd6: + case 0xd7: + status = PopRegisterMask( + ref context, + RangeToMask(4u, 4u + (curCode & 3u), curCode & 4u)); + break; + + // + // 0xd8-0xdf: 4-byte range pop ... pop {r4-r11, lr} + // + + case 0xd8: + case 0xd9: + case 0xda: + case 0xdb: + case 0xdc: + case 0xdd: + case 0xde: + case 0xdf: + status = PopRegisterMask( + ref context, + RangeToMask(4u, 8u + (curCode & 3u), curCode & 4u)); + break; + + // + // 0xe0-0xe7: 4-byte range vpop ... vpop {d8-d15} + // + + case 0xe0: + case 0xe1: + case 0xe2: + case 0xe3: + case 0xe4: + case 0xe5: + case 0xe6: + case 0xe7: + status = PopVfpRegisterRange( + ref context, + 8u, 8u + (curCode & 0x07u)); + break; + + // + // 0xe8-0xeb: 4-byte stack adjust ... addw sp, sp, #0xval + // + + case 0xe8: + case 0xe9: + case 0xea: + case 0xeb: + if (unwindCodePtr >= unwindCodesEndPtr) + { + status = false; + break; + } + context.Sp += 4u * 256u * (curCode & 3u); + context.Sp += 4u * _target.Read(unwindCodePtr); + unwindCodePtr++; + break; + + // + // 0xec-0xed: 2-byte bitmasked pop ... pop {r0-r7,lr} + // + + case 0xec: + case 0xed: + if (unwindCodePtr >= unwindCodesEndPtr) + { + status = false; + break; + } + status = PopRegisterMask( + ref context, + (ushort)(_target.Read(unwindCodePtr) | ((curCode << 14) & 0x4000u))); + unwindCodePtr++; + break; + + // + // 0xee: 0-byte custom opcode + // + + case 0xee: + if (unwindCodePtr >= unwindCodesEndPtr) + { + status = false; + break; + } + param = _target.Read(unwindCodePtr); + unwindCodePtr++; + if ((param & 0xf0) == 0x00) + { + status = UnwindCustom( + ref context, + (byte)(param & 0x0f)); + } + else + { + status = false; + } + break; + + // + // 0xef: 4-byte stack restore with post-increment ... ldr pc, [sp], #X + // + case 0xef: + if (unwindCodePtr >= unwindCodesEndPtr) + { + status = false; + break; + } + param = _target.Read(unwindCodePtr); + unwindCodePtr++; + if ((param & 0xf0) == 0x00) + { + status = PopRegisterMask( + ref context, + 0x4000); + context.Sp += ((param & 15) - 1) * 4; + } + else + { + status = false; + } + break; + + // + // 0xf5: 4-byte range vpop ... vpop {d0-d15} + // + case 0xf5: + if (unwindCodePtr >= unwindCodesEndPtr) + { + status = false; + break; + } + param = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = PopVfpRegisterRange( + ref context, + param >> 4, param & 0x0f); + break; + + // + // 0xf6: 4-byte range vpop ... vpop {d16-d31} + // + case 0xf6: + if (unwindCodePtr >= unwindCodesEndPtr) + { + status = false; + break; + } + param = _target.Read(unwindCodePtr); + unwindCodePtr++; + status = PopVfpRegisterRange( + ref context, + 16 + (param >> 4), 16 + (param & 0x0f)); + break; + + // + // 0xf7: 2-byte stack adjust ... add sp, sp, + // 0xf9: 4-byte stack adjust ... add sp, sp, + // + case 0xf7: + case 0xf9: + if (unwindCodePtr + 2 > unwindCodesEndPtr) + { + status = false; + break; + } + context.Sp += 4u * 256u * _target.Read(unwindCodePtr); + context.Sp += 4u * _target.Read(unwindCodePtr + 1); + unwindCodePtr += 2; + break; + + // + // 0xf8: 2-byte stack adjust ... add sp, sp, + // 0xfa: 4-byte stack adjust ... add sp, sp, + // + case 0xf8: + case 0xfa: + if (unwindCodePtr + 3 > unwindCodesEndPtr) + { + status = false; + break; + } + context.Sp += 4u * 256u * 256u * _target.Read(unwindCodePtr); + context.Sp += 4u * 256u * _target.Read(unwindCodePtr + 1); + context.Sp += 4u * _target.Read(unwindCodePtr + 2); + unwindCodePtr += 3; + break; + + // + // 0xfb: 2-byte no-op/misc instruction + // 0xfc: 4-byte no-op/misc instruction + // + case 0xfb: + case 0xfc: + break; + + // + // 0xfd: 2-byte end (epilog) + // 0xfe: 4-byte end (epilog) + // 0xff: generic end + // + case 0xfd: + case 0xfe: + case 0xff: + break; + + default: + status = false; + break; + } + } + } + + // + // 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 ((context.ContextFlags & (uint)ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL) != 0) + { + context.Pc = context.Lr; + } + } + + return status; + } + + private unsafe bool UnwindCustom( + ref ARMContext context, + byte opcode) + { + ARM_CONTEXT_OFFSETS offsets; + + // Determine which set of offsets to use + switch (opcode) + { + case 0: + offsets = TrapFrameOffsets; + break; + case 1: + offsets = MachineFrameOffsets; + break; + case 2: + offsets = ContextOffsets; + break; + default: + return false; + } + + // Handle general registers first + for (int regIndex = 0; regIndex < 13; regIndex++) + { + if (offsets.RegOffset[regIndex] != OFFSET_NONE) + { + TargetPointer sourceAddress = context.Sp + offsets.RegOffset[regIndex]; + SetRegister(ref context, regIndex, _target.Read(sourceAddress)); + } + } + + for (int fpRegIndex = 0; fpRegIndex < 32; fpRegIndex++) + { + if (offsets.FpRegOffset[fpRegIndex] != OFFSET_NONE) + { + TargetPointer sourceAddress = context.Sp + offsets.FpRegOffset[fpRegIndex]; + context.D[fpRegIndex] = _target.Read(sourceAddress); + } + } + + // Link register and PC next + if (offsets.LrOffset != OFFSET_NONE) + { + TargetPointer sourceAddress = context.Sp + offsets.LrOffset; + context.Lr = _target.Read(sourceAddress); + } + if (offsets.PcOffset != OFFSET_NONE) { + TargetPointer sourceAddress = context.Sp + offsets.PcOffset; + context.Pc = _target.Read(sourceAddress); + + // + // If we pull the PC out of one of these, this means we are not + // unwinding from a call, but rather from another frame. + // + context.ContextFlags &= ~(uint)ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL; + } + + // Finally the stack pointer + if (offsets.SpOffset != OFFSET_NONE) + { + TargetPointer sourceAddress = context.Sp + offsets.SpOffset; + context.Sp = _target.Read(sourceAddress); + } + else + { + context.Sp += offsets.TotalSize; + } + + return true; + } + + private bool UnwindCompact( + ref ARMContext context, + TargetPointer imageBase, + Data.RuntimeFunction functionEntry) + { + uint controlPcRva = (uint)(context.Pc - imageBase.Value); + bool status = true; + + // + // Compact records always describe an unwind to a call. + // + context.ContextFlags |= (uint)ContextFlagsValues.CONTEXT_UNWOUND_TO_CALL; + + // + // Extract the basic information about how to do a full unwind. + // + uint unwindData = functionEntry.UnwindData; + uint functionLength = (unwindData >> 2) & 0x7ff; + uint retBits = (unwindData >> 13) & 3; + uint hBit = (unwindData >> 15) & 1; + uint cBit = (unwindData >> 21) & 1; + uint stackAdjust = (unwindData >> 22) & 0x3ff; + + // + // Determine push/pop masks based on this information. This comes + // from a mix of the C, L, R, and Reg fields. + // + uint vfpSaveCount = RegisterMaskLookup[(int)((unwindData >> 16) & 0x3f)]; + uint pushMask = vfpSaveCount & 0xffff; + uint popMask = vfpSaveCount & 0xffff; + vfpSaveCount >>= 16; + + // + // If the stack adjustment is folded into the push/pop, encode this + // by setting one of the low 4 bits of the push/pop mask and recovering + // the actual stack adjustment. + // + if (stackAdjust >= 0x3f4) + { + pushMask |= stackAdjust & 4; + popMask |= stackAdjust & 8; + stackAdjust = (stackAdjust & 3) + 1; + } + + // + // If we're near the start of the function (within 9 halfwords), + // see if we are within the prolog. + // + // N.B. If the low 2 bits of the UnwindData are 2, then we have + // no prolog. + // + uint offsetInFunction = (controlPcRva - (functionEntry.BeginAddress & ~1u)) / 2; + uint offsetInScope = 0; + + uint computeFramePointerLength = 0; + uint pushPopParamsLength = 0; + uint pushPopFloatingPointLength = 0; + uint pushPopIntegerLength = 0; + uint stackAdjustLength = 0; + + if (offsetInFunction < 9 && (unwindData & 3) != 2) + { + + // + // Compute sizes for each opcode in the prolog. + // + pushPopParamsLength = (hBit != 0) ? 1u : 0u; + pushPopIntegerLength = (pushMask == 0) ? 0u : + ((pushMask & 0xbf00) == 0) ? 1u : 2u; + computeFramePointerLength = (cBit == 0) ? 0u : + ((pushMask & ~0x4800) == 0) ? 1u : 2u; + pushPopFloatingPointLength = (vfpSaveCount != 0) ? 2u : 0u; + stackAdjustLength = (stackAdjust == 0 || (pushMask & 4) != 0) ? 0u : + (stackAdjust < 0x80) ? 1u : 2u; + + // + // Compute the total prolog length and determine if we are within + // its scope. + // + // N.B. We must execute prolog operations backwards to unwind, so + // our final scope offset in this case is the distance from the end. + // + + uint prologLength = pushPopParamsLength + + pushPopIntegerLength + + computeFramePointerLength + + pushPopFloatingPointLength + + stackAdjustLength; + + if (offsetInFunction < prologLength) + { + offsetInScope = prologLength - offsetInFunction; + } + } + + // + // If we're near the end of the function (within 8 halfwords), see if + // we are within the epilog. + // + // N.B. If Ret == 3, then we have no epilog. + // + if (offsetInScope == 0 && offsetInFunction + 8 >= functionLength && retBits != 3) + { + + // + // Compute sizes for each opcode in the epilog. + // + stackAdjustLength = (stackAdjust == 0 || (popMask & 8) != 0) ? 0u : + (stackAdjust < 0x80) ? 1u : 2u; + pushPopFloatingPointLength = (vfpSaveCount != 0) ? 2u : 0u; + computeFramePointerLength = 0; + pushPopIntegerLength = (popMask == 0 || (hBit != 0 && retBits == 0 && popMask == 0x8000)) ? 0u : + ((popMask & 0x7f00) == 0) ? 1u : 2u; + pushPopParamsLength = (hBit == 0) ? 0 : (retBits == 0) ? 2u : 1u; + uint returnLength = retBits; + + // + // Compute the total epilog length and determine if we are within + // its scope. + // + + uint epilogLength = stackAdjustLength + + pushPopFloatingPointLength + + pushPopIntegerLength + + pushPopParamsLength + + returnLength; + + uint scopeStart = functionLength - epilogLength; + if (offsetInFunction > scopeStart) + { + offsetInScope = offsetInFunction - scopeStart; + pushMask = popMask & 0x1fff; + if (hBit == 0) + { + pushMask |= (popMask >> 1) & 0x4000; + } + } + } + + // + // Process operations backwards, in the order: stack deallocation, + // VFP register popping, integer register popping, parameter home + // area recovery. + // + // First case is simple: we process everything with no regard for + // the current offset within the scope. + // + if (offsetInScope == 0) + { + + context.Sp += 4 * stackAdjust; + if (vfpSaveCount != 0) + { + status = PopVfpRegisterRange(ref context, 8, 8 + vfpSaveCount - 1); + } + pushMask &= 0xfff0; + if (pushMask != 0) + { + status = PopRegisterMask(ref context, (ushort)pushMask); + } + if (hBit != 0) + { + context.Sp += 4 * 4; + } + } + + // + // Second case is more complex: we must step along each operation + // to ensure it should be executed. + // + + else + { + + uint currentOffset = 0; + if (currentOffset >= offsetInScope && stackAdjustLength != 0) + { + context.Sp += 4 * stackAdjust; + } + currentOffset += stackAdjustLength; + + if (currentOffset >= offsetInScope && pushPopFloatingPointLength != 0) + { + status = PopVfpRegisterRange(ref context, 8, 8 + vfpSaveCount - 1); + } + currentOffset += pushPopFloatingPointLength; + + // + // N.B. We don't need to undo any side effects of frame pointer linkage + // + + currentOffset += computeFramePointerLength; + + // + // N.B. In the epilog case above, we copied PopMask to PushMask + // + + if (currentOffset >= offsetInScope && pushPopIntegerLength != 0) + { + pushMask &= 0xfff0; + status = PopRegisterMask(ref context, (ushort)pushMask); + if (stackAdjustLength == 0) + { + context.Sp += 4 * stackAdjust; + } + } + currentOffset += pushPopIntegerLength; + + // + // N.B. In the epilog case, we also need to pop the return address + // + + if (currentOffset >= offsetInScope && pushPopParamsLength != 0) + { + if (pushPopParamsLength == 2) + { + status = PopRegisterMask(ref context, 1 << 14); + } + context.Sp += 4 * 4; + } + } + + // + // If we succeeded, post-process the results a bit + // + + if (status) + { + + // + // Since we always POP to the LR, recover the final PC from there. + // Also set the establisher frame equal to the final stack pointer. + // + + context.Pc = context.Lr; + } + + return status; + } + + #endregion + #region Unwind Helpers + + private uint ComputeScopeSize( + TargetPointer unwindCodePtr, + TargetPointer unwindCodesEndPtr, + bool isEpilog) + { + // + // Iterate through the unwind codes until we hit an end marker. + // While iterating, accumulate the total scope size. + // + uint scopeSize = 0; + byte opcode = _target.Read(unwindCodePtr); + while (unwindCodePtr < unwindCodesEndPtr && opcode < 0xfd) + { + byte tableValue = UnwindOpTable[opcode]; + scopeSize += (uint)tableValue >> 4; + unwindCodePtr += tableValue & 0xfu; + opcode = _target.Read(unwindCodePtr); + } + + // + // Handle the special epilog-only end codes. + // + if (opcode >= 0xfd && opcode <= 0xfe && isEpilog) + { + scopeSize += opcode - 0xfcu; + } + return scopeSize; + } + + private static bool CheckCondition( + ref ARMContext context, + uint condition) + { + int value = (ConditionTable[(int)condition & 0xf] >> (int)(context.Cpsr >> 28)) & 1; + return value != 0; + } + + private static ushort RangeToMask(uint start, uint stop, uint lr) + { + ushort mask = 0; + if (start <= stop) + { + mask |= (ushort)(((1 << (int)(stop + 1)) - 1) - ((1 << (int)start) - 1)); + } + if (lr != 0) + { + mask |= 1 << 14; + } + return mask; + } + + private unsafe bool PopVfpRegisterRange( + ref ARMContext context, + uint regStart, + uint regStop) + { + for (uint regIndex = regStart; regIndex <= regStop; regIndex++) + { + context.D[regIndex] = _target.Read(context.Sp); + context.Sp += 8; + } + return true; + } + + private bool PopRegisterMask( + ref ARMContext context, + ushort regMask) + { + // Pop each register in sequence + for (int regIndex = 0; regIndex < 15; regIndex++) + { + if ((regMask & (1 << regIndex)) != 0) + { + SetRegister(ref context, regIndex, _target.Read(context.Sp)); + context.Sp += 4; + } + } + + // If we popped LR, move it to the PC. + if ((regMask & 0x4000) != 0) + { + context.Pc = context.Lr; + } + return true; + } + + private static void SetRegister(ref ARMContext context, int regIndex, uint value) + { + switch (regIndex) + { + case 0: context.R0 = value; break; + case 1: context.R1 = value; break; + case 2: context.R2 = value; break; + case 3: context.R3 = value; break; + case 4: context.R4 = value; break; + case 5: context.R5 = value; break; + case 6: context.R6 = value; break; + case 7: context.R7 = value; break; + case 8: context.R8 = value; break; + case 9: context.R9 = value; break; + case 10: context.R10 = value; break; + case 11: context.R11 = value; break; + case 12: context.R12 = value; break; + case 13: context.Sp = value; break; + case 14: context.Lr = value; break; + case 15: context.Pc = value; break; + case 16: context.Cpsr = value; break; + default: throw new ArgumentOutOfRangeException(nameof(regIndex)); + } + } + + private static uint GetRegister(ref ARMContext context, int regIndex) => + regIndex switch + { + 0 => context.R0, + 1 => context.R1, + 2 => context.R2, + 3 => context.R3, + 4 => context.R4, + 5 => context.R5, + 6 => context.R6, + 7 => context.R7, + 8 => context.R8, + 9 => context.R9, + 10 => context.R10, + 11 => context.R11, + 12 => context.R12, + 13 => context.Sp, + 14 => context.Lr, + 15 => context.Pc, + 16 => context.Cpsr, + _ => throw new ArgumentOutOfRangeException(nameof(regIndex)), + }; + + #endregion +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/LookupValues.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/LookupValues.cs new file mode 100644 index 00000000000000..10a862f61fb2f3 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/LookupValues.cs @@ -0,0 +1,245 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Immutable; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.ARM; + +internal static class LookupValues +{ + /// + /// This table provides the register mask described by the given C/L/R/Reg bit + /// combinations in the compact pdata format, along with the number of VFP + /// registers to save in bits 16-19. + /// + public static ReadOnlySpan RegisterMaskLookup => + [ // C L R Reg + 0x00010, // 0 0 0 000 + 0x00030, // 0 0 0 001 + 0x00070, // 0 0 0 010 + 0x000f0, // 0 0 0 011 + 0x001f0, // 0 0 0 100 + 0x003f0, // 0 0 0 101 + 0x007f0, // 0 0 0 110 + 0x00ff0, // 0 0 0 111 + + 0x10000, // 0 0 1 000 + 0x20000, // 0 0 1 001 + 0x30000, // 0 0 1 010 + 0x40000, // 0 0 1 011 + 0x50000, // 0 0 1 100 + 0x60000, // 0 0 1 101 + 0x70000, // 0 0 1 110 + 0x00000, // 0 0 1 111 + + 0x04010, // 0 1 0 000 + 0x04030, // 0 1 0 001 + 0x04070, // 0 1 0 010 + 0x040f0, // 0 1 0 011 + 0x041f0, // 0 1 0 100 + 0x043f0, // 0 1 0 101 + 0x047f0, // 0 1 0 110 + 0x04ff0, // 0 1 0 111 + + 0x14000, // 0 1 1 000 + 0x24000, // 0 1 1 001 + 0x34000, // 0 1 1 010 + 0x44000, // 0 1 1 011 + 0x54000, // 0 1 1 100 + 0x64000, // 0 1 1 101 + 0x74000, // 0 1 1 110 + 0x04000, // 0 1 1 111 + + 0x00810, // 1 0 0 000 + 0x00830, // 1 0 0 001 + 0x00870, // 1 0 0 010 + 0x008f0, // 1 0 0 011 + 0x009f0, // 1 0 0 100 + 0x00bf0, // 1 0 0 101 + 0x00ff0, // 1 0 0 110 + 0x0ffff, // 1 0 0 111 + + 0x1ffff, // 1 0 1 000 + 0x2ffff, // 1 0 1 001 + 0x3ffff, // 1 0 1 010 + 0x4ffff, // 1 0 1 011 + 0x5ffff, // 1 0 1 100 + 0x6ffff, // 1 0 1 101 + 0x7ffff, // 1 0 1 110 + 0x0ffff, // 1 0 1 111 + + 0x04810, // 1 1 0 000 + 0x04830, // 1 1 0 001 + 0x04870, // 1 1 0 010 + 0x048f0, // 1 1 0 011 + 0x049f0, // 1 1 0 100 + 0x04bf0, // 1 1 0 101 + 0x04ff0, // 1 1 0 110 + 0x0ffff, // 1 1 0 111 + + 0x14800, // 1 1 1 000 + 0x24800, // 1 1 1 001 + 0x34800, // 1 1 1 010 + 0x44800, // 1 1 1 011 + 0x54800, // 1 1 1 100 + 0x64800, // 1 1 1 101 + 0x74800, // 1 1 1 110 + 0x04800, // 1 1 1 111 + ]; + + /// + /// This table describes the size of each unwind code, in bytes (lower nibble), + /// along with the size of the corresponding machine code, in halfwords + /// (upper nibble). + /// + public static ReadOnlySpan UnwindOpTable => + [ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x12, 0x12, 0x02, 0x22, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x22, 0x22, 0x13, 0x14, 0x23, 0x24, 0x11, 0x21, 0x10, 0x20, 0x00 + ]; + + private const ushort NSET_MASK = 0xff00; + private const ushort ZSET_MASK = 0xf0f0; + private const ushort CSET_MASK = 0xcccc; + private const ushort VSET_MASK = 0xaaaa; + private const ushort NEQUALV_MASK = unchecked((ushort)((NSET_MASK & VSET_MASK) | (~NSET_MASK & ~VSET_MASK))); + + /// + /// The ConditionTable is used to look up the state of a condition + /// based on the CPSR flags N,Z,C,V, which reside in the upper 4 + /// bits. To use this table, take the condition you are interested + /// in and use it as the index to look up the UINT16 from the table. + /// Then right-shift that value by the upper 4 bits of the CPSR, + /// and the low bit will be the result. + /// + /// The bits in the CPSR are ordered (MSB to LSB): N,Z,C,V. Taken + /// together, this is called the CpsrFlags. + /// + /// The macros below are defined such that: + /// + /// N = (NSET_MASK >> CpsrFlags) & 1 + /// Z = (ZSET_MASK >> CpsrFlags) & 1 + /// C = (CSET_MASK >> CpsrFlags) & 1 + /// V = (VSET_MASK >> CpsrFlags) & 1 + /// + /// Also: + /// + /// (N == V) = (NEQUALV_MASK >> CpsrFlags) & 1 + /// + public static ReadOnlySpan ConditionTable => + [ + // EQ: Z + ZSET_MASK, + // NE: !Z + unchecked((ushort)~ZSET_MASK), + // CS: C + CSET_MASK, + // CC: !C + unchecked((ushort)~CSET_MASK), + // MI: N + NSET_MASK, + // PL: !N + unchecked((ushort)~NSET_MASK), + // VS: V + VSET_MASK, + // VC: !V + unchecked((ushort)~VSET_MASK), + // HI: C & !Z + CSET_MASK & ~ZSET_MASK, + // LO: !C | Z + unchecked((short)~CSET_MASK | ZSET_MASK), + // GE: N == V + NEQUALV_MASK, + // LT: N != V + unchecked((ushort)~NEQUALV_MASK), + // GT: (N == V) & !Z + NEQUALV_MASK & ~ZSET_MASK, + // LE: (N != V) | Z + unchecked((ushort)~NEQUALV_MASK | ZSET_MASK), + // AL: always + 0xffff, + // NV: never + 0x0000 + ]; + + + public const ushort OFFSET_NONE = ushort.MaxValue; + public readonly struct ARM_CONTEXT_OFFSETS( + ushort alignment, + ushort totalSize, + ImmutableArray regOffset, + ImmutableArray fpRegOffset, + ushort spOffset, + ushort lrOffset, + ushort pcOffset, + ushort cpsrOffset, + ushort fpscrOffset) + { + public readonly ushort Alignment = alignment; + public readonly ushort TotalSize = totalSize; + public readonly ImmutableArray RegOffset = regOffset; + public readonly ImmutableArray FpRegOffset = fpRegOffset; + public readonly ushort SpOffset = spOffset; + public readonly ushort LrOffset = lrOffset; + public readonly ushort PcOffset = pcOffset; + public readonly ushort CpsrOffset = cpsrOffset; + public readonly ushort FpscrOffset = fpscrOffset; + } + + public static readonly ARM_CONTEXT_OFFSETS TrapFrameOffsets = + new( + alignment: 8, + totalSize: 272, + regOffset: [248, 252, 256, 260, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, 72], + fpRegOffset: [184, 192, 200, 208, 216, 224, 232, 240, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, + OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, + OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE], + spOffset: 64, + lrOffset: 68, + pcOffset: 264, + cpsrOffset: 268, + fpscrOffset: 176); + + + public static readonly ARM_CONTEXT_OFFSETS MachineFrameOffsets = + new( + alignment: 8, + totalSize: 8, + regOffset: [OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE], + fpRegOffset: [OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, + OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, + OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE, OFFSET_NONE], + spOffset: 0, + lrOffset: OFFSET_NONE, + pcOffset: 4, + cpsrOffset: OFFSET_NONE, + fpscrOffset: OFFSET_NONE); + + public static readonly ARM_CONTEXT_OFFSETS ContextOffsets = + new( + alignment: 16, + totalSize: 416, + regOffset: [4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52], + fpRegOffset: [80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 264, 272, 280, 288, 296, 304, 312, 320, 328], + spOffset: 56, + lrOffset: 60, + pcOffset: 64, + cpsrOffset: 68, + fpscrOffset: 72); +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs new file mode 100644 index 00000000000000..b9e7ccdd3ba7ca --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs @@ -0,0 +1,250 @@ +// 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.ARM; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// ARM-specific thread context. +/// +[StructLayout(LayoutKind.Explicit, Pack = 1)] +internal struct ARMContext : IPlatformContext +{ + + [Flags] + public enum ContextFlagsValues : uint + { + CONTEXT_ARM = 0x00200000, + CONTEXT_CONTROL = CONTEXT_ARM | 0x1, + CONTEXT_INTEGER = CONTEXT_ARM | 0x2, + CONTEXT_FLOATING_POINT = CONTEXT_ARM | 0x4, + CONTEXT_DEBUG_REGISTERS = CONTEXT_ARM | 0x8, + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, + CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, + + CONTEXT_UNWOUND_TO_CALL = 0x20000000, + } + + public readonly uint Size => 0x1a0; + public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public TargetPointer StackPointer + { + readonly get => new(Sp); + set => Sp = (uint)value.Value; + } + + public TargetPointer InstructionPointer + { + readonly get => new(Pc); + set => Pc = (uint)value.Value; + } + + public TargetPointer FramePointer + { + readonly get => new(R11); + set => R11 = (uint)value.Value; + } + + public void Unwind(Target target) + { + ARMUnwinder unwinder = new(target); + unwinder.Unwind(ref this); + } + + // Control flags + + [FieldOffset(0x0)] + public uint ContextFlags; + + #region General registers + + [Register(RegisterType.General)] + [FieldOffset(0x4)] + public uint R0; + + [Register(RegisterType.General)] + [FieldOffset(0x8)] + public uint R1; + + [Register(RegisterType.General)] + [FieldOffset(0xc)] + public uint R2; + + [Register(RegisterType.General)] + [FieldOffset(0x10)] + public uint R3; + + [Register(RegisterType.General)] + [FieldOffset(0x14)] + public uint R4; + + [Register(RegisterType.General)] + [FieldOffset(0x18)] + public uint R5; + + [Register(RegisterType.General)] + [FieldOffset(0x1c)] + public uint R6; + + [Register(RegisterType.General)] + [FieldOffset(0x20)] + public uint R7; + + [Register(RegisterType.General)] + [FieldOffset(0x24)] + public uint R8; + + [Register(RegisterType.General)] + [FieldOffset(0x28)] + public uint R9; + + [Register(RegisterType.General)] + [FieldOffset(0x2c)] + public uint R10; + + [Register(RegisterType.General | RegisterType.FramePointer)] + [FieldOffset(0x30)] + public uint R11; + + [Register(RegisterType.General)] + [FieldOffset(0x34)] + public uint R12; + + #endregion + + #region Control Registers + + [Register(RegisterType.Control | RegisterType.StackPointer)] + [FieldOffset(0x38)] + public uint Sp; + + [Register(RegisterType.Control)] + [FieldOffset(0x3c)] + public uint Lr; + + [Register(RegisterType.Control | RegisterType.ProgramCounter)] + [FieldOffset(0x40)] + public uint Pc; + + [Register(RegisterType.General)] + [FieldOffset(0x44)] + public uint Cpsr; + + #endregion + + #region Floating Point/NEON Registers + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x48)] + public uint Fpscr; + + [FieldOffset(0x50)] + public unsafe fixed ulong D[32]; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x50)] + public M128A Q0; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x60)] + public M128A Q1; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x70)] + public M128A Q2; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x80)] + public M128A Q3; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x90)] + public M128A Q4; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0xa0)] + public M128A Q5; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0xb0)] + public M128A Q6; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0xc0)] + public M128A Q7; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0xd0)] + public M128A Q8; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0xe0)] + public M128A Q9; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0xf0)] + public M128A Q10; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x100)] + public M128A Q11; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x110)] + public M128A Q12; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x120)] + public M128A Q13; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x130)] + public M128A Q14; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x140)] + public M128A Q15; + + #endregion + + #region Debug registers + [Register(RegisterType.Debug)] + [FieldOffset(0x150)] + public unsafe fixed uint Bvr[8]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x170)] + public unsafe fixed uint Bcr[8]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x190)] + public unsafe fixed uint Wvr[1]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x194)] + public unsafe fixed uint Wcr[1]; + + #endregion + + [FieldOffset(0x198)] + public ulong Padding; + + public override readonly string ToString() => + $"ARM Context: ContextFlags={ContextFlags:X8} PC={Pc:X8} SP={Sp:X8} LR={Lr:X8} " + + $"R0={R0:X8} R1={R1:X8} R2={R2:X8} R3={R3:X8} " + + $"R4={R4:X8} R5={R5:X8} R6={R6:X8} R7={R7:X8} " + + $"R8={R8:X8} R9={R9:X8} R10={R10:X8} R11={R11:X8} R12={R12:X8} " + + $"Cpsr={Cpsr:X8} Fpscr={Fpscr:X8} " + + $"Q0={Q0.Low:X16}:{Q0.High:X16} Q1={Q1.Low:X16}:{Q1.High:X16} " + + $"Q2={Q2.Low:X16}:{Q2.High:X16} Q3={Q3.Low:X16}:{Q3.High:X16} " + + $"Q4={Q4.Low:X16}:{Q4.High:X16} Q5={Q5.Low:X16}:{Q5.High:X16} " + + $"Q6={Q6.Low:X16}:{Q6.High:X16} Q7={Q7.Low:X16}:{Q7.High:X16} " + + $"Q8={Q8.Low:X16}:{Q8.High:X16} Q9={Q9.Low:X16}:{Q9.High:X16} " + + $"Q10={Q10.Low:X16}:{Q10.High:X16} Q11={Q11.Low:X16}:{Q11.High:X16} " + + $"Q12={Q12.Low:X16}:{Q12.High:X16} Q13={Q13.Low:X16}:{Q13.High:X16} " + + $"Q14={Q14.Low:X16}:{Q14.High:X16} Q15={Q15.Low:X16}:{Q15.High:X16}"; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index cf9aac6ac54507..5bacec253602eb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -29,6 +29,7 @@ public static IPlatformAgnosticContext GetContextForPlatform(Target target) return runtimeInfo.GetTargetArchitecture() switch { RuntimeInfoArchitecture.X64 => new ContextHolder(), + RuntimeInfoArchitecture.Arm => new ContextHolder(), RuntimeInfoArchitecture.Arm64 => new ContextHolder(), RuntimeInfoArchitecture.Unknown => throw new InvalidOperationException($"Processor architecture is required for creating a platform specific context and is not provided by the target"), _ => throw new InvalidOperationException($"Unsupported architecture {runtimeInfo.GetTargetArchitecture()}"), diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs new file mode 100644 index 00000000000000..59ca76d9e4c1b2 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +[StructLayout(LayoutKind.Sequential)] +public struct M128A : IEquatable +{ + public ulong Low; + public ulong High; + + public void Clear() + { + Low = 0; + High = 0; + } + + public static bool operator ==(M128A left, M128A right) => left.Equals(right); + + public static bool operator !=(M128A left, M128A right) => !(left == right); + + public override bool Equals(object? obj) => obj is M128A other && Equals(other); + + public bool Equals(M128A other) => Low == other.Low && High == other.High; + + public override int GetHashCode() => base.GetHashCode(); +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs new file mode 100644 index 00000000000000..90b39264d279dc --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Diagnostics.DataContractReader.Data; +using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.ARMContext; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class ARMFrameHandler(Target target, ContextHolder contextHolder) : BaseFrameHandler(target, contextHolder), IPlatformFrameHandler +{ + private readonly ContextHolder _holder = contextHolder; + + void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + if (frame.TargetContext is not TargetPointer targetContext) + { + throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); + } + _holder.ReadFromAddress(_target, targetContext); + } + + void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + { + // TODO(cdacarm) + throw new NotImplementedException(); + } +} 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 53356b3bbf5a41..69a68e13788a01 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 @@ -172,6 +172,7 @@ private IPlatformFrameHandler GetFrameHandler(IPlatformAgnosticContext context) return context switch { ContextHolder contextHolder => new AMD64FrameHandler(target, contextHolder), + ContextHolder contextHolder => new ARMFrameHandler(target, contextHolder), ContextHolder contextHolder => new ARM64FrameHandler(target, contextHolder), _ => throw new InvalidOperationException("Unsupported context type"), }; diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs index 8440611106be13..9c7c68d9bab5d1 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs @@ -76,7 +76,7 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* contextStruct.FillFromBuffer(contextBuf); localContextStruct.FillFromBuffer(localContextBuf); - Debug.Assert(contextStruct.Equals(localContextStruct)); + Debug.Assert(contextStruct.Equals(localContextStruct), $"\ncDAC: {contextStruct} \n DAC: {localContextStruct}"); } } #endif @@ -128,7 +128,7 @@ int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, IStackWalk sw = _target.Contracts.StackWalk; IStackDataFrameHandle frameData = _dataFrames.Current; TargetPointer frameAddr = sw.GetFrameAddress(frameData); - *(ulong*)outBuffer = frameAddr.Value; + *(ulong*)outBuffer = frameAddr.ToClrDataAddress(_target); hr = HResults.S_OK; break; default: @@ -149,7 +149,7 @@ int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, for (int i = 0; i < outBufferSize; i++) { - Debug.Assert(localOutBuffer[i] == outBuffer[i], $"cDAC: {outBuffer[i]:x}, DAC: {localOutBuffer[i]:x}"); + Debug.Assert(localOutBuffer[i] == outBuffer[i], $"cDAC: {Convert.ToHexString(new ReadOnlySpan(outBuffer, (int)outBufferSize))}, DAC: {Convert.ToHexString(localOutBuffer)}"); } } #endif diff --git a/src/native/managed/compile-native.proj b/src/native/managed/compile-native.proj index 6f0f88a25b449d..737593267ed65f 100644 --- a/src/native/managed/compile-native.proj +++ b/src/native/managed/compile-native.proj @@ -23,7 +23,7 @@ false false - false + false true false From f8cf2c4c6960e7fe27a5cd9bcc66ca71dfd32bd9 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:52:37 -0400 Subject: [PATCH 02/20] fix softwareexceptionframes --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 12 ++++++++++++ .../Contracts/StackWalk/Context/ARM/ARMUnwinder.cs | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index aab39b5678ad99..872b845faf3900 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -790,6 +790,18 @@ CDAC_TYPE_SIZE(sizeof(CalleeSavedRegisters)) ENUM_CALLEE_SAVED_REGISTERS() #undef CALLEE_SAVED_REGISTER +#elif defined(TARGET_ARM) + +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, R4, offsetof(CalleeSavedRegisters, r4)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, R5, offsetof(CalleeSavedRegisters, r5)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, R6, offsetof(CalleeSavedRegisters, r6)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, R7, offsetof(CalleeSavedRegisters, r7)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, R8, offsetof(CalleeSavedRegisters, r8)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, R9, offsetof(CalleeSavedRegisters, r9)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, R10, offsetof(CalleeSavedRegisters, r10)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, R11, offsetof(CalleeSavedRegisters, r11)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Lr, offsetof(CalleeSavedRegisters, r14)) + #elif defined(TARGET_ARM64) CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X19, offsetof(CalleeSavedRegisters, x19)) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs index 597eafd2e02c04..3ddafc4296581a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs @@ -29,8 +29,6 @@ public bool Unwind(ref ARMContext context) TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh)); - if (functionEntry.EndAddress is null) - return false; if ((functionEntry.UnwindData & 0x3) != 0) { @@ -223,7 +221,8 @@ private bool UnwindFull( // // Now execute codes until we hit the end. // - while (unwindCodePtr < unwindCodesEndPtr && status) + bool keepReading = true; + while (unwindCodePtr < unwindCodesEndPtr && keepReading && status) { byte curCode = _target.Read(unwindCodePtr); @@ -485,6 +484,7 @@ private bool UnwindFull( case 0xfd: case 0xfe: case 0xff: + keepReading = false; break; default: From e78de008275702564cedef56aac65f0d5b726706 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:52:49 -0400 Subject: [PATCH 03/20] fix missing ClrDataAddress conversion --- .../Legacy/ConversionExtensions.cs | 22 ++++++++++++++++++- .../Legacy/SOSDacImpl.cs | 4 ++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ConversionExtensions.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ConversionExtensions.cs index 7ad4114956f309..df38c10520eec2 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ConversionExtensions.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ConversionExtensions.cs @@ -38,7 +38,27 @@ public static TargetPointer ToTargetPointer(this ClrDataAddress address, Target { throw new ArgumentException(nameof(address), "ClrDataAddress out of range for the target platform."); } - return new TargetPointer((ulong)address); + return new TargetPointer((uint)address); + } + } + + /// + /// Converts a ClrDataAddress to a TargetCodePointer, ensuring the address is within the valid range for the target platform. + /// + public static TargetCodePointer ToTargetCodePointer(this ClrDataAddress address, Target target) + { + if (target.PointerSize == sizeof(ulong)) + { + return new TargetCodePointer(address); + } + else + { + long signedAddr = (long)address.Value; + if (signedAddr > int.MaxValue || signedAddr < int.MinValue) + { + throw new ArgumentException(nameof(address), "ClrDataAddress out of range for the target platform."); + } + return new TargetCodePointer((uint)address); } } } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index 5a078fd5a8bca4..c204a301574539 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -803,7 +803,7 @@ int ISOSDacInterface.GetMethodDescPtrFromIP(ClrDataAddress ip, ClrDataAddress* p IExecutionManager executionManager = _target.Contracts.ExecutionManager; IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - CodeBlockHandle? handle = executionManager.GetCodeBlockHandle(new TargetCodePointer(ip)); + CodeBlockHandle? handle = executionManager.GetCodeBlockHandle(ip.ToTargetCodePointer(_target)); if (handle is CodeBlockHandle codeHandle) { TargetPointer methodDescAddr = executionManager.GetMethodDesc(codeHandle); @@ -814,7 +814,7 @@ int ISOSDacInterface.GetMethodDescPtrFromIP(ClrDataAddress ip, ClrDataAddress* p // if validation fails, should return E_INVALIDARG rts.GetMethodDescHandle(methodDescAddr); - *ppMD = methodDescAddr.Value; + *ppMD = methodDescAddr.ToClrDataAddress(_target); hr = HResults.S_OK; } catch (System.Exception) From 274ce8349d8735c2e5647a3f16c8052595134b6f Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 13 Jun 2025 09:12:18 -0400 Subject: [PATCH 04/20] add ICF ARM Field --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 872b845faf3900..37bb3f3cb31b39 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -698,6 +698,9 @@ CDAC_TYPE_SIZE(sizeof(InlinedCallFrame)) CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CallSiteSP, offsetof(InlinedCallFrame, m_pCallSiteSP)) CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CallerReturnAddress, offsetof(InlinedCallFrame, m_pCallerReturnAddress)) CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CalleeSavedFP, offsetof(InlinedCallFrame, m_pCalleeSavedFP)) +#ifdef TARGET_ARM +CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, SPAfterProlog, offsetof(InlinedCallFrame, m_pSPAfterProlog)) +#endif // TARGET_ARM CDAC_TYPE_END(InlinedCallFrame) CDAC_TYPE_BEGIN(SoftwareExceptionFrame) From 868090e4c8350dfc07e238d2c0959268884dd9ec Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:47:50 -0400 Subject: [PATCH 05/20] fix ICF --- .../StackWalk/FrameHandling/ARMFrameHandler.cs | 16 ++++++++++++++++ .../Data/Frames/InlinedCallFrame.cs | 3 +++ 2 files changed, 19 insertions(+) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs index 90b39264d279dc..efb93ddf5ea257 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs @@ -27,4 +27,20 @@ void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) // TODO(cdacarm) throw new NotImplementedException(); } + + public override void HandleInlinedCallFrame(InlinedCallFrame inlinedCallFrame) + { + base.HandleInlinedCallFrame(inlinedCallFrame); + + // On ARM, the InlinedCallFrame stores the value of the SP after the prolog + // to allow unwinding for functions with stackalloc. When a function uses + // stackalloc, the CallSiteSP can already have been adjusted. + + if (inlinedCallFrame.SPAfterProlog is not TargetPointer spAfterProlog) + { + throw new InvalidOperationException("ARM InlinedCallFrame does not have SPAfterProlog set"); + } + + _holder.Context.R9 = (uint)spAfterProlog; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs index 4cdd72e1ab8a09..2875242956a5f4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs @@ -14,6 +14,8 @@ public InlinedCallFrame(Target target, TargetPointer address) CallSiteSP = target.ReadPointer(address + (ulong)type.Fields[nameof(CallSiteSP)].Offset); CallerReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(CallerReturnAddress)].Offset); CalleeSavedFP = target.ReadPointer(address + (ulong)type.Fields[nameof(CalleeSavedFP)].Offset); + if (type.Fields.ContainsKey(nameof(SPAfterProlog))) + SPAfterProlog = target.ReadPointer(address + (ulong)type.Fields[nameof(SPAfterProlog)].Offset); Address = address; } @@ -21,4 +23,5 @@ public InlinedCallFrame(Target target, TargetPointer address) public TargetPointer CallSiteSP { get; } public TargetPointer CallerReturnAddress { get; } public TargetPointer CalleeSavedFP { get; } + public TargetPointer? SPAfterProlog { get; } } From 3be42da1d2c7efb189b59a3770acaa6087a6b62e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:48:07 -0400 Subject: [PATCH 06/20] fix other ARM issues --- src/coreclr/debug/daccess/request.cpp | 4 +- .../CodePointerUtils.cs | 5 ++ .../Legacy/ConversionExtensions.cs | 29 ++++++- .../Legacy/SOSDacImpl.cs | 82 +++++++++---------- 4 files changed, 75 insertions(+), 45 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 4f95b4bd324a76..16d4547756ffdb 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -888,11 +888,11 @@ HRESULT ClrDataAccess::GetThreadData(CLRDATA_ADDRESS threadAddr, struct DacpThre #ifdef FEATURE_EH_FUNCLETS if (thread->m_ExceptionState.m_pCurrentTracker) { - threadData->firstNestedException = PTR_HOST_TO_TADDR( + threadData->firstNestedException = HOST_CDADDR( thread->m_ExceptionState.m_pCurrentTracker->m_pPrevNestedInfo); } #else - threadData->firstNestedException = PTR_HOST_TO_TADDR( + threadData->firstNestedException = HOST_CDADDR( thread->m_ExceptionState.m_currentExInfo.m_pPrevNestedInfo); #endif // FEATURE_EH_FUNCLETS diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CodePointerUtils.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CodePointerUtils.cs index b0b5d10f4ac742..0a39b0ccacfe8b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CodePointerUtils.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CodePointerUtils.cs @@ -13,6 +13,11 @@ internal static class CodePointerUtils internal static TargetCodePointer CodePointerFromAddress(TargetPointer address, Target target) { + if (address == TargetCodePointer.Null) + { + return TargetCodePointer.Null; + } + IPlatformMetadata metadata = target.Contracts.PlatformMetadata; CodePointerFlags flags = metadata.GetCodePointerFlags(); if (flags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ConversionExtensions.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ConversionExtensions.cs index df38c10520eec2..3400538b8ac031 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ConversionExtensions.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ConversionExtensions.cs @@ -2,11 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; +using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader.Legacy; internal static class ConversionExtensions { + private const uint Arm32ThumbBit = 1; + /// /// Converts a TargetPointer to a ClrDataAddress using sign extension if required. /// @@ -24,8 +28,10 @@ public static ClrDataAddress ToClrDataAddress(this TargetPointer address, Target /// /// Converts a ClrDataAddress to a TargetPointer, ensuring the address is within the valid range for the target platform. + /// When overrideCheck is true, this will not check the range and will allow any address. This is used on legacy endpoints which + /// may pass in invalid ClrDataAddress values. /// - public static TargetPointer ToTargetPointer(this ClrDataAddress address, Target target) + public static TargetPointer ToTargetPointer(this ClrDataAddress address, Target target, bool overrideCheck = false) { if (target.PointerSize == sizeof(ulong)) { @@ -34,7 +40,7 @@ public static TargetPointer ToTargetPointer(this ClrDataAddress address, Target else { long signedAddr = (long)address.Value; - if (signedAddr > int.MaxValue || signedAddr < int.MinValue) + if (!overrideCheck && (signedAddr > int.MaxValue || signedAddr < int.MinValue)) { throw new ArgumentException(nameof(address), "ClrDataAddress out of range for the target platform."); } @@ -61,4 +67,23 @@ public static TargetCodePointer ToTargetCodePointer(this ClrDataAddress address, return new TargetCodePointer((uint)address); } } + + /// + /// Converts a TargetCodePointer to an address TargetPointer, removing any platform-specific bits such as the ARM32 Thumb bit or ARM64 pointer authentication. + /// + internal static TargetPointer ToAddress(this TargetCodePointer code, Target target) + { + IPlatformMetadata metadata = target.Contracts.PlatformMetadata; + CodePointerFlags flags = metadata.GetCodePointerFlags(); + if (flags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) + { + return new TargetPointer(code.Value & ~Arm32ThumbBit); + } + else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) + { + throw new NotImplementedException($"{nameof(ToAddress)}: ARM64 with pointer authentication"); + } + Debug.Assert(flags == default); + return new TargetPointer(code.Value); + } } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index c204a301574539..5704a792243f44 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -438,7 +438,7 @@ int ISOSDacInterface.GetMethodDescData(ClrDataAddress addr, ClrDataAddress ip, D NativeCodeVersionHandle? activeNativeCodeVersion = null; if (ip != 0) { - requestedNativeCodeVersion = nativeCodeContract.GetNativeCodeVersionForIP(new TargetCodePointer(ip)); + requestedNativeCodeVersion = nativeCodeContract.GetNativeCodeVersionForIP(ip.ToTargetCodePointer(_target)); } else { @@ -457,7 +457,7 @@ int ISOSDacInterface.GetMethodDescData(ClrDataAddress addr, ClrDataAddress ip, D if (nativeCodeAddr != TargetCodePointer.Null) { data->bHasNativeCode = 1; - data->NativeCodeAddr = nativeCodeAddr.AsTargetPointer.ToClrDataAddress(_target); + data->NativeCodeAddr = nativeCodeAddr.ToAddress(_target).ToClrDataAddress(_target); } else { @@ -615,41 +615,41 @@ int ISOSDacInterface.GetMethodDescData(ClrDataAddress addr, ClrDataAddress ip, D Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); if (hr == HResults.S_OK) { - Debug.Assert(data->bHasNativeCode == dataLocal.bHasNativeCode); - Debug.Assert(data->bIsDynamic == dataLocal.bIsDynamic); - Debug.Assert(data->wSlotNumber == dataLocal.wSlotNumber); - Debug.Assert(data->NativeCodeAddr == dataLocal.NativeCodeAddr); - Debug.Assert(data->AddressOfNativeCodeSlot == dataLocal.AddressOfNativeCodeSlot); - Debug.Assert(data->MethodDescPtr == dataLocal.MethodDescPtr); - Debug.Assert(data->MethodTablePtr == dataLocal.MethodTablePtr); - Debug.Assert(data->ModulePtr == dataLocal.ModulePtr); - Debug.Assert(data->MDToken == dataLocal.MDToken); - Debug.Assert(data->GCInfo == dataLocal.GCInfo); - Debug.Assert(data->GCStressCodeCopy == dataLocal.GCStressCodeCopy); + Debug.Assert(data->bHasNativeCode == dataLocal.bHasNativeCode, $"cDAC: {data->bHasNativeCode}, DAC: {dataLocal.bHasNativeCode}"); + Debug.Assert(data->bIsDynamic == dataLocal.bIsDynamic, $"cDAC: {data->bIsDynamic}, DAC: {dataLocal.bIsDynamic}"); + Debug.Assert(data->wSlotNumber == dataLocal.wSlotNumber, $"cDAC: {data->wSlotNumber}, DAC: {dataLocal.wSlotNumber}"); + Debug.Assert(data->NativeCodeAddr == dataLocal.NativeCodeAddr, $"cDAC: {data->NativeCodeAddr:x}, DAC: {dataLocal.NativeCodeAddr:x}"); + Debug.Assert(data->AddressOfNativeCodeSlot == dataLocal.AddressOfNativeCodeSlot, $"cDAC: {data->AddressOfNativeCodeSlot:x}, DAC: {dataLocal.AddressOfNativeCodeSlot:x}"); + Debug.Assert(data->MethodDescPtr == dataLocal.MethodDescPtr, $"cDAC: {data->MethodDescPtr:x}, DAC: {dataLocal.MethodDescPtr:x}"); + Debug.Assert(data->MethodTablePtr == dataLocal.MethodTablePtr, $"cDAC: {data->MethodTablePtr:x}, DAC: {dataLocal.MethodTablePtr:x}"); + Debug.Assert(data->ModulePtr == dataLocal.ModulePtr, $"cDAC: {data->ModulePtr:x}, DAC: {dataLocal.ModulePtr:x}"); + Debug.Assert(data->MDToken == dataLocal.MDToken, $"cDAC: {data->MDToken:x}, DAC: {dataLocal.MDToken:x}"); + Debug.Assert(data->GCInfo == dataLocal.GCInfo, $"cDAC: {data->GCInfo:x}, DAC: {dataLocal.GCInfo:x}"); + Debug.Assert(data->GCStressCodeCopy == dataLocal.GCStressCodeCopy, $"cDAC: {data->GCStressCodeCopy:x}, DAC: {dataLocal.GCStressCodeCopy:x}"); // managedDynamicMethodObject is not currently populated by the cDAC API and may differ from legacyImpl. Debug.Assert(data->managedDynamicMethodObject == 0); - Debug.Assert(data->requestedIP == dataLocal.requestedIP); - Debug.Assert(data->cJittedRejitVersions == dataLocal.cJittedRejitVersions); + Debug.Assert(data->requestedIP == dataLocal.requestedIP, $"cDAC: {data->requestedIP:x}, DAC: {dataLocal.requestedIP:x}"); + Debug.Assert(data->cJittedRejitVersions == dataLocal.cJittedRejitVersions, $"cDAC: {data->cJittedRejitVersions}, DAC: {dataLocal.cJittedRejitVersions}"); // rejitDataCurrent - Debug.Assert(data->rejitDataCurrent.rejitID == dataLocal.rejitDataCurrent.rejitID); - Debug.Assert(data->rejitDataCurrent.NativeCodeAddr == dataLocal.rejitDataCurrent.NativeCodeAddr); - Debug.Assert(data->rejitDataCurrent.flags == dataLocal.rejitDataCurrent.flags); + Debug.Assert(data->rejitDataCurrent.rejitID == dataLocal.rejitDataCurrent.rejitID, $"cDAC: {data->rejitDataCurrent.rejitID}, DAC: {dataLocal.rejitDataCurrent.rejitID}"); + Debug.Assert(data->rejitDataCurrent.NativeCodeAddr == dataLocal.rejitDataCurrent.NativeCodeAddr, $"cDAC: {data->rejitDataCurrent.NativeCodeAddr:x}, DAC: {dataLocal.rejitDataCurrent.NativeCodeAddr:x}"); + Debug.Assert(data->rejitDataCurrent.flags == dataLocal.rejitDataCurrent.flags, $"cDAC: {data->rejitDataCurrent.flags}, DAC: {dataLocal.rejitDataCurrent.flags}"); // rejitDataRequested - Debug.Assert(data->rejitDataRequested.rejitID == dataLocal.rejitDataRequested.rejitID); - Debug.Assert(data->rejitDataRequested.NativeCodeAddr == dataLocal.rejitDataRequested.NativeCodeAddr); - Debug.Assert(data->rejitDataRequested.flags == dataLocal.rejitDataRequested.flags); + Debug.Assert(data->rejitDataRequested.rejitID == dataLocal.rejitDataRequested.rejitID, $"cDAC: {data->rejitDataRequested.rejitID}, DAC: {dataLocal.rejitDataRequested.rejitID}"); + Debug.Assert(data->rejitDataRequested.NativeCodeAddr == dataLocal.rejitDataRequested.NativeCodeAddr, $"cDAC: {data->rejitDataRequested.NativeCodeAddr:x}, DAC: {dataLocal.rejitDataRequested.NativeCodeAddr:x}"); + Debug.Assert(data->rejitDataRequested.flags == dataLocal.rejitDataRequested.flags, $"cDAC: {data->rejitDataRequested.flags}, DAC: {dataLocal.rejitDataRequested.flags}"); // rgRevertedRejitData if (rgRevertedRejitData != null && rgRevertedRejitDataLocal != null) { - Debug.Assert(cNeededRevertedRejitDataLocal == *pcNeededRevertedRejitData); + Debug.Assert(cNeededRevertedRejitDataLocal == *pcNeededRevertedRejitData, $"cDAC: {*pcNeededRevertedRejitData}, DAC: {cNeededRevertedRejitDataLocal}"); for (ulong i = 0; i < cNeededRevertedRejitDataLocal; i++) { - Debug.Assert(rgRevertedRejitData[i].rejitID == rgRevertedRejitDataLocal[i].rejitID); - Debug.Assert(rgRevertedRejitData[i].NativeCodeAddr == rgRevertedRejitDataLocal[i].NativeCodeAddr); - Debug.Assert(rgRevertedRejitData[i].flags == rgRevertedRejitDataLocal[i].flags); + Debug.Assert(rgRevertedRejitData[i].rejitID == rgRevertedRejitDataLocal[i].rejitID, $"cDAC: {rgRevertedRejitData[i].rejitID}, DAC: {rgRevertedRejitDataLocal[i].rejitID}"); + Debug.Assert(rgRevertedRejitData[i].NativeCodeAddr == rgRevertedRejitDataLocal[i].NativeCodeAddr, $"cDAC: {rgRevertedRejitData[i].NativeCodeAddr:x}, DAC: {rgRevertedRejitDataLocal[i].NativeCodeAddr:x}"); + Debug.Assert(rgRevertedRejitData[i].flags == rgRevertedRejitDataLocal[i].flags, $"cDAC: {rgRevertedRejitData[i].flags}, DAC: {rgRevertedRejitDataLocal[i].flags}"); } } } @@ -963,7 +963,7 @@ int ISOSDacInterface.GetMethodTableName(ClrDataAddress mt, uint count, char* mtN try { Contracts.IRuntimeTypeSystem typeSystemContract = _target.Contracts.RuntimeTypeSystem; - Contracts.TypeHandle methodTableHandle = typeSystemContract.GetTypeHandle(mt.ToTargetPointer(_target)); + Contracts.TypeHandle methodTableHandle = typeSystemContract.GetTypeHandle(mt.ToTargetPointer(_target, overrideCheck: true)); if (typeSystemContract.IsFreeObjectMethodTable(methodTableHandle)) { OutputBufferHelpers.CopyStringToBuffer(mtName, count, pNeeded, "Free"); @@ -1511,21 +1511,21 @@ int ISOSDacInterface.GetThreadData(ClrDataAddress thread, DacpThreadData* data) Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); if (hr == HResults.S_OK) { - Debug.Assert(data->corThreadId == dataLocal.corThreadId); - Debug.Assert(data->osThreadId == dataLocal.osThreadId); - Debug.Assert(data->state == dataLocal.state); - Debug.Assert(data->preemptiveGCDisabled == dataLocal.preemptiveGCDisabled); - Debug.Assert(data->allocContextPtr == dataLocal.allocContextPtr); - Debug.Assert(data->allocContextLimit == dataLocal.allocContextLimit); - Debug.Assert(data->fiberData == dataLocal.fiberData); - Debug.Assert(data->context == dataLocal.context); - Debug.Assert(data->domain == dataLocal.domain); - Debug.Assert(data->lockCount == dataLocal.lockCount); - Debug.Assert(data->pFrame == dataLocal.pFrame); - Debug.Assert(data->firstNestedException == dataLocal.firstNestedException); - Debug.Assert(data->teb == dataLocal.teb); - Debug.Assert(data->lastThrownObjectHandle == dataLocal.lastThrownObjectHandle); - Debug.Assert(data->nextThread == dataLocal.nextThread); + Debug.Assert(data->corThreadId == dataLocal.corThreadId, $"cDAC: {data->corThreadId}, DAC: {dataLocal.corThreadId}"); + Debug.Assert(data->osThreadId == dataLocal.osThreadId, $"cDAC: {data->osThreadId}, DAC: {dataLocal.osThreadId}"); + Debug.Assert(data->state == dataLocal.state, $"cDAC: {data->state}, DAC: {dataLocal.state}"); + Debug.Assert(data->preemptiveGCDisabled == dataLocal.preemptiveGCDisabled, $"cDAC: {data->preemptiveGCDisabled}, DAC: {dataLocal.preemptiveGCDisabled}"); + Debug.Assert(data->allocContextPtr == dataLocal.allocContextPtr, $"cDAC: {data->allocContextPtr:x}, DAC: {dataLocal.allocContextPtr:x}"); + Debug.Assert(data->allocContextLimit == dataLocal.allocContextLimit, $"cDAC: {data->allocContextLimit:x}, DAC: {dataLocal.allocContextLimit:x}"); + Debug.Assert(data->fiberData == dataLocal.fiberData, $"cDAC: {data->fiberData:x}, DAC: {dataLocal.fiberData:x}"); + Debug.Assert(data->context == dataLocal.context, $"cDAC: {data->context:x}, DAC: {dataLocal.context:x}"); + Debug.Assert(data->domain == dataLocal.domain, $"cDAC: {data->domain:x}, DAC: {dataLocal.domain:x}"); + Debug.Assert(data->lockCount == dataLocal.lockCount, $"cDAC: {data->lockCount}, DAC: {dataLocal.lockCount}"); + Debug.Assert(data->pFrame == dataLocal.pFrame, $"cDAC: {data->pFrame:x}, DAC: {dataLocal.pFrame:x}"); + Debug.Assert(data->firstNestedException == dataLocal.firstNestedException, $"cDAC: {data->firstNestedException:x}, DAC: {dataLocal.firstNestedException:x}"); + Debug.Assert(data->teb == dataLocal.teb, $"cDAC: {data->teb:x}, DAC: {dataLocal.teb:x}"); + Debug.Assert(data->lastThrownObjectHandle == dataLocal.lastThrownObjectHandle, $"cDAC: {data->lastThrownObjectHandle:x}, DAC: {dataLocal.lastThrownObjectHandle:x}"); + Debug.Assert(data->nextThread == dataLocal.nextThread, $"cDAC: {data->nextThread:x}, DAC: {dataLocal.nextThread:x}"); } } #endif From aa3dcec7038ca12db978b1a867b024cb3529f044 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:58:51 -0400 Subject: [PATCH 07/20] temp fix --- .../Contracts/StackWalk/Context/ARM/ARMUnwinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs index 3ddafc4296581a..9fa9dcb63f0b04 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs @@ -28,7 +28,7 @@ public bool Unwind(ref ARMContext context) uint startingSp = context.Sp; TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); - Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh)); + Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh, context.InstructionPointer.Value)); if ((functionEntry.UnwindData & 0x3) != 0) { From e02c2fdfa8b24101302fa6547d14039b80725864 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Thu, 19 Jun 2025 15:10:03 -0400 Subject: [PATCH 08/20] fix merge issue --- .../Contracts/StackWalk/Context/ARM/ARMUnwinder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs index 9fa9dcb63f0b04..a6603d51fb5e45 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs @@ -28,7 +28,7 @@ public bool Unwind(ref ARMContext context) uint startingSp = context.Sp; TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); - Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh, context.InstructionPointer.Value)); + Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd(_eman.GetUnwindInfo(cbh)); if ((functionEntry.UnwindData & 0x3) != 0) { @@ -561,7 +561,8 @@ private unsafe bool UnwindCustom( TargetPointer sourceAddress = context.Sp + offsets.LrOffset; context.Lr = _target.Read(sourceAddress); } - if (offsets.PcOffset != OFFSET_NONE) { + if (offsets.PcOffset != OFFSET_NONE) + { TargetPointer sourceAddress = context.Sp + offsets.PcOffset; context.Pc = _target.Read(sourceAddress); From dbe77ba3324992fd5c66f749c9045a415a71d021 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Thu, 19 Jun 2025 15:15:13 -0400 Subject: [PATCH 09/20] add docs --- docs/design/datacontracts/StackWalk.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 080c3a7795e04c..67a46a88ea106c 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -42,6 +42,7 @@ This contract depends on the following descriptors: | `InlinedCallFrame` | `CallSiteSP` | SP saved in Frame | | `InlinedCallFrame` | `CallerReturnAddress` | Return address saved in Frame | | `InlinedCallFrame` | `CalleeSavedFP` | FP saved in Frame | +| `InlinedCallFrame` (arm32) | `SPAfterProlog` | Value of the SP after prolog. Used on ARM to maintain additional JIT invariant | | `SoftwareExceptionFrame` | `TargetContext` | Context object saved in Frame | | `SoftwareExceptionFrame` | `ReturnAddress` | Return address saved in Frame | | `FramedMethodFrame` | `TransitionBlockPtr` | Pointer to Frame's TransitionBlock | @@ -242,6 +243,8 @@ Most of the handlers are implemented in `BaseFrameHandler`. Platform specific co InlinedCallFrames store and update only the IP, SP, and FP of a given context. If the stored IP (CallerReturnAddress) is 0 then the InlinedCallFrame does not have an active call and should not update the context. +* On ARM, the InlinedCallFrame stores the value of the SP after the prolog (`SPAfterProlog`) to allow unwinding for functions with stackalloc. When a function uses stackalloc, the CallSiteSP can already have been adjusted. This value should be placed in R9. + #### SoftwareExceptionFrame SoftwareExceptionFrames store a copy of the context struct. The IP, SP, and all ABI specified (platform specific) callee-saved registers are copied from the stored context to the working context. From 4920a6836238c162d22f9b1e0526ca2c45d46498 Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Thu, 19 Jun 2025 15:16:26 -0400 Subject: [PATCH 10/20] remove prints --- .../Contracts/StackWalk/Context/ARMContext.cs | 15 --------------- .../Legacy/ClrDataStackWalk.cs | 4 ++-- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs index b9e7ccdd3ba7ca..af85910ff53087 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs @@ -232,19 +232,4 @@ public void Unwind(Target target) [FieldOffset(0x198)] public ulong Padding; - - public override readonly string ToString() => - $"ARM Context: ContextFlags={ContextFlags:X8} PC={Pc:X8} SP={Sp:X8} LR={Lr:X8} " + - $"R0={R0:X8} R1={R1:X8} R2={R2:X8} R3={R3:X8} " + - $"R4={R4:X8} R5={R5:X8} R6={R6:X8} R7={R7:X8} " + - $"R8={R8:X8} R9={R9:X8} R10={R10:X8} R11={R11:X8} R12={R12:X8} " + - $"Cpsr={Cpsr:X8} Fpscr={Fpscr:X8} " + - $"Q0={Q0.Low:X16}:{Q0.High:X16} Q1={Q1.Low:X16}:{Q1.High:X16} " + - $"Q2={Q2.Low:X16}:{Q2.High:X16} Q3={Q3.Low:X16}:{Q3.High:X16} " + - $"Q4={Q4.Low:X16}:{Q4.High:X16} Q5={Q5.Low:X16}:{Q5.High:X16} " + - $"Q6={Q6.Low:X16}:{Q6.High:X16} Q7={Q7.Low:X16}:{Q7.High:X16} " + - $"Q8={Q8.Low:X16}:{Q8.High:X16} Q9={Q9.Low:X16}:{Q9.High:X16} " + - $"Q10={Q10.Low:X16}:{Q10.High:X16} Q11={Q11.Low:X16}:{Q11.High:X16} " + - $"Q12={Q12.Low:X16}:{Q12.High:X16} Q13={Q13.Low:X16}:{Q13.High:X16} " + - $"Q14={Q14.Low:X16}:{Q14.High:X16} Q15={Q15.Low:X16}:{Q15.High:X16}"; } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs index 9c7c68d9bab5d1..5c4d4f71a9bfb6 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs @@ -76,7 +76,7 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* contextStruct.FillFromBuffer(contextBuf); localContextStruct.FillFromBuffer(localContextBuf); - Debug.Assert(contextStruct.Equals(localContextStruct), $"\ncDAC: {contextStruct} \n DAC: {localContextStruct}"); + Debug.Assert(contextStruct.Equals(localContextStruct)); } } #endif @@ -149,7 +149,7 @@ int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, for (int i = 0; i < outBufferSize; i++) { - Debug.Assert(localOutBuffer[i] == outBuffer[i], $"cDAC: {Convert.ToHexString(new ReadOnlySpan(outBuffer, (int)outBufferSize))}, DAC: {Convert.ToHexString(localOutBuffer)}"); + Debug.Assert(localOutBuffer[i] == outBuffer[i]); } } #endif From a560e112173bf58f3d2d9857a9f7444c4cd93fbd Mon Sep 17 00:00:00 2001 From: maxcharlamb Date: Thu, 19 Jun 2025 15:17:10 -0400 Subject: [PATCH 11/20] fix --- .../cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs index 5c4d4f71a9bfb6..720d445fc3f2d4 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataStackWalk.cs @@ -149,7 +149,7 @@ int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, for (int i = 0; i < outBufferSize; i++) { - Debug.Assert(localOutBuffer[i] == outBuffer[i]); + Debug.Assert(localOutBuffer[i] == outBuffer[i], $"cDAC: {outBuffer[i]:x}, DAC: {localOutBuffer[i]:x}"); } } #endif From 622c4e89990953726044aa099f3620db1fe37b6c Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:21:25 -0400 Subject: [PATCH 12/20] Update src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Contracts/StackWalk/Context/M128.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs index 59ca76d9e4c1b2..c2e0bf073db683 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/M128.cs @@ -26,5 +26,5 @@ public void Clear() public bool Equals(M128A other) => Low == other.Low && High == other.High; - public override int GetHashCode() => base.GetHashCode(); + public override int GetHashCode() => HashCode.Combine(Low, High); } From 4db64652c911143ec3aaacc04092c4186bce939e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:59:47 -0400 Subject: [PATCH 13/20] ARM TransitionFrame support --- .../debug/runtimeinfo/datadescriptor.h | 16 ++++++++++++ .../DataType.cs | 1 + .../FrameHandling/ARMFrameHandler.cs | 17 ++++++++++++ .../Data/Frames/ArgumentRegisters.cs | 26 +++++++++++++++++++ .../Data/Frames/TransitionBlock.cs | 10 +++++++ 5 files changed, 70 insertions(+) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ArgumentRegisters.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index d68a0018911078..9fb7a28d4038a3 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -726,6 +726,9 @@ CDAC_TYPE_BEGIN(TransitionBlock) CDAC_TYPE_SIZE(sizeof(TransitionBlock)) CDAC_TYPE_FIELD(TransitionBlock, /*pointer*/, ReturnAddress, offsetof(TransitionBlock, m_ReturnAddress)) CDAC_TYPE_FIELD(TransitionBlock, /*CalleeSavedRegisters*/, CalleeSavedRegisters, offsetof(TransitionBlock, m_calleeSavedRegisters)) +#ifdef TARGET_ARM +CDAC_TYPE_FIELD(TransitionBlock, /*ArgumentRegisters*/, ArgumentRegisters, offsetof(TransitionBlock, m_argumentRegisters)) +#endif // TARGET_ARM CDAC_TYPE_END(TransitionBlock) #ifdef DEBUGGING_SUPPORTED @@ -791,6 +794,19 @@ CDAC_TYPE_FIELD(FaultingExceptionFrame, /*T_CONTEXT*/, TargetContext, cdac_data< #endif // FEATURE_EH_FUNCLETS CDAC_TYPE_END(FaultingExceptionFrame) +// ArgumentRegisters struct is different on each platform +CDAC_TYPE_BEGIN(ArgumentRegisters) +CDAC_TYPE_SIZE(sizeof(ArgumentRegisters)) +#if defined(TARGET_ARM) + +CDAC_TYPE_FIELD(ArgumentRegisters, /*nuint*/, R0, offsetof(ArgumentRegisters, r[0])) +CDAC_TYPE_FIELD(ArgumentRegisters, /*nuint*/, R1, offsetof(ArgumentRegisters, r[1])) +CDAC_TYPE_FIELD(ArgumentRegisters, /*nuint*/, R2, offsetof(ArgumentRegisters, r[2])) +CDAC_TYPE_FIELD(ArgumentRegisters, /*nuint*/, R3, offsetof(ArgumentRegisters, r[3])) + +#endif // TARGET_ARM +CDAC_TYPE_END(ArgumentRegisters) + // CalleeSavedRegisters struct is different on each platform CDAC_TYPE_BEGIN(CalleeSavedRegisters) CDAC_TYPE_SIZE(sizeof(CalleeSavedRegisters)) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index cda5f7de597926..f222850414cd8f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -103,6 +103,7 @@ public enum DataType TransitionBlock, DebuggerEval, + ArgumentRegisters, CalleeSavedRegisters, HijackArgs, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs index efb93ddf5ea257..9ca2590e42b4b1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs @@ -43,4 +43,21 @@ public override void HandleInlinedCallFrame(InlinedCallFrame inlinedCallFrame) _holder.Context.R9 = (uint)spAfterProlog; } + + public override void HandleTransitionFrame(FramedMethodFrame framedMethodFrame) + { + // Call the base method to handle common logic + base.HandleTransitionFrame(framedMethodFrame); + + Data.TransitionBlock transitionBlock = _target.ProcessedData.GetOrAdd(framedMethodFrame.TransitionBlockPtr); + + if (transitionBlock.ArgumentRegisters is not TargetPointer argumentRegistersPtr) + { + throw new InvalidOperationException("ARM TransitionBlock does not have ArgumentRegisters set"); + } + + // On ARM, TransitionFrames update the argument registers + Data.ArgumentRegisters argumentRegisters = _target.ProcessedData.GetOrAdd(argumentRegistersPtr); + UpdateFromRegisterDict(argumentRegisters.Registers); + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ArgumentRegisters.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ArgumentRegisters.cs new file mode 100644 index 00000000000000..79f2d9f54963d5 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ArgumentRegisters.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class ArgumentRegisters : IData +{ + static ArgumentRegisters IData.Create(Target target, TargetPointer address) + => new ArgumentRegisters(target, address); + + public ArgumentRegisters(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ArgumentRegisters); + Dictionary registers = new Dictionary(type.Fields.Count); + foreach ((string name, Target.FieldInfo field) in type.Fields) + { + TargetNUInt value = target.ReadNUInt(address + (ulong)field.Offset); + registers.Add(name, value); + } + Registers = registers; + } + + public IReadOnlyDictionary Registers { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs index b6a0d9c666b67e..58aae33418c2bb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs @@ -13,8 +13,18 @@ public TransitionBlock(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.TransitionBlock); ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); CalleeSavedRegisters = address + (ulong)type.Fields[nameof(CalleeSavedRegisters)].Offset; + + if (type.Fields.ContainsKey(nameof(ArgumentRegisters))) + { + ArgumentRegisters = target.ReadPointer(address + (ulong)type.Fields[nameof(ArgumentRegisters)].Offset); + } } public TargetPointer ReturnAddress { get; } public TargetPointer CalleeSavedRegisters { get; } + + /// + /// Only available on ARM targets. + /// + public TargetPointer? ArgumentRegisters { get; } } From 68a2278ca0c706630a59a93189661032f558b53b Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:05:36 -0400 Subject: [PATCH 14/20] update FEF handling --- .../FrameHandling/AMD64FrameHandler.cs | 20 +++++++++---------- .../FrameHandling/ARM64FrameHandler.cs | 20 +++++++++---------- .../FrameHandling/ARMFrameHandler.cs | 11 +--------- .../FrameHandling/BaseFrameHandler.cs | 5 +++++ .../FrameHandling/X86FrameHandler.cs | 19 +++++++++--------- 5 files changed, 35 insertions(+), 40 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs index b3950dd1205086..86d36f963343b4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs @@ -11,16 +11,7 @@ internal class AMD64FrameHandler(Target target, ContextHolder cont { private readonly ContextHolder _holder = contextHolder; - void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) - { - _holder.ReadFromAddress(_target, frame.TargetContext); - - // Clear the CONTEXT_XSTATE, since the AMD64Context contains just plain CONTEXT structure - // that does not support holding any extended state. - _holder.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); - } - - void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + public void HandleHijackFrame(HijackFrame frame) { HijackArgsAMD64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); @@ -40,4 +31,13 @@ void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(args.CalleeSavedRegisters); UpdateFromRegisterDict(calleeSavedRegisters.Registers); } + + public override void HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + base.HandleFaultingExceptionFrame(frame); + + // Clear the CONTEXT_XSTATE, since the AMD64Context contains just plain CONTEXT structure + // that does not support holding any extended state. + _holder.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs index 0c9f32c6560e81..4069bf77fa475b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs @@ -13,16 +13,7 @@ internal class ARM64FrameHandler(Target target, ContextHolder cont { private readonly ContextHolder _holder = contextHolder; - void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) - { - _holder.ReadFromAddress(_target, frame.TargetContext); - - // Clear the CONTEXT_XSTATE, since the ARM64Context contains just plain CONTEXT structure - // that does not support holding any extended state. - _holder.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); - } - - void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + public void HandleHijackFrame(HijackFrame frame) { HijackArgsARM64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); @@ -37,4 +28,13 @@ void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) UpdateFromRegisterDict(args.Registers); } + + public override void HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + base.HandleFaultingExceptionFrame(frame); + + // Clear the CONTEXT_XSTATE, since the ARM64Context contains just plain CONTEXT structure + // that does not support holding any extended state. + _holder.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs index 9ca2590e42b4b1..c64f28c832f041 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs @@ -13,16 +13,7 @@ internal class ARMFrameHandler(Target target, ContextHolder contextH { private readonly ContextHolder _holder = contextHolder; - void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) - { - if (frame.TargetContext is not TargetPointer targetContext) - { - throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); - } - _holder.ReadFromAddress(_target, targetContext); - } - - void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + public void HandleHijackFrame(HijackFrame frame) { // TODO(cdacarm) throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs index 7fbcec81c9cd31..7b341491fc8c29 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs @@ -72,6 +72,11 @@ public virtual void HandleResumableFrame(ResumableFrame frame) _context.ReadFromAddress(_target, frame.TargetContextPtr); } + public virtual void HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + _context.ReadFromAddress(_target, frame.TargetContext); + } + public virtual void HandleTailCallFrame(TailCallFrame tailCallFrame) { throw new InvalidOperationException("TailCallFrame handling is not implemented on the target platform."); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs index 6f7b33cbda1412..da24f8a1b6831c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs @@ -11,16 +11,6 @@ internal class X86FrameHandler(Target target, ContextHolder contextH { private readonly ContextHolder _context = contextHolder; - - public void HandleFaultingExceptionFrame(FaultingExceptionFrame frame) - { - _context.ReadFromAddress(_target, frame.TargetContext); - - // Clear the CONTEXT_XSTATE, since the X86Context contains just plain CONTEXT structure - // that does not support holding any extended state. - _context.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); - } - public void HandleHijackFrame(HijackFrame frame) { HijackArgsX86 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); @@ -47,6 +37,15 @@ public override void HandleTailCallFrame(TailCallFrame frame) UpdateFromRegisterDict(calleeSavedRegisters.Registers); } + public override void HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + base.HandleFaultingExceptionFrame(frame); + + // Clear the CONTEXT_XSTATE, since the X86Context contains just plain CONTEXT structure + // that does not support holding any extended state. + _context.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + } + public override void HandleFuncEvalFrame(FuncEvalFrame funcEvalFrame) { Data.DebuggerEval debuggerEval = _target.ProcessedData.GetOrAdd(funcEvalFrame.DebuggerEvalPtr); From 412f15186087a6c5d80a83a02b65c9ed7d54e3cd Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 23 Jun 2025 13:32:54 -0400 Subject: [PATCH 15/20] ARM support HijackFrame --- .../debug/runtimeinfo/datadescriptor.h | 15 +++++++++++ .../FrameHandling/ARM64FrameHandler.cs | 4 +-- .../FrameHandling/ARMFrameHandler.cs | 14 ++++++++-- .../FrameHandling/X86FrameHandler.cs | 2 +- .../{HijackArgsX86.cs => HijackArgs.cs} | 8 +++--- .../Data/Frames/HijackArgsARM64.cs | 27 ------------------- 6 files changed, 34 insertions(+), 36 deletions(-) rename src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/{HijackArgsX86.cs => HijackArgs.cs} (74%) delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 33a2bc21d5f592..dd041eb2e37d0f 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -794,6 +794,21 @@ CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Eax, offsetof(HijackArgs, Eax)) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Ebp, offsetof(HijackArgs, Ebp)) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Eip, offsetof(HijackArgs, Eip)) +#elif defined(TARGET_ARM) + +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R0, offsetof(HijackArgs, R0)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R1, offsetof(HijackArgs, R1)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R2, offsetof(HijackArgs, R2)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R4, offsetof(HijackArgs, R4)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R5, offsetof(HijackArgs, R5)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R6, offsetof(HijackArgs, R6)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R7, offsetof(HijackArgs, R7)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R8, offsetof(HijackArgs, R8)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R9, offsetof(HijackArgs, R9)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R10, offsetof(HijackArgs, R10)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R11, offsetof(HijackArgs, R11)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Lr, offsetof(HijackArgs, Lr)) + #endif // Platform switch CDAC_TYPE_END(HijackArgs) #endif // FEATURE_HIJACK diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs index 4069bf77fa475b..5efa802f7bd725 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs @@ -15,13 +15,13 @@ internal class ARM64FrameHandler(Target target, ContextHolder cont public void HandleHijackFrame(HijackFrame frame) { - HijackArgsARM64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); + 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 8"); + Debug.Assert(hijackArgsSize % 8 == 0, "HijackArgs contains register values and should be a multiple of the pointer size (8 bytes for ARM64)"); // 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; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs index c64f28c832f041..55b712edb05ad4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs @@ -15,8 +15,18 @@ internal class ARMFrameHandler(Target target, ContextHolder contextH public void HandleHijackFrame(HijackFrame frame) { - // TODO(cdacarm) - throw new NotImplementedException(); + 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 % 4 == 0, "HijackArgs contains register values and should be a multiple of the pointer size (4 bytes for ARM)"); + // The stack must be multiple of 8. So if hijackArgsSize is not multiple of 8 then there must be padding of 4 bytes + hijackArgsSize += hijackArgsSize % 8; + _holder.StackPointer = frame.HijackArgsPtr + hijackArgsSize; + + UpdateFromRegisterDict(args.Registers); } public override void HandleInlinedCallFrame(InlinedCallFrame inlinedCallFrame) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs index da24f8a1b6831c..af2d60cd31c156 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs @@ -13,7 +13,7 @@ internal class X86FrameHandler(Target target, ContextHolder contextH public void HandleHijackFrame(HijackFrame frame) { - HijackArgsX86 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); + HijackArgs args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); // The stack pointer is the address immediately following HijackArgs uint hijackArgsSize = _target.GetTypeInfo(DataType.HijackArgs).Size ?? throw new InvalidOperationException("HijackArgs size is not set"); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsX86.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgs.cs similarity index 74% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsX86.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgs.cs index 352983461fe075..6142d40a95241c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsX86.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgs.cs @@ -5,12 +5,12 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; -internal class HijackArgsX86 : IData +internal class HijackArgs : IData { - static HijackArgsX86 IData.Create(Target target, TargetPointer address) - => new HijackArgsX86(target, address); + static HijackArgs IData.Create(Target target, TargetPointer address) + => new HijackArgs(target, address); - public HijackArgsX86(Target target, TargetPointer address) + public HijackArgs(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.HijackArgs); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs deleted file mode 100644 index 73168fd286c7c7..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; - -namespace Microsoft.Diagnostics.DataContractReader.Data; - -internal class HijackArgsARM64 : IData -{ - static HijackArgsARM64 IData.Create(Target target, TargetPointer address) - => new HijackArgsARM64(target, address); - - public HijackArgsARM64(Target target, TargetPointer address) - { - Target.TypeInfo type = target.GetTypeInfo(DataType.HijackArgs); - - Dictionary registers = new Dictionary(type.Fields.Count); - foreach ((string name, Target.FieldInfo field) in type.Fields) - { - TargetNUInt value = target.ReadNUInt(address + (ulong)field.Offset); - registers.Add(name, value); - } - Registers = registers; - } - - public IReadOnlyDictionary Registers { get; } -} From 5eada5b68dde2848f562149c724864982a3d7ff8 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:19:05 -0400 Subject: [PATCH 16/20] add docs for frame handling --- docs/design/datacontracts/StackWalk.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 884efb6e850de1..ae4ab1124c21ce 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -48,6 +48,7 @@ This contract depends on the following descriptors: | `FramedMethodFrame` | `TransitionBlockPtr` | Pointer to Frame's TransitionBlock | | `TransitionBlock` | `ReturnAddress` | Return address associated with the TransitionBlock | | `TransitionBlock` | `CalleeSavedRegisters` | Platform specific CalleeSavedRegisters struct associated with the TransitionBlock | +| `TransitionBlock` (arm) | `ArgumentRegisters` | ARM specific `ArgumentRegisters` struct | | `FuncEvalFrame` | `DebuggerEvalPtr` | Pointer to the Frame's DebuggerEval object | | `DebuggerEval` | `TargetContext` | Context saved inside DebuggerEval | | `DebuggerEval` | `EvalDuringException` | Flag used in processing FuncEvalFrame | @@ -57,7 +58,8 @@ This contract depends on the following descriptors: | `HijackFrame` | `HijackArgsPtr` | Pointer to the Frame's stored HijackArgs | | `HijackArgs` (amd64) | `CalleeSavedRegisters` | CalleeSavedRegisters data structure | | `HijackArgs` (amd64 Windows) | `Rsp` | Saved stack pointer | -| `HijackArgs` (arm64/x86) | For each register `r` saved in HijackArgs, `r` | Register names associated with stored register values | +| `HijackArgs` (arm/arm64/x86) | For each register `r` saved in HijackArgs, `r` | Register names associated with stored register values | +| `ArgumentRegisters` (arm) | For each register `r` saved in ArgumentRegisters, `r` | Register names associated with stored register values | | `CalleeSavedRegisters` | For each callee saved register `r`, `r` | Register names associated with stored register values | | `TailCallFrame` (x86 Windows) | `CalleeSavedRegisters` | CalleeSavedRegisters data structure | | `TailCallFrame` (x86 Windows) | `ReturnAddress` | Frame's stored instruction pointer | @@ -257,6 +259,8 @@ TransitionFrames hold a pointer to a `TransitionBlock`. The TransitionBlock hold When updating the context from a TransitionFrame, the IP, SP, and all ABI specified callee-saved registers are copied over. +* On ARM, the additional register values stored in `ArgumentRegisters` are copied over. The `TransitionBlock` holds a pointer to the `ArgumentRegister` struct containing these values. + The following Frame types also use this mechanism: * FramedMethodFrame * CLRToCOMMethodFrame @@ -292,7 +296,9 @@ HijackFrames carry a IP (ReturnAddress) and a pointer to `HijackArgs`. All platf * x64 - On x64, HijackArgs contains a CalleeSavedRegister struct. The saved registers values contained in the struct are copied over to the working context. * Windows - On Windows, HijackArgs also contains the SP value directly which is copied over to the working context. * Non-Windows - On OS's other than Windows, HijackArgs does not contain an SP value. Instead since the HijackArgs struct lives on the stack, the SP is `&hijackArgs + sizeof(HijackArgs)`. This value is also copied over. -* arm64 - Unlike on x64, on arm64 HijackArgs contains a list of register values instead of the CalleeSavedRegister struct. These values are copied over to the working context. The SP is fetched using the same technique as on x64 non-Windows where `SP = &hijackArgs + sizeof(HijackArgs)` and is copied over to the working context. +* x86 - On x86, HijackArgs contains a list of register values instead of the CalleeSavedRegister struct. These values are copied over to the working context. The SP copied over to the working context and found using `SP = &hijackArgs + sizeof(HijackArgs)`. +* arm64 - Unlike on x64, on arm64 HijackArgs contains a list of register values instead of the CalleeSavedRegister struct. These values are copied over to the working context. The SP is fetched using the same technique as on x64 non-Windows where `SP = &hijackArgs + sizeof(HijackArgs) + (hijackArgsSize % 16)` and is copied over to the working context. Note: `HijackArgs` may be padded to maintain 16 byte stack alignment. +* arm - Similar to arm64, HijackArgs contains a list of register values. These values are copied over to the working context. The SP is fetched using the same technique as arm64 where `SP = &hijackArgs + sizeof(HijackArgs) + (hijackArgsSize % 8)` and is copied over to the working context. Note: `HijackArgs` may be padded to maintain 8 byte stack alignment. #### TailCallFrame From 65f3d7126ebe499b99772eed23109c5a8ebaf036 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:00:01 -0400 Subject: [PATCH 17/20] improve error message --- .../managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index f56cab86548497..414dfe87a4d5b6 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -782,7 +782,7 @@ int ISOSDacInterface.GetMethodDescName(ClrDataAddress addr, uint count, char* na if (hr == HResults.S_OK) { Debug.Assert(pNeeded == null || *pNeeded == neededLocal); - Debug.Assert(name == null || new ReadOnlySpan(nameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(name))); + Debug.Assert(name == null || new ReadOnlySpan(nameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(name)), $"cDAC: {new string(name)}, DAC: {new string(nameLocal, 0, (int)neededLocal - 1)}"); } } #endif From b3bece908e8c75df8095c677685e1452622a72e6 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:27:33 -0400 Subject: [PATCH 18/20] fix bug in HijackFrame handling --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index dd041eb2e37d0f..83e42dc3c36f85 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -797,7 +797,6 @@ CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Eip, offsetof(HijackArgs, Eip)) #elif defined(TARGET_ARM) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R0, offsetof(HijackArgs, R0)) -CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R1, offsetof(HijackArgs, R1)) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R2, offsetof(HijackArgs, R2)) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R4, offsetof(HijackArgs, R4)) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R5, offsetof(HijackArgs, R5)) @@ -807,7 +806,6 @@ CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R8, offsetof(HijackArgs, R8)) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R9, offsetof(HijackArgs, R9)) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R10, offsetof(HijackArgs, R10)) CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, R11, offsetof(HijackArgs, R11)) -CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Lr, offsetof(HijackArgs, Lr)) #endif // Platform switch CDAC_TYPE_END(HijackArgs) From 997091bb51be776cadf89d35b844fd22c5dbf65f Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:03:18 -0400 Subject: [PATCH 19/20] fix TransitionFrame handling --- .../Data/Frames/TransitionBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs index 58aae33418c2bb..121e7ce2561650 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs @@ -16,7 +16,7 @@ public TransitionBlock(Target target, TargetPointer address) if (type.Fields.ContainsKey(nameof(ArgumentRegisters))) { - ArgumentRegisters = target.ReadPointer(address + (ulong)type.Fields[nameof(ArgumentRegisters)].Offset); + ArgumentRegisters = address + (ulong)type.Fields[nameof(ArgumentRegisters)].Offset; } } From bab7c4485b467061a4730ff8519da4e9ad32fe78 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:03:39 -0400 Subject: [PATCH 20/20] doc nit --- docs/design/datacontracts/StackWalk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index ae4ab1124c21ce..e8d46fd396bd9e 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -42,7 +42,7 @@ This contract depends on the following descriptors: | `InlinedCallFrame` | `CallSiteSP` | SP saved in Frame | | `InlinedCallFrame` | `CallerReturnAddress` | Return address saved in Frame | | `InlinedCallFrame` | `CalleeSavedFP` | FP saved in Frame | -| `InlinedCallFrame` (arm32) | `SPAfterProlog` | Value of the SP after prolog. Used on ARM to maintain additional JIT invariant | +| `InlinedCallFrame` (arm) | `SPAfterProlog` | Value of the SP after prolog. Used on ARM to maintain additional JIT invariant | | `SoftwareExceptionFrame` | `TargetContext` | Context object saved in Frame | | `SoftwareExceptionFrame` | `ReturnAddress` | Return address saved in Frame | | `FramedMethodFrame` | `TransitionBlockPtr` | Pointer to Frame's TransitionBlock |