Files
kichikuou_xsystem35-sdl2/src/debugger_cui.c
T
kichikuou 6f01734215 SDL: Add event and input compatibility
Map SDL2 and SDL3 event names, keyboard fields, touch queries, joystick
device identifiers, message boxes, and text input APIs through the common
interface.  Keep the deferred touch deadline on the millisecond clock so
SDL3's nanosecond event timestamps are not mixed with SDL_GetTicks().
2026-09-22 15:33:51 +09:00

483 lines
13 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "system.h"
#include "debugger.h"
#include "debugger_private.h"
#include "debug_symbol.h"
#include "ald_manager.h"
#include "cg.h"
#include "font.h"
#include "nact.h"
#include "variable.h"
#ifdef HAVE_SIGACTION
#include <signal.h>
#endif
#ifdef _WIN32
#include "win/console.h"
#endif
#define CMD_BUFFER_SIZE 256
static const char whitespaces[] = " \t\r\n";
static char default_command[CMD_BUFFER_SIZE];
typedef enum { CONTINUE_REPL, EXIT_REPL } CommandResult;
static const char *format_address(int page, int addr) {
static char buf[100];
const char *src = dsym_page2src(symbols, page);
int line = dsym_addr2line(symbols, page, addr);
const char *func = dsym_addr2func(symbols, page, addr);
if (src && line > 0) {
if (func)
snprintf(buf, sizeof(buf), "%s:%d in %s", src, line, func);
else
snprintf(buf, sizeof(buf), "%s:%d", src, line);
} else {
snprintf(buf, sizeof(buf), "%d:0x%x", page, addr);
}
return buf;
}
static 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 = dsym_src2page(symbols, filename);
if (*page < 0) {
printf("No source file named %s.\n", filename);
return false;
}
*addr = dsym_line2addr(symbols, *page, line_no);
if (*addr < 0) {
printf("No line %d in file %s.\n", line_no, filename);
return false;
}
return true;
}
*page = nact->current_page;
// <linenum>
char *endptr;
line_no = strtol(str, &endptr, 0);
if (!*endptr) {
*addr = dsym_line2addr(symbols, nact->current_page, line_no);
if (*addr < 0) {
printf("No line %d in current file.\n", line_no);
return false;
}
return true;
}
// <funcname>
if (dsym_func2addr(symbols, str, page, addr))
return true;
return false;
}
static bool print_source_line(int page, int addr) {
int line_no = dsym_addr2line(symbols, page, addr);
if (line_no <= 0)
return false;
const char *line = dsym_source_line(symbols, page, line_no);
if (!line)
return false;
printf("%s:%d\t%s\n", dsym_page2src(symbols, page), line_no, line);
return true;
}
static const char desc_backtrace[] = "Print backtrace of stack frames.";
static const char * const help_backtrace = NULL;
static CommandResult cmd_backtrace(void) {
StackTrace *trace = dbg_stack_trace();
for (int i = 0; i < trace->nr_frame; i++) {
StackFrame *frame = &trace->frames[i];
if (frame->src && frame->line > 0) {
if (frame->name)
printf("\t%s:%d in %s\n", frame->src, frame->line, frame->name);
else
printf("\t%s:%d\n", frame->src, frame->line);
} else {
printf("\t%d:0x%x\n", frame->page, frame->addr);
}
}
free(trace);
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>]\n"
" break <function> [if <condition>]";
static CommandResult cmd_break(void) {
char *token = strtok(NULL, whitespaces);
int page, addr;
if (!token || !parse_address(token, &page, &addr)) {
puts(help_break);
return CONTINUE_REPL;
}
Breakpoint *bp = dbg_set_breakpoint(page, addr, false);
if (!bp) {
printf("Failed to set breakpoint at %d:0x%x: invalid address\n", page, addr);
return CONTINUE_REPL;
}
token = strtok(NULL, whitespaces);
if (token && !strcmp(token, "if")) {
char *condition = strtok(NULL, "");
if (!condition)
condition = "";
char errmsg[256];
if (!dbg_set_breakpoint_condition(bp, condition, errmsg, sizeof(errmsg))) {
puts(errmsg);
dbg_delete_breakpoint(bp->no);
return CONTINUE_REPL;
}
}
printf("Breakpoint %d at %s\n", bp->no, format_address(bp->phys->page, bp->phys->addr));
return CONTINUE_REPL;
}
static const char desc_continue[] = "Continue program execution.";
static const char * const help_continue = NULL;
static CommandResult cmd_continue(void) {
return EXIT_REPL;
}
static const char desc_delete[] = "Delete breakpoints.";
static const char help_delete[] =
"Syntax: delete <breakpoint_no>...";
static CommandResult cmd_delete(void) {
char *arg = strtok(NULL, 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 (!dbg_delete_breakpoint(bp_no))
printf("No breakpoint number %d.\n", bp_no);
}
arg = strtok(NULL, whitespaces);
}
return CONTINUE_REPL;
}
static const char desc_help[] = "Print list of commands.";
static const char * const help_help = NULL;
static CommandResult cmd_help(void);
static const char desc_info[] = "Print information about the program.";
static const char help_info[] =
"Syntax: info cache\n"
"\n"
"Displays usage and lookup statistics for each cache.";
static void print_cache_stats(const char *name, CacheStats stats) {
size_t lookups = stats.hits + stats.misses;
double hit_rate = lookups ? 100.0 * stats.hits / lookups : 0.0;
printf("%-8s %8zu %12zu %12zu %8zu %8zu %8.1f%%\n",
name, stats.count, stats.size, stats.capacity,
stats.hits, stats.misses, hit_rate);
}
static CommandResult cmd_info(void) {
char *arg = strtok(NULL, whitespaces);
if (!arg || strcmp(arg, "cache") || strtok(NULL, whitespaces)) {
puts(help_info);
return CONTINUE_REPL;
}
puts("Cache Entries Size Capacity Hits Misses Hit rate");
print_cache_stats("Glyph", font_get_cache_stats());
print_cache_stats("CG", cg_get_cache_stats());
print_cache_stats("Archive", ald_get_cache_stats());
return CONTINUE_REPL;
}
static const char desc_list[] = "List specified function or line.";
static const char help_list[] =
"Syntax: list\n"
" list <linenum>\n"
" list <filename>:<linenum>\n"
" list <function>\n"
"\n"
"With no argument, lists ten lines around current location.";
static CommandResult cmd_list(void) {
char *arg = strtok(NULL, whitespaces);
int page, addr;
if (arg) {
if (!parse_address(arg, &page, &addr)) {
puts(help_list);
return CONTINUE_REPL;
}
} else {
page = nact->current_page;
addr = nact->current_addr;
}
int line_no = dsym_addr2line(symbols, 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 = dsym_source_line(symbols, page, line_no + i);
if (!line) {
if (i == 0)
printf("No source text for %s:%d\n", dsym_page2src(symbols, page), line_no);
break;
}
printf("%d\t%s\n", line_no + i, line);
}
// Empty REPL input will print the next 10 lines.
sprintf(default_command, "l %s:%d", dsym_page2src(symbols, 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 = NULL;
static CommandResult cmd_step(void) {
dbg_stepin();
return EXIT_REPL;
}
static const char desc_finish[] = "Execute until current function returns.";
static const char * const help_finish = NULL;
static CommandResult cmd_finish(void) {
dbg_stepout();
return EXIT_REPL;
}
static const char desc_next[] = "Step program, proceeding through subroutine calls.";
static const char * const help_next = NULL;
static CommandResult cmd_next(void) {
dbg_next();
return EXIT_REPL;
}
static const char desc_print[] = "Print value of expression.";
static const char help_print[] =
"Syntax: print <expr>";
static CommandResult cmd_print(void) {
char *expr = strtok(NULL, "");
if (!expr) {
puts(help_print);
return CONTINUE_REPL;
}
char buf[256];
dbg_evaluate(expr, buf, sizeof(buf));
puts(buf);
return CONTINUE_REPL;
}
static const char desc_quit[] = "Exit " PACKAGE ".";
static const char * const help_quit = NULL;
static CommandResult cmd_quit(void) {
sys_exit(0);
return EXIT_REPL;
}
static const char desc_string[] = "Print value of string variable.";
static const char help_string[] =
"Syntax: string <index>";
static CommandResult cmd_string(void) {
char *arg = strtok(NULL, whitespaces);
if (!arg) {
puts(help_string);
return CONTINUE_REPL;
}
while (arg) {
int no = atoi(arg);
if (no < 1) {
printf("Bad string index %s\n", arg);
} else {
char *utf = toUTF8(svar_get(no));
printf("string[%d] = \"%s\"\n", no, utf); // TODO: escaping
free(utf);
}
arg = strtok(NULL, whitespaces);
}
return CONTINUE_REPL;
}
static void sigint_handler(int sig_num) {
dbg_state = DBG_STOPPED_INTERRUPT;
}
static void dbg_cui_init(const char *symbols_path) {
symbols = dsym_load(symbols_path);
#ifdef HAVE_SIGACTION
sys_set_signalhandler(SIGINT, sigint_handler);
#endif
#ifdef _WIN32
win_alloc_console();
win_set_ctrl_c_handler(sigint_handler);
#endif
dbg_state = DBG_STOPPED_ENTRY;
}
static void dbg_cui_quit(void) {
}
typedef struct {
const char *name;
const char *alias;
const char *description;
const char *help;
CommandResult (*func)(void);
} Command;
const Command dbg_cui_commands[] = {
{"backtrace", "bt", desc_backtrace, help_backtrace, cmd_backtrace},
{"break", "b", desc_break, help_break, cmd_break},
{"continue", "c", desc_continue, help_continue, cmd_continue},
{"delete", "d", desc_delete, help_delete, cmd_delete},
{"help", "h", desc_help, help_help, cmd_help},
{"info", "i", desc_info, help_info, cmd_info},
{"list", "l", desc_list, help_list, cmd_list},
{"step", "s", desc_step, help_step, cmd_step},
{"finish", NULL, desc_finish, help_finish, cmd_finish},
{"next", "n", desc_next, help_next, cmd_next},
{"print", "p", desc_print, help_print, cmd_print},
{"quit", "q", desc_quit, help_quit, cmd_quit},
{"string", "str", desc_string, help_string, cmd_string},
{NULL} // terminator
};
const Command *find_command(const char *str) {
for (const Command *cmd = dbg_cui_commands; cmd->name; cmd++) {
if (!strcmp(str, cmd->name) || (cmd->alias && !strcmp(str, cmd->alias)))
return cmd;
}
printf("Unknown command \"%s\". Try \"help\".\n", str);
return NULL;
}
static 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);
}
static CommandResult cmd_help(void) {
char *token = strtok(NULL, whitespaces);
if (!token) {
puts("List of commands:");
puts("");
for (const Command *cmd = dbg_cui_commands; cmd->name; cmd++)
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 void dbg_cui_repl(int bp_no) {
if (dbg_state == DBG_STOPPED_BREAKPOINT && bp_no)
printf("Breakpoint %d\n", bp_no);
if (!print_source_line(nact->current_page, nact->current_addr))
printf("Stopped at %s\n", format_address(nact->current_page, nact->current_addr));
dbg_state = DBG_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 (cmd->func() == EXIT_REPL)
return;
}
}
}
static void dbg_cui_onsleep(void) {
if (dbg_state == DBG_STOPPED_INTERRUPT)
dbg_main(0);
}
DebuggerImpl dbg_cui_impl = {
.init = dbg_cui_init,
.quit = dbg_cui_quit,
.repl = dbg_cui_repl,
.onsleep = dbg_cui_onsleep,
};