mirror of
https://github.com/open-goal/jak-project
synced 2026-08-19 14:03:38 -04:00
fe2086acfb
This adds a debug server to `goalc` that sends JSON over the socket to communicate with an external debugger using the Debug Adapter Protocol. This lets us debug GOAL code in a proper debugger with breakpoints, step over, step in and step out per line, stack frames and supports watches for global symbols, registers and local variables (local variables only work within the most recent stack frame). Special registers (`r13`, `r14`, `r15`, argument registers, etc.) are tracked separately and the current process register even displays the type of the current `pp` if possible. Watches that track addresses holding a reference type generate a list of field names according to the object's type. All fields will show their name, type and value and, depending on the type, will try to infer extra info like symbol names/values, function names for `function` fields, enum values and more. This also works nested, so any field that is also a reference type can also be accessed and display its fields, etc. Dynamic arrays are also supported where possible, e.g. in `inline-array-class` children and boxed arrays, it will figure out the value of the `length` field and access the memory up to that point so all the elements can be accessed and viewed from the `data` field. Our VS Code extension implements the DAP in open-goal/opengoal-vscode#375. Using it is as simple as connecting a REPL to a running game instance with `(lt)`, compiling with `(mi)` and, with the extension installed, pressing F5 in VS Code to start the debugger. By default, it will try to connect to the game that the active `.gc` file is from, the socket port is different per game (8128 for Jak 1, 8129 for Jak 2, 8130 for Jak 3). The `launch.json` was updated with two entries for this, the second entry lets you pick the game/port manually if desired. ~~Not tested on Windows.~~ Only supports x86 for now.
136 lines
3.3 KiB
C++
136 lines
3.3 KiB
C++
/*!
|
|
* @file xdbg.h
|
|
* Debugging utility library. This hides the platform specific details of the debugger.
|
|
* Nothing in here should hold state, that should all be managed in Debugger.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
|
|
#include "common/common_types.h"
|
|
|
|
#ifdef __linux
|
|
#include <sys/types.h>
|
|
#elif _WIN32
|
|
#define NOMINMAX
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
#endif
|
|
|
|
namespace xdbg {
|
|
#ifdef OS_POSIX
|
|
|
|
/*!
|
|
* Identification for a thread.
|
|
*/
|
|
struct ThreadID {
|
|
pid_t id = 0;
|
|
|
|
std::string to_string() const;
|
|
explicit ThreadID(const std::string& str);
|
|
explicit ThreadID(pid_t _id);
|
|
ThreadID() = default;
|
|
};
|
|
|
|
/*!
|
|
* Handle for the memory of a process.
|
|
*/
|
|
struct MemoryHandle {
|
|
int fd;
|
|
};
|
|
|
|
#elif _WIN32
|
|
struct ThreadID {
|
|
DWORD pid = 0;
|
|
DWORD tid = 0;
|
|
|
|
std::string to_string() const;
|
|
ThreadID(const std::string& str);
|
|
ThreadID(DWORD pid, DWORD tid);
|
|
ThreadID() = default;
|
|
};
|
|
|
|
struct MemoryHandle {};
|
|
#endif
|
|
|
|
/*!
|
|
* The info required to debug the target.
|
|
*/
|
|
struct DebugContext {
|
|
ThreadID tid; //! The target's GOAL thread
|
|
uintptr_t base; //! The base address for the GOAL memory
|
|
uint32_t s7; //! The value of s7 (GOAL address)
|
|
};
|
|
|
|
/*!
|
|
* The x86-64 registers, including rip.
|
|
*/
|
|
struct Regs {
|
|
u64 gprs[16];
|
|
u128 xmms[16];
|
|
|
|
u64 rip;
|
|
|
|
std::string print_gprs() const;
|
|
std::string print_xmms_as_flt() const;
|
|
std::string print_xmms_as_int() const;
|
|
std::string print_xmms_as_flt_vec() const;
|
|
};
|
|
|
|
/*!
|
|
* Information about why the target has stopped.
|
|
*/
|
|
struct SignalInfo {
|
|
enum Kind {
|
|
SEGFAULT, // access bad memory
|
|
BREAK, // hit a breakpoint or execute int3
|
|
MATH_EXCEPTION, // divide by zero
|
|
ILLEGAL_INSTR, // bad instruction
|
|
UNKNOWN, // some other signal that is unsupported
|
|
DISAPPEARED, // process disappeared (maybe killed by the user)
|
|
NOTHING, // nothing of importance. Windows sends many irrelevant (to us) events
|
|
EXCEPTION, // some unhandled exception
|
|
|
|
} kind = UNKNOWN;
|
|
|
|
std::string msg;
|
|
};
|
|
|
|
// Functions
|
|
ThreadID get_current_thread_id();
|
|
bool attach_and_break(const ThreadID& tid);
|
|
void allow_debugging();
|
|
bool detach_and_resume(const ThreadID& tid);
|
|
bool get_regs_now(const ThreadID& tid, Regs* out);
|
|
bool set_regs_now(const ThreadID& tid, const Regs& in);
|
|
bool break_now(const ThreadID& tid);
|
|
bool cont_now(const ThreadID& tid);
|
|
bool single_step_now(const ThreadID& tid);
|
|
bool open_memory(const ThreadID& tid, MemoryHandle* out);
|
|
bool close_memory(const ThreadID& tid, MemoryHandle* handle);
|
|
bool read_goal_memory(u8* dest_buffer,
|
|
int size,
|
|
u32 goal_addr,
|
|
const DebugContext& context,
|
|
const MemoryHandle& mem);
|
|
|
|
bool write_goal_memory(const u8* src_buffer,
|
|
int size,
|
|
u32 goal_addr,
|
|
const DebugContext& context,
|
|
const MemoryHandle& mem);
|
|
|
|
template <typename T>
|
|
bool write_goal_value(T& value,
|
|
u32 goal_addr,
|
|
const DebugContext& context,
|
|
const MemoryHandle& handle) {
|
|
return write_goal_memory(&value, sizeof(value), goal_addr, context, handle);
|
|
}
|
|
|
|
bool check_stopped(const ThreadID& tid, SignalInfo* out);
|
|
|
|
} // namespace xdbg
|