mirror of
https://github.com/open-goal/jak-project
synced 2026-08-04 01:04:11 -04:00
[compiler] print a backtrace as part of di (#658)
* print a backtrace as part of di * missing include
This commit is contained in:
@@ -63,6 +63,7 @@ void CodeGenerator::do_function(FunctionEnv* env, int f_idx) {
|
||||
*/
|
||||
void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
|
||||
bool use_new_xmms = true;
|
||||
auto* debug = &m_debug_info->function_by_name(env->name());
|
||||
|
||||
auto f_rec = m_gen.get_existing_function_record(f_idx);
|
||||
// todo, extra alignment settings
|
||||
@@ -154,6 +155,7 @@ void CodeGenerator::do_goal_function(FunctionEnv* env, int f_idx) {
|
||||
InstructionInfo::Kind::PROLOGUE);
|
||||
}
|
||||
}
|
||||
debug->stack_usage = stack_offset;
|
||||
|
||||
// emit each IR into x86 instructions.
|
||||
for (int ir_idx = 0; ir_idx < int(env->code().size()); ir_idx++) {
|
||||
|
||||
@@ -170,7 +170,7 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
|
||||
// send to target
|
||||
if (load) {
|
||||
if (m_listener.is_connected()) {
|
||||
m_listener.send_code(data);
|
||||
m_listener.send_code(data, obj_file_name);
|
||||
} else {
|
||||
printf("WARNING - couldn't load because listener isn't connected\n"); // todo log warn
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include "common/util/assert.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -24,6 +25,7 @@ struct FunctionDebugInfo {
|
||||
|
||||
// the actual bytes in the object file.
|
||||
std::vector<u8> generated_code;
|
||||
std::optional<int> stack_usage;
|
||||
|
||||
std::string disassemble_debug_info(bool* had_failure);
|
||||
};
|
||||
@@ -54,6 +56,8 @@ class DebugInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
FunctionDebugInfo& function_by_name(const std::string& name) { return m_functions.at(name); }
|
||||
|
||||
void clear() { m_functions.clear(); }
|
||||
|
||||
std::string disassemble_all_functions(bool* had_failure);
|
||||
|
||||
+205
-79
@@ -4,6 +4,7 @@
|
||||
* Uses xdbg functions to debug an OpenGOAL target.
|
||||
*/
|
||||
|
||||
#include "goalc/emitter/Register.h"
|
||||
#include "common/util/assert.h"
|
||||
#include "Debugger.h"
|
||||
#include "common/util/Timer.h"
|
||||
@@ -147,6 +148,180 @@ std::string Debugger::get_info_about_addr(u32 addr) {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* This assumes we have an up-to-date memory map and symbol info.
|
||||
*/
|
||||
InstructionPointerInfo Debugger::get_rip_info(u64 rip) {
|
||||
InstructionPointerInfo result;
|
||||
result.real_rip = rip;
|
||||
|
||||
if (m_context_valid) {
|
||||
result.goal_rip = rip - m_debug_context.base;
|
||||
if (rip >= m_debug_context.base + EE_MAIN_MEM_LOW_PROTECT &&
|
||||
rip < m_debug_context.base + EE_MAIN_MEM_SIZE) {
|
||||
result.in_goal_mem = true;
|
||||
auto map_loc = m_memory_map.lookup(rip - m_debug_context.base);
|
||||
if (map_loc.empty) {
|
||||
result.knows_object = false;
|
||||
result.knows_function = false;
|
||||
} else {
|
||||
u64 obj_offset = rip - m_debug_context.base - map_loc.start_addr;
|
||||
result.map_entry = map_loc;
|
||||
result.knows_object = true;
|
||||
result.object_name = map_loc.obj_name;
|
||||
result.object_seg = map_loc.seg_id;
|
||||
result.object_offset = obj_offset;
|
||||
|
||||
FunctionDebugInfo* info = nullptr;
|
||||
std::string name;
|
||||
|
||||
if (get_debug_info_for_object(map_loc.obj_name)
|
||||
.lookup_function(&info, &name, obj_offset, map_loc.seg_id)) {
|
||||
result.knows_function = true;
|
||||
result.function_name = name;
|
||||
result.function_offset = obj_offset - info->offset_in_seg;
|
||||
result.func_debug = info;
|
||||
|
||||
assert(!info->instructions.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
fmt::print("Backtrace:\n");
|
||||
std::vector<BacktraceFrame> bt;
|
||||
|
||||
if (rip == m_debug_context.base) {
|
||||
// we jumped to NULL.
|
||||
fmt::print("Jumped to GOAL 0x0. Attempting to find previous function.\n");
|
||||
u64 next_rip = 0;
|
||||
if (!read_memory_if_safe<u64>(&next_rip, rsp - m_debug_context.base)) {
|
||||
fmt::print(" failed to read return address off of the stack\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
rip = next_rip;
|
||||
rsp += 8;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
fmt::print(" rsp: 0x{:x} rip: 0x{:x}\n", rsp, rip);
|
||||
BacktraceFrame frame;
|
||||
frame.rip_info = get_rip_info(rip);
|
||||
frame.rsp_at_rip = rsp;
|
||||
|
||||
if (frame.rip_info.knows_function && frame.rip_info.func_debug &&
|
||||
frame.rip_info.func_debug->stack_usage) {
|
||||
fmt::print("{}\n", frame.rip_info.function_name);
|
||||
// we're good!
|
||||
u64 rsp_at_call = rsp + *frame.rip_info.func_debug->stack_usage;
|
||||
|
||||
u64 next_rip = 0;
|
||||
if (!read_memory_if_safe<u64>(&next_rip, rsp_at_call - m_debug_context.base)) {
|
||||
fmt::print("Invalid return address encountered!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
rip = next_rip;
|
||||
rsp = rsp_at_call + 8; // 8 for the call itself.
|
||||
|
||||
} else {
|
||||
if (!frame.rip_info.knows_function) {
|
||||
fmt::print("Unknown Function at 0x{:x}\n", rip);
|
||||
break;
|
||||
}
|
||||
if (!frame.rip_info.func_debug) {
|
||||
fmt::print("Function {} has no debug info.\n", frame.rip_info.function_name);
|
||||
break;
|
||||
} else {
|
||||
fmt::print("Function {} with no stack frame data.\n", frame.rip_info.function_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bt.push_back(frame);
|
||||
}
|
||||
|
||||
return bt;
|
||||
}
|
||||
|
||||
/*!
|
||||
* This assumes we have an up-to-date memory map and symbol info.
|
||||
*/
|
||||
Disassembly Debugger::disassemble_at_rip(const InstructionPointerInfo& info) {
|
||||
// todo adjust rip if break instruction????
|
||||
Disassembly result;
|
||||
|
||||
result.failed = false;
|
||||
u64 rip = info.real_rip;
|
||||
|
||||
if (info.in_goal_mem) {
|
||||
// we only want to disassemble GOAL code.
|
||||
// if the crash happens outside of GOAL code, use a normal debugger.
|
||||
|
||||
if (!info.knows_function || !info.knows_object || !info.map_entry) {
|
||||
// something went wrong and we can't find this code.
|
||||
// however, we can still do better than nothing by dumping the memory and disassembling.
|
||||
std::vector<u8> mem;
|
||||
mem.resize(INSTR_DUMP_SIZE_REV + INSTR_DUMP_SIZE_FWD);
|
||||
read_memory(mem.data(), INSTR_DUMP_SIZE_REV + INSTR_DUMP_SIZE_FWD,
|
||||
info.real_rip - m_debug_context.base - INSTR_DUMP_SIZE_REV);
|
||||
result.failed = true;
|
||||
if (info.knows_object) {
|
||||
result.text += fmt::format("In segment {} of obj {}, offset 0x{:x}\n", info.object_seg,
|
||||
info.object_name, info.object_offset);
|
||||
result.text += disassemble_x86(mem.data(), mem.size(), rip - INSTR_DUMP_SIZE_REV, rip);
|
||||
} else {
|
||||
result.text += "In unknown code\n";
|
||||
result.text += disassemble_x86(mem.data(), mem.size(), rip - INSTR_DUMP_SIZE_REV, rip);
|
||||
}
|
||||
} else {
|
||||
// we have enough info to do a fancy disassembly!
|
||||
u64 obj_offset = rip - m_debug_context.base - info.map_entry->start_addr;
|
||||
|
||||
FunctionDebugInfo* func_info = info.func_debug;
|
||||
std::string name = func_info->name;
|
||||
auto continue_info = get_continue_info(rip);
|
||||
assert(!func_info->instructions.empty());
|
||||
|
||||
std::vector<u8> function_mem;
|
||||
function_mem.resize(func_info->instructions.back().offset +
|
||||
func_info->instructions.back().instruction.length());
|
||||
read_memory(function_mem.data(), function_mem.size(),
|
||||
info.map_entry->start_addr + func_info->offset_in_seg);
|
||||
|
||||
int rip_offset = 0;
|
||||
if (continue_info.valid && continue_info.is_addr_breakpiont) {
|
||||
int offset_in_fmem = uint64_t(continue_info.addr_breakpoint.goal_addr) -
|
||||
uint64_t(info.map_entry->start_addr + func_info->offset_in_seg);
|
||||
if (offset_in_fmem < 0 || offset_in_fmem >= int(function_mem.size())) {
|
||||
result.failed = true;
|
||||
} else {
|
||||
function_mem.at(offset_in_fmem) = continue_info.addr_breakpoint.old_data;
|
||||
rip_offset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
result.text += fmt::format(
|
||||
"In function {} in segment {} of obj {}, offset_obj 0x{:x}, offset_func 0x{:x}\n", name,
|
||||
info.map_entry->seg_id, info.map_entry->obj_name, obj_offset, info.function_offset);
|
||||
|
||||
result.text += disassemble_x86_function(
|
||||
function_mem.data(), function_mem.size(),
|
||||
m_debug_context.base + info.map_entry->start_addr + func_info->offset_in_seg,
|
||||
rip + rip_offset, func_info->instructions, func_info->irs, &result.failed);
|
||||
}
|
||||
} else {
|
||||
result.failed = true;
|
||||
result.text = "Not in GOAL code!\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Read the registers, symbol table, and instructions near rip.
|
||||
* Print out some info about where we are.
|
||||
@@ -166,85 +341,12 @@ void Debugger::update_break_info() {
|
||||
}
|
||||
|
||||
if (regs_valid()) {
|
||||
std::vector<u8> mem;
|
||||
mem.resize(INSTR_DUMP_SIZE_REV + INSTR_DUMP_SIZE_FWD);
|
||||
// very basic asm dump.
|
||||
auto rip = m_regs_at_break.rip;
|
||||
m_break_info.real_rip = rip;
|
||||
m_break_info.goal_rip = rip - m_debug_context.base;
|
||||
m_break_info = get_rip_info(m_regs_at_break.rip);
|
||||
update_continue_info();
|
||||
auto dis = disassemble_at_rip(m_break_info);
|
||||
fmt::print("{}\n", dis.text);
|
||||
|
||||
m_break_info.disassembly_failed = false;
|
||||
|
||||
if (rip >= m_debug_context.base + EE_MAIN_MEM_LOW_PROTECT &&
|
||||
rip < m_debug_context.base + EE_MAIN_MEM_SIZE) {
|
||||
read_memory(mem.data(), INSTR_DUMP_SIZE_REV + INSTR_DUMP_SIZE_FWD,
|
||||
rip - m_debug_context.base - INSTR_DUMP_SIZE_REV);
|
||||
auto map_loc = m_memory_map.lookup(rip - m_debug_context.base);
|
||||
if (map_loc.empty) {
|
||||
fmt::print("In unknown code\n");
|
||||
fmt::print("{}", disassemble_x86(mem.data(), mem.size(), rip - INSTR_DUMP_SIZE_REV, rip));
|
||||
m_break_info.disassembly_failed = true;
|
||||
m_break_info.knows_object = false;
|
||||
m_break_info.knows_function = false;
|
||||
} else {
|
||||
u64 obj_offset = rip - m_debug_context.base - map_loc.start_addr;
|
||||
m_break_info.knows_object = true;
|
||||
m_break_info.object_name = map_loc.obj_name;
|
||||
m_break_info.object_seg = map_loc.seg_id;
|
||||
m_break_info.object_offset = obj_offset;
|
||||
|
||||
FunctionDebugInfo* info = nullptr;
|
||||
std::string name;
|
||||
|
||||
if (get_debug_info_for_object(map_loc.obj_name)
|
||||
.lookup_function(&info, &name, obj_offset, map_loc.seg_id)) {
|
||||
update_continue_info();
|
||||
m_break_info.knows_function = true;
|
||||
m_break_info.function_name = name;
|
||||
m_break_info.function_offset = obj_offset - info->offset_in_seg;
|
||||
|
||||
assert(!info->instructions.empty());
|
||||
|
||||
std::vector<u8> function_mem;
|
||||
function_mem.resize(info->instructions.back().offset +
|
||||
info->instructions.back().instruction.length());
|
||||
read_memory(function_mem.data(), function_mem.size(),
|
||||
map_loc.start_addr + info->offset_in_seg);
|
||||
|
||||
int rip_offset = 0;
|
||||
if (m_continue_info.valid && m_continue_info.is_addr_breakpiont) {
|
||||
int offset_in_fmem = uint64_t(m_continue_info.addr_breakpoint.goal_addr) -
|
||||
uint64_t(map_loc.start_addr + info->offset_in_seg);
|
||||
if (offset_in_fmem < 0 || offset_in_fmem >= int(function_mem.size())) {
|
||||
m_break_info.disassembly_failed = true;
|
||||
} else {
|
||||
function_mem.at(offset_in_fmem) = m_continue_info.addr_breakpoint.old_data;
|
||||
rip_offset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
fmt::print(
|
||||
"In function {} in segment {} of obj {}, offset_obj 0x{:x}, offset_func 0x{:x}\n",
|
||||
name, map_loc.seg_id, map_loc.obj_name, obj_offset, m_break_info.function_offset);
|
||||
|
||||
fmt::print("{}", disassemble_x86_function(
|
||||
function_mem.data(), function_mem.size(),
|
||||
m_debug_context.base + map_loc.start_addr + info->offset_in_seg,
|
||||
rip + rip_offset, info->instructions, info->irs,
|
||||
&m_break_info.disassembly_failed));
|
||||
|
||||
} else {
|
||||
m_break_info.disassembly_failed = true;
|
||||
m_break_info.knows_function = false;
|
||||
fmt::print("In segment {} of obj {}, offset 0x{:x}\n", map_loc.seg_id, map_loc.obj_name,
|
||||
obj_offset);
|
||||
fmt::print("{}", disassemble_x86(mem.data(), mem.size(), rip - INSTR_DUMP_SIZE_REV, rip));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_break_info.disassembly_failed = true;
|
||||
fmt::print("Not in GOAL code!\n");
|
||||
}
|
||||
get_backtrace(m_regs_at_break.rip, m_regs_at_break.gprs[emitter::RSP]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,11 +404,19 @@ bool Debugger::do_continue() {
|
||||
/*!
|
||||
* Read memory from an attached and halted target.
|
||||
*/
|
||||
bool Debugger::read_memory(u8* dest_buffer, int size, u32 goal_addr) {
|
||||
bool Debugger::read_memory(u8* dest_buffer, int size, u32 goal_addr) const {
|
||||
assert(is_valid() && is_attached() && is_halted());
|
||||
return xdbg::read_goal_memory(dest_buffer, size, goal_addr, m_debug_context, m_memory_handle);
|
||||
}
|
||||
|
||||
bool Debugger::read_memory_if_safe(u8* dest_buffer, int size, u32 goal_addr) const {
|
||||
assert(is_valid() && is_attached() && is_halted());
|
||||
if (goal_addr >= EE_MAIN_MEM_LOW_PROTECT && goal_addr + size < EE_MAIN_MEM_SIZE) {
|
||||
return read_memory(dest_buffer, size, goal_addr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Write the memory of an attached and halted target.
|
||||
*/
|
||||
@@ -618,6 +728,22 @@ void Debugger::update_continue_info() {
|
||||
m_continue_info.valid = true;
|
||||
}
|
||||
|
||||
Debugger::ContinueInfo Debugger::get_continue_info(u64 rip) const {
|
||||
ContinueInfo result;
|
||||
auto kv = m_addr_breakpoints.find(rip - m_debug_context.base - 1);
|
||||
if (kv == m_addr_breakpoints.end()) {
|
||||
result.subtract_1 = false;
|
||||
result.is_addr_breakpiont = false;
|
||||
} else {
|
||||
result.subtract_1 = true;
|
||||
result.is_addr_breakpiont = true;
|
||||
result.addr_breakpoint = kv->second;
|
||||
}
|
||||
|
||||
result.valid = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
DebugInfo& Debugger::get_debug_info_for_object(const std::string& object_name) {
|
||||
auto kv = m_debug_info.find(object_name);
|
||||
if (kv != m_debug_info.end()) {
|
||||
|
||||
@@ -15,14 +15,22 @@
|
||||
#include "common/cross_os_debug/xdbg.h"
|
||||
#include "goalc/listener/MemoryMap.h"
|
||||
#include "DebugInfo.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace listener {
|
||||
class Listener;
|
||||
}
|
||||
|
||||
struct BreakInfo {
|
||||
u64 real_rip = 0;
|
||||
u32 goal_rip = 0;
|
||||
/*!
|
||||
* Information about an instruction pointer, used for constructing a useful disassembly around it.
|
||||
*/
|
||||
struct InstructionPointerInfo {
|
||||
u64 real_rip = 0; // x86-64 rip register value (64-bits)
|
||||
u32 goal_rip = 0; // GOAL pointer of rip.
|
||||
|
||||
u64 real_rsp = 0;
|
||||
|
||||
bool in_goal_mem = false;
|
||||
|
||||
bool knows_object = false;
|
||||
std::string object_name;
|
||||
@@ -33,7 +41,19 @@ struct BreakInfo {
|
||||
std::string function_name;
|
||||
u32 function_offset = -1;
|
||||
|
||||
bool disassembly_failed = false;
|
||||
std::optional<listener::MemoryMapEntry> map_entry;
|
||||
|
||||
FunctionDebugInfo* func_debug = nullptr;
|
||||
};
|
||||
|
||||
struct Disassembly {
|
||||
std::string text;
|
||||
bool failed = false;
|
||||
};
|
||||
|
||||
struct BacktraceFrame {
|
||||
InstructionPointerInfo rip_info;
|
||||
u64 rsp_at_rip = 0;
|
||||
};
|
||||
|
||||
class Debugger {
|
||||
@@ -51,7 +71,17 @@ class Debugger {
|
||||
bool attach_and_break();
|
||||
bool do_break();
|
||||
bool do_continue();
|
||||
bool read_memory(u8* dest_buffer, int size, u32 goal_addr);
|
||||
bool read_memory(u8* dest_buffer, int size, u32 goal_addr) const;
|
||||
bool read_memory_if_safe(u8* dest_buffer, int size, u32 goal_addr) const;
|
||||
template <typename T>
|
||||
bool read_memory_if_safe(T* dst, u32 goal_addr) const {
|
||||
u8 temp[sizeof(T)];
|
||||
if (read_memory_if_safe(temp, sizeof(T), goal_addr)) {
|
||||
memcpy(dst, temp, sizeof(T));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool write_memory(const u8* src_buffer, int size, u32 goal_addr);
|
||||
void read_symbol_table();
|
||||
u32 get_symbol_address(const std::string& sym_name);
|
||||
@@ -59,9 +89,14 @@ class Debugger {
|
||||
void add_addr_breakpoint(u32 addr);
|
||||
void remove_addr_breakpoint(u32 addr);
|
||||
void update_break_info();
|
||||
|
||||
InstructionPointerInfo get_rip_info(u64 x86_rip);
|
||||
DebugInfo& get_debug_info_for_object(const std::string& object_name);
|
||||
const BreakInfo& get_cached_break_info() { return m_break_info; }
|
||||
const InstructionPointerInfo& get_cached_break_info() { return m_break_info; }
|
||||
std::string get_info_about_addr(u32 addr);
|
||||
Disassembly disassemble_at_rip(const InstructionPointerInfo& info);
|
||||
|
||||
std::vector<BacktraceFrame> get_backtrace(u64 rip, u64 rsp);
|
||||
|
||||
/*!
|
||||
* Get the x86 address of GOAL memory
|
||||
@@ -150,6 +185,8 @@ class Debugger {
|
||||
Breakpoint addr_breakpoint;
|
||||
} m_continue_info;
|
||||
|
||||
ContinueInfo get_continue_info(u64 rip) const;
|
||||
|
||||
// for more complicated breakpoint stuff, we have a queue of stops.
|
||||
// right now it's barely used for anything other than waiting for a "break" to be acknowledged.
|
||||
struct SignalInfo {
|
||||
@@ -165,7 +202,7 @@ class Debugger {
|
||||
bool m_running = true;
|
||||
bool m_attached = false;
|
||||
|
||||
BreakInfo m_break_info;
|
||||
InstructionPointerInfo m_break_info;
|
||||
|
||||
listener::Listener* m_listener = nullptr;
|
||||
listener::MemoryMap m_memory_map;
|
||||
|
||||
@@ -337,8 +337,11 @@ int Listener::get_received_message_count() {
|
||||
/*!
|
||||
* Send a "CODE" message for the target to execute as the Listener Function.
|
||||
* Returns once the target acks the code.
|
||||
*
|
||||
* The load name is not actually sent to the target. Instead, if the target loads successfully
|
||||
* and outputs a *listener* load message, this will be remapped to a load of the given name.
|
||||
*/
|
||||
void Listener::send_code(std::vector<uint8_t>& code) {
|
||||
void Listener::send_code(std::vector<uint8_t>& code, const std::optional<std::string>& load_name) {
|
||||
got_ack = false;
|
||||
int total_size = code.size() + sizeof(ListenerMessageHeader);
|
||||
if (total_size > BUFFER_SIZE) {
|
||||
@@ -346,6 +349,10 @@ void Listener::send_code(std::vector<uint8_t>& code) {
|
||||
return;
|
||||
}
|
||||
|
||||
rcv_mtx.lock();
|
||||
m_pending_listener_load_object_name = load_name;
|
||||
rcv_mtx.unlock();
|
||||
|
||||
auto* header = (ListenerMessageHeader*)m_buffer;
|
||||
auto* buffer_data = (char*)(header + 1);
|
||||
header->deci2_header.rsvd = 0;
|
||||
@@ -551,7 +558,14 @@ void Listener::handle_output_message(const char* msg) {
|
||||
* Add a load to the load listing.
|
||||
*/
|
||||
void Listener::add_load(const std::string& name, const LoadEntry& le) {
|
||||
m_load_entries[name] = le;
|
||||
// if we load a file through the listener, the compiler will set the pending load name,
|
||||
// and the runtime will send a load message with *listener*.
|
||||
if (name == "*listener*" && m_pending_listener_load_object_name) {
|
||||
m_load_entries[*m_pending_listener_load_object_name] = le;
|
||||
m_pending_listener_load_object_name = {};
|
||||
} else {
|
||||
m_load_entries[name] = le;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -36,7 +36,7 @@ class Listener {
|
||||
void send_reset(bool shutdown);
|
||||
void send_poke();
|
||||
void disconnect();
|
||||
void send_code(std::vector<uint8_t>& code);
|
||||
void send_code(std::vector<uint8_t>& code, const std::optional<std::string>& load_name = {});
|
||||
void add_debugger(Debugger* debugger);
|
||||
bool most_recent_send_was_acked() const { return got_ack; }
|
||||
MemoryMap build_memory_map();
|
||||
@@ -64,6 +64,8 @@ class Listener {
|
||||
ListenerMessageKind filter = ListenerMessageKind::MSG_INVALID;
|
||||
std::vector<std::string> message_record;
|
||||
std::unordered_map<std::string, LoadEntry> m_load_entries;
|
||||
|
||||
std::optional<std::string> m_pending_listener_load_object_name;
|
||||
char ack_recv_buff[512];
|
||||
uint64_t last_sent_id = 0;
|
||||
uint64_t last_recvd_id = 0;
|
||||
|
||||
@@ -182,7 +182,8 @@ TEST(Debugger, SimpleBreakpoint) {
|
||||
EXPECT_TRUE(bi.knows_object);
|
||||
EXPECT_TRUE(bi.object_name == "*listener*");
|
||||
EXPECT_TRUE(bi.function_name == "test-function");
|
||||
EXPECT_FALSE(bi.disassembly_failed);
|
||||
auto disasm = compiler.get_debugger().disassemble_at_rip(bi);
|
||||
EXPECT_FALSE(disasm.failed);
|
||||
// if we change this to be before the break instruction this might need to be 0 in the future.
|
||||
EXPECT_EQ(bi.function_offset, 1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user