File: Contracts\StackWalk\Context\ARM\ARMUnwinder.cs
Web Access
Project: src\src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts)
// 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)
        {
            return false;
        }

        uint startingPc = context.Pc;
        uint startingSp = context.Sp;

        TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh);
        TargetPointer unwindInfoAddr = _eman.GetUnwindInfo(cbh);

        if (unwindInfoAddr == TargetPointer.Null)
            return false;

        Data.RuntimeFunction functionEntry = _target.ProcessedData.GetOrAdd<Data.RuntimeFunction>(unwindInfoAddr);

        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<uint>(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<uint>(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<uint>(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<byte>(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<byte>(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<byte>(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<byte>(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<byte>(unwindCodePtr) | ((curCode << 14) & 0x4000u)));
                        unwindCodePtr++;
                        break;

                    //
                    // 0xee: 0-byte custom opcode
                    //

                    case 0xee:
                        if (unwindCodePtr >= unwindCodesEndPtr)
                        {
                            status = false;
                            break;
                        }
                        param = _target.Read<byte>(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<byte>(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<byte>(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<byte>(unwindCodePtr);
                        unwindCodePtr++;
                        status = PopVfpRegisterRange(
                            ref context,
                            16 + (param >> 4), 16 + (param & 0x0f));
                        break;

                    //
                    // 0xf7: 2-byte stack adjust ... add sp, sp, <reg>
                    // 0xf9: 4-byte stack adjust ... add sp, sp, <reg>
                    //
                    case 0xf7:
                    case 0xf9:
                        if (unwindCodePtr + 2 > unwindCodesEndPtr)
                        {
                            status = false;
                            break;
                        }
                        context.Sp += 4u * 256u * _target.Read<byte>(unwindCodePtr);
                        context.Sp += 4u * _target.Read<byte>(unwindCodePtr + 1);
                        unwindCodePtr += 2;
                        break;

                    //
                    // 0xf8: 2-byte stack adjust ... add sp, sp, <reg>
                    // 0xfa: 4-byte stack adjust ... add sp, sp, <reg>
                    //
                    case 0xf8:
                    case 0xfa:
                        if (unwindCodePtr + 3 > unwindCodesEndPtr)
                        {
                            status = false;
                            break;
                        }
                        context.Sp += 4u * 256u * 256u * _target.Read<byte>(unwindCodePtr);
                        context.Sp += 4u * 256u * _target.Read<byte>(unwindCodePtr + 1);
                        context.Sp += 4u * _target.Read<byte>(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<uint>(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<ulong>(sourceAddress);
            }
        }

        // Link register and PC next
        if (offsets.LrOffset != OFFSET_NONE)
        {
            TargetPointer sourceAddress = context.Sp + offsets.LrOffset;
            context.Lr = _target.Read<uint>(sourceAddress);
        }
        if (offsets.PcOffset != OFFSET_NONE)
        {
            TargetPointer sourceAddress = context.Sp + offsets.PcOffset;
            context.Pc = _target.Read<uint>(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<uint>(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<byte>(unwindCodePtr);
        while (unwindCodePtr < unwindCodesEndPtr && opcode < 0xfd)
        {
            byte tableValue = UnwindOpTable[opcode];
            scopeSize += (uint)tableValue >> 4;
            unwindCodePtr += tableValue & 0xfu;
            opcode = _target.Read<byte>(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<ulong>(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<uint>(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)
    {
        if (!context.TrySetRegister(regIndex, new TargetNUInt(value)))
            throw new ArgumentOutOfRangeException(nameof(regIndex));
    }

    private static uint GetRegister(ref ARMContext context, int regIndex)
        => context.TryReadRegister(regIndex, out TargetNUInt value) ? (uint)value.Value : throw new ArgumentOutOfRangeException(nameof(regIndex));

    #endregion
}