diff --git a/include/debugger.h b/include/debugger.h index d990a99..705c00f 100644 --- a/include/debugger.h +++ b/include/debugger.h @@ -20,6 +20,17 @@ #include #include "system4/instructions.h" +#include "xsystem4.h" + +#define DBG_ERROR(fmt, ...) sys_warning("ERROR: " fmt "\n", ##__VA_ARGS__) + +struct breakpoint { + enum opcode restore_op; + void *data; + void (*cb)(struct breakpoint*); + char *message; + int count; +}; extern bool dbg_enabled; @@ -27,7 +38,19 @@ void dbg_init(void); void dbg_fini(void); void dbg_repl(void); +void dbg_continue(void); +void dbg_quit(void); +void dbg_start(void(*fun)(void*), void *data); +void dbg_cmd_repl(void); enum opcode dbg_handle_breakpoint(unsigned bp_no); +bool dbg_set_function_breakpoint(const char *_name, void(*cb)(struct breakpoint*), void *data); +bool dbg_set_address_breakpoint(uint32_t address, void(*cb)(struct breakpoint*), void *data); +void dbg_print_stack_trace(void); +#ifdef HAVE_SCHEME +void dbg_scm_init(void); +void dbg_scm_fini(void); +void dbg_scm_repl(void); +#endif /* HAVE_SCHEME */ #endif /* DEBUGGER_ENABLED */ #endif /* SYSTEM4_DEBUGGER_H */ diff --git a/include/xsystem4.h b/include/xsystem4.h index de6265b..ebd17c3 100644 --- a/include/xsystem4.h +++ b/include/xsystem4.h @@ -43,6 +43,13 @@ struct config { extern struct config config; +// Convenience functions to avoid cumbersome memory management when passing +// text to printf-like functions +const char *display_sjis0(const char *sjis); +const char *display_sjis1(const char *sjis); +const char *display_utf0(const char *utf); +const char *display_utf1(const char *utf); + char *unix_path(const char *path); char *gamedir_path(const char *path); char *savedir_path(const char *path); diff --git a/meson.build b/meson.build index a281424..f9be7ed 100644 --- a/meson.build +++ b/meson.build @@ -24,7 +24,8 @@ else gl_deps = [gl, glew] endif -chibi = dependency('chibi-scheme', required : get_option('debugger')) +chibi = dependency('chibi-scheme', required : false) +readline = dependency('readline', required : false) flex = find_program('flex') bison = find_program('bison') diff --git a/src/debug-ffi.c b/src/debug-ffi.c index 1212d0c..3b8e20e 100644 --- a/src/debug-ffi.c +++ b/src/debug-ffi.c @@ -72,7 +72,7 @@ sexp sexp_get_page_stub (sexp ctx, sexp self, sexp_sint_t n, sexp arg0) { sexp sexp_backtrace_stub (sexp ctx, sexp self, sexp_sint_t n) { sexp res; - res = ((print_stack_trace()), SEXP_VOID); + res = ((dbg_print_stack_trace()), SEXP_VOID); return res; } diff --git a/src/debug-ffi.stub b/src/debug-ffi.stub index beb1fbf..255df88 100644 --- a/src/debug-ffi.stub +++ b/src/debug-ffi.stub @@ -82,7 +82,7 @@ (int varno variable-varno) (vm_value value variable-vm-value)) -(define-c void (backtrace print_stack_trace) ()) +(define-c void (backtrace dbg_print_stack_trace) ()) (define-c (maybe-null page) (get-page get_page) (int)) (define-c (maybe-null page) (frame-page frame_page) (int)) diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 0000000..8ecbd02 --- /dev/null +++ b/src/debug.c @@ -0,0 +1,178 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 . + */ + +#define VM_PRIVATE + +#include +#include +#include +#include +#include +#include + +#include "system4/string.h" +#include "system4/utfsjis.h" + +#include "vm.h" +#include "vm/heap.h" +#include "vm/page.h" + +#include "debugger.h" +#include "little_endian.h" +#include "xsystem4.h" + +static jmp_buf dbg_continuation; + +enum { + DBG_CONTINUE = 1, + DBG_QUIT = 2, +}; + +void dbg_continue(void) +{ + longjmp(dbg_continuation, DBG_CONTINUE); +} + +void dbg_quit(void) +{ + longjmp(dbg_continuation, DBG_QUIT); +} + +void dbg_start(void(*fun)(void*), void *data) +{ + switch (setjmp(dbg_continuation)) { + case 0: + break; + case DBG_CONTINUE: + return; + case DBG_QUIT: + vm_exit(0); + default: + ERROR("setjmp returned unexpected value"); + } + fun(data); +} + +static void _dbg_repl(void *_) +{ + dbg_cmd_repl(); +} + +void dbg_repl(void) +{ + if (!dbg_enabled) + return; + dbg_start(_dbg_repl, NULL); +} + +void dbg_init(void) +{ +#ifdef HAVE_SCHEME + dbg_scm_init(); +#endif +} + +void dbg_fini(void) +{ +#ifdef HAVE_SCHEME + dbg_scm_fini(); +#endif +} + +static struct breakpoint *breakpoints = NULL; +static unsigned nr_breakpoints = 0; + +bool dbg_set_function_breakpoint(const char *_name, void(*cb)(struct breakpoint*), void *data) +{ + char *name = utf2sjis(_name, 0); + int fno = ain_get_function(ain, name); + free(name); + + if (fno < 0) { + DBG_ERROR("No function with name '%s'", _name); + return false; + } + + struct ain_function *f = &ain->functions[fno]; + breakpoints = xrealloc_array(breakpoints, nr_breakpoints, nr_breakpoints+1, sizeof(struct breakpoint)); + breakpoints[nr_breakpoints].restore_op = LittleEndian_getW(ain->code, f->address); + breakpoints[nr_breakpoints].cb = cb; + breakpoints[nr_breakpoints].data = data; + breakpoints[nr_breakpoints].message = xmalloc(512); + snprintf(breakpoints[nr_breakpoints].message, 511, "Hit breakpoint at function '%s' (0x%08x)", _name, f->address); + LittleEndian_putW(ain->code, f->address, BREAKPOINT + nr_breakpoints); + nr_breakpoints++; + + NOTICE("Set breakpoint at function '%s' (0x%08x)", _name, f->address); + return true; +} + +bool dbg_set_address_breakpoint(uint32_t address, void(*cb)(struct breakpoint*), void *data) +{ + if (address & 1 || address >= ain->code_size - 2) { + DBG_ERROR("Invalid address: 0x%08x", address); + return false; + } + + // XXX: this is not very robust; some illegal addresses will slip through + // if the data looks like a valid opcode + enum opcode op = LittleEndian_getW(ain->code, address); + if (op < 0 || op >= NR_OPCODES) { + DBG_ERROR("Invalid address: 0x%08x", address); + return false; + } + + breakpoints = xrealloc_array(breakpoints, nr_breakpoints, nr_breakpoints+1, sizeof(struct breakpoint)); + breakpoints[nr_breakpoints].restore_op = op; + breakpoints[nr_breakpoints].cb = cb; + breakpoints[nr_breakpoints].data = data; + breakpoints[nr_breakpoints].message = xmalloc(512); + snprintf(breakpoints[nr_breakpoints].message, 511, "Hit breakpoint at 0x%08x", address); + LittleEndian_putW(ain->code, address, BREAKPOINT + nr_breakpoints); + nr_breakpoints++; + + NOTICE("Set breakpoint at 0x%08x", address); + return true; +} + +static void _dbg_handle_breakpoint(void *data) +{ + unsigned bp_no = *((unsigned*)data); + if (breakpoints[bp_no].cb) { + breakpoints[bp_no].cb(&breakpoints[bp_no]); + } else { + NOTICE("%s", breakpoints[bp_no].message); + dbg_cmd_repl(); + } +} + +enum opcode dbg_handle_breakpoint(unsigned bp_no) +{ + assert(bp_no < nr_breakpoints); + dbg_start(_dbg_handle_breakpoint, &bp_no); + return breakpoints[bp_no].restore_op; +} + +void dbg_print_stack_trace(void) +{ + for (int i = call_stack_ptr - 1, j = 0; i >= 0; i--, j++) { + struct ain_function *f = &ain->functions[call_stack[i].fno]; + char *u = sjis2utf(f->name, strlen(f->name)); + uint32_t addr = (i == call_stack_ptr - 1) ? instr_ptr : call_stack[i+1].call_address; + NOTICE("#%d 0x%08x in %s", j, addr, u); + free(u); + } +} diff --git a/src/debugger_cmd.c b/src/debugger_cmd.c new file mode 100644 index 0000000..94d1282 --- /dev/null +++ b/src/debugger_cmd.c @@ -0,0 +1,351 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 . + */ + +#define VM_PRIVATE + +#include +#include +#include +#include +#include +#include + +#include "system4/string.h" +#include "system4/utfsjis.h" + +#include "vm.h" +#include "vm/heap.h" +#include "vm/page.h" + +#include "debugger.h" +#include "little_endian.h" +#include "xsystem4.h" + +struct dbg_cmd { + const char *fullname; + const char *shortname; + const char *description; + unsigned min_args; + unsigned max_args; + void (*run)(unsigned nr_args, char **args); +}; + +static void dbg_cmd_help(unsigned nr_args, char **args); + +static void dbg_cmd_backtrace(unsigned nr_args, char **args) +{ + dbg_print_stack_trace(); +} + +static void dbg_cmd_breakpoint(unsigned nr_args, char **args) +{ + int addr = atoi(args[0]); + if (addr > 0) { + dbg_set_address_breakpoint(addr, NULL, NULL); + } else { + dbg_set_function_breakpoint(args[0], NULL, NULL); + } +} + +static void dbg_cmd_continue(unsigned nr_args, char **args) +{ + dbg_continue(); +} + +static struct string *value_to_string(struct ain_type *type, union vm_value value, int recursive) +{ + switch (type->data) { + case AIN_INT: + return integer_to_string(value.i); + case AIN_FLOAT: + return float_to_string(value.f, 6); + break; + case AIN_STRING: { + struct string *out = cstr_to_string("\""); + string_append(&out, heap_get_string(value.i)); + string_push_back(&out, '"'); + return out; + } + case AIN_STRUCT: + case AIN_REF_STRUCT: { + struct page *page = heap_get_page(value.i); + if (page->nr_vars == 0) { + return cstr_to_string("{}"); + } + if (!recursive) { + return cstr_to_string("{ <...> }"); + } + struct string *out = cstr_to_string("{ "); + for (int i = 0; i < page->nr_vars; i++) { + struct ain_variable *m = &ain->structures[type->struc].members[i]; + if (i) { + string_append_cstr(&out, "; ", 2); + } + string_append_cstr(&out, m->name, strlen(m->name)); + string_append_cstr(&out, " = ", 3); + struct string *tmp = value_to_string(&m->type, page->values[i], recursive-1); + string_append(&out, tmp); + free_string(tmp); + } + string_append_cstr(&out, " }", 2); + return out; + } + case AIN_ARRAY_TYPE: + case AIN_REF_ARRAY_TYPE: { + struct page *page = heap_get_page(value.i); + if (!page || page->nr_vars == 0) { + return cstr_to_string("[]"); + } + // get member type + struct ain_type t; + t.data = variable_type(page, 0, &t.struc, &t.rank); + t.array_type = NULL; + if (!recursive) { + switch (t.data) { + case AIN_STRUCT: + case AIN_REF_STRUCT: + case AIN_ARRAY_TYPE: + case AIN_REF_ARRAY_TYPE: + return cstr_to_string("[ <...> ]"); + default: + break; + } + } + struct string *out = cstr_to_string("[ "); + for (int i = 0; i < page->nr_vars; i++) { + if (i) { + string_append_cstr(&out, "; ", 2); + } + struct string *tmp = value_to_string(&t, page->values[i], recursive-1); + string_append(&out, tmp); + free_string(tmp); + } + string_append_cstr(&out, " ]", 2); + return out; + } + default: + return cstr_to_string(""); + } + return string_ref(&EMPTY_STRING); +} + +static void dbg_cmd_locals(unsigned nr_args, char **args) +{ + unsigned frame_no = nr_args > 0 ? atoi(args[0]) : 0; + if (frame_no < 0 || frame_no >= call_stack_ptr) { + DBG_ERROR("Invalid frame number: %u", frame_no); + return; + } + + struct page *page = heap_get_page(call_stack[call_stack_ptr - (frame_no+1)].page_slot); + struct ain_function *f = &ain->functions[page->index]; + for (int i = 0; i < f->nr_vars; i++) { + struct string *value = value_to_string(&f->vars[i].type, page->values[i], 1); + printf("[%d] %s: %s\n", i, display_sjis0(f->vars[i].name), display_sjis1(value->text)); + free_string(value); + } +} + +static void log_handler(struct breakpoint *bp) +{ + struct ain_function *f = bp->data; + struct page *locals = local_page(); + printf("%s(", display_sjis0(f->name)); + for (int i = 0; i < f->nr_args; i++) { + struct string *value = value_to_string(&f->vars[i].type, locals->values[i], 1); + printf(i > 0 ? ", %s" : "%s", display_sjis0(value->text)); + free_string(value); + } + printf(")\n"); +} + +static void dbg_cmd_log(unsigned nr_args, char **args) +{ + int fno = ain_get_function(ain, args[0]); + if (fno < 0) { + DBG_ERROR("No function with name '%s'", args[0]); + return; + } + dbg_set_function_breakpoint(args[0], log_handler, &ain->functions[fno]); +} + +struct ain_variable *dbg_get_variable(const char *name, union vm_value *val_out) +{ + struct page *page = local_page(); + struct ain_function *f = &ain->functions[page->index]; + for (int i = 0; i < f->nr_vars; i++) { + if (!strcmp(f->vars[i].name, name)) { + *val_out = page->values[i]; + return &f->vars[i]; + } + } + for (int i = 0; i < ain->nr_globals; i++) { + if (!strcmp(ain->globals[i].name, name)) { + *val_out = global_get(i); + return &ain->globals[i]; + } + } + return NULL; +} + +static void dbg_cmd_print(unsigned nr_args, char **args) +{ + union vm_value value; + struct ain_variable *var = dbg_get_variable(args[0], &value); + if (var) { + struct string *v = value_to_string(&var->type, value, 1); + printf("%s\n", display_sjis0(v->text)); + free_string(v); + return; + } + // TODO: print functions, etc. + DBG_ERROR("Undefined variable: %s", args[0]); +} + +static void dbg_cmd_quit(unsigned nr_args, char **args) +{ + dbg_quit(); +} + +#ifdef HAVE_SCHEME +static void dbg_cmd_scheme(unsigned nr_args, char **args) +{ + dbg_scm_repl(); +} +#endif + +static struct dbg_cmd dbg_commands[] = { + { "help", "h", "[command-name] - Display this message", 0, 1, dbg_cmd_help }, + { "backtrace", "bt", "- Display stack trace", 0, 0, dbg_cmd_backtrace }, + { "breakpoint", "bp", " - Set breakpoint", 1, 1, dbg_cmd_breakpoint }, + { "continue", "c", "- Resume execution", 0, 0, dbg_cmd_continue }, + { "locals", "l", "[frame-number] - Print local variables", 0, 1, dbg_cmd_locals }, + { "log", NULL, " - Log function calls", 1, 1, dbg_cmd_log }, + { "print", "p", " - Print a variable", 1, 1, dbg_cmd_print }, + { "quit", "q", "- Quit xsystem4", 0, 0, dbg_cmd_quit }, +#ifdef HAVE_SCHEME + { "scheme", "scm", "- Drop into Scheme REPL", 0, 0, dbg_cmd_scheme }, +#endif +}; + +static void dbg_cmd_help(unsigned nr_args, char **args) +{ + NOTICE("Available Commands"); + NOTICE("------------------"); + for (unsigned i = 0; i < sizeof(dbg_commands)/sizeof(*dbg_commands); i++) { + struct dbg_cmd *cmd = &dbg_commands[i]; + if (!nr_args || !strcmp(args[0], cmd->fullname) || !strcmp(args[0], cmd->shortname)) { + if (cmd->shortname) + NOTICE("%s (%s) %s", cmd->fullname, cmd->shortname, cmd->description); + else + NOTICE("%s %s", cmd->fullname, cmd->description); + } + } +} + +/* + * Get a command by name. + */ +static struct dbg_cmd *dbg_get_command(const char *name) +{ + for (unsigned i = 0; i < sizeof(dbg_commands)/sizeof(*dbg_commands); i++) { + struct dbg_cmd *cmd = &dbg_commands[i]; + if (!strcmp(name, cmd->fullname) || (cmd->shortname && !strcmp(name, cmd->shortname))) + return cmd; + } + return NULL; +} + +#ifdef HAVE_READLINE +#include +#include +/* + * Get a string from the user. The result can be modified but shouldn't be free'd. + */ +static char *cmd_gets(void) +{ + static char *line_read = NULL; + free(line_read); + line_read = readline("dbg(cmd)> "); + if (line_read && *line_read) + add_history(line_read); + return line_read; +} +#else +/* + * Get a string from the user. The result can be modified but shouldn't be free'd. + */ +static char *cmd_gets(void) +{ + static char line[1024]; + + printf("dbg(cmd)> "); + fflush(stdout); + fgets(line, 1024, stdin); + return line; +} +#endif + +/* + * Parse a line into an array of words. Modified the input string. + */ +static char **cmd_parse(char *line, unsigned *nr_words) +{ + static char *words[32]; + + *nr_words = 0; + while (*line) { + // skip whitespace + while (*line && isspace(*line)) line++; + if (!*line) break; + // save word ptr + words[(*nr_words)++] = line; + // skip word + while (*line && !isspace(*line)) line++; + if (!*line) break; + *line = '\0'; + line++; + } + return words; +} + +void dbg_cmd_repl(void) +{ + NOTICE("Entering the debugger REPL. Type 'help' for a list of commands."); + while (1) { + char *line = cmd_gets(); + if (!line) + continue; + + unsigned nr_words; + char **words = cmd_parse(line, &nr_words); + if (!nr_words) + continue; + + struct dbg_cmd *cmd = dbg_get_command(words[0]); + if (!cmd) { + NOTICE("Invalid command: %s (type 'help' for a list of commands)", words[0]); + continue; + } + + if ((nr_words-1) < cmd->min_args || (nr_words-1) > cmd->max_args) { + NOTICE("Wrong number of arguments to '%s' command", cmd->fullname); + continue; + } + + cmd->run(nr_words-1, words+1); + } +} diff --git a/src/debugger.c b/src/debugger_scm.c similarity index 63% rename from src/debugger.c rename to src/debugger_scm.c index 0655051..72da3da 100644 --- a/src/debugger.c +++ b/src/debugger_scm.c @@ -31,8 +31,6 @@ #include "debugger.h" #include "little_endian.h" -#define SCM_ERROR(fmt, ...) sys_warning("ERROR: " fmt "\n", ##__VA_ARGS__) - bool dbg_enabled = true; struct variable { @@ -131,85 +129,53 @@ static struct variable *page_ref(struct page *page, int i) return v; } -struct breakpoint { - enum opcode restore_op; - sexp handler; - char *message; - int count; -}; - -static struct breakpoint *breakpoints = NULL; -static unsigned nr_breakpoints = 0; - static sexp dbg_ctx; -static void set_function_breakpoint(const char *_name, sexp handler) +static void set_parameter(const char *param, sexp value) { - if (!sexp_procedurep(handler) && handler != SEXP_FALSE) { - SCM_ERROR("Breakpoint handler must be a procedure or #f"); + sexp_eval(dbg_ctx, sexp_list2(dbg_ctx, sexp_intern(dbg_ctx, param, -1), value), NULL); +} + +static void dbg_scm_breakpoint_handler(struct breakpoint *bp) +{ + set_parameter("breakpoint-count", sexp_make_integer(dbg_ctx, bp->count++)); + // TODO: breakpoint-function parameter with function name, argument count, etc. + + if ((sexp)bp->data == SEXP_FALSE) { + NOTICE("%s", bp->message); + dbg_scm_repl(); + } else { + sexp_apply(dbg_ctx, (sexp)bp->data, SEXP_NULL); + } +} + +static void set_function_breakpoint(const char *name, sexp handler) +{ + if (handler == SEXP_FALSE) { + dbg_set_function_breakpoint(name, NULL, NULL); return; } - - char *name = utf2sjis(_name, 0); - int fno = ain_get_function(ain, name); - free(name); - - if (fno < 0) { - SCM_ERROR("No function with name '%s'", _name); + if (!sexp_procedurep(handler)) { + DBG_ERROR("Breakpoint handler must be a procedure or #f"); return; } - - struct ain_function *f = &ain->functions[fno]; - breakpoints = xrealloc_array(breakpoints, nr_breakpoints, nr_breakpoints+1, sizeof(struct breakpoint)); - breakpoints[nr_breakpoints].restore_op = LittleEndian_getW(ain->code, f->address); - breakpoints[nr_breakpoints].handler = handler; - if (handler != SEXP_FALSE) + if (dbg_set_function_breakpoint(name, dbg_scm_breakpoint_handler, handler)) { sexp_preserve_object(dbg_ctx, handler); - breakpoints[nr_breakpoints].message = xmalloc(512); - snprintf(breakpoints[nr_breakpoints].message, 511, "Hit breakpoint at function '%s' (0x%08x)", _name, f->address); - LittleEndian_putW(ain->code, f->address, BREAKPOINT + nr_breakpoints); - nr_breakpoints++; + } } static void set_address_breakpoint(uint32_t address, sexp handler) { - if (!sexp_procedurep(handler) && handler != SEXP_FALSE) { - SCM_ERROR("Breakpoint handler must be a procedure or #f"); + if (handler == SEXP_FALSE) { + dbg_set_address_breakpoint(address, NULL, NULL); return; } - - if (address & 1 || address >= ain->code_size - 2) { - SCM_ERROR("Invalid instruction address: 0x%08x", address); + if (!sexp_procedurep(handler)) { + DBG_ERROR("Breakpoint handler must be a procedure or #f"); return; } - - // XXX: this is not very robust; some illegal addresses will slip through - // if the data looks like a valid opcode - enum opcode op = LittleEndian_getW(ain->code, address); - if (op < 0 || op >= NR_OPCODES) { - SCM_ERROR("Invalid instruction address: 0x%08x", address); - return; - } - - breakpoints = xrealloc_array(breakpoints, nr_breakpoints, nr_breakpoints+1, sizeof(struct breakpoint)); - breakpoints[nr_breakpoints].restore_op = op; - breakpoints[nr_breakpoints].handler = handler; - if (handler != SEXP_FALSE) + if (dbg_set_address_breakpoint(address, dbg_scm_breakpoint_handler, handler)) { sexp_preserve_object(dbg_ctx, handler); - breakpoints[nr_breakpoints].message = xmalloc(512); - snprintf(breakpoints[nr_breakpoints].message, 511, "Hit breakpoint at 0x%08x", address); - LittleEndian_putW(ain->code, address, BREAKPOINT + nr_breakpoints); - nr_breakpoints++; -} - -static void print_stack_trace(void) -{ - for (int i = call_stack_ptr - 1, j = 0; i >= 0; i--, j++) { - struct ain_function *f = &ain->functions[call_stack[i].fno]; - char *u = sjis2utf(f->name, strlen(f->name)); - uint32_t addr = (i == call_stack_ptr - 1) ? instr_ptr : call_stack[i+1].call_address; - NOTICE("#%d 0x%08x in %s", j, addr, u); - free(u); } } @@ -227,7 +193,7 @@ static struct page *frame_page(int i) // autogenerated with `chibi-ffi src/debug-ffi.stub` #include "debug-ffi.c" -void dbg_init(void) +void dbg_scm_init(void) { if (!dbg_enabled) return; @@ -237,7 +203,7 @@ void dbg_init(void) sexp_init_library(dbg_ctx, NULL, 0, sexp_context_env(dbg_ctx), sexp_version, SEXP_ABI_IDENTIFIER); sexp_eval_string(dbg_ctx, "(import (scheme base))", -1, NULL); sexp_eval_string(dbg_ctx, "(import (chibi repl))", -1, NULL); - sexp_eval_string(dbg_ctx, "(define *prompt* (make-parameter \"dbg> \"))", -1, NULL); + sexp_eval_string(dbg_ctx, "(define *prompt* (make-parameter \"dbg(scm)> \"))", -1, NULL); sexp_eval_string(dbg_ctx, "(define breakpoint-count (make-parameter 0))", -1, NULL); sexp_eval_string(dbg_ctx, "(define (repl-make-prompt m) (*prompt*))", -1, NULL); @@ -247,38 +213,19 @@ void dbg_init(void) sexp_eval_string(dbg_ctx, "(load \"" XSYS4_DATA_DIR "/debugger.scm\")", -1, NULL); } -void dbg_fini(void) +void dbg_scm_fini(void) { if (!dbg_enabled) return; sexp_destroy_context(dbg_ctx); } -void dbg_repl(void) +void dbg_scm_repl(void) { if (!dbg_enabled) return; + NOTICE("Entering the Scheme debugger REPL. Type '\\exit' to exit."); sexp_eval_string(dbg_ctx, "(repl 'escape: #\\\\ 'make-prompt: repl-make-prompt)", -1, NULL); } -static void set_parameter(const char *param, sexp value) -{ - sexp_eval(dbg_ctx, sexp_list2(dbg_ctx, sexp_intern(dbg_ctx, param, -1), value), NULL); -} -enum opcode dbg_handle_breakpoint(unsigned bp_no) -{ - assert(bp_no < nr_breakpoints); - - set_parameter("breakpoint-count", sexp_make_integer(dbg_ctx, breakpoints[bp_no].count++)); - // TODO: breakpoint-function parameter with function name, argument count, etc. - - if (breakpoints[bp_no].handler == SEXP_FALSE) { - NOTICE("%s", breakpoints[bp_no].message); - dbg_repl(); - } else { - sexp_apply(dbg_ctx, breakpoints[bp_no].handler, SEXP_NULL); - } - - return breakpoints[bp_no].restore_op; -} diff --git a/src/meson.build b/src/meson.build index 3f360d4..b6ac1ac 100644 --- a/src/meson.build +++ b/src/meson.build @@ -25,6 +25,7 @@ xsystem4 = [version_h, 'sprite.c', 'system4.c', 'text.c', + 'util.c', 'video.c', 'vm.c', @@ -101,10 +102,19 @@ xsystem4 = [version_h, xsystem4_deps = [libm, zlib, sdl2, sdl2ttf, ffi, cglm, sndfile, libsys4_dep] + gl_deps -if chibi.found() - xsystem4_deps += chibi - xsystem4 += 'debugger.c' +if get_option('debugger').allowed() add_project_arguments('-DDEBUGGER_ENABLED', language : 'c') + xsystem4 += 'debug.c' + xsystem4 += 'debugger_cmd.c' + if chibi.found() + xsystem4_deps += chibi + xsystem4 += 'debugger_scm.c' + add_project_arguments('-DHAVE_SCHEME', language : 'c') + endif + if readline.found() + xsystem4_deps += readline + add_project_arguments('-DHAVE_READLINE', language : 'c') + endif endif executable('xsystem4', xsystem4, diff --git a/src/system4.c b/src/system4.c index 9dc5d5a..52aea56 100644 --- a/src/system4.c +++ b/src/system4.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include "system4.h" @@ -61,75 +60,6 @@ struct config config = { .ex_path = NULL, }; -char *unix_path(const char *path) -{ - char *utf = sjis2utf(path, strlen(path)); - for (int i = 0; utf[i]; i++) { - if (utf[i] == '\\') - utf[i] = '/'; - } - return utf; -} - -static bool is_absolute_path(const char *path) -{ -#if (defined(_WIN32) || defined(__WIN32__)) - int i = (isalpha(path[0]) && path[1] == ':') ? 2 : 0; - return path[i] == '/' || path[i] == '\\'; -#else - return path[0] == '/'; -#endif -} - -static char *resolve_path(const char *dir, const char *path) -{ - char *utf = unix_path(path); - if (is_absolute_path(utf)) - return utf; - - char *resolved = xmalloc(strlen(dir) + strlen(utf) + 2); - strcpy(resolved, dir); - strcat(resolved, "/"); - strcat(resolved, utf); - - free(utf); - return resolved; -} - -char *gamedir_path(const char *path) -{ - return resolve_path(config.game_dir, path); -} - -char *savedir_path(const char *path) -{ - return resolve_path(config.save_dir, path); -} - -void get_date(int *year, int *month, int *mday, int *wday) -{ - time_t t = time(NULL); - struct tm *tm = localtime(&t); - - *year = tm->tm_year; - *month = tm->tm_mon; - *mday = tm->tm_mday; - *wday = tm->tm_wday; -} - -void get_time(int *hour, int *min, int *sec, int *ms) -{ - time_t t = time(NULL); - struct tm *tm = localtime(&t); - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - - *hour = tm->tm_hour; - *min = tm->tm_min; - *sec = ts.tv_sec; - *ms = ts.tv_nsec / 1000000; -} - static struct string *ini_string(struct ini_entry *entry) { if (entry->value.type != INI_STRING) @@ -381,8 +311,8 @@ static void usage(void) puts(" --font-mincho Specify the path to the mincho font to use"); puts(" --font-gothic Specify the path to the gothic font to use"); puts(" -j, --joypad Enable joypad"); - puts(" --nodebug Disable debugger"); #ifdef DEBUGGER_ENABLED + puts(" --nodebug Disable debugger"); puts(" --debug Start in debugger"); #endif } @@ -395,8 +325,8 @@ enum { LOPT_FONT_MINCHO, LOPT_FONT_GOTHIC, LOPT_JOYPAD, - LOPT_NODEBUG, #ifdef DEBUGGER_ENABLED + LOPT_NODEBUG, LOPT_DEBUG, #endif }; @@ -423,10 +353,11 @@ int main(int argc, char *argv[]) { "font-mincho", required_argument, 0, LOPT_FONT_MINCHO }, { "font-gothic", required_argument, 0, LOPT_FONT_GOTHIC }, { "joypad", no_argument, 0, LOPT_JOYPAD }, - { "nodebug", no_argument, 0, LOPT_NODEBUG }, #ifdef DEBUGGER_ENABLED + { "nodebug", no_argument, 0, LOPT_NODEBUG }, { "debug", no_argument, 0, LOPT_DEBUG }, #endif + { 0 } }; int option_index = 0; int c = getopt_long(argc, argv, "haejv", long_options, &option_index); @@ -467,9 +398,6 @@ int main(int argc, char *argv[]) case LOPT_DEBUG: start_in_debugger = true; break; -#else - case LOPT_NODEBUG: - break; #endif } } diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..d9e53a1 --- /dev/null +++ b/src/util.c @@ -0,0 +1,147 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 . + */ + +#include +#include +#include + +#include "system4.h" +#include "system4/utfsjis.h" + +#include "xsystem4.h" + +// In a Windows environment, console output should be SJIS-encoded +// (assuming the user is using Japanese non-unicode locale). +#if (defined(_WIN32) || defined(__WIN32)) +#define NATIVE_SJIS +#endif + +const char *display_sjis0(const char *sjis) +{ +#ifdef NATIVE_SJIS + return sjis; +#else + static char *utf = NULL; + free(utf); + utf = sjis2utf(sjis, 0); + return utf; +#endif +} + +const char *display_sjis1(const char *sjis) +{ +#ifdef NATIVE_SJIS + return sjis; +#else + static char *utf = NULL; + free(utf); + utf = sjis2utf(sjis, 0); + return utf; +#endif +} + +const char *display_utf0(const char *utf) +{ +#ifdef NATIVE_SJIS + static char *sjis = NULL; + free(sjis); + sjis = utf2sjis(utf, 0); + return sjis; +#else + return utf; +#endif +} + +const char *display_utf1(const char *utf) +{ +#ifdef NATIVE_SJIS + static char *sjis = NULL; + free(sjis); + sjis = utf2sjis(utf, 0); + return sjis; +#else + return utf; +#endif +} + +char *unix_path(const char *path) +{ + char *utf = sjis2utf(path, strlen(path)); + for (int i = 0; utf[i]; i++) { + if (utf[i] == '\\') + utf[i] = '/'; + } + return utf; +} + +static bool is_absolute_path(const char *path) +{ +#if (defined(_WIN32) || defined(__WIN32__)) + int i = (isalpha(path[0]) && path[1] == ':') ? 2 : 0; + return path[i] == '/' || path[i] == '\\'; +#else + return path[0] == '/'; +#endif +} + +static char *resolve_path(const char *dir, const char *path) +{ + char *utf = unix_path(path); + if (is_absolute_path(utf)) + return utf; + + char *resolved = xmalloc(strlen(dir) + strlen(utf) + 2); + strcpy(resolved, dir); + strcat(resolved, "/"); + strcat(resolved, utf); + + free(utf); + return resolved; +} + +char *gamedir_path(const char *path) +{ + return resolve_path(config.game_dir, path); +} + +char *savedir_path(const char *path) +{ + return resolve_path(config.save_dir, path); +} + +void get_date(int *year, int *month, int *mday, int *wday) +{ + time_t t = time(NULL); + struct tm *tm = localtime(&t); + + *year = tm->tm_year; + *month = tm->tm_mon; + *mday = tm->tm_mday; + *wday = tm->tm_wday; +} + +void get_time(int *hour, int *min, int *sec, int *ms) +{ + time_t t = time(NULL); + struct tm *tm = localtime(&t); + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + + *hour = tm->tm_hour; + *min = tm->tm_min; + *sec = ts.tv_sec; + *ms = ts.tv_nsec / 1000000; +}