Files
jak-project/goalc/debugger/DebugInfo.h
T
Hat Kid fe2086acfb goalc: add debug server (#4365)
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.
2026-07-27 22:28:28 +02:00

119 lines
3.1 KiB
C++

#pragma once
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "common/type_system/TypeSpec.h"
#include "common/util/Assert.h"
#include "goalc/debugger/disassemble.h"
#include "goalc/emitter/Instruction.h"
class FunctionEnv;
namespace goos {
class Object;
class HeapObject;
} // namespace goos
// location of a variable over a range of IR instructions.
struct VariableLocation {
int start_ir = 0;
int end_ir = 0;
enum class Kind : u8 { REGISTER, STACK } kind = Kind::REGISTER;
int reg = -1;
int stack_offset = 0;
};
struct LocalVariableDebugInfo {
std::string name;
TypeSpec type;
bool is_parameter = false;
std::vector<VariableLocation> locations;
const VariableLocation* location_at(int ir_idx) const {
for (const auto& loc : locations) {
if (ir_idx >= loc.start_ir && ir_idx <= loc.end_ir) {
return &loc;
}
}
return nullptr;
}
};
/*!
* FunctionDebugInfo stores per-function debugging information.
*/
struct FunctionDebugInfo {
u32 offset_in_seg; // not including type tag.
u32 length;
u8 seg;
std::string name;
std::string obj_name;
std::vector<InstructionInfo> instructions; // contains mapping to IRs
std::vector<std::shared_ptr<goos::HeapObject>> code_sources;
std::vector<std::string> ir_strings;
// the actual bytes in the object file.
std::vector<u8> generated_code;
std::optional<int> stack_usage;
// named locals and parameters, for showing variable values at a breakpoint
std::vector<LocalVariableDebugInfo> locals;
std::string disassemble_debug_info(bool* had_failure, const goos::Reader* reader, bool omit_ir);
};
class DebugInfo {
public:
explicit DebugInfo(std::string obj_name);
FunctionDebugInfo& add_function(const std::string& name, const std::string& obj_name) {
if (m_functions.find(name) != m_functions.end()) {
ASSERT(false);
}
auto& result = m_functions[name];
result.name = name;
result.obj_name = obj_name;
return result;
}
bool lookup_function(FunctionDebugInfo** info, std::string* name, u32 offset, u8 seg) {
for (auto& kv : m_functions) {
auto start = kv.second.offset_in_seg;
auto end = start + kv.second.length;
if (offset >= start && offset < end && seg == kv.second.seg) {
*info = &kv.second;
*name = kv.first;
return true;
}
}
return false;
}
FunctionDebugInfo& function_by_name(const std::string& name) { return m_functions.at(name); }
const std::unordered_map<std::string, FunctionDebugInfo>& functions() const {
return m_functions;
}
void clear() { m_functions.clear(); }
std::string disassemble_all_functions(bool* had_failure,
const goos::Reader* reader,
bool omit_ir);
std::string disassemble_function_by_name(const std::string& name,
bool* had_failure,
const goos::Reader* reader);
private:
std::string m_obj_name;
std::unordered_map<std::string, FunctionDebugInfo> m_functions;
};