Files
kichikuou_xsystem35-sdl2/src/debugger.c
T
kichikuou 4b15f45897 debugger: Add custom DAP messages for communicating palette changes
This implements "xsystem35.palette" custom request and
"xsystem35.paletteChanged" custom event.

The "xsystem35.palette" request retrieves the color values of the AGS
palette.

  interface PaletteRequest extends Request {
    command: 'xsystem35.palette';
  }

  interface PaletteResponse extends Response {
    body: {
      /**
       * A number that increases each time the palette changes.
       */
      version: number;

      /**
       * 256 element array containing color values in 0xRRGGBB format.
       */
      palette: number[];
    };
  }

The "xsystem35.paletteChanged" event indicates that the AGS palette has
changed.

  interface PaletteChangedEvent extends Event {
    event: 'xsystem35.paletteChanged';

    body: {
      /**
       * A number that increases each time the palette changes.
       */
      version: number;
    };
  }

DAP clients can know if the received value is up to date by comparing
the `version` values of paletteChanged and PaletteResponse.
2023-12-08 08:35:45 +09:00

546 lines
12 KiB
C

/*
* Copyright (C) 2021 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include "debugger.h"
#include "debugger_private.h"
#include "debug_symbol.h"
#include "system.h"
#include "ald_manager.h"
#include "scenario.h"
#include "nact.h"
#include "variable.h"
#define INTERNAL_BREAKPOINT_NO -1
DebuggerState dbg_state = DBG_RUNNING;
DebuggerImpl *dbg_impl;
struct debug_symbols *symbols;
static Breakpoint *breakpoints = NULL;
static int next_breakpoint_no = 1;
static Breakpoint *internal_breakpoint;
// Used in DBG_STOPPED_STEP and DBG_STOPPED_NEXT
static struct {
int page;
int line;
} step_exec_state;
void dbg_init(const char *symbols_path, boolean use_dap) {
dbg_impl = use_dap ? &dbg_dap_impl : &dbg_cui_impl;
dbg_impl->init(symbols_path);
}
void dbg_quit(void) {
if (dbg_impl)
dbg_impl->quit();
}
int dbg_lookup_var(const char *name) {
if (symbols)
return dsym_lookup_variable(symbols, name);
// If debug information is not available, lookup from System39.ain.
if (nact->ain.var) {
for (int i = 0; i < nact->ain.varnum; i++) {
if (!strcmp(name, nact->ain.var[i]))
return i;
}
}
return -1;
}
static const char *eval_input;
static jmp_buf eval_jmp_buf;
static char *eval_result;
static size_t eval_result_size;
static int eval_expr(void);
static void eval_error(char *format, ...) {
va_list args;
va_start(args, format);
vsnprintf(eval_result, eval_result_size, format, args);
va_end(args);
longjmp(eval_jmp_buf, 1);
}
static char next_char(void) {
while (isspace(*eval_input))
eval_input++;
return *eval_input;
}
static boolean consume(char c) {
if (next_char() != c)
return false;
eval_input++;
return true;
}
static void expect(char c) {
if (next_char() != c)
eval_error("syntax error");
eval_input++;
}
static int clamp(int val) {
return val > 0xffff ? 0xffff
: val < 0 ? 0
: val;
}
static boolean is_identifier(uint8_t c) {
return isalnum(c) || !isascii(c) || c == '_' || c == '.';
}
static int parse_number(void) {
int base = 10;
if (eval_input[0] == '0' && tolower(eval_input[1]) == 'x') {
base = 16;
eval_input += 2;
} else if (eval_input[0] == '0' && tolower(eval_input[1]) == 'b') {
base = 2;
eval_input += 2;
}
char *p;
long val = strtol(eval_input, &p, base);
eval_input = p;
return clamp(val);
}
static int eval_variable(void) {
const char *top = eval_input;
while (is_identifier(*eval_input))
eval_input++;
if (top == eval_input)
eval_error("syntax error");
char *buf = alloca(eval_input - top + 1);
strncpy(buf, top, eval_input - top);
buf[eval_input - top] = '\0';
int var = dbg_lookup_var(buf);
if (var < 0)
eval_error("unknown variable \"%s\"", buf);
int *store;
if (consume('[')) {
int index = eval_expr();
expect(']');
store = v_ref_indexed(var, index, NULL);
} else {
store = v_ref(var, NULL);
}
if (!store)
eval_error("out of bounds array access");
return *store;
}
static int eval_prim(void) {
if (consume('(')) {
int val = eval_expr();
expect(')');
return val;
}
if (isdigit(next_char()))
return parse_number();
return eval_variable();
}
static int eval_mul(void) {
int val = eval_prim();
for (;;) {
if (consume('*')) {
val = clamp(val * eval_prim());
} else if (consume('/')) {
int rhs = eval_prim();
val = rhs ? val / rhs : 0;
} else if (consume('%')) {
int rhs = eval_prim();
val = rhs ? val % rhs : 0;
} else {
break;
}
}
return val;
}
static int eval_add(void) {
int val = eval_mul();
for (;;) {
if (consume('+'))
val = clamp(val + eval_mul());
else if (consume('-'))
val = clamp(val - eval_mul());
else
break;
}
return val;
}
static int eval_bit(void) {
int val = eval_add();
for (;;) {
if (consume('&'))
val = val & eval_add();
else if (consume('|'))
val = val | eval_add();
else if (consume('^'))
val = val ^ eval_add();
else
break;
}
return val;
}
static int eval_compare(void) {
int val = eval_bit();
for (;;) {
if (consume('<')) {
if (consume('='))
val = val <= eval_bit() ? 1 : 0;
else
val = val < eval_bit() ? 1 : 0;
} else if (consume('>')) {
if (consume('='))
val = val >= eval_bit() ? 1 : 0;
else
val = val > eval_bit() ? 1 : 0;
} else {
break;
}
}
return val;
}
static int eval_expr(void) {
int val = eval_compare();
for (;;) {
if (consume('=')) {
val = val == eval_compare() ? 1 : 0;
} else if (consume('\\')) {
val = val != eval_compare() ? 1 : 0;
} else {
break;
}
}
return val;
}
static int eval_condition(const char *expr) {
eval_input = expr;
eval_result = NULL;
eval_result_size = 0;
if (!setjmp(eval_jmp_buf)) {
return eval_expr();
} else {
return 0;
}
}
boolean dbg_evaluate(const char *expr, char *result, size_t result_size) {
eval_input = expr;
eval_result = result;
eval_result_size = result_size;
if (!setjmp(eval_jmp_buf)) {
int val = eval_expr();
expect('\0');
snprintf(result, result_size, "%d", val);
return true;
} else {
return false;
}
}
static PhysicalBreakpoint *find_physical_breakpoint(int page, int addr) {
for (Breakpoint *bp = breakpoints; bp; bp = bp->next) {
if (bp->phys->page == page && bp->phys->addr == addr)
return bp->phys;
}
return NULL;
}
Breakpoint *dbg_set_breakpoint(int page, int addr, boolean is_internal) {
dridata *dfile = ald_getdata(DRIFILE_SCO, page);
if (!dfile)
return NULL;
if (addr < 0 || addr >= dfile->size) {
ald_freedata(dfile);
return NULL;
}
PhysicalBreakpoint *phys;
if (dfile->data[addr] == BREAKPOINT) {
phys = find_physical_breakpoint(page, addr);
if (!phys)
SYSERROR("Illegal BREAKPOINT instruction");
phys->refcnt++;
} else {
phys = calloc(1, sizeof(PhysicalBreakpoint));
phys->page = page;
phys->addr = addr;
phys->refcnt = 1;
phys->restore_op = dfile->data[addr];
}
Breakpoint *bp = calloc(1, sizeof(Breakpoint));
bp->no = is_internal ? INTERNAL_BREAKPOINT_NO : next_breakpoint_no++;
bp->phys = phys;
bp->dfile = dfile;
bp->next = breakpoints;
breakpoints = bp;
dfile->data[addr] = BREAKPOINT;
return bp;
}
boolean dbg_set_breakpoint_condition(Breakpoint *bp, const char *condition, char *err, size_t errsize) {
if (!dbg_evaluate(condition, err, errsize))
return false;
if (bp->condition)
free(bp->condition);
bp->condition = strdup(condition);
return true;
}
static Breakpoint *breakpoint_free(Breakpoint *bp) {
assert(bp->dfile->data[bp->phys->addr] == BREAKPOINT);
assert(bp->phys->refcnt > 0);
if (--bp->phys->refcnt == 0) {
bp->dfile->data[bp->phys->addr] = bp->phys->restore_op;
free(bp->phys);
}
if (bp->condition)
free(bp->condition);
ald_freedata(bp->dfile);
Breakpoint *next = bp->next;
free(bp);
return next;
}
boolean dbg_delete_breakpoint(int no) {
for (Breakpoint **p = &breakpoints; *p; p = &(*p)->next) {
if ((*p)->no == no) {
*p = breakpoint_free(*p);
return true;
}
}
return false;
}
void dbg_delete_breakpoints_in_page(int page) {
Breakpoint **p = &breakpoints;
while (*p) {
if ((*p)->phys->page == page)
*p = breakpoint_free(*p);
else
p = &(*p)->next;
}
}
uint8_t dbg_handle_breakpoint(int page, int addr) {
uint8_t restore_op = BREAKPOINT;
for (Breakpoint *bp = breakpoints; bp; bp = bp->next) {
if (bp->phys->page != page || bp->phys->addr != addr)
continue;
restore_op = bp->phys->restore_op;
if (bp->condition && !eval_condition(bp->condition))
continue;
dbg_state = bp->no == INTERNAL_BREAKPOINT_NO ?
DBG_STOPPED_NEXT : DBG_STOPPED_BREAKPOINT;
dbg_main(bp->no); // this may destroy bp
return restore_op;
}
if (restore_op == BREAKPOINT)
SYSERROR("Illegal BREAKPOINT instruction");
return restore_op;
}
static void set_stack_frame(StackFrame *frame, int page, int addr, boolean is_return_addr) {
frame->page = page;
frame->addr = addr;
frame->src = dsym_page2src(symbols, page);
// For return addresses, search with (addr - 1) so that the function call's
// line number will be returned.
frame->line = dsym_addr2line(symbols, page, is_return_addr ? addr - 1 : addr);
frame->name = dsym_addr2func(symbols, page, is_return_addr ? addr - 1 : addr);
}
StackTrace *dbg_stack_trace(void) {
int page = nact->current_page;
int cap = 16;
StackTrace *trace = malloc(sizeof(StackTrace) + cap * sizeof(StackFrame));
set_stack_frame(&trace->frames[0], page, nact->current_addr, false);
trace->nr_frame = 1;
struct stack_frame_info *sfi = NULL;
while ((sfi = sl_next_stack_frame(sfi)) != NULL) {
if (sfi->tag != STACK_NEARCALL && sfi->tag != STACK_FARCALL)
continue;
if (trace->nr_frame >= cap) {
cap *= 2;
trace = realloc(trace, sizeof(StackTrace) + cap * sizeof(StackFrame));
}
if (sfi->tag == STACK_FARCALL)
page = sfi->page;
set_stack_frame(&trace->frames[trace->nr_frame++], page, sfi->addr, true);
}
return trace;
}
void dbg_stepin(void) {
step_exec_state.page = nact->current_page;
step_exec_state.line = dsym_addr2line(symbols, step_exec_state.page, nact->current_addr);
dbg_state = DBG_STOPPED_STEP;
}
static boolean should_continue_step(void) {
if (step_exec_state.line < 0)
return false; // line info was not available
return step_exec_state.page == nact->current_page
&& step_exec_state.line == dsym_addr2line(symbols, step_exec_state.page, nact->current_addr);
}
void dbg_stepout(void) {
// Set an internal breakpoint at the return address of current frame.
struct stack_frame_info *sfi = NULL;
while ((sfi = sl_next_stack_frame(sfi)) != NULL) {
switch (sfi->tag) {
case STACK_NEARCALL:
internal_breakpoint = dbg_set_breakpoint(nact->current_page, sfi->addr, true);
return;
case STACK_FARCALL:
internal_breakpoint = dbg_set_breakpoint(sfi->page, sfi->addr, true);
return;
}
}
// No parent frame found, continue execution.
}
static int get_retaddr_if_funcall(void) {
int orig_addr = sl_getIndex();
int retaddr = -1;
assert(nact->current_page == sl_getPage());
sl_jmpNear(nact->current_addr);
int c0 = sl_getc();
if (c0 == BREAKPOINT) {
PhysicalBreakpoint *bp = find_physical_breakpoint(nact->current_page, nact->current_addr);
if (bp)
c0 = bp->restore_op;
else
WARNING("Illegal BREAKPOINT instruction");
}
switch (c0) {
case '%':
if (getCaliValue() != 0)
retaddr = sl_getIndex();
break;
case '\\':
if (sl_getaddr() != 0)
retaddr = sl_getIndex();
break;
case '~':
{
int page = sl_getw();
if (page != 0 && page != 0xffff) {
sl_getaddr();
retaddr = sl_getIndex();
}
}
break;
}
sl_jmpNear(orig_addr);
return retaddr;
}
static void do_next(void) {
assert(dbg_state == DBG_STOPPED_NEXT);
int retaddr = get_retaddr_if_funcall();
if (retaddr >= 0) {
internal_breakpoint = dbg_set_breakpoint(step_exec_state.page, retaddr, true);
dbg_state = DBG_RUNNING;
}
}
void dbg_next(void) {
step_exec_state.page = nact->current_page;
step_exec_state.line = dsym_addr2line(symbols, step_exec_state.page, nact->current_addr);
dbg_state = DBG_STOPPED_NEXT;
do_next();
}
static boolean should_continue_next(void) {
if (!should_continue_step())
return false;
do_next();
return true;
}
void dbg_main(int bp_no) {
if (internal_breakpoint) {
dbg_delete_breakpoint(INTERNAL_BREAKPOINT_NO);
internal_breakpoint = NULL;
}
switch (dbg_state) {
case DBG_STOPPED_STEP:
if (should_continue_step())
return;
break;
case DBG_STOPPED_NEXT:
if (should_continue_next())
return;
break;
default:
break;
}
dbg_impl->repl(bp_no);
}
void dbg_onsleep(void) {
if (dbg_impl)
dbg_impl->onsleep();
}
void dbg_on_palette_change(void) {
if (dbg_impl)
dbg_impl->on_palette_change();
}
boolean dbg_console_vprintf(int lv, const char *format, va_list ap) {
if (!dbg_impl || !dbg_impl->console_output)
return false;
char buf[1024];
vsnprintf(buf, sizeof(buf), format, ap);
dbg_impl->console_output(lv, buf);
return true;
}