A new debugger.

Lots of bugs/rough edges/etc - issues will be filed.
Old-style debugging still works (just use --emit_source_annotations to get
the helpful movs back and --break_on_instruction will still fire).
This commit is contained in:
Ben Vanik
2015-09-20 21:31:05 -07:00
parent 8046c19898
commit 5d033f9cb3
90 changed files with 4183 additions and 4651 deletions

View File

@@ -13,37 +13,61 @@
#include <functional>
#include <vector>
#include "xenia/base/x64_context.h"
namespace xe {
class ExceptionHandler {
class Exception {
public:
struct Info {
enum {
kInvalidException = 0,
kAccessViolation,
} code = kInvalidException;
uint64_t pc = 0; // Program counter address. RIP on x64.
uint64_t fault_address =
0; // In case of AV, address that was read from/written to.
void* thread_context = nullptr; // Platform-specific thread context info.
enum class Code {
kInvalidException = 0,
kAccessViolation,
kIllegalInstruction,
};
typedef std::function<bool(Info* ex_info)> Handler;
// Static initialization. Only call this once!
static bool Initialize();
void InitializeAccessViolation(X64Context* thread_context,
uint64_t fault_address) {
code_ = Code::kAccessViolation;
thread_context_ = thread_context;
fault_address_ = fault_address;
}
void InitializeIllegalInstruction(X64Context* thread_context) {
code_ = Code::kIllegalInstruction;
thread_context_ = thread_context;
}
// Install an exception handler. Returns an ID which you can save to remove
// this later. This will install the exception handler in the last place.
// TODO: ID support!
static uint32_t Install(Handler fn);
static bool Remove(uint32_t id);
Code code() const { return code_; }
static const std::vector<Handler>& handlers() { return handlers_; }
// Returns the platform-specific thread context info.
X64Context* thread_context() const { return thread_context_; }
// Returns the program counter where the exception occurred.
// RIP on x64.
uint64_t pc() const { return thread_context_->rip; }
// Sets the program counter where execution will resume.
void set_resume_pc(uint64_t pc) { thread_context_->rip = pc; }
// In case of AV, address that was read from/written to.
uint64_t fault_address() const { return fault_address_; }
private:
static std::vector<Handler> handlers_;
Code code_ = Code::kInvalidException;
X64Context* thread_context_ = nullptr;
uint64_t fault_address_ = 0;
};
}; // namespace xe
#endif // XENIA_BASE_EXCEPTION_HANDLER_H_
class ExceptionHandler {
public:
typedef bool (*Handler)(Exception* ex, void* data);
// Installs an exception handler.
// Handlers are called in the order they are installed.
static void Install(Handler fn, void* data);
// Uninstalls a previously-installed exception handler.
static void Uninstall(Handler fn, void* data);
};
} // namespace xe
#endif // XENIA_BASE_EXCEPTION_HANDLER_H_

View File

@@ -9,53 +9,118 @@
#include "xenia/base/exception_handler.h"
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/base/platform_win.h"
namespace xe {
std::vector<ExceptionHandler::Handler> ExceptionHandler::handlers_;
// Handle of the added VectoredExceptionHandler.
void* veh_handle_ = nullptr;
// Handle of the added VectoredContinueHandler.
void* vch_handle_ = nullptr;
// This can be as large as needed, but isn't often needed.
// As we will be sometimes firing many exceptions we want to avoid having to
// scan the table too much or invoke many custom handlers.
constexpr size_t kMaxHandlerCount = 8;
// All custom handlers, left-aligned and null terminated.
// Executed in order.
std::pair<ExceptionHandler::Handler, void*> handlers_[kMaxHandlerCount];
LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
// Visual Studio SetThreadName
// Visual Studio SetThreadName.
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
return EXCEPTION_CONTINUE_SEARCH;
}
auto code = ex_info->ExceptionRecord->ExceptionCode;
ExceptionHandler::Info info;
info.pc = ex_info->ContextRecord->Rip;
info.thread_context = ex_info->ContextRecord;
// TODO(benvanik): avoid this by mapping X64Context virtual?
X64Context thread_context;
thread_context.rip = ex_info->ContextRecord->Rip;
thread_context.eflags = ex_info->ContextRecord->EFlags;
std::memcpy(thread_context.int_registers, &ex_info->ContextRecord->Rax,
sizeof(thread_context.int_registers));
std::memcpy(thread_context.xmm_registers, &ex_info->ContextRecord->Xmm0,
sizeof(thread_context.xmm_registers));
switch (code) {
case STATUS_ACCESS_VIOLATION:
info.code = ExceptionHandler::Info::kAccessViolation;
info.fault_address = ex_info->ExceptionRecord->ExceptionInformation[1];
// http://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
// http://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
Exception ex;
switch (ex_info->ExceptionRecord->ExceptionCode) {
case STATUS_ILLEGAL_INSTRUCTION:
ex.InitializeIllegalInstruction(&thread_context);
break;
case STATUS_ACCESS_VIOLATION:
ex.InitializeAccessViolation(
&thread_context, ex_info->ExceptionRecord->ExceptionInformation[1]);
break;
default:
// Unknown/unhandled type.
return EXCEPTION_CONTINUE_SEARCH;
}
// Only call a handler if we support this type of exception.
if (info.code != ExceptionHandler::Info::kInvalidException) {
for (auto handler : ExceptionHandler::handlers()) {
if (handler(&info)) {
// Exception handled.
return EXCEPTION_CONTINUE_EXECUTION;
}
for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) {
if (handlers_[i].first(&ex, handlers_[i].second)) {
// Exception handled.
// TODO(benvanik): update all thread state? Dirty flags?
ex_info->ContextRecord->Rip = thread_context.rip;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
bool ExceptionHandler::Initialize() {
AddVectoredExceptionHandler(0, ExceptionHandlerCallback);
void ExceptionHandler::Install(Handler fn, void* data) {
if (!veh_handle_) {
veh_handle_ = AddVectoredExceptionHandler(1, ExceptionHandlerCallback);
// TODO: Do we need a continue handler if a debugger is attached?
return true;
if (IsDebuggerPresent()) {
// TODO(benvanik): do we need a continue handler if a debugger is
// attached?
// vch_handle_ = AddVectoredContinueHandler(1, ExceptionHandlerCallback);
}
}
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (!handlers_[i].first) {
handlers_[i].first = fn;
handlers_[i].second = data;
return;
}
}
assert_always("Too many exception handlers installed");
}
uint32_t ExceptionHandler::Install(std::function<bool(Info* ex_info)> fn) {
handlers_.push_back(fn);
void ExceptionHandler::Uninstall(Handler fn, void* data) {
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (handlers_[i].first == fn && handlers_[i].second == data) {
for (; i < xe::countof(handlers_) - 1; ++i) {
handlers_[i] = handlers_[i + 1];
}
handlers_[i].first = nullptr;
handlers_[i].second = nullptr;
break;
}
}
// TODO: ID support!
return 0;
bool has_any = false;
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (handlers_[i].first) {
has_any = true;
break;
}
}
if (!has_any) {
if (veh_handle_) {
RemoveVectoredExceptionHandler(veh_handle_);
veh_handle_ = nullptr;
}
if (vch_handle_) {
RemoveVectoredContinueHandler(vch_handle_);
vch_handle_ = nullptr;
}
}
}
}
} // namespace xe

View File

@@ -179,6 +179,12 @@ inline uint64_t rotate_left(uint64_t v, uint8_t sh) {
}
#endif // XE_PLATFORM_WIN32
template <typename T>
T clamp(T value, T min_value, T max_value) {
const T t = value < min_value ? min_value : value;
return t > max_value ? max_value : t;
}
// Utilities for SSE values.
template <int N>
float m128_f32(const __m128& v) {

View File

@@ -16,6 +16,7 @@
#include <codecvt>
#endif // XE_PLATFORM_LINUX
#include <cctype>
#include <cstring>
#include <locale>
@@ -25,7 +26,7 @@ std::string to_string(const std::wstring& source) {
#if NO_CODECVT
return std::string(source.begin(), source.end());
#else
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t> > converter;
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(source);
#endif // XE_PLATFORM_LINUX
}
@@ -34,7 +35,7 @@ std::wstring to_wstring(const std::string& source) {
#if NO_CODECVT
return std::wstring(source.begin(), source.end());
#else
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t> > converter;
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(source);
#endif // XE_PLATFORM_LINUX
}
@@ -227,4 +228,40 @@ std::wstring find_base_path(const std::wstring& path, wchar_t sep) {
}
}
int fuzzy_match(const std::string& pattern, const char* value) {
// https://github.com/mattyork/fuzzy/blob/master/lib/fuzzy.js
// TODO(benvanik): look at https://github.com/atom/fuzzaldrin/tree/master/src
// This does not weight complete substrings or prefixes right, which
// kind of sucks.
size_t pattern_index = 0;
size_t value_length = std::strlen(value);
int total_score = 0;
int local_score = 0;
for (size_t i = 0; i < value_length; ++i) {
if (std::tolower(value[i]) == std::tolower(pattern[pattern_index])) {
++pattern_index;
local_score += 1 + local_score;
} else {
local_score = 0;
}
total_score += local_score;
}
return total_score;
}
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
const void* const* entries,
size_t entry_count,
size_t string_offset) {
std::vector<std::pair<size_t, int>> results;
results.reserve(entry_count);
for (size_t i = 0; i < entry_count; ++i) {
auto entry_value =
reinterpret_cast<const char*>(entries[i]) + string_offset;
int score = fuzzy_match(pattern, entry_value);
results.emplace_back(i, score);
}
return results;
}
} // namespace xe

View File

@@ -12,6 +12,7 @@
#include <cstdio>
#include <string>
#include <utility>
#include <vector>
#include "xenia/base/platform.h"
@@ -56,6 +57,27 @@ std::string find_base_path(const std::string& path,
std::wstring find_base_path(const std::wstring& path,
wchar_t sep = xe::kPathSeparator);
// Tests a match against a case-insensitive fuzzy filter.
// Returns the score of the match or 0 if none.
int fuzzy_match(const std::string& pattern, const char* value);
// Applies a case-insensitive fuzzy filter to the given entries and ranks
// results.
// Entries is a list of pointers to opaque structs, each of which contains a
// char* string at the given offset.
// Returns an unsorted list of {original index, score}.
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
const void* const* entries,
size_t entry_count,
size_t string_offset);
template <typename T>
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
const std::vector<T>& entries,
size_t string_offset) {
return fuzzy_filter(pattern, reinterpret_cast<void* const*>(entries.data()),
entries.size(), string_offset);
}
} // namespace xe
#endif // XENIA_BASE_STRING_H_

View File

@@ -19,6 +19,7 @@ namespace xe {
StringBuffer::StringBuffer(size_t initial_capacity) {
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16 * 1024));
buffer_ = reinterpret_cast<char*>(malloc(buffer_capacity_));
buffer_[0] = 0;
}
StringBuffer::~StringBuffer() {

View File

@@ -33,6 +33,15 @@ inline std::string to_hex_string(uint64_t value) {
return std::string(buffer);
}
inline std::string to_hex_string(float value) {
union {
uint32_t ui;
float flt;
} v;
v.flt = value;
return to_hex_string(v.ui);
}
inline std::string to_hex_string(double value) {
union {
uint64_t ui;
@@ -57,12 +66,46 @@ inline std::string to_hex_string(const __m128& value) {
return std::string(buffer);
}
inline std::string to_string(const __m128& value) {
char buffer[128];
std::snprintf(buffer, sizeof(buffer), "(%F, %F, %F, %F)", value.m128_f32[0],
value.m128_f32[1], value.m128_f32[2], value.m128_f32[3]);
return std::string(buffer);
}
template <typename T>
inline T from_string(const char* value);
inline T from_string(const char* value, bool force_hex = false);
template <>
inline uint64_t from_string<uint64_t>(const char* value) {
if (std::strchr(value, 'h') != nullptr) {
inline int32_t from_string<int32_t>(const char* value, bool force_hex) {
if (force_hex || std::strchr(value, 'h') != nullptr) {
return std::strtol(value, nullptr, 16);
} else {
return std::strtol(value, nullptr, 0);
}
}
template <>
inline uint32_t from_string<uint32_t>(const char* value, bool force_hex) {
if (force_hex || std::strchr(value, 'h') != nullptr) {
return std::strtoul(value, nullptr, 16);
} else {
return std::strtoul(value, nullptr, 0);
}
}
template <>
inline int64_t from_string<int64_t>(const char* value, bool force_hex) {
if (force_hex || std::strchr(value, 'h') != nullptr) {
return std::strtoll(value, nullptr, 16);
} else {
return std::strtoll(value, nullptr, 0);
}
}
template <>
inline uint64_t from_string<uint64_t>(const char* value, bool force_hex) {
if (force_hex || std::strchr(value, 'h') != nullptr) {
return std::strtoull(value, nullptr, 16);
} else {
return std::strtoull(value, nullptr, 0);
@@ -70,23 +113,38 @@ inline uint64_t from_string<uint64_t>(const char* value) {
}
template <>
inline double from_string<double>(const char* value) {
if (std::strstr(value, "0x") == value || std::strchr(value, 'h') != nullptr) {
inline float from_string<float>(const char* value, bool force_hex) {
if (force_hex || std::strstr(value, "0x") == value ||
std::strchr(value, 'h') != nullptr) {
union {
uint32_t ui;
float flt;
} v;
v.ui = from_string<uint32_t>(value, force_hex);
return v.flt;
}
return std::strtof(value, nullptr);
}
template <>
inline double from_string<double>(const char* value, bool force_hex) {
if (force_hex || std::strstr(value, "0x") == value ||
std::strchr(value, 'h') != nullptr) {
union {
uint64_t ui;
double dbl;
} v;
v.ui = from_string<uint64_t>(value);
v.ui = from_string<uint64_t>(value, force_hex);
return v.dbl;
}
return std::strtod(value, nullptr);
}
template <>
inline vec128_t from_string<vec128_t>(const char* value) {
inline vec128_t from_string<vec128_t>(const char* value, bool force_hex) {
vec128_t v;
char* p = const_cast<char*>(value);
bool hex_mode = false;
bool hex_mode = force_hex;
if (*p == '[') {
hex_mode = true;
++p;
@@ -119,10 +177,10 @@ inline vec128_t from_string<vec128_t>(const char* value) {
}
template <>
inline __m128 from_string<__m128>(const char* value) {
inline __m128 from_string<__m128>(const char* value, bool force_hex) {
__m128 v;
char* p = const_cast<char*>(value);
bool hex_mode = false;
bool hex_mode = force_hex;
if (*p == '[') {
hex_mode = true;
++p;
@@ -155,8 +213,8 @@ inline __m128 from_string<__m128>(const char* value) {
}
template <typename T>
inline T from_string(const std::string& value) {
return from_string<T>(value.c_str());
inline T from_string(const std::string& value, bool force_hex = false) {
return from_string<T>(value.c_str(), force_hex);
}
} // namespace string_util

View File

@@ -0,0 +1,63 @@
/**
******************************************************************************
* 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/base/x64_context.h"
#include "xenia/base/assert.h"
#include "xenia/base/string_util.h"
namespace xe {
// NOTE: this order matches 1:1 with the X64Register enum.
static const char* kRegisterNames[] = {
"rip", "eflags", "rax", "rcx", "rdx", "rbx", "rsp",
"rbp", "rsi", "rdi", "r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9",
"xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
};
const char* X64Context::GetRegisterName(X64Register reg) {
return kRegisterNames[static_cast<int>(reg)];
}
std::string X64Context::GetStringFromValue(X64Register reg, bool hex) const {
switch (reg) {
case X64Register::kRip:
return hex ? string_util::to_hex_string(rip) : std::to_string(rip);
case X64Register::kEflags:
return hex ? string_util::to_hex_string(eflags) : std::to_string(eflags);
default:
if (static_cast<int>(reg) >= static_cast<int>(X64Register::kRax) &&
static_cast<int>(reg) <= static_cast<int>(X64Register::kR15)) {
auto value = int_registers[static_cast<int>(reg) -
static_cast<int>(X64Register::kRax)];
return hex ? string_util::to_hex_string(value) : std::to_string(value);
} else if (static_cast<int>(reg) >=
static_cast<int>(X64Register::kXmm0) &&
static_cast<int>(reg) <=
static_cast<int>(X64Register::kXmm15)) {
auto value = xmm_registers[static_cast<int>(reg) -
static_cast<int>(X64Register::kXmm0)];
return hex ? string_util::to_hex_string(value)
: string_util::to_string(value);
} else {
assert_unhandled_case(reg);
return "";
}
}
}
void X64Context::SetValueFromString(X64Register reg, std::string value,
bool hex) {
// TODO(benvanik): set value from string.
assert_always(false);
}
} // namespace xe

View File

@@ -0,0 +1,112 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_BASE_X64_CONTEXT_H_
#define XENIA_BASE_X64_CONTEXT_H_
#include <cstdint>
#include <string>
namespace xe {
enum class X64Register {
// NOTE: this order matches 1:1 with the order in the X64Context.
// NOTE: this order matches 1:1 with a string table in the x64_context.cc.
kRip,
kEflags,
kRax,
kRcx,
kRdx,
kRbx,
kRsp,
kRbp,
kRsi,
kRdi,
kR8,
kR9,
kR10,
kR11,
kR12,
kR13,
kR14,
kR15,
kXmm0,
kXmm1,
kXmm2,
kXmm3,
kXmm4,
kXmm5,
kXmm6,
kXmm7,
kXmm8,
kXmm9,
kXmm10,
kXmm11,
kXmm12,
kXmm13,
kXmm14,
kXmm15,
};
class X64Context {
public:
uint64_t rip;
uint32_t eflags;
union {
struct {
uint64_t rax;
uint64_t rcx;
uint64_t rdx;
uint64_t rbx;
uint64_t rsp;
uint64_t rbp;
uint64_t rsi;
uint64_t rdi;
uint64_t r8;
uint64_t r9;
uint64_t r10;
uint64_t r11;
uint64_t r12;
uint64_t r13;
uint64_t r14;
uint64_t r15;
};
uint64_t int_registers[16];
};
union {
struct {
__m128 xmm0;
__m128 xmm1;
__m128 xmm2;
__m128 xmm3;
__m128 xmm4;
__m128 xmm5;
__m128 xmm6;
__m128 xmm7;
__m128 xmm8;
__m128 xmm9;
__m128 xmm10;
__m128 xmm11;
__m128 xmm12;
__m128 xmm13;
__m128 xmm14;
__m128 xmm15;
};
__m128 xmm_registers[16];
};
static const char* GetRegisterName(X64Register reg);
std::string GetStringFromValue(X64Register reg, bool hex) const;
void SetValueFromString(X64Register reg, std::string value, bool hex);
};
} // namespace xe
#endif // XENIA_BASE_X64_CONTEXT_H_