mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-04 12:58:04 +03:00
Merge pull request #210 from kichikuou/debugger
debugger_cmd: Add support for source-level debugging
This commit is contained in:
+14
-2
@@ -59,12 +59,15 @@ struct dbg_cmd {
|
||||
void (*run)(unsigned nr_args, char **args);
|
||||
};
|
||||
|
||||
struct dbg_info;
|
||||
|
||||
extern bool dbg_dap;
|
||||
extern bool dbg_enabled;
|
||||
extern bool dbg_start_in_debugger;
|
||||
extern unsigned dbg_current_frame;
|
||||
extern struct dbg_info *dbg_info;
|
||||
|
||||
void dbg_init(void);
|
||||
void dbg_init(const char *debug_info_path);
|
||||
void dbg_fini(void);
|
||||
void dbg_repl(enum dbg_stop_type, const char *msg);
|
||||
|
||||
@@ -87,6 +90,7 @@ void dbg_print_frame(unsigned no);
|
||||
void dbg_print_stack_trace(void);
|
||||
void dbg_print_stack(void);
|
||||
void dbg_print_vm_state(void);
|
||||
void dbg_print_current_line(void);
|
||||
union vm_value dbg_eval_string(const char *str, struct ain_type *type_out);
|
||||
struct string *dbg_value_to_string(struct ain_type *type, union vm_value value, int recursive);
|
||||
|
||||
@@ -96,13 +100,21 @@ void dbg_dap_repl(struct dbg_stop *stop);
|
||||
void dbg_dap_handle_messages(void);
|
||||
void dbg_dap_log(const char *log, const char *fmt, va_list ap);
|
||||
|
||||
struct dbg_info *dbg_info_load(const char *path);
|
||||
const char *dbg_info_source_name(const struct dbg_info *info, int file);
|
||||
char *dbg_info_source_path(const struct dbg_info *info, int file);
|
||||
const char *dbg_info_source_line(const struct dbg_info *info, int file, int line);
|
||||
int dbg_info_find_file(const struct dbg_info *info, const char *filename);
|
||||
bool dbg_info_addr2line(const struct dbg_info *info, int addr, int *file, int *line);
|
||||
int dbg_info_line2addr(const struct dbg_info *info, int file, int line);
|
||||
|
||||
#ifdef HAVE_SCHEME
|
||||
void dbg_scm_init(void);
|
||||
void dbg_scm_fini(void);
|
||||
void dbg_scm_repl(void);
|
||||
#endif /* HAVE_SCHEME */
|
||||
#else /* DEBUGGER ENABLED */
|
||||
#define dbg_init()
|
||||
#define dbg_init(debug_info_path)
|
||||
#define dbg_repl(type, msg)
|
||||
#define dbg_dap 0
|
||||
#define dbg_dap_handle_messages()
|
||||
|
||||
+47
-2
@@ -24,6 +24,7 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "system4/dasm.h"
|
||||
#include "system4/file.h"
|
||||
#include "system4/hashtable.h"
|
||||
#include "system4/little_endian.h"
|
||||
#include "system4/string.h"
|
||||
@@ -39,6 +40,7 @@
|
||||
bool dbg_dap = false;
|
||||
bool dbg_enabled = true;
|
||||
bool dbg_start_in_debugger = false;
|
||||
struct dbg_info *dbg_info = NULL;
|
||||
unsigned dbg_current_frame = 0;
|
||||
static jmp_buf dbg_continuation;
|
||||
|
||||
@@ -94,8 +96,19 @@ void dbg_repl(enum dbg_stop_type type, const char *msg)
|
||||
dbg_start(_dbg_repl, &stop);
|
||||
}
|
||||
|
||||
void dbg_init(void)
|
||||
void dbg_init(const char *debug_info_path)
|
||||
{
|
||||
if (debug_info_path) {
|
||||
dbg_info = dbg_info_load(debug_info_path);
|
||||
} else {
|
||||
// Try to load from the default location
|
||||
char *path = gamedir_path("src/debug_info.json");
|
||||
if (file_exists(path)) {
|
||||
dbg_info = dbg_info_load(path);
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
|
||||
if (dbg_dap)
|
||||
dbg_dap_init();
|
||||
else
|
||||
@@ -437,8 +450,14 @@ void dbg_print_frame(unsigned no)
|
||||
unsigned cs_no = call_stack_ptr - (1 + no);
|
||||
struct ain_function *f = &ain->functions[call_stack[cs_no].fno];
|
||||
uint32_t addr = no ? call_stack[cs_no+1].call_address : instr_ptr;
|
||||
sys_message("%c #%d 0x%08x in %s\n", no == dbg_current_frame ? '*' : ' ',
|
||||
sys_message("%c #%d 0x%08x in %s", no == dbg_current_frame ? '*' : ' ',
|
||||
no, addr, display_sjis0(f->name));
|
||||
int file, line;
|
||||
if (dbg_info && dbg_info_addr2line(dbg_info, addr, &file, &line)) {
|
||||
sys_message(" at %s:%d\n", display_utf0(dbg_info_source_name(dbg_info, file)), line);
|
||||
} else {
|
||||
sys_message("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void dbg_print_stack_trace(void)
|
||||
@@ -1094,3 +1113,29 @@ void dbg_print_vm_state(void)
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
void dbg_print_current_line(void)
|
||||
{
|
||||
if (call_stack_ptr < 1) {
|
||||
DBG_ERROR("VM not running");
|
||||
return;
|
||||
}
|
||||
if (!dbg_info) {
|
||||
DBG_ERROR("No debug information available");
|
||||
return;
|
||||
}
|
||||
|
||||
int file, line;
|
||||
if (!dbg_info_addr2line(dbg_info, instr_ptr, &file, &line)) {
|
||||
DBG_ERROR("No line number information for address: 0x%08x", instr_ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *src_line = dbg_info_source_line(dbg_info, file, line);
|
||||
if (!src_line) {
|
||||
DBG_ERROR("No source text for %s:%d", display_utf0(dbg_info_source_name(dbg_info, file)), line);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s:%d\t%s\n", display_utf0(dbg_info_source_name(dbg_info, file)), line, display_utf1(src_line));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/* Copyright (C) 2024 kichikuou <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "system4/file.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "debugger.h"
|
||||
|
||||
struct mapping {
|
||||
int addr;
|
||||
int file;
|
||||
int line;
|
||||
};
|
||||
|
||||
struct source_content {
|
||||
int nr_lines;
|
||||
char *lines[];
|
||||
};
|
||||
|
||||
struct dbg_info {
|
||||
char *src_root;
|
||||
int nr_sources;
|
||||
char **sources;
|
||||
struct source_content **source_contents;
|
||||
int nr_mappings;
|
||||
struct mapping *mappings;
|
||||
};
|
||||
|
||||
struct dbg_info *dbg_info_load(const char *path)
|
||||
{
|
||||
char *json_text = file_read(path, NULL);
|
||||
if (!json_text) {
|
||||
DBG_ERROR("Cannot load debug information file %s: %s", path, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_Parse(json_text);
|
||||
free(json_text);
|
||||
|
||||
cJSON *version = cJSON_GetObjectItem(root, "version");
|
||||
if (!cJSON_IsString(version) || strcmp(version->valuestring, "alpha-1")) {
|
||||
DBG_ERROR("%s: Unsupported debug information format", display_utf0(path));
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct dbg_info *info = xcalloc(1, sizeof(struct dbg_info));
|
||||
cJSON *sources = cJSON_GetObjectItem(root, "sources");
|
||||
info->nr_sources = cJSON_GetArraySize(sources);
|
||||
info->sources = xcalloc(info->nr_sources, sizeof(char *));
|
||||
cJSON *source;
|
||||
int i = 0;
|
||||
cJSON_ArrayForEach(source, sources) {
|
||||
info->sources[i++] = xstrdup(source->valuestring);
|
||||
}
|
||||
|
||||
cJSON *mappings = cJSON_GetObjectItem(root, "mappings");
|
||||
info->nr_mappings = cJSON_GetArraySize(mappings);
|
||||
info->mappings = xcalloc(info->nr_mappings, sizeof(struct mapping));
|
||||
|
||||
struct mapping *m = info->mappings;
|
||||
cJSON *item;
|
||||
cJSON_ArrayForEach(item, mappings) {
|
||||
m->addr = cJSON_GetArrayItem(item, 0)->valueint;
|
||||
m->file = cJSON_GetArrayItem(item, 1)->valueint;
|
||||
m->line = cJSON_GetArrayItem(item, 2)->valueint;
|
||||
m++;
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
|
||||
info->src_root = xstrdup(path_dirname(path));
|
||||
info->source_contents = xcalloc(info->nr_sources, sizeof(struct source_content *));
|
||||
return info;
|
||||
}
|
||||
|
||||
static struct source_content *load_source_content(const char *path)
|
||||
{
|
||||
char *text = file_read(path, NULL);
|
||||
if (!text) {
|
||||
DBG_ERROR("Cannot load source file %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int nr_lines = 1; // count the last line even if it doesn't end with '\n'
|
||||
for (char *p = text; *p; p++) {
|
||||
if (*p == '\n')
|
||||
nr_lines++;
|
||||
}
|
||||
struct source_content *content = xcalloc(1, sizeof(struct source_content) + nr_lines * sizeof(char *));
|
||||
content->nr_lines = nr_lines;
|
||||
for (int i = 0;; i++) {
|
||||
content->lines[i] = text;
|
||||
char *next = strchr(text, '\n');
|
||||
if (!next)
|
||||
break;
|
||||
*next = '\0';
|
||||
text = next + 1;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
const char *dbg_info_source_name(const struct dbg_info *info, int file)
|
||||
{
|
||||
if (file < 0 || file >= info->nr_sources)
|
||||
return NULL;
|
||||
return info->sources[file];
|
||||
}
|
||||
|
||||
char *dbg_info_source_path(const struct dbg_info *info, int file)
|
||||
{
|
||||
if (file < 0 || file >= info->nr_sources)
|
||||
return NULL;
|
||||
return path_join(info->src_root, info->sources[file]);
|
||||
}
|
||||
|
||||
const char *dbg_info_source_line(const struct dbg_info *info, int file, int line)
|
||||
{
|
||||
if (file < 0 || file >= info->nr_sources)
|
||||
return NULL;
|
||||
if (!info->source_contents[file]) {
|
||||
char *path = dbg_info_source_path(info, file);
|
||||
info->source_contents[file] = load_source_content(path);
|
||||
free(path);
|
||||
if (!info->source_contents[file])
|
||||
return NULL;
|
||||
}
|
||||
if (line <= 0 || line > info->source_contents[file]->nr_lines)
|
||||
return NULL;
|
||||
return info->source_contents[file]->lines[line - 1];
|
||||
}
|
||||
|
||||
int dbg_info_find_file(const struct dbg_info *info, const char *filename)
|
||||
{
|
||||
for (int i = 0; i < info->nr_sources; i++) {
|
||||
if (!strcmp(info->sources[i], filename) || !strcmp(path_basename(info->sources[i]), filename))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool dbg_info_addr2line(const struct dbg_info *info, int addr, int *file, int *line)
|
||||
{
|
||||
// Find the last mapping whose address is less than or equal to `addr`.
|
||||
int left = 0, right = info->nr_mappings;
|
||||
while (left < right) {
|
||||
int mid = (left + right) / 2;
|
||||
if (info->mappings[mid].addr <= addr)
|
||||
left = mid + 1;
|
||||
else
|
||||
right = mid;
|
||||
}
|
||||
if (left == 0) {
|
||||
assert(info->nr_mappings == 0 || addr < info->mappings[0].addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(info->mappings[left - 1].addr <= addr);
|
||||
assert(left == info->nr_mappings || addr < info->mappings[left].addr);
|
||||
|
||||
if (file)
|
||||
*file = info->mappings[left - 1].file;
|
||||
if (line)
|
||||
*line = info->mappings[left - 1].line;
|
||||
return true;
|
||||
}
|
||||
|
||||
int dbg_info_line2addr(const struct dbg_info *info, int file, int line)
|
||||
{
|
||||
if (file < 0 || file >= info->nr_sources || line <= 0)
|
||||
return -1;
|
||||
|
||||
// Find the first mapping whose file is `file` and line is greater than or equal to `line`.
|
||||
int left = 0, right = info->nr_mappings;
|
||||
while (left < right) {
|
||||
int mid = (left + right) / 2;
|
||||
if (info->mappings[mid].file < file || (info->mappings[mid].file == file && info->mappings[mid].line < line))
|
||||
left = mid + 1;
|
||||
else
|
||||
right = mid;
|
||||
}
|
||||
if (left == info->nr_mappings) {
|
||||
assert(info->nr_mappings == 0 || info->mappings[info->nr_mappings - 1].file < file ||
|
||||
(info->mappings[info->nr_mappings - 1].file == file && info->mappings[info->nr_mappings - 1].line < line));
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(left == 0 || info->mappings[left - 1].file < file ||
|
||||
(info->mappings[left - 1].file == file && info->mappings[left - 1].line < line));
|
||||
if (file < info->mappings[left].file)
|
||||
return -1;
|
||||
assert(file == info->mappings[left].file && line <= info->mappings[left].line);
|
||||
|
||||
return info->mappings[left].addr;
|
||||
}
|
||||
+60
-12
@@ -40,7 +40,14 @@
|
||||
#include "xsystem4.h"
|
||||
|
||||
struct dbg_cmd_node;
|
||||
static bool stepping = false;
|
||||
static enum {
|
||||
NOT_STEPPING = 0,
|
||||
STEPPING_INTO,
|
||||
STEPPING_OVER,
|
||||
STEPPING_FINISH
|
||||
} stepping = NOT_STEPPING;
|
||||
static int stepping_file = 0;
|
||||
static int stepping_line = 0;
|
||||
|
||||
struct dbg_cmd_list {
|
||||
unsigned nr_commands;
|
||||
@@ -67,17 +74,33 @@ static void dbg_cmd_backtrace(unsigned nr_args, char **args)
|
||||
|
||||
static void dbg_cmd_breakpoint(unsigned nr_args, char **args)
|
||||
{
|
||||
int addr = strtol(args[0], NULL, 0);
|
||||
if (addr > 0) {
|
||||
if (nr_args == 1) {
|
||||
int addr = strtol(args[0], NULL, 0);
|
||||
if (addr > 0) {
|
||||
dbg_set_address_breakpoint(addr, NULL, NULL);
|
||||
} else {
|
||||
dbg_set_function_breakpoint(args[0], NULL, NULL);
|
||||
}
|
||||
} else if (nr_args == 2 && dbg_info) {
|
||||
int file = dbg_info_find_file(dbg_info, args[0]);
|
||||
if (file < 0) {
|
||||
DBG_ERROR("No such file: %s", args[0]);
|
||||
return;
|
||||
}
|
||||
int line = atoi(args[1]);
|
||||
int addr = dbg_info_line2addr(dbg_info, file, line);
|
||||
if (addr < 0) {
|
||||
DBG_ERROR("No such line: %s:%d", args[0], line);
|
||||
return;
|
||||
}
|
||||
dbg_set_address_breakpoint(addr, NULL, NULL);
|
||||
} else {
|
||||
dbg_set_function_breakpoint(args[0], NULL, NULL);
|
||||
DBG_ERROR("Invalid breakpoint command");
|
||||
}
|
||||
}
|
||||
|
||||
static void dbg_cmd_continue(unsigned nr_args, char **args)
|
||||
{
|
||||
stepping = false;
|
||||
dbg_continue();
|
||||
}
|
||||
|
||||
@@ -184,21 +207,29 @@ static void dbg_cmd_scene(unsigned nr_args, char **args)
|
||||
|
||||
static void dbg_cmd_next(unsigned nr_args, char **args)
|
||||
{
|
||||
stepping_file = stepping_line = 0;
|
||||
if (dbg_info)
|
||||
dbg_info_addr2line(dbg_info, instr_ptr, &stepping_file, &stepping_line);
|
||||
|
||||
if (!dbg_set_step_over_breakpoint()) {
|
||||
DBG_ERROR("Can't step over this instruction");
|
||||
return;
|
||||
}
|
||||
stepping = true;
|
||||
stepping = STEPPING_OVER;
|
||||
dbg_continue();
|
||||
}
|
||||
|
||||
static void dbg_cmd_step(unsigned nr_args, char **args)
|
||||
{
|
||||
stepping_file = stepping_line = 0;
|
||||
if (dbg_info)
|
||||
dbg_info_addr2line(dbg_info, instr_ptr, &stepping_file, &stepping_line);
|
||||
|
||||
if (!dbg_set_step_into_breakpoint()) {
|
||||
DBG_ERROR("Can't step into this instruction");
|
||||
return;
|
||||
}
|
||||
stepping = true;
|
||||
stepping = STEPPING_INTO;
|
||||
dbg_continue();
|
||||
}
|
||||
|
||||
@@ -208,7 +239,7 @@ static void dbg_cmd_finish(unsigned nr_args, char **args)
|
||||
DBG_ERROR("Can't set finish breakpoint from this location");
|
||||
return;
|
||||
}
|
||||
stepping = true;
|
||||
stepping = STEPPING_FINISH;
|
||||
dbg_continue();
|
||||
}
|
||||
|
||||
@@ -226,7 +257,7 @@ static void dbg_cmd_vm_state(unsigned nr_args, char **args)
|
||||
|
||||
static struct dbg_cmd dbg_default_commands[] = {
|
||||
{ "backtrace", "bt", NULL, "Display stack trace", 0, 0, dbg_cmd_backtrace },
|
||||
{ "breakpoint", "bp", "<function-or-address>", "Set breakpoint at a function or address", 1, 1, dbg_cmd_breakpoint },
|
||||
{ "breakpoint", "bp", "<function> | <address> | <file> <line>", "Set breakpoint", 1, 2, dbg_cmd_breakpoint },
|
||||
{ "continue", "c", NULL, "Resume execution", 0, 0, dbg_cmd_continue },
|
||||
{ "finish", "fin", NULL, "Execute until the current function returns", 0, 0, dbg_cmd_finish },
|
||||
{ "frame", "f", "<frame-number>", "Set the current frame", 1, 1, dbg_cmd_frame },
|
||||
@@ -477,10 +508,27 @@ static void execute_line(char *line)
|
||||
|
||||
void dbg_cmd_repl(void)
|
||||
{
|
||||
if (stepping)
|
||||
dbg_print_vm_state();
|
||||
else
|
||||
if (stepping) {
|
||||
if (stepping_line) {
|
||||
int file, line;
|
||||
dbg_info_addr2line(dbg_info, instr_ptr, &file, &line);
|
||||
if (file == stepping_file && line == stepping_line) {
|
||||
if (stepping == STEPPING_INTO)
|
||||
dbg_set_step_into_breakpoint();
|
||||
else if (stepping == STEPPING_OVER)
|
||||
dbg_set_step_over_breakpoint();
|
||||
dbg_continue();
|
||||
}
|
||||
}
|
||||
stepping = NOT_STEPPING;
|
||||
stepping_file = stepping_line = 0;
|
||||
if (dbg_info)
|
||||
dbg_print_current_line();
|
||||
else
|
||||
dbg_print_vm_state();
|
||||
} else {
|
||||
puts("Entering the debugger REPL. Type 'help' for a list of commands.");
|
||||
}
|
||||
while (1) {
|
||||
char *line = cmd_gets();
|
||||
if (line)
|
||||
|
||||
@@ -176,6 +176,7 @@ endif
|
||||
if get_option('debugger').allowed()
|
||||
add_project_arguments('-DDEBUGGER_ENABLED', language : 'c')
|
||||
xsystem4 += 'debug.c'
|
||||
xsystem4 += 'debug_info.c'
|
||||
xsystem4 += 'debugger_cmd.c'
|
||||
xsystem4 += 'debugger_dap.c'
|
||||
if chibi.found()
|
||||
|
||||
+8
-1
@@ -371,6 +371,7 @@ static void usage(void)
|
||||
#ifdef DEBUGGER_ENABLED
|
||||
puts(" --nodebug Disable debugger");
|
||||
puts(" --debug Start in debugger");
|
||||
puts(" --debug-info Specify the path to the debug information file");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -402,6 +403,7 @@ enum {
|
||||
LOPT_NODEBUG,
|
||||
LOPT_DEBUG,
|
||||
LOPT_DEBUG_API,
|
||||
LOPT_DEBUG_INFO,
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -424,6 +426,7 @@ int main(int argc, char *argv[])
|
||||
char *font_fnl = NULL;
|
||||
char *joypad = NULL;
|
||||
char *savedir = NULL;
|
||||
char *debug_info_path = NULL;
|
||||
|
||||
while (1) {
|
||||
static struct option long_options[] = {
|
||||
@@ -443,6 +446,7 @@ int main(int argc, char *argv[])
|
||||
{ "nodebug", no_argument, 0, LOPT_NODEBUG },
|
||||
{ "debug", no_argument, 0, LOPT_DEBUG },
|
||||
{ "debug-api", no_argument, 0, LOPT_DEBUG_API },
|
||||
{ "debug-info", required_argument, 0, LOPT_DEBUG_INFO },
|
||||
#endif
|
||||
{ 0 }
|
||||
};
|
||||
@@ -520,6 +524,9 @@ int main(int argc, char *argv[])
|
||||
dbg_dap = true;
|
||||
sys_silent = true;
|
||||
break;
|
||||
case LOPT_DEBUG_INFO:
|
||||
debug_info_path = optarg;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -584,6 +591,6 @@ int main(int argc, char *argv[])
|
||||
if (config.msgskip_delay)
|
||||
set_msgskip_delay(ain, config.msgskip_delay);
|
||||
asset_manager_init();
|
||||
dbg_init();
|
||||
dbg_init(debug_info_path);
|
||||
sys_exit(vm_execute_ain(ain));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user