/*
 * File cpu_x86_64.c
 *
 * Copyright (C) 1999, 2005 Alexandre Julliard
 * Copyright (C) 2009, 2011 Eric Pouech.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <assert.h>

#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "dbghelp_private.h"
#include "winternl.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(dbghelp);

/* x86-64 unwind information, for PE modules, as described on MSDN */

typedef enum _UNWIND_OP_CODES
{
    UWOP_PUSH_NONVOL = 0,
    UWOP_ALLOC_LARGE,
    UWOP_ALLOC_SMALL,
    UWOP_SET_FPREG,
    UWOP_SAVE_NONVOL,
    UWOP_SAVE_NONVOL_FAR,
    UWOP_SAVE_XMM128,
    UWOP_SAVE_XMM128_FAR,
    UWOP_PUSH_MACHFRAME
} UNWIND_CODE_OPS;

typedef union _UNWIND_CODE
{
    struct
    {
        BYTE CodeOffset;
        BYTE UnwindOp : 4;
        BYTE OpInfo   : 4;
    } u;
    USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

typedef struct _UNWIND_INFO
{
    BYTE Version       : 3;
    BYTE Flags         : 5;
    BYTE SizeOfProlog;
    BYTE CountOfCodes;
    BYTE FrameRegister : 4;
    BYTE FrameOffset   : 4;
    UNWIND_CODE UnwindCode[1]; /* actually CountOfCodes (aligned) */
/*
 *  union
 *  {
 *      OPTIONAL ULONG ExceptionHandler;
 *      OPTIONAL ULONG FunctionEntry;
 *  };
 *  OPTIONAL ULONG ExceptionData[];
 */
} UNWIND_INFO, *PUNWIND_INFO;

static BOOL x86_64_get_addr(HANDLE hThread, const CONTEXT* ctx,
                            enum cpu_addr ca, ADDRESS64* addr)
{
    addr->Mode = AddrModeFlat;
    switch (ca)
    {
#ifdef __x86_64__
    case cpu_addr_pc:    addr->Segment = ctx->SegCs; addr->Offset = ctx->Rip; return TRUE;
    case cpu_addr_stack: addr->Segment = ctx->SegSs; addr->Offset = ctx->Rsp; return TRUE;
    case cpu_addr_frame: addr->Segment = ctx->SegSs; addr->Offset = ctx->Rbp; return TRUE;
#endif
    default: addr->Mode = -1;
        return FALSE;
    }
}

#ifdef __x86_64__

enum st_mode {stm_start, stm_64bit, stm_done};

/* indexes in Reserved array */
#define __CurrentMode     0
#define __CurrentCount    1
/* #define __     2 (unused) */

#define curr_mode   (frame->Reserved[__CurrentMode])
#define curr_count  (frame->Reserved[__CurrentCount])
/* #define ??? (frame->Reserved[__]) (unused) */

union handler_data
{
    RUNTIME_FUNCTION chain;
    ULONG handler;
};

static void dump_unwind_info(struct cpu_stack_walk* csw, ULONG64 base, RUNTIME_FUNCTION *function)
{
    static const char * const reg_names[16] =
        { "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
          "r8",  "r9",  "r10", "r11", "r12", "r13", "r14", "r15" };

    union handler_data handler_data;
    char buffer[sizeof(UNWIND_INFO) + 256 * sizeof(UNWIND_CODE)];
    UNWIND_INFO* info = (UNWIND_INFO*)buffer;
    unsigned int i, count;
    RUNTIME_FUNCTION snext;
    ULONG64 addr;

    TRACE("**** func %x-%x\n", function->BeginAddress, function->EndAddress);
    for (;;)
    {
        if (function->UnwindData & 1)
        {
            if (!sw_read_mem(csw, base + function->UnwindData, &snext, sizeof(snext)))
            {
                TRACE("Couldn't unwind RUNTIME_INFO at %lx\n", base + function->UnwindData);
                return;
            }
            TRACE("unwind info for function %p-%p chained to function %p-%p\n",
                  (char*)base + function->BeginAddress, (char*)base + function->EndAddress,
                  (char*)base + snext.BeginAddress, (char*)base + snext.EndAddress);
            function = &snext;
            continue;
        }
        addr = base + function->UnwindData;
        if (!sw_read_mem(csw, addr, info, FIELD_OFFSET(UNWIND_INFO, UnwindCode)) ||
            !sw_read_mem(csw, addr + FIELD_OFFSET(UNWIND_INFO, UnwindCode),
                         info->UnwindCode, info->CountOfCodes * sizeof(UNWIND_CODE)))
        {
            FIXME("couldn't read memory for UNWIND_INFO at %lx\n", addr);
            return;
        }
        TRACE("unwind info at %p flags %x prolog 0x%x bytes function %p-%p\n",
              (char*)addr, info->Flags, info->SizeOfProlog,
              (char*)base + function->BeginAddress, (char*)base + function->EndAddress);

        if (info->FrameRegister)
            TRACE("    frame register %s offset 0x%x(%%rsp)\n",
                  reg_names[info->FrameRegister], info->FrameOffset * 16);

        for (i = 0; i < info->CountOfCodes; i++)
        {
            TRACE("    0x%x: ", info->UnwindCode[i].u.CodeOffset);
            switch (info->UnwindCode[i].u.UnwindOp)
            {
            case UWOP_PUSH_NONVOL:
                TRACE("pushq %%%s\n", reg_names[info->UnwindCode[i].u.OpInfo]);
                break;
            case UWOP_ALLOC_LARGE:
                if (info->UnwindCode[i].u.OpInfo)
                {
                    count = *(DWORD*)&info->UnwindCode[i+1];
                    i += 2;
                }
                else
                {
                    count = *(USHORT*)&info->UnwindCode[i+1] * 8;
                    i++;
                }
                TRACE("subq $0x%x,%%rsp\n", count);
                break;
            case UWOP_ALLOC_SMALL:
                count = (info->UnwindCode[i].u.OpInfo + 1) * 8;
                TRACE("subq $0x%x,%%rsp\n", count);
                break;
            case UWOP_SET_FPREG:
                TRACE("leaq 0x%x(%%rsp),%s\n",
                      info->FrameOffset * 16, reg_names[info->FrameRegister]);
                break;
            case UWOP_SAVE_NONVOL:
                count = *(USHORT*)&info->UnwindCode[i+1] * 8;
                TRACE("movq %%%s,0x%x(%%rsp)\n", reg_names[info->UnwindCode[i].u.OpInfo], count);
                i++;
                break;
            case UWOP_SAVE_NONVOL_FAR:
                count = *(DWORD*)&info->UnwindCode[i+1];
                TRACE("movq %%%s,0x%x(%%rsp)\n", reg_names[info->UnwindCode[i].u.OpInfo], count);
                i += 2;
                break;
            case UWOP_SAVE_XMM128:
                count = *(USHORT*)&info->UnwindCode[i+1] * 16;
                TRACE("movaps %%xmm%u,0x%x(%%rsp)\n", info->UnwindCode[i].u.OpInfo, count);
                i++;
                break;
            case UWOP_SAVE_XMM128_FAR:
                count = *(DWORD*)&info->UnwindCode[i+1];
                TRACE("movaps %%xmm%u,0x%x(%%rsp)\n", info->UnwindCode[i].u.OpInfo, count);
                i += 2;
                break;
            case UWOP_PUSH_MACHFRAME:
                TRACE("PUSH_MACHFRAME %u\n", info->UnwindCode[i].u.OpInfo);
                break;
            default:
                FIXME("unknown code %u\n", info->UnwindCode[i].u.UnwindOp);
                break;
            }
        }

        addr += FIELD_OFFSET(UNWIND_INFO, UnwindCode) +
            ((info->CountOfCodes + 1) & ~1) * sizeof(UNWIND_CODE);
        if (info->Flags & UNW_FLAG_CHAININFO)
        {
            if (!sw_read_mem(csw, addr, &handler_data, sizeof(handler_data.chain)))
            {
                FIXME("couldn't read memory for handler_data.chain\n");
                return;
            }
            TRACE("    chained to function %p-%p\n",
                  (char*)base + handler_data.chain.BeginAddress,
                  (char*)base + handler_data.chain.EndAddress);
            function = &handler_data.chain;
            continue;
        }
        if (info->Flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER))
        {
            if (!sw_read_mem(csw, addr, &handler_data, sizeof(handler_data.handler)))
            {
                FIXME("couldn't read memory for handler_data.handler\n");
                return;
            }
            TRACE("    handler %p data at %p\n",
                  (char*)base + handler_data.handler, (char*)addr + sizeof(handler_data.handler));
        }
        break;
    }
}

/* highly derived from dlls/ntdll/signal_x86_64.c */
static ULONG64 get_int_reg(CONTEXT *context, int reg)
{
    return *(&context->Rax + reg);
}

static void set_int_reg(CONTEXT *context, int reg, ULONG64 val)
{
    *(&context->Rax + reg) = val;
}

static void set_float_reg(CONTEXT *context, int reg, M128A val)
{
    *(&context->u.s.Xmm0 + reg) = val;
}

static int get_opcode_size(UNWIND_CODE op)
{
    switch (op.u.UnwindOp)
    {
    case UWOP_ALLOC_LARGE:
        return 2 + (op.u.OpInfo != 0);
    case UWOP_SAVE_NONVOL:
    case UWOP_SAVE_XMM128:
        return 2;
    case UWOP_SAVE_NONVOL_FAR:
    case UWOP_SAVE_XMM128_FAR:
        return 3;
    default:
        return 1;
    }
}

static BOOL is_inside_epilog(struct cpu_stack_walk* csw, DWORD64 pc,
                             DWORD64 base, const RUNTIME_FUNCTION *function )
{
    BYTE op0, op1, op2;
    LONG val32;

    if (!sw_read_mem(csw, pc, &op0, 1)) return FALSE;

    /* add or lea must be the first instruction, and it must have a rex.W prefix */
    if ((op0 & 0xf8) == 0x48)
    {
        if (!sw_read_mem(csw, pc + 1, &op1, 1)) return FALSE;
        switch (op1)
        {
        case 0x81: /* add $nnnn,%rsp */
            if (!sw_read_mem(csw, pc + 2, &op2, 1)) return FALSE;
            if (op0 == 0x48 && op2 == 0xc4)
            {
                pc += 7;
                break;
            }
            return FALSE;
        case 0x83: /* add $n,%rsp */
            if (!sw_read_mem(csw, pc + 2, &op2, 1)) return FALSE;
            if (op0 == 0x48 && op2 == 0xc4)
            {
                pc += 4;
                break;
            }
            return FALSE;
        case 0x8d: /* lea n(reg),%rsp */
            if (!sw_read_mem(csw, pc + 2, &op2, 1)) return FALSE;
            if (op0 & 0x06) return FALSE;  /* rex.RX must be cleared */
            if (((op2 >> 3) & 7) != 4) return FALSE;  /* dest reg mus be %rsp */
            if ((op2 & 7) == 4) return FALSE;  /* no SIB byte allowed */
            if ((op2 >> 6) == 1)  /* 8-bit offset */
            {
                pc += 4;
                break;
            }
            if ((op2 >> 6) == 2)  /* 32-bit offset */
            {
                pc += 7;
                break;
            }
            return FALSE;
        }
    }

    /* now check for various pop instructions */
    for (;;)
    {
        if (!sw_read_mem(csw, pc, &op0, 1)) return FALSE;
        if ((op0 & 0xf0) == 0x40)  /* rex prefix */
        {
            if (!sw_read_mem(csw, ++pc, &op0, 1)) return FALSE;
        }

        switch (op0)
        {
        case 0x58: /* pop %rax/%r8 */
        case 0x59: /* pop %rcx/%r9 */
        case 0x5a: /* pop %rdx/%r10 */
        case 0x5b: /* pop %rbx/%r11 */
        case 0x5c: /* pop %rsp/%r12 */
        case 0x5d: /* pop %rbp/%r13 */
        case 0x5e: /* pop %rsi/%r14 */
        case 0x5f: /* pop %rdi/%r15 */
            pc++;
            continue;
        case 0xc2: /* ret $nn */
        case 0xc3: /* ret */
            return TRUE;
        case 0xe9: /* jmp nnnn */
            if (!sw_read_mem(csw, pc + 1, &val32, sizeof(LONG))) return FALSE;
            pc += 5 + val32;
            if (pc - base >= function->BeginAddress && pc - base < function->EndAddress)
                continue;
            break;
        case 0xeb: /* jmp n */
            if (!sw_read_mem(csw, pc + 1, &op1, 1)) return FALSE;
            pc += 2 + (signed char)op1;
            if (pc - base >= function->BeginAddress && pc - base < function->EndAddress)
                continue;
            break;
        case 0xf3: /* rep; ret (for amd64 prediction bug) */
            if (!sw_read_mem(csw, pc + 1, &op1, 1)) return FALSE;
            return op1 == 0xc3;
        }
        return FALSE;
    }
}

static BOOL interpret_epilog(struct cpu_stack_walk* csw, ULONG64 pc, CONTEXT *context )
{
    BYTE        insn, val8;
    WORD        val16;
    LONG        val32;
    DWORD64     val64;

    for (;;)
    {
        BYTE rex = 0;

        if (!sw_read_mem(csw, pc, &insn, 1)) return FALSE;
        if ((insn & 0xf0) == 0x40)
        {
            rex = insn & 0x0f;  /* rex prefix */
            if (!sw_read_mem(csw, ++pc, &insn, 1)) return FALSE;
        }

        switch (insn)
        {
        case 0x58: /* pop %rax/r8 */
        case 0x59: /* pop %rcx/r9 */
        case 0x5a: /* pop %rdx/r10 */
        case 0x5b: /* pop %rbx/r11 */
        case 0x5c: /* pop %rsp/r12 */
        case 0x5d: /* pop %rbp/r13 */
        case 0x5e: /* pop %rsi/r14 */
        case 0x5f: /* pop %rdi/r15 */
            if (!sw_read_mem(csw, context->Rsp, &val64, sizeof(DWORD64))) return FALSE;
            set_int_reg(context, insn - 0x58 + (rex & 1) * 8, val64);
            context->Rsp += sizeof(ULONG64);
            pc++;
            continue;
        case 0x81: /* add $nnnn,%rsp */
            if (!sw_read_mem(csw, pc + 2, &val32, sizeof(LONG))) return FALSE;
            context->Rsp += val32;
            pc += 2 + sizeof(LONG);
            continue;
        case 0x83: /* add $n,%rsp */
            if (!sw_read_mem(csw, pc + 2, &val8, sizeof(BYTE))) return FALSE;
            context->Rsp += (signed char)val8;
            pc += 3;
            continue;
        case 0x8d:
            if (!sw_read_mem(csw, pc + 1, &insn, sizeof(BYTE))) return FALSE;
            if ((insn >> 6) == 1)  /* lea n(reg),%rsp */
            {
                if (!sw_read_mem(csw, pc + 2, &val8, sizeof(BYTE))) return FALSE;
                context->Rsp = get_int_reg( context, (insn & 7) + (rex & 1) * 8 ) + (signed char)val8;
                pc += 3;
            }
            else  /* lea nnnn(reg),%rsp */
            {
                if (!sw_read_mem(csw, pc + 2, &val32, sizeof(LONG))) return FALSE;
                context->Rsp = get_int_reg( context, (insn & 7) + (rex & 1) * 8 ) + val32;
                pc += 2 + sizeof(LONG);
            }
            continue;
        case 0xc2: /* ret $nn */
            if (!sw_read_mem(csw, context->Rsp, &val64, sizeof(DWORD64))) return FALSE;
            if (!sw_read_mem(csw, pc + 1, &val16, sizeof(WORD))) return FALSE;
            context->Rip = val64;
            context->Rsp += sizeof(ULONG64) + val16;
            return TRUE;
        case 0xc3: /* ret */
        case 0xf3: /* rep; ret */
            if (!sw_read_mem(csw, context->Rsp, &val64, sizeof(DWORD64))) return FALSE;
            context->Rip = val64;
            context->Rsp += sizeof(ULONG64);
            return TRUE;
        case 0xe9: /* jmp nnnn */
            if (!sw_read_mem(csw, pc + 1, &val32, sizeof(LONG))) return FALSE;
            pc += 5 + val32;
            continue;
        case 0xeb: /* jmp n */
            if (!sw_read_mem(csw, pc + 1, &val8, sizeof(BYTE))) return FALSE;
            pc += 2 + (signed char)val8;
            continue;
        }
        FIXME("unsupported insn %x\n", insn);
        return FALSE;
    }
}

static BOOL default_unwind(struct cpu_stack_walk* csw, CONTEXT* context)
{
    if (!sw_read_mem(csw, context->Rsp, &context->Rip, sizeof(DWORD64)))
    {
        WARN("Cannot read new frame offset %s\n", wine_dbgstr_longlong(context->Rsp));
        return FALSE;
    }
    context->Rsp += sizeof(DWORD64);
    return TRUE;
}

static BOOL interpret_function_table_entry(struct cpu_stack_walk* csw,
                                           CONTEXT* context, RUNTIME_FUNCTION* function, DWORD64 base)
{
    char                buffer[sizeof(UNWIND_INFO) + 256 * sizeof(UNWIND_CODE)];
    UNWIND_INFO*        info = (UNWIND_INFO*)buffer;
    unsigned            i;
    DWORD64             newframe, prolog_offset, off, value;
    M128A               floatvalue;
    union handler_data  handler_data;

    /* FIXME: we have some assumptions here */
    assert(context);
    dump_unwind_info(csw, sw_module_base(csw, context->Rip), function);
    newframe = context->Rsp;
    for (;;)
    {
        if (!sw_read_mem(csw, base + function->UnwindData, info, sizeof(*info)) ||
            !sw_read_mem(csw, base + function->UnwindData + FIELD_OFFSET(UNWIND_INFO, UnwindCode),
                         info->UnwindCode, info->CountOfCodes * sizeof(UNWIND_CODE)))
        {
            WARN("Couldn't read unwind_code at %lx\n", base + function->UnwindData);
            return FALSE;
        }

        if (info->Version != 1)
        {
            WARN("unknown unwind info version %u at %lx\n", info->Version, base + function->UnwindData);
            return FALSE;
        }

        if (info->FrameRegister)
            newframe = get_int_reg(context, info->FrameRegister) - info->FrameOffset * 16;

        /* check if in prolog */
        if (context->Rip >= base + function->BeginAddress &&
            context->Rip < base + function->BeginAddress + info->SizeOfProlog)
        {
            prolog_offset = context->Rip - base - function->BeginAddress;
        }
        else
        {
            prolog_offset = ~0;
            if (is_inside_epilog(csw, context->Rip, base, function))
            {
                interpret_epilog(csw, context->Rip, context);
                return TRUE;
            }
        }

        for (i = 0; i < info->CountOfCodes; i += get_opcode_size(info->UnwindCode[i]))
        {
            if (prolog_offset < info->UnwindCode[i].u.CodeOffset) continue; /* skip it */

            switch (info->UnwindCode[i].u.UnwindOp)
            {
            case UWOP_PUSH_NONVOL:  /* pushq %reg */
                if (!sw_read_mem(csw, context->Rsp, &value, sizeof(DWORD64))) return FALSE;
                set_int_reg(context, info->UnwindCode[i].u.OpInfo, value);
                context->Rsp += sizeof(ULONG64);
                break;
            case UWOP_ALLOC_LARGE:  /* subq $nn,%rsp */
                if (info->UnwindCode[i].u.OpInfo) context->Rsp += *(DWORD*)&info->UnwindCode[i+1];
                else context->Rsp += *(USHORT*)&info->UnwindCode[i+1] * 8;
                break;
            case UWOP_ALLOC_SMALL:  /* subq $n,%rsp */
                context->Rsp += (info->UnwindCode[i].u.OpInfo + 1) * 8;
                break;
            case UWOP_SET_FPREG:  /* leaq nn(%rsp),%framereg */
                context->Rsp = newframe;
                break;
            case UWOP_SAVE_NONVOL:  /* movq %reg,n(%rsp) */
                off = newframe + *(USHORT*)&info->UnwindCode[i+1] * 8;
                if (!sw_read_mem(csw, off, &value, sizeof(DWORD64))) return FALSE;
                set_int_reg(context, info->UnwindCode[i].u.OpInfo, value);
                break;
            case UWOP_SAVE_NONVOL_FAR:  /* movq %reg,nn(%rsp) */
                off = newframe + *(DWORD*)&info->UnwindCode[i+1];
                if (!sw_read_mem(csw, off, &value, sizeof(DWORD64))) return FALSE;
                set_int_reg(context, info->UnwindCode[i].u.OpInfo, value);
                break;
            case UWOP_SAVE_XMM128:  /* movaps %xmmreg,n(%rsp) */
                off = newframe + *(USHORT*)&info->UnwindCode[i+1] * 16;
                if (!sw_read_mem(csw, off, &floatvalue, sizeof(M128A))) return FALSE;
                set_float_reg(context, info->UnwindCode[i].u.OpInfo, floatvalue);
                break;
            case UWOP_SAVE_XMM128_FAR:  /* movaps %xmmreg,nn(%rsp) */
                off = newframe + *(DWORD*)&info->UnwindCode[i+1];
                if (!sw_read_mem(csw, off, &floatvalue, sizeof(M128A))) return FALSE;
                set_float_reg(context, info->UnwindCode[i].u.OpInfo, floatvalue);
                break;
            case UWOP_PUSH_MACHFRAME:
                FIXME("PUSH_MACHFRAME %u\n", info->UnwindCode[i].u.OpInfo);
                break;
            default:
                FIXME("unknown code %u\n", info->UnwindCode[i].u.UnwindOp);
                break;
            }
        }
        if (!(info->Flags & UNW_FLAG_CHAININFO)) break;
        if (!sw_read_mem(csw, base + function->UnwindData + FIELD_OFFSET(UNWIND_INFO, UnwindCode) +
                                   ((info->CountOfCodes + 1) & ~1) * sizeof(UNWIND_CODE),
                         &handler_data, sizeof(handler_data))) return FALSE;
        function = &handler_data.chain;  /* restart with the chained info */
    }
    return default_unwind(csw, context);
}

/* fetch_next_frame()
 *
 * modify (at least) context.{rip, rsp, rbp} using unwind information
 * either out of PE exception handlers, debug info (dwarf), or simple stack unwind
 */
static BOOL fetch_next_frame(struct cpu_stack_walk* csw, CONTEXT* context,
                             DWORD_PTR curr_pc, void** prtf)
{
    DWORD_PTR               cfa;
    RUNTIME_FUNCTION*       rtf;
    DWORD64                 base;

    if (!curr_pc || !(base = sw_module_base(csw, curr_pc))) return FALSE;
    rtf = sw_table_access(csw, curr_pc);
    if (prtf) *prtf = rtf;
    if (rtf)
    {
        return interpret_function_table_entry(csw, context, rtf, base);
    }
    else if (dwarf2_virtual_unwind(csw, curr_pc, context, &cfa))
    {
        context->Rsp = cfa;
        TRACE("next function rip=%016lx\n", context->Rip);
        TRACE("  rax=%016lx rbx=%016lx rcx=%016lx rdx=%016lx\n",
              context->Rax, context->Rbx, context->Rcx, context->Rdx);
        TRACE("  rsi=%016lx rdi=%016lx rbp=%016lx rsp=%016lx\n",
              context->Rsi, context->Rdi, context->Rbp, context->Rsp);
        TRACE("   r8=%016lx  r9=%016lx r10=%016lx r11=%016lx\n",
              context->R8, context->R9, context->R10, context->R11);
        TRACE("  r12=%016lx r13=%016lx r14=%016lx r15=%016lx\n",
              context->R12, context->R13, context->R14, context->R15);
        return TRUE;
    }
    else
        return default_unwind(csw, context);
}

static BOOL x86_64_stack_walk(struct cpu_stack_walk* csw, LPSTACKFRAME64 frame, CONTEXT* context)
{
    unsigned    deltapc = curr_count <= 1 ? 0 : 1;

    /* sanity check */
    if (curr_mode >= stm_done) return FALSE;
    assert(!csw->is32);

    TRACE("Enter: PC=%s Frame=%s Return=%s Stack=%s Mode=%s Count=%s\n",
          wine_dbgstr_addr(&frame->AddrPC),
          wine_dbgstr_addr(&frame->AddrFrame),
          wine_dbgstr_addr(&frame->AddrReturn),
          wine_dbgstr_addr(&frame->AddrStack),
          curr_mode == stm_start ? "start" : "64bit",
          wine_dbgstr_longlong(curr_count));

    if (curr_mode == stm_start)
    {
        if ((frame->AddrPC.Mode == AddrModeFlat) &&
            (frame->AddrFrame.Mode != AddrModeFlat))
        {
            WARN("Bad AddrPC.Mode / AddrFrame.Mode combination\n");
            goto done_err;
        }

        /* Init done */
        curr_mode = stm_64bit;
        frame->AddrReturn.Mode = frame->AddrStack.Mode = AddrModeFlat;
        /* don't set up AddrStack on first call. Either the caller has set it up, or
         * we will get it in the next frame
         */
        memset(&frame->AddrBStore, 0, sizeof(frame->AddrBStore));
    }
    else
    {
        if (context->Rsp != frame->AddrStack.Offset) FIXME("inconsistent Stack Pointer\n");
        if (context->Rip != frame->AddrPC.Offset) FIXME("inconsistent Instruction Pointer\n");

        if (frame->AddrReturn.Offset == 0) goto done_err;
        if (!fetch_next_frame(csw, context, frame->AddrPC.Offset - deltapc, &frame->FuncTableEntry))
            goto done_err;
        deltapc = 1;
    }

    memset(&frame->Params, 0, sizeof(frame->Params));

    /* set frame information */
    frame->AddrStack.Offset = context->Rsp;
    frame->AddrFrame.Offset = context->Rbp;
    frame->AddrPC.Offset = context->Rip;
    if (1)
    {
        CONTEXT         newctx = *context;

        if (!fetch_next_frame(csw, &newctx, frame->AddrPC.Offset - deltapc, NULL))
            goto done_err;
        frame->AddrReturn.Mode = AddrModeFlat;
        frame->AddrReturn.Offset = newctx.Rip;
    }

    frame->Far = TRUE;
    frame->Virtual = TRUE;
    curr_count++;

    TRACE("Leave: PC=%s Frame=%s Return=%s Stack=%s Mode=%s Count=%s FuncTable=%p\n",
          wine_dbgstr_addr(&frame->AddrPC),
          wine_dbgstr_addr(&frame->AddrFrame),
          wine_dbgstr_addr(&frame->AddrReturn),
          wine_dbgstr_addr(&frame->AddrStack),
          curr_mode == stm_start ? "start" : "64bit",
          wine_dbgstr_longlong(curr_count),
          frame->FuncTableEntry);

    return TRUE;
done_err:
    curr_mode = stm_done;
    return FALSE;
}
#else
static BOOL x86_64_stack_walk(struct cpu_stack_walk* csw, LPSTACKFRAME64 frame, CONTEXT* context)
{
    return FALSE;
}
#endif

static void*    x86_64_find_runtime_function(struct module* module, DWORD64 addr)
{
#ifdef __x86_64__
    RUNTIME_FUNCTION*   rtf;
    ULONG               size;
    int                 min, max;

    rtf = (RUNTIME_FUNCTION*)pe_map_directory(module, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &size);
    if (rtf) for (min = 0, max = size / sizeof(*rtf); min <= max; )
    {
        int pos = (min + max) / 2;
        if (addr < module->module.BaseOfImage + rtf[pos].BeginAddress) max = pos - 1;
        else if (addr >= module->module.BaseOfImage + rtf[pos].EndAddress) min = pos + 1;
        else
        {
            rtf += pos;
            while (rtf->UnwindData & 1)  /* follow chained entry */
            {
                FIXME("RunTime_Function outside IMAGE_DIRECTORY_ENTRY_EXCEPTION unimplemented yet!\n");
                /* we need to read into the other process */
                /* rtf = (RUNTIME_FUNCTION*)(module->module.BaseOfImage + (rtf->UnwindData & ~1)); */
            }
            return rtf;
        }
    }
#endif
    return NULL;
}

static unsigned x86_64_map_dwarf_register(unsigned regno, BOOL eh_frame)
{
    unsigned    reg;

    if (regno >= 17 && regno <= 24)
        reg = CV_AMD64_XMM0 + regno - 17;
    else if (regno >= 25 && regno <= 32)
        reg = CV_AMD64_XMM8 + regno - 25;
    else if (regno >= 33 && regno <= 40)
        reg = CV_AMD64_ST0 + regno - 33;
    else switch (regno)
    {
    case  0: reg = CV_AMD64_RAX;    break;
    case  1: reg = CV_AMD64_RDX;    break;
    case  2: reg = CV_AMD64_RCX;    break;
    case  3: reg = CV_AMD64_RBX;    break;
    case  4: reg = CV_AMD64_RSI;    break;
    case  5: reg = CV_AMD64_RDI;    break;
    case  6: reg = CV_AMD64_RBP;    break;
    case  7: reg = CV_AMD64_RSP;    break;
    case  8: reg = CV_AMD64_R8;     break;
    case  9: reg = CV_AMD64_R9;     break;
    case 10: reg = CV_AMD64_R10;    break;
    case 11: reg = CV_AMD64_R11;    break;
    case 12: reg = CV_AMD64_R12;    break;
    case 13: reg = CV_AMD64_R13;    break;
    case 14: reg = CV_AMD64_R14;    break;
    case 15: reg = CV_AMD64_R15;    break;
    case 16: reg = CV_AMD64_RIP;    break;
    case 49: reg = CV_AMD64_EFLAGS; break;
    case 50: reg = CV_AMD64_ES;     break;
    case 51: reg = CV_AMD64_CS;     break;
    case 52: reg = CV_AMD64_SS;     break;
    case 53: reg = CV_AMD64_DS;     break;
    case 54: reg = CV_AMD64_FS;     break;
    case 55: reg = CV_AMD64_GS;     break;
    case 62: reg = CV_AMD64_TR;     break;
    case 63: reg = CV_AMD64_LDTR;   break;
    case 64: reg = CV_AMD64_MXCSR;  break;
    case 65: reg = CV_AMD64_CTRL;   break;
    case 66: reg = CV_AMD64_STAT;   break;
/*
 * 56-57 reserved
 * 58 %fs.base
 * 59 %gs.base
 * 60-61 reserved
 */
    default:
        FIXME("Don't know how to map register %d\n", regno);
        return 0;
    }
    return reg;
}

static void* x86_64_fetch_context_reg(CONTEXT* ctx, unsigned regno, unsigned* size)
{
#ifdef __x86_64__
    switch (regno)
    {
    case CV_AMD64_RAX: *size = sizeof(ctx->Rax); return &ctx->Rax;
    case CV_AMD64_RDX: *size = sizeof(ctx->Rdx); return &ctx->Rdx;
    case CV_AMD64_RCX: *size = sizeof(ctx->Rcx); return &ctx->Rcx;
    case CV_AMD64_RBX: *size = sizeof(ctx->Rbx); return &ctx->Rbx;
    case CV_AMD64_RSI: *size = sizeof(ctx->Rsi); return &ctx->Rsi;
    case CV_AMD64_RDI: *size = sizeof(ctx->Rdi); return &ctx->Rdi;
    case CV_AMD64_RBP: *size = sizeof(ctx->Rbp); return &ctx->Rbp;
    case CV_AMD64_RSP: *size = sizeof(ctx->Rsp); return &ctx->Rsp;
    case CV_AMD64_R8:  *size = sizeof(ctx->R8);  return &ctx->R8;
    case CV_AMD64_R9:  *size = sizeof(ctx->R9);  return &ctx->R9;
    case CV_AMD64_R10: *size = sizeof(ctx->R10); return &ctx->R10;
    case CV_AMD64_R11: *size = sizeof(ctx->R11); return &ctx->R11;
    case CV_AMD64_R12: *size = sizeof(ctx->R12); return &ctx->R12;
    case CV_AMD64_R13: *size = sizeof(ctx->R13); return &ctx->R13;
    case CV_AMD64_R14: *size = sizeof(ctx->R14); return &ctx->R14;
    case CV_AMD64_R15: *size = sizeof(ctx->R15); return &ctx->R15;
    case CV_AMD64_RIP: *size = sizeof(ctx->Rip); return &ctx->Rip;

    case CV_AMD64_XMM0 + 0: *size = sizeof(ctx->u.s.Xmm0 ); return &ctx->u.s.Xmm0;
    case CV_AMD64_XMM0 + 1: *size = sizeof(ctx->u.s.Xmm1 ); return &ctx->u.s.Xmm1;
    case CV_AMD64_XMM0 + 2: *size = sizeof(ctx->u.s.Xmm2 ); return &ctx->u.s.Xmm2;
    case CV_AMD64_XMM0 + 3: *size = sizeof(ctx->u.s.Xmm3 ); return &ctx->u.s.Xmm3;
    case CV_AMD64_XMM0 + 4: *size = sizeof(ctx->u.s.Xmm4 ); return &ctx->u.s.Xmm4;
    case CV_AMD64_XMM0 + 5: *size = sizeof(ctx->u.s.Xmm5 ); return &ctx->u.s.Xmm5;
    case CV_AMD64_XMM0 + 6: *size = sizeof(ctx->u.s.Xmm6 ); return &ctx->u.s.Xmm6;
    case CV_AMD64_XMM0 + 7: *size = sizeof(ctx->u.s.Xmm7 ); return &ctx->u.s.Xmm7;
    case CV_AMD64_XMM8 + 0: *size = sizeof(ctx->u.s.Xmm8 ); return &ctx->u.s.Xmm8;
    case CV_AMD64_XMM8 + 1: *size = sizeof(ctx->u.s.Xmm9 ); return &ctx->u.s.Xmm9;
    case CV_AMD64_XMM8 + 2: *size = sizeof(ctx->u.s.Xmm10); return &ctx->u.s.Xmm10;
    case CV_AMD64_XMM8 + 3: *size = sizeof(ctx->u.s.Xmm11); return &ctx->u.s.Xmm11;
    case CV_AMD64_XMM8 + 4: *size = sizeof(ctx->u.s.Xmm12); return &ctx->u.s.Xmm12;
    case CV_AMD64_XMM8 + 5: *size = sizeof(ctx->u.s.Xmm13); return &ctx->u.s.Xmm13;
    case CV_AMD64_XMM8 + 6: *size = sizeof(ctx->u.s.Xmm14); return &ctx->u.s.Xmm14;
    case CV_AMD64_XMM8 + 7: *size = sizeof(ctx->u.s.Xmm15); return &ctx->u.s.Xmm15;

    case CV_AMD64_ST0 + 0: *size = sizeof(ctx->u.s.Legacy[0]); return &ctx->u.s.Legacy[0];
    case CV_AMD64_ST0 + 1: *size = sizeof(ctx->u.s.Legacy[1]); return &ctx->u.s.Legacy[1];
    case CV_AMD64_ST0 + 2: *size = sizeof(ctx->u.s.Legacy[2]); return &ctx->u.s.Legacy[2];
    case CV_AMD64_ST0 + 3: *size = sizeof(ctx->u.s.Legacy[3]); return &ctx->u.s.Legacy[3];
    case CV_AMD64_ST0 + 4: *size = sizeof(ctx->u.s.Legacy[4]); return &ctx->u.s.Legacy[4];
    case CV_AMD64_ST0 + 5: *size = sizeof(ctx->u.s.Legacy[5]); return &ctx->u.s.Legacy[5];
    case CV_AMD64_ST0 + 6: *size = sizeof(ctx->u.s.Legacy[6]); return &ctx->u.s.Legacy[6];
    case CV_AMD64_ST0 + 7: *size = sizeof(ctx->u.s.Legacy[7]); return &ctx->u.s.Legacy[7];

    case CV_AMD64_EFLAGS: *size = sizeof(ctx->EFlags); return &ctx->EFlags;
    case CV_AMD64_ES: *size = sizeof(ctx->SegEs); return &ctx->SegEs;
    case CV_AMD64_CS: *size = sizeof(ctx->SegCs); return &ctx->SegCs;
    case CV_AMD64_SS: *size = sizeof(ctx->SegSs); return &ctx->SegSs;
    case CV_AMD64_DS: *size = sizeof(ctx->SegDs); return &ctx->SegDs;
    case CV_AMD64_FS: *size = sizeof(ctx->SegFs); return &ctx->SegFs;
    case CV_AMD64_GS: *size = sizeof(ctx->SegGs); return &ctx->SegGs;

    }
#endif
    FIXME("Unknown register %x\n", regno);
    return NULL;
}

static const char* x86_64_fetch_regname(unsigned regno)
{
    switch (regno)
    {
    case CV_AMD64_RAX:          return "rax";
    case CV_AMD64_RDX:          return "rdx";
    case CV_AMD64_RCX:          return "rcx";
    case CV_AMD64_RBX:          return "rbx";
    case CV_AMD64_RSI:          return "rsi";
    case CV_AMD64_RDI:          return "rdi";
    case CV_AMD64_RBP:          return "rbp";
    case CV_AMD64_RSP:          return "rsp";
    case CV_AMD64_R8:           return "r8";
    case CV_AMD64_R9:           return "r9";
    case CV_AMD64_R10:          return "r10";
    case CV_AMD64_R11:          return "r11";
    case CV_AMD64_R12:          return "r12";
    case CV_AMD64_R13:          return "r13";
    case CV_AMD64_R14:          return "r14";
    case CV_AMD64_R15:          return "r15";
    case CV_AMD64_RIP:          return "rip";

    case CV_AMD64_XMM0 + 0:     return "xmm0";
    case CV_AMD64_XMM0 + 1:     return "xmm1";
    case CV_AMD64_XMM0 + 2:     return "xmm2";
    case CV_AMD64_XMM0 + 3:     return "xmm3";
    case CV_AMD64_XMM0 + 4:     return "xmm4";
    case CV_AMD64_XMM0 + 5:     return "xmm5";
    case CV_AMD64_XMM0 + 6:     return "xmm6";
    case CV_AMD64_XMM0 + 7:     return "xmm7";
    case CV_AMD64_XMM8 + 0:     return "xmm8";
    case CV_AMD64_XMM8 + 1:     return "xmm9";
    case CV_AMD64_XMM8 + 2:     return "xmm10";
    case CV_AMD64_XMM8 + 3:     return "xmm11";
    case CV_AMD64_XMM8 + 4:     return "xmm12";
    case CV_AMD64_XMM8 + 5:     return "xmm13";
    case CV_AMD64_XMM8 + 6:     return "xmm14";
    case CV_AMD64_XMM8 + 7:     return "xmm15";

    case CV_AMD64_ST0 + 0:      return "st0";
    case CV_AMD64_ST0 + 1:      return "st1";
    case CV_AMD64_ST0 + 2:      return "st2";
    case CV_AMD64_ST0 + 3:      return "st3";
    case CV_AMD64_ST0 + 4:      return "st4";
    case CV_AMD64_ST0 + 5:      return "st5";
    case CV_AMD64_ST0 + 6:      return "st6";
    case CV_AMD64_ST0 + 7:      return "st7";

    case CV_AMD64_EFLAGS:       return "eflags";
    case CV_AMD64_ES:           return "es";
    case CV_AMD64_CS:           return "cs";
    case CV_AMD64_SS:           return "ss";
    case CV_AMD64_DS:           return "ds";
    case CV_AMD64_FS:           return "fs";
    case CV_AMD64_GS:           return "gs";
    }
    FIXME("Unknown register %x\n", regno);
    return NULL;
}

static BOOL x86_64_fetch_minidump_thread(struct dump_context* dc, unsigned index, unsigned flags, const CONTEXT* ctx)
{
    if (ctx->ContextFlags && (flags & ThreadWriteInstructionWindow))
    {
        /* FIXME: crop values across module boundaries, */
#ifdef __x86_64__
        ULONG64 base = ctx->Rip <= 0x80 ? 0 : ctx->Rip - 0x80;
        minidump_add_memory_block(dc, base, ctx->Rip + 0x80 - base, 0);
#endif
    }

    return TRUE;
}

static BOOL x86_64_fetch_minidump_module(struct dump_context* dc, unsigned index, unsigned flags)
{
    /* FIXME: not sure about the flags... */
    if (1)
    {
        /* FIXME: crop values across module boundaries, */
#ifdef __x86_64__
        struct process*         pcs;
        struct module*          module;
        const RUNTIME_FUNCTION* rtf;
        ULONG                   size;

        if (!(pcs = process_find_by_handle(dc->hProcess)) ||
            !(module = module_find_by_addr(pcs, dc->modules[index].base, DMT_UNKNOWN)))
            return FALSE;
        rtf = (const RUNTIME_FUNCTION*)pe_map_directory(module, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &size);
        if (rtf)
        {
            const RUNTIME_FUNCTION* end = (const RUNTIME_FUNCTION*)((const char*)rtf + size);
            UNWIND_INFO ui;

            while (rtf + 1 < end)
            {
                while (rtf->UnwindData & 1)  /* follow chained entry */
                {
                    FIXME("RunTime_Function outside IMAGE_DIRECTORY_ENTRY_EXCEPTION unimplemented yet!\n");
                    return FALSE;
                    /* we need to read into the other process */
                    /* rtf = (RUNTIME_FUNCTION*)(module->module.BaseOfImage + (rtf->UnwindData & ~1)); */
                }
                if (ReadProcessMemory(dc->hProcess,
                                      (void*)(dc->modules[index].base + rtf->UnwindData),
                                      &ui, sizeof(ui), NULL))
                    minidump_add_memory_block(dc, dc->modules[index].base + rtf->UnwindData,
                                              FIELD_OFFSET(UNWIND_INFO, UnwindCode) + ui.CountOfCodes * sizeof(UNWIND_CODE), 0);
                rtf++;
            }
        }
#endif
    }

    return TRUE;
}

DECLSPEC_HIDDEN struct cpu cpu_x86_64 = {
    IMAGE_FILE_MACHINE_AMD64,
    8,
    CV_AMD64_RSP,
    x86_64_get_addr,
    x86_64_stack_walk,
    x86_64_find_runtime_function,
    x86_64_map_dwarf_register,
    x86_64_fetch_context_reg,
    x86_64_fetch_regname,
    x86_64_fetch_minidump_thread,
    x86_64_fetch_minidump_module,
};