mirror of
https://github.com/open-goal/jak-project
synced 2026-08-21 23:00:45 -04:00
dbgr: allow dumping the backtrace to a file (#2284)
Some backtraces are quite large, an option is to increase your terminal buffer -- but dumping to a file is also useful if you want to share the crash. I'm not crazy about the way I hacked this in, but it felt like the least invasive way for now and I don't want to cause a regression with the debugger. It's also nice that it dumps with ansi colors as then you can view the backtrace with the original coloring:  Usage: ```clj (:di "./stacktrace.log") ```
This commit is contained in:
@@ -340,8 +340,14 @@ Val* Compiler::compile_di(const goos::Object& form, const goos::Object& rest, En
|
||||
form,
|
||||
"Cannot get debug info, the debugger must be connected and the target must be halted.");
|
||||
}
|
||||
auto args = get_va(form, rest);
|
||||
|
||||
m_debugger.update_break_info();
|
||||
std::optional<std::string> dump_path;
|
||||
if (args.unnamed.size() > 0 && args.unnamed.at(0).is_string()) {
|
||||
dump_path = args.unnamed.at(0).as_string()->data;
|
||||
}
|
||||
|
||||
m_debugger.update_break_info(dump_path);
|
||||
return get_none();
|
||||
}
|
||||
|
||||
|
||||
+47
-20
@@ -10,6 +10,7 @@
|
||||
#include "common/log/log.h"
|
||||
#include "common/symbols.h"
|
||||
#include "common/util/Assert.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/Timer.h"
|
||||
|
||||
#include "goalc/debugger/disassemble.h"
|
||||
@@ -143,7 +144,7 @@ bool Debugger::attach_and_break() {
|
||||
m_running = false;
|
||||
|
||||
// get info from target
|
||||
update_break_info();
|
||||
update_break_info({});
|
||||
|
||||
auto signal_count = get_signal_count();
|
||||
if (signal_count != 0) {
|
||||
@@ -221,16 +222,28 @@ InstructionPointerInfo Debugger::get_rip_info(u64 rip) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
void print_and_append_to_string(std::string& str, const std::string& log) {
|
||||
str += log;
|
||||
lg::print(log);
|
||||
}
|
||||
|
||||
std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip,
|
||||
u64 rsp,
|
||||
std::optional<std::string> dump_path) {
|
||||
// TODO - it would probably be nice to decouple printing the backtrace from getting the backtrace
|
||||
// for now, build up a string and dump it at the end (if a path is provided)
|
||||
std::string backtrace_contents = "";
|
||||
lg::print("Backtrace:\n");
|
||||
std::vector<BacktraceFrame> bt;
|
||||
|
||||
if (rip == m_debug_context.base) {
|
||||
// we jumped to NULL.
|
||||
lg::print("Jumped to GOAL 0x0. Attempting to find previous function.\n");
|
||||
print_and_append_to_string(backtrace_contents,
|
||||
"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)) {
|
||||
lg::print(" failed to read return address off of the stack\n");
|
||||
print_and_append_to_string(backtrace_contents,
|
||||
" failed to read return address off of the stack\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -240,8 +253,10 @@ std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
|
||||
int fails = 0;
|
||||
while (true) {
|
||||
lg::print(" rsp: 0x{:x} (#x{:x}) rip: 0x{:x} (#x{:x})\n", rsp, rsp - m_debug_context.base,
|
||||
rip, rip - m_debug_context.base);
|
||||
print_and_append_to_string(
|
||||
backtrace_contents,
|
||||
fmt::format(" rsp: 0x{:x} (#x{:x}) rip: 0x{:x} (#x{:x})\n", rsp,
|
||||
rsp - m_debug_context.base, rip, rip - m_debug_context.base));
|
||||
BacktraceFrame frame;
|
||||
frame.rip_info = get_rip_info(rip);
|
||||
frame.rsp_at_rip = rsp;
|
||||
@@ -249,16 +264,19 @@ std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
if (frame.rip_info.knows_function && frame.rip_info.func_debug &&
|
||||
frame.rip_info.func_debug->stack_usage) {
|
||||
fails = 0;
|
||||
lg::print("<====================== CALL STACK ======================>\n");
|
||||
lg::print("{} from {}\n", frame.rip_info.function_name, frame.rip_info.func_debug->obj_name);
|
||||
print_and_append_to_string(backtrace_contents,
|
||||
"<====================== CALL STACK ======================>\n");
|
||||
print_and_append_to_string(backtrace_contents,
|
||||
fmt::format("{} from {}\n", frame.rip_info.function_name,
|
||||
frame.rip_info.func_debug->obj_name));
|
||||
// we're good!
|
||||
auto disasm = disassemble_at_rip(frame.rip_info);
|
||||
lg::print("{}\n", disasm.text);
|
||||
print_and_append_to_string(backtrace_contents, fmt::format("{}\n", disasm.text));
|
||||
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)) {
|
||||
lg::print("Invalid return address encountered!\n");
|
||||
print_and_append_to_string(backtrace_contents, "Invalid return address encountered!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -268,7 +286,7 @@ std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
} else {
|
||||
if (!frame.rip_info.knows_function) {
|
||||
if (fails == 0) {
|
||||
lg::print("Unknown Function at rip\n");
|
||||
print_and_append_to_string(backtrace_contents, "Unknown Function at rip\n");
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -305,7 +323,8 @@ std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
}
|
||||
} else*/
|
||||
if (fails > 70) {
|
||||
lg::print(
|
||||
print_and_append_to_string(
|
||||
backtrace_contents,
|
||||
"Backtrace was too long. Exception might have happened outside GOAL code, or the "
|
||||
"stack frame is too long.\n");
|
||||
break;
|
||||
@@ -313,7 +332,7 @@ std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
// attempt to backtrace anyway! if this fails then rip
|
||||
u64 next_rip = 0;
|
||||
if (!read_memory_if_safe<u64>(&next_rip, rsp - m_debug_context.base - 8)) {
|
||||
lg::print("Invalid return address encountered!\n");
|
||||
print_and_append_to_string(backtrace_contents, "Invalid return address encountered!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -322,10 +341,14 @@ std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
++fails;
|
||||
// break;
|
||||
} else if (!frame.rip_info.func_debug) {
|
||||
lg::print("Function {} has no debug info.\n", frame.rip_info.function_name);
|
||||
print_and_append_to_string(
|
||||
backtrace_contents,
|
||||
fmt::format("Function {} has no debug info.\n", frame.rip_info.function_name));
|
||||
break;
|
||||
} else {
|
||||
lg::print("Function {} with no stack frame data.\n", frame.rip_info.function_name);
|
||||
print_and_append_to_string(
|
||||
backtrace_contents,
|
||||
fmt::format("Function {} with no stack frame data.\n", frame.rip_info.function_name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -333,6 +356,10 @@ std::vector<BacktraceFrame> Debugger::get_backtrace(u64 rip, u64 rsp) {
|
||||
bt.push_back(frame);
|
||||
}
|
||||
|
||||
if (dump_path) {
|
||||
file_util::write_text_file(dump_path.value(), backtrace_contents);
|
||||
}
|
||||
|
||||
return bt;
|
||||
}
|
||||
|
||||
@@ -414,7 +441,7 @@ Disassembly Debugger::disassemble_at_rip(const InstructionPointerInfo& info) {
|
||||
* Read the registers, symbol table, and instructions near rip.
|
||||
* Print out some info about where we are.
|
||||
*/
|
||||
void Debugger::update_break_info() {
|
||||
void Debugger::update_break_info(std::optional<std::string> dump_path) {
|
||||
// todo adjust rip if break instruction????
|
||||
|
||||
m_memory_map = m_listener->build_memory_map();
|
||||
@@ -434,7 +461,7 @@ void Debugger::update_break_info() {
|
||||
auto dis = disassemble_at_rip(m_break_info);
|
||||
lg::print("{}\n", dis.text);
|
||||
|
||||
get_backtrace(m_regs_at_break.rip, m_regs_at_break.gprs[emitter::RSP]);
|
||||
get_backtrace(m_regs_at_break.rip, m_regs_at_break.gprs[emitter::RSP], dump_path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,7 +479,7 @@ bool Debugger::do_break() {
|
||||
} else {
|
||||
auto info = pop_signal();
|
||||
ASSERT(info.kind == xdbg::SignalInfo::BREAK);
|
||||
update_break_info();
|
||||
update_break_info({});
|
||||
m_running = false;
|
||||
return true;
|
||||
}
|
||||
@@ -464,7 +491,7 @@ bool Debugger::do_break() {
|
||||
bool Debugger::do_continue() {
|
||||
ASSERT(is_valid() && is_attached() && is_halted());
|
||||
if (!m_regs_valid) {
|
||||
update_break_info();
|
||||
update_break_info({});
|
||||
}
|
||||
ASSERT(regs_valid());
|
||||
|
||||
@@ -992,7 +1019,7 @@ void Debugger::update_continue_info() {
|
||||
}
|
||||
|
||||
if (!m_regs_valid) {
|
||||
update_break_info();
|
||||
update_break_info({});
|
||||
}
|
||||
|
||||
auto kv = m_addr_breakpoints.find(get_regs().rip - m_debug_context.base - 1);
|
||||
|
||||
@@ -96,7 +96,7 @@ class Debugger {
|
||||
const char* get_symbol_name_from_offset(s32 ofs) const;
|
||||
void add_addr_breakpoint(u32 addr);
|
||||
void remove_addr_breakpoint(u32 addr);
|
||||
void update_break_info();
|
||||
void update_break_info(std::optional<std::string> dump_path);
|
||||
|
||||
InstructionPointerInfo get_rip_info(u64 x86_rip);
|
||||
DebugInfo& get_debug_info_for_object(const std::string& object_name);
|
||||
@@ -105,7 +105,7 @@ class Debugger {
|
||||
std::string get_info_about_addr(u32 addr);
|
||||
Disassembly disassemble_at_rip(const InstructionPointerInfo& info);
|
||||
|
||||
std::vector<BacktraceFrame> get_backtrace(u64 rip, u64 rsp);
|
||||
std::vector<BacktraceFrame> get_backtrace(u64 rip, u64 rsp, std::optional<std::string> dump_path);
|
||||
|
||||
std::string disassemble_x86_with_symbols(int len, u64 base_addr) const;
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ TEST(Jak1Debugger, SimpleBreakpoint) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
compiler.get_debugger().update_break_info();
|
||||
compiler.get_debugger().update_break_info({});
|
||||
auto expected_instr_before_rip = compiler.get_debugger().get_x86_base_addr() + func_addr;
|
||||
auto rip = compiler.get_debugger().get_regs().rip;
|
||||
// instructions can be at most 15 bytes long.
|
||||
|
||||
Reference in New Issue
Block a user