mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-10-01 11:18:22 +03:00
450 lines
12 KiB
C
450 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 <stdio.h>
|
|
#include <string.h>
|
|
#include "system.h"
|
|
#include "debugger.h"
|
|
#include "debugger_private.h"
|
|
#include "debug_symbol.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_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},
|
|
{"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,
|
|
};
|