Files
Xenia-Canary/src/xenia/cpu/stack_walker_win.cc

321 lines
13 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/stack_walker.h"
#include <cstdint>
#include <mutex>
#include "xenia/base/logging.h"
#include "xenia/base/platform_win.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/backend/code_cache.h"
#include "xenia/cpu/processor.h"
DEFINE_bool(debug_symbol_loader, false,
"Enable dbghelp debug logging and validation.", "CPU");
// Must be included after platform_win.h:
#pragma warning(push)
#pragma warning(disable : 4091)
#include <dbghelp.h> // NOLINT(build/include_order)
#pragma warning(pop)
typedef DWORD(__stdcall* LPSYMGETOPTIONS)(VOID);
typedef DWORD(__stdcall* LPSYMSETOPTIONS)(IN DWORD SymOptions);
typedef BOOL(__stdcall* LPSYMINITIALIZE)(IN HANDLE hProcess,
IN PSTR UserSearchPath,
IN BOOL fInvadeProcess);
typedef BOOL(__stdcall* LPSTACKWALK64)(
DWORD MachineType, HANDLE hProcess, HANDLE hThread,
LPSTACKFRAME64 StackFrame, PVOID ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);
typedef PVOID(__stdcall* LPSYMFUNCTIONTABLEACCESS64)(
HANDLE hProcess,
DWORD64 AddrBase); // DbgHelp.h typedef PFUNCTION_TABLE_ACCESS_ROUTINE64
typedef DWORD64(__stdcall* LPSYMGETMODULEBASE64)(
HANDLE hProcess,
DWORD64 AddrBase); // DbgHelp.h typedef PGET_MODULE_BASE_ROUTINE64
typedef BOOL(__stdcall* LPSYMGETSYMFROMADDR64)(IN HANDLE hProcess,
IN DWORD64 qwAddr,
OUT PDWORD64 pdwDisplacement,
OUT PIMAGEHLP_SYMBOL64 Symbol);
LPSYMGETOPTIONS sym_get_options_ = nullptr;
LPSYMSETOPTIONS sym_set_options_ = nullptr;
LPSYMINITIALIZE sym_initialize_ = nullptr;
LPSTACKWALK64 stack_walk_64_ = nullptr;
LPSYMFUNCTIONTABLEACCESS64 sym_function_table_access_64_ = nullptr;
LPSYMGETMODULEBASE64 sym_get_module_base_64_ = nullptr;
LPSYMGETSYMFROMADDR64 sym_get_sym_from_addr_64_ = nullptr;
namespace xe {
namespace cpu {
bool InitializeStackWalker() {
if (sym_get_options_) {
// Already initialized.
return true;
}
// Attempt to load dbghelp.
// NOTE: we never free it. That's fine.
HMODULE module = LoadLibrary(TEXT("dbghelp.dll"));
if (!module) {
XELOGE("Unable to load dbghelp.dll - not found on path or invalid");
return false;
}
sym_get_options_ = reinterpret_cast<LPSYMGETOPTIONS>(
GetProcAddress(module, "SymGetOptions"));
sym_set_options_ = reinterpret_cast<LPSYMSETOPTIONS>(
GetProcAddress(module, "SymSetOptions"));
sym_initialize_ = reinterpret_cast<LPSYMINITIALIZE>(
GetProcAddress(module, "SymInitialize"));
stack_walk_64_ =
reinterpret_cast<LPSTACKWALK64>(GetProcAddress(module, "StackWalk64"));
sym_function_table_access_64_ = reinterpret_cast<LPSYMFUNCTIONTABLEACCESS64>(
GetProcAddress(module, "SymFunctionTableAccess64"));
sym_get_module_base_64_ = reinterpret_cast<LPSYMGETMODULEBASE64>(
GetProcAddress(module, "SymGetModuleBase64"));
sym_get_sym_from_addr_64_ = reinterpret_cast<LPSYMGETSYMFROMADDR64>(
GetProcAddress(module, "SymGetSymFromAddr64"));
if (!sym_get_options_ || !sym_set_options_ || !sym_initialize_ ||
!stack_walk_64_ || !sym_function_table_access_64_ ||
!sym_get_module_base_64_ || !sym_get_sym_from_addr_64_) {
XELOGE("Unable to get one or more symbols from dbghelp.dll");
return false;
}
// Initialize the symbol lookup services.
DWORD options = sym_get_options_();
if (cvars::debug_symbol_loader) {
options |= SYMOPT_DEBUG;
}
options |= SYMOPT_DEFERRED_LOADS;
options |= SYMOPT_LOAD_LINES;
options |= SYMOPT_FAIL_CRITICAL_ERRORS;
sym_set_options_(options);
if (!sym_initialize_(GetCurrentProcess(), nullptr, TRUE)) {
XELOGE("Unable to initialize symbol services - already in use?");
return false;
}
return true;
}
class Win32StackWalker : public StackWalker {
public:
explicit Win32StackWalker(backend::CodeCache* code_cache) {
// Get the boundaries of the code cache so we can quickly tell if a symbol
// is ours or not.
// We store these globally so that the Sym* callbacks can access them.
// They never change, so it's fine even if they are touched from multiple
// threads.
code_cache_ = code_cache;
code_cache_min_ = code_cache_->execute_base_address();
code_cache_max_ = code_cache_min_ + code_cache_->total_size();
}
bool Initialize() {
std::lock_guard<std::mutex> lock(dbghelp_mutex_);
return InitializeStackWalker();
}
size_t CaptureStackTrace(uint64_t* frame_host_pcs, size_t frame_offset,
size_t frame_count,
uint64_t* out_stack_hash) override {
if (out_stack_hash) {
*out_stack_hash = 0;
}
// Simple method: captures just stack frame PC addresses, optionally
// computing a whole-stack hash.
ULONG back_trace_hash = 0;
DWORD frames_to_skip = DWORD(frame_offset) + 1;
DWORD frames_to_capture =
std::min(DWORD(frame_count), UINT16_MAX - frames_to_skip);
USHORT captured_count = CaptureStackBackTrace(
frames_to_skip, frames_to_capture,
reinterpret_cast<PVOID*>(frame_host_pcs), &back_trace_hash);
if (out_stack_hash) {
*out_stack_hash = back_trace_hash;
}
return captured_count;
}
size_t CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs,
size_t frame_offset, size_t frame_count,
const X64Context* in_host_context,
X64Context* out_host_context,
uint64_t* out_stack_hash) override {
// TODO(benvanik): use xstate?
// https://msdn.microsoft.com/en-us/library/windows/desktop/hh134240(v=vs.85).aspx
// Query context. Thread must be suspended.
// Need at least CONTEXT_CONTROL (for rip and rsp) and CONTEXT_INTEGER (for
// rbp).
CONTEXT thread_context;
if (!in_host_context) {
// If not given a context we need to ask for it.
// This gets the state of the thread exactly where it was when suspended.
thread_context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(thread_handle, &thread_context)) {
XELOGE("Unable to read thread context for stack walk");
return 0;
}
} else {
// Copy thread context local. We will be modifying it during stack
// walking, so we don't want to mess with the incoming copy.
thread_context.Rip = in_host_context->rip;
thread_context.EFlags = in_host_context->eflags;
std::memcpy(&thread_context.Rax, in_host_context->int_registers,
sizeof(in_host_context->int_registers));
std::memcpy(&thread_context.Xmm0, in_host_context->xmm_registers,
sizeof(in_host_context->xmm_registers));
}
if (out_host_context) {
// Write out the captured thread context if the caller asked for it.
out_host_context->rip = thread_context.Rip;
out_host_context->eflags = thread_context.EFlags;
std::memcpy(out_host_context->int_registers, &thread_context.Rax,
sizeof(out_host_context->int_registers));
std::memcpy(out_host_context->xmm_registers, &thread_context.Xmm0,
sizeof(out_host_context->xmm_registers));
}
// Setup the frame for walking.
STACKFRAME64 stack_frame = {0};
stack_frame.AddrPC.Mode = AddrModeFlat;
stack_frame.AddrPC.Offset = thread_context.Rip;
stack_frame.AddrFrame.Mode = AddrModeFlat;
stack_frame.AddrFrame.Offset = thread_context.Rbp;
stack_frame.AddrStack.Mode = AddrModeFlat;
stack_frame.AddrStack.Offset = thread_context.Rsp;
// Walk the stack.
// Note that StackWalk64 is thread safe, though other dbghelp functions are
// not.
size_t frame_index = 0;
while (frame_index < frame_count &&
stack_walk_64_(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(),
thread_handle, &stack_frame, &thread_context, nullptr,
XSymFunctionTableAccess64, XSymGetModuleBase64,
nullptr) == TRUE) {
if (frame_index >= frame_offset) {
frame_host_pcs[frame_index - frame_offset] = stack_frame.AddrPC.Offset;
}
++frame_index;
}
return frame_index - frame_offset;
}
bool ResolveStack(uint64_t* frame_host_pcs, StackFrame* frames,
size_t frame_count) override {
// TODO(benvanik): collect symbols to resolve with dbghelp and resolve
// afterward in a smaller lock.
std::lock_guard<std::mutex> lock(dbghelp_mutex_);
for (size_t i = 0; i < frame_count; ++i) {
auto& frame = frames[i];
std::memset(&frame, 0, sizeof(frame));
frame.host_pc = frame_host_pcs[i];
// If in the generated range, we know it's ours.
if (frame.host_pc >= code_cache_min_ && frame.host_pc < code_cache_max_) {
// Guest symbol, so we can look it up quickly in the code cache.
frame.type = StackFrame::Type::kGuest;
auto function = code_cache_->LookupFunction(frame.host_pc);
if (function) {
frame.guest_symbol.function = function;
// Figure out where in guest code we are by looking up the
// displacement in x64 from the JIT'ed code start to the PC.
if (function->is_guest()) {
auto guest_function = static_cast<GuestFunction*>(function);
// Adjust the host PC by -1 so that we will go back into whatever
// instruction was executing before the capture (like a call).
frame.guest_pc =
guest_function->MapMachineCodeToGuestAddress(frame.host_pc - 1);
}
} else {
frame.guest_symbol.function = nullptr;
}
} else {
// Host symbol, which means either emulator or system.
frame.type = StackFrame::Type::kHost;
// TODO(benvanik): cache so that we can avoid calling into dbghelp (and
// taking the lock).
union {
IMAGEHLP_SYMBOL64 info;
uint8_t buffer[sizeof(IMAGEHLP_SYMBOL64) +
MAX_SYM_NAME * sizeof(CHAR) + sizeof(ULONG64) - 1];
} symbol;
symbol.info.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
symbol.info.MaxNameLength = MAX_SYM_NAME;
uint64_t displacement = 0;
if (sym_get_sym_from_addr_64_(GetCurrentProcess(), frame.host_pc,
&displacement, &symbol.info)) {
// Resolved successfully.
// TODO(benvanik): stash: module, base, displacement, name?
frame.host_symbol.address = symbol.info.Address;
std::strncpy(frame.host_symbol.name, symbol.info.Name, 256);
}
}
}
return true;
}
private:
static PVOID WINAPI XSymFunctionTableAccess64(__in HANDLE hProcess,
__in DWORD64 AddrBase) {
if (AddrBase >= code_cache_min_ && AddrBase < code_cache_max_) {
// Within our generated range so ask code cache.
return code_cache_->LookupUnwindInfo(AddrBase);
}
// Normal symbol lookup.
return sym_function_table_access_64_(hProcess, AddrBase);
}
static DWORD64 WINAPI XSymGetModuleBase64(_In_ HANDLE hProcess,
_In_ DWORD64 dwAddr) {
if (dwAddr >= code_cache_min_ && dwAddr < code_cache_max_) {
// In our generated range all addresses are relative to the code cache
// base.
return code_cache_min_;
}
// Normal module base lookup.
return sym_get_module_base_64_(hProcess, dwAddr);
}
std::mutex dbghelp_mutex_;
static xe::cpu::backend::CodeCache* code_cache_;
static uintptr_t code_cache_min_;
static uintptr_t code_cache_max_;
};
xe::cpu::backend::CodeCache* Win32StackWalker::code_cache_ = nullptr;
uintptr_t Win32StackWalker::code_cache_min_ = 0;
uintptr_t Win32StackWalker::code_cache_max_ = 0;
std::unique_ptr<StackWalker> StackWalker::Create(
backend::CodeCache* code_cache) {
auto stack_walker = std::make_unique<Win32StackWalker>(code_cache);
if (!stack_walker->Initialize()) {
XELOGE("Unable to initialize stack walker: debug/save states disabled");
return nullptr;
}
return std::unique_ptr<StackWalker>(stack_walker.release());
}
} // namespace cpu
} // namespace xe