diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index aec5ab51f24554..e8d46fd396bd9e 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -42,11 +42,13 @@ 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` (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 | | `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 | @@ -56,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 | @@ -244,6 +247,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. @@ -254,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 @@ -289,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 diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 30d158eea04e8d..83e42dc3c36f85 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -706,6 +706,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) @@ -723,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 @@ -788,6 +794,19 @@ 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*/, 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)) + #endif // Platform switch CDAC_TYPE_END(HijackArgs) #endif // FEATURE_HIJACK @@ -807,6 +826,19 @@ CDAC_TYPE_FIELD(TailCallFrame, /*pointer*/, ReturnAddress, cdac_data(_eman.GetUnwindInfo(cbh)); + + 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. + // + bool keepReading = true; + while (unwindCodePtr < unwindCodesEndPtr && keepReading && 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: + keepReading = false; + 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..af85910ff53087 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs @@ -0,0 +1,235 @@ +// 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; +} 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 e687784927153f..c841ba7b7a80be 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 @@ -30,6 +30,7 @@ public static IPlatformAgnosticContext GetContextForPlatform(Target target) { RuntimeInfoArchitecture.X86 => new ContextHolder(), 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..c2e0bf073db683 --- /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() => HashCode.Combine(Low, High); +} 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..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 @@ -13,28 +13,28 @@ internal class ARM64FrameHandler(Target target, ContextHolder cont { private readonly ContextHolder _holder = contextHolder; - void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + public void HandleHijackFrame(HijackFrame 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) - { - 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; 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 new file mode 100644 index 00000000000000..55b712edb05ad4 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARMFrameHandler.cs @@ -0,0 +1,64 @@ +// 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; + + public void HandleHijackFrame(HijackFrame frame) + { + HijackArgs args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); + + _holder.InstructionPointer = frame.ReturnAddress; + + // The stack pointer is the address immediately following HijackArgs + uint hijackArgsSize = _target.GetTypeInfo(DataType.HijackArgs).Size ?? throw new InvalidOperationException("HijackArgs size is not set"); + Debug.Assert(hijackArgsSize % 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) + { + 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; + } + + 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/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/FrameIterator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs index 4d482922bc72da..ccccb35550aac7 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 @@ -180,6 +180,7 @@ private IPlatformFrameHandler GetFrameHandler(IPlatformAgnosticContext context) { ContextHolder contextHolder => new X86FrameHandler(target, contextHolder), 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/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..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 @@ -11,19 +11,9 @@ 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); + 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"); @@ -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); 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/ArgumentRegisters.cs similarity index 65% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ArgumentRegisters.cs index 73168fd286c7c7..79f2d9f54963d5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ArgumentRegisters.cs @@ -5,15 +5,14 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; -internal class HijackArgsARM64 : IData +internal class ArgumentRegisters : IData { - static HijackArgsARM64 IData.Create(Target target, TargetPointer address) - => new HijackArgsARM64(target, address); + static ArgumentRegisters IData.Create(Target target, TargetPointer address) + => new ArgumentRegisters(target, address); - public HijackArgsARM64(Target target, TargetPointer address) + public ArgumentRegisters(Target target, TargetPointer address) { - Target.TypeInfo type = target.GetTypeInfo(DataType.HijackArgs); - + Target.TypeInfo type = target.GetTypeInfo(DataType.ArgumentRegisters); Dictionary registers = new Dictionary(type.Fields.Count); foreach ((string name, Target.FieldInfo field) in type.Fields) { 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/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; } } 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..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 @@ -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 = address + (ulong)type.Fields[nameof(ArgumentRegisters)].Offset; + } } public TargetPointer ReturnAddress { get; } public TargetPointer CalleeSavedRegisters { get; } + + /// + /// Only available on ARM targets. + /// + public TargetPointer? ArgumentRegisters { get; } } 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