| /* |
| * 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, |
| }; |