Merge pull request #210 from kichikuou/debugger

debugger_cmd: Add support for source-level debugging
This commit is contained in:
Nunuhara Cabbage
2024-11-10 07:32:29 -08:00
committed by GitHub
6 changed files with 343 additions and 17 deletions
+14 -2
View File
@@ -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
View File
@@ -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));
}
+213
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -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
View File
@@ -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));
}