--- hatari/src/debug/debugui.c 2019/04/09 08:53:06 1.1.1.6 +++ hatari/src/debug/debugui.c 2019/04/09 08:59:20 1.1.1.11 @@ -13,6 +13,7 @@ const char DebugUI_fileid[] = "Hatari de #include #include #include +#include #include "config.h" @@ -29,6 +30,7 @@ const char DebugUI_fileid[] = "Hatari de #include "m68000.h" #include "memorySnapShot.h" #include "options.h" +#include "reset.h" #include "screen.h" #include "statusbar.h" #include "str.h" @@ -43,8 +45,7 @@ const char DebugUI_fileid[] = "Hatari de #include "evaluate.h" #include "history.h" #include "symbols.h" - -int bExceptionDebugging; +#include "vars.h" FILE *debugOutput; @@ -57,6 +58,9 @@ static char lastResult[10]; /* parse debugger commands from here on init */ static const char *parseFileName; +/* to which directory to change after (potentially recursed) scripts parsing finishes */ +static char *finalDir; + /** * Save/Restore snapshot of debugging session variables @@ -113,17 +117,25 @@ static void DebugUI_SetLogDefault(void) */ static int DebugUI_SetLogFile(int nArgc, char *psArgs[]) { - File_Close(debugOutput); - debugOutput = NULL; + if (debugOutput != stderr) + { + fprintf(stderr, "Debug log closed.\n"); + File_Close(debugOutput); + } + debugOutput = stderr; if (nArgc > 1) - debugOutput = File_Open(psArgs[1], "w"); - - if (debugOutput) - fprintf(stderr, "Debug log '%s' opened.\n", psArgs[1]); - else - debugOutput = stderr; - + { + if ((debugOutput = File_Open(psArgs[1], "w"))) + { + fprintf(stderr, "Debug log '%s' opened.\n", psArgs[1]); + } + else + { + fprintf(stderr, "Debug log '%s' opening FAILED.\n", psArgs[1]); + debugOutput = stderr; + } + } return DEBUGGER_CMDDONE; } @@ -140,7 +152,7 @@ static void DebugUI_PrintValue(Uint32 va ones = false; for (bit = 31; bit >= 0; bit--) { - one = value & (1< 2) + return DebugUI_PrintCmdHelp(psArgv[0]); + + if (nArgc == 2) + exitval = atoi(psArgv[1]); + else + exitval = 0; + + ConfigureParams.Log.bConfirmQuit = false; + Main_RequestQuit(exitval); return DEBUGGER_END; } @@ -426,7 +495,7 @@ static int DebugUI_QuitEmu(int nArgc, ch /** * Print help text for one command */ -void DebugUI_PrintCmdHelp(const char *psCmd) +int DebugUI_PrintCmdHelp(const char *psCmd) { dbgcommand_t *cmd; int i; @@ -457,11 +526,12 @@ void DebugUI_PrintCmdHelp(const char *ps fprintf(stderr, "Usage: %s %s\n", bShort ? cmd->sShortName : cmd->sLongName, cmd->sUsage); - return; + return DEBUGGER_CMDDONE; } } fprintf(stderr, "Unknown command '%s'\n", psCmd); + return DEBUGGER_CMDDONE; } @@ -474,8 +544,7 @@ static int DebugUI_Help(int nArgc, char if (nArgc > 1) { - DebugUI_PrintCmdHelp(psArgs[1]); - return DEBUGGER_CMDDONE; + return DebugUI_PrintCmdHelp(psArgs[1]); } for (i = 0; i < debugCommands; i++) @@ -559,23 +628,29 @@ static int DebugUI_ParseCommand(const ch delim = " \t"; /* Separate arguments and put the pointers into psArgs */ - for (nArgc = 1; nArgc < ARRAYSIZE(psArgs); nArgc++) + for (nArgc = 1; nArgc < ARRAY_SIZE(psArgs); nArgc++) { psArgs[nArgc] = strtok(NULL, delim); if (psArgs[nArgc] == NULL) break; } - - if (!debugOutput) { - /* make sure also calls from control.c work */ - DebugUI_SetLogDefault(); + if (nArgc >= ARRAY_SIZE(psArgs)) + { + fprintf(stderr, "Error: too many arguments (currently up to %d supported)\n", + ARRAY_SIZE(psArgs)); + retval = DEBUGGER_CMDCONT; + } + else + { + /* ... and execute the function */ + retval = debugCommand[i].pFunction(nArgc, psArgs); } - - /* ... and execute the function */ - retval = debugCommand[i].pFunction(nArgc, psArgs); /* Save commando string if it can be repeated */ if (retval == DEBUGGER_CMDCONT) - strncpy(sLastCmd, psArgs[0], sizeof(sLastCmd)); + { + if (psArgs[0] != sLastCmd) + strlcpy(sLastCmd, psArgs[0], sizeof(sLastCmd)); + } else sLastCmd[0] = '\0'; free(input); @@ -586,6 +661,29 @@ static int DebugUI_ParseCommand(const ch /* See "info:readline" e.g. in Konqueror for readline usage. */ /** + * Generic readline match callback helper. + * STATE = 0 -> different text from previous one. + * Return next match or NULL if no matches. + */ +char *DebugUI_MatchHelper(const char **strings, int items, const char *text, int state) +{ + static int i, len; + + if (!state) + { + /* first match */ + len = strlen(text); + i = 0; + } + /* next match */ + while (i < items) { + if (strncasecmp(strings[i++], text, len) == 0) + return (strdup(strings[i-1])); + } + return NULL; +} + +/** * Readline match callback for long command name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. @@ -634,10 +732,10 @@ static char **DebugUI_Completion(const c size_t len; /* check where's the first word (ignore white space) */ - while (start < rl_point && isspace(rl_line_buffer[start])) + while (start < rl_point && isspace((unsigned char)rl_line_buffer[start])) start++; end = start; - while (end < rl_point && !isspace(rl_line_buffer[end])) + while (end < rl_point && !isspace((unsigned char)rl_line_buffer[end])) end++; if (end >= rl_point) @@ -697,9 +795,8 @@ static char **DebugUI_Completion(const c /** * Add non-repeated command to readline history * and free the given string - * @return its new value */ -static char *DebugUI_FreeCommand(char *input) +static void DebugUI_FreeCommand(char *input) { if (input && *input) { @@ -711,7 +808,6 @@ static char *DebugUI_FreeCommand(char *i } free(input); } - return NULL; } /** @@ -724,26 +820,50 @@ static char *DebugUI_FreeCommand(char *i */ static char *DebugUI_GetCommand(char *input) { + /* We need this indirection for libedit's rl_readline_name which is + * not declared as "const char *" (i.e. this is necessary for macOS) */ + static char hatari_readline_name[] = "Hatari"; + /* Allow conditional parsing of the ~/.inputrc file. */ - rl_readline_name = "Hatari"; + rl_readline_name = hatari_readline_name; /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = DebugUI_Completion; - input = DebugUI_FreeCommand(input); + DebugUI_FreeCommand(input); return Str_Trim(readline("> ")); } +/** + * Get readlines idea of the terminal size + */ +static void DebugUI_GetScreenSize(int *rows, int *cols) +{ + rl_get_screen_size(rows, cols); +} + #else /* !HAVE_LIBREADLINE */ /** * Free Command input string - * @return its new value */ -static char *DebugUI_FreeCommand(char *input) +static void DebugUI_FreeCommand(char *input) { - if (input) - free(input); - return NULL; + free(input); +} + +/** + * Get number of lines/columns for terminal output + */ +static void DebugUI_GetScreenSize(int *rows, int *cols) +{ + const char *p; + + *rows = 24; + *cols = 80; + if ((p = getenv("LINES")) != NULL) + *rows = (int)strtol(p, NULL, 0); + if ((p = getenv("COLUMS")) != NULL) + *cols = (int)strtol(p, NULL, 0); } /** @@ -772,6 +892,29 @@ static char *DebugUI_GetCommand(char *in #endif /* !HAVE_LIBREADLINE */ +/** + * How many lines to "page" when user invokes calling command. + * + * If config value is >=0, use that. If it's negative, get number of lines + * from screensize. If even that's not defined, fall back to default value. + * + * @return Number of lines to output at the time. + */ +int DebugUI_GetPageLines(int config, int defvalue) +{ + int rows, cols; + + if (config >= 0) { + return config; + } + DebugUI_GetScreenSize(&rows, &cols); + /* leave 1 line for pager prompt */ + if (--rows > 0) { + return rows; + } + return defvalue; +} + static const dbgcommand_t uicommand[] = { @@ -780,17 +923,18 @@ static const dbgcommand_t uicommand[] = { DebugUI_ChangeDir, NULL, "cd", "", "change directory", - "\n" - "\tChange Hatari work directory.", + " [-f]\n" + "\tChange Hatari work directory. With '-f', directory is\n" + "\tchanged only after all script files have been parsed.", false }, - { DebugUI_Evaluate, Symbols_MatchCpuAddress, + { DebugUI_Evaluate, Vars_MatchCpuVariable, "evaluate", "e", "evaluate an expression", "\n" "\tEvaluate an expression and show the result. Expression can\n" - "\tinclude CPU register and symbol names, those are replaced\n" - "\tby their values. Supported operators in expressions are,\n" - "\tin the decending order of precedence:\n" + "\tinclude CPU register & symbol and Hatari variable names.\n" + "\tThose are replaced by their values. Supported operators in\n" + "\texpressions are, in the decending order of precedence:\n" "\t\t(), +, -, ~, *, /, +, -, >>, <<, ^, &, |\n" "\tParenthesis will fetch a _long_ value from the address\n" "\tto what the value inside it evaluates to. Prefixes can be\n" @@ -806,14 +950,15 @@ static const dbgcommand_t uicommand[] = "[command]\n" "\tPrint help text for available commands.", false }, - { History_Parse, NULL, + { History_Parse, History_Match, "history", "hi", "show last CPU/DSP PC values & executed instructions", - "cpu|dsp|on|off|\n" - "\t'cpu' and 'dsp' tracks instruction history for just given\n" - "\t processor, 'on' tracks them both, 'off' will disable history.\n" - "\tCount will show (at max) given number of last saved PC values\n" - "\tand instructions at corresponding RAM addresses.", + "cpu|dsp|on|off| [limit]|save \n" + "\t'cpu' and 'dsp' enable instruction history tracking for just given\n" + "\tprocessor, 'on' tracks them both, 'off' will disable history.\n" + "\tOptional 'limit' will set how many past instructions are tracked.\n" + "\tGiving just count will show (at max) given number of last saved PC\n" + "\tvalues and instructions currently at corresponding RAM addresses.", false }, { DebugInfo_Command, DebugInfo_MatchInfo, "info", "i", @@ -840,16 +985,29 @@ static const dbgcommand_t uicommand[] = "parse", "p", "get debugger commands from file", "[filename]\n" - "\tRead debugger commands from given file and do them.", + "\tRead debugger commands from given file and do them.\n" + "\tCurrent directory is script directory during this.\n" + "\tTo specify directory to be used also for breakpoint\n" + "\tscripts execution, use '-f' option for 'cd' command.", + false }, + { DebugUI_Rename, NULL, + "rename", "", + "rename given file", + "old new\n" + "\tRenames file with 'old' name to 'new'.", + false }, + { DebugUI_Reset, DebugUI_MatchReset, + "reset", "", + "reset emulation", + "\n", false }, { DebugUI_SetOptions, Opt_MatchOption, "setopt", "o", "set Hatari command line and debugger options", "[bin|dec|hex|]\n" - "\tSet Hatari options. For example to enable exception catching,\n" - "\tuse following command line option: 'setopt --debug'. Special\n" - "\t'bin', 'dec' and 'hex' arguments change the default number base\n" - "\tused in debugger.", + "\tSpecial 'bin', 'dec' and 'hex' arguments change the default\n" + "\tnumber base used in debugger. lists available command\n" + "\tline options, 'setopt --help' their descriptions.", false }, { DebugUI_DoMemorySnap, NULL, "stateload", "", @@ -871,11 +1029,18 @@ static const dbgcommand_t uicommand[] = "\tsettings. For example, to enable CPU disassembly and VBL\n" "\ttracing, use:\n\t\ttrace cpu_disasm,video_hbl", false }, + { Vars_List, NULL, + "variables", "v", + "List builtin symbols / variables", + "\n" + "\tList Hatari debugger builtin symbols / variables and their values.\n" + "\tThey're accepted by breakpoints and evaluate command.", + false }, { DebugUI_QuitEmu, NULL, "quit", "q", "quit emulator", - "\n" - "\tLeave debugger and quit emulator.", + "[exit value]\n" + "\tLeave debugger and quit emulator with given exit value.", false } }; @@ -892,6 +1057,9 @@ void DebugUI_Init(void) if (debugCommands) return; + if (!debugOutput) + DebugUI_SetLogDefault(); + /* if you want disassembly or memdumping to start/continue from * specific address, you can set them in these functions. */ @@ -899,7 +1067,7 @@ void DebugUI_Init(void) cpucmds = DebugCpu_Init(&cpucmd); /* on first time copy the command structures to a single table */ - debugCommands = ARRAYSIZE(uicommand); + debugCommands = ARRAY_SIZE(uicommand); debugCommand = malloc(sizeof(dbgcommand_t) * (dspcmds + cpucmds + debugCommands)); assert(debugCommand); @@ -942,12 +1110,27 @@ void DebugUI(debug_reason_t reason) static const char *welcome = "\n----------------------------------------------------------------------" "\nYou have entered debug mode. Type c to continue emulation, h for help.\n"; + static bool recursing; + + if (recursing) + { + fprintf(stderr, "WARNING: recursive call to DebugUI (through profiler debug option?)!\n"); + recursing = false; + return; + } + recursing = true; History_Mark(reason); if (bInFullScreen) Screen_ReturnFromFullScreen(); + /* Make sure mouse isn't grabbed regardless of where + * this is invoked from. E.g. returning from fullscreen + * enables grab if that was enabled on windowed mode. + */ + SDL_WM_GrabInput(SDL_GRAB_OFF); + DebugUI_Init(); if (welcome) @@ -957,13 +1140,14 @@ void DebugUI(debug_reason_t reason) } DebugCpu_InitSession(); DebugDsp_InitSession(); + Symbols_LoadCurrentProgram(); DebugInfo_ShowSessionInfo(); /* override paused message so that user knows to look into console * on how to continue in case he invoked the debugger by accident. */ Statusbar_AddMessage("Console Debugger", 100); - Statusbar_Update(sdlscrn); + Statusbar_Update(sdlscrn, true); /* disable normal GUI alerts while on console */ alertLevel = Log_SetAlertLevel(LOG_FATAL); @@ -989,13 +1173,14 @@ void DebugUI(debug_reason_t reason) while (cmdret != DEBUGGER_END); /* free exit command */ - psCmd = DebugUI_FreeCommand(psCmd); + DebugUI_FreeCommand(psCmd); Log_SetAlertLevel(alertLevel); - DebugUI_SetLogDefault(); DebugCpu_SetDebugging(); DebugDsp_SetDebugging(); + + recursing = false; } @@ -1006,6 +1191,8 @@ void DebugUI(debug_reason_t reason) */ bool DebugUI_ParseFile(const char *path, bool reinit) { + int recurse; + static int recursing; char *olddir, *dir, *cmd, *input, *expanded, *slash; FILE *fp; @@ -1032,8 +1219,7 @@ bool DebugUI_ParseFile(const char *path, if (chdir(dir) != 0) { perror("ERROR"); - if (olddir) - free(olddir); + free(olddir); free(dir); fclose(fp); return false; @@ -1042,6 +1228,9 @@ bool DebugUI_ParseFile(const char *path, } free(dir); + recurse = recursing; + recursing = true; + input = NULL; for (;;) { @@ -1068,6 +1257,7 @@ bool DebugUI_ParseFile(const char *path, DebugUI_ParseCommand(cmd); free(expanded); } + recursing = false; free(input); fclose(fp); @@ -1081,10 +1271,27 @@ bool DebugUI_ParseFile(const char *path, free(olddir); } - if (reinit) + if (!recurse) { - DebugCpu_SetDebugging(); - DebugDsp_SetDebugging(); + /* current script (or something called by it) specified final dir */ + if (finalDir) + { + if (chdir(finalDir) != 0) + perror("ERROR"); + else + fprintf(stderr, "Delayed change to '%s' dir.\n", finalDir); + free(finalDir); + finalDir = NULL; + } + /* only top-level (non-recursed) call has valid re-init info, + * as that's the only one that can get directly called from + * breakpoints + */ + if (reinit) + { + DebugCpu_SetDebugging(); + DebugDsp_SetDebugging(); + } } return true; } @@ -1114,3 +1321,30 @@ bool DebugUI_ParseLine(const char *input } return (ret == DEBUGGER_CMDDONE); } + +/** + * Debugger invocation based on exception + */ +void DebugUI_Exceptions(int nr, long pc) +{ + static struct { + int flag; + const char *name; + } ex[] = { + { EXCEPT_BUS, "Bus error" }, /* 2 */ + { EXCEPT_ADDRESS, "Address error" }, /* 3 */ + { EXCEPT_ILLEGAL, "Illegal instruction" }, /* 4 */ + { EXCEPT_ZERODIV, "Div by zero" }, /* 5 */ + { EXCEPT_CHK, "CHK" }, /* 6 */ + { EXCEPT_TRAPV, "TRAPV" }, /* 7 */ + { EXCEPT_PRIVILEGE, "Privilege violation" }, /* 8 */ + { EXCEPT_TRACE, "Trace" } /* 9 */ + }; + nr -= 2; + if (nr < 0 || nr >= ARRAY_SIZE(ex)) + return; + if (!(ExceptionDebugMask & ex[nr].flag)) + return; + fprintf(stderr,"%s exception at 0x%lx!\n", ex[nr].name, pc); + DebugUI(REASON_CPU_EXCEPTION); +}