2018-02-24 03:58:30 +00:00
|
|
|
/*
|
|
|
|
* vm_debug.c
|
|
|
|
*/
|
|
|
|
|
2018-02-25 20:25:02 +00:00
|
|
|
#include <ctype.h>
|
2018-02-24 22:30:46 +00:00
|
|
|
#include <errno.h>
|
2018-02-24 03:58:30 +00:00
|
|
|
#include <stdio.h>
|
2018-02-24 22:30:46 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <strings.h>
|
2018-02-24 03:58:30 +00:00
|
|
|
|
2018-02-25 00:57:00 +00:00
|
|
|
#include "mos6502.h"
|
2018-02-24 03:58:30 +00:00
|
|
|
#include "vm_debug.h"
|
|
|
|
#include "vm_di.h"
|
2018-02-24 22:30:46 +00:00
|
|
|
#include "vm_reflect.h"
|
2018-02-24 03:58:30 +00:00
|
|
|
|
2018-02-25 20:25:02 +00:00
|
|
|
/*
|
|
|
|
* The largest address size we can set a breakpoint for
|
|
|
|
*/
|
|
|
|
#define BREAKPOINTS_MAX 0x10000
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A table of breakpoints, arranged by address in a CPU. If
|
|
|
|
* breakpoints[i] is zero, then there is no breakpoint. If it is
|
|
|
|
* non-zero, then there is a breakpoint at address i.
|
|
|
|
*/
|
|
|
|
static int breakpoints[BREAKPOINTS_MAX];
|
|
|
|
|
2018-02-24 03:58:30 +00:00
|
|
|
vm_debug_cmd cmdtable[] = {
|
2018-02-25 00:57:00 +00:00
|
|
|
{ "help", "h", vm_debug_cmd_help, 0, "",
|
2018-02-24 03:58:30 +00:00
|
|
|
"Print out this list of commands", },
|
2018-02-25 01:36:02 +00:00
|
|
|
{ "jump", "j", vm_debug_cmd_jump, 1, "<addr>",
|
|
|
|
"Jump to <addr> for next execution", },
|
2018-02-25 00:57:00 +00:00
|
|
|
{ "printaddr", "pa", vm_debug_cmd_printaddr, 1, "<addr>",
|
|
|
|
"Print the value at memory address <addr>", },
|
|
|
|
{ "printstate", "ps", vm_debug_cmd_printstate, 0, "",
|
|
|
|
"Print the machine and CPU state", },
|
2018-02-25 20:25:02 +00:00
|
|
|
{ "quit", "q", vm_debug_cmd_quit, 0, "",
|
|
|
|
"Quit the emulator", },
|
2018-02-25 00:57:00 +00:00
|
|
|
{ "resume", "r", vm_debug_cmd_resume, 0, "",
|
2018-02-24 22:30:46 +00:00
|
|
|
"Resume execution", },
|
2018-02-25 01:38:55 +00:00
|
|
|
{ "writeaddr", "wa", vm_debug_cmd_writeaddr, 2, "<addr> <byte>",
|
|
|
|
"Write <byte> at <addr>", },
|
2018-02-25 20:25:02 +00:00
|
|
|
{ "writestate", "ws", vm_debug_cmd_writestate, 2, "<reg> <byte>",
|
|
|
|
"Write <byte> into <reg>", },
|
2018-02-24 03:58:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#define CMDTABLE_SIZE (sizeof(cmdtable) / sizeof(vm_debug_cmd))
|
|
|
|
|
2018-02-24 22:30:46 +00:00
|
|
|
char *
|
|
|
|
vm_debug_next_arg(char **str)
|
|
|
|
{
|
|
|
|
char *tok;
|
|
|
|
|
2018-02-25 20:25:02 +00:00
|
|
|
while ((tok = strsep(str, " "))){
|
2018-02-24 22:30:46 +00:00
|
|
|
if (tok == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*tok == '\0') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-02-25 20:25:02 +00:00
|
|
|
return tok;
|
|
|
|
}
|
2018-02-24 22:30:46 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
vm_debug_addr(const char *str)
|
|
|
|
{
|
|
|
|
int addr;
|
|
|
|
|
|
|
|
if (str == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
addr = strtol(str, NULL, 16);
|
|
|
|
if (addr == 0 && errno == EINVAL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return addr;
|
|
|
|
}
|
|
|
|
|
2018-02-25 20:25:02 +00:00
|
|
|
void
|
|
|
|
vm_debug_break(int addr)
|
|
|
|
{
|
|
|
|
if (addr < 0 || addr >= BREAKPOINTS_MAX) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
breakpoints[addr] = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
vm_debug_broke(int addr)
|
|
|
|
{
|
|
|
|
if (addr < 0 || addr >= BREAKPOINTS_MAX) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return breakpoints[addr] != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
|
|
vm_debug_prompt()
|
|
|
|
{
|
|
|
|
char buf[256];
|
|
|
|
FILE *stream = (FILE *)vm_di_get(VM_OUTPUT);
|
|
|
|
|
|
|
|
if (feof(stdin)) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fputs("erc> ", stream);
|
|
|
|
|
|
|
|
if (fgets(buf, 256, stdin) == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cut off the newline character, if there is one
|
|
|
|
buf[strlen(buf)-1] = '\0';
|
|
|
|
|
|
|
|
return strdup(buf);
|
|
|
|
}
|
|
|
|
|
2018-02-24 22:30:46 +00:00
|
|
|
void
|
|
|
|
vm_debug_execute(const char *str)
|
|
|
|
{
|
|
|
|
char *tok, *ebuf;
|
|
|
|
vm_debug_cmd *cmd;
|
|
|
|
vm_debug_args args;
|
|
|
|
|
|
|
|
ebuf = strdup(str);
|
|
|
|
cmd = NULL;
|
|
|
|
|
|
|
|
tok = vm_debug_next_arg(&ebuf);
|
|
|
|
|
|
|
|
// No input
|
|
|
|
if (tok == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd = vm_debug_find_cmd(tok);
|
|
|
|
|
|
|
|
// No command found
|
|
|
|
if (cmd == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
args.addr1 = 0;
|
|
|
|
args.addr2 = 0;
|
|
|
|
args.target = NULL;
|
|
|
|
|
|
|
|
switch (cmd->nargs) {
|
|
|
|
case 2:
|
|
|
|
args.target = vm_debug_next_arg(&ebuf);
|
|
|
|
|
|
|
|
// This _may_ be -1 if we have a string target for argument
|
|
|
|
// 1, as in the writestate command
|
|
|
|
args.addr1 = vm_debug_addr(args.target);
|
|
|
|
|
|
|
|
args.addr2 = vm_debug_addr(vm_debug_next_arg(&ebuf));
|
|
|
|
|
|
|
|
// But if this is -1, then something went wrong
|
|
|
|
if (args.addr2 == -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
args.addr1 = vm_debug_addr(vm_debug_next_arg(&ebuf));
|
|
|
|
|
|
|
|
// Oh no
|
|
|
|
if (args.addr1 == -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd->handler(&args);
|
2018-02-25 20:25:02 +00:00
|
|
|
|
|
|
|
free(ebuf);
|
2018-02-24 22:30:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
cmd_compar(const void *k, const void *elem)
|
|
|
|
{
|
|
|
|
const char *key = (const char *)k;
|
|
|
|
const vm_debug_cmd *cmd = (const vm_debug_cmd *)elem;
|
|
|
|
|
|
|
|
if (strlen(key) < 3) {
|
|
|
|
return strcmp(key, cmd->abbrev);
|
|
|
|
}
|
|
|
|
|
|
|
|
return strcmp(key, cmd->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the cmd struct for a command that matches str, which can
|
|
|
|
* either be an abbreviation (if 1 or 2 characters) or a full name (if
|
|
|
|
* otherwise). If no matching cmd can be found, return NULL.
|
|
|
|
*/
|
|
|
|
vm_debug_cmd *
|
|
|
|
vm_debug_find_cmd(const char *str)
|
|
|
|
{
|
|
|
|
return (vm_debug_cmd *)bsearch(str, &cmdtable, CMDTABLE_SIZE,
|
|
|
|
sizeof(vm_debug_cmd), cmd_compar);
|
|
|
|
}
|
|
|
|
|
2018-02-24 03:58:30 +00:00
|
|
|
DEBUG_CMD(help)
|
|
|
|
{
|
|
|
|
FILE *stream = (FILE *)vm_di_get(VM_OUTPUT);
|
|
|
|
vm_debug_cmd *cmd;
|
|
|
|
|
|
|
|
for (int i = 0; i < CMDTABLE_SIZE; i++) {
|
|
|
|
cmd = &cmdtable[i];
|
|
|
|
fprintf(stream, "%-15s%-5s%-15s%s\n", cmd->name, cmd->abbrev,
|
|
|
|
cmd->argdesc, cmd->desc);
|
|
|
|
}
|
|
|
|
}
|
2018-02-24 22:30:46 +00:00
|
|
|
|
|
|
|
DEBUG_CMD(resume)
|
|
|
|
{
|
|
|
|
vm_reflect_pause(NULL);
|
|
|
|
}
|
2018-02-25 00:57:00 +00:00
|
|
|
|
|
|
|
DEBUG_CMD(printstate)
|
|
|
|
{
|
|
|
|
vm_reflect_cpu_info(NULL);
|
|
|
|
vm_reflect_machine_info(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG_CMD(printaddr)
|
|
|
|
{
|
|
|
|
// FIXME: This is... too machine-specific; we need to abstract this logic
|
|
|
|
|
|
|
|
mos6502 *cpu = (mos6502 *)vm_di_get(VM_CPU);
|
|
|
|
FILE *stream = (FILE *)vm_di_get(VM_OUTPUT);
|
|
|
|
|
|
|
|
fprintf(stream, "$%02X\n", mos6502_get(cpu, args->addr1));
|
|
|
|
}
|
2018-02-25 01:36:02 +00:00
|
|
|
|
|
|
|
DEBUG_CMD(jump)
|
|
|
|
{
|
|
|
|
// FIXME: same issue as for printaddr -- overall we need to refactor
|
|
|
|
// vm_reflect quite a bit
|
|
|
|
|
|
|
|
mos6502 *cpu = (mos6502 *)vm_di_get(VM_CPU);
|
|
|
|
cpu->PC = args->addr1;
|
|
|
|
}
|
2018-02-25 01:38:04 +00:00
|
|
|
|
|
|
|
DEBUG_CMD(writeaddr)
|
|
|
|
{
|
|
|
|
mos6502 *cpu = (mos6502 *)vm_di_get(VM_CPU);
|
|
|
|
mos6502_set(cpu, args->addr1, args->addr2);
|
|
|
|
}
|
2018-02-25 20:25:02 +00:00
|
|
|
|
|
|
|
DEBUG_CMD(writestate)
|
|
|
|
{
|
|
|
|
mos6502 *cpu = (mos6502 *)vm_di_get(VM_CPU);
|
|
|
|
|
|
|
|
switch (tolower(*args->target)) {
|
|
|
|
case 'a': cpu->A = args->addr1; break;
|
|
|
|
case 'p': cpu->P = args->addr1; break;
|
|
|
|
case 's': cpu->S = args->addr1; break;
|
|
|
|
case 'x': cpu->X = args->addr1; break;
|
|
|
|
case 'y': cpu->Y = args->addr1; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG_CMD(quit)
|
|
|
|
{
|
|
|
|
exit(0);
|
|
|
|
}
|