Add command line debugger

This commit is contained in:
kichikuou
2024-12-28 14:09:53 +09:00
parent d6e3cb8415
commit 8883bd5d68
16 changed files with 1128 additions and 16 deletions
+12
View File
@@ -67,6 +67,10 @@ elseif (EMSCRIPTEN)
)
else() # NOT (ANDROID OR EMSCRIPTEN)
if (NOT NINTENDO_SWITCH)
option(ENABLE_DEBUGGER "Enable built-in debugger" ON)
endif()
add_executable(system3)
target_link_libraries(system3 PRIVATE ymfm)
@@ -176,6 +180,14 @@ target_sources(system3 PRIVATE
src/sys/nact_sys3.cpp
src/sys/scenario.cpp
)
if (ENABLE_DEBUGGER)
target_compile_definitions(system3 PRIVATE ENABLE_DEBUGGER)
target_sources(system3 PRIVATE
src/debugger/debug_info.cpp
src/debugger/debugger.cpp
src/debugger/cli_frontend.cpp
)
endif()
if(NINTENDO_SWITCH)
set(RESOURCE_PATH romfs:/)
+25
View File
@@ -77,6 +77,23 @@ std::pair<char*, char*> parse_keyval(char *line)
return { trim(line), trim(eq + 1) };
}
#ifdef ENABLE_DEBUGGER
DebuggerMode parse_debugger_mode(const char *s, int lineno = -1)
{
if (!strcasecmp(s, "none"))
return DebuggerMode::DISABLED;
if (!strcasecmp(s, "cli"))
return DebuggerMode::CLI;
if (!strcasecmp(s, "dap"))
return DebuggerMode::DAP;
if (lineno == -1)
ERROR("Command line: Invalid debugger mode '%s'", s);
else
ERROR(INIFILENAME ":%d Invalid debugger mode '%s'", lineno, s);
return DebuggerMode::DISABLED;
}
#endif
TexthookMode parse_texthook_mode(const char *s, int lineno = -1)
{
if (!strcasecmp(s, "none"))
@@ -135,6 +152,10 @@ Config::Config(int argc, char *argv[])
title = argv[++i];
else if (strcmp(argv[i], "-scanline") == 0)
scanline = true;
#ifdef ENABLE_DEBUGGER
else if (strcmp(argv[i], "-debugger") == 0)
debugger_mode = parse_debugger_mode(argv[++i]);
#endif
else if (strcmp(argv[i], "-texthook") == 0)
texthook_mode = parse_texthook_mode(argv[++i]);
else if (strcmp(argv[i], "-texthook_suppress") == 0)
@@ -197,6 +218,10 @@ void Config::load_ini()
title = val;
else if (!strcasecmp(key, "scanline"))
scanline = to_bool(val, lineno);
#ifdef ENABLE_DEBUGGER
else if (!strcasecmp(key, "debugger"))
debugger_mode = parse_debugger_mode(val, lineno);
#endif
else if (!strcasecmp(key, "texthook"))
texthook_mode = parse_texthook_mode(val, lineno);
else if (!strcasecmp(key, "texthook_suppress"))
+7
View File
@@ -11,6 +11,12 @@ enum class TexthookMode {
COPY,
};
enum class DebuggerMode {
DISABLED,
CLI,
DAP,
};
struct Strings {
std::string back;
std::string next_page;
@@ -39,6 +45,7 @@ struct Config {
bool no_antialias = false;
bool scanline = false;
TexthookMode texthook_mode = TexthookMode::NONE;
DebuggerMode debugger_mode = DebuggerMode::DISABLED;
std::string texthook_suppressions;
private:
+408
View File
@@ -0,0 +1,408 @@
#include "debugger/frontend.h"
#include <stdio.h>
#include "debugger/debug_info.h"
#include "debugger/debugger.h"
#include "encoding.h"
#include "nact.h"
#ifndef _WIN32
#include <signal.h>
#endif
namespace debugger {
namespace {
const char whitespaces[] = " \t\r\n";
const size_t CMD_BUFFER_SIZE = 256;
#ifndef _WIN32
void handle_sigint(int) {
g_debugger->set_state(State::STOPPED_INTERRUPT);
}
void set_sigint_handler() {
struct sigaction sa;
sa.sa_handler = handle_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
}
#endif
} // namespace
class CliFrontend : public Frontend {
public:
CliFrontend(Debugger* backend, const DebugInfo& symbols) : Frontend(backend, symbols) {
backend->set_state(State::STOPPED_ENTRY);
#ifndef _WIN32
set_sigint_handler();
#endif
}
~CliFrontend() override = default;
void repl(int bp_no) override {
if (backend->get_state() == State::STOPPED_BREAKPOINT && bp_no)
printf("Breakpoint %d\n", bp_no);
printf("Stopped at %s\n", format_address(g_nact->sco.page(), g_nact->sco.cmd_addr()).c_str());
backend->set_state(State::RUNNING);
char buf[CMD_BUFFER_SIZE];
while (printf("dbg> "), fflush(stdout), fgets(buf, CMD_BUFFER_SIZE, stdin)) {
// If input line is empty, repeat the last command.
if (buf[0] == '\n')
strcpy(buf, default_command);
else
strcpy(default_command, buf);
char *token = strtok(buf, whitespaces);
if (!token)
continue;
const Command *cmd = find_command(token);
if (cmd) {
if ((this->*(cmd->func))() == EXIT_REPL)
return;
}
}
}
void on_sleep() override {
if (backend->get_state() == State::STOPPED_INTERRUPT)
backend->repl(0);
}
void on_palette_change() override {
}
void console_output(int lv, const char *output) override {
}
private:
enum CommandResult { CONTINUE_REPL, EXIT_REPL };
char default_command[CMD_BUFFER_SIZE];
CommandResult cmd_backtrace();
CommandResult cmd_break();
CommandResult cmd_continue();
CommandResult cmd_delete();
CommandResult cmd_help();
CommandResult cmd_list();
CommandResult cmd_step();
CommandResult cmd_finish();
CommandResult cmd_next();
CommandResult cmd_print();
CommandResult cmd_quit();
CommandResult cmd_string();
struct Command {
const char *name;
const char *alias;
const char *description;
const char *help;
CommandResult (CliFrontend::* func)();
};
static const Command commands[];
const Command *find_command(const char *str);
void cmd_print_help(const Command& cmd) {
if (cmd.alias)
printf("%s, %s -- %s\n", cmd.name, cmd.alias, cmd.description);
else
printf("%s -- %s\n", cmd.name, cmd.description);
}
std::string format_address(int page, int addr) {
char buf[200];
const char *src = symbols.page2src(page);
int line = symbols.addr2line(page, addr);
if (src && line > 0)
snprintf(buf, sizeof(buf), "%s:%d", src, line);
else
snprintf(buf, sizeof(buf), "%d:0x%x", page, addr);
return buf;
}
bool parse_address(const char *str, int *page, int *addr) {
// <page>:<address>
if (sscanf(str, "%i:%i", page, addr) == 2)
return true;
// <filename>:<linenum>
char filename[100];
int line_no;
if (sscanf(str, "%[^:]:%i", filename, &line_no) == 2) {
*page = symbols.src2page(filename);
if (*page < 0) {
printf("No source file named %s.\n", filename);
return false;
}
*addr = symbols.line2addr(*page, line_no);
if (*addr < 0) {
printf("No line %d in file %s.\n", line_no, filename);
return false;
}
return true;
}
*page = g_nact->sco.page();
// <linenum>
char *endptr;
line_no = strtol(str, &endptr, 0);
if (!*endptr) {
*addr = symbols.line2addr(g_nact->sco.page(), line_no);
if (*addr < 0) {
printf("No line %d in current file.\n", line_no);
return false;
}
return true;
}
return false;
}
};
static const char desc_backtrace[] = "Print backtrace of stack frames.";
static const char * const help_backtrace = nullptr;
CliFrontend::CommandResult CliFrontend::cmd_backtrace() {
for (const StackFrame &frame : backend->stack_trace()) {
if (frame.src && frame.line > 0) {
printf("\t%s:%d\n", frame.src, frame.line);
} else {
printf("\t%d:%04x\n", frame.page, frame.addr);
}
}
return CONTINUE_REPL;
}
static const char desc_break[] = "Set breakpoint at specified location.";
static const char help_break[] =
"Syntax: break <filename>:<linenum> [if <condition>]\n"
" break <linenum> [if <condition>]\n"
" break <page>:<address> [if <condition>]";
CliFrontend::CommandResult CliFrontend::cmd_break() {
char *token = strtok(nullptr, whitespaces);
int page, addr;
if (!token || !parse_address(token, &page, &addr)) {
puts(help_break);
return CONTINUE_REPL;
}
int bp_no = backend->set_breakpoint(page, addr, false);
if (bp_no < 0) {
printf("Failed to set breakpoint at %d:%04x: invalid address\n", page, addr);
return CONTINUE_REPL;
}
printf("Breakpoint %d at %s\n", bp_no, format_address(page, addr).c_str());
return CONTINUE_REPL;
}
static const char desc_continue[] = "Continue program execution.";
static const char * const help_continue = nullptr;
CliFrontend::CommandResult CliFrontend::cmd_continue() {
return EXIT_REPL;
}
static const char desc_delete[] = "Delete breakpoints.";
static const char help_delete[] =
"Syntax: delete <breakpoint_no>...";
CliFrontend::CommandResult CliFrontend::cmd_delete() {
char *arg = strtok(nullptr, whitespaces);
if (!arg) {
puts(help_delete);
return CONTINUE_REPL;
}
while (arg) {
char *endptr;
int bp_no = strtol(arg, &endptr, 0);
if (*endptr)
printf("Bad breakpoint number %s\n", arg);
else {
if (!backend->delete_breakpoint(bp_no))
printf("No breakpoint number %d.\n", bp_no);
}
arg = strtok(nullptr, whitespaces);
}
return CONTINUE_REPL;
}
static const char desc_list[] = "List specified line.";
static const char help_list[] =
"Syntax: list\n"
" list <linenum>\n"
" list <filename>:<linenum>\n"
"\n"
"With no argument, lists ten lines around current location.";
CliFrontend::CommandResult CliFrontend::cmd_list() {
char *arg = strtok(NULL, whitespaces);
int page, addr;
if (arg) {
if (!parse_address(arg, &page, &addr)) {
puts(help_list);
return CONTINUE_REPL;
}
} else {
page = g_nact->sco.page();
addr = g_nact->sco.cmd_addr();
}
int line_no = symbols.addr2line(page, addr);
if (line_no <= 0) {
printf("Cannot determine source location for %x:%x\n", page, addr);
return CONTINUE_REPL;
}
if ((line_no -= 5) <= 0)
line_no = 1;
for (int i = 0; i < 10; i++) {
const char *line = symbols.source_line(page, line_no + i);
if (!line) {
if (i == 0)
printf("No source text for %s:%d\n", symbols.page2src(page), line_no);
break;
}
printf("%d\t%s\n", line_no + i, line);
}
// Empty REPL input will print the next 10 lines.
snprintf(default_command, CMD_BUFFER_SIZE, "l %s:%d", symbols.page2src(page), line_no + 15);
return CONTINUE_REPL;
}
static const char desc_step[] = "Step program until it reaches a different source line.";
static const char * const help_step = nullptr;
CliFrontend::CommandResult CliFrontend::cmd_step() {
backend->stepin();
return EXIT_REPL;
}
static const char desc_finish[] = "Execute until current function returns.";
static const char * const help_finish = nullptr;
CliFrontend::CommandResult CliFrontend::cmd_finish() {
backend->stepout();
return EXIT_REPL;
}
static const char desc_next[] = "Step program, proceeding through subroutine calls.";
static const char * const help_next = nullptr;
CliFrontend::CommandResult CliFrontend::cmd_next() {
backend->next();
return EXIT_REPL;
}
static const char desc_print[] = "Print value of variable.";
static const char help_print[] =
"Syntax: print <variable>";
CliFrontend::CommandResult CliFrontend::cmd_print() {
char *arg = strtok(NULL, whitespaces);
if (!arg) {
puts(help_print);
return CONTINUE_REPL;
}
int var = symbols.lookup_variable(arg);
if (var < 0) {
printf("Unrecognized variable name \"%s\".\n", arg);
return CONTINUE_REPL;
}
printf("%s = %d\n", arg, g_nact->get_var(var));
return CONTINUE_REPL;
}
static const char desc_quit[] = "Exit system3-sdl2.";
static const char * const help_quit = nullptr;
CliFrontend::CommandResult CliFrontend::cmd_quit() {
g_nact->quit(0);
return EXIT_REPL;
}
static const char desc_string[] = "Print value of string variable.";
static const char help_string[] =
"Syntax: string <index>";
CliFrontend::CommandResult CliFrontend::cmd_string() {
char *arg = strtok(NULL, whitespaces);
if (!arg) {
puts(help_string);
return CONTINUE_REPL;
}
while (arg) {
int no = atoi(arg);
if (no < 1 || no > MAX_STRVAR) {
printf("Bad string index %s\n", arg);
} else {
char *utf = g_nact->encoding->toUtf8(g_nact->get_string(no - 1));
printf("string[%d] = \"%s\"\n", no, utf); // TODO: escaping
free(utf);
}
arg = strtok(NULL, whitespaces);
}
return CONTINUE_REPL;
}
static const char desc_help[] = "Print list of commands.";
static const char * const help_help = nullptr;
const CliFrontend::Command CliFrontend::commands[] = {
{"backtrace", "bt", desc_backtrace, help_backtrace, &CliFrontend::cmd_backtrace},
{"break", "b", desc_break, help_break, &CliFrontend::cmd_break},
{"continue", "c", desc_continue, help_continue, &CliFrontend::cmd_continue},
{"delete", "d", desc_delete, help_delete, &CliFrontend::cmd_delete},
{"help", "h", desc_help, help_help, &CliFrontend::cmd_help},
{"list", "l", desc_list, help_list, &CliFrontend::cmd_list},
{"step", "s", desc_step, help_step, &CliFrontend::cmd_step},
{"finish", nullptr, desc_finish, help_finish, &CliFrontend::cmd_finish},
{"next", "n", desc_next, help_next, &CliFrontend::cmd_next},
{"print", "p", desc_print, help_print, &CliFrontend::cmd_print},
{"quit", "q", desc_quit, help_quit, &CliFrontend::cmd_quit},
{"string", "str", desc_string, help_string, &CliFrontend::cmd_string},
};
const CliFrontend::Command *CliFrontend::find_command(const char *str) {
for (const Command &cmd : commands) {
if (!strcmp(str, cmd.name) || (cmd.alias && !strcmp(str, cmd.alias)))
return &cmd;
}
printf("Unknown command \"%s\". Try \"help\".\n", str);
return nullptr;
}
CliFrontend::CommandResult CliFrontend::cmd_help() {
char *token = strtok(nullptr, whitespaces);
if (!token) {
puts("List of commands:");
puts("");
for (auto& cmd : commands)
cmd_print_help(cmd);
puts("");
puts("Type \"help\" followed by command name for full documentation.");
return CONTINUE_REPL;
}
const Command *cmd = find_command(token);
if (cmd) {
cmd_print_help(*cmd);
if (cmd->help) {
puts("");
puts(cmd->help);
}
}
return CONTINUE_REPL;
}
// static
Frontend* Frontend::create_cli(Debugger* backend, const DebugInfo& symbols) {
return new CliFrontend(backend, symbols);
}
} // namespace debugger
+220
View File
@@ -0,0 +1,220 @@
#include "debugger/debug_info.h"
#include <algorithm>
#include <assert.h>
#include <string.h>
#include "fileio.h"
namespace debugger {
namespace {
inline uint32_t LittleEndian_getDW(uint8_t* buf, int ofs) {
return buf[ofs] | (buf[ofs + 1] << 8) | (buf[ofs + 2] << 16) | (buf[ofs + 3] << 24);
}
std::string fget4cc(FILEIO& fio) {
char buf[5] = {0};
if (!fio.read(buf, 4))
buf[0] = '\0';
return buf;
}
bool ensure_srcs(std::vector<SrcInfo>& srcs, uint32_t nr_srcs) {
if (srcs.empty()) {
srcs.resize(nr_srcs);
} else if (nr_srcs != srcs.size()) {
WARNING("malformed debug information");
return false;
}
return true;
}
bool load_srcs(std::vector<SrcInfo>& srcs, std::vector<uint8_t>& buf) {
uint32_t nr_srcs = LittleEndian_getDW(buf.data(), 0);
if (!ensure_srcs(srcs, nr_srcs))
return false;
char *p = reinterpret_cast<char*>(buf.data()) + 4;
for (uint32_t i = 0; i < nr_srcs; i++) {
srcs[i].filename = p;
p += strlen(p) + 1;
}
return true;
}
void store_src_lines(SrcInfo& info, char *src) {
while (src) {
char *eol = strchr(src, '\n');
if (eol) {
if (src < eol && eol[-1] == '\r')
eol[-1] = '\0';
else
eol[0] = '\0';
eol++;
}
info.lines.push_back(src);
src = eol;
}
}
bool load_scnt(std::vector<SrcInfo>& srcs, std::vector<uint8_t>& buf) {
uint32_t nr_srcs = LittleEndian_getDW(buf.data(), 0);
if (!ensure_srcs(srcs, nr_srcs))
return false;
char *p = reinterpret_cast<char*>(buf.data()) + 4;
for (uint32_t i = 0; i < nr_srcs; i++) {
size_t len = strlen(p);
store_src_lines(srcs[i], p);
p += len + 1;
}
return true;
}
bool load_line(std::vector<SrcInfo>& srcs, std::vector<uint8_t>& buf) {
uint32_t nr_srcs = LittleEndian_getDW(buf.data(), 0);
if (!ensure_srcs(srcs, nr_srcs))
return false;
int ofs = 4;
for (uint32_t i = 0; i < nr_srcs; i++) {
SrcInfo& info = srcs[i];
uint32_t nr_mappings = LittleEndian_getDW(buf.data(), ofs);
ofs += 4;
info.mappings.resize(nr_mappings);
for (uint32_t i = 0; i < nr_mappings; i++) {
info.mappings[i].line = LittleEndian_getDW(buf.data(), ofs);
info.mappings[i].addr = LittleEndian_getDW(buf.data(), ofs + 4);
ofs += 8;
}
}
return true;
}
bool load_vari(std::vector<std::string>& variables, std::vector<uint8_t>& buf) {
uint32_t nr_vars = LittleEndian_getDW(buf.data(), 0);
char *p = reinterpret_cast<char*>(buf.data()) + 4;
for (uint32_t i = 0; i < nr_vars; i++) {
variables.push_back(p);
p += strlen(p) + 1;
}
return true;
}
} // namespace
int SrcInfo::line2addr(int line) const {
// Find the first mapping with line >= `line`.
auto it = std::lower_bound(mappings.begin(), mappings.end(), line,
[](const Mapping& mapping, int line) {
return mapping.line < line;
});
return it != mappings.end() ? it->addr : -1;
}
int SrcInfo::addr2line(int addr) const {
// Find the last mapping with addr <= `addr`.
auto it = std::upper_bound(mappings.begin(), mappings.end(), addr,
[](int addr, const Mapping& mapping) {
return addr < mapping.addr;
});
return it != mappings.begin() ? (it - 1)->line : -1;
}
void DebugInfo::load(const char *path) {
auto fio = FILEIO::open(path, FILEIO_READ_BINARY);
if (!fio) {
WARNING("Cannot open %s: %s", path, strerror(errno));
return;
}
if (fget4cc(*fio) != "DSYM") {
WARNING("%s: wrong signature", path);
return;
}
int version = fio->getdw();
if (version != 0) {
WARNING("%s: unsupported debug info version", path);
return;
}
uint32_t nr_sections = fio->getdw();
for (uint32_t i = 0; i < nr_sections; i++) {
std::string tag = fget4cc(*fio);
uint32_t section_size = fio->getdw();
std::vector<uint8_t> section_content(section_size - 8);
if (!fio->read(section_content.data(), section_content.size())) {
WARNING("%s: %s", path, strerror(errno));
return;
}
if (tag == "SRCS") {
if (!load_srcs(srcs, section_content)) return;
} else if (tag == "SCNT") {
if (!load_scnt(srcs, section_content)) return;
} else if (tag == "LINE") {
if (!load_line(srcs, section_content)) return;
} else if (tag == "VARI") {
if (!load_vari(variables, section_content)) return;
} else {
WARNING("%s: unrecognized section %s", path, tag.c_str());
}
}
if (fio->getc() != EOF) {
WARNING("%s: broken debug information structure", path);
return;
}
loaded_ = !srcs.empty() && !variables.empty();
}
int DebugInfo::src2page(const char *fname) const {
for (size_t i = 0; i < srcs.size(); i++) {
if (!strcasecmp(srcs[i].filename.c_str(), fname))
return static_cast<int>(i);
}
return -1;
}
const char *DebugInfo::page2src(int page) const {
if (static_cast<size_t>(page) >= srcs.size())
return NULL;
return srcs[page].filename.c_str();
}
int DebugInfo::line2addr(int page, int line) const {
if (static_cast<size_t>(page) >= srcs.size())
return -1;
return srcs[page].line2addr(line);
}
int DebugInfo::addr2line(int page, int addr) const {
if (static_cast<size_t>(page) >= srcs.size())
return -1;
return srcs[page].addr2line(addr);
}
const char *DebugInfo::source_line(int page, int line) const {
if (static_cast<size_t>(page) >= srcs.size())
return NULL;
const SrcInfo& info = srcs[page];
line--; // 1-based to 0-based index
if (static_cast<size_t>(line) >= info.lines.size())
return NULL;
return info.lines[line].c_str();
}
const char *DebugInfo::variable_name(int i) const {
if (static_cast<size_t>(i) >= variables.size())
return NULL;
return variables[i].c_str();
}
int DebugInfo::lookup_variable(const char *name) const {
for (size_t i = 0; i < variables.size(); i++) {
if (variables[i] == name)
return static_cast<int>(i);
}
return -1;
}
} // namespace debugger
+43
View File
@@ -0,0 +1,43 @@
#ifndef _DEBUG_INFO_H_
#define _DEBUG_INFO_H_
#include <string>
#include <vector>
namespace debugger {
struct SrcInfo {
struct Mapping {
int line;
int addr;
};
std::string filename;
std::vector<std::string> lines;
std::vector<Mapping> mappings;
int line2addr(int line) const;
int addr2line(int addr) const;
};
class DebugInfo {
public:
void load(const char *filename);
bool loaded() const { return loaded_; }
int src2page(const char *fname) const;
const char *page2src(int page) const;
int line2addr(int page, int line) const;
int addr2line(int page, int addr) const;
const char *source_line(int page, int line) const;
size_t num_variables() const { return variables.size(); }
const char *variable_name(int i) const;
int lookup_variable(const char *name) const;
private:
bool loaded_ = false;
std::vector<SrcInfo> srcs;
std::vector<std::string> variables;
};
} // namespace debugger
#endif // _DEBUG_INFO_H_
+219
View File
@@ -0,0 +1,219 @@
#include "debugger/debugger.h"
#include <algorithm>
#include <map>
#include "common.h"
#include "nact.h"
#include "debugger/frontend.h"
namespace debugger {
namespace {
const int INTERNAL_BREAKPOINT_NO = -1;
struct Breakpoint {
int page;
int addr;
int no;
uint8_t restore_op;
};
std::map<std::pair<int, int>, Breakpoint> breakpoints;
int next_breakpoint_no = 1;
// Used in State::STOPPED_STEP and State::STOPPED_NEXT
struct {
int page;
int line;
} step_exec_state;
bool should_continue_step(const DebugInfo& symbols) {
if (step_exec_state.line < 0)
return false; // line info was not available
return step_exec_state.page == g_nact->sco.page()
&& step_exec_state.line == symbols.addr2line(step_exec_state.page, g_nact->sco.cmd_addr());
}
int get_retaddr_if_funcall() {
int orig_addr = g_nact->sco.current_addr();
int retaddr = -1;
g_nact->sco.jump_to(g_nact->sco.cmd_addr());
int c0 = g_nact->sco.getd();
if (c0 == BREAKPOINT_INSTRUCTION) {
auto bp = breakpoints.find({g_nact->sco.page(), g_nact->sco.cmd_addr()});
if (bp == breakpoints.end())
sys_error("Illegal BREAKPOINT instruction");
c0 = bp->second.restore_op;
}
switch (c0) {
case '%':
if (g_nact->cali() != 0)
retaddr = g_nact->sco.current_addr();
break;
case '\\':
if (g_nact->sco.getw() != 0)
retaddr = g_nact->sco.current_addr();
break;
}
g_nact->sco.jump_to(orig_addr);
return retaddr;
}
} // namespace
Debugger::Debugger(const char *symbols_path, DebuggerMode mode) {
symbols.load(symbols_path);
if (mode == DebuggerMode::DAP) {
// frontend = std::make_unique<DapFrontend>(this);
} else {
frontend = std::unique_ptr<Frontend>(Frontend::create_cli(this, symbols));
}
}
Debugger::~Debugger() = default;
void Debugger::repl(int bp_no) {
delete_breakpoint(INTERNAL_BREAKPOINT_NO);
switch (state) {
case State::STOPPED_STEP:
if (should_continue_step(symbols))
return;
break;
case State::STOPPED_NEXT:
if (should_continue_step(symbols)) {
do_next();
return;
}
break;
default:
break;
}
frontend->repl(bp_no);
}
void Debugger::on_sleep() {
frontend->on_sleep();
}
void Debugger::on_palette_change() {
frontend->on_palette_change();
}
uint8_t Debugger::handle_breakpoint(int page, int addr) {
auto bp = breakpoints.find({page, addr});
if (bp == breakpoints.end())
sys_error("Illegal BREAKPOINT instruction");
uint8_t restore_op = bp->second.restore_op;
state = bp->second.no == INTERNAL_BREAKPOINT_NO ? State::STOPPED_NEXT : State::STOPPED_BREAKPOINT;
repl(bp->second.no); // this may destroy bp
return restore_op;
}
bool Debugger::console_vprintf(int lv, const char *format, va_list ap) {
return false;
}
void Debugger::post_command(void *data) {
}
int Debugger::set_breakpoint(int page, int addr, bool is_internal)
{
int restore_op = g_nact->sco.write_instruction(page, addr, BREAKPOINT_INSTRUCTION);
if (restore_op == -1)
return -1;
if (restore_op == BREAKPOINT_INSTRUCTION) {
auto bp = breakpoints.find({page, addr});
if (bp != breakpoints.end()) {
sys_error("Illegal BREAKPOINT instruction");
} else {
return bp->second.no;
}
}
int no = is_internal ? INTERNAL_BREAKPOINT_NO : next_breakpoint_no++;
breakpoints[{page, addr}] = {page, addr, no, static_cast<uint8_t>(restore_op)};
return no;
}
bool Debugger::delete_breakpoint(int no)
{
for (auto it = breakpoints.begin(); it != breakpoints.end(); ++it) {
if (it->second.no == no) {
g_nact->sco.write_instruction(it->second.page, it->second.addr, it->second.restore_op);
breakpoints.erase(it);
return true;
}
}
return false;
}
void Debugger::delete_breakpoints_in_page(int page)
{
auto lower = breakpoints.lower_bound({page, 0});
auto upper = breakpoints.upper_bound({page, 0xffff});
for (auto it = lower; it != upper; ++it) {
g_nact->sco.write_instruction(it->second.page, it->second.addr, it->second.restore_op);
}
breakpoints.erase(lower, upper);
}
void Debugger::stepin()
{
step_exec_state.page = g_nact->sco.page();
step_exec_state.line = symbols.addr2line(step_exec_state.page, g_nact->sco.cmd_addr());
state = State::STOPPED_STEP;
}
void Debugger::stepout()
{
// Set an internal breakpoint at the return address of current frame.
auto& call_stack = g_nact->sco.get_call_stack();
if (call_stack.empty()) {
return;
}
const auto& frame = call_stack.back();
set_breakpoint(frame.page, frame.addr, true);
}
void Debugger::next()
{
step_exec_state.page = g_nact->sco.page();
step_exec_state.line = symbols.addr2line(step_exec_state.page, g_nact->sco.cmd_addr());
state = State::STOPPED_NEXT;
do_next();
}
void Debugger::do_next()
{
assert(state == State::STOPPED_NEXT);
int retaddr = get_retaddr_if_funcall();
if (retaddr >= 0) {
set_breakpoint(step_exec_state.page, retaddr, true);
state = State::RUNNING;
}
}
std::vector<StackFrame> Debugger::stack_trace()
{
std::vector<StackFrame> frames;
const char *src = symbols.page2src(g_nact->sco.page());
int line = symbols.addr2line(g_nact->sco.page(), g_nact->sco.cmd_addr());
frames.emplace_back(g_nact->sco.page(), g_nact->sco.cmd_addr(), src, line);
auto& call_stack = g_nact->sco.get_call_stack();
std::for_each(call_stack.rbegin(), call_stack.rend(), [&](const auto& frame) {
const char* src = symbols.page2src(frame.page);
// -1 because the return address is the address of the next command
int line = symbols.addr2line(frame.page, frame.addr - 1);
frames.emplace_back(frame.page, frame.addr, src, line);
});
return frames;
}
} // namespace debugger
std::unique_ptr<debugger::Debugger> g_debugger;
+76
View File
@@ -0,0 +1,76 @@
#ifndef _DEBUGGER_H_
#define _DEBUGGER_H_
#ifdef ENABLE_DEBUGGER
#include <memory>
#include <stdarg.h>
#include "config.h"
#include "debugger/debug_info.h"
namespace debugger {
const uint8_t BREAKPOINT_INSTRUCTION = 0x0f;
class Frontend;
enum class State {
RUNNING,
STOPPED_ENTRY,
STOPPED_STEP,
STOPPED_NEXT,
STOPPED_BREAKPOINT,
STOPPED_INTERRUPT,
STOPPED_EXCEPTION,
};
struct StackFrame {
int page;
int addr;
const char *src;
int line;
StackFrame(int page, int addr, const char *src, int line)
: page(page), addr(addr), src(src), line(line) {}
};
class Debugger {
public:
Debugger(const char *symbols_path, DebuggerMode mode);
~Debugger();
void repl(int bp_no);
// API for VM
bool trapped() const { return state != State::RUNNING; }
void on_sleep();
void on_palette_change();
uint8_t handle_breakpoint(int page, int addr);
bool console_vprintf(int lv, const char *format, va_list ap);
void post_command(void *data);
// API for frontend
State get_state() const { return state; }
void set_state(State s) { state = s; }
int set_breakpoint(int page, int addr, bool is_internal);
bool delete_breakpoint(int no);
void delete_breakpoints_in_page(int page);
void stepin();
void stepout();
void next();
std::vector<StackFrame> stack_trace();
private:
State state = State::RUNNING;
DebugInfo symbols;
std::unique_ptr<Frontend> frontend;
void do_next();
};
} // namespace debugger
extern std::unique_ptr<debugger::Debugger> g_debugger;
#endif // ENABLE_DEBUGGER
#endif /* _DEBUGGER_H_ */
+26
View File
@@ -0,0 +1,26 @@
#ifndef _DEBUGGER_FRONTEND_H_
#define _DEBUGGER_FRONTEND_H_
namespace debugger {
class Debugger;
class DebugInfo;
class Frontend {
public:
static Frontend* create_cli(Debugger* backend, const DebugInfo& symbols);
Frontend(Debugger* backend, const DebugInfo& symbols) : backend(backend), symbols(symbols) {}
virtual ~Frontend() = default;
virtual void repl(int bp_no) = 0;
virtual void on_sleep() = 0;
virtual void on_palette_change() = 0;
virtual void console_output(int lv, const char *output) = 0;
protected:
Debugger* backend;
const DebugInfo& symbols;
};
} // namespace debugger
#endif /* _DEBUGGER_FRONTEND_H_ */
+7
View File
@@ -44,6 +44,13 @@ public:
int val = getc();
return val | (getc() << 8);
}
uint32_t getdw() {
uint32_t val = getc();
val |= getc() << 8;
val |= getc() << 16;
val |= getc() << 24;
return val;
}
void putc(int c) { fputc(c, fp); }
void putw(int w) {
putc(w & 0xff);
+7
View File
@@ -4,6 +4,7 @@
#include "fileio.h"
#include "texthook.h"
#include "sys/nact.h"
#include "debugger/debugger.h"
#ifdef _WIN32
#include <direct.h>
#else
@@ -108,6 +109,12 @@ int main(int argc, char *argv[])
texthook_set_mode(config.texthook_mode);
texthook_set_suppression_list(config.texthook_suppressions.c_str());
#ifdef ENABLE_DEBUGGER
if (config.debugger_mode != DebuggerMode::DISABLED) {
g_debugger = std::make_unique<debugger::Debugger>("ADISK.DAT.symbols", config.debugger_mode);
}
#endif
int exit_code = 0;
while (g_nact) {
exit_code = g_nact->mainloop();
+1 -2
View File
@@ -39,8 +39,7 @@ void MsgSkip::load_from_file()
{
auto fio = FILEIO::open(MSGSKIP_FILENAME, FILEIO_READ_BINARY | FILEIO_SAVEDATA);
if (fio) {
uint32 size = fio->getw();
size |= fio->getw() << 16;
uint32 size = fio->getdw();
if (size == BLOOM_FILTER_SIZE)
fio->read(bloom, BLOOM_FILTER_SIZE / 8);
}
+19
View File
@@ -16,6 +16,7 @@
#include "../config.h"
#include "../fileio.h"
#include "game_id.h"
#include "debugger/debugger.h"
extern SDL_Window* g_window;
@@ -86,6 +87,12 @@ int NACT::mainloop()
int sleep_cnt = 0;
while(!terminate) {
#ifdef ENABLE_DEBUGGER
if (g_debugger && g_debugger->trapped()) {
sco.update_cmd_addr();
g_debugger->repl(0);
}
#endif
execute();
// 512コマンド実行毎にSleep(10)
if(!(sleep_cnt = (sleep_cnt + 1) & 0x1ff)) {
@@ -132,6 +139,14 @@ void NACT::execute()
return;
}
#ifdef ENABLE_DEBUGGER
if (cmd == debugger::BREAKPOINT_INSTRUCTION) {
if (!g_debugger)
sys_error("Illegal BREAKPOINT instruction");
cmd = g_debugger->handle_breakpoint(sco.page(), sco.cmd_addr());
}
#endif
switch(cmd) {
case '!':
cmd_calc();
@@ -817,6 +832,10 @@ void NACT::wait_after_open_menu()
void NACT::sys_sleep(int ms) {
pump_events();
ags->update_screen();
#ifdef ENABLE_DEBUGGER
if (g_debugger)
g_debugger->on_sleep();
#endif
#ifdef __EMSCRIPTEN__
emscripten_sleep(ms);
#else
+9 -5
View File
@@ -80,6 +80,7 @@
#define MAX_VERB 128
#define MAX_OBJ 256
#define MAX_PCM 256
#define MAX_STRVAR 10
class AGS;
class MAKO;
@@ -97,6 +98,8 @@ public:
NACT(const Config& config, const GameId& game_id);
virtual ~NACT();
Scenario sco;
protected:
AGS* ags;
MAKO* mako;
@@ -105,13 +108,11 @@ protected:
// コマンドパーサ
void execute();
Scenario sco;
// 変数
uint16 var[512] = {};
uint16 var_stack[30][20] = {};
char tvar[10][33] = {};
char tvar_stack[30][10][22] = {};
char tvar[MAX_STRVAR][33] = {};
char tvar_stack[30][MAX_STRVAR][22] = {};
int tvar_index = 0;
int tvar_maxlen;
@@ -194,7 +195,6 @@ protected:
int pcm[MAX_PCM] = {};
// 下位関数
virtual uint16 cali() = 0;
bool is_message(uint8_t c) { return c == ' ' || c & 0x80; }
uint16 random(uint16 range);
@@ -237,6 +237,8 @@ public:
void text_wait();
void set_skip_menu_state(bool enabled, bool checked);
virtual uint16 cali() = 0;
bool mouse_move_enabled = true;
const Config& config;
const GameId& game_id;
@@ -247,6 +249,8 @@ public:
void output_console(const char *format, ...);
int get_scenario_page() const { return sco.page(); }
uint16 get_var(int index) const { return var[index]; }
const char* get_string(int index) const { return tvar[index]; }
private:
void pump_events();
+42 -3
View File
@@ -6,17 +6,44 @@
uint8_t Scenario::fetch_command()
{
if (addr_ < 2 || static_cast<size_t>(addr_) >= data_.size())
if (addr_ < 2 || static_cast<size_t>(addr_) >= data_size_)
sys_error("Scenario error: invalid address %d:%04x", page_, addr_);
// Skip SysEng's "new style" marker
if (page_ == 0 && addr_ == 2 && data_[2] == 'R' && data_[3] == 'E' && data_[4] == 'V')
addr_ = 5;
cmd_addr_ = addr_;
update_cmd_addr();
return getd();
}
std::vector<uint8_t>* Scenario::load_page(int page)
{
if (static_cast<size_t>(page) < pages_.size() && !pages_[page].empty())
return &pages_[page];
std::vector<uint8_t> data = adisk.load(page + 1);
if (data.empty())
return nullptr;
if (static_cast<size_t>(page) >= pages_.size())
pages_.resize(page + 1);
pages_[page] = std::move(data);
return &pages_[page];
}
void Scenario::page_jump(int page, int addr)
{
std::vector<uint8_t>* page_data = load_page(page);
if (page_data) {
data_ = page_data->data();
data_size_ = page_data->size();
} else {
data_ = nullptr;
data_size_ = 0;
}
page_ = page;
cmd_addr_ = addr_ = addr;
}
void Scenario::skip_syseng_string(Encoding *enc, uint8_t terminator)
{
for (uint8_t c = getd(); c != terminator; c = getd()) {
@@ -136,4 +163,16 @@ void Scenario::page_stack_clear()
} else {
sys_error("Unknown Command: %02x at %d:%04x", cmd, page_, cmd_addr_);
}
}
}
int Scenario::write_instruction(int page, int addr, uint8_t op)
{
std::vector<uint8_t>* page_data = load_page(page);
if (!page_data)
return -1;
if (addr < 0 || addr >= static_cast<int>(page_data->size()))
return -1;
uint8_t old_op = (*page_data)[addr];
(*page_data)[addr] = op;
return old_op;
}
+7 -6
View File
@@ -11,11 +11,7 @@ class Encoding;
class Scenario {
public:
void open(const char* file_name) { adisk.open(file_name); }
void page_jump(int page, int addr) {
data_ = adisk.load(page + 1);
page_ = page;
cmd_addr_ = addr_ = addr;
}
void page_jump(int page, int addr);
int default_addr() { return data_[0] | data_[1] << 8; }
int page() const { return page_; }
@@ -28,6 +24,7 @@ public:
void skip(int n) { addr_ += n; }
uint8_t fetch_command();
void update_cmd_addr() { cmd_addr_ = addr_; }
uint8_t getd() { return data_[addr_++]; }
uint16_t getw() {
@@ -63,8 +60,12 @@ public:
int write_instruction(int page, int addr, uint8_t op);
private:
std::vector<uint8_t>* load_page(int page);
Dri adisk;
std::vector<uint8_t> data_;
std::vector<std::vector<uint8_t>> pages_;
const uint8_t* data_;
size_t data_size_;
int page_;
int addr_;
int cmd_addr_;