jak-project/goalc/compiler/compilation/Debug.cpp
water111 71dda76e2b
Add some compiler features and documentation (#147)
* update doc

* add disassemble and type checking

* improve compiler error messages
2020-12-01 21:39:46 -05:00

391 lines
12 KiB
C++

#include "goalc/compiler/Compiler.h"
#include "goalc/debugger/disassemble.h"
#include "common/util/FileUtil.h"
#include "third-party/fmt/core.h"
u32 Compiler::parse_address_spec(const goos::Object& form) {
if (form.is_int()) {
return form.as_int();
}
if (form.is_pair()) {
auto first = form.as_pair()->car;
auto rest = form.as_pair()->cdr;
if (first.is_symbol() && symbol_string(first) == "sym") {
if (rest.is_pair() && rest.as_pair()->car.is_symbol()) {
u32 addr = m_debugger.get_symbol_address(symbol_string(rest.as_pair()->car));
if (!addr) {
throw_compiler_error(form,
"Could not find symbol {} in the target to create an addr-spec",
symbol_string(rest.as_pair()->car));
}
return addr;
} else {
throw_compiler_error(form, "addr-spec sym form must receive exactly one symbol argument.");
return 0;
}
} else if (first.is_symbol() && symbol_string(first) == "sym-val") {
if (rest.is_pair() && rest.as_pair()->car.is_symbol()) {
u32 addr = 0;
if (!m_debugger.get_symbol_value(symbol_string(rest.as_pair()->car), &addr)) {
throw_compiler_error(form,
"Could not find symbol {} in the target to create an addr-spec",
symbol_string(rest.as_pair()->car));
}
return addr;
} else {
throw_compiler_error(form,
"addr-spec sym-val form must receive exactly one symbol argument.");
return 0;
}
} else {
throw_compiler_error(form, "Can't parse this address spec: option {} was not recognized.",
first.print());
return 0;
}
} else {
throw_compiler_error(form, "Invalid address spec.");
return 0;
}
}
Val* Compiler::compile_dbg(const goos::Object& form, const goos::Object& rest, Env* env) {
// todo - do something with args.
(void)form;
(void)rest;
(void)env;
if (!m_debugger.is_valid()) {
fmt::print("[Debugger] Could not start debugger because there is no valid debugging context\n");
return get_none();
}
if (m_debugger.is_attached()) {
fmt::print("[Debugger] Could not start debugger because the debugger is already attached.\n");
return get_none();
}
if (m_debugger.attach_and_break()) {
fmt::print("Debugger connected.\n");
} else {
fmt::print("ERROR\n");
}
return get_none();
}
Val* Compiler::compile_dbs(const goos::Object& form, const goos::Object& rest, Env* env) {
// todo - do something with args.
(void)form;
(void)rest;
(void)env;
fmt::print(" Listener connected? {}\n", m_listener.is_connected());
fmt::print(" Debugger context? {}\n", m_debugger.is_valid());
if (m_debugger.is_valid()) {
fmt::print(" Attached? {}\n", m_debugger.is_attached());
if (m_debugger.is_attached()) {
fmt::print(" Halted? {}\n", m_debugger.is_halted());
}
fmt::print(" Context: {}\n", m_debugger.get_context_string());
}
if (m_debugger.is_valid()) {
} else {
fmt::print("There is no valid debug context from the target.");
}
return get_none();
}
Val* Compiler::compile_cont(const goos::Object& form, const goos::Object& rest, Env* env) {
// todo - do something with args.
(void)form;
(void)rest;
(void)env;
if (m_debugger.is_valid() && m_debugger.is_attached() && m_debugger.is_halted()) {
m_debugger.do_continue();
} else {
fmt::print("Couldn't do :cont. Valid {}, attached {}, halted {}\n", m_debugger.is_valid(),
m_debugger.is_attached(), m_debugger.is_halted());
}
return get_none();
}
Val* Compiler::compile_break(const goos::Object& form, const goos::Object& rest, Env* env) {
// todo - do something with args.
(void)form;
(void)rest;
(void)env;
if (m_debugger.is_valid() && m_debugger.is_attached() && m_debugger.is_running()) {
m_debugger.do_break();
} else {
fmt::print("Couldn't do :break. Valid {}, attached {}, running {}\n", m_debugger.is_valid(),
m_debugger.is_attached(), m_debugger.is_running());
}
return get_none();
}
Val* Compiler::compile_dump_all(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
if (!m_debugger.is_halted()) {
fmt::print("Couldn't dump memory. Must be attached and halted.\n");
return get_none();
}
auto args = get_va(form, rest);
va_check(form, args, {{goos::ObjectType::STRING}}, {});
auto dest_file = args.unnamed.at(0).as_string()->data;
auto buffer = new u8[EE_MAIN_MEM_SIZE];
memset(buffer, 0, EE_MAIN_MEM_SIZE);
if (!m_debugger.read_memory(buffer + EE_MAIN_MEM_LOW_PROTECT,
EE_MAIN_MEM_SIZE - EE_MAIN_MEM_LOW_PROTECT,
EE_MAIN_MEM_LOW_PROTECT)) {
fmt::print("Reading memory failed, not dumping.\n");
} else {
file_util::write_binary_file(file_util::get_file_path({dest_file}), buffer, EE_MAIN_MEM_SIZE);
}
return get_none();
}
namespace {
enum PrintMode { HEX, UNSIGNED_DEC, SIGNED_DEC, FLOAT };
template <typename T>
void mem_print(T* data, int count, u32 start_addr, PrintMode mode) {
// always print 16 bytes / line.
int elt_per_line = 16 / sizeof(T);
// pad wit the correct number of zeros.
std::string format_string;
switch (mode) {
case HEX:
format_string = "0x{:0" + std::to_string(2 * sizeof(T)) + "x} ";
break;
case UNSIGNED_DEC:
case SIGNED_DEC:
format_string = "{:" + std::to_string(3 * sizeof(T)) + "d} ";
break;
case FLOAT:
format_string = "{:8.4f} "; // todo, is this what we want?
break;
default:
assert(false);
}
// loop over elts
for (int i = 0; i < count; i++) {
if ((i % elt_per_line) == 0) {
// first in line, so we should print the GOAL address
fmt::print(" 0x{:08x}: ", start_addr + (i * sizeof(T)));
}
// print the thing
fmt::print(format_string, data[i]);
if ((i % elt_per_line) == (elt_per_line - 1)) {
// last in line, newline!
fmt::print("\n");
}
}
fmt::print("\n");
}
} // namespace
Val* Compiler::compile_pm(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
va_check(form, args, {goos::ObjectType::INTEGER, {}, goos::ObjectType::INTEGER},
{{"print-mode", {false, goos::ObjectType::SYMBOL}}});
int elt_size = args.unnamed.at(0).as_int();
u32 addr = parse_address_spec(args.unnamed.at(1));
u32 elts = args.unnamed.at(2).as_int();
PrintMode mode = HEX;
if (args.has_named("print-mode")) {
auto mode_name = symbol_string(args.get_named("print-mode"));
if (mode_name == "hex") {
mode = HEX;
} else if (mode_name == "unsigned-dec") {
mode = UNSIGNED_DEC;
} else if (mode_name == "signed-dec") {
mode = SIGNED_DEC;
} else if (mode_name == "float") {
mode = FLOAT;
} else {
throw_compiler_error(form, "Unknown print-mode {} for :pm.", mode_name);
}
}
if (!m_debugger.is_halted()) {
throw_compiler_error(
form, "Cannot print memory, the debugger must be connected and the target must be halted.");
}
auto mem_size = elts * elt_size;
if (mem_size > 1024 * 1024) {
throw_compiler_error(form, fmt::format(":pm used on over 1 MB of memory. Not printing."));
}
std::vector<u8> mem;
mem.resize(mem_size);
if (addr < EE_MAIN_MEM_LOW_PROTECT || (addr + mem_size) > EE_MAIN_MEM_SIZE) {
throw_compiler_error(form,
":pm memory out of range. Wanted to print 0x{:x} to 0x{:x}, but valid "
"memory is 0x{:x} to 0x{:x}.",
addr, addr + mem_size, EE_MAIN_MEM_LOW_PROTECT, EE_MAIN_MEM_SIZE);
}
m_debugger.read_memory(mem.data(), mem_size, addr);
switch (mode) {
case HEX:
case UNSIGNED_DEC:
switch (elt_size) {
case 1:
mem_print((u8*)mem.data(), elts, addr, mode);
break;
case 2:
mem_print((u16*)mem.data(), elts, addr, mode);
break;
case 4:
mem_print((u32*)mem.data(), elts, addr, mode);
break;
case 8:
mem_print((u64*)mem.data(), elts, addr, mode);
break;
default:
throw_compiler_error(form, ":pm {} is an invalid element size for unsigned", elt_size);
}
break;
case SIGNED_DEC:
switch (elt_size) {
case 1:
mem_print((s8*)mem.data(), elts, addr, mode);
break;
case 2:
mem_print((s16*)mem.data(), elts, addr, mode);
break;
case 4:
mem_print((s32*)mem.data(), elts, addr, mode);
break;
case 8:
mem_print((s64*)mem.data(), elts, addr, mode);
break;
default:
throw_compiler_error(form, ":pm {} is a bad element size for signed", elt_size);
}
break;
case FLOAT:
switch (elt_size) {
case 4:
mem_print((float*)mem.data(), elts, addr, mode);
break;
default:
throw_compiler_error(form, ":pm float can only be printed with size 4, but got size {}",
elt_size);
}
break;
default:
assert(false);
}
return get_none();
}
Val* Compiler::compile_di(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)form;
(void)rest;
(void)env;
if (!m_debugger.is_halted()) {
throw_compiler_error(
form,
"Cannot get debug info, the debugger must be connected and the target must be halted.");
}
m_debugger.update_break_info();
return get_none();
}
Val* Compiler::compile_disasm(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
va_check(form, args, {{}, goos::ObjectType::INTEGER}, {});
u32 addr = parse_address_spec(args.unnamed.at(0));
u32 size = args.unnamed.at(1).as_int();
if (!m_debugger.is_halted()) {
throw_compiler_error(
form,
"Cannot disassemble memory, the debugger must be connected and the target must be halted.");
}
if (size > 1024 * 1024) {
throw_compiler_error(
form,
fmt::format(
":disasm used on over 1 MB of memory, this probably isn't what you meant to do."));
}
std::vector<u8> mem;
mem.resize(size);
if (addr < EE_MAIN_MEM_LOW_PROTECT || (addr + size) > EE_MAIN_MEM_SIZE) {
throw_compiler_error(form,
":disasm memory out of range. Wanted to print 0x{:x} to 0x{:x}, but valid "
"memory is 0x{:x} to 0x{:x}.",
addr, addr + size, EE_MAIN_MEM_LOW_PROTECT, EE_MAIN_MEM_SIZE);
}
m_debugger.read_memory(mem.data(), size, addr);
fmt::print("{}\n",
disassemble_x86(mem.data(), mem.size(), m_debugger.get_x86_base_addr() + addr));
return get_none();
}
Val* Compiler::compile_bp(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
va_check(form, args, {{}}, {});
if (!m_debugger.is_halted()) {
throw_compiler_error(
form,
"Cannot add breakpoint, the debugger must be connected and the target must be halted.");
}
u32 addr = parse_address_spec(args.unnamed.at(0));
m_debugger.add_addr_breakpoint(addr);
return get_none();
}
Val* Compiler::compile_ubp(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
va_check(form, args, {{}}, {});
if (!m_debugger.is_halted()) {
throw_compiler_error(
form,
"Cannot remove breakpoint, the debugger must be connected and the target must be halted.");
}
u32 addr = parse_address_spec(args.unnamed.at(0));
m_debugger.remove_addr_breakpoint(addr);
return get_none();
}