mirror of
https://github.com/holub/mame
synced 2025-06-05 04:16:28 +03:00
569 lines
15 KiB
C
569 lines
15 KiB
C
/*********************************************************************
|
|
|
|
debugcon.c
|
|
|
|
Debugger console engine.
|
|
|
|
Copyright Nicola Salmoria and the MAME Team.
|
|
Visit http://mamedev.org for licensing and usage restrictions.
|
|
|
|
*********************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "debugcon.h"
|
|
#include "debugcpu.h"
|
|
#include "debughlp.h"
|
|
#include "debugvw.h"
|
|
#include "textbuf.h"
|
|
#include "debugger.h"
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CONSTANTS
|
|
***************************************************************************/
|
|
|
|
#define CONSOLE_BUF_SIZE (1024 * 1024)
|
|
#define CONSOLE_MAX_LINES (CONSOLE_BUF_SIZE / 20)
|
|
|
|
#define ERRORLOG_BUF_SIZE (1024 * 1024)
|
|
#define ERRORLOG_MAX_LINES (ERRORLOG_BUF_SIZE / 20)
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
TYPE DEFINITIONS
|
|
***************************************************************************/
|
|
|
|
typedef struct _debug_command debug_command;
|
|
struct _debug_command
|
|
{
|
|
debug_command * next;
|
|
char command[32];
|
|
const char * params;
|
|
const char * help;
|
|
void (*handler)(running_machine *machine, int ref, int params, const char **param);
|
|
void (*handler_ex)(int ref);
|
|
UINT32 flags;
|
|
int ref;
|
|
int minparams;
|
|
int maxparams;
|
|
};
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
LOCAL VARIABLES
|
|
***************************************************************************/
|
|
|
|
static text_buffer *console_textbuf;
|
|
static text_buffer *errorlog_textbuf;
|
|
|
|
static debug_command *commandlist;
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
FUNCTION PROTOTYPES
|
|
***************************************************************************/
|
|
|
|
static void debug_console_exit(running_machine &machine);
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
Initialization and tear down
|
|
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_init - initializes the console
|
|
system
|
|
-------------------------------------------------*/
|
|
|
|
void debug_console_init(running_machine *machine)
|
|
{
|
|
/* allocate text buffers */
|
|
console_textbuf = text_buffer_alloc(CONSOLE_BUF_SIZE, CONSOLE_MAX_LINES);
|
|
if (!console_textbuf)
|
|
return;
|
|
|
|
errorlog_textbuf = text_buffer_alloc(ERRORLOG_BUF_SIZE, ERRORLOG_MAX_LINES);
|
|
if (!errorlog_textbuf)
|
|
return;
|
|
|
|
/* print the opening lines */
|
|
debug_console_printf(machine, "MAME new debugger version %s\n", build_version);
|
|
debug_console_printf(machine, "Currently targeting %s (%s)\n", machine->gamedrv->name, machine->gamedrv->description);
|
|
|
|
/* request callback upon exiting */
|
|
machine->add_notifier(MACHINE_NOTIFY_EXIT, debug_console_exit);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_exit - frees the console
|
|
system
|
|
-------------------------------------------------*/
|
|
|
|
static void debug_console_exit(running_machine &machine)
|
|
{
|
|
/* free allocated memory */
|
|
if (console_textbuf)
|
|
text_buffer_free(console_textbuf);
|
|
console_textbuf = NULL;
|
|
|
|
if (errorlog_textbuf)
|
|
text_buffer_free(errorlog_textbuf);
|
|
errorlog_textbuf = NULL;
|
|
|
|
/* free the command list */
|
|
commandlist = NULL;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
Command Handling
|
|
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
trim_parameter - executes a
|
|
command
|
|
-------------------------------------------------*/
|
|
|
|
static void trim_parameter(char **paramptr, int keep_quotes)
|
|
{
|
|
char *param = *paramptr;
|
|
size_t len = strlen(param);
|
|
int repeat;
|
|
|
|
/* loop until all adornments are gone */
|
|
do
|
|
{
|
|
repeat = 0;
|
|
|
|
/* check for begin/end quotes */
|
|
if (len >= 2 && param[0] == '"' && param[len - 1] == '"')
|
|
{
|
|
if (!keep_quotes)
|
|
{
|
|
param[len - 1] = 0;
|
|
param++;
|
|
len -= 2;
|
|
}
|
|
}
|
|
|
|
/* check for start/end braces */
|
|
else if (len >= 2 && param[0] == '{' && param[len - 1] == '}')
|
|
{
|
|
param[len - 1] = 0;
|
|
param++;
|
|
len -= 2;
|
|
repeat = 1;
|
|
}
|
|
|
|
/* check for leading spaces */
|
|
else if (len >= 1 && param[0] == ' ')
|
|
{
|
|
param++;
|
|
len--;
|
|
repeat = 1;
|
|
}
|
|
|
|
/* check for trailing spaces */
|
|
else if (len >= 1 && param[len - 1] == ' ')
|
|
{
|
|
param[len - 1] = 0;
|
|
len--;
|
|
repeat = 1;
|
|
}
|
|
} while (repeat);
|
|
|
|
*paramptr = param;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
internal_execute_command - executes a
|
|
command
|
|
-------------------------------------------------*/
|
|
|
|
static CMDERR internal_execute_command(running_machine *machine, int execute, int params, char **param)
|
|
{
|
|
debug_command *cmd, *found = NULL;
|
|
int i, foundcount = 0;
|
|
char *p, *command;
|
|
size_t len;
|
|
|
|
/* no params is an error */
|
|
if (params == 0)
|
|
return CMDERR_NONE;
|
|
|
|
/* the first parameter has the command and the real first parameter; separate them */
|
|
for (p = param[0]; *p && isspace((UINT8)*p); p++) { }
|
|
for (command = p; *p && !isspace((UINT8)*p); p++) { }
|
|
if (*p != 0)
|
|
{
|
|
*p++ = 0;
|
|
for ( ; *p && isspace((UINT8)*p); p++) { }
|
|
if (*p != 0)
|
|
param[0] = p;
|
|
else
|
|
params = 0;
|
|
}
|
|
else
|
|
{
|
|
params = 0;
|
|
param[0] = NULL;
|
|
}
|
|
|
|
/* search the command list */
|
|
len = strlen(command);
|
|
for (cmd = commandlist; cmd != NULL; cmd = cmd->next)
|
|
if (!strncmp(command, cmd->command, len))
|
|
{
|
|
foundcount++;
|
|
found = cmd;
|
|
if (strlen(cmd->command) == len)
|
|
{
|
|
foundcount = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* error if not found */
|
|
if (!found)
|
|
return MAKE_CMDERR_UNKNOWN_COMMAND(0);
|
|
if (foundcount > 1)
|
|
return MAKE_CMDERR_AMBIGUOUS_COMMAND(0);
|
|
|
|
/* NULL-terminate and trim space around all the parameters */
|
|
for (i = 1; i < params; i++)
|
|
*param[i]++ = 0;
|
|
|
|
/* now go back and trim quotes and braces and any spaces they reveal*/
|
|
for (i = 0; i < params; i++)
|
|
trim_parameter(¶m[i], found->flags & CMDFLAG_KEEP_QUOTES);
|
|
|
|
/* see if we have the right number of parameters */
|
|
if (params < found->minparams)
|
|
return MAKE_CMDERR_NOT_ENOUGH_PARAMS(0);
|
|
if (params > found->maxparams)
|
|
return MAKE_CMDERR_TOO_MANY_PARAMS(0);
|
|
|
|
/* execute the handler */
|
|
if (execute)
|
|
(*found->handler)(machine, found->ref, params, (const char **)param);
|
|
return CMDERR_NONE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
internal_parse_command - parses a command
|
|
and either executes or just validates it
|
|
-------------------------------------------------*/
|
|
|
|
static CMDERR internal_parse_command(running_machine *machine, const char *original_command, int execute)
|
|
{
|
|
char command[MAX_COMMAND_LENGTH], parens[MAX_COMMAND_LENGTH];
|
|
char *params[MAX_COMMAND_PARAMS] = { 0 };
|
|
CMDERR result = CMDERR_NONE;
|
|
char *command_start;
|
|
char *p, c = 0;
|
|
|
|
/* make a copy of the command */
|
|
strcpy(command, original_command);
|
|
|
|
/* loop over all semicolon-separated stuff */
|
|
for (p = command; *p != 0; )
|
|
{
|
|
int paramcount = 0, foundend = FALSE, instring = FALSE, isexpr = FALSE, parendex = 0;
|
|
|
|
/* find a semicolon or the end */
|
|
for (params[paramcount++] = p; !foundend; p++)
|
|
{
|
|
c = *p;
|
|
if (instring)
|
|
{
|
|
if (c == '"' && p[-1] != '\\')
|
|
instring = FALSE;
|
|
}
|
|
else
|
|
{
|
|
switch (c)
|
|
{
|
|
case '"': instring = TRUE; break;
|
|
case '(':
|
|
case '[':
|
|
case '{': parens[parendex++] = c; break;
|
|
case ')': if (parendex == 0 || parens[--parendex] != '(') return MAKE_CMDERR_UNBALANCED_PARENS(p - command); break;
|
|
case ']': if (parendex == 0 || parens[--parendex] != '[') return MAKE_CMDERR_UNBALANCED_PARENS(p - command); break;
|
|
case '}': if (parendex == 0 || parens[--parendex] != '{') return MAKE_CMDERR_UNBALANCED_PARENS(p - command); break;
|
|
case ',': if (parendex == 0) params[paramcount++] = p; break;
|
|
case ';': if (parendex == 0) foundend = TRUE; break;
|
|
case '-': if (parendex == 0 && paramcount == 1 && p[1] == '-') isexpr = TRUE; *p = c; break;
|
|
case '+': if (parendex == 0 && paramcount == 1 && p[1] == '+') isexpr = TRUE; *p = c; break;
|
|
case '=': if (parendex == 0 && paramcount == 1) isexpr = TRUE; *p = c; break;
|
|
case 0: foundend = TRUE; break;
|
|
default: *p = tolower((UINT8)c); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* check for unbalanced parentheses or quotes */
|
|
if (instring)
|
|
return MAKE_CMDERR_UNBALANCED_QUOTES(p - command);
|
|
if (parendex != 0)
|
|
return MAKE_CMDERR_UNBALANCED_PARENS(p - command);
|
|
|
|
/* NULL-terminate if we ended in a semicolon */
|
|
p--;
|
|
if (c == ';') *p++ = 0;
|
|
|
|
/* process the command */
|
|
command_start = params[0];
|
|
|
|
/* allow for "do" commands */
|
|
if (tolower((UINT8)command_start[0] == 'd') && tolower((UINT8)command_start[1] == 'o') && isspace((UINT8)command_start[2]))
|
|
{
|
|
isexpr = TRUE;
|
|
command_start += 3;
|
|
}
|
|
|
|
/* if it smells like an assignment expression, treat it as such */
|
|
if (isexpr && paramcount == 1)
|
|
{
|
|
try
|
|
{
|
|
UINT64 expresult;
|
|
parsed_expression expression(debug_cpu_get_visible_symtable(machine), command_start, &expresult);
|
|
}
|
|
catch (expression_error &err)
|
|
{
|
|
return MAKE_CMDERR_EXPRESSION_ERROR(err);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = internal_execute_command(machine, execute, paramcount, ¶ms[0]);
|
|
if (result != CMDERR_NONE)
|
|
return MAKE_CMDERR(CMDERR_ERROR_CLASS(result), command_start - command);
|
|
}
|
|
}
|
|
return CMDERR_NONE;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_execute_command - execute a
|
|
command string
|
|
-------------------------------------------------*/
|
|
|
|
CMDERR debug_console_execute_command(running_machine *machine, const char *command, int echo)
|
|
{
|
|
CMDERR result;
|
|
|
|
/* echo if requested */
|
|
if (echo)
|
|
debug_console_printf(machine, ">%s\n", command);
|
|
|
|
/* parse and execute */
|
|
result = internal_parse_command(machine, command, TRUE);
|
|
|
|
/* display errors */
|
|
if (result != CMDERR_NONE)
|
|
{
|
|
if (!echo)
|
|
debug_console_printf(machine, ">%s\n", command);
|
|
debug_console_printf(machine, " %*s^\n", CMDERR_ERROR_OFFSET(result), "");
|
|
debug_console_printf(machine, "%s\n", debug_cmderr_to_string(result));
|
|
}
|
|
|
|
/* update all views */
|
|
if (echo)
|
|
{
|
|
machine->debug_view().update_all();
|
|
debugger_refresh_display(machine);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_validate_command - validate a
|
|
command string
|
|
-------------------------------------------------*/
|
|
|
|
CMDERR debug_console_validate_command(running_machine *machine, const char *command)
|
|
{
|
|
return internal_parse_command(machine, command, FALSE);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_register_command - register a
|
|
command handler
|
|
-------------------------------------------------*/
|
|
|
|
void debug_console_register_command(running_machine *machine, const char *command, UINT32 flags, int ref, int minparams, int maxparams, void (*handler)(running_machine *machine, int ref, int params, const char **param))
|
|
{
|
|
debug_command *cmd;
|
|
|
|
assert_always(machine->phase() == MACHINE_PHASE_INIT, "Can only call debug_console_register_command() at init time!");
|
|
assert_always((machine->debug_flags & DEBUG_FLAG_ENABLED) != 0, "Cannot call debug_console_register_command() when debugger is not running");
|
|
|
|
cmd = auto_alloc_clear(machine, debug_command);
|
|
|
|
/* fill in the command */
|
|
strcpy(cmd->command, command);
|
|
cmd->flags = flags;
|
|
cmd->ref = ref;
|
|
cmd->minparams = minparams;
|
|
cmd->maxparams = maxparams;
|
|
cmd->handler = handler;
|
|
|
|
/* link it */
|
|
cmd->next = commandlist;
|
|
commandlist = cmd;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
Error Handling
|
|
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_cmderr_to_string - return a friendly
|
|
string for a given command error
|
|
-------------------------------------------------*/
|
|
|
|
const char *debug_cmderr_to_string(CMDERR error)
|
|
{
|
|
switch (CMDERR_ERROR_CLASS(error))
|
|
{
|
|
case CMDERR_UNKNOWN_COMMAND: return "unknown command";
|
|
case CMDERR_AMBIGUOUS_COMMAND: return "ambiguous command";
|
|
case CMDERR_UNBALANCED_PARENS: return "unbalanced parentheses";
|
|
case CMDERR_UNBALANCED_QUOTES: return "unbalanced quotes";
|
|
case CMDERR_NOT_ENOUGH_PARAMS: return "not enough parameters for command";
|
|
case CMDERR_TOO_MANY_PARAMS: return "too many parameters for command";
|
|
case CMDERR_EXPRESSION_ERROR: return "error in assignment expression";
|
|
default: return "unknown error";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
Console Management
|
|
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_printf - printfs the given
|
|
arguments using the format to the debug
|
|
console
|
|
-------------------------------------------------*/
|
|
|
|
void CLIB_DECL debug_console_printf(running_machine *machine, const char *format, ...)
|
|
{
|
|
astring buffer;
|
|
va_list arg;
|
|
|
|
va_start(arg, format);
|
|
buffer.vprintf(format, arg);
|
|
va_end(arg);
|
|
|
|
text_buffer_print(console_textbuf, buffer);
|
|
|
|
/* force an update of any console views */
|
|
machine->debug_view().update_all(DVT_CONSOLE);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_vprintf - printfs the given
|
|
arguments using the format to the debug
|
|
console
|
|
-------------------------------------------------*/
|
|
|
|
void CLIB_DECL debug_console_vprintf(running_machine *machine, const char *format, va_list args)
|
|
{
|
|
astring buffer;
|
|
|
|
buffer.vprintf(format, args);
|
|
text_buffer_print(console_textbuf, buffer);
|
|
|
|
/* force an update of any console views */
|
|
machine->debug_view().update_all(DVT_CONSOLE);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_printf_wrap - printfs the given
|
|
arguments using the format to the debug
|
|
console
|
|
-------------------------------------------------*/
|
|
|
|
void CLIB_DECL debug_console_printf_wrap(running_machine *machine, int wrapcol, const char *format, ...)
|
|
{
|
|
astring buffer;
|
|
va_list arg;
|
|
|
|
va_start(arg, format);
|
|
buffer.vprintf(format, arg);
|
|
va_end(arg);
|
|
|
|
text_buffer_print_wrap(console_textbuf, buffer, wrapcol);
|
|
|
|
/* force an update of any console views */
|
|
machine->debug_view().update_all(DVT_CONSOLE);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_console_get_textbuf - return a pointer
|
|
to the console text buffer
|
|
-------------------------------------------------*/
|
|
|
|
text_buffer *debug_console_get_textbuf(void)
|
|
{
|
|
return console_textbuf;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_errorlog_write_line - writes a line to
|
|
the errorlog ring buffer
|
|
-------------------------------------------------*/
|
|
|
|
void debug_errorlog_write_line(running_machine &machine, const char *line)
|
|
{
|
|
if (errorlog_textbuf)
|
|
text_buffer_print(errorlog_textbuf, line);
|
|
|
|
/* force an update of any log views */
|
|
machine.debug_view().update_all(DVT_LOG);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
debug_errorlog_get_textbuf - return a pointer
|
|
to the errorlog text buffer
|
|
-------------------------------------------------*/
|
|
|
|
text_buffer *debug_errorlog_get_textbuf(void)
|
|
{
|
|
return errorlog_textbuf;
|
|
}
|
|
|