/* * Copyright (C) 2021 kichikuou * * 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 #include #include #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 #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) { // :
if (sscanf(str, "%i:%i", page, addr) == 2) return true; // : 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; // 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; } // 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 : [if ]\n" " break [if ]\n" " break :
[if ]\n" " break [if ]"; 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 ..."; 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 \n" " list :\n" " list \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 "; 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 "; 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, };