changeset 2178:f6d5bde4d07f

Finish debugger refactor started with expression parser changes
author Michael Pavone <pavone@retrodev.com>
date Sat, 13 Aug 2022 19:16:30 -0700
parents 44596610b2a0
children 9a8dd4ba2753
files debug.c debug.h z80_to_x86.h
diffstat 3 files changed, 1887 insertions(+), 918 deletions(-) [+]
line wrap: on
line diff
--- a/debug.c	Sun Aug 07 01:16:47 2022 -0700
+++ b/debug.c	Sat Aug 13 19:16:30 2022 -0700
@@ -20,8 +20,6 @@
 #define Z80_OPTS options
 #endif
 
-
-
 static debug_root roots[5];
 static uint32_t num_roots;
 #define MAX_DEBUG_ROOTS (sizeof(roots)/sizeof(*roots))
@@ -65,18 +63,6 @@
 	return cur;
 }
 
-typedef enum {
-	TOKEN_NONE,
-	TOKEN_NUM,
-	TOKEN_NAME,
-	TOKEN_OPER,
-	TOKEN_SIZE,
-	TOKEN_LBRACKET,
-	TOKEN_RBRACKET,
-	TOKEN_LPAREN,
-	TOKEN_RPAREN
-} token_type;
-
 static const char *token_type_names[] = {
 	"TOKEN_NONE",
 	"TOKEN_NUM",
@@ -89,22 +75,13 @@
 	"TOKEN_RPAREN"
 };
 
-typedef struct {
-	token_type type;
-	union {
-		char     *str;
-		char     op[3];
-		uint32_t num;
-	} v;
-} token;
-
 static token parse_token(char *start, char **end)
 {
-	while(*start && isblank(*start) && *start != '\n')
+	while(*start && isblank(*start) && *start != '\n' && *start != '\r')
 	{
 		++start;
 	}
-	if (!*start || *start == '\n') {
+	if (!*start || *start == '\n' || *start == '\r') {
 		return (token){
 			.type = TOKEN_NONE
 		};
@@ -184,7 +161,7 @@
 		};
 	}
 	*end = start + 1;
-	while (**end && !isblank(**end))
+	while (**end && !isspace(**end))
 	{
 		uint8_t done = 0;
 		switch (**end)
@@ -220,24 +197,6 @@
 	};
 }
 
-typedef enum {
-	EXPR_NONE,
-	EXPR_SCALAR,
-	EXPR_UNARY,
-	EXPR_BINARY,
-	EXPR_SIZE,
-	EXPR_MEM
-} expr_type;
-
-typedef struct expr expr;
-
-struct expr {
-	expr_type type;
-	expr      *left;
-	expr      *right;
-	token     op;
-};
-
 static void free_expr(expr *e)
 {
 	if (!e) {
@@ -619,6 +578,9 @@
 		value->left->op = first;
 		return maybe_binary(value, after_second, end);
 	} else {
+		if (second.type == TOKEN_NAME) {
+			free(second.v.str);
+		}
 		expr *ret = calloc(1, sizeof(expr));
 		ret->type = EXPR_SCALAR;
 		ret->op = first;
@@ -719,30 +681,6 @@
 	}
 }
 
-void add_display(disp_def ** head, uint32_t *index, char format_char, char * param)
-{
-	disp_def * ndisp = malloc(sizeof(*ndisp));
-	ndisp->format_char = format_char;
-	ndisp->param = strdup(param);
-	ndisp->next = *head;
-	ndisp->index = *index++;
-	*head = ndisp;
-}
-
-void remove_display(disp_def ** head, uint32_t index)
-{
-	while (*head) {
-		if ((*head)->index == index) {
-			disp_def * del_disp = *head;
-			*head = del_disp->next;
-			free(del_disp->param);
-			free(del_disp);
-		} else {
-			head = &(*head)->next;
-		}
-	}
-}
-
 char * find_param(char * buf)
 {
 	for (; *buf; buf++) {
@@ -787,13 +725,43 @@
 	if (size == 'b') {
 		*out = m68k_read_byte(*out, context);
 	} else if (size == 'l') {
+		if (*out & 1) {
+			fprintf(stderr, "Longword access to odd addresses (%X) is not allowed\n", *out);
+			return 0;
+		}
 		*out = m68k_read_long(*out, context);
 	} else {
+		if (*out & 1) {
+			fprintf(stderr, "Wword access to odd addresses (%X) is not allowed\n", *out);
+			return 0;
+		}
 		*out = m68k_read_word(*out, context);
 	}
 	return 1;
 }
 
+static uint8_t write_m68k(debug_root *root, uint32_t address, uint32_t value, char size)
+{
+	m68k_context *context = root->cpu_context;
+	if (size == 'b') {
+		write_byte(address, value, (void **)context->mem_pointers, &context->options->gen, context);
+	} else if (size == 'l') {
+		if (address & 1) {
+			fprintf(stderr, "Longword access to odd addresses (%X) is not allowed\n", address);
+			return 0;
+		}
+		write_word(address, value >> 16, (void **)context->mem_pointers, &context->options->gen, context);
+		write_word(address + 2, value, (void **)context->mem_pointers, &context->options->gen, context);
+	} else {
+		if (address & 1) {
+			fprintf(stderr, "Wword access to odd addresses (%X) is not allowed\n", address);
+			return 0;
+		}
+		write_word(address, value, (void **)context->mem_pointers, &context->options->gen, context);
+	}
+	return 1;
+}
+
 static uint8_t resolve_m68k(debug_root *root, const char *name, uint32_t *out)
 {
 	m68k_context *context = root->cpu_context;
@@ -820,6 +788,28 @@
 	return 1;
 }
 
+static uint8_t set_m68k(debug_root *root, const char *name, uint32_t value)
+{
+	m68k_context *context = root->cpu_context;
+	if ((name[0] == 'd' || name[0] == 'D') && name[1] >= '0' && name[1] <= '7' && !name[2]) {
+		context->dregs[name[1]-'0'] = value;
+	} else if ((name[0] == 'a' || name[0] == 'A') && name[1] >= '0' && name[1] <= '7' && !name[2]) {
+		context->aregs[name[1]-'0'] = value;
+	} else if (!strcasecmp(name, "sr")) {
+		context->status = value >> 8;
+		for (int flag = 0; flag < 5; flag++) {
+			context->flags[flag] = (value & (1 << (4 - flag))) != 0;
+		}
+	} else if (!strcasecmp(name, "usp")) {
+		context->aregs[context->status & 0x20 ? 8 : 7] = value;
+	} else if (!strcasecmp(name, "ssp")) {
+		context->aregs[context->status & 0x20 ? 7 : 8] = value;
+	} else {
+		return 0;
+	}
+	return 1;
+}
+
 static uint8_t resolve_genesis(debug_root *root, const char *name, uint32_t *out)
 {
 	if (resolve_m68k(root, name, out)) {
@@ -834,251 +824,1783 @@
 	return 0;
 }
 
-void debugger_print(debug_root *root, char format_char, char *param)
+void ambiguous_iter(char *key, tern_val val, uint8_t valtype, void *data)
 {
-	uint32_t value;
-	char format[8];
-	strcpy(format, "%s: %d\n");
-	switch (format_char)
+	char *prefix = data;
+	char * full = alloc_concat(prefix, key);
+	fprintf(stderr, "\t%s\n", full);
+	free(full);
+}
+
+uint8_t parse_command(debug_root *root, char *text, parsed_command *out)
+{
+	char *cur = text;
+	while (*cur && *cur != '/' && !isspace(*cur))
 	{
-	case 'x':
-	case 'X':
-	case 'd':
-	case 'c':
-	case 's':
-		format[5] = format_char;
-		break;
-	case '\0':
-		break;
-	default:
-		fprintf(stderr, "Unrecognized format character: %c\n", format_char);
+		++cur;
+	}
+	char *name = malloc(cur - text + 1);
+	memcpy(name, text, cur - text);
+	name[cur-text] = 0;
+	uint8_t ret = 0;
+	tern_node *prefix_res = tern_find_prefix(root->commands, name);
+	command_def *def = tern_find_ptr(prefix_res, "");
+	if (!def) {
+		tern_node *node = prefix_res;
+		while (node)
+		{
+			if (node->left || node->right) {
+				break;
+			}
+			if (node->el) {
+				node = node->straight.next;
+			} else {
+				def = node->straight.value.ptrval;
+				break;
+			}
+		}
+		if (!def && prefix_res) {
+			fprintf(stderr, "%s is ambiguous. Matching commands:\n", name);
+			tern_foreach(prefix_res, ambiguous_iter, name);
+			goto cleanup_name;
+		}
+	}
+	if (!def) {
+		fprintf(stderr, "%s is not a recognized command\n", name);
+		goto cleanup_name;
+	}
+	char *format = NULL;
+	if (*cur == '/') {
+		++cur;
+		text = cur;
+		while (*cur && !isspace(*cur))
+		{
+			++cur;
+		}
+		format = malloc(cur - text + 1);
+		memcpy(format, text, cur - text);
+		format[cur - text] = 0;
+	}
+	int num_args = 0;
+	command_arg *args = NULL;
+	if (*cur && *cur != '\n') {
+		++cur;
+	}
+	text = cur;
+	if (def->raw_impl) {
+		while (*cur && *cur != '\n')
+		{
+			++cur;
+		}
+		char *raw_param = NULL;
+		if (cur != text) {
+			raw_param = malloc(cur - text + 1);
+			memcpy(raw_param, text, cur - text);
+			raw_param[cur - text] = 0;
+		}
+		out->raw = raw_param;
+		out->args = NULL;
+		out->num_args = 0;
+	} else {
+		int arg_storage = 0;
+		if (def->max_args > 0) {
+			arg_storage = def->max_args;
+		} else if (def->max_args) {
+			arg_storage = def->min_args > 0 ? 2 * def->min_args : 2;
+		}
+		if (arg_storage) {
+			args = calloc(arg_storage, sizeof(command_arg));
+		}
+		while (*text && *text != '\n')
+		{
+			char *after;
+			expr *e = parse_expression(text, &after);
+			if (e) {
+				if (num_args == arg_storage) {
+					if (def->max_args >= 0) {
+						free_expr(e);
+						fprintf(stderr, "Command %s takes a max of %d arguments, but at least %d provided\n", name, def->max_args, def->max_args+1);
+						goto cleanup_args;
+					} else {
+						arg_storage *= 2;
+						args = realloc(args, arg_storage * sizeof(command_arg));
+					}
+				}
+				args[num_args].parsed = e;
+				args[num_args].raw = malloc(after - text + 1);
+				memcpy(args[num_args].raw, text, after - text);
+				args[num_args++].raw[after - text] = 0;
+				text = after;
+			} else {
+				goto cleanup_args;
+			}
+		}
+		if (num_args < def->min_args) {
+			fprintf(stderr, "Command %s requires at least %d arguments, but only %d provided\n", name, def->min_args, num_args);
+			goto cleanup_args;
+		}
+		out->raw = NULL;
+		out->args = args;
+		out->num_args = num_args;
+	}
+	out->def = def;
+	out->format = format;
+	
+	ret = 1;
+cleanup_args:
+	if (!ret) {
+		for (int i = 0; i < num_args; i++)
+		{
+			free_expr(args[i].parsed);
+			free(args[i].raw);
+		}
+		free(args);
 	}
-	char *after;
-	uint8_t at_least_one = 0;
-	while (*param && *param != '\n')
+cleanup_name:
+	free(name);
+	return ret;
+}
+
+static void free_parsed_command(parsed_command *cmd)
+{
+	free(cmd->format);
+	free(cmd->raw);
+	for (int i = 0; i < cmd->num_args; i++)
 	{
-		at_least_one = 1;
-		expr *e = parse_expression(param, &after);
-		if (e) {
-			if (!eval_expr(root, e, &value)) {
-				fprintf(stderr, "Failed to eval %s\n", param);
+		free(cmd->args[i].raw);
+		free_expr(cmd->args[i].parsed);
+	}
+	free(cmd->args);
+}
+
+static uint8_t cmd_quit(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	exit(0);
+}
+
+typedef struct {
+	size_t num_commands;
+	size_t longest_command;
+} help_state;
+
+static void help_first_pass(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	command_def *def = val.ptrval;
+	if (def->visited) {
+		return;
+	}
+	def->visited = 1;
+	help_state *state = data;
+	state->num_commands++;
+	size_t len = strlen(def->usage);
+	if (len > state->longest_command) {
+		state->longest_command = len;
+	}
+}
+
+static void help_reset_visited(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	command_def *def = val.ptrval;
+	def->visited = 0;
+}
+
+static void help_second_pass(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	command_def *def = val.ptrval;
+	if (def->visited) {
+		return;
+	}
+	def->visited = 1;
+	help_state *state = data;
+	size_t len = strlen(def->usage);
+	printf("  %s", def->usage);
+	while (len < state->longest_command) {
+		putchar(' ');
+		len++;
+	}
+	int remaining = 80 - state->longest_command - 5;
+	const char *extra_desc = NULL;
+	if (strlen(def->desc) <= remaining) {
+		printf(" - %s\n", def->desc);
+	} else {
+		char split[76];
+		int split_point = remaining;
+		while (split_point > 0 && !isspace(def->desc[split_point]))
+		{
+			--split_point;
+		}
+		if (!split_point) {
+			split_point = remaining;
+		}
+		memcpy(split, def->desc, split_point);
+		extra_desc = def->desc + split_point + 1;
+		split[split_point] = 0;
+		printf(" - %s\n", split);
+	}
+	if (def->names[1]) {
+		fputs("    Aliases: ", stdout);
+		len = strlen("    Aliases: ");
+		const char **name = def->names + 1;
+		uint8_t first = 1;
+		while (*name)
+		{
+			if (first) {
+				first = 0;
+			} else {
+				putchar(',');
+				putchar(' ');
+				len += 2;
 			}
-			free_expr(e);
-		} else {
-			fprintf(stderr, "Failed to parse %s\n", param);
+			fputs(*name, stdout);
+			len += strlen(*name);
+			++name;
+		}
+	} else {
+		len = 0;
+	}
+	if (extra_desc) {
+		while (len < state->longest_command + 5) {
+			putchar(' ');
+			len++;
 		}
-		char *tmp_param = malloc(after-param+1);
-		memcpy(tmp_param, param, after-param);
-		tmp_param[after-param] = 0;
-		param = after;
-		if (format_char == 's') {
+		fputs(extra_desc, stdout);
+	}
+	putchar('\n');
+}
+
+static uint8_t cmd_help(debug_root *root, char *format, char *param)
+{
+	help_state state = {0,0};
+	tern_foreach(root->commands, help_first_pass, &state);
+	tern_foreach(root->commands, help_reset_visited, &state);
+	tern_foreach(root->commands, help_second_pass, &state);
+	tern_foreach(root->commands, help_reset_visited, &state);
+	return 1;
+}
+
+static uint8_t cmd_continue(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	return 0;
+}
+
+static uint8_t cmd_print(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	char format_str[8];
+	strcpy(format_str, "%s: %d\n");
+	if (format) {
+		switch (format[0])
+		{
+		case 'x':
+		case 'X':
+		case 'd':
+		case 'c':
+		case 's':
+			format_str[5] = format[0];
+			break;
+		default:
+			fprintf(stderr, "Unrecognized format character: %c\n", format[0]);
+		}
+	}
+	for (int i = 0; i < num_args; i++)
+	{
+		if (format && format[0] == 's') {
 			char tmp[128];
-			int i;
-			for (i = 0; i < sizeof(tmp)-1; i++, value++)
+			int j;
+			uint32_t addr = args[i].value;
+			for (j = 0; j < sizeof(tmp)-1; j++, addr++)
 			{
-				uint32_t addr = value;
-				root->read_mem(root, &addr, 'b');
+				uint32_t tmp_addr = addr;
+				root->read_mem(root, &tmp_addr, 'b');
 				char c = addr;
 				if (c < 0x20 || c > 0x7F) {
 					break;
 				}
-				tmp[i] = c;
+				tmp[j] = c;
+			}
+			tmp[j] = 0;
+			printf(format_str, args[i].raw, tmp);
+		} else {
+			printf(format_str, args[i].raw, args[i].value);
+		}
+	}
+	return 1;
+}
+
+static uint8_t cmd_display(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	cmd_print(root, format, num_args, args);
+	disp_def *ndisp = calloc(1, sizeof(*ndisp));
+	ndisp->next = root->displays;
+	ndisp->index = root->disp_index++;
+	ndisp->format = format ? strdup(format) : NULL;
+	ndisp->num_args = num_args;
+	ndisp->args = calloc(num_args, sizeof(command_arg));
+	memcpy(ndisp->args, args, num_args * sizeof(command_arg));
+	memset(args, 0, num_args * sizeof(command_arg));
+	root->displays = ndisp;
+	printf("Added display %d\n", ndisp->index);
+	return 1;
+}
+
+static uint8_t cmd_delete_display(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	disp_def **cur = &root->displays;
+	while (*cur)
+	{
+		if ((*cur)->index == args[0].value) {
+			disp_def *del_disp = *cur;
+			*cur = del_disp->next;
+			free(del_disp->format);
+			for (int i = 0; i < del_disp->num_args; i++)
+			{
+				free(del_disp->args[i].raw);
+				free_expr(del_disp->args[i].parsed);
 			}
-			tmp[i] = 0;
-			printf(format, tmp_param, tmp);
+			free(del_disp->args);
+			free(del_disp);
+			break;
+		} else {
+			cur = &(*cur)->next;
+		}
+	}
+	return 1;
+}
+
+static uint8_t cmd_softreset(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	if (current_system->soft_reset) {
+		current_system->soft_reset(current_system);
+		return 0;
+	} else {
+		fputs("Current system does not support soft reset", stderr);
+		return 1;
+	}
+}
+
+static uint8_t cmd_command(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	bp_def **target = find_breakpoint_idx(&root->breakpoints, args[0].value);
+	if (!target) {
+		fprintf(stderr, "Breakpoint %d does not exist!\n", args[0].value);
+		return 1;
+	}
+	printf("Enter commands for breakpoing %d, type end when done\n", args[0].value);
+	char cmd_buf[1024];
+	char *commands = NULL;
+	uint32_t cmd_storage = 4;
+	for (uint32_t i = 0; i < (*target)->num_commands; i++)
+	{
+		free_parsed_command((*target)->commands + i);
+	}
+	free((*target)->commands);
+	(*target)->commands = calloc(cmd_storage, sizeof(parsed_command));
+	for (;;)
+	{
+		fputs(">>", stdout);
+		fflush(stdout);
+		fgets(cmd_buf, sizeof(cmd_buf), stdin);
+		if (!strcmp(cmd_buf, "end\n")) {
+			return 1;
+		} else {
+			if ((*target)->num_commands == cmd_storage) {
+				cmd_storage *= 2;
+				(*target)->commands = realloc((*target)->commands, cmd_storage * sizeof(parsed_command));
+			}
+			if (parse_command(root, cmd_buf, (*target)->commands + (*target)->num_commands)) {
+				++(*target)->num_commands;
+			}
+		}
+	}
+}
+
+const char *expr_type_names[] = {
+	"EXPR_NONE",
+	"EXPR_SCALAR",
+	"EXPR_UNARY",
+	"EXPR_BINARY",
+	"EXPR_SIZE",
+	"EXPR_MEM"
+};
+
+static uint8_t run_command(debug_root *root, parsed_command *cmd)
+{
+	if (cmd->def->raw_impl) {
+		return cmd->def->raw_impl(root, cmd->format, cmd->raw);
+	} else {
+		for (int i = 0; i < cmd->num_args; i++)
+		{
+			if (!eval_expr(root, cmd->args[i].parsed, &cmd->args[i].value)) {
+				fprintf(stderr, "Failed to eval %s\n", cmd->args[i].raw);
+				return 1;
+			}
+		}
+		return cmd->def->impl(root, cmd->format, cmd->num_args, cmd->args);
+	}
+}
+
+static uint8_t cmd_set(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	char *name = NULL;
+	char size = 0;
+	uint32_t address;
+	switch (args[0].parsed->type)
+	{
+	case EXPR_SCALAR:
+		if (args[0].parsed->op.type == TOKEN_NAME) {
+			name = args[0].parsed->op.v.str;
+		} else {
+			fputs("First argument to set must be a name or memory expression, not a number", stderr);
+			return 1;
+		}
+		break;
+	case EXPR_SIZE:
+		size = args[0].parsed->op.v.op[0];
+		if (args[0].parsed->left->op.type == TOKEN_NAME) {
+			name = args[0].parsed->left->op.v.str;
+		} else {
+			fputs("First argument to set must be a name or memory expression, not a number", stderr);
+			return 1;
+		}
+		break;
+	case EXPR_MEM:
+		size = args[0].parsed->op.v.op[0];
+		if (!eval_expr(root, args[0].parsed->left, &address)) {
+			fprintf(stderr, "Failed to eval %s\n", args[0].raw);
+			return 1;
+		}
+		break;
+	default:
+		fprintf(stderr, "First argument to set must be a name or memory expression, got %s\n", expr_type_names[args[0].parsed->type]);
+		return 1;
+	}
+	if (!eval_expr(root, args[1].parsed, &args[1].value)) {
+		fprintf(stderr, "Failed to eval %s\n", args[1].raw);
+		return 1;
+	}
+	uint32_t value = args[1].value;
+	if (name && size && size != 'l') {
+		uint32_t old;
+		if (!root->resolve(root, name, &old)) {
+			fprintf(stderr, "Failed to eval %s\n", name);
+			return 1;
+		}
+		if (size == 'b') {
+			old &= 0xFFFFFF00;
+			value &= 0xFF;
+			value |= old;
 		} else {
-			printf(format, tmp_param, value);
+			old &= 0xFFFF0000;
+			value &= 0xFFFF;
+			value |= old;
+		}
+	}
+	if (name) {
+		if (!root->set(root, name, value)) {
+			fprintf(stderr, "Failed to set %s\n", name);
+		}
+	} else if (!root->write_mem(root, address, value, size)) {
+		fprintf(stderr, "Failed to write to address %X\n", address);
+	}
+	return 1;
+}
+
+static uint8_t cmd_delete_m68k(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	bp_def **this_bp = find_breakpoint_idx(&root->breakpoints, args[0].value);
+	if (!*this_bp) {
+		fprintf(stderr, "Breakpoint %d does not exist\n", args[0].value);
+		return 1;
+	}
+	bp_def *tmp = *this_bp;
+	remove_breakpoint(root->cpu_context, tmp->address);
+	*this_bp = (*this_bp)->next;
+	if (tmp->commands) {
+		for (uint32_t i = 0; i < tmp->num_commands; i++)
+		{
+			free_parsed_command(tmp->commands + i);
+		}
+		free(tmp->commands);
+	}
+	free(tmp);
+	return 1;
+}
+
+static uint8_t cmd_breakpoint_m68k(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	insert_breakpoint(root->cpu_context, args[0].value, debugger);
+	bp_def *new_bp = calloc(1, sizeof(bp_def));
+	new_bp->next = root->breakpoints;
+	new_bp->address = args[0].value;
+	new_bp->index = root->bp_index++;
+	root->breakpoints = new_bp;
+	printf("68K Breakpoint %d set at %X\n", new_bp->index, args[0].value);
+	return 1;
+}
+
+static uint8_t cmd_advance_m68k(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	insert_breakpoint(root->cpu_context, args[0].value, debugger);
+	return 0;
+}
+
+static uint8_t cmd_step_m68k(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	m68kinst *inst = root->inst;
+	m68k_context *context = root->cpu_context;
+	uint32_t after = root->after;
+	if (inst->op == M68K_RTS) {
+		after = m68k_read_long(context->aregs[7], context);
+	} else if (inst->op == M68K_RTE || inst->op == M68K_RTR) {
+		after = m68k_read_long(context->aregs[7] + 2, context);
+	} else if(m68k_is_branch(inst)) {
+		if (inst->op == M68K_BCC && inst->extra.cond != COND_TRUE) {
+			root->branch_f = after;
+			root->branch_t = m68k_branch_target(inst, context->dregs, context->aregs) & 0xFFFFFF;
+			insert_breakpoint(context, root->branch_t, debugger);
+		} else if(inst->op == M68K_DBCC) {
+			if (inst->extra.cond == COND_FALSE) {
+				if (context->dregs[inst->dst.params.regs.pri] & 0xFFFF) {
+					after = m68k_branch_target(inst, context->dregs, context->aregs);
+				}
+			} else {
+				root->branch_t = after;
+				root->branch_f = m68k_branch_target(inst, context->dregs, context->aregs);
+				insert_breakpoint(context, root->branch_f, debugger);
+			}
+		} else {
+			after = m68k_branch_target(inst, context->dregs, context->aregs) & 0xFFFFFF;
 		}
-		free(tmp_param);
-		while (*param && isblank(*param) && *param != '\n')
-		{
-			++param;
+	}
+	insert_breakpoint(root->cpu_context, after, debugger);
+	return 0;
+}
+
+static uint8_t cmd_over_m68k(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	m68kinst *inst = root->inst;
+	m68k_context *context = root->cpu_context;
+	uint32_t after = root->after;
+	if (inst->op == M68K_RTS) {
+		after = m68k_read_long(context->aregs[7], context);
+	} else if (inst->op == M68K_RTE || inst->op == M68K_RTR) {
+		after = m68k_read_long(context->aregs[7] + 2, context);
+	} else if(m68k_is_noncall_branch(inst)) {
+		if (inst->op == M68K_BCC && inst->extra.cond != COND_TRUE) {
+			root->branch_t = m68k_branch_target(inst, context->dregs, context->aregs)  & 0xFFFFFF;
+			if (root->branch_t < after) {
+					root->branch_t = 0;
+			} else {
+				root->branch_f = after;
+				insert_breakpoint(context, root->branch_t, debugger);
+			}
+		} else if(inst->op == M68K_DBCC) {
+			uint32_t target = m68k_branch_target(inst, context->dregs, context->aregs)  & 0xFFFFFF;
+			if (target > after) {
+				if (inst->extra.cond == COND_FALSE) {
+					after = target;
+				} else {
+					root->branch_f = target;
+					root->branch_t = after;
+					insert_breakpoint(context, root->branch_f, debugger);
+				}
+			}
+		} else {
+			after = m68k_branch_target(inst, context->dregs, context->aregs) & 0xFFFFFF;
+		}
+	}
+	insert_breakpoint(root->cpu_context, after, debugger);
+	return 0;
+}
+
+static uint8_t cmd_next_m68k(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	m68kinst *inst = root->inst;
+	m68k_context *context = root->cpu_context;
+	uint32_t after = root->after;
+	if (inst->op == M68K_RTS) {
+		after = m68k_read_long(context->aregs[7], context);
+	} else if (inst->op == M68K_RTE || inst->op == M68K_RTR) {
+		after = m68k_read_long(context->aregs[7] + 2, context);
+	} else if(m68k_is_noncall_branch(inst)) {
+		if (inst->op == M68K_BCC && inst->extra.cond != COND_TRUE) {
+			root->branch_f = after;
+			root->branch_t = m68k_branch_target(inst, context->dregs, context->aregs);
+			insert_breakpoint(context, root->branch_t, debugger);
+		} else if(inst->op == M68K_DBCC) {
+			if ( inst->extra.cond == COND_FALSE) {
+				if (context->dregs[inst->dst.params.regs.pri] & 0xFFFF) {
+					after = m68k_branch_target(inst, context->dregs, context->aregs);
+				}
+			} else {
+				root->branch_t = after;
+				root->branch_f = m68k_branch_target(inst, context->dregs, context->aregs);
+				insert_breakpoint(context, root->branch_f, debugger);
+			}
+		} else {
+			after = m68k_branch_target(inst, context->dregs, context->aregs) & 0xFFFFFF;
+		}
+	}
+	insert_breakpoint(root->cpu_context, after, debugger);
+	return 0;
+}
+
+static uint8_t cmd_backtrace_m68k(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	m68k_context *context = root->cpu_context;
+	uint32_t stack = context->aregs[7];
+	uint8_t non_adr_count = 0;
+	do {
+		uint32_t bt_address = m68k_instruction_fetch(stack, context);
+		bt_address = get_instruction_start(context->options, bt_address - 2);
+		if (bt_address) {
+			stack += 4;
+			non_adr_count = 0;
+			m68kinst inst;
+			char buf[128];
+			m68k_decode(m68k_instruction_fetch, context, &inst, bt_address);
+			m68k_disasm(&inst, buf);
+			printf("%X: %s\n", bt_address, buf);
+		} else {
+			//non-return address value on stack can be word wide
+			stack += 2;
+			non_adr_count++;
+		}
+		//TODO: Make sure we don't wander into an invalid memory region
+	} while (stack && non_adr_count < 6);
+	return 1;
+}
+
+static uint8_t cmd_vdp_sprites(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	m68k_context *context = root->cpu_context;
+	genesis_context * gen = context->system;
+	vdp_print_sprite_table(gen->vdp);
+	return 1;
+}
+
+static uint8_t cmd_vdp_regs(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	m68k_context *context = root->cpu_context;
+	genesis_context * gen = context->system;
+	vdp_print_reg_explain(gen->vdp);
+	return 1;
+}
+
+static uint8_t cmd_ym_channel(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	m68k_context *context = root->cpu_context;
+	genesis_context * gen = context->system;
+	if (num_args) {
+		ym_print_channel_info(gen->ym, args[0].value - 1);
+	} else {
+		for (int i = 0; i < 6; i++) {
+			ym_print_channel_info(gen->ym, i);
 		}
 	}
-	if (!at_least_one) {
-		fprintf(stderr, "Missing argument to print/%c\n", format_char);
+	return 1;
+}
+
+static uint8_t cmd_ym_timer(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	m68k_context *context = root->cpu_context;
+	genesis_context * gen = context->system;
+	ym_print_timer_info(gen->ym);
+	return 1;
+}
+
+static uint8_t cmd_sub(debug_root *root, char *format, char *param)
+{
+	while (param && *param && isblank(*param))
+	{
+		++param;
+	}
+	m68k_context *m68k = root->cpu_context;
+	genesis_context *gen = m68k->system;
+	segacd_context *cd = gen->expansion;
+	if (param && *param && !isspace(*param)) {
+		parsed_command cmd;
+		debug_root *sub_root = find_m68k_root(cd->m68k);
+		if (!sub_root) {
+			fputs("Failed to get debug root for Sub CPU\n", stderr);
+			return 1;
+		}
+		if (!parse_command(sub_root, param, &cmd)) {
+			return 1;
+		}
+		uint8_t ret = run_command(sub_root, &cmd);
+		free_parsed_command(&cmd);
+		return ret;
+	} else {
+		cd->enter_debugger = 1;
+		return 0;
+	}
+}
+
+static uint8_t cmd_main(debug_root *root, char *format, char *param)
+{
+	while (param && *param && isblank(*param))
+	{
+		++param;
+	}
+	m68k_context *m68k = root->cpu_context;
+	segacd_context *cd = m68k->system;
+	
+	if (param && *param && !isspace(*param)) {
+		parsed_command cmd;
+		debug_root *main_root = find_m68k_root(cd->genesis->m68k);
+		if (!main_root) {
+			fputs("Failed to get debug root for Main CPU\n", stderr);
+			return 1;
+		}
+		if (!parse_command(main_root, param, &cmd)) {
+			return 1;
+		}
+		uint8_t ret = run_command(main_root, &cmd);
+		free_parsed_command(&cmd);
+		return ret;
+	} else {
+		cd->genesis->header.enter_debugger = 1;
+		return 0;
+	}
+}
+
+static uint8_t cmd_gen_z80(debug_root *root, char *format, char *param)
+{
+	while (param && *param && isblank(*param))
+	{
+		++param;
+	}
+	m68k_context *m68k = root->cpu_context;
+	genesis_context *gen = m68k->system;
+	
+	if (param && *param && !isspace(*param)) {
+		parsed_command cmd;
+		debug_root *z80_root = find_z80_root(gen->z80);
+		if (!z80_root) {
+			fputs("Failed to get debug root for Z80\n", stderr);
+			return 1;
+		}
+		if (!parse_command(z80_root, param, &cmd)) {
+			return 1;
+		}
+		uint8_t ret = run_command(z80_root, &cmd);
+		free_parsed_command(&cmd);
+		return ret;
+	} else {
+		fputs("not implemented yet", stderr);
+		return 0;
+	}
+}
+
+command_def common_commands[] = {
+	{
+		.names = (const char *[]){
+			"quit", NULL
+		},
+		.usage = "quit",
+		.desc = "Quit BlastEm",
+		.impl = cmd_quit,
+		.min_args = 0,
+		.max_args = 0,
+	},
+	{
+		.names = (const char *[]){
+			"help", "?", NULL
+		},
+		.usage = "help",
+		.desc = "Print a list of available commands for the current debug context",
+		.raw_impl = cmd_help,
+		.min_args = 0,
+		.max_args = 1
+	},
+	{
+		.names = (const char *[]){
+			"continue", "c", NULL
+		},
+		.usage = "continue",
+		.desc = "Resume execution",
+		.impl = cmd_continue,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"print", NULL
+		},
+		.usage = "print[/FORMAT] EXPRESSION...",
+		.desc = "Print one or more expressions using the optional format character",
+		.impl = cmd_print,
+		.min_args = 1,
+		.max_args = -1
+	},
+	{
+		.names = (const char *[]){
+			"softreset", "sr", NULL
+		},
+		.usage = "softreset",
+		.desc = "Perform a soft-reset for the current system",
+		.impl = cmd_softreset,
+		.min_args = 0,
+		.max_args = 0,
+	},
+	{
+		.names = (const char *[]){
+			"display", NULL
+		},
+		.usage = "display[/FORMAT] EXPRESSION...",
+		.desc = "Print one or more expressions every time the debugger is entered",
+		.impl = cmd_display,
+		.min_args = 1,
+		.max_args = -1
+	},
+	{
+		.names = (const char *[]){
+			"deletedisplay", "dd", NULL
+		},
+		.usage = "deletedisplay DISPLAYNUM",
+		.desc = "Remove expressions added with the `display` command",
+		.impl = cmd_delete_display,
+		.min_args = 1,
+		.max_args = 1
+	},
+	{
+		.names = (const char *[]){
+			"commands", NULL
+		},
+		.usage = "command BREAKPOINT",
+		.desc = "Set a list of debugger commands to be executed when the given breakpoint is hit",
+		.impl = cmd_command,
+		.min_args = 1,
+		.max_args = 1
+	},
+	{
+		.names = (const char *[]){
+			"set", NULL
+		},
+		.usage = "set MEM|NAME VALUE",
+		.desc = "Set a register, symbol or memory location to the result of evaluating VALUE",
+		.impl = cmd_set,
+		.min_args = 2,
+		.max_args = 2,
+		.skip_eval = 1
+	}
+};
+#define NUM_COMMON (sizeof(common_commands)/sizeof(*common_commands))
+
+command_def m68k_commands[] = {
+	{
+		.names = (const char *[]){
+			"breakpoint", "b", NULL
+		},
+		.usage = "breakpoint ADDRESSS",
+		.desc = "Set a breakpoint at ADDRESS",
+		.impl = cmd_breakpoint_m68k,
+		.min_args = 1,
+		.max_args = 1
+	},
+	{
+		.names = (const char *[]){
+			"advance", NULL
+		},
+		.usage = "advance ADDRESS",
+		.desc = "Advance to ADDRESS",
+		.impl = cmd_advance_m68k,
+		.min_args = 1,
+		.max_args = 1
+	},
+	{
+		.names = (const char *[]){
+			"step", "s", NULL
+		},
+		.usage = "step",
+		.desc = "Advance to the next instruction, stepping into subroutines",
+		.impl = cmd_step_m68k,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"over", NULL
+		},
+		.usage = "over",
+		.desc = "Advance to the next instruction, ignoring branches to lower addresses",
+		.impl = cmd_over_m68k,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"next", NULL
+		},
+		.usage = "next",
+		.desc = "Advance to the next instruction",
+		.impl = cmd_next_m68k,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"backtrace", "bt", NULL
+		},
+		.usage = "backtrace",
+		.desc = "Print a backtrace",
+		.impl = cmd_backtrace_m68k,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"delete", "d", NULL
+		},
+		.usage = "delete BREAKPOINT",
+		.desc = "Remove breakpoint identified by BREAKPOINT",
+		.impl = cmd_delete_m68k,
+		.min_args = 1,
+		.max_args = 1
 	}
+};
+
+#define NUM_68K (sizeof(m68k_commands)/sizeof(*m68k_commands))
+
+command_def genesis_commands[] = {
+	{
+		.names = (const char *[]){
+			"vdpsprites", "vs", NULL
+		},
+		.usage = "vdpsprites",
+		.desc = "Print the VDP sprite table",
+		.impl = cmd_vdp_sprites,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"vdpsregs", "vr", NULL
+		},
+		.usage = "vdpregs",
+		.desc = "Print VDP register values with a short description",
+		.impl = cmd_vdp_regs,
+		.min_args = 0,
+		.max_args = 0
+	},
+#ifndef NO_Z80
+	{
+		.names = (const char *[]){
+			"z80", NULL
+		},
+		.usage = "z80 [COMMAND]",
+		.desc = "Run a Z80 debugger command or switch to Z80 context when no command is given",
+		.raw_impl = cmd_gen_z80,
+		.min_args = 0,
+		.max_args = -1
+	},
+#endif
+	{
+		.names = (const char *[]){
+			"ymchannel", "yc", NULL
+		},
+		.usage = "ymchannel [CHANNEL]",
+		.desc = "Print YM-2612 channel and operator params. Limited to CHANNEL if specified",
+		.impl = cmd_ym_channel,
+		.min_args = 0,
+		.max_args = 1
+	},
+	{
+		.names = (const char *[]){
+			"ymtimer", "yt", NULL
+		},
+		.usage = "ymtimer",
+		.desc = "Print YM-2612 timer info",
+		.impl = cmd_ym_timer,
+		.min_args = 0,
+		.max_args = 0
+	}
+};
+
+#define NUM_GENESIS (sizeof(genesis_commands)/sizeof(*genesis_commands))
+
+command_def scd_main_commands[] = {
+	{
+		.names = (const char *[]){
+			"subcpu", NULL
+		},
+		.usage = "subcpu [COMMAND]",
+		.desc = "Run a Sub-CPU debugger command or switch to Sub-CPU context when no command is given",
+		.raw_impl = cmd_sub,
+		.min_args = 0,
+		.max_args = -1
+	}
+};
+
+#define NUM_SCD_MAIN (sizeof(scd_main_commands)/sizeof(*scd_main_commands))
+
+command_def scd_sub_commands[] = {
+	{
+		.names = (const char *[]){
+			"maincpu", NULL
+		},
+		.usage = "maincpu [COMMAND]",
+		.desc = "Run a Main-CPU debugger command or switch to Main-CPU context when no command is given",
+		.raw_impl = cmd_main,
+		.min_args = 0,
+		.max_args = -1
+	}
+};
+
+#define NUM_SCD_SUB (sizeof(scd_main_commands)/sizeof(*scd_main_commands))
+
+#ifndef NO_Z80
+
+static uint8_t cmd_delete_z80(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	bp_def **this_bp = find_breakpoint_idx(&root->breakpoints, args[0].value);
+	if (!*this_bp) {
+		fprintf(stderr, "Breakpoint %d does not exist\n", args[0].value);
+		return 1;
+	}
+	bp_def *tmp = *this_bp;
+	zremove_breakpoint(root->cpu_context, tmp->address);
+	*this_bp = (*this_bp)->next;
+	if (tmp->commands) {
+		for (uint32_t i = 0; i < tmp->num_commands; i++)
+		{
+			free_parsed_command(tmp->commands + i);
+		}
+		free(tmp->commands);
+	}
+	free(tmp);
+	return 1;
+}
+
+static uint8_t cmd_breakpoint_z80(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	zinsert_breakpoint(root->cpu_context, args[0].value, (uint8_t *)zdebugger);
+	bp_def *new_bp = calloc(1, sizeof(bp_def));
+	new_bp->next = root->breakpoints;
+	new_bp->address = args[0].value;
+	new_bp->index = root->bp_index++;
+	root->breakpoints = new_bp;
+	printf("Z80 Breakpoint %d set at %X\n", new_bp->index, args[0].value);
+	return 1;
+}
+
+static uint8_t cmd_advance_z80(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	zinsert_breakpoint(root->cpu_context, args[0].value, (uint8_t *)zdebugger);
+	return 0;
+}
+
+static uint8_t cmd_step_z80(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	z80inst *inst = root->inst;
+	z80_context *context = root->cpu_context;
+	uint32_t after = root->after;
+	//TODO: handle conditional branches
+	if (inst->op == Z80_JP || inst->op == Z80_CALL || inst->op == Z80_RST) {
+		if (inst->addr_mode == Z80_IMMED) {
+			after = inst->immed;
+		} else if (inst->ea_reg == Z80_HL) {
+#ifndef NEW_CORE
+			after = context->regs[Z80_H] << 8 | context->regs[Z80_L];
+		} else if (inst->ea_reg == Z80_IX) {
+			after = context->regs[Z80_IXH] << 8 | context->regs[Z80_IXL];
+		} else if (inst->ea_reg == Z80_IY) {
+			after = context->regs[Z80_IYH] << 8 | context->regs[Z80_IYL];
+#endif
+		}
+	} else if(inst->op == Z80_JR) {
+		after += inst->immed;
+	} else if(inst->op == Z80_RET) {
+		uint8_t *sp = get_native_pointer(context->sp, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
+		if (sp) {
+			after = *sp;
+			sp = get_native_pointer((context->sp + 1) & 0xFFFF, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
+			if (sp) {
+				after |= *sp << 8;
+			}
+		}
+	}
+	zinsert_breakpoint(context, after, (uint8_t *)zdebugger);
+	return 0;
+}
+
+static uint8_t cmd_over_z80(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	fputs("not implemented yet\n", stderr);
+	return 1;
+}
+
+static uint8_t cmd_next_z80(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	z80inst *inst = root->inst;
+	z80_context *context = root->cpu_context;
+	uint32_t after = root->after;
+	//TODO: handle conditional branches
+	if (inst->op == Z80_JP) {
+		if (inst->addr_mode == Z80_IMMED) {
+			after = inst->immed;
+		} else if (inst->ea_reg == Z80_HL) {
+#ifndef NEW_CORE
+			after = context->regs[Z80_H] << 8 | context->regs[Z80_L];
+		} else if (inst->ea_reg == Z80_IX) {
+			after = context->regs[Z80_IXH] << 8 | context->regs[Z80_IXL];
+		} else if (inst->ea_reg == Z80_IY) {
+			after = context->regs[Z80_IYH] << 8 | context->regs[Z80_IYL];
+#endif
+		}
+	} else if(inst->op == Z80_JR) {
+		after += inst->immed;
+	} else if(inst->op == Z80_RET) {
+		uint8_t *sp = get_native_pointer(context->sp, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
+		if (sp) {
+			after = *sp;
+			sp = get_native_pointer((context->sp + 1) & 0xFFFF, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
+			if (sp) {
+				after |= *sp << 8;
+			}
+		}
+	}
+	zinsert_breakpoint(context, after, (uint8_t *)zdebugger);
+	return 0;
+}
+
+static uint8_t cmd_backtrace_z80(debug_root *root, char *format, int num_args, command_arg *args)
+{
+	z80_context *context = root->cpu_context;
+	uint32_t stack = context->sp;
+	uint8_t non_adr_count = 0;
+	do {
+		uint32_t bt_address = stack;
+		if (!root->read_mem(root, &bt_address, 'w')) {
+			break;
+		}
+		bt_address = z80_get_instruction_start(context, bt_address - 1);
+		if (bt_address != 0xFEEDFEED) {
+			stack += 4;
+			non_adr_count = 0;
+			z80inst inst;
+			char buf[128];
+			uint8_t *pc = get_native_pointer(bt_address, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
+			z80_decode(pc, &inst);
+			z80_disasm(&inst, buf, bt_address);
+			printf("%X: %s\n", bt_address, buf);
+		} else {
+			//non-return address value on stack can be byte wide
+			stack++;
+			non_adr_count++;
+		}
+		//TODO: Make sure we don't wander into an invalid memory region
+	} while (stack && non_adr_count < 6);
+	return 1;
+}
+
+command_def z80_commands[] = {
+	{
+		.names = (const char *[]){
+			"breakpoint", "b", NULL
+		},
+		.usage = "breakpoint ADDRESSS",
+		.desc = "Set a breakpoint at ADDRESS",
+		.impl = cmd_breakpoint_z80,
+		.min_args = 1,
+		.max_args = 1
+	},
+	{
+		.names = (const char *[]){
+			"advance", NULL
+		},
+		.usage = "advance ADDRESS",
+		.desc = "Advance to ADDRESS",
+		.impl = cmd_advance_z80,
+		.min_args = 1,
+		.max_args = 1
+	},
+	{
+		.names = (const char *[]){
+			"step", "s", NULL
+		},
+		.usage = "step",
+		.desc = "Advance to the next instruction, stepping into subroutines",
+		.impl = cmd_step_z80,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"over", NULL
+		},
+		.usage = "over",
+		.desc = "Advance to the next instruction, ignoring branches to lower addresses",
+		.impl = cmd_over_z80,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"next", NULL
+		},
+		.usage = "next",
+		.desc = "Advance to the next instruction",
+		.impl = cmd_next_z80,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"backtrace", "bt", NULL
+		},
+		.usage = "backtrace",
+		.desc = "Print a backtrace",
+		.impl = cmd_backtrace_z80,
+		.min_args = 0,
+		.max_args = 0
+	},
+	{
+		.names = (const char *[]){
+			"delete", "d", NULL
+		},
+		.usage = "delete BREAKPOINT",
+		.desc = "Remove breakpoint identified by BREAKPOINT",
+		.impl = cmd_delete_z80,
+		.min_args = 1,
+		.max_args = 1
+	}
+};
+
+#define NUM_Z80 (sizeof(z80_commands)/sizeof(*z80_commands))
+
+#endif
+
+void add_commands(debug_root *root, command_def *defs, uint32_t num_commands)
+{
+	for (uint32_t i = 0; i < num_commands; i++)
+	{
+		for (int j = 0; defs[i].names[j]; j++)
+		{
+			root->commands = tern_insert_ptr(root->commands, defs[i].names[j], defs + i);
+		}
+	}
+}
+
+debug_root *find_m68k_root(m68k_context *context)
+{
+	debug_root *root = find_root(context);
+	if (root && !root->commands) {
+		add_commands(root, common_commands, NUM_COMMON);
+		add_commands(root, m68k_commands, NUM_68K);
+		root->read_mem = read_m68k;
+		root->write_mem = write_m68k;
+		root->set = set_m68k;
+		switch (current_system->type)
+		{
+		case SYSTEM_GENESIS:
+		case SYSTEM_SEGACD:
+			//check if this is the main CPU
+			if (context->system == current_system) {
+				root->resolve = resolve_genesis;
+				add_commands(root, genesis_commands, NUM_GENESIS);
+				if (current_system->type == SYSTEM_SEGACD) {
+					add_commands(root, scd_main_commands, NUM_SCD_MAIN);
+				}
+				break;
+			} else {
+				add_commands(root, scd_sub_commands, NUM_SCD_SUB);
+			}
+		default:
+			root->resolve = resolve_m68k;
+		}
+	}
+	return root;
 }
 
 #ifndef NO_Z80
 
-void zdebugger_print(z80_context * context, char format_char, char * param)
+static uint8_t read_z80(debug_root *root, uint32_t *out, char size)
+{
+	z80_context *context = root->cpu_context;
+	uint32_t address = *out;
+	*out = read_byte(address, (void **)context->mem_pointers, &context->options->gen, context);
+	if (size == 'w') {
+		*out |= read_byte(address + 1, (void **)context->mem_pointers, &context->options->gen, context) << 8;
+	}
+	return 1;
+}
+
+static uint8_t write_z80(debug_root *root, uint32_t address, uint32_t value, char size)
 {
-	uint32_t value;
-	char format[8];
-	strcpy(format, "%s: %d\n");
-	genesis_context *system = context->system;
-	switch (format_char)
+	z80_context *context = root->cpu_context;
+	write_byte(address, value, (void **)context->mem_pointers, &context->options->gen, context);
+	if (size == 'w') {
+		write_byte(address + 1, value >> 8, (void **)context->mem_pointers, &context->options->gen, context);
+	}
+	return 1;
+}
+
+static uint8_t resolve_z80(debug_root *root, const char *name, uint32_t *out)
+{
+	z80_context *context = root->cpu_context;
+	switch (tolower(name[0]))
 	{
-	case 'x':
-	case 'X':
-	case 'd':
-	case 'c':
-		format[5] = format_char;
-		break;
-	case '\0':
-		break;
-	default:
-		fprintf(stderr, "Unrecognized format character: %c\n", format_char);
-	}
-	switch (param[0])
-	{
-#ifndef NEW_CORE
 	case 'a':
-		if (param[1] == 'f') {
-			if(param[2] == '\'') {
-				value = context->alt_regs[Z80_A] << 8;
-				value |= context->alt_flags[ZF_S] << 7;
-				value |= context->alt_flags[ZF_Z] << 6;
-				value |= context->alt_flags[ZF_H] << 4;
-				value |= context->alt_flags[ZF_PV] << 2;
-				value |= context->alt_flags[ZF_N] << 1;
-				value |= context->alt_flags[ZF_C];
+		if (!name[1]) {
+			*out = context->regs[Z80_A];
+		} else if (name[1] == '\'' && !name[2]) {
+			*out = context->alt_regs[Z80_A];
+		} else if (tolower(name[1]) == 'f') {
+			if (!name[2]) {
+				*out = context->regs[Z80_A] << 8;
+				*out |= context->flags[ZF_S] << 7;
+				*out |= context->flags[ZF_Z] << 6;
+				*out |= context->flags[ZF_H] << 4;
+				*out |= context->flags[ZF_PV] << 2;
+				*out |= context->flags[ZF_N] << 1;
+				*out |= context->flags[ZF_C];
+			} else if (name[2] == '\'' && !name[3]) {
+				*out = context->alt_regs[Z80_A] << 8;
+				*out |= context->alt_flags[ZF_S] << 7;
+				*out |= context->alt_flags[ZF_Z] << 6;
+				*out |= context->alt_flags[ZF_H] << 4;
+				*out |= context->alt_flags[ZF_PV] << 2;
+				*out |= context->alt_flags[ZF_N] << 1;
+				*out |= context->alt_flags[ZF_C];
 			} else {
-				value = context->regs[Z80_A] << 8;
-				value |= context->flags[ZF_S] << 7;
-				value |= context->flags[ZF_Z] << 6;
-				value |= context->flags[ZF_H] << 4;
-				value |= context->flags[ZF_PV] << 2;
-				value |= context->flags[ZF_N] << 1;
-				value |= context->flags[ZF_C];
+				return 0;
 			}
-		} else if(param[1] == '\'') {
-			value = context->alt_regs[Z80_A];
 		} else {
-			value = context->regs[Z80_A];
+			return 0;
 		}
 		break;
 	case 'b':
-		if (param[1] == 'c') {
-			if(param[2] == '\'') {
-				value = context->alt_regs[Z80_B] << 8;
-				value |= context->alt_regs[Z80_C];
+		if (!name[1]) {
+			*out = context->regs[Z80_B];
+		} else if (name[1] == '\'' && !name[2]) {
+			*out = context->alt_regs[Z80_B];
+		} else if (tolower(name[1]) == 'c') {
+			if (!name[2]) {
+				*out = context->regs[Z80_B] << 8 | context->regs[Z80_C];
+			} else if (name[2] == '\'' && !name[3]) {
+				*out = context->alt_regs[Z80_B] << 8 | context->alt_regs[Z80_C];
 			} else {
-				value = context->regs[Z80_B] << 8;
-				value |= context->regs[Z80_C];
+				return 0;
 			}
-		} else if(param[1] == '\'') {
-			value = context->alt_regs[Z80_B];
-		} else if(param[1] == 'a') {
-			value = context->bank_reg << 15;
-		} else {
-			value = context->regs[Z80_B];
 		}
 		break;
 	case 'c':
-		if(param[1] == '\'') {
-			value = context->alt_regs[Z80_C];
-		} else if(param[1] == 'y') {
-			value = context->current_cycle;
+		if (!name[1]) {
+			*out = context->regs[Z80_C];
+		} else if (name[1] == '\'' && !name[2]) {
+			*out = context->alt_regs[Z80_C];
 		} else {
-			value = context->regs[Z80_C];
+			return 0;
 		}
 		break;
 	case 'd':
-		if (param[1] == 'e') {
-			if(param[2] == '\'') {
-				value = context->alt_regs[Z80_D] << 8;
-				value |= context->alt_regs[Z80_E];
+		if (!name[1]) {
+			*out = context->regs[Z80_D];
+		} else if (name[1] == '\'' && !name[2]) {
+			*out = context->alt_regs[Z80_D];
+		} else if (tolower(name[1]) == 'e') {
+			if (!name[2]) {
+				*out = context->regs[Z80_D] << 8 | context->regs[Z80_E];
+			} else if (name[2] == '\'' && !name[3]) {
+				*out = context->alt_regs[Z80_D] << 8 | context->alt_regs[Z80_E];
 			} else {
-				value = context->regs[Z80_D] << 8;
-				value |= context->regs[Z80_E];
+				return 0;
 			}
-		} else if(param[1] == '\'') {
-			value = context->alt_regs[Z80_D];
-		} else {
-			value = context->regs[Z80_D];
 		}
 		break;
 	case 'e':
-		if(param[1] == '\'') {
-			value = context->alt_regs[Z80_E];
+		if (!name[1]) {
+			*out = context->regs[Z80_E];
+		} else if (name[1] == '\'' && !name[2]) {
+			*out = context->alt_regs[Z80_E];
 		} else {
-			value = context->regs[Z80_E];
+			return 0;
 		}
 		break;
 	case 'f':
-		if(param[2] == '\'') {
-			value = context->alt_flags[ZF_S] << 7;
-			value |= context->alt_flags[ZF_Z] << 6;
-			value |= context->alt_flags[ZF_H] << 4;
-			value |= context->alt_flags[ZF_PV] << 2;
-			value |= context->alt_flags[ZF_N] << 1;
-			value |= context->alt_flags[ZF_C];
+		if (!name[1]) {
+			*out = context->flags[ZF_S] << 7;
+			*out |= context->flags[ZF_Z] << 6;
+			*out |= context->flags[ZF_H] << 4;
+			*out |= context->flags[ZF_PV] << 2;
+			*out |= context->flags[ZF_N] << 1;
+			*out |= context->flags[ZF_C];
+		} else if (name[1] == '\'' && !name[2]) {
+			*out = context->alt_flags[ZF_S] << 7;
+			*out |= context->alt_flags[ZF_Z] << 6;
+			*out |= context->alt_flags[ZF_H] << 4;
+			*out |= context->alt_flags[ZF_PV] << 2;
+			*out |= context->alt_flags[ZF_N] << 1;
+			*out |= context->alt_flags[ZF_C];
 		} else {
-			value = context->flags[ZF_S] << 7;
-			value |= context->flags[ZF_Z] << 6;
-			value |= context->flags[ZF_H] << 4;
-			value |= context->flags[ZF_PV] << 2;
-			value |= context->flags[ZF_N] << 1;
-			value |= context->flags[ZF_C];
+			return 0;
 		}
 		break;
 	case 'h':
-		if (param[1] == 'l') {
-			if(param[2] == '\'') {
-				value = context->alt_regs[Z80_H] << 8;
-				value |= context->alt_regs[Z80_L];
+		if (!name[1]) {
+			*out = context->regs[Z80_H];
+		} else if (name[1] == '\'' && !name[2]) {
+			*out = context->alt_regs[Z80_H];
+		} else if (tolower(name[1]) == 'e') {
+			if (!name[2]) {
+				*out = context->regs[Z80_H] << 8 | context->regs[Z80_L];
+			} else if (name[2] == '\'' && !name[3]) {
+				*out = context->alt_regs[Z80_H] << 8 | context->alt_regs[Z80_L];
+			} else {
+				return 0;
+			}
+		}
+		break;
+	case 'i':
+		switch (tolower(name[1]))
+		{
+		case 0:
+			*out = context->regs[Z80_I];
+			break;
+		case 'f':
+			if (name[2] != 'f' || name[3] < '1' || name[4]) {
+				return 0;
+			}
+			if (name[3] == '1') {
+				*out = context->iff1;
+			} else if (name[3] == '2') {
+				*out = context->iff2;
 			} else {
-				value = context->regs[Z80_H] << 8;
-				value |= context->regs[Z80_L];
+				return 0;
+			}
+			break;
+		case 'm':
+			if (name[2]) {
+				return 0;
+			}
+			*out = context->im;
+			break;
+		case 'n':
+			if (strcasecmp(name +2, "t_cycle")) {
+				return 0;
+			}
+			*out = context->int_cycle;
+			break;
+		case 'r':
+			if (name[2]) {
+				return 0;
 			}
-		} else if(param[1] == '\'') {
-			value = context->alt_regs[Z80_H];
-		} else {
-			value = context->regs[Z80_H];
+			*out = context->regs[Z80_I] << 8 | context->regs[Z80_R];
+			break;
+		case 'x':
+			switch (tolower(name[2]))
+			{
+			case 0:
+				*out = context->regs[Z80_IXH] << 8 | context->regs[Z80_IXL];
+				break;
+			case 'h':
+				if (name[3]) {
+					return 0;
+				}
+				*out = context->regs[Z80_IXH];
+			case 'l':
+				if (name[3]) {
+					return 0;
+				}
+				*out = context->regs[Z80_IXL];
+			default:
+				return 0;
+			}
+			break;
+		case 'y':
+			switch (tolower(name[2]))
+			{
+			case 0:
+				*out = context->regs[Z80_IYH] << 8 | context->regs[Z80_IYL];
+				break;
+			case 'h':
+				if (name[3]) {
+					return 0;
+				}
+				*out = context->regs[Z80_IYH];
+			case 'l':
+				if (name[3]) {
+					return 0;
+				}
+				*out = context->regs[Z80_IYL];
+			default:
+				return 0;
+			}
+			break;
+		default:
+			return 0;
 		}
 		break;
 	case 'l':
-		if(param[1] == '\'') {
-			value = context->alt_regs[Z80_L];
+		if (!name[1]) {
+			*out = context->regs[Z80_L];
+		} else if (name[1] == '\'' && !name[2]) {
+			*out = context->alt_regs[Z80_L];
+		} else {
+			return 0;
+		}
+		break;
+	case 'p':
+		if (tolower(name[1]) != 'c' || name[2]) {
+			return 0;
+		}
+		*out = root->address;
+		break;
+	case 'r':
+		if (name[1]) {
+			return 0;
+		}
+		*out = context->regs[Z80_R];
+	case 's':
+		if (tolower(name[1]) != 'p' || name[2]) {
+			return 0;
+		}
+		*out = context->sp;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static uint8_t set_z80(debug_root *root, const char *name, uint32_t value)
+{
+	z80_context *context = root->cpu_context;
+	switch (tolower(name[0]))
+	{
+	case 'a':
+		if (!name[1]) {
+			context->regs[Z80_A] = value;
+		} else if (name[1] == '\'' && !name[2]) {
+			context->alt_regs[Z80_A] = value;
+		} else if (tolower(name[1]) == 'f') {
+			if (!name[2]) {
+				context->regs[Z80_A] = value >> 8;
+				context->flags[ZF_S] = value >> 7 & 1;
+				context->flags[ZF_Z] = value >> 6 & 1;
+				context->flags[ZF_H] = value >> 4 & 1;
+				context->flags[ZF_PV] = value >> 2 & 1;
+				context->flags[ZF_N] = value >> 1 & 1;
+				context->flags[ZF_C] = value & 1;
+			} else if (name[2] == '\'' && !name[3]) {
+				context->alt_regs[Z80_A] = value >> 8;
+				context->alt_flags[ZF_S] = value >> 7 & 1;
+				context->alt_flags[ZF_Z] = value >> 6 & 1;
+				context->alt_flags[ZF_H] = value >> 4 & 1;
+				context->alt_flags[ZF_PV] = value >> 2 & 1;
+				context->alt_flags[ZF_N] = value >> 1 & 1;
+				context->alt_flags[ZF_C] = value & 1;
+			} else {
+				return 0;
+			}
 		} else {
-			value = context->regs[Z80_L];
+			return 0;
+		}
+		break;
+	case 'b':
+		if (!name[1]) {
+			context->regs[Z80_B] = value;
+		} else if (name[1] == '\'' && !name[2]) {
+			context->alt_regs[Z80_B] = value;
+		} else if (tolower(name[1]) == 'c') {
+			if (!name[2]) {
+				context->regs[Z80_B] = value >> 8;
+				context->regs[Z80_C] = value;
+			} else if (name[2] == '\'' && !name[3]) {
+				context->alt_regs[Z80_B] = value >> 8;
+				context->alt_regs[Z80_C] = value;
+			} else {
+				return 0;
+			}
+		}
+		break;
+	case 'c':
+		if (!name[1]) {
+			context->regs[Z80_C] = value;
+		} else if (name[1] == '\'' && !name[2]) {
+			context->alt_regs[Z80_C] = value;
+		} else {
+			return 0;
+		}
+		break;
+	case 'd':
+		if (!name[1]) {
+			context->regs[Z80_D] = value;
+		} else if (name[1] == '\'' && !name[2]) {
+			context->alt_regs[Z80_D] = value;
+		} else if (tolower(name[1]) == 'e') {
+			if (!name[2]) {
+				context->regs[Z80_D] = value >> 8;
+				context->regs[Z80_E] = value;
+			} else if (name[2] == '\'' && !name[3]) {
+				context->alt_regs[Z80_D] = value >> 8;
+				context->alt_regs[Z80_E] = value;
+			} else {
+				return 0;
+			}
+		}
+		break;
+	case 'e':
+		if (!name[1]) {
+			context->regs[Z80_E] = value;
+		} else if (name[1] == '\'' && !name[2]) {
+			context->alt_regs[Z80_E] = value;
+		} else {
+			return 0;
+		}
+		break;
+	case 'f':
+		if (!name[1]) {
+			context->flags[ZF_S] = value >> 7 & 1;
+			context->flags[ZF_Z] = value >> 6 & 1;
+			context->flags[ZF_H] = value >> 4 & 1;
+			context->flags[ZF_PV] = value >> 2 & 1;
+			context->flags[ZF_N] = value >> 1 & 1;
+			context->flags[ZF_C] = value & 1;
+		} else if (name[1] == '\'' && !name[2]) {
+			context->alt_flags[ZF_S] = value >> 7 & 1;
+			context->alt_flags[ZF_Z] = value >> 6 & 1;
+			context->alt_flags[ZF_H] = value >> 4 & 1;
+			context->alt_flags[ZF_PV] = value >> 2 & 1;
+			context->alt_flags[ZF_N] = value >> 1 & 1;
+			context->alt_flags[ZF_C] = value & 1;
+		} else {
+			return 0;
+		}
+		break;
+	case 'h':
+		if (!name[1]) {
+			context->regs[Z80_H] = value;
+		} else if (name[1] == '\'' && !name[2]) {
+			context->alt_regs[Z80_H] = value;
+		} else if (tolower(name[1]) == 'e') {
+			if (!name[2]) {
+				context->regs[Z80_H] = value >> 8;
+				context->regs[Z80_L] = value;
+			} else if (name[2] == '\'' && !name[3]) {
+				context->alt_regs[Z80_H] = value >> 8;
+				context->alt_regs[Z80_L] = value;
+			} else {
+				return 0;
+			}
 		}
 		break;
 	case 'i':
-		if(param[1] == 'x') {
-			if (param[2] == 'h') {
-				value = context->regs[Z80_IXH];
-			} else if(param[2] == 'l') {
-				value = context->regs[Z80_IXL];
+		switch (tolower(name[1]))
+		{
+		case 0:
+			context->regs[Z80_I] = value;
+			break;
+		case 'f':
+			if (name[2] != 'f' || name[3] < '1' || name[4]) {
+				return 0;
+			}
+			if (name[3] == '1') {
+				context->iff1 = value != 0;
+			} else if (name[3] == '2') {
+				context->iff2 = value != 0;
 			} else {
-				value = context->regs[Z80_IXH] << 8;
-				value |= context->regs[Z80_IXL];
+				return 0;
+			}
+			break;
+		case 'm':
+			if (name[2]) {
+				return 0;
+			}
+			context->im = value & 3;
+			break;
+		case 'r':
+			if (name[2]) {
+				return 0;
 			}
-		} else if(param[1] == 'y') {
-			if (param[2] == 'h') {
-				value = context->regs[Z80_IYH];
-			} else if(param[2] == 'l') {
-				value = context->regs[Z80_IYL];
-			} else {
-				value = context->regs[Z80_IYH] << 8;
-				value |= context->regs[Z80_IYL];
+			context->regs[Z80_I] = value >> 8;
+			context->regs[Z80_R] = value;
+			break;
+		case 'x':
+			switch (tolower(name[2]))
+			{
+			case 0:
+				context->regs[Z80_IXH] = value >> 8;
+				context->regs[Z80_IXL] = value;
+				break;
+			case 'h':
+				if (name[3]) {
+					return 0;
+				}
+				context->regs[Z80_IXH] = value;
+			case 'l':
+				if (name[3]) {
+					return 0;
+				}
+				context->regs[Z80_IXL] = value;
+			default:
+				return 0;
 			}
-		} else if(param[1] == 'n') {
-			value = context->int_cycle;
-		} else if(param[1] == 'f' && param[2] == 'f' && param[3] == '1') {
-			value = context->iff1;
-		} else if(param[1] == 'f' && param[2] == 'f' && param[3] == '2') {
-			value = context->iff2;
-		} else {
-			value = context->im;
+			break;
+		case 'y':
+			switch (tolower(name[2]))
+			{
+			case 0:
+				context->regs[Z80_IYH] = value >> 8;
+				context->regs[Z80_IYL] = value;
+				break;
+			case 'h':
+				if (name[3]) {
+					return 0;
+				}
+				context->regs[Z80_IYH] = value;
+			case 'l':
+				if (name[3]) {
+					return 0;
+				}
+				context->regs[Z80_IYL] = value;
+			default:
+				return 0;
+			}
+			break;
+		default:
+			return 0;
 		}
 		break;
-#endif
-	case 's':
-		if (param[1] == 'p') {
-			value = context->sp;
+	case 'l':
+		if (!name[1]) {
+			context->regs[Z80_L] = value;
+		} else if (name[1] == '\'' && !name[2]) {
+			context->alt_regs[Z80_L] = value;
+		} else {
+			return 0;
 		}
 		break;
-	case '0':
-		if (param[1] == 'x') {
-			uint16_t p_addr = strtol(param+2, NULL, 16);
-			value = read_byte(p_addr, (void **)context->mem_pointers, &context->options->gen, context);
+	case 'r':
+		if (name[1]) {
+			return 0;
+		}
+		context->regs[Z80_R] = value;
+	case 's':
+		if (tolower(name[1]) != 'p' || name[2]) {
+			return 0;
 		}
+		context->sp = value;
 		break;
+	default:
+		return 0;
 	}
-	printf(format, param, value);
+	return 1;
+}
+
+debug_root *find_z80_root(z80_context *context)
+{
+	debug_root *root = find_root(context);
+	if (root && !root->commands) {
+		add_commands(root, common_commands, NUM_COMMON);
+		add_commands(root, z80_commands, NUM_Z80);
+		root->read_mem = read_z80;
+		root->write_mem = write_z80;
+		root->set = set_z80;
+		root->resolve = resolve_z80;
+	}
+	return root;
 }
 
 z80_context * zdebugger(z80_context * context, uint16_t address)
@@ -1088,11 +2610,11 @@
 	z80inst inst;
 	genesis_context *system = context->system;
 	init_terminal();
-	//Check if this is a user set breakpoint, or just a temporary one
 	debug_root *root = find_root(context);
 	if (!root) {
 		return context;
 	}
+	//Check if this is a user set breakpoint, or just a temporary one
 	bp_def ** this_bp = find_breakpoint(&root->breakpoints, address);
 	if (*this_bp) {
 		printf("Z80 Breakpoint %d hit\n", (*this_bp)->index);
@@ -1104,12 +2626,15 @@
 		fatal_error("Failed to get native pointer on entering Z80 debugger at address %X\n", address);
 	}
 	for (disp_def * cur = root->displays; cur; cur = cur->next) {
-		zdebugger_print(context, cur->format_char, cur->param);
+		cmd_print(root, cur->format, cur->num_args, cur->args);
 	}
 	uint8_t * after_pc = z80_decode(pc, &inst);
 	z80_disasm(&inst, input_buf, address);
 	printf("%X:\t%s\n", address, input_buf);
 	uint16_t after = address + (after_pc-pc);
+	root->address = address;
+	root->after = after;
+	root->inst = &inst;
 	int debugging = 1;
 	while(debugging) {
 		fputs(">", stdout);
@@ -1124,173 +2649,10 @@
 		} else {
 			strcpy(input_buf, last_cmd);
 		}
-		char * param;
-		char format[8];
-		uint32_t value;
-		bp_def * new_bp;
-		switch(input_buf[0])
-		{
-			case 'a':
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("a command requires a parameter\n", stderr);
-					break;
-				}
-				value = strtol(param, NULL, 16);
-				zinsert_breakpoint(context, value, (uint8_t *)zdebugger);
-				debugging = 0;
-				break;
-			case 'b':
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("b command requires a parameter\n", stderr);
-					break;
-				}
-				value = strtol(param, NULL, 16);
-				zinsert_breakpoint(context, value, (uint8_t *)zdebugger);
-				new_bp = malloc(sizeof(bp_def));
-				new_bp->next = root->breakpoints;
-				new_bp->address = value;
-				new_bp->index = root->bp_index++;
-				new_bp->commands = NULL;
-				root->breakpoints = new_bp;
-				printf("Z80 Breakpoint %d set at %X\n", new_bp->index, value);
-				break;
-			case 'c':
-				puts("Continuing");
-				debugging = 0;
-				break;
-			case 'd':
-				if (input_buf[1] == 'i') {
-					char format_char = 0;
-					for(int i = 2; input_buf[i] != 0 && input_buf[i] != ' '; i++) {
-						if (input_buf[i] == '/') {
-							format_char = input_buf[i+1];
-							break;
-						}
-					}
-					param = find_param(input_buf);
-					if (!param) {
-						fputs("display command requires a parameter\n", stderr);
-						break;
-					}
-					zdebugger_print(context, format_char, param);
-					add_display(&root->displays, &root->disp_index, format_char, param);
-				} else if (input_buf[1] == 'e' || input_buf[1] == ' ') {
-					param = find_param(input_buf);
-					if (!param) {
-						fputs("delete command requires a parameter\n", stderr);
-						break;
-					}
-					if (param[0] >= '0' && param[0] <= '9') {
-						value = atoi(param);
-						this_bp = find_breakpoint_idx(&root->breakpoints, value);
-						if (!*this_bp) {
-							fprintf(stderr, "Breakpoint %d does not exist\n", value);
-							break;
-						}
-						new_bp = *this_bp;
-						zremove_breakpoint(context, new_bp->address);
-						*this_bp = new_bp->next;
-						free(new_bp);
-					} else if (param[0] == 'd') {
-						param = find_param(param);
-						if (!param) {
-							fputs("delete display command requires a parameter\n", stderr);
-							break;
-						}
-						remove_display(&root->displays, atoi(param));
-					}
-				}
-				break;
-			case 'n':
-				//TODO: Handle conditional branch instructions
-				if (inst.op == Z80_JP) {
-					if (inst.addr_mode == Z80_IMMED) {
-						after = inst.immed;
-					} else if (inst.ea_reg == Z80_HL) {
-#ifndef NEW_CORE
-						after = context->regs[Z80_H] << 8 | context->regs[Z80_L];
-					} else if (inst.ea_reg == Z80_IX) {
-						after = context->regs[Z80_IXH] << 8 | context->regs[Z80_IXL];
-					} else if (inst.ea_reg == Z80_IY) {
-						after = context->regs[Z80_IYH] << 8 | context->regs[Z80_IYL];
-#endif
-					}
-				} else if(inst.op == Z80_JR) {
-					after += inst.immed;
-				} else if(inst.op == Z80_RET) {
-					uint8_t *sp = get_native_pointer(context->sp, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
-					if (sp) {
-						after = *sp;
-						sp = get_native_pointer((context->sp + 1) & 0xFFFF, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
-						if (sp) {
-							after |= *sp << 8;
-						}
-					}
-				}
-				zinsert_breakpoint(context, after, (uint8_t *)zdebugger);
-				debugging = 0;
-				break;
-			case 'p':
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("p command requires a parameter\n", stderr);
-					break;
-				}
-				zdebugger_print(context, input_buf[1] == '/' ? input_buf[2] : 0, param);
-				break;
-			case 'q':
-				puts("Quitting");
-				exit(0);
-				break;
-			case 's': {
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("s command requires a file name\n", stderr);
-					break;
-				}
-				memmap_chunk const *ram_chunk = NULL;
-				for (int i = 0; i < context->Z80_OPTS->gen.memmap_chunks; i++)
-				{
-					memmap_chunk const *cur = context->Z80_OPTS->gen.memmap + i;
-					if (cur->flags & MMAP_WRITE) {
-						ram_chunk = cur;
-						break;
-					}
-				}
-				if (ram_chunk) {
-					uint32_t size = ram_chunk->end - ram_chunk->start;
-					if (size > ram_chunk->mask) {
-						size = ram_chunk->mask+1;
-					}
-					uint8_t *buf = get_native_pointer(ram_chunk->start, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
-					FILE * f = fopen(param, "wb");
-					if (f) {
-						if(fwrite(buf, 1, size, f) != size) {
-							fputs("Error writing file\n", stderr);
-						}
-						fclose(f);
-						printf("Wrote %d bytes to %s\n", size, param);
-					} else {
-						fprintf(stderr, "Could not open %s for writing\n", param);
-					}
-				} else {
-					fputs("Failed to find a RAM memory chunk\n", stderr);
-				}
-				break;
-			}
-			case '?':
-				print_z80_help();
-				break;
-			default:
-				if (
-					!context->Z80_OPTS->gen.debug_cmd_handler
-					|| !context->Z80_OPTS->gen.debug_cmd_handler(&system->header, input_buf)
-				) {
-					fprintf(stderr, "Unrecognized debugger command %s\nUse '?' for help.\n", input_buf);
-				}
-				break;
+		parsed_command cmd;
+		if (parse_command(root, input_buf, &cmd)) {
+			debugging = run_command(root, &cmd);
+			free_parsed_command(&cmd);
 		}
 	}
 	return context;
@@ -1298,476 +2660,6 @@
 
 #endif
 
-int run_debugger_command(m68k_context *context, uint32_t address, char *input_buf, m68kinst inst, uint32_t after);
-int run_genesis_debugger_command(m68k_context *context, uint32_t address, char *input_buf)
-{
-	genesis_context * gen = context->system;
-	char *param;
-	uint32_t value;
-	bp_def *new_bp;
-	switch (input_buf[0])
-	{
-	case 'v':
-		//VDP debug commands
-		switch(input_buf[1])
-		{
-		case 's':
-			vdp_print_sprite_table(gen->vdp);
-			break;
-		case 'r':
-			vdp_print_reg_explain(gen->vdp);
-			break;
-		}
-		break;
-	case 'y':
-		//YM-2612 debug commands
-		switch(input_buf[1])
-		{
-		case 'c':
-			if (input_buf[2] == ' ') {
-				int channel = atoi(input_buf+3)-1;
-				ym_print_channel_info(gen->ym, channel);
-			} else {
-				for (int i = 0; i < 6; i++) {
-					ym_print_channel_info(gen->ym, i);
-				}
-			}
-			break;
-		case 't':
-			ym_print_timer_info(gen->ym);
-			break;
-		}
-		break;
-	case 'u':
-		if (gen->expansion) {
-			segacd_context *cd = gen->expansion;
-			if (input_buf[1]) {
-				//TODO: filter out commands that are unsafe to run when we don't have the current Sub CPU address
-				run_debugger_command(cd->m68k, 0, input_buf + 1, (m68kinst){}, 0);
-			} else {
-				cd->enter_debugger = 1;
-				return 0;
-			}
-		} else {
-			fputs("u command only valid when Sega/Mega CD is active\n", stderr);
-		}
-		break;
-#ifndef NO_Z80
-	case 'z':
-		//Z80 debug commands
-		switch(input_buf[1])
-		{
-		case 'b': {
-			param = find_param(input_buf);
-			if (!param) {
-				fputs("zb command requires a parameter\n", stderr);
-				break;
-			}
-			value = strtol(param, NULL, 16);
-			debug_root *zroot = find_root(gen->z80);
-			zinsert_breakpoint(gen->z80, value, (uint8_t *)zdebugger);
-			new_bp = malloc(sizeof(bp_def));
-			new_bp->next = zroot->breakpoints;
-			new_bp->address = value;
-			new_bp->index = zroot->bp_index++;
-			zroot->breakpoints = new_bp;
-			printf("Z80 Breakpoint %d set at %X\n", new_bp->index, value);
-			break;
-		}
-		case 'p':
-			param = find_param(input_buf);
-			if (!param) {
-				fputs("zp command requires a parameter\n", stderr);
-				break;
-			}
-			zdebugger_print(gen->z80, input_buf[2] == '/' ? input_buf[3] : 0, param);
-		}
-		break;
-#endif
-	default:
-		fprintf(stderr, "Unrecognized debugger command %s\nUse '?' for help.\n", input_buf);
-		break;
-	}
-	return 1;
-}
-
-int run_subcpu_debugger_command(m68k_context *context, uint32_t address, char *input_buf)
-{
-	segacd_context *cd = context->system;
-	switch (input_buf[0])
-	{
-	case 'm':
-		if (input_buf[1]) {
-			//TODO: filter out commands that are unsafe to run when we don't have the current Main CPU address
-			return run_debugger_command(cd->genesis->m68k, 0, input_buf + 1, (m68kinst){}, 0);
-		} else {
-			cd->genesis->header.enter_debugger = 1;
-			return 0;
-		}
-		break;
-	default:
-		fprintf(stderr, "Unrecognized debugger command %s\nUse '?' for help.\n", input_buf);
-		break;
-	}
-	return 1;
-}
-
-int run_debugger_command(m68k_context *context, uint32_t address, char *input_buf, m68kinst inst, uint32_t after)
-{
-	char * param;
-	char format_char;
-	genesis_context *system = context->system;
-	uint32_t value;
-	bp_def *new_bp, **this_bp;
-	debug_root *root = find_root(context);
-	if (!root) {
-		return 0;
-	}
-	root->address = address;
-	switch(input_buf[0])
-	{
-		case 'c':
-			if (input_buf[1] == 0 || input_buf[1] == 'o' && input_buf[2] == 'n')
-			{
-				puts("Continuing");
-				return 0;
-			} else if (input_buf[1] == 'o' && input_buf[2] == 'm') {
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("com command requires a parameter\n", stderr);
-					break;
-				}
-				bp_def **target = find_breakpoint_idx(&root->breakpoints, atoi(param));
-				if (!target) {
-					fprintf(stderr, "Breakpoint %s does not exist!\n", param);
-					break;
-				}
-				printf("Enter commands for breakpoing %d, type end when done\n", atoi(param));
-				char cmd_buf[1024];
-				char *commands = NULL;
-				for (;;)
-				{
-					fputs(">>", stdout);
-					fflush(stdout);
-					fgets(cmd_buf, sizeof(cmd_buf), stdin);
-					if (strcmp(cmd_buf, "end\n")) {
-						if (commands) {
-							char *tmp = commands;
-							commands = alloc_concat(commands, cmd_buf);
-							free(tmp);
-						} else {
-							commands = strdup(cmd_buf);
-						}
-					} else {
-						break;
-					}
-				}
-				(*target)->commands = commands;
-			} else {
-			}
-			break;
-		case 'b':
-			if (input_buf[1] == 't') {
-				uint32_t stack = context->aregs[7];
-				uint8_t non_adr_count = 0;
-				do {
-					uint32_t bt_address = m68k_instruction_fetch(stack, context);
-					bt_address = get_instruction_start(context->options, bt_address - 2);
-					if (bt_address) {
-						stack += 4;
-						non_adr_count = 0;
-						m68k_decode(m68k_instruction_fetch, context, &inst, bt_address);
-						m68k_disasm(&inst, input_buf);
-						printf("%X: %s\n", bt_address, input_buf);
-					} else {
-						//non-return address value on stack can be word wide
-						stack += 2;
-						non_adr_count++;
-					}
-					//TODO: Make sure we don't wander into an invalid memory region
-				} while (stack && non_adr_count < 6);
-			} else {
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("b command requires a parameter\n", stderr);
-					break;
-				}
-				value = strtol(param, NULL, 16);
-				insert_breakpoint(context, value, debugger);
-				new_bp = malloc(sizeof(bp_def));
-				new_bp->next = root->breakpoints;
-				new_bp->address = value;
-				new_bp->index = root->bp_index++;
-				new_bp->commands = NULL;
-				root->breakpoints = new_bp;
-				printf("68K Breakpoint %d set at %X\n", new_bp->index, value);
-			}
-			break;
-		case 'a':
-			param = find_param(input_buf);
-			if (!param) {
-				fputs("a command requires a parameter\n", stderr);
-				break;
-			}
-			value = strtol(param, NULL, 16);
-			insert_breakpoint(context, value, debugger);
-			return 0;
-		case 'd':
-			if (input_buf[1] == 'i') {
-				format_char = 0;
-				for(int i = 2; input_buf[i] != 0 && input_buf[i] != ' '; i++) {
-					if (input_buf[i] == '/') {
-						format_char = input_buf[i+1];
-						break;
-					}
-				}
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("display command requires a parameter\n", stderr);
-					break;
-				}
-				debugger_print(root, format_char, param);
-				add_display(&root->displays, &root->disp_index, format_char, param);
-			} else {
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("d command requires a parameter\n", stderr);
-					break;
-				}
-				value = atoi(param);
-				this_bp = find_breakpoint_idx(&root->breakpoints, value);
-				if (!*this_bp) {
-					fprintf(stderr, "Breakpoint %d does not exist\n", value);
-					break;
-				}
-				new_bp = *this_bp;
-				*this_bp = (*this_bp)->next;
-				if (new_bp->commands) {
-					free(new_bp->commands);
-				}
-				free(new_bp);
-			}
-			break;
-		case 'p':
-			format_char = 0;
-			for(int i = 1; input_buf[i] != 0 && input_buf[i] != ' '; i++) {
-				if (input_buf[i] == '/') {
-					format_char = input_buf[i+1];
-					break;
-				}
-			}
-			param = find_param(input_buf);
-			if (param) {
-				debugger_print(root, format_char, param);
-			} else {
-				m68k_disasm(&inst, input_buf);
-				printf("%X: %s\n", address, input_buf);
-			}
-
-			break;
-		case 'n':
-			if (inst.op == M68K_RTS) {
-				after = m68k_read_long(context->aregs[7], context);
-			} else if (inst.op == M68K_RTE || inst.op == M68K_RTR) {
-				after = m68k_read_long(context->aregs[7] + 2, context);
-			} else if(m68k_is_noncall_branch(&inst)) {
-				if (inst.op == M68K_BCC && inst.extra.cond != COND_TRUE) {
-					root->branch_f = after;
-					root->branch_t = m68k_branch_target(&inst, context->dregs, context->aregs);
-					insert_breakpoint(context, root->branch_t, debugger);
-				} else if(inst.op == M68K_DBCC) {
-					if ( inst.extra.cond == COND_FALSE) {
-						if (context->dregs[inst.dst.params.regs.pri] & 0xFFFF) {
-							after = m68k_branch_target(&inst, context->dregs, context->aregs);
-						}
-					} else {
-						root->branch_t = after;
-						root->branch_f = m68k_branch_target(&inst, context->dregs, context->aregs);
-						insert_breakpoint(context, root->branch_f, debugger);
-					}
-				} else {
-					after = m68k_branch_target(&inst, context->dregs, context->aregs);
-				}
-			}
-			insert_breakpoint(context, after, debugger);
-			return 0;
-		case 'o':
-			if (inst.op == M68K_RTS) {
-				after = m68k_read_long(context->aregs[7], context);
-			} else if (inst.op == M68K_RTE || inst.op == M68K_RTR) {
-				after = m68k_read_long(context->aregs[7] + 2, context);
-			} else if(m68k_is_noncall_branch(&inst)) {
-				if (inst.op == M68K_BCC && inst.extra.cond != COND_TRUE) {
-					root->branch_t = m68k_branch_target(&inst, context->dregs, context->aregs)  & 0xFFFFFF;
-					if (root->branch_t < after) {
-							root->branch_t = 0;
-					} else {
-						root->branch_f = after;
-						insert_breakpoint(context, root->branch_t, debugger);
-					}
-				} else if(inst.op == M68K_DBCC) {
-					uint32_t target = m68k_branch_target(&inst, context->dregs, context->aregs)  & 0xFFFFFF;
-					if (target > after) {
-						if (inst.extra.cond == COND_FALSE) {
-							after = target;
-						} else {
-							root->branch_f = target;
-							root->branch_t = after;
-							insert_breakpoint(context, root->branch_f, debugger);
-						}
-					}
-				} else {
-					after = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
-				}
-			}
-			insert_breakpoint(context, after, debugger);
-			return 0;
-		case 's':
-			if (input_buf[1] == 'e') {
-				param = find_param(input_buf);
-				if (!param) {
-					fputs("Missing destination parameter for set\n", stderr);
-					return 1;
-				}
-				char *val = find_param(param);
-				if (!val) {
-					fputs("Missing value parameter for set\n", stderr);
-					return 1;
-				}
-				long int_val;
-				int reg_num;
-				switch (val[0])
-				{
-				case 'd':
-				case 'a':
-					reg_num = val[1] - '0';
-					if (reg_num < 0 || reg_num > 8) {
-						fprintf(stderr, "Invalid register %s\n", val);
-						return 1;
-					}
-					int_val = (val[0] == 'd' ? context->dregs : context->aregs)[reg_num];
-					break;
-				case '$':
-					int_val = strtol(val+1, NULL, 16);
-					break;
-				case '0':
-					if (val[1] == 'x') {
-						int_val = strtol(val+2, NULL, 16);
-						break;
-					}
-				default:
-					int_val = strtol(val, NULL, 10);
-				}
-				switch(param[0])
-				{
-				case 'd':
-				case 'a':
-					reg_num = param[1] - '0';
-					if (reg_num < 0 || reg_num > 8) {
-						fprintf(stderr, "Invalid register %s\n", param);
-						return 1;
-					}
-					(param[0] == 'd' ? context->dregs : context->aregs)[reg_num] = int_val;
-					break;
-				default:
-					fprintf(stderr, "Invalid destinatino %s\n", param);
-				}
-				break;
-			} else if (input_buf[1] == 'r') {
-				system->header.soft_reset(&system->header);
-				return 0;
-			} else {
-				if (inst.op == M68K_RTS) {
-					after = m68k_read_long(context->aregs[7], context);
-				} else if (inst.op == M68K_RTE || inst.op == M68K_RTR) {
-					after = m68k_read_long(context->aregs[7] + 2, context);
-				} else if(m68k_is_branch(&inst)) {
-					if (inst.op == M68K_BCC && inst.extra.cond != COND_TRUE) {
-						root->branch_f = after;
-						root->branch_t = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
-						insert_breakpoint(context, root->branch_t, debugger);
-					} else if(inst.op == M68K_DBCC) {
-						if (inst.extra.cond == COND_FALSE) {
-							if (context->dregs[inst.dst.params.regs.pri] & 0xFFFF) {
-								after = m68k_branch_target(&inst, context->dregs, context->aregs);
-							}
-						} else {
-							root->branch_t = after;
-							root->branch_f = m68k_branch_target(&inst, context->dregs, context->aregs);
-							insert_breakpoint(context, root->branch_f, debugger);
-						}
-					} else {
-						after = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
-					}
-				}
-				insert_breakpoint(context, after, debugger);
-				return 0;
-			}
-		case '?':
-			print_m68k_help();
-			break;
-		case 'q':
-			puts("Quitting");
-			exit(0);
-			break;
-		default: {
-			if (context->system == current_system) {
-				//primary 68K for current system
-				return run_genesis_debugger_command(context, address, input_buf);
-			} else {
-				//presumably Sega CD sub CPU
-				//TODO: consider making this more generic
-				return run_subcpu_debugger_command(context, address, input_buf);
-			}
-			break;
-		}
-	}
-	return 1;
-}
-
-void print_m68k_help()
-{
-	printf("M68k Debugger Commands\n");
-	printf("    b ADDRESS            - Set a breakpoint at ADDRESS\n");
-	printf("    d BREAKPOINT         - Delete a 68K breakpoint\n");
-	printf("    co BREAKPOINT        - Run a list of debugger commands each time\n");
-	printf("                           BREAKPOINT is hit\n");
-	printf("    a ADDRESS            - Advance to address\n");
-	printf("    n                    - Advance to next instruction\n");
-	printf("    o                    - Advance to next instruction ignoring branches to\n");
-	printf("                           lower addresses (good for breaking out of loops)\n");
-	printf("    s                    - Advance to next instruction (follows bsr/jsr)\n");
-	printf("    se REG|ADDRESS VALUE - Set value\n");
-	printf("    sr                   - Soft reset\n");
-	printf("    c                    - Continue\n");
-	printf("    bt                   - Print a backtrace\n");
-	printf("    p[/(x|X|d|c)] VALUE  - Print a register or memory location\n");
-	printf("    di[/(x|X|d|c)] VALUE - Print a register or memory location each time\n");
-	printf("                           a breakpoint is hit\n");
-	printf("    vs                   - Print VDP sprite list\n");
-	printf("    vr                   - Print VDP register info\n");
-	printf("    yc [CHANNEL NUM]     - Print YM-2612 channel info\n");
-	printf("    yt                   - Print YM-2612 timer info\n");
-	printf("    zb ADDRESS           - Set a Z80 breakpoint\n");
-	printf("    zp[/(x|X|d|c)] VALUE - Display a Z80 value\n");
-	printf("    ?                    - Display help\n");
-	printf("    q                    - Quit BlastEm\n");
-}
-
-void print_z80_help()
-{
-	printf("Z80 Debugger Commands\n");
-	printf("    b  ADDRESS           - Set a breakpoint at ADDRESS\n");
-	printf("    de BREAKPOINT        - Delete a Z80 breakpoint\n");
-	printf("    a  ADDRESS           - Advance to address\n");
-	printf("    n                    - Advance to next instruction\n");
-	printf("    c                    - Continue\n");
-	printf("    p[/(x|X|d|c)] VALUE  - Print a register or memory location\n");
-	printf("    di[/(x|X|d|c)] VALUE - Print a register or memory location each time\n");
-	printf("                           a breakpoint is hit\n");
-	printf("    q                    - Quit BlastEm\n");
-}
-
 void debugger(m68k_context * context, uint32_t address)
 {
 	static char last_cmd[1024];
@@ -1781,7 +2673,7 @@
 		genesis_context *gen = context->system;
 		vdp_force_update_framebuffer(gen->vdp);
 	}
-	debug_root *root = find_root(context);
+	debug_root *root = find_m68k_root(context);
 	if (!root) {
 		return;
 	}
@@ -1802,24 +2694,16 @@
 	}
 
 	uint32_t after = m68k_decode(m68k_instruction_fetch, context, &inst, address);
+	root->address = address;
+	root->after = after;
+	root->inst = &inst;
 	int debugging = 1;
 	//Check if this is a user set breakpoint, or just a temporary one
 	bp_def ** this_bp = find_breakpoint(&root->breakpoints, address);
 	if (*this_bp) {
-
-		if ((*this_bp)->commands)
+		for (uint32_t i = 0; debugging && i < (*this_bp)->num_commands; i++)
 		{
-			char *commands = strdup((*this_bp)->commands);
-			char *copy = commands;
-
-			while (debugging && *commands)
-			{
-				char *cmd = commands;
-				strip_nl(cmd);
-				commands += strlen(cmd) + 1;
-				debugging = run_debugger_command(context, address, cmd, inst, after);
-			}
-			free(copy);
+			debugging = run_command(root, (*this_bp)->commands + i);
 		}
 		if (debugging) {
 			printf("68K Breakpoint %d hit\n", (*this_bp)->index);
@@ -1830,7 +2714,7 @@
 		remove_breakpoint(context, address);
 	}
 	for (disp_def * cur = root->displays; cur; cur = cur->next) {
-		debugger_print(root, cur->format_char, cur->param);
+		cmd_print(root, cur->format, cur->num_args, cur->args);
 	}
 	m68k_disasm(&inst, input_buf);
 	printf("%X: %s\n", address, input_buf);
@@ -1870,7 +2754,11 @@
 		} else {
 			strcpy(input_buf, last_cmd);
 		}
-		debugging = run_debugger_command(context, address, input_buf, inst, after);
+		parsed_command cmd;
+		if (parse_command(root, input_buf, &cmd)) {
+			debugging = run_command(root, &cmd);
+			free_parsed_command(&cmd);
+		}
 	}
 	return;
 }
--- a/debug.h	Sun Aug 07 01:16:47 2022 -0700
+++ b/debug.h	Sat Aug 13 19:16:30 2022 -0700
@@ -2,6 +2,7 @@
 #define DEBUG_H_
 
 #include <stdint.h>
+#include "tern.h"
 #include "m68k_core.h"
 #ifdef NEW_CORE
 #include "z80.h"
@@ -9,38 +10,117 @@
 #include "z80_to_x86.h"
 #endif
 
-typedef struct disp_def {
-	struct disp_def * next;
-	char *            param;
-	uint32_t          index;
-	char              format_char;
-} disp_def;
+typedef enum {
+	TOKEN_NONE,
+	TOKEN_NUM,
+	TOKEN_NAME,
+	TOKEN_OPER,
+	TOKEN_SIZE,
+	TOKEN_LBRACKET,
+	TOKEN_RBRACKET,
+	TOKEN_LPAREN,
+	TOKEN_RPAREN
+} token_type;
+
+typedef struct {
+	token_type type;
+	union {
+		char     *str;
+		char     op[3];
+		uint32_t num;
+	} v;
+} token;
 
-typedef struct bp_def {
-	struct bp_def *next;
-	char          *commands;
-	uint32_t      address;
-	uint32_t      index;
-} bp_def;
+typedef enum {
+	EXPR_NONE,
+	EXPR_SCALAR,
+	EXPR_UNARY,
+	EXPR_BINARY,
+	EXPR_SIZE,
+	EXPR_MEM
+} expr_type;
+
+typedef struct expr expr;
+
+struct expr {
+	expr_type type;
+	expr      *left;
+	expr      *right;
+	token     op;
+};
+
+typedef struct {
+	char     *raw;
+	expr     *parsed;
+	uint32_t value;
+} command_arg;
 
 typedef struct debug_root debug_root;
+typedef uint8_t (*raw_cmd)(debug_root *root, char *format, char *param);
+typedef uint8_t (*cmd)(debug_root *root, char *format, int num_args, command_arg *args);
+
+typedef struct {
+	const char **names;
+	const char *usage;
+	const char *desc;
+	raw_cmd    raw_impl;
+	cmd        impl;
+	int        min_args;
+	int        max_args;
+	uint8_t    skip_eval;
+	uint8_t    visited;
+} command_def;
+
+typedef struct disp_def {
+	struct disp_def *next;
+	char             *format;
+	int              num_args;
+	command_arg      *args;
+	uint32_t          index;
+} disp_def;
+
+typedef struct {
+	command_def *def;
+	char        *format;
+	char        *raw;
+	command_arg *args;
+	int         num_args;
+} parsed_command;
+
+typedef struct bp_def {
+	struct bp_def  *next;
+	parsed_command *commands;
+	uint32_t       num_commands;
+	uint32_t       address;
+	uint32_t       index;
+} bp_def;
+
 typedef uint8_t (*resolver)(debug_root *root, const char *name, uint32_t *out);
+typedef uint8_t (*setter)(debug_root *root, const char *name, uint32_t value);
 typedef uint8_t (*reader)(debug_root *root, uint32_t *out, char size);
+typedef uint8_t (*writer)(debug_root *root, uint32_t address, uint32_t value, char size);
 
 struct debug_root {
-	void     *cpu_context;
-	bp_def   *breakpoints;
-	disp_def *displays;
-	resolver resolve;
-	reader   read_mem;
-	uint32_t bp_index;
-	uint32_t disp_index;
-	uint32_t branch_t;
-	uint32_t branch_f;
-	uint32_t address;
+	void      *cpu_context;
+	bp_def    *breakpoints;
+	disp_def  *displays;
+	tern_node *commands;
+	resolver  resolve;
+	reader    read_mem;
+	setter    set;
+	writer    write_mem;
+	uint32_t  bp_index;
+	uint32_t  disp_index;
+	uint32_t  branch_t;
+	uint32_t  branch_f;
+	void      *inst;
+	uint32_t  address;
+	uint32_t  after;
 };
 
 debug_root *find_root(void *cpu);
+debug_root *find_m68k_root(m68k_context *context);
+debug_root *find_z80_root(z80_context *context);
 bp_def ** find_breakpoint(bp_def ** cur, uint32_t address);
 bp_def ** find_breakpoint_idx(bp_def ** cur, uint32_t index);
 void add_display(disp_def ** head, uint32_t *index, char format_char, char * param);
--- a/z80_to_x86.h	Sun Aug 07 01:16:47 2022 -0700
+++ b/z80_to_x86.h	Sat Aug 13 19:16:30 2022 -0700
@@ -112,6 +112,7 @@
 void z80_adjust_cycles(z80_context * context, uint32_t deduction);
 void z80_serialize(z80_context *context, serialize_buffer *buf);
 void z80_deserialize(deserialize_buffer *buf, void *vcontext);
+uint32_t z80_get_instruction_start(z80_context *context, uint32_t address);
 
 #endif //Z80_TO_X86_H_