Mercurial > repos > blastem
view debug.c @ 2327:9dd27530c570
Fix Z80 access to VDP debug register
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sun, 06 Aug 2023 20:00:33 -0700 |
parents | 0343f0d5add0 |
children | 344c6a3fe8a8 |
line wrap: on
line source
#include "debug.h" #include "genesis.h" #include "68kinst.h" #include "segacd.h" #include "blastem.h" #include "bindings.h" #include <ctype.h> #include <stdlib.h> #include <string.h> #ifndef _WIN32 #include <sys/select.h> #endif #include "render.h" #include "util.h" #include "terminal.h" #include "z80inst.h" #ifndef NO_Z80 #include "sms.h" #endif #ifdef NEW_CORE #define Z80_OPTS opts #else #define Z80_OPTS options #endif static debug_root roots[5]; static uint32_t num_roots; #define MAX_DEBUG_ROOTS (sizeof(roots)/sizeof(*roots)) debug_root *find_root(void *cpu) { for (uint32_t i = 0; i < num_roots; i++) { if (roots[i].cpu_context == cpu) { return roots + i; } } if (num_roots < MAX_DEBUG_ROOTS) { num_roots++; memset(roots + num_roots - 1, 0, sizeof(debug_root)); roots[num_roots-1].cpu_context = cpu; return roots + num_roots - 1; } return NULL; } bp_def ** find_breakpoint(bp_def ** cur, uint32_t address) { while (*cur) { if ((*cur)->address == address) { break; } cur = &((*cur)->next); } return cur; } bp_def ** find_breakpoint_idx(bp_def ** cur, uint32_t index) { while (*cur) { if ((*cur)->index == index) { break; } cur = &((*cur)->next); } return cur; } static const char *token_type_names[] = { "TOKEN_NONE", "TOKEN_NUM", "TOKEN_NAME", "TOKEN_OPER", "TOKEN_SIZE", "TOKEN_LBRACKET", "TOKEN_RBRACKET", "TOKEN_LPAREN", "TOKEN_RPAREN" }; static token parse_token(char *start, char **end) { while(*start && isblank(*start) && *start != '\n' && *start != '\r') { ++start; } if (!*start || *start == '\n' || *start == '\r') { return (token){ .type = TOKEN_NONE }; *end = start; } if (*start == '$' || (*start == '0' && start[1] == 'x')) { return (token) { .type = TOKEN_NUM, .v = { .num = strtol(start + (*start == '$' ? 1 : 2), end, 16) } }; } if (isdigit(*start)) { return (token) { .type = TOKEN_NUM, .v = { .num = strtol(start, end, 10) } }; } switch (*start) { case '+': case '-': case '*': case '/': case '&': case '|': case '^': case '~': case '=': case '!': case '>': case '<': if ((*start == '!' || *start == '>' || *start == '<') && start[1] == '=') { *end = start + 2; return (token) { .type = TOKEN_OPER, .v = { .op = {*start, start[1], 0} } }; } *end = start + 1; return (token) { .type = TOKEN_OPER, .v = { .op = {*start, 0} } }; case '.': *end = start + 2; return (token) { .type = TOKEN_SIZE, .v = { .op = {start[1], 0} } }; case '[': *end = start + 1; return (token) { .type = TOKEN_LBRACKET }; case ']': *end = start + 1; return (token) { .type = TOKEN_RBRACKET }; case '(': *end = start + 1; return (token) { .type = TOKEN_LPAREN }; case ')': *end = start + 1; return (token) { .type = TOKEN_RPAREN }; } *end = start + 1; while (**end && !isspace(**end)) { uint8_t done = 0; switch (**end) { case '+': case '-': case '*': case '/': case '&': case '|': case '^': case '~': case '=': case '!': case '>': case '<': case '.': done = 1; break; } if (done) { break; } ++*end; } char *name = malloc(*end - start + 1); memcpy(name, start, *end - start); name[*end-start] = 0; return (token) { .type = TOKEN_NAME, .v = { .str = name } }; } static void free_expr(expr *e) { if (!e) { return; } free_expr(e->left); free_expr(e->right); if (e->op.type == TOKEN_NAME) { free(e->op.v.str); } free(e); } static expr *parse_scalar_or_muldiv(char *start, char **end); static expr *parse_expression(char *start, char **end); static expr *parse_scalar(char *start, char **end) { char *after_first; token first = parse_token(start, &after_first); if (!first.type) { return NULL; } if (first.type == TOKEN_SIZE) { fprintf(stderr, "Unexpected TOKEN_SIZE '.%s'\n", first.v.op); return NULL; } if (first.type == TOKEN_OPER) { expr *target = parse_scalar(after_first, end); if (!target) { fprintf(stderr, "Unary expression %s needs value\n", first.v.op); return NULL; } expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_UNARY; ret->op = first; ret->left = target; *end = after_first; return ret; } if (first.type == TOKEN_LBRACKET) { expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_MEM; ret->left = parse_expression(after_first, end); if (!ret->left) { fprintf(stderr, "Expression expected after `[`\n"); free(ret); return NULL; } token rbrack = parse_token(*end, end); if (rbrack.type != TOKEN_RBRACKET) { fprintf(stderr, "Missing closing `]`"); free_expr(ret); return NULL; } char *after_size; token size = parse_token(*end, &after_size); if (size.type == TOKEN_SIZE) { *end = after_size; ret->op = size; } return ret; } if (first.type == TOKEN_LPAREN) { expr *ret = parse_expression(after_first, end); if (!ret) { fprintf(stderr, "Expression expected after `(`\n"); return NULL; } token rparen = parse_token(*end, end); if (rparen.type != TOKEN_RPAREN) { fprintf(stderr, "Missing closing `)`"); free_expr(ret); return NULL; } return ret; } if (first.type != TOKEN_NUM && first.type != TOKEN_NAME) { fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); return NULL; } token second = parse_token(after_first, end); if (second.type != TOKEN_SIZE) { expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_SCALAR; ret->op = first; *end = after_first; return ret; } expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_SIZE; ret->left = calloc(1, sizeof(expr)); ret->left->type = EXPR_SCALAR; ret->left->op = second; ret->op = first; return ret; } static expr *maybe_binary(expr *left, char *start, char **end) { char *after_first; token first = parse_token(start, &after_first); if (first.type != TOKEN_OPER) { *end = start; return left; } expr *bin = calloc(1, sizeof(expr)); bin->left = left; bin->op = first; bin->type = EXPR_BINARY; switch (first.v.op[0]) { case '*': case '/': case '&': case '|': case '^': bin->right = parse_scalar(after_first, end); return maybe_binary(bin, *end, end); case '+': case '-': bin->right = parse_scalar_or_muldiv(after_first, end); return maybe_binary(bin, *end, end); case '=': case '!': case '>': case '<': bin->right = parse_expression(after_first, end); return bin; default: bin->left = NULL; free(bin); return left; } } static expr *maybe_muldiv(expr *left, char *start, char **end) { char *after_first; token first = parse_token(start, &after_first); if (first.type != TOKEN_OPER) { *end = start; return left; } expr *bin = calloc(1, sizeof(expr)); bin->left = left; bin->op = first; bin->type = EXPR_BINARY; switch (first.v.op[0]) { case '*': case '/': case '&': case '|': case '^': bin->right = parse_scalar(after_first, end); return maybe_binary(bin, *end, end); default: bin->left = NULL; free(bin); return left; } } static expr *parse_scalar_or_muldiv(char *start, char **end) { char *after_first; token first = parse_token(start, &after_first); if (!first.type) { return NULL; } if (first.type == TOKEN_SIZE) { fprintf(stderr, "Unexpected TOKEN_SIZE '.%s'\n", first.v.op); return NULL; } if (first.type == TOKEN_OPER) { expr *target = parse_scalar(after_first, end); if (!target) { fprintf(stderr, "Unary expression %s needs value\n", first.v.op); return NULL; } expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_UNARY; ret->op = first; ret->left = target; return ret; } if (first.type == TOKEN_LBRACKET) { expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_MEM; ret->left = parse_expression(after_first, end); if (!ret->left) { fprintf(stderr, "Expression expected after `[`\n"); free(ret); return NULL; } token rbrack = parse_token(*end, end); if (rbrack.type != TOKEN_RBRACKET) { fprintf(stderr, "Missing closing `]`"); free_expr(ret); return NULL; } char *after_size; token size = parse_token(*end, &after_size); if (size.type == TOKEN_SIZE) { *end = after_size; ret->op = size; } return maybe_muldiv(ret, *end, end); } if (first.type == TOKEN_LPAREN) { expr *ret = parse_expression(after_first, end); if (!ret) { fprintf(stderr, "Expression expected after `(`\n"); return NULL; } token rparen = parse_token(*end, end); if (rparen.type != TOKEN_RPAREN) { fprintf(stderr, "Missing closing `)`"); free_expr(ret); return NULL; } return maybe_muldiv(ret, *end, end); } if (first.type != TOKEN_NUM && first.type != TOKEN_NAME) { fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); return NULL; } char *after_second; token second = parse_token(after_first, &after_second); if (second.type == TOKEN_OPER) { expr *ret; expr *bin = calloc(1, sizeof(expr)); bin->type = EXPR_BINARY; bin->left = calloc(1, sizeof(expr)); bin->left->type = EXPR_SCALAR; bin->left->op = first; bin->op = second; switch (second.v.op[0]) { case '*': case '/': case '&': case '|': case '^': bin->right = parse_scalar(after_second, end); return maybe_muldiv(bin, *end, end); case '+': case '-': case '=': case '!': case '>': case '<': ret = bin->left; bin->left = NULL; free_expr(bin); return ret; default: fprintf(stderr, "%s is not a valid binary operator\n", second.v.op); free(bin->left); free(bin); return NULL; } } else if (second.type == TOKEN_SIZE) { expr *value = calloc(1, sizeof(expr)); value->type = EXPR_SIZE; value->op = second; value->left = calloc(1, sizeof(expr)); value->left->type = EXPR_SCALAR; value->left->op = first; return maybe_muldiv(value, after_second, end); } else { expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_SCALAR; ret->op = first; *end = after_first; return ret; } } static expr *parse_expression(char *start, char **end) { char *after_first; token first = parse_token(start, &after_first); if (!first.type) { return NULL; } if (first.type == TOKEN_SIZE) { fprintf(stderr, "Unexpected TOKEN_SIZE '.%s'\n", first.v.op); return NULL; } if (first.type == TOKEN_OPER) { expr *target = parse_scalar(after_first, end); if (!target) { fprintf(stderr, "Unary expression %s needs value\n", first.v.op); return NULL; } expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_UNARY; ret->op = first; ret->left = target; return ret; } if (first.type == TOKEN_LBRACKET) { expr *ret = calloc(1, sizeof(expr)); ret->type = EXPR_MEM; ret->left = parse_expression(after_first, end); if (!ret->left) { fprintf(stderr, "Expression expected after `[`\n"); free(ret); return NULL; } token rbrack = parse_token(*end, end); if (rbrack.type != TOKEN_RBRACKET) { fprintf(stderr, "Missing closing `]`"); free_expr(ret); return NULL; } char *after_size; token size = parse_token(*end, &after_size); if (size.type == TOKEN_SIZE) { *end = after_size; ret->op = size; } return maybe_binary(ret, *end, end); } if (first.type == TOKEN_LPAREN) { expr *ret = parse_expression(after_first, end); if (!ret) { fprintf(stderr, "Expression expected after `(`\n"); return NULL; } token rparen = parse_token(*end, end); if (rparen.type != TOKEN_RPAREN) { fprintf(stderr, "Missing closing `)`"); free_expr(ret); return NULL; } return maybe_binary(ret, *end, end); } if (first.type != TOKEN_NUM && first.type != TOKEN_NAME) { fprintf(stderr, "Unexpected token %s\n", token_type_names[first.type]); return NULL; } char *after_second; token second = parse_token(after_first, &after_second); if (second.type == TOKEN_OPER) { expr *bin = calloc(1, sizeof(expr)); bin->type = EXPR_BINARY; bin->left = calloc(1, sizeof(expr)); bin->left->type = EXPR_SCALAR; bin->left->op = first; bin->op = second; switch (second.v.op[0]) { case '*': case '/': case '&': case '|': case '^': bin->right = parse_scalar(after_second, end); if (!bin->right) { fprintf(stderr, "Expected expression to the right of %s\n", second.v.op); free_expr(bin); return NULL; } return maybe_binary(bin, *end, end); case '+': case '-': bin->right = parse_scalar_or_muldiv(after_second, end); if (!bin->right) { fprintf(stderr, "Expected expression to the right of %s\n", second.v.op); free_expr(bin); return NULL; } return maybe_binary(bin, *end, end); case '=': case '!': case '>': case '<': bin->right = parse_expression(after_second, end); if (!bin->right) { fprintf(stderr, "Expected expression to the right of %s\n", second.v.op); free_expr(bin); return NULL; } return bin; default: fprintf(stderr, "%s is not a valid binary operator\n", second.v.op); free(bin->left); free(bin); return NULL; } } else if (second.type == TOKEN_SIZE) { expr *value = calloc(1, sizeof(expr)); value->type = EXPR_SIZE; value->op = second; value->left = calloc(1, sizeof(expr)); value->left->type = EXPR_SCALAR; 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; *end = after_first; return ret; } } uint8_t eval_expr(debug_root *root, expr *e, uint32_t *out) { uint32_t right; switch(e->type) { case EXPR_SCALAR: if (e->op.type == TOKEN_NAME) { if (root->resolve(root, e->op.v.str, out)) { return 1; } tern_val v; if (tern_find(root->symbols, e->op.v.str, &v)) { *out = v.intval; return 1; } return 0; } else { *out = e->op.v.num; return 1; } case EXPR_UNARY: if (!eval_expr(root, e->left, out)) { return 0; } switch (e->op.v.op[0]) { case '!': *out = !*out; break; case '~': *out = ~*out; break; case '-': *out = -*out; break; default: return 0; } return 1; case EXPR_BINARY: if (!eval_expr(root, e->left, out) || !eval_expr(root, e->right, &right)) { return 0; } switch (e->op.v.op[0]) { case '+': *out += right; break; case '-': *out -= right; break; case '*': *out *= right; break; case '/': *out /= right; break; case '&': *out &= right; break; case '|': *out |= right; break; case '^': *out ^= right; break; case '=': *out = *out == right; break; case '!': *out = *out != right; break; case '>': *out = e->op.v.op[1] ? *out >= right : *out > right; break; case '<': *out = e->op.v.op[1] ? *out <= right : *out < right; break; default: return 0; } return 1; case EXPR_SIZE: if (!eval_expr(root, e->left, out)) { return 0; } switch (e->op.v.op[0]) { case 'b': *out &= 0xFF; break; case 'w': *out &= 0xFFFF; break; } return 1; case EXPR_MEM: if (!eval_expr(root, e->left, out)) { return 0; } return root->read_mem(root, out, e->op.v.op[0]); default: return 0; } } char * find_param(char * buf) { for (; *buf; buf++) { if (*buf == ' ') { if (*(buf+1)) { return buf+1; } } } return NULL; } void strip_nl(char * buf) { for(; *buf; buf++) { if (*buf == '\n') { *buf = 0; return; } } } static uint8_t m68k_read_byte(uint32_t address, m68k_context *context) { //TODO: share this implementation with GDB debugger return read_byte(address, (void **)context->mem_pointers, &context->options->gen, context); } static uint16_t m68k_read_word(uint32_t address, m68k_context *context) { return read_word(address, (void **)context->mem_pointers, &context->options->gen, context); } static uint32_t m68k_read_long(uint32_t address, m68k_context *context) { return m68k_read_word(address, context) << 16 | m68k_read_word(address + 2, context); } static uint8_t read_m68k(debug_root *root, uint32_t *out, char size) { m68k_context *context = root->cpu_context; 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; if ((name[0] == 'd' || name[0] == 'D') && name[1] >= '0' && name[1] <= '7' && !name[2]) { *out = context->dregs[name[1]-'0']; } else if ((name[0] == 'a' || name[0] == 'A') && name[1] >= '0' && name[1] <= '7' && !name[2]) { *out = context->aregs[name[1]-'0']; } else if (!strcasecmp(name, "sr")) { *out = context->status << 8; for (int flag = 0; flag < 5; flag++) { *out |= context->flags[flag] << (4-flag); } } else if(!strcasecmp(name, "cycle")) { *out = context->current_cycle; } else if (!strcasecmp(name, "pc")) { *out = root->address; } else if (!strcasecmp(name, "usp")) { *out = context->status & 0x20 ? context->aregs[8] : context->aregs[7]; } else if (!strcasecmp(name, "ssp")) { *out = context->status & 0x20 ? context->aregs[7] : context->aregs[8]; } else { return 0; } 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)) { return 1; } m68k_context *m68k = root->cpu_context; genesis_context *gen = m68k->system; if (!strcmp(name, "f") || !strcmp(name, "frame")) { *out = gen->vdp->frame; return 1; } return 0; } void ambiguous_iter(char *key, tern_val val, uint8_t valtype, void *data) { 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)) { ++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_args) { 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); } cleanup_name: free(name); return ret; } static void free_parsed_command(parsed_command *cmd); static void free_command_block(command_block *block) { for (int i = 0; i < block->num_commands; i++) { free_parsed_command(block->commands + i); } free(block->commands); } static void free_parsed_command(parsed_command *cmd) { free(cmd->format); free(cmd->raw); for (int i = 0; i < cmd->num_args; i++) { free(cmd->args[i].raw); free_expr(cmd->args[i].parsed); } free_command_block(&cmd->block); free_command_block(&cmd->else_block); free(cmd->args); } enum { READ_FAILED = 0, NORMAL, EMPTY, ELSE, END }; static uint8_t read_parse_command(debug_root *root, parsed_command *out, int indent_level) { ++indent_level; for (int i = 0; i < indent_level; i++) { putchar('>'); } putchar(' '); fflush(stdout); #ifdef _WIN32 #define wait 0 #else int wait = 1; fd_set read_fds; FD_ZERO(&read_fds); struct timeval timeout; #endif do { process_events(); #ifndef IS_LIB render_update_display(); #endif #ifndef _WIN32 timeout.tv_sec = 0; timeout.tv_usec = 16667; FD_SET(fileno(stdin), &read_fds); if(select(fileno(stdin) + 1, &read_fds, NULL, NULL, &timeout) >= 1) { wait = 0; } #endif } while (wait); char input_buf[1024]; if (!fgets(input_buf, sizeof(input_buf), stdin)) { fputs("fgets failed", stderr); return READ_FAILED; } char *stripped = strip_ws(input_buf); if (!stripped[0]) { return EMPTY; } if (indent_level > 1) { if (!strcmp(stripped, "else")) { return ELSE; } if (!strcmp(stripped, "end")) { return END; } } if (parse_command(root, input_buf, out)) { if (!out->def->has_block) { return NORMAL; } int command_storage = 4; command_block *block = &out->block; block->commands = calloc(command_storage, sizeof(parsed_command)); block->num_commands = 0; for (;;) { if (block->num_commands == command_storage) { command_storage *= 2; block->commands = realloc(block->commands, command_storage * sizeof(parsed_command)); } switch (read_parse_command(root, block->commands + block->num_commands, indent_level)) { case READ_FAILED: return READ_FAILED; case NORMAL: block->num_commands++; break; case END: return NORMAL; case ELSE: if (block == &out->else_block) { fprintf(stderr, "Too many else blocks for command %s\n", out->def->names[0]); return READ_FAILED; } if (!out->def->accepts_else) { fprintf(stderr, "Command %s does not take an else block\n", out->def->names[0]); return READ_FAILED; } block = &out->else_block; block->commands = calloc(command_storage, sizeof(parsed_command)); block->num_commands = 0; break; } } } return READ_FAILED; } static uint8_t run_command(debug_root *root, parsed_command *cmd) { if (!cmd->def->raw_args && !cmd->def->skip_eval) { 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); } static void debugger_repl(debug_root *root) { int debugging = 1; parsed_command cmds[2] = {0}; int cur = 0; uint8_t has_last = 0; if (root->last_cmd.def) { memcpy(cmds + 1, &root->last_cmd, sizeof(root->last_cmd)); has_last = 1; } while(debugging) { switch (read_parse_command(root, cmds + cur, 0)) { case NORMAL: debugging = run_command(root, cmds + cur); cur = !cur; if (debugging && has_last) { free_parsed_command(cmds + cur); memset(cmds + cur, 0, sizeof(cmds[cur])); } has_last = 1; break; case EMPTY: if (has_last) { debugging = run_command(root, cmds + !cur); } break; } } if (has_last) { memcpy(&root->last_cmd, cmds + !cur, sizeof(root->last_cmd)); } else { free_parsed_command(cmds + !cur); } free_parsed_command(cmds + cur); } static uint8_t cmd_quit(debug_root *root, parsed_command *cmd) { 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; } fputs(*name, stdout); len += strlen(*name); ++name; } } else { len = 0; } if (extra_desc) { while (len < state->longest_command + 5) { putchar(' '); len++; } fputs(extra_desc, stdout); } putchar('\n'); } static uint8_t cmd_help(debug_root *root, parsed_command *cmd) { 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, parsed_command *cmd) { return 0; } static void make_format_str(char *format_str, char *format) { 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]); } } } static void do_print(debug_root *root, char *format_str, char *raw, uint32_t value) { if (format_str[5] == 's') { char tmp[128]; int j; uint32_t addr = value; for (j = 0; j < sizeof(tmp)-1; j++, addr++) { uint32_t tmp_addr = addr; root->read_mem(root, &tmp_addr, 'b'); char c = tmp_addr; if (c < 0x20 || c > 0x7F) { break; } tmp[j] = c; } tmp[j] = 0; printf(format_str, raw, tmp); } else { printf(format_str, raw, value); } } static uint8_t cmd_print(debug_root *root, parsed_command *cmd) { char format_str[8]; make_format_str(format_str, cmd->format); for (int i = 0; i < cmd->num_args; i++) { do_print(root, format_str, cmd->args[i].raw, cmd->args[i].value); } return 1; } static uint8_t cmd_printf(debug_root *root, parsed_command *cmd) { char *param = cmd->raw; if (!param) { fputs("printf requires at least one parameter\n", stderr); return 1; } while (isblank(*param)) { ++param; } if (*param != '"') { fprintf(stderr, "First parameter to printf must be a string, found '%s'\n", param); return 1; } ++param; char *fmt = strdup(param); char *cur = param, *out = fmt; while (*cur && *cur != '"') { if (*cur == '\\') { switch (cur[1]) { case 't': *(out++) = '\t'; break; case 'n': *(out++) = '\n'; break; case 'r': *(out++) = '\r'; break; case '\\': *(out++) = '\\'; break; default: fprintf(stderr, "Unsupported escape character %c in string %s\n", cur[1], fmt); free(fmt); return 1; } cur += 2; } else { *(out++) = *(cur++); } } *out = 0; ++cur; param = cur; cur = fmt; char format_str[3] = {'%', 'd', 0}; while (*cur) { if (*cur == '%') { switch(cur[1]) { case 'x': case 'X': case 'c': case 'd': case 's': break; default: fprintf(stderr, "Unsupported format character %c\n", cur[1]); free(fmt); return 1; } format_str[1] = cur[1]; expr *arg = parse_expression(param, ¶m); if (!arg) { free(fmt); return 1; } uint32_t val; if (!eval_expr(root, arg, &val)) { free(fmt); return 1; } if (cur[1] == 's') { char tmp[128]; int j; for (j = 0; j < sizeof(tmp)-1; j++, val++) { uint32_t addr = val; root->read_mem(root, &addr, 'b'); char c = addr; if (c < 0x20 || c > 0x7F) { break; } tmp[j] = c; } tmp[j] = 0; printf(format_str, tmp); } else { printf(format_str, val); } cur += 2; } else { putchar(*cur); ++cur; } } return 1; } static uint8_t cmd_display(debug_root *root, parsed_command *cmd) { cmd_print(root, cmd); disp_def *ndisp = calloc(1, sizeof(*ndisp)); ndisp->next = root->displays; ndisp->index = root->disp_index++; ndisp->format = cmd->format ? strdup(cmd->format) : NULL; ndisp->num_args = cmd->num_args; ndisp->args = cmd->args; cmd->args = NULL; cmd->num_args = 0; root->displays = ndisp; printf("Added display %d\n", ndisp->index); return 1; } static uint8_t cmd_delete_display(debug_root *root, parsed_command *cmd) { disp_def **cur = &root->displays; while (*cur) { if ((*cur)->index == cmd->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); } free(del_disp->args); free(del_disp); break; } else { cur = &(*cur)->next; } } return 1; } static uint8_t cmd_softreset(debug_root *root, parsed_command *cmd) { 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, parsed_command *cmd) { bp_def **target = find_breakpoint_idx(&root->breakpoints, cmd->args[0].value); if (!target) { fprintf(stderr, "Breakpoint %d does not exist!\n", cmd->args[0].value); return 1; } for (uint32_t i = 0; i < (*target)->num_commands; i++) { free_parsed_command((*target)->commands + i); } free((*target)->commands); (*target)->commands = cmd->block.commands; (*target)->num_commands = cmd->block.num_commands; cmd->block.commands = NULL; cmd->block.num_commands = 0; return 1; } static uint8_t execute_block(debug_root *root, command_block * block) { uint8_t debugging = 1; for (int i = 0; i < block->num_commands; i++) { debugging = run_command(root, block->commands + i) && debugging; } return debugging; } static uint8_t cmd_if(debug_root *root, parsed_command *cmd) { return execute_block(root, cmd->args[0].value ? &cmd->block : &cmd->else_block); } static uint8_t cmd_while(debug_root *root, parsed_command *cmd) { if (!cmd->args[0].value) { return execute_block(root, &cmd->else_block); } int debugging = 1; do { debugging = execute_block(root, &cmd->block) && debugging; if (!eval_expr(root, cmd->args[0].parsed, &cmd->args[0].value)) { fprintf(stderr, "Failed to eval %s\n", cmd->args[0].raw); return 1; } } while (cmd->args[0].value); return debugging; } const char *expr_type_names[] = { "EXPR_NONE", "EXPR_SCALAR", "EXPR_UNARY", "EXPR_BINARY", "EXPR_SIZE", "EXPR_MEM" }; static uint8_t cmd_set(debug_root *root, parsed_command *cmd) { char *name = NULL; char size = 0; uint32_t address; switch (cmd->args[0].parsed->type) { case EXPR_SCALAR: if (cmd->args[0].parsed->op.type == TOKEN_NAME) { name = cmd->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 = cmd->args[0].parsed->op.v.op[0]; if (cmd->args[0].parsed->left->op.type == TOKEN_NAME) { name = cmd->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 = cmd->args[0].parsed->op.v.op[0]; if (!eval_expr(root, cmd->args[0].parsed->left, &address)) { fprintf(stderr, "Failed to eval %s\n", cmd->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[cmd->args[0].parsed->type]); return 1; } if (!eval_expr(root, cmd->args[1].parsed, &cmd->args[1].value)) { fprintf(stderr, "Failed to eval %s\n", cmd->args[1].raw); return 1; } uint32_t value = cmd->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 { 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_frames(debug_root *root, parsed_command *cmd) { current_system->enter_debugger_frames = cmd->args[0].value; return 0; } static uint8_t cmd_bindup(debug_root *root, parsed_command *cmd) { if (!bind_up(cmd->raw)) { fprintf(stderr, "%s is not a valid binding name\n", cmd->raw); } return 1; } static uint8_t cmd_binddown(debug_root *root, parsed_command *cmd) { if (!bind_down(cmd->raw)) { fprintf(stderr, "%s is not a valid binding name\n", cmd->raw); } return 1; } static uint8_t cmd_condition(debug_root *root, parsed_command *cmd) { if (!eval_expr(root, cmd->args[0].parsed, &cmd->args[0].value)) { fprintf(stderr, "Failed to evaluate breakpoint number: %s\n", cmd->args[0].raw); return 1; } bp_def **target = find_breakpoint_idx(&root->breakpoints, cmd->args[0].value); if (!*target) { fprintf(stderr, "Failed to find breakpoint %u\n", cmd->args[0].value); return 1; } free_expr((*target)->condition); if (cmd->num_args > 1 && cmd->args[1].parsed) { (*target)->condition = cmd->args[1].parsed; cmd->args[1].parsed = NULL; } else { (*target)->condition = NULL; } return 1; } static void symbol_max_len(char *key, tern_val val, uint8_t valtype, void *data) { size_t *max_len = data; size_t len = strlen(key); if (len > *max_len) { *max_len = len; } } static void print_symbol(char *key, tern_val val, uint8_t valtype, void *data) { size_t *padding = data; size_t len = strlen(key); fputs(key, stdout); while (len < *padding) { putchar(' '); len++; } printf("%X\n", (uint32_t)val.intval); } static uint8_t cmd_symbols(debug_root *root, parsed_command *cmd) { char *filename = cmd->raw ? strip_ws(cmd->raw) : NULL; if (filename && *filename) { FILE *f = fopen(filename, "r"); if (!f) { fprintf(stderr, "Failed to open %s for reading\n", filename); return 1; } char linebuf[1024]; while (fgets(linebuf, sizeof(linebuf), f)) { char *line = strip_ws(linebuf); if (*line) { char *end; uint32_t address = strtol(line, &end, 16); if (end != line) { if (*end == '=') { char *name = strip_ws(end + 1); add_label(root->disasm, name, address); root->symbols = tern_insert_int(root->symbols, name, address); } } } } } else { size_t max_len = 0; tern_foreach(root->symbols, symbol_max_len, &max_len); max_len += 2; tern_foreach(root->symbols, print_symbol, &max_len); } return 1; } static uint8_t cmd_delete_m68k(debug_root *root, parsed_command *cmd) { bp_def **this_bp = find_breakpoint_idx(&root->breakpoints, cmd->args[0].value); if (!*this_bp) { fprintf(stderr, "Breakpoint %d does not exist\n", cmd->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, parsed_command *cmd) { insert_breakpoint(root->cpu_context, cmd->args[0].value, debugger); bp_def *new_bp = calloc(1, sizeof(bp_def)); new_bp->next = root->breakpoints; new_bp->address = cmd->args[0].value; new_bp->index = root->bp_index++; root->breakpoints = new_bp; printf("68K Breakpoint %d set at %X\n", new_bp->index, cmd->args[0].value); return 1; } static uint8_t cmd_advance_m68k(debug_root *root, parsed_command *cmd) { insert_breakpoint(root->cpu_context, cmd->args[0].value, debugger); return 0; } static uint8_t cmd_step_m68k(debug_root *root, parsed_command *cmd) { 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; } } insert_breakpoint(root->cpu_context, after, debugger); return 0; } static uint8_t cmd_over_m68k(debug_root *root, parsed_command *cmd) { 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, parsed_command *cmd) { 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, parsed_command *cmd) { 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_disassemble_m68k(debug_root *root, parsed_command *cmd) { m68k_context *context = root->cpu_context; uint32_t address = root->address; if (cmd->num_args) { address = cmd->args[0].value; } char disasm_buf[1024]; m68kinst inst; do { label_def *def = find_label(root->disasm, address); if (def) { for (uint32_t i = 0; i < def->num_labels; i++) { printf("%s:\n", def->labels[i]); } } address = m68k_decode(m68k_instruction_fetch, context, &inst, address); m68k_disasm_labels(&inst, disasm_buf, root->disasm); printf("\t%s\n", disasm_buf); } while(!m68k_is_terminal(&inst)); return 1; } static uint8_t cmd_vdp_sprites(debug_root *root, parsed_command *cmd) { 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, parsed_command *cmd) { 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, parsed_command *cmd) { m68k_context *context = root->cpu_context; genesis_context * gen = context->system; if (cmd->num_args) { ym_print_channel_info(gen->ym, cmd->args[0].value - 1); } else { for (int i = 0; i < 6; i++) { ym_print_channel_info(gen->ym, i); } } return 1; } static uint8_t cmd_ym_timer(debug_root *root, parsed_command *cmd) { 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, parsed_command *cmd) { char *param = cmd->raw; 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 = {0}; 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, parsed_command *cmd) { char *param = cmd->raw; 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 = {0}; 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, parsed_command *cmd) { char *param = cmd->raw; 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 = {0}; 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 { gen->enter_z80_debugger = 1; 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", .impl = cmd_help, .min_args = 0, .max_args = 1, .raw_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", "p", 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 *[]){ "printf", NULL }, .usage = "printf FORMAT EXPRESSION...", .desc = "Print a string with C-style formatting specifiers replaced with the value of the remaining arguments", .impl = cmd_printf, .min_args = 1, .max_args = -1, .raw_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, .has_block = 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 }, { .names = (const char *[]){ "frames", NULL }, .usage = "frames EXPRESSION", .desc = "Resume execution for EXPRESSION video frames", .impl = cmd_frames, .min_args = 1, .max_args = 1 }, { .names = (const char *[]){ "bindup", NULL }, .usage = "bindup NAME", .desc = "Simulate a keyup for binding NAME", .impl = cmd_bindup, .min_args = 1, .max_args = 1, .raw_args = 1 }, { .names = (const char *[]){ "binddown", NULL }, .usage = "bindown NAME", .desc = "Simulate a keydown for binding NAME", .impl = cmd_binddown, .min_args = 1, .max_args = 1, .raw_args = 1 }, { .names = (const char *[]){ "condition", NULL }, .usage = "condition BREAKPOINT [EXPRESSION]", .desc = "Makes breakpoint BREAKPOINT conditional on the value of EXPRESSION or removes a condition if EXPRESSION is omitted", .impl = cmd_condition, .min_args = 1, .max_args = 2, .skip_eval = 1 }, { .names = (const char *[]){ "if", NULL }, .usage = "if CONDITION", .desc = "If the condition is true, the following block is executed. Otherwise the else block is executed if present", .impl = cmd_if, .min_args = 1, .max_args = 1, .has_block = 1, .accepts_else = 1 }, { .names = (const char *[]){ "while", NULL }, .usage = "while CONDITION", .desc = "The following block is executed repeatedly until the condition is false. If the condition is false at the start, the else block is executed if present", .impl = cmd_while, .min_args = 1, .max_args = 1, .has_block = 1, .accepts_else = 1 }, { .names = (const char *[]){ "symbols", NULL }, .usage = "symbols [FILENAME]", .desc = "Loads a list of symbols from the file indicated by FILENAME or lists currently loaded symbols if FILENAME is omitted", .impl = cmd_symbols, .min_args = 0, .max_args = 1, .raw_args = 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 }, { .names = (const char *[]){ "disassemble", "disasm", NULL }, .usage = "disassemble [ADDRESS]", .desc = "Disassemble code starting at ADDRESS if provided or the current address if not", .impl = cmd_disassemble_m68k, .min_args = 0, .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", .impl = cmd_gen_z80, .min_args = 0, .max_args = -1, .raw_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", .impl = cmd_sub, .min_args = 0, .max_args = -1, .raw_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", .impl = cmd_main, .min_args = 0, .max_args = -1, .raw_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, parsed_command *cmd) { bp_def **this_bp = find_breakpoint_idx(&root->breakpoints, cmd->args[0].value); if (!*this_bp) { fprintf(stderr, "Breakpoint %d does not exist\n", cmd->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, parsed_command *cmd) { zinsert_breakpoint(root->cpu_context, cmd->args[0].value, (uint8_t *)zdebugger); bp_def *new_bp = calloc(1, sizeof(bp_def)); new_bp->next = root->breakpoints; new_bp->address = cmd->args[0].value; new_bp->index = root->bp_index++; root->breakpoints = new_bp; printf("Z80 Breakpoint %d set at %X\n", new_bp->index, cmd->args[0].value); return 1; } static uint8_t cmd_advance_z80(debug_root *root, parsed_command *cmd) { zinsert_breakpoint(root->cpu_context, cmd->args[0].value, (uint8_t *)zdebugger); return 0; } static uint8_t cmd_step_z80(debug_root *root, parsed_command *cmd) { 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, parsed_command *cmd) { fputs("not implemented yet\n", stderr); return 1; } static uint8_t cmd_next_z80(debug_root *root, parsed_command *cmd) { 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, parsed_command *cmd) { 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; } static uint8_t cmd_disassemble_z80(debug_root *root, parsed_command *cmd) { z80_context *context = root->cpu_context; uint32_t address = root->address; if (cmd->num_args) { address = cmd->args[0].value; } char disasm_buf[1024]; z80inst inst; do { label_def *def = find_label(root->disasm, address); if (def) { for (uint32_t i = 0; i < def->num_labels; i++) { printf("%s:\n", def->labels[i]); } } uint8_t *pc = get_native_pointer(address, (void **)context->mem_pointers, &context->Z80_OPTS->gen); uint8_t *after = z80_decode(pc, &inst); z80_disasm(&inst, disasm_buf, address); address += after - pc; printf("\t%s\n", disasm_buf); } while(!z80_is_terminal(&inst)); return 1; } static uint8_t cmd_gen_m68k(debug_root *root, parsed_command *cmd) { char *param = cmd->raw; while (param && *param && isblank(*param)) { ++param; } genesis_context *gen = (genesis_context *)current_system; if (param && *param && !isspace(*param)) { parsed_command cmd = {0}; debug_root *m68k_root = find_m68k_root(gen->m68k); if (!m68k_root) { fputs("Failed to get debug root for M68K\n", stderr); return 1; } if (!parse_command(m68k_root, param, &cmd)) { return 1; } uint8_t ret = run_command(m68k_root, &cmd); free_parsed_command(&cmd); return ret; } else { gen->header.enter_debugger = 1; return 0; } } static uint8_t cmd_vdp_sprites_sms(debug_root *root, parsed_command *cmd) { z80_context *context = root->cpu_context; sms_context * sms = context->system; vdp_print_sprite_table(sms->vdp); return 1; } static uint8_t cmd_vdp_regs_sms(debug_root *root, parsed_command *cmd) { z80_context *context = root->cpu_context; sms_context * sms = context->system; vdp_print_reg_explain(sms->vdp); 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 }, { .names = (const char *[]){ "disassemble", "disasm", NULL }, .usage = "disassemble [ADDRESS]", .desc = "Disassemble code starting at ADDRESS if provided or the current address if not", .impl = cmd_disassemble_z80, .min_args = 0, .max_args = 1 } }; #define NUM_Z80 (sizeof(z80_commands)/sizeof(*z80_commands)) command_def gen_z80_commands[] = { { .names = (const char *[]){ "m68k", NULL }, .usage = "m68k [COMMAND]", .desc = "Run a M68K debugger command or switch to M68K context when no command is given", .impl = cmd_gen_m68k, .min_args = 0, .max_args = -1, .raw_args = 1 } }; #define NUM_GEN_Z80 (sizeof(gen_z80_commands)/sizeof(*gen_z80_commands)) command_def sms_commands[] = { { .names = (const char *[]){ "vdpsprites", "vs", NULL }, .usage = "vdpsprites", .desc = "Print the VDP sprite table", .impl = cmd_vdp_sprites_sms, .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_sms, .min_args = 0, .max_args = 0 } }; #define NUM_SMS (sizeof(sms_commands)/sizeof(*sms_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); } } } static void symbol_map(char *key, tern_val val, uint8_t valtype, void *data) { debug_root *root = data; label_def *label = val.ptrval; for (uint32_t i = 0; i < label->num_labels; i++) { root->symbols = tern_insert_int(root->symbols, label->labels[i], label->full_address); } } 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; root->disasm = create_68000_disasm(); 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_segacd_maincpu_labels(root->disasm); add_commands(root, scd_main_commands, NUM_SCD_MAIN); } break; } else { add_segacd_subcpu_labels(root->disasm); add_commands(root, scd_sub_commands, NUM_SCD_SUB); } default: root->resolve = resolve_m68k; } tern_foreach(root->disasm->labels, symbol_map, root); } return root; } #ifndef NO_Z80 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) { 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 'a': 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 { return 0; } } else { return 0; } break; case 'b': 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 { return 0; } } break; case 'c': if (!name[1]) { *out = context->regs[Z80_C]; } else if (name[1] == '\'' && !name[2]) { *out = context->alt_regs[Z80_C]; } else if (!strcmp(name + 1, "ycle")) { *out = context->current_cycle; } else { return 0; } break; case 'd': 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 { return 0; } } break; case 'e': if (!name[1]) { *out = context->regs[Z80_E]; } else if (name[1] == '\'' && !name[2]) { *out = context->alt_regs[Z80_E]; } else { return 0; } break; case 'f': 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 { return 0; } break; case 'h': 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]) == 'l') { 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 { 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; } *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 (!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 resolve_sms(debug_root *root, const char *name, uint32_t *out) { if (resolve_z80(root, name, out)) { return 1; } z80_context *z80 = root->cpu_context; sms_context *sms = z80->system; if (!strcmp(name, "f") || !strcmp(name, "frame")) { *out = sms->vdp->frame; return 1; } return 0; } 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 { 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': 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 { return 0; } break; case 'm': if (name[2]) { return 0; } context->im = value & 3; break; case 'r': if (name[2]) { return 0; } 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; } 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; 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 '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; } 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); switch (current_system->type) { case SYSTEM_GENESIS: case SYSTEM_SEGACD: add_commands(root, gen_z80_commands, NUM_GEN_Z80); root->resolve = resolve_z80; break; case SYSTEM_SMS: root->resolve = resolve_sms; add_commands(root, sms_commands, NUM_SMS); break; default: root->resolve = resolve_z80; } root->read_mem = read_z80; root->write_mem = write_z80; root->set = set_z80; root->disasm = create_z80_disasm(); } return root; } z80_context * zdebugger(z80_context * context, uint16_t address) { static char last_cmd[1024]; char input_buf[1024]; z80inst inst; genesis_context *system = context->system; init_terminal(); debug_root *root = find_z80_root(context); if (!root) { return context; } root->address = address; //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)->condition) { uint32_t condres; if (eval_expr(root, (*this_bp)->condition, &condres)) { if (!condres) { return context; } } else { fprintf(stderr, "Failed to eval condition for Z80 breakpoint %u\n", (*this_bp)->index); free_expr((*this_bp)->condition); (*this_bp)->condition = NULL; } } int debugging = 1; for (uint32_t i = 0; debugging && i < (*this_bp)->num_commands; i++) { debugging = run_command(root, (*this_bp)->commands + i); } if (debugging) { printf("Z80 Breakpoint %d hit\n", (*this_bp)->index); } else { return context; } } else { zremove_breakpoint(context, address); } uint8_t * pc = get_native_pointer(address, (void **)context->mem_pointers, &context->Z80_OPTS->gen); if (!pc) { fatal_error("Failed to get native pointer on entering Z80 debugger at address %X\n", address); } uint8_t * after_pc = z80_decode(pc, &inst); uint16_t after = address + (after_pc-pc); root->after = after; root->inst = &inst; for (disp_def * cur = root->displays; cur; cur = cur->next) { char format_str[8]; make_format_str(format_str, cur->format); for (int i = 0; i < cur->num_args; i++) { eval_expr(root, cur->args[i].parsed, &cur->args[i].value); do_print(root, format_str, cur->args[i].raw, cur->args[i].value); } } z80_disasm(&inst, input_buf, address); printf("%X:\t%s\n", address, input_buf); debugger_repl(root); return context; } #endif void debugger(m68k_context * context, uint32_t address) { static char last_cmd[1024]; char input_buf[1024]; m68kinst inst; init_terminal(); context->options->sync_components(context, 0); if (context->system == current_system) { genesis_context *gen = context->system; vdp_force_update_framebuffer(gen->vdp); } debug_root *root = find_m68k_root(context); if (!root) { return; } //probably not necessary, but let's play it safe address &= 0xFFFFFF; if (address == root->branch_t) { bp_def ** f_bp = find_breakpoint(&root->breakpoints, root->branch_f); if (!*f_bp) { remove_breakpoint(context, root->branch_f); } root->branch_t = root->branch_f = 0; } else if(address == root->branch_f) { bp_def ** t_bp = find_breakpoint(&root->breakpoints, root->branch_t); if (!*t_bp) { remove_breakpoint(context, root->branch_t); } root->branch_t = root->branch_f = 0; } root->address = address; 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)->condition) { uint32_t condres; if (eval_expr(root, (*this_bp)->condition, &condres)) { if (!condres) { return; } } else { fprintf(stderr, "Failed to eval condition for M68K breakpoint %u\n", (*this_bp)->index); free_expr((*this_bp)->condition); (*this_bp)->condition = NULL; } } for (uint32_t i = 0; debugging && i < (*this_bp)->num_commands; i++) { debugging = run_command(root, (*this_bp)->commands + i); } if (debugging) { printf("68K Breakpoint %d hit\n", (*this_bp)->index); } else { return; } } else { remove_breakpoint(context, address); } uint32_t after = m68k_decode(m68k_instruction_fetch, context, &inst, address); root->after = after; root->inst = &inst; for (disp_def * cur = root->displays; cur; cur = cur->next) { char format_str[8]; make_format_str(format_str, cur->format); for (int i = 0; i < cur->num_args; i++) { eval_expr(root, cur->args[i].parsed, &cur->args[i].value); do_print(root, format_str, cur->args[i].raw, cur->args[i].value); } } m68k_disasm_labels(&inst, input_buf, root->disasm); printf("%X: %s\n", address, input_buf); debugger_repl(root); return; }