File: Contracts\StackWalk\Context\AMD64\AMD64Unwinder.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 System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions;
using Microsoft.Diagnostics.DataContractReader.Data;
using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.AMD64Context;

namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.AMD64;

internal class AMD64Unwinder(Target target)
{
    private const byte SIZE64_PREFIX = 0x48;
    private const byte ADD_IMM8_OP = 0x83;
    private const byte ADD_IMM32_OP = 0x81;
    private const byte JMP_IMM8_OP = 0xeb;
    private const byte JMP_IMM32_OP = 0xe9;
    private const byte JMP_IND_OP = 0xff;
    private const byte LEA_OP = 0x8d;
    private const byte REPNE_PREFIX = 0xf2;
    private const byte REP_PREFIX = 0xf3;
    private const byte POP_OP = 0x58;
    private const byte RET_OP = 0xc3;
    private const byte RET_OP_2 = 0xc2;

    private const uint UNWIND_CHAIN_LIMIT = 32;

    private readonly Target _target = target;
    private readonly IExecutionManager _eman = target.Contracts.ExecutionManager;

    private readonly bool _unixAMD64ABI = target.Contracts.RuntimeInfo.GetTargetOperatingSystem() != RuntimeInfoOperatingSystem.Windows;

    public bool Unwind(ref AMD64Context context)
    {
        ulong branchTarget;
        uint epilogueOffset = 0;
        TargetPointer establisherFrame;
        uint frameOffset;
        uint frameRegister = 0;
        uint index;
        bool inEpilogue = false;
        Data.RuntimeFunction? primaryFunctionEntry;
        UnwindCode unwindOp;

        if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh)
            return false;

        TargetPointer controlPC = context.InstructionPointer;

        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.EndAddress is null)
            return false;
        if (GetUnwindInfoHeader(imageBase + functionEntry.UnwindData) is not UnwindInfoHeader unwindInfo)
            return false;

        //
        // If the specified function does not use a frame pointer, then the
        // establisher frame is the contents of the stack pointer. This may
        // not actually be the real establisher frame if control left the
        // function from within the prologue. In this case the establisher
        // frame may be not required since control has not actually entered
        // the function and prologue entries cannot refer to the establisher
        // frame before it has been established, i.e., if it has not been
        // established, then no save unwind codes should be encountered during
        // the unwind operation.
        //
        // If the specified function uses a frame pointer and control left the
        // function outside of the prologue or the unwind information contains
        // a chained information structure, then the establisher frame is the
        // contents of the frame pointer.
        //
        // If the specified function uses a frame pointer and control left the
        // function from within the prologue, then the set frame pointer unwind
        // code must be looked up in the unwind codes to determine if the
        // contents of the stack pointer or the contents of the frame pointer
        // should be used for the establisher frame. This may not actually be
        // the real establisher frame. In this case the establisher frame may
        // not be required since control has not actually entered the function
        // and prologue entries cannot refer to the establisher frame before it
        // has been established, i.e., if it has not been established, then no
        // save unwind codes should be encountered during the unwind operation.
        //
        // N.B. The correctness of these assumptions is based on the ordering of
        //      unwind codes.
        //

        uint prologOffset = (uint)(controlPC - (functionEntry.BeginAddress + imageBase));
        if (unwindInfo.FrameRegister == 0)
        {
            establisherFrame = context.StackPointer;
        }
        else if ((prologOffset >= unwindInfo.SizeOfProlog) || unwindInfo.Flags.HasFlag(UnwindInfoHeader.Flag.UNW_FLAG_CHAININFO))
        {
            frameOffset = unwindInfo.FrameOffset;

            if (_unixAMD64ABI)
            {
                // If UnwindInfo->FrameOffset == 15 (the maximum value), then there might be a UWOP_SET_FPREG_LARGE.
                // However, it is still legal for a UWOP_SET_FPREG to set UnwindInfo->FrameOffset == 15 (since this
                // was always part of the specification), so we need to look through the UnwindCode array to determine
                // if there is indeed a UWOP_SET_FPREG_LARGE. If we don't find UWOP_SET_FPREG_LARGE, then just use
                // (scaled) FrameOffset of 240, as before. (We don't verify there is a UWOP_SET_FPREG code, but we could.)
                if (frameOffset == 15)
                {
                    index = 0;
                    while (index < unwindInfo.CountOfUnwindCodes)
                    {
                        unwindOp = GetUnwindCode(unwindInfo, index);
                        if (unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_SET_FPREG_LARGE)
                        {
                            frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset;
                            frameOffset += (uint)(GetUnwindCode(unwindInfo, index + 2).FrameOffset << 16);
                            break;
                        }
                        index += unwindOp.UnwindOpSlots();
                    }
                }
            }

            establisherFrame = GetRegister(context, unwindInfo.FrameRegister);
            establisherFrame -= frameOffset * 16;
        }
        else
        {
            frameOffset = unwindInfo.FrameOffset;
            index = 0;
            while (index < unwindInfo.CountOfUnwindCodes)
            {
                unwindOp = GetUnwindCode(unwindInfo, index);
                if (unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_SET_FPREG)
                    break;
                if (_unixAMD64ABI)
                {
                    if (unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_SET_FPREG_LARGE)
                    {
                        UnwinderAssert(unwindInfo.FrameOffset == 15, "FrameOffset should be 15 for UWOP_SET_FPREG_LARGE.");
                        frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset;
                        frameOffset += (uint)(GetUnwindCode(unwindInfo, index + 2).FrameOffset << 16);
                        break;
                    }
                }
                index += unwindOp.UnwindOpSlots();
            }

            if (prologOffset >= GetUnwindCode(unwindInfo, index).CodeOffset)
            {
                establisherFrame = GetRegister(context, unwindInfo.FrameRegister);
                establisherFrame -= frameOffset * 16;
            }
            else
            {
                establisherFrame = context.Rsp;
            }
        }

        //
        // Check if control left the specified function during an epilogue
        // sequence and emulate the execution of the epilogue forward and
        // return no exception handler.
        //
        // If the unwind version indicates the absence of epilogue unwind codes
        // this is done by emulating the instruction stream. Otherwise, epilogue
        // detection and emulation is performed using the function unwind codes.
        //

        if (unwindInfo.Version < 2)
        {
            TargetPointer nextByte = controlPC;

            //
            // Check for one of:
            //
            //   add rsp, imm8
            //       or
            //   add rsp, imm32
            //       or
            //   lea rsp, -disp8[fp]
            //       or
            //   lea rsp, -disp32[fp]
            //

            if ((ReadByteAt(nextByte) == SIZE64_PREFIX) &&
                (ReadByteAt(nextByte + 1) == ADD_IMM8_OP) &&
                (ReadByteAt(nextByte + 2) == 0xc4))
            {

                //
                // add rsp, imm8.
                //

                nextByte += 4;

            }
            else if ((ReadByteAt(nextByte) == SIZE64_PREFIX) &&
                     (ReadByteAt(nextByte + 1) == ADD_IMM32_OP) &&
                     (ReadByteAt(nextByte + 2) == 0xc4))
            {

                //
                // add rsp, imm32.
                //

                nextByte += 7;

            }
            else if (((ReadByteAt(nextByte) & 0xfe) == SIZE64_PREFIX) &&
                      (ReadByteAt(nextByte + 1) == LEA_OP))
            {
                frameRegister = (uint)(((ReadByteAt(nextByte) & 0x1) << 3) | (ReadByteAt(nextByte + 2) & 0x7));

                if ((frameRegister != 0) &&
                    (frameRegister == unwindInfo.FrameRegister))
                {

                    if ((ReadByteAt(nextByte + 2) & 0xf8) == 0x60)
                    {

                        //
                        // lea rsp, disp8[fp].
                        //

                        nextByte += 4;

                    }
                    else if ((ReadByteAt(nextByte + 2) & 0xf8) == 0xa0)
                    {

                        //
                        // lea rsp, disp32[fp].
                        //

                        nextByte += 7;
                    }
                }
            }

            //
            // Check for any number of:
            //
            //   pop nonvolatile-integer-register[0..15].
            //

            while (true)
            {
                if ((ReadByteAt(nextByte) & 0xf8) == POP_OP)
                {
                    nextByte += 1;
                }
                else if (IsRexPrefix(ReadByteAt(nextByte)) &&
                       ((ReadByteAt(nextByte + 1) & 0xf8) == POP_OP))
                {
                    nextByte += 2;
                }
                else
                {
                    break;
                }
            }

            //
            // A REPNE prefix may optionally precede a control transfer
            // instruction with no effect on unwinding.
            //

            if (ReadByteAt(nextByte) == REPNE_PREFIX)
            {
                nextByte += 1;
            }

            //
            // If the next instruction is a return or an appropriate jump, then
            // control is currently in an epilogue and execution of the epilogue
            // should be emulated. Otherwise, execution is not in an epilogue and
            // the prologue should be unwound.
            //

            inEpilogue = false;
            if ((ReadByteAt(nextByte) == RET_OP) ||
                (ReadByteAt(nextByte) == RET_OP_2) ||
               ((ReadByteAt(nextByte) == REP_PREFIX) && (ReadByteAt(nextByte + 1) == RET_OP)))
            {

                //
                // A return is an unambiguous indication of an epilogue.
                //

                inEpilogue = true;

            }
            else if ((ReadByteAt(nextByte) == JMP_IMM8_OP) ||
                     (ReadByteAt(nextByte) == JMP_IMM32_OP))
            {

                //
                // An unconditional branch to a target that is equal to the start of
                // or outside of this routine is logically a call to another function.
                //

                branchTarget = nextByte - imageBase;
                if (ReadByteAt(nextByte) == JMP_IMM8_OP)
                {
                    // sign-extend the 8-bit immediate value
                    branchTarget += 2u + (ulong)(sbyte)ReadByteAt(nextByte + 1);
                }
                else
                {
                    // sign-extend the 32-bit immediate value
                    int delta = ReadByteAt(nextByte + 1) |
                                (ReadByteAt(nextByte + 2) << 8) |
                                (ReadByteAt(nextByte + 3) << 16) |
                                (ReadByteAt(nextByte + 4) << 24);
                    branchTarget += (ulong)(5 + delta);
                }

                //
                // Determine whether the branch target refers to code within this
                // function. If not, then it is an epilogue indicator.
                //
                // A branch to the start of self implies a recursive call, so
                // is treated as an epilogue.
                //

                if (branchTarget < functionEntry.BeginAddress ||
                    branchTarget >= functionEntry.EndAddress)
                {

                    //
                    // The branch target is outside of the region described by
                    // this function entry.  See whether it is contained within
                    // an indirect function entry associated with this same
                    // function.
                    //
                    // If not, then the branch target really is outside of
                    // this function.
                    //

                    primaryFunctionEntry = SameFunction(
                        functionEntry,
                        imageBase,
                        branchTarget + imageBase);

                    if ((primaryFunctionEntry is null) ||
                        (branchTarget == primaryFunctionEntry.BeginAddress))
                    {
                        inEpilogue = true;
                    }

                }
                else if ((branchTarget == functionEntry.BeginAddress) &&
                        (!unwindInfo.Flags.HasFlag(UnwindInfoHeader.Flag.UNW_FLAG_CHAININFO)))
                {
                    inEpilogue = true;
                }

            }
            else if ((ReadByteAt(nextByte) == JMP_IND_OP) && (ReadByteAt(nextByte + 1) == 0x25))
            {
                //
                // An unconditional jump indirect.
                //
                // This is a jmp outside of the function, probably a tail call
                // to an import function.
                //

                inEpilogue = true;
            }
            else if (((ReadByteAt(nextByte) & 0xf8) == SIZE64_PREFIX) &&
                    (ReadByteAt(nextByte + 1) == 0xff) &&
                    (ReadByteAt(nextByte + 2) & 0x38) == 0x20)
            {
                //
                // This is an indirect jump opcode: 0x48 0xff /4.  The 64-bit
                // flag (REX.W) is always redundant here, so its presence is
                // overloaded to indicate a branch out of the function - a tail
                // call.
                //
                // Such an opcode is an unambiguous epilogue indication.
                //

                inEpilogue = true;
            }

            if (inEpilogue)
            {
                nextByte = controlPC;

                //
                // Emulate one of (if any):
                //
                //   add rsp, imm8
                //       or
                //   add rsp, imm32
                //       or
                //   lea rsp, disp8[frame-register]
                //       or
                //   lea rsp, disp32[frame-register]
                //

                if ((ReadByteAt(nextByte) & 0xf8) == SIZE64_PREFIX)
                {

                    if (ReadByteAt(nextByte + 1) == ADD_IMM8_OP)
                    {

                        //
                        // add rsp, imm8.
                        //

                        context.Rsp += ReadByteAt(nextByte + 3);
                        nextByte += 4;

                    }
                    else if (ReadByteAt(nextByte + 1) == ADD_IMM32_OP)
                    {

                        //
                        // add rsp, imm32.
                        //

                        int displacement = ReadByteAt(nextByte + 3) |
                                          (ReadByteAt(nextByte + 4) << 8) |
                                          (ReadByteAt(nextByte + 5) << 16) |
                                          (ReadByteAt(nextByte + 6) << 24);
                        context.Rsp += (uint)displacement;
                        nextByte += 7;

                    }
                    else if (ReadByteAt(nextByte + 1) == LEA_OP)
                    {
                        if ((ReadByteAt(nextByte + 2) & 0xf8) == 0x60)
                        {

                            //
                            // lea rsp, disp8[frame-register].
                            //

                            context.Rsp = GetRegister(context, (byte)frameRegister);
                            context.Rsp += ReadByteAt(nextByte + 3);
                            nextByte += 4;

                        }
                        else if ((ReadByteAt(nextByte + 2) & 0xf8) == 0xa0)
                        {

                            //
                            // lea rsp, disp32[frame-register].
                            //

                            int displacement = ReadByteAt(nextByte + 3) |
                                            (ReadByteAt(nextByte + 4) << 8) |
                                            (ReadByteAt(nextByte + 5) << 16) |
                                            (ReadByteAt(nextByte + 6) << 24);
                            context.Rsp = GetRegister(context, (byte)frameRegister);
                            context.Rsp += (uint)displacement;
                            nextByte += 7;
                        }
                    }
                }

                //
                // Emulate any number of (if any):
                //
                //   pop nonvolatile-integer-register.
                //

                while (true)
                {
                    if ((ReadByteAt(nextByte) & 0xf8) == POP_OP)
                    {

                        //
                        // pop nonvolatile-integer-register[0..7]
                        //

                        byte registerNumber = (byte)(ReadByteAt(nextByte) & 0x7);
                        SetRegister(ref context, registerNumber, _target.Read<ulong>(context.Rsp));

                        context.Rsp += 8;
                        nextByte += 1;
                    }
                    else if (IsRexPrefix(ReadByteAt(nextByte)) &&
                            (ReadByteAt(nextByte + 1) & 0xf8) == POP_OP)
                    {

                        //
                        // pop nonvolatile-integer-register[8..15]
                        //

                        byte registerNumber = (byte)(((ReadByteAt(nextByte) & 1) << 3) | (ReadByteAt(nextByte + 1) & 0x7));
                        SetRegister(ref context, registerNumber, _target.Read<ulong>(context.Rsp));

                        context.Rsp += 8;
                        nextByte += 2;
                    }
                    else
                    {
                        break;
                    }
                }

                //
                // Emulate return and return null exception handler.
                //
                // Note: This instruction might in fact be a jmp, however
                //       we want to emulate a return regardless.
                //

                context.Rip = _target.Read<ulong>(context.Rsp);
                context.Rsp += 8;
                return true;
            }
        }
        else if (unwindInfo.CountOfUnwindCodes != 0)
        {
            UnwinderAssert(unwindInfo.Version >= 2);

            //
            // Capture the first unwind code and check if it is an epilogue code.
            // If it is not an epilogue code, the current function entry does not
            // contain any epilogues (it could represent a body region of a
            // separated function or it could represent a function which never
            // returns).
            //

            unwindOp = GetUnwindCode(unwindInfo, 0);
            if (unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_EPILOG)
            {
                uint epilogueSize = unwindOp.CodeOffset;

                UnwinderAssert(epilogueSize != 0);

                //
                // If the low bit of the OpInfo field of the first epilogue code
                // is set, the function has a single epilogue at the end of the
                // function. Otherwise, subsequent epilogue unwind codes indicate
                // the offset of the epilogue(s) from the function end and the
                // relative PC must be compared against each epilogue record.
                //
                // N.B. The relative instruction pointer may not be within the
                //      bounds of the runtime function entry if control left the
                //      function in a region described by an indirect function
                //      entry. Such a region cannot contain any epilogues.
                //

                uint relativePC = (uint)(controlPC - imageBase);
                if ((unwindOp.OpInfo & 0x1) != 0)
                {
                    epilogueOffset = functionEntry.EndAddress.Value - epilogueSize;
                    if (relativePC - epilogueOffset < epilogueSize)
                    {
                        inEpilogue = true;
                    }
                }

                if (!inEpilogue)
                {
                    for (uint i = 1; i < unwindInfo.CountOfUnwindCodes; i += 1)
                    {
                        unwindOp = GetUnwindCode(unwindInfo, i);

                        if (unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_EPILOG)
                        {
                            epilogueOffset = unwindOp.CodeOffset +
                                unwindOp.OpInfo * 256u;

                            //
                            // An epilogue offset of 0 indicates that this is
                            // a padding entry (the number of epilogue codes
                            // is a multiple of 2).
                            //

                            if (epilogueOffset == 0)
                            {
                                break;
                            }

                            epilogueOffset = functionEntry.EndAddress.Value - epilogueOffset;
                            if (relativePC - epilogueOffset < epilogueSize)
                            {
                                UnwinderAssert(epilogueOffset != functionEntry.EndAddress.Value);
                                inEpilogue = true;
                                break;
                            }

                        }
                        else
                        {
                            break;
                        }
                    }
                }

                if (inEpilogue)
                {
                    return UnwindEpilogue(
                        ref context,
                        controlPC,
                        imageBase,
                        functionEntry,
                        epilogueOffset);
                }
            }
        }

        //
        // Control left the specified function outside an epilogue. Unwind the
        // subject function and any chained unwind information.
        //

        if (!UnwindPrologue(ref context, controlPC, imageBase, establisherFrame, functionEntry))
            return false;

        //
        // If control left the specified function outside of the prologue and
        // the function has a handler that matches the specified type, then
        // return the address of the language specific exception handler.
        // Otherwise, return NULL.
        //

        // The cDAC doesn't care about handlers and therefore that logic is omitted

        return true;
    }

    private bool UnwindEpilogue(
        ref AMD64Context context,
        TargetPointer controlPC,
        TargetPointer imageBase,
        Data.RuntimeFunction functionEntry,
        uint epilogueOffset)
    {
        //
        // A canonical epilogue sequence consists of the following operations:
        //
        // 1. Optional cleanup of fixed and dynamic stack allocations, which is
        //    considered to be outside of the epilogue region.
        //
        //    add rsp, imm
        //        or
        //    lea rsp, disp[fp]
        //
        // 2. Zero or more pop nonvolatile-integer-register[0..15] instructions,
        //    which are unwound using the corresponding UWOP_PUSH_NONVOL opcodes.
        //
        //    pop r64
        //        or
        //    REX.R pop r64
        //
        // 3. An optional one-byte pop r64 to a volatile register to clean up an
        //    RFLAGS register pushed with pushfq. This is marked with a
        //    UWOP_ALLOC_SMALL 8 opcode.
        //
        //    pop rcx
        //
        // 4. A control transfer instruction (ret or jump). In both cases, there
        //    will be no prologue unwind codes remaining after the previous set of
        //    recognized operations are emulated.
        //
        //    ret 0
        //        or
        //    jmp imm
        //        or
        //    jmp [target]
        //        or
        //    iretq
        //
        // N.B. The correctness of these assumptions is based on the ordering
        //      of unwind codes and the mirroring of epilogue and prologue
        //      regions.
        //
        // Find the function's primary entry, which contains the relevant frame
        // adjustment unwind codes.
        //
        // Locate the first push unwind code. This code requires that all pushes
        // occur within a single function entry, though not necessarily within the
        // root function entry of a chained function.
        //

        uint relativePC = (uint)(controlPC - imageBase);
        uint offsetIntoEpilogue = relativePC - epilogueOffset;

        UnwindInfoHeader? unwindInfo;
        uint chainCount = 0;
        uint firstPushIndex;
        UnwindCode unwindOp = default;
        while (true)
        {
            unwindInfo = GetUnwindInfoHeader(functionEntry.UnwindData + imageBase);
            if (unwindInfo is null)
                return false;

            firstPushIndex = 0;
            while (firstPushIndex < unwindInfo.Value.CountOfUnwindCodes)
            {
                unwindOp = GetUnwindCode(unwindInfo.Value, firstPushIndex);
                if (unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_PUSH_NONVOL ||
                    unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_PUSH_MACHFRAME)
                {
                    break;
                }

                firstPushIndex += unwindOp.UnwindOpSlots();
            }

            if (firstPushIndex < unwindInfo.Value.CountOfUnwindCodes)
            {
                break;
            }

            //
            // If a chained parent function entry exists, continue looking for
            // push opcodes in the parent.
            //

            if (!unwindInfo.Value.Flags.HasFlag(UnwindInfoHeader.Flag.UNW_FLAG_CHAININFO))
            {
                break;
            }

            chainCount++;
            if (chainCount > UNWIND_CHAIN_LIMIT)
            {
                // Too many chained unwind entries, stop unwinding.
                return false;
            }

            functionEntry = _target.ProcessedData.GetOrAdd<Data.RuntimeFunction>(unwindInfo.Value.GetChainedEntryAddress());
        }

        //
        // Unwind any push codes that have not already been reversed by the
        // epilogue.
        //

        uint currentOffset = 0;
        uint index;
        for (index = firstPushIndex; index < unwindInfo.Value.CountOfUnwindCodes; index++)
        {
            unwindOp = GetUnwindCode(unwindInfo.Value, index);
            if (unwindOp.UnwindOp != UnwindCode.OpCodes.UWOP_PUSH_NONVOL)
            {
                break;
            }

            if (currentOffset >= offsetIntoEpilogue)
            {
                SetRegister(ref context, unwindOp.OpInfo, _target.Read<ulong>(context.Rsp));
                context.Rsp += 8;
            }

            //
            // POP r64 is encoded as (58h + r64) for the lower 8 general-purpose
            // registers and REX.R, (58h + r64) for r8 - r15.
            //

            currentOffset += 1;
            if (unwindOp.OpInfo >= 8)
            {
                currentOffset += 1;
            }
        }

        //
        // Check for an UWOP_ALLOC_SMALL 8 directive, which corresponds to a push
        // of the FLAGS register.
        //

        if ((index < unwindInfo.Value.CountOfUnwindCodes) &&
            (unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_ALLOC_SMALL) && (unwindOp.OpInfo == 0))
        {

            if (currentOffset >= offsetIntoEpilogue)
            {
                context.Rsp += 8;
            }

            // currentOffset += 1;
            index += 1;
        }

        //
        // Check for a machine frame.
        //

        if (index < unwindInfo.Value.CountOfUnwindCodes)
        {
            unwindOp = GetUnwindCode(unwindInfo.Value, index);
            if (unwindOp.UnwindOp == UnwindCode.OpCodes.UWOP_PUSH_MACHFRAME)
            {
                context.Rip = _target.ReadPointer(context.Rsp);
                context.Rsp = _target.ReadPointer(context.Rsp + (3 * 8));
                return true;
            }

            Debug.Fail("Any remaining operation must be a machine frame");
        }

        //
        // Emulate a return operation.
        //

        context.Rip = _target.ReadPointer(context.Rsp);
        context.Rsp += 8;
        return true;
    }

    private bool UnwindPrologue(
        ref AMD64Context context,
        TargetPointer controlPC,
        TargetPointer imageBase,
        TargetPointer frameBase,
        Data.RuntimeFunction functionEntry)
    {
        uint chainCount = 0;

        while (true)
        {
            uint index = 0;
            bool machineFrame = false;
            uint prologOffset = (uint)(controlPC - (functionEntry.BeginAddress + imageBase));

            if (GetUnwindInfoHeader(imageBase + functionEntry.UnwindData) is not UnwindInfoHeader unwindInfo)
                return false;

            while (index < unwindInfo.CountOfUnwindCodes)
            {
                //
                // If the prologue offset is greater than the next unwind code
                // offset, then simulate the effect of the unwind code.
                //

                UnwindCode unwindOp = GetUnwindCode(unwindInfo, index);

                if (_unixAMD64ABI)
                {
                    if (unwindOp.UnwindOp > UnwindCode.OpCodes.UWOP_SET_FPREG_LARGE)
                    {
                        Debug.Fail("Expected unwind code");
                        return false;
                    }
                }
                else
                {
                    Debug.Assert(_target.Contracts.RuntimeInfo.GetTargetOperatingSystem() == RuntimeInfoOperatingSystem.Windows);
                    if (unwindOp.UnwindOp > UnwindCode.OpCodes.UWOP_PUSH_MACHFRAME)
                    {
                        Debug.Fail("Expected unwind code");
                        return false;
                    }
                }

                if (prologOffset >= unwindOp.CodeOffset)
                {
                    switch (unwindOp.UnwindOp)
                    {
                        //
                        // Push nonvolatile integer register.
                        //
                        // The operation information is the register number of
                        // the register than was pushed.
                        //
                        case UnwindCode.OpCodes.UWOP_PUSH_NONVOL:
                            {
                                SetRegister(ref context, unwindOp.OpInfo, _target.ReadPointer(context.Rsp));
                                context.Rsp += 8;
                                break;
                            }

                        //
                        // Allocate a large sized area on the stack.
                        //
                        // The operation information determines if the size is
                        // 16- or 32-bits.
                        //
                        case UnwindCode.OpCodes.UWOP_ALLOC_LARGE:
                            {
                                index++;
                                UnwindCode nextUnwindOp = GetUnwindCode(unwindInfo, index);
                                uint frameOffset = nextUnwindOp.FrameOffset;
                                if (unwindOp.OpInfo != 0)
                                {
                                    index++;
                                    nextUnwindOp = GetUnwindCode(unwindInfo, index);
                                    frameOffset += (uint)(nextUnwindOp.FrameOffset << 16);
                                }
                                else
                                {
                                    // The 16-bit form is scaled.
                                    frameOffset *= 8;
                                }

                                context.Rsp += frameOffset;
                                break;
                            }

                        //
                        // Allocate a small sized area on the stack.
                        //
                        // The operation information is the size of the unscaled
                        // allocation size (8 is the scale factor) minus 8.
                        //
                        case UnwindCode.OpCodes.UWOP_ALLOC_SMALL:
                            {
                                context.Rsp += (unwindOp.OpInfo * 8u) + 8u;
                                break;
                            }

                        //
                        // Establish the frame pointer register.
                        //
                        // The operation information is not used.
                        //
                        case UnwindCode.OpCodes.UWOP_SET_FPREG:
                            {
                                context.Rsp = GetRegister(context, unwindInfo.FrameRegister);
                                context.Rsp -= unwindInfo.FrameOffset * 16u;
                                break;
                            }

                        //
                        // Establish the frame pointer register using a large size displacement.
                        // UNWIND_INFO.FrameOffset must be 15 (the maximum value, corresponding to a scaled
                        // offset of 15 * 16 == 240). The next two codes contain a 32-bit offset, which
                        // is also scaled by 16, since the stack must remain 16-bit aligned.
                        // Unix only.
                        //
                        case UnwindCode.OpCodes.UWOP_SET_FPREG_LARGE:
                            {
                                UnwinderAssert(_unixAMD64ABI);
                                UnwinderAssert(unwindInfo.FrameOffset == 15);
                                uint frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset;
                                frameOffset += (uint)(GetUnwindCode(unwindInfo, index + 2).FrameOffset << 16);
                                UnwinderAssert((frameOffset & 0xF0000000) == 0);

                                context.Rsp = GetRegister(context, unwindInfo.FrameRegister);
                                context.Rsp -= frameOffset * 16;

                                index += 2;
                                break;
                            }

                        //
                        // Save nonvolatile integer register on the stack using a
                        // 16-bit displacement.
                        //
                        // The operation information is the register number.
                        //
                        case UnwindCode.OpCodes.UWOP_SAVE_NONVOL:
                            {
                                uint frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset * 8u;
                                SetRegister(ref context, unwindOp.OpInfo, _target.ReadPointer(frameBase + frameOffset));
                                index += 1;
                                break;
                            }

                        //
                        // Save nonvolatile integer register on the stack using a
                        // 32-bit displacement.
                        //
                        // The operation information is the register number.
                        //
                        case UnwindCode.OpCodes.UWOP_SAVE_NONVOL_FAR:
                            {
                                uint frameOffset = GetUnwindCode(unwindInfo, index + 1).FrameOffset;
                                frameOffset += (uint)(GetUnwindCode(unwindInfo, index + 2).FrameOffset << 16);
                                SetRegister(ref context, unwindOp.OpInfo, _target.ReadPointer(frameBase + frameOffset));
                                index += 2;
                                break;
                            }

                        //
                        // Function epilog marker (ignored for prologue unwind).
                        //
                        case UnwindCode.OpCodes.UWOP_EPILOG:
                            index += 1;
                            break;

                        //
                        // Spare unused codes.
                        //
                        case UnwindCode.OpCodes.UWOP_SPARE_CODE:
                            UnwinderAssert(false);
                            index += 2;
                            break;

                        //
                        // Save a nonvolatile XMM(128) register on the stack using a
                        // 16-bit displacement.
                        //
                        // The operation information is the register number.
                        //
                        case UnwindCode.OpCodes.UWOP_SAVE_XMM128:
                            index += 1;
                            // Operation not currently supported by the cDAC.
                            break;

                        //
                        // Save a nonvolatile XMM(128) register on the stack using
                        // a 32-bit displacement.
                        //
                        // The operation information is the register number.
                        //
                        case UnwindCode.OpCodes.UWOP_SAVE_XMM128_FAR:
                            index += 2;
                            // Operation not currently supported by the cDAC.
                            break;

                        //
                        // Push a machine frame on the stack.
                        //
                        // The operation information determines whether the
                        // machine frame contains an error code or not.
                        //
                        case UnwindCode.OpCodes.UWOP_PUSH_MACHFRAME:
                            {
                                machineFrame = true;
                                TargetPointer returnAddressPtr = context.Rsp;
                                TargetPointer stackAddressPtr = context.Rsp + (3 * 8);
                                if (unwindOp.OpInfo != 0)
                                {
                                    returnAddressPtr += (uint)_target.PointerSize;
                                    stackAddressPtr += (uint)_target.PointerSize;
                                }

                                context.Rip = _target.ReadPointer(returnAddressPtr);
                                context.Rsp = _target.ReadPointer(stackAddressPtr);
                                break;
                            }

                        default:
                            Debug.Fail("Unexpected unwind operation code.");
                            break;
                    }

                    index += 1;
                }
                else
                {
                    index += unwindOp.UnwindOpSlots();
                }
            }

            //
            // If chained unwind information is specified, then set the function
            // entry address to the chained function entry and continue the scan.
            // Otherwise, determine the return address if a machine frame was not
            // encountered during the scan of the unwind codes and terminate the
            // scan.
            //

            if (unwindInfo.Flags.HasFlag(UnwindInfoHeader.Flag.UNW_FLAG_CHAININFO))
            {
                functionEntry = _target.ProcessedData.GetOrAdd<Data.RuntimeFunction>(unwindInfo.GetChainedEntryAddress());
            }
            else
            {
                if (!machineFrame)
                {
                    context.Rip = _target.ReadPointer(context.Rsp);
                    context.Rsp += (uint)_target.PointerSize;
                }

                break;
            }

            //
            // Limit the number of iterations possible for chained function table
            // entries.
            //
            chainCount += 1;
            UnwinderAssert(chainCount <= UNWIND_CHAIN_LIMIT);
        }

        return true;
    }


    #region Unwind Helpers

    /// <summary>
    /// Well known UnwindInfo header for AMD64.
    /// </summary>
    private struct UnwindInfoHeader(TargetPointer address, uint header)
    {
        [Flags]
        public enum Flag : byte
        {
            UNW_FLAG_NHANDLER = 0x0,
            UNW_FLAG_EHANDLER = 0x1,
            UNW_FLAG_UHANDLER = 0x2,
            UNW_FLAG_CHAININFO = 0x4,
        }

        private TargetPointer _address = address;
        public byte Version = (byte)(header & 0x7);                     // bits 0-2 (3 bits)
        public Flag Flags = (Flag)((header >> 3) & 0x1F);               // bits 3-7 (5 bits)
        public byte SizeOfProlog = (byte)((header >> 8) & 0xFF);        // bits 8-15 (8 bits)
        public byte CountOfUnwindCodes = (byte)((header >> 16) & 0xFF); // bits 16-23 (8 bits)
        public byte FrameRegister = (byte)((header >> 24) & 0xF);       // bits 24-27 (4 bits)
        public byte FrameOffset = (byte)((header >> 28) & 0xF);         // bits 28-31 (4 bits)

        public TargetPointer GetUnwindCodeAddress(uint index)
        {
            if (index >= CountOfUnwindCodes)
                throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range for unwind codes.");

            TargetPointer unwindCodeAddress = _address + sizeof(uint) /* size of header */ + (index * sizeof(ushort) /* size of unwind code */);
            return unwindCodeAddress;
        }

        public TargetPointer GetChainedEntryAddress()
        {
            if (!Flags.HasFlag(Flag.UNW_FLAG_CHAININFO))
                throw new InvalidOperationException("This unwind info does not contain a chained entry.");

            uint index = CountOfUnwindCodes;
            if ((index & 0x1) != 0)
                index++;

            TargetPointer chainedEntryAddress = _address + sizeof(uint) /* size of header */ + (index * sizeof(ushort) /* size of unwind code */);
            return chainedEntryAddress;
        }
    }

    private struct UnwindCode(ushort value)
    {
        public enum OpCodes : byte
        {
            UWOP_PUSH_NONVOL = 0,
            UWOP_ALLOC_LARGE,
            UWOP_ALLOC_SMALL,
            UWOP_SET_FPREG,
            UWOP_SAVE_NONVOL,
            UWOP_SAVE_NONVOL_FAR,
            UWOP_EPILOG,
            UWOP_SPARE_CODE,
            UWOP_SAVE_XMM128,
            UWOP_SAVE_XMM128_FAR,
            UWOP_PUSH_MACHFRAME,

            // UWOP_SET_FPREG_LARGE is a CLR Unix-only extension to the Windows AMD64 unwind codes.
            // It is not part of the standard Windows AMD64 unwind codes specification.
            // UWOP_SET_FPREG allows for a maximum of a 240 byte offset between RSP and the
            // frame pointer, when the frame pointer is established. UWOP_SET_FPREG_LARGE
            // has a 32-bit range scaled by 16. When UWOP_SET_FPREG_LARGE is used,
            // UNWIND_INFO.FrameRegister must be set to the frame pointer register, and
            // UNWIND_INFO.FrameOffset must be set to 15 (its maximum value). UWOP_SET_FPREG_LARGE
            // is followed by two UNWIND_CODEs that are combined to form a 32-bit offset (the same
            // as UWOP_SAVE_NONVOL_FAR). This offset is then scaled by 16. The result must be less
            // than 2^32 (that is, the top 4 bits of the unscaled 32-bit number must be zero). This
            // result is used as the frame pointer register offset from RSP at the time the frame pointer
            // is established. Either UWOP_SET_FPREG or UWOP_SET_FPREG_LARGE can be used, but not both.
            UWOP_SET_FPREG_LARGE,
        }

        public byte CodeOffset = (byte)(value & 0xFF);           // bits 0-8 (8 bits)
        public OpCodes UnwindOp = (OpCodes)((value >> 8) & 0xF); // bits 9-12 (4 bits)
        public byte OpInfo = (byte)((value >> 12) & 0xF);        // bits 13-16 (4 bits)

        public ushort FrameOffset = value;

        public uint UnwindOpSlots()
        {
            UnwinderAssert(UnwindOp != OpCodes.UWOP_SPARE_CODE);
            return UnwindOp switch
            {
                OpCodes.UWOP_PUSH_NONVOL => 1u,
                OpCodes.UWOP_ALLOC_LARGE => OpInfo != 0 ? 3u : 2u,
                OpCodes.UWOP_ALLOC_SMALL => 1u,
                OpCodes.UWOP_SET_FPREG => 1u,
                OpCodes.UWOP_SAVE_NONVOL => 2u,
                OpCodes.UWOP_SAVE_NONVOL_FAR => 3u,
                OpCodes.UWOP_EPILOG => 2u,
                // previously 64-bit UWOP_SAVE_XMM_FAR
                OpCodes.UWOP_SPARE_CODE => 3u,
                OpCodes.UWOP_SAVE_XMM128 => 2u,
                OpCodes.UWOP_SAVE_XMM128_FAR => 3u,
                OpCodes.UWOP_PUSH_MACHFRAME => 1u,
                OpCodes.UWOP_SET_FPREG_LARGE => 3u,
                _ => throw new InvalidOperationException($"Unsupported unwind operation: {UnwindOp}"),
            };
        }
    }

    private UnwindInfoHeader? GetUnwindInfoHeader(TargetPointer unwindInfoAddress)
    {
        try
        {
            Data.UnwindInfo unwindInfoData = _target.ProcessedData.GetOrAdd<Data.UnwindInfo>(unwindInfoAddress);

            if (unwindInfoData.Header is not uint headerValue)
                return null;

            return new UnwindInfoHeader(unwindInfoAddress, headerValue);
        }
        catch (VirtualReadException)
        {
            return null;
        }
    }

    private UnwindCode GetUnwindCode(UnwindInfoHeader unwindInfo, uint index) =>
        new UnwindCode(_target.Read<ushort>(unwindInfo.GetUnwindCodeAddress(index)));

    private Data.RuntimeFunction LookupPrimaryFunctionEntry(Data.RuntimeFunction functionEntry, TargetPointer imageBase)
    {
        uint chainCount = 0;

        while (true)
        {
            UnwindInfoHeader? unwindInfo = GetUnwindInfoHeader(imageBase + functionEntry.UnwindData);

            if (unwindInfo is null || !unwindInfo.Value.Flags.HasFlag(UnwindInfoHeader.Flag.UNW_FLAG_CHAININFO))
            {
                break;
            }

            functionEntry = _target.ProcessedData.GetOrAdd<Data.RuntimeFunction>(unwindInfo.Value.GetChainedEntryAddress());

            //
            // Limit the number of iterations possible for chained function table
            // entries.
            //

            chainCount += 1;
            UnwinderAssert(chainCount <= UNWIND_CHAIN_LIMIT, "Unwind chain limit exceeded.");
        }

        return functionEntry;
    }

    private Data.RuntimeFunction? SameFunction(Data.RuntimeFunction functionEntry, TargetPointer imageBase, TargetPointer controlPC)
    {
        Data.RuntimeFunction primaryFunctionEntry = LookupPrimaryFunctionEntry(functionEntry, imageBase);

        if (_eman.GetCodeBlockHandle(controlPC.Value) is not CodeBlockHandle cbh)
            return null;

        TargetPointer targetImageBase = _eman.GetUnwindInfoBaseAddress(cbh);
        Data.RuntimeFunction targetFunctionEntry = _target.ProcessedData.GetOrAdd<Data.RuntimeFunction>(_eman.GetUnwindInfo(cbh));

        targetFunctionEntry = LookupPrimaryFunctionEntry(targetFunctionEntry, targetImageBase);

        if (primaryFunctionEntry.BeginAddress == targetFunctionEntry.BeginAddress)
        {
            return primaryFunctionEntry;
        }
        else
        {
            return null;
        }
    }

    #endregion
    #region Helpers

    private byte ReadByteAt(TargetPointer address) => _target.Read<byte>(address);

    private static bool IsRexPrefix(byte b) => (b & 0xf0) == 0x40;

    private static TargetPointer GetRegister(AMD64Context context, byte register)
        => context.TryReadRegister(register, out TargetNUInt value) ? value.Value : throw new ArgumentOutOfRangeException(nameof(register), "Invalid register number for AMD64 context.");

    private static void SetRegister(ref AMD64Context context, byte register, TargetPointer value)
    {
        if (!context.TrySetRegister(register, new TargetNUInt(value)))
            throw new ArgumentOutOfRangeException(nameof(register), "Invalid register number for AMD64 context.");
    }

    private static void UnwinderAssert(bool condition, string? message = null)
    {
        if (!condition)
        {
            throw new InvalidOperationException(message);
        }
    }

    #endregion
}