--- hatari/src/debug/debugui.c 2019/04/09 08:48:37 1.1 +++ hatari/src/debug/debugui.c 2019/04/09 08:53:06 1.1.1.6 @@ -1,8 +1,8 @@ /* Hatari - debugui.c - This file is distributed under the GNU Public License, version 2 or at - your option any later version. Read the file gpl.txt for details. + This file is distributed under the GNU General Public License, version 2 + or at your option any later version. Read the file gpl.txt for details. debugui.c - this is the code for the mini-debugger. When the pause button is pressed, the emulator is (hopefully) halted and this little CLI can be used @@ -37,9 +37,11 @@ const char DebugUI_fileid[] = "Hatari de #include "breakcond.h" #include "debugcpu.h" #include "debugdsp.h" +#include "68kDisass.h" #include "debugInfo.h" #include "debugui.h" #include "evaluate.h" +#include "history.h" #include "symbols.h" int bExceptionDebugging; @@ -52,8 +54,8 @@ static int debugCommands; /* stores last 'e' command result as hex, used for TAB-completion */ static char lastResult[10]; +/* parse debugger commands from here on init */ static const char *parseFileName; -static bool DebugUI_ParseFile(const char *path); /** @@ -82,7 +84,7 @@ void DebugUI_MemorySnapShot_Capture(cons if (File_Exists(filename)) { /* and parse back the saved breakpoints */ - DebugUI_ParseFile(filename); + DebugUI_ParseFile(filename, true); } } free(filename); @@ -188,39 +190,45 @@ static int DebugUI_Evaluate(int nArgc, c */ static bool DebugUI_IsForDsp(const char *cmd) { - return ((cmd[0] == 'd' && cmd[1] && !cmd[2]) + return ((cmd[0] == 'd' && isalpha(cmd[1]) && !isalpha(cmd[2])) || strncmp(cmd, "dsp", 3) == 0); } /** - * Evaluate everything include within "" and replace them with the result. - * String given as an argument needs to be allocated, it may be freed and - * re-allocated if it needs to be expanded. Caller needs to free the returned - * string if it's not NULL. + * Evaluate everything include within single or double quotes ("" or '') + * and replace them with the result. + * Caller needs to free the returned string separately. * - * Return string with expressions expanded or NULL for an error. + * Return new string with expressions (potentially) expanded, or + * NULL when there's an error in the expression. */ -static char *DebugUI_EvaluateExpressions(char *input) +static char *DebugUI_EvaluateExpressions(const char *initial) { int offset, count, diff, inputlen; - char *end, *start; + char *end, *start, *input; const char *errstr; char valuestr[12]; Uint32 value; bool fordsp; /* input is split later on, need to save len here */ + input = strdup(initial); + if (!input) + { + perror("ERROR: Input string alloc failed\n"); + return NULL; + } fordsp = DebugUI_IsForDsp(input); inputlen = strlen(input); start = input; - - while ((start = strchr(start, '"'))) + + while ((count = strcspn(start, "\"'")) && *(start+count)) { - end = strchr(start+1, '"'); + start += count; + end = strchr(start+1, *start); if (!end) { - fprintf(stderr, "ERROR: matching '\"' missing from '%s'!\n", start); - free(input); + fprintf(stderr, "ERROR: matching '%c' missing from '%s'!\n", *start, start); return NULL; } @@ -234,16 +242,15 @@ static char *DebugUI_EvaluateExpressions *end = '\0'; errstr = Eval_Expression(start+1, &value, &offset, fordsp); if (errstr) { - *end = '"'; + *end = *start; /* restore expression mark */ fprintf(stderr, "Expression ERROR:\n'%s'\n%*c-%s\n", input, (int)(start-input)+offset+3, '^', errstr); - free(input); return NULL; } end++; - + count = sprintf(valuestr, "$%x", value); - fprintf(stderr, "- \"%s\" -> %s\n", start+1, valuestr); + fprintf(stderr, "- '%s' -> %s\n", start+1, valuestr); diff = end-start; if (count < diff) @@ -259,7 +266,6 @@ static char *DebugUI_EvaluateExpressions if (!tmp) { perror("ERROR: Input string alloc failed\n"); - free(input); return NULL; } @@ -322,7 +328,7 @@ static int DebugUI_SetOptions(int argc, return DEBUGGER_CMDDONE; } arg = argv[1]; - + for (i = 0; i < ARRAYSIZE(bases); i++) { if (strcasecmp(bases[i].name, arg) == 0) @@ -344,7 +350,7 @@ static int DebugUI_SetOptions(int argc, current = ConfigureParams; /* Parse and apply options */ - if (Opt_ParseParameters(argc, (const char**)argv)) + if (Opt_ParseParameters(argc, (const char * const *)argv)) { ConfigureParams.Screen.bFullScreen = false; Change_CopyChangedParamsToConfiguration(¤t, &ConfigureParams, false); @@ -399,33 +405,11 @@ static int DebugUI_ChangeDir(int argc, c static int DebugUI_CommandsFromFile(int argc, char *argv[]) { if (argc == 2) - DebugUI_ParseFile(argv[1]); - else - DebugUI_PrintCmdHelp(argv[0]); - return DEBUGGER_CMDDONE; -} - - -/** - * Command: Execute a system command - */ -#if ENABLE_SYSTEM_DEBUG_CALL /* Disabled by default - could be a security risk? */ -static int DebugUI_Exec(int argc, char *argv[]) -{ - if (argc == 2) - { - int ret = system(argv[1]); - if (ret) - { - /* error -> show return code */ - fprintf(stderr, "Error code = %d\n", ret); - } - } + DebugUI_ParseFile(argv[1], true); else DebugUI_PrintCmdHelp(argv[0]); return DEBUGGER_CMDDONE; } -#endif /** @@ -526,14 +510,15 @@ static int DebugUI_Help(int nArgc, char /** * Parse debug command and execute it. */ -static int DebugUI_ParseCommand(char *input) +static int DebugUI_ParseCommand(const char *input_orig) { - char *psArgs[64]; + char *psArgs[64], *input; const char *delim; static char sLastCmd[80] = { '\0' }; int nArgc, cmd = -1; int i, retval; + input = strdup(input_orig); psArgs[0] = strtok(input, " \t"); if (psArgs[0] == NULL) @@ -541,7 +526,10 @@ static int DebugUI_ParseCommand(char *in if (strlen(sLastCmd) > 0) psArgs[0] = sLastCmd; else + { + free(input); return DEBUGGER_CMDDONE; + } } /* Search the command ... */ @@ -561,6 +549,7 @@ static int DebugUI_ParseCommand(char *in fprintf(stderr, "Command '%s' not found.\n" "Use 'help' to view a list of available commands.\n", psArgs[0]); + free(input); return DEBUGGER_CMDDONE; } @@ -589,6 +578,7 @@ static int DebugUI_ParseCommand(char *in strncpy(sLastCmd, psArgs[0], sizeof(sLastCmd)); else sLastCmd[0] = '\0'; + free(input); return retval; } @@ -704,47 +694,73 @@ static char **DebugUI_Completion(const c return rl_completion_matches(text, rl_filename_completion_function); } +/** + * Add non-repeated command to readline history + * and free the given string + * @return its new value + */ +static char *DebugUI_FreeCommand(char *input) +{ + if (input && *input) + { + HIST_ENTRY *hist = history_get(history_length); + /* don't store duplicate successive entries */ + if (!hist || !hist->line || strcmp(hist->line, input) != 0) + { + add_history(input); + } + free(input); + } + return NULL; +} /** * Read a command line from the keyboard and return a pointer to the string. - * @return Pointer to the string which should be deallocated free() - * after use. Returns NULL when error occured. + * Only string returned by this function can be given for it as argument! + * The string will be stored into command history buffer. + * @return Pointer to the string which should be given back to this + * function or DebugUI_FreeCommand() for re-use/history. + * Returns NULL when error occurred. */ -static char *DebugUI_GetCommand(void) +static char *DebugUI_GetCommand(char *input) { - char *input; - /* Allow conditional parsing of the ~/.inputrc file. */ rl_readline_name = "Hatari"; /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = DebugUI_Completion; - - input = readline("> "); - if (!input) - return NULL; - - input = Str_Trim(input); - if (input[0]) - add_history(input); - - return input; + input = DebugUI_FreeCommand(input); + return Str_Trim(readline("> ")); } #else /* !HAVE_LIBREADLINE */ /** + * Free Command input string + * @return its new value + */ +static char *DebugUI_FreeCommand(char *input) +{ + if (input) + free(input); + return NULL; +} + +/** * Read a command line from the keyboard and return a pointer to the string. - * @return Pointer to the string which should be deallocated free() - * after use. Returns NULL when error occured. + * Only string returned by this function can be given for it as argument! + * @return Pointer to the string which should be given back to this + * function or DebugUI_FreeCommand() for re-use/freeing. + * Returns NULL when error occurred. */ -static char *DebugUI_GetCommand(void) +static char *DebugUI_GetCommand(char *input) { - char *input; fprintf(stderr, "> "); - input = malloc(256); if (!input) - return NULL; + { + input = malloc(256); + assert(input); + } input[0] = '\0'; if (fgets(input, 256, stdin) == NULL) { @@ -776,25 +792,29 @@ static const dbgcommand_t uicommand[] = "\tby their values. Supported operators in expressions are,\n" "\tin 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" + "\tused only in start of line or parenthesis.\n" "\tFor example:\n" - "\t\t((0x21 * 0x200) + (-5)) ^ (~%111 & $f0f0f0)\n" + "\t\t~%101 & $f0f0f ^ (d0 + 0x21)\n" "\tResult value is shown as binary, decimal and hexadecimal.\n" "\tAfter this, '$' will TAB-complete to last result value.", true }, -#if ENABLE_SYSTEM_DEBUG_CALL - { DebugUI_Exec, NULL, - "exec", "", - "execute a shell command", - "\n" - "\tRun the given command with the system shell.", - true }, -#endif { DebugUI_Help, DebugUI_MatchCommand, "help", "h", "print help", "[command]\n" "\tPrint help text for available commands.", false }, + { History_Parse, NULL, + "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.", + false }, { DebugInfo_Command, DebugInfo_MatchInfo, "info", "i", "show machine/OS information", @@ -804,10 +824,10 @@ static const dbgcommand_t uicommand[] = false }, { DebugInfo_Command, DebugInfo_MatchLock, "lock", "", - "lock info to show on entering debugger", + "specify information to show on entering the debugger", "[subject [args]]\n" - "\tLock requested information to be shown every time debugger\n" - "\tis entered or list available options if no subject's given.", + "\tLock what information should be shown every time debugger\n" + "\tis entered, or list available options if no subject's given.", false }, { DebugUI_SetLogFile, NULL, "logfile", "f", @@ -825,7 +845,7 @@ static const dbgcommand_t uicommand[] = { DebugUI_SetOptions, Opt_MatchOption, "setopt", "o", "set Hatari command line and debugger options", - "[|]\n" + "[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" @@ -847,8 +867,9 @@ static const dbgcommand_t uicommand[] = "trace", "t", "select Hatari tracing settings", "[set1,set2...]\n" - "\tSelect Hatari tracing settings. For example to enable CPU\n" - "\tdisassembly and VBL tracing, use: trace cpu_disasm,video_hbl", + "\tSelect Hatari tracing settings. 'help' shows all the available\n" + "\tsettings. For example, to enable CPU disassembly and VBL\n" + "\ttracing, use:\n\t\ttrace cpu_disasm,video_hbl", false }, { DebugUI_QuitEmu, NULL, "quit", "q", @@ -889,12 +910,14 @@ void DebugUI_Init(void) debugCommands += dspcmds; if (parseFileName) - DebugUI_ParseFile(parseFileName); + DebugUI_ParseFile(parseFileName, true); } /** - * Set debugger commands file. + * Set debugger commands file during Hatari startup before things + * needed by the debugger are initialized so that it can be parsed + * when debugger itself gets initialized. * Return true if file exists, false otherwise. */ bool DebugUI_SetParseFile(const char *path) @@ -912,14 +935,16 @@ bool DebugUI_SetParseFile(const char *pa /** * Debugger user interface main function. */ -void DebugUI(void) +void DebugUI(debug_reason_t reason) { int cmdret, alertLevel; - char *psCmd; + char *expCmd, *psCmd = NULL; static const char *welcome = "\n----------------------------------------------------------------------" "\nYou have entered debug mode. Type c to continue emulation, h for help.\n"; - + + History_Mark(reason); + if (bInFullScreen) Screen_ReturnFromFullScreen(); @@ -946,21 +971,26 @@ void DebugUI(void) cmdret = DEBUGGER_CMDDONE; do { - /* Read command from the keyboard */ - psCmd = DebugUI_GetCommand(); + /* Read command from the keyboard and give previous + * command for freeing / adding to history + */ + psCmd = DebugUI_GetCommand(psCmd); if (!psCmd) break; - /* expand all quoted expressions */ - if (!(psCmd = DebugUI_EvaluateExpressions(psCmd))) + /* returns new expression expanded string */ + if (!(expCmd = DebugUI_EvaluateExpressions(psCmd))) continue; /* Parse and execute the command string */ - cmdret = DebugUI_ParseCommand(psCmd); - free(psCmd); + cmdret = DebugUI_ParseCommand(expCmd); + free(expCmd); } while (cmdret != DEBUGGER_END); + /* free exit command */ + psCmd = DebugUI_FreeCommand(psCmd); + Log_SetAlertLevel(alertLevel); DebugUI_SetLogDefault(); @@ -970,12 +1000,13 @@ void DebugUI(void) /** - * Read debugger commands from a file. - * return false for error, true for success. + * Read debugger commands from a file. If 'reinit' is set + * (as it normally should), reinitialize breakpoints etc. + * afterwards. return false for error, true for success. */ -static bool DebugUI_ParseFile(const char *path) +bool DebugUI_ParseFile(const char *path, bool reinit) { - char *dir, *cmd, *input, *slash; + char *olddir, *dir, *cmd, *input, *expanded, *slash; FILE *fp; fprintf(stderr, "Reading debugger commands from '%s'...\n", path); @@ -986,17 +1017,28 @@ static bool DebugUI_ParseFile(const char } /* change to directory where the debugger file resides */ + olddir = NULL; dir = strdup(path); slash = strrchr(dir, PATHSEP); if (slash) { + olddir = malloc(FILENAME_MAX); + if (olddir) + { + if (!getcwd(olddir, FILENAME_MAX)) + strcpy(olddir, "."); + } *slash = '\0'; - if (chdir(dir)) + if (chdir(dir) != 0) { perror("ERROR"); + if (olddir) + free(olddir); free(dir); + fclose(fp); return false; } + fprintf(stderr, "Changed to input file dir '%s'.\n", dir); } free(dir); @@ -1011,40 +1053,64 @@ static bool DebugUI_ParseFile(const char if (!fgets(input, 256, fp)) break; - input = DebugUI_EvaluateExpressions(input); - if (!input) + /* ignore empty and comment lines */ + cmd = Str_Trim(input); + if (!*cmd || *cmd == '#') continue; - cmd = Str_Trim(input); - if (*cmd && *cmd != '#') - { - fprintf(stderr, "> %s\n", cmd); - DebugUI_ParseCommand(cmd); - } + /* returns new string if input needed expanding! */ + expanded = DebugUI_EvaluateExpressions(input); + if (!expanded) + continue; + + cmd = Str_Trim(expanded); + fprintf(stderr, "> %s\n", cmd); + DebugUI_ParseCommand(cmd); + free(expanded); } free(input); + fclose(fp); - DebugCpu_SetDebugging(); - DebugDsp_SetDebugging(); + if (olddir) + { + if (chdir(olddir) != 0) + perror("ERROR"); + else + fprintf(stderr, "Changed back to '%s' dir.\n", olddir); + free(olddir); + } + + if (reinit) + { + DebugCpu_SetDebugging(); + DebugDsp_SetDebugging(); + } return true; } /** - * Remote/parallel debugger usage API. + * Remote/parallel debugger line usage API. * Return false for failed command, true for success. */ -bool DebugUI_RemoteParse(char *input) +bool DebugUI_ParseLine(const char *input) { - int ret; + char *expanded; + int ret = 0; DebugUI_Init(); - - ret = DebugUI_ParseCommand(input); - DebugCpu_SetDebugging(); - DebugDsp_SetDebugging(); + /* returns new string if input needed expanding! */ + expanded = DebugUI_EvaluateExpressions(input); + if (expanded) + { + fprintf(stderr, "> %s\n", expanded); + ret = DebugUI_ParseCommand(expanded); + free(expanded); + DebugCpu_SetDebugging(); + DebugDsp_SetDebugging(); + } return (ret == DEBUGGER_CMDDONE); }