| /* | |
| * Stack walking | |
| * | |
| * Copyright 1995 Alexandre Julliard | |
| * Copyright 1996 Eric Youngdale | |
| * Copyright 1999 Ove Kåven | |
| * Copyright 2004 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 <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <assert.h> | |
| #include "dbghelp_private.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(dbghelp); | |
| static DWORD64 WINAPI addr_to_linear(HANDLE hProcess, HANDLE hThread, ADDRESS64* addr) | |
| { | |
| LDT_ENTRY le; | |
| switch (addr->Mode) | |
| { | |
| case AddrMode1616: | |
| if (GetThreadSelectorEntry(hThread, addr->Segment, &le)) | |
| return (le.HighWord.Bits.BaseHi << 24) + | |
| (le.HighWord.Bits.BaseMid << 16) + le.BaseLow + LOWORD(addr->Offset); | |
| break; | |
| case AddrMode1632: | |
| if (GetThreadSelectorEntry(hThread, addr->Segment, &le)) | |
| return (le.HighWord.Bits.BaseHi << 24) + | |
| (le.HighWord.Bits.BaseMid << 16) + le.BaseLow + addr->Offset; | |
| break; | |
| case AddrModeReal: | |
| return (DWORD)(LOWORD(addr->Segment) << 4) + addr->Offset; | |
| case AddrModeFlat: | |
| return addr->Offset; | |
| default: | |
| FIXME("Unsupported (yet) mode (%x)\n", addr->Mode); | |
| return 0; | |
| } | |
| FIXME("Failed to linearize address %04x:%I64x (mode %x)\n", | |
| addr->Segment, addr->Offset, addr->Mode); | |
| return 0; | |
| } | |
| #ifndef _WIN64 | |
| static BOOL CALLBACK read_mem(HANDLE hProcess, DWORD addr, void* buffer, | |
| DWORD size, LPDWORD nread) | |
| { | |
| SIZE_T r; | |
| if (!ReadProcessMemory(hProcess, (void*)(DWORD_PTR)addr, buffer, size, &r)) return FALSE; | |
| if (nread) *nread = r; | |
| return TRUE; | |
| } | |
| #endif | |
| static BOOL CALLBACK read_mem64(HANDLE hProcess, DWORD64 addr, void* buffer, | |
| DWORD size, LPDWORD nread) | |
| { | |
| SIZE_T r; | |
| if (!ReadProcessMemory(hProcess, (void*)(DWORD_PTR)addr, buffer, size, &r)) return FALSE; | |
| if (nread) *nread = r; | |
| return TRUE; | |
| } | |
| /* | |
| * Access ARM64EC/CHPE metadata that describes the type of code (ARM64, | |
| * ARM64EC, x64) for each range in dbghelp's mapped copy of the module. | |
| * This follows the same load-config CHPEMetadataPointer lookup used by | |
| * tools/winedump/pe.c:get_hybrid_metadata(), but uses dbghelp's module mapping | |
| * helpers to read from the PE module. | |
| */ | |
| BOOL read_module_chpe_metadata(struct module *module, IMAGE_ARM64EC_METADATA *metadata) | |
| { | |
| const IMAGE_LOAD_CONFIG_DIRECTORY64 *cfg; | |
| const BYTE *metadata_ptr; | |
| DWORD size, rva; | |
| DWORD64 image_base; | |
| if (!(cfg = (const IMAGE_LOAD_CONFIG_DIRECTORY64 *)pe_map_directory(module, | |
| IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &size))) | |
| return FALSE; | |
| /* Bail out if the 'load-config directory' doesn't contain CHPE metadata. */ | |
| size = min(size, cfg->Size); | |
| if (size <= offsetof(IMAGE_LOAD_CONFIG_DIRECTORY64, CHPEMetadataPointer) || !cfg->CHPEMetadataPointer) | |
| { | |
| pe_unmap_directory(module, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, (const char *)cfg); | |
| return FALSE; | |
| } | |
| image_base = module->module.BaseOfImage - module->reloc_delta; | |
| /* Bail out if CHPEMetadataPointer points outside the module's image. */ | |
| if (cfg->CHPEMetadataPointer < image_base || cfg->CHPEMetadataPointer >= image_base + module->module.ImageSize) | |
| { | |
| pe_unmap_directory(module, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, (const char *)cfg); | |
| return FALSE; | |
| } | |
| /* Convert from virtual address to relative virtual address. */ | |
| rva = cfg->CHPEMetadataPointer - image_base; | |
| pe_unmap_directory(module, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, (const char *)cfg); | |
| /* Obtain a pointer to the metadata and read from it. */ | |
| if (!(metadata_ptr = pe_lock_region_from_rva(module, rva, sizeof(*metadata), NULL))) return FALSE; | |
| *metadata = *(const IMAGE_ARM64EC_METADATA *)metadata_ptr; | |
| pe_unlock_region(module, metadata_ptr); | |
| return TRUE; | |
| } | |
| #ifdef __aarch64__ | |
| /* Offset of STACKFRAME64's 'Reserved' field used to store the effective | |
| machine between calls */ | |
| #define STACKWALK_EFFECTIVE_MACHINE_RESERVED 2 | |
| /* | |
| * Return the walker machine for a PC inside an ARM64EC/ARM64X CodeMap range. | |
| * ARM64EC ranges are ARM64 instructions with ARM64 unwind records, so they | |
| * use the ARM64 walker; x64 ranges use the AMD64 walker. | |
| */ | |
| static DWORD chpe_range_effective_machine(struct module *module, DWORD64 pc) | |
| { | |
| const IMAGE_CHPE_RANGE_ENTRY *map; | |
| IMAGE_ARM64EC_METADATA metadata; | |
| DWORD machine = 0; | |
| ULONG i; | |
| /* Bail out if the module has no CodeMap to classify mixed-code ranges. */ | |
| if (!read_module_chpe_metadata(module, &metadata) || !metadata.CodeMap || !metadata.CodeMapCount) | |
| return 0; | |
| /* Map the CodeMap array described by the ARM64EC metadata. */ | |
| if (!(map = (const IMAGE_CHPE_RANGE_ENTRY *)pe_lock_region_from_rva(module, metadata.CodeMap, | |
| metadata.CodeMapCount * sizeof(*map), NULL))) | |
| return 0; | |
| /* | |
| * Interpret the CodeMap range table in the same way as | |
| * dlls/ntdll/unix/virtual.c:update_arm64ec_ranges(). dbghelp uses its PE | |
| * module mapping helpers instead of ntdll's live process mapping. | |
| */ | |
| for (i = 0; i < metadata.CodeMapCount; i++) | |
| { | |
| DWORD64 start, end; | |
| DWORD type = map[i].StartOffset & 0x3; | |
| /* Convert the CodeMap RVA range to loaded module addresses. */ | |
| start = module->module.BaseOfImage + (map[i].StartOffset & ~3u); | |
| end = start + map[i].Length; | |
| if (pc >= start && pc < end) | |
| { | |
| switch (type) | |
| { | |
| case 0: /* ARM64 */ | |
| case 1: /* ARM64EC */ | |
| machine = IMAGE_FILE_MACHINE_ARM64; | |
| break; | |
| case 2: /* x64 */ | |
| machine = IMAGE_FILE_MACHINE_AMD64; | |
| break; | |
| } | |
| break; | |
| } | |
| } | |
| pe_unlock_region(module, (const BYTE *)map); | |
| return machine; | |
| } | |
| /* Return the stack walker machine for a PC, refining hybrid modules with the CHPE CodeMap. */ | |
| DWORD stack_effective_machine(HANDLE process, DWORD64 pc) | |
| { | |
| struct module *module; | |
| DWORD machine; | |
| if (!pc || !(module = module_find_by_addr(process_find_by_handle(process), pc))) return 0; | |
| switch (module->module.MachineType) | |
| { | |
| case IMAGE_FILE_MACHINE_AMD64: | |
| /* | |
| * Some hybrid modules have an AMD64-facing machine type but still carry | |
| * CHPE ranges. Prefer the range classification when present. | |
| */ | |
| if ((machine = chpe_range_effective_machine(module, pc))) return machine; | |
| return IMAGE_FILE_MACHINE_AMD64; | |
| case IMAGE_FILE_MACHINE_ARM64: | |
| return IMAGE_FILE_MACHINE_ARM64; | |
| case IMAGE_FILE_MACHINE_ARM64EC: | |
| case IMAGE_FILE_MACHINE_ARM64X: | |
| /* | |
| * ARM64EC/ARM64X modules are hybrid. Use the CodeMap to distinguish | |
| * ARM64/ARM64EC ranges from x64 ranges; fall back to the x64-compatible | |
| * view if the PC is not classified. | |
| */ | |
| if ((machine = chpe_range_effective_machine(module, pc))) return machine; | |
| return IMAGE_FILE_MACHINE_AMD64; | |
| default: | |
| return 0; | |
| } | |
| } | |
| static BOOL is_supported_effective_machine(DWORD64 machine) | |
| { | |
| return machine == IMAGE_FILE_MACHINE_AMD64 || machine == IMAGE_FILE_MACHINE_ARM64; | |
| } | |
| /* | |
| * ARM64EC code uses ARM64 instructions and unwind records, but exposes an | |
| * x64-compatible register context. This follows the ARM64EC path in | |
| * dlls/ntdll/unwind.c:RtlVirtualUnwind2(). | |
| * | |
| * These helpers only reshape the state needed to continue stack walking across | |
| * ARM64EC/x64 transition frames; they do not reconstruct a complete | |
| * architectural context. In particular, SIMD/FPU/debug state and precise flags | |
| * are not modeled here. | |
| */ | |
| /* | |
| * Convert ARM64 unwind state back to an AMD64-shaped context. This is the | |
| * subset of dlls/ntdll/unwind.h:context_arm_to_x64() needed by StackWalkEx(). | |
| */ | |
| static void arm64ec_unwind_to_amd64_context(AMD64_CONTEXT *amd64, const CONTEXT *arm64) | |
| { | |
| memset(amd64, 0, sizeof(*amd64)); | |
| amd64->ContextFlags = CONTEXT_AMD64_FULL; | |
| /* The AMD64 walker expects plausible segment/flag values, but they are not unwind state. */ | |
| amd64->SegCs = 0x33; | |
| amd64->SegDs = 0x2b; | |
| amd64->SegEs = 0x2b; | |
| amd64->SegFs = 0x53; | |
| amd64->SegGs = 0x2b; | |
| amd64->SegSs = 0x2b; | |
| amd64->EFlags = 0x202; | |
| amd64->Rax = arm64->X8; | |
| amd64->Rcx = arm64->X0; | |
| amd64->Rdx = arm64->X1; | |
| amd64->Rbx = arm64->X27; | |
| amd64->Rsp = arm64->Sp; | |
| amd64->Rbp = arm64->Fp; | |
| amd64->Rsi = arm64->X25; | |
| amd64->Rdi = arm64->X26; | |
| amd64->R8 = arm64->X2; | |
| amd64->R9 = arm64->X3; | |
| amd64->R10 = arm64->X4; | |
| amd64->R11 = arm64->X5; | |
| amd64->R12 = arm64->X19; | |
| amd64->R13 = arm64->X20; | |
| amd64->R14 = arm64->X21; | |
| amd64->R15 = arm64->X22; | |
| amd64->Rip = arm64->Pc; | |
| } | |
| /* | |
| * Convert an AMD64-shaped context to the ARM64 state needed by the ARM64 | |
| * walker. This is the subset of dlls/ntdll/unwind.h:context_x64_to_arm() | |
| * needed by StackWalkEx(). | |
| */ | |
| static void amd64_unwind_to_arm64ec_context(CONTEXT *arm64, const AMD64_CONTEXT *amd64) | |
| { | |
| memset(arm64, 0, sizeof(*arm64)); | |
| arm64->ContextFlags = CONTEXT_FULL; | |
| arm64->X8 = amd64->Rax; | |
| arm64->X0 = amd64->Rcx; | |
| arm64->X1 = amd64->Rdx; | |
| arm64->X27 = amd64->Rbx; | |
| arm64->Sp = amd64->Rsp; | |
| arm64->Fp = amd64->Rbp; | |
| arm64->X25 = amd64->Rsi; | |
| arm64->X26 = amd64->Rdi; | |
| arm64->X2 = amd64->R8; | |
| arm64->X3 = amd64->R9; | |
| arm64->X4 = amd64->R10; | |
| arm64->X5 = amd64->R11; | |
| arm64->X19 = amd64->R12; | |
| arm64->X20 = amd64->R13; | |
| arm64->X21 = amd64->R14; | |
| arm64->X22 = amd64->R15; | |
| arm64->Pc = amd64->Rip; | |
| } | |
| /* | |
| * After converting the register context, rebuild the generic STACKFRAME64 | |
| * fields expected by the selected CPU walker. | |
| */ | |
| static void set_arm64_frame(STACKFRAME64 *frame, const CONTEXT *ctx) | |
| { | |
| frame->AddrFrame.Mode = frame->AddrPC.Mode = frame->AddrStack.Mode = frame->AddrReturn.Mode = AddrModeFlat; | |
| frame->AddrFrame.Segment = frame->AddrPC.Segment = frame->AddrStack.Segment = frame->AddrReturn.Segment = 0; | |
| frame->AddrFrame.Offset = ctx->Fp; | |
| frame->AddrPC.Offset = ctx->Pc; | |
| frame->AddrStack.Offset = ctx->Sp; | |
| /* ARM64 carries the return address in LR. Keep AddrReturn nonzero if LR is unavailable. */ | |
| frame->AddrReturn.Offset = ctx->Lr ? ctx->Lr : ctx->Pc; | |
| } | |
| static void set_amd64_frame(STACKFRAME64 *frame, const AMD64_CONTEXT *ctx) | |
| { | |
| frame->AddrFrame.Mode = frame->AddrPC.Mode = frame->AddrStack.Mode = frame->AddrReturn.Mode = AddrModeFlat; | |
| frame->AddrFrame.Segment = frame->AddrStack.Segment = frame->AddrReturn.Segment = ctx->SegSs; | |
| frame->AddrPC.Segment = ctx->SegCs; | |
| frame->AddrFrame.Offset = ctx->Rbp; | |
| frame->AddrPC.Offset = ctx->Rip; | |
| frame->AddrStack.Offset = ctx->Rsp; | |
| /* | |
| * As AMD64 has no link register, seed AddrReturn with a nonzero value. The | |
| * AMD64 walker will replace it with the caller RIP after trial-unwinding | |
| * the context. | |
| */ | |
| frame->AddrReturn.Offset = ctx->Rip; | |
| } | |
| /* | |
| * Switch the active CPU walker and translate the context when the current | |
| * frame belongs to another machine. Return FALSE only if the requested switch | |
| * cannot be performed; changed records whether a switch was actually made. | |
| */ | |
| static BOOL stack_update_effective_machine(struct cpu_stack_walk *csw, DWORD *machine, struct cpu **cpu, | |
| STACKFRAME64 *frame, union ctx *context, DWORD next_machine, | |
| BOOL *changed) | |
| { | |
| struct cpu *next_cpu; | |
| if (changed) *changed = FALSE; | |
| if (!is_supported_effective_machine(next_machine)) return TRUE; | |
| if (next_machine == *machine) | |
| { | |
| frame->Reserved[STACKWALK_EFFECTIVE_MACHINE_RESERVED] = *machine; | |
| return TRUE; | |
| } | |
| if (!is_supported_effective_machine(*machine)) return TRUE; | |
| if (!(next_cpu = cpu_find(next_machine))) return FALSE; | |
| if (*machine == IMAGE_FILE_MACHINE_AMD64) | |
| { | |
| AMD64_CONTEXT amd64 = context->amd64; | |
| amd64_unwind_to_arm64ec_context(&context->ctx, &amd64); | |
| context->ctx.Lr = frame->AddrReturn.Offset ? frame->AddrReturn.Offset : context->ctx.Pc; | |
| set_arm64_frame(frame, &context->ctx); | |
| } | |
| else | |
| { | |
| CONTEXT arm64 = context->ctx; | |
| arm64ec_unwind_to_amd64_context(&context->amd64, &arm64); | |
| set_amd64_frame(frame, &context->amd64); | |
| } | |
| memset(frame->Reserved, 0, sizeof(frame->Reserved)); | |
| frame->Reserved[STACKWALK_EFFECTIVE_MACHINE_RESERVED] = next_machine; | |
| *machine = next_machine; | |
| *cpu = next_cpu; | |
| csw->cpu = next_cpu; | |
| if (changed) *changed = TRUE; | |
| return TRUE; | |
| } | |
| #endif | |
| #ifndef _WIN64 | |
| static inline void addr_32to64(const ADDRESS* addr32, ADDRESS64* addr64) | |
| { | |
| addr64->Offset = (ULONG64)addr32->Offset; | |
| addr64->Segment = addr32->Segment; | |
| addr64->Mode = addr32->Mode; | |
| } | |
| #endif | |
| static inline void addr_64to32(const ADDRESS64* addr64, ADDRESS* addr32) | |
| { | |
| addr32->Offset = (ULONG)addr64->Offset; | |
| addr32->Segment = addr64->Segment; | |
| addr32->Mode = addr64->Mode; | |
| } | |
| BOOL sw_read_mem(struct cpu_stack_walk* csw, DWORD64 addr, void* ptr, DWORD sz) | |
| { | |
| DWORD bytes_read = 0; | |
| if (csw->is32) | |
| return csw->u.s32.f_read_mem(csw->hProcess, addr, ptr, sz, &bytes_read); | |
| else | |
| return csw->u.s64.f_read_mem(csw->hProcess, addr, ptr, sz, &bytes_read); | |
| } | |
| DWORD64 sw_xlat_addr(struct cpu_stack_walk* csw, ADDRESS64* addr) | |
| { | |
| if (addr->Mode == AddrModeFlat) return addr->Offset; | |
| if (csw->is32) | |
| { | |
| ADDRESS addr32; | |
| addr_64to32(addr, &addr32); | |
| return csw->u.s32.f_xlat_adr(csw->hProcess, csw->hThread, &addr32); | |
| } | |
| else if (csw->u.s64.f_xlat_adr) | |
| return csw->u.s64.f_xlat_adr(csw->hProcess, csw->hThread, addr); | |
| return addr_to_linear(csw->hProcess, csw->hThread, addr); | |
| } | |
| void* sw_table_access(struct cpu_stack_walk* csw, DWORD64 addr) | |
| { | |
| if (csw->is32) | |
| return csw->u.s32.f_tabl_acs(csw->hProcess, addr); | |
| else | |
| return csw->u.s64.f_tabl_acs(csw->hProcess, addr); | |
| } | |
| DWORD64 sw_module_base(struct cpu_stack_walk* csw, DWORD64 addr) | |
| { | |
| if (csw->is32) | |
| return csw->u.s32.f_modl_bas(csw->hProcess, addr); | |
| else | |
| return csw->u.s64.f_modl_bas(csw->hProcess, addr); | |
| } | |
| #ifndef _WIN64 | |
| /*********************************************************************** | |
| * StackWalk (DBGHELP.@) | |
| */ | |
| BOOL WINAPI StackWalk(DWORD MachineType, HANDLE hProcess, HANDLE hThread, | |
| LPSTACKFRAME frame32, PVOID ctx, | |
| PREAD_PROCESS_MEMORY_ROUTINE f_read_mem, | |
| PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, | |
| PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, | |
| PTRANSLATE_ADDRESS_ROUTINE f_xlat_adr) | |
| { | |
| struct cpu_stack_walk csw; | |
| STACKFRAME64 frame64; | |
| BOOL ret; | |
| struct cpu* cpu; | |
| TRACE("(%ld, %p, %p, %p, %p, %p, %p, %p, %p)\n", | |
| MachineType, hProcess, hThread, frame32, ctx, | |
| f_read_mem, FunctionTableAccessRoutine, | |
| GetModuleBaseRoutine, f_xlat_adr); | |
| if (!(cpu = cpu_find(MachineType))) | |
| { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| addr_32to64(&frame32->AddrPC, &frame64.AddrPC); | |
| addr_32to64(&frame32->AddrReturn, &frame64.AddrReturn); | |
| addr_32to64(&frame32->AddrFrame, &frame64.AddrFrame); | |
| addr_32to64(&frame32->AddrStack, &frame64.AddrStack); | |
| addr_32to64(&frame32->AddrBStore, &frame64.AddrBStore); | |
| frame64.FuncTableEntry = frame32->FuncTableEntry; /* FIXME */ | |
| frame64.Far = frame32->Far; | |
| frame64.Virtual = frame32->Virtual; | |
| frame64.Reserved[0] = frame32->Reserved[0]; | |
| frame64.Reserved[1] = frame32->Reserved[1]; | |
| frame64.Reserved[2] = frame32->Reserved[2]; | |
| /* we don't handle KdHelp */ | |
| csw.hProcess = hProcess; | |
| csw.hThread = hThread; | |
| csw.is32 = TRUE; | |
| csw.cpu = cpu; | |
| /* sigh... MS isn't even consistent in the func prototypes */ | |
| csw.u.s32.f_read_mem = (f_read_mem) ? f_read_mem : read_mem; | |
| csw.u.s32.f_xlat_adr = f_xlat_adr; | |
| csw.u.s32.f_tabl_acs = (FunctionTableAccessRoutine) ? FunctionTableAccessRoutine : SymFunctionTableAccess; | |
| csw.u.s32.f_modl_bas = (GetModuleBaseRoutine) ? GetModuleBaseRoutine : SymGetModuleBase; | |
| if ((ret = cpu->stack_walk(&csw, &frame64, ctx))) | |
| { | |
| addr_64to32(&frame64.AddrPC, &frame32->AddrPC); | |
| addr_64to32(&frame64.AddrReturn, &frame32->AddrReturn); | |
| addr_64to32(&frame64.AddrFrame, &frame32->AddrFrame); | |
| addr_64to32(&frame64.AddrStack, &frame32->AddrStack); | |
| addr_64to32(&frame64.AddrBStore, &frame32->AddrBStore); | |
| frame32->FuncTableEntry = frame64.FuncTableEntry; /* FIXME */ | |
| frame32->Params[0] = frame64.Params[0]; | |
| frame32->Params[1] = frame64.Params[1]; | |
| frame32->Params[2] = frame64.Params[2]; | |
| frame32->Params[3] = frame64.Params[3]; | |
| frame32->Far = frame64.Far; | |
| frame32->Virtual = frame64.Virtual; | |
| frame32->Reserved[0] = frame64.Reserved[0]; | |
| frame32->Reserved[1] = frame64.Reserved[1]; | |
| frame32->Reserved[2] = frame64.Reserved[2]; | |
| } | |
| return ret; | |
| } | |
| #endif | |
| /*********************************************************************** | |
| * StackWalk64 (DBGHELP.@) | |
| */ | |
| BOOL WINAPI StackWalk64(DWORD MachineType, HANDLE hProcess, HANDLE hThread, | |
| LPSTACKFRAME64 frame, PVOID ctx, | |
| PREAD_PROCESS_MEMORY_ROUTINE64 f_read_mem, | |
| PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, | |
| PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, | |
| PTRANSLATE_ADDRESS_ROUTINE64 f_xlat_adr) | |
| { | |
| struct cpu_stack_walk csw; | |
| struct cpu* cpu; | |
| TRACE("(%ld, %p, %p, %p, %p, %p, %p, %p, %p)\n", | |
| MachineType, hProcess, hThread, frame, ctx, | |
| f_read_mem, FunctionTableAccessRoutine, | |
| GetModuleBaseRoutine, f_xlat_adr); | |
| if (!(cpu = cpu_find(MachineType))) | |
| { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| csw.hProcess = hProcess; | |
| csw.hThread = hThread; | |
| csw.is32 = FALSE; | |
| csw.cpu = cpu; | |
| /* sigh... MS isn't even consistent in the func prototypes */ | |
| csw.u.s64.f_read_mem = (f_read_mem) ? f_read_mem : read_mem64; | |
| csw.u.s64.f_xlat_adr = (f_xlat_adr) ? f_xlat_adr : addr_to_linear; | |
| csw.u.s64.f_tabl_acs = (FunctionTableAccessRoutine) ? FunctionTableAccessRoutine : SymFunctionTableAccess64; | |
| csw.u.s64.f_modl_bas = (GetModuleBaseRoutine) ? GetModuleBaseRoutine : SymGetModuleBase64; | |
| if (!cpu->stack_walk(&csw, frame, ctx)) return FALSE; | |
| /* we don't handle KdHelp */ | |
| return TRUE; | |
| } | |
| /* all the fields of STACKFRAME64 are present in STACKFRAME_EX at same offset | |
| * So casting down a STACKFRAME_EX into a STACKFRAME64 is valid! | |
| */ | |
| C_ASSERT(sizeof(STACKFRAME64) == FIELD_OFFSET(STACKFRAME_EX, StackFrameSize)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, AddrPC) == FIELD_OFFSET(STACKFRAME_EX, AddrPC)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, AddrReturn) == FIELD_OFFSET(STACKFRAME_EX, AddrReturn)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, AddrFrame) == FIELD_OFFSET(STACKFRAME_EX, AddrFrame)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, AddrStack) == FIELD_OFFSET(STACKFRAME_EX, AddrStack)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, AddrBStore) == FIELD_OFFSET(STACKFRAME_EX, AddrBStore)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, FuncTableEntry) == FIELD_OFFSET(STACKFRAME_EX, FuncTableEntry)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, Params) == FIELD_OFFSET(STACKFRAME_EX, Params)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, Far) == FIELD_OFFSET(STACKFRAME_EX, Far)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, Virtual) == FIELD_OFFSET(STACKFRAME_EX, Virtual)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, Reserved) == FIELD_OFFSET(STACKFRAME_EX, Reserved)); | |
| C_ASSERT(FIELD_OFFSET(STACKFRAME64, KdHelp) == FIELD_OFFSET(STACKFRAME_EX, KdHelp)); | |
| /*********************************************************************** | |
| * StackWalkEx (DBGHELP.@) | |
| */ | |
| BOOL WINAPI StackWalkEx(DWORD MachineType, HANDLE hProcess, HANDLE hThread, | |
| LPSTACKFRAME_EX frame, PVOID ctx, | |
| PREAD_PROCESS_MEMORY_ROUTINE64 f_read_mem, | |
| PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, | |
| PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, | |
| PTRANSLATE_ADDRESS_ROUTINE64 f_xlat_adr, | |
| DWORD flags) | |
| { | |
| struct cpu_stack_walk csw; | |
| struct cpu* cpu; | |
| DWORD64 addr; | |
| DWORD machine = MachineType; | |
| TRACE("(%ld, %p, %p, %p, %p, %p, %p, %p, %p, 0x%lx)\n", | |
| MachineType, hProcess, hThread, frame, ctx, | |
| f_read_mem, FunctionTableAccessRoutine, | |
| GetModuleBaseRoutine, f_xlat_adr, flags); | |
| if (frame->StackFrameSize != sizeof(*frame)) | |
| { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| #ifdef __aarch64__ | |
| if (is_supported_effective_machine(MachineType) && | |
| is_supported_effective_machine(frame->Reserved[STACKWALK_EFFECTIVE_MACHINE_RESERVED])) | |
| machine = frame->Reserved[STACKWALK_EFFECTIVE_MACHINE_RESERVED]; | |
| #endif | |
| if (!(cpu = cpu_find(machine))) | |
| { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| if (flags != 0) | |
| { | |
| FIXME("Unsupported yet flags 0x%lx\n", flags); | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| return FALSE; | |
| } | |
| csw.hProcess = hProcess; | |
| csw.hThread = hThread; | |
| csw.is32 = FALSE; | |
| csw.cpu = cpu; | |
| /* sigh... MS isn't even consistent in the func prototypes */ | |
| csw.u.s64.f_read_mem = (f_read_mem) ? f_read_mem : read_mem64; | |
| csw.u.s64.f_xlat_adr = (f_xlat_adr) ? f_xlat_adr : addr_to_linear; | |
| csw.u.s64.f_tabl_acs = (FunctionTableAccessRoutine) ? FunctionTableAccessRoutine : SymFunctionTableAccess64; | |
| csw.u.s64.f_modl_bas = (GetModuleBaseRoutine) ? GetModuleBaseRoutine : SymGetModuleBase64; | |
| #ifdef __aarch64__ | |
| if (!stack_update_effective_machine(&csw, &machine, &cpu, (STACKFRAME64*)frame, ctx, | |
| stack_effective_machine(csw.hProcess, frame->AddrPC.Offset), NULL)) | |
| return FALSE; | |
| #endif | |
| addr = sw_xlat_addr(&csw, &frame->AddrPC); | |
| if (IFC_MODE(frame->InlineFrameContext) == IFC_MODE_INLINE) | |
| { | |
| DWORD depth = SymAddrIncludeInlineTrace(hProcess, addr); | |
| if (IFC_DEPTH(frame->InlineFrameContext) + 1 < depth) /* move to next inlined function? */ | |
| { | |
| TRACE("found inline ctx: depth=%lu current=%lu++\n", | |
| depth, frame->InlineFrameContext); | |
| frame->InlineFrameContext++; /* just increase index, FIXME detect overflow */ | |
| } | |
| else | |
| { | |
| frame->InlineFrameContext = IFC_MODE_REGULAR; /* move to next top level function */ | |
| } | |
| } | |
| else | |
| { | |
| if (!cpu->stack_walk(&csw, (STACKFRAME64*)frame, ctx)) return FALSE; | |
| #ifdef __aarch64__ | |
| { | |
| BOOL changed; | |
| /* | |
| * The produced frame may belong to a different effective machine | |
| * from the frame just consumed. Reclassify it before returning to | |
| * the caller. This runs after every public StackWalkEx() step, so | |
| * later steps can switch back again. | |
| */ | |
| if (!stack_update_effective_machine(&csw, &machine, &cpu, (STACKFRAME64*)frame, ctx, | |
| stack_effective_machine(csw.hProcess, frame->AddrPC.Offset), &changed)) | |
| return FALSE; | |
| if (changed) | |
| { | |
| /* | |
| * Switching walkers clears Reserved[0], which is the selected | |
| * walker's private state. The first call to a walker only | |
| * initializes it from the current frame, so do that here. | |
| * Otherwise the next public StackWalkEx() call would return | |
| * the same frame again. | |
| */ | |
| if (!cpu->stack_walk(&csw, (STACKFRAME64*)frame, ctx)) return FALSE; | |
| } | |
| } | |
| #endif | |
| if (frame->InlineFrameContext != INLINE_FRAME_CONTEXT_IGNORE) | |
| { | |
| addr = sw_xlat_addr(&csw, &frame->AddrPC); | |
| frame->InlineFrameContext = SymAddrIncludeInlineTrace(hProcess, addr) == 0 ? IFC_MODE_REGULAR : IFC_MODE_INLINE; | |
| TRACE("setting IFC mode to %lx\n", frame->InlineFrameContext); | |
| } | |
| } | |
| /* we don't handle KdHelp */ | |
| return TRUE; | |
| } | |
| #ifndef _WIN64 | |
| /****************************************************************** | |
| * SymRegisterFunctionEntryCallback (DBGHELP.@) | |
| * | |
| * | |
| */ | |
| BOOL WINAPI SymRegisterFunctionEntryCallback(HANDLE hProc, | |
| PSYMBOL_FUNCENTRY_CALLBACK cb, PVOID user) | |
| { | |
| FIXME("(%p %p %p): stub!\n", hProc, cb, user); | |
| SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | |
| return FALSE; | |
| } | |
| #endif | |
| /****************************************************************** | |
| * SymRegisterFunctionEntryCallback64 (DBGHELP.@) | |
| * | |
| * | |
| */ | |
| BOOL WINAPI SymRegisterFunctionEntryCallback64(HANDLE hProc, | |
| PSYMBOL_FUNCENTRY_CALLBACK64 cb, | |
| ULONG64 user) | |
| { | |
| FIXME("(%p %p %I64x): stub!\n", hProc, cb, user); | |
| SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | |
| return FALSE; | |
| } |