gbdb: add scripting features for automatic testing
This commit is contained in:
371
src/apps/gbdb.c
371
src/apps/gbdb.c
@@ -7,8 +7,10 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "gb_disas.h"
|
||||
@@ -37,27 +39,6 @@ static const char usage[] =
|
||||
"peek: view the next instruction to run\n"
|
||||
"exit: quit the program\n";
|
||||
|
||||
static const char *reg16_names[] = {
|
||||
"AF",
|
||||
"BC",
|
||||
"DE",
|
||||
"HL",
|
||||
"SP",
|
||||
"PC",
|
||||
};
|
||||
|
||||
static const char *reg8_names[] = {
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"H",
|
||||
"L",
|
||||
"N/A",
|
||||
"A",
|
||||
"F"
|
||||
};
|
||||
|
||||
static struct lr35902_state cpu;
|
||||
static struct gb_video video;
|
||||
|
||||
@@ -66,17 +47,30 @@ static unsigned char bootrom[0x100] = {0};
|
||||
static int bootrom_mapped = 1;
|
||||
static volatile sig_atomic_t paused = 0;
|
||||
|
||||
static struct {
|
||||
bool quiet;
|
||||
} config;
|
||||
|
||||
static void gb_log(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
if (config.quiet) {
|
||||
return;
|
||||
}
|
||||
|
||||
vprintf(fmt, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
||||
static void break_execution_handler(int signum)
|
||||
{
|
||||
paused = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ram_{read,write} work easily because the cartridge is mapped to 0x0000
|
||||
*
|
||||
*/
|
||||
|
||||
static void strip_newline(char *string)
|
||||
{
|
||||
char *pos;
|
||||
@@ -89,6 +83,10 @@ static void strip_newline(char *string)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ram_{read,write} work easily because the cartridge is mapped to 0x0000
|
||||
*
|
||||
*/
|
||||
static uint8_t mem_read(struct lr35902_state *cpu, uint16_t addr)
|
||||
{
|
||||
|
||||
@@ -113,7 +111,7 @@ static void mem_write(struct lr35902_state *cpu, uint16_t addr, uint8_t val)
|
||||
case UNMAP_BOOTROM_ADDR:
|
||||
if (val == 1) {
|
||||
bootrom_mapped = 0;
|
||||
printf("bootrom unmapped\n");
|
||||
gb_log("bootrom unmapped\n");
|
||||
}
|
||||
break;
|
||||
case 0xFF40 ... 0xFF4B:
|
||||
@@ -137,13 +135,13 @@ static void mem_dump(char *filename)
|
||||
char *token = strtok(filename, " ");
|
||||
|
||||
if (token == NULL) {
|
||||
printf("usage: load <file>\n");
|
||||
gb_log("usage: load <file>\n");
|
||||
return;
|
||||
}
|
||||
strip_newline(token);
|
||||
dump_file = fopen(token, "w");
|
||||
if(dump_file == NULL) {
|
||||
printf("Failed to open mem dump file: %d\n", errno);
|
||||
gb_log("Failed to open mem dump file: %d\n", errno);
|
||||
return;
|
||||
}
|
||||
fwrite(ram, MAX_RAM_LEN, 1, dump_file);
|
||||
@@ -170,10 +168,24 @@ static void show_prompt()
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void set(char *arg_list)
|
||||
{
|
||||
/* TODO make this support other options */
|
||||
config.quiet = true;
|
||||
}
|
||||
|
||||
static void echo(char *arg_list)
|
||||
{
|
||||
if (arg_list != NULL) {
|
||||
printf("%s\n", arg_list);
|
||||
} else {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void step(char *arg_list)
|
||||
{
|
||||
uint64_t steps;
|
||||
int i = 0;
|
||||
uint64_t steps, init_steps;
|
||||
char *token;
|
||||
token = strtok(arg_list, " ");
|
||||
if (token == NULL) {
|
||||
@@ -185,35 +197,36 @@ static void step(char *arg_list)
|
||||
|
||||
paused = 0;
|
||||
signal(SIGINT, break_execution_handler);
|
||||
|
||||
init_steps = cpu.metrics.retired_instrs;
|
||||
do {
|
||||
cycle();
|
||||
i++;
|
||||
} while (i < steps && !cpu_at_breakpoint() && !cpu.halted && !paused);
|
||||
} while (init_steps + steps > cpu.metrics.retired_instrs &&
|
||||
!cpu_at_breakpoint() &&
|
||||
!cpu.halted &&
|
||||
!paused);
|
||||
|
||||
if (i == steps) {
|
||||
printf("CPU stopped after %d cycles\n", i);
|
||||
if (init_steps + steps <= cpu.metrics.retired_instrs) {
|
||||
gb_log("CPU stopped after %" PRId64 " instructions\n", steps);
|
||||
} else if (cpu_at_breakpoint()) {
|
||||
printf("Breakpoint hit\n");
|
||||
gb_log("Breakpoint hit\n");
|
||||
} else if (cpu.halted) {
|
||||
printf("CPU halted\n");
|
||||
gb_log("CPU halted\n");
|
||||
} else {
|
||||
printf("Interrupted after %d cycles\n", i);
|
||||
gb_log("Interrupted\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void regs(char *arg_list)
|
||||
{
|
||||
int i;
|
||||
uint8_t f = (cpu.nf << CPU_F_BIT_POS_N) | (cpu.zf << CPU_F_BIT_POS_Z) |
|
||||
(cpu.cf << CPU_F_BIT_POS_C) | (cpu.hf << CPU_F_BIT_POS_H);
|
||||
|
||||
for(i = 0; i < NUM_LR35902_REGS_8; i++){
|
||||
if (i != LR35902_REG_HL_DEREF) {
|
||||
printf("%s: 0x%02x\n", reg8_names[i], lr35902_get_reg_8(&cpu, i));
|
||||
};
|
||||
}
|
||||
for(i = 0; i < NUM_LR35902_REGS_16; i++){
|
||||
printf("%s: 0x%04x\n", reg16_names[i], lr35902_get_reg_16(&cpu, i));
|
||||
}
|
||||
printf("AF: 0x%04x A: 0x%02x F: 0x%02x\n", (uint16_t) (cpu.a << 8) | f, cpu.a, f);
|
||||
printf("BC: 0x%04x B: 0x%02x C: 0x%02x\n", cpu.bc, cpu.b, cpu.c);
|
||||
printf("DE: 0x%04x D: 0x%02x E: 0x%02x\n", cpu.de, cpu.d, cpu.e);
|
||||
printf("HL: 0x%04x H: 0x%02x L: 0x%02x\n", cpu.hl, cpu.h, cpu.l);
|
||||
printf("PC: 0x%04x\n", cpu.pc);
|
||||
printf("SP: 0x%04x\n", cpu.sp);
|
||||
}
|
||||
|
||||
static void peek(char *arg_list)
|
||||
@@ -236,6 +249,107 @@ static void help(char *arg_list)
|
||||
printf(usage);
|
||||
}
|
||||
|
||||
static int64_t parse_reg_str(const char *str)
|
||||
{
|
||||
int i;
|
||||
const struct {
|
||||
const char *str;
|
||||
int64_t value;
|
||||
} entries[] = {
|
||||
//{ "af", cpu,af },
|
||||
{ "bc", cpu.bc },
|
||||
{ "de", cpu.de },
|
||||
{ "sp", cpu.sp },
|
||||
{ "pc", cpu.pc },
|
||||
{ "hl", cpu.hl },
|
||||
{ "a", cpu.a },
|
||||
{ "b", cpu.b },
|
||||
{ "c", cpu.c },
|
||||
{ "d", cpu.d },
|
||||
{ "e", cpu.e },
|
||||
{ "f", (cpu.zf << 3) | (cpu.nf << 2) | (cpu.hf << 1) | (cpu.cf << 0) },
|
||||
};
|
||||
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(entries); i++) {
|
||||
if (strncmp(str, entries[i].str, strlen(entries[i].str)) == 0) {
|
||||
return entries[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t parse_val(const char *str)
|
||||
{
|
||||
if (str[0] == '$') {
|
||||
return parse_reg_str(str + 1);
|
||||
}
|
||||
|
||||
return strtoll(str, NULL, 0);
|
||||
}
|
||||
|
||||
static void assert(char *arg_list)
|
||||
{
|
||||
char *val0_str, *val1_str, *operator_str;
|
||||
const char *usage = "usage: assert <value> <operator> <value>\n";
|
||||
int64_t val0, val1;
|
||||
|
||||
val0_str = strtok(arg_list, " ");
|
||||
if (val0_str == NULL) {
|
||||
printf("%s", usage);
|
||||
return;
|
||||
}
|
||||
val0 = parse_val(val0_str);
|
||||
|
||||
operator_str = strtok(NULL, " ");
|
||||
if (operator_str == NULL) {
|
||||
printf("%s", usage);
|
||||
return;
|
||||
}
|
||||
|
||||
val1_str = strtok(NULL, " ");
|
||||
if (val1_str == NULL) {
|
||||
printf("%s", usage);
|
||||
return;
|
||||
}
|
||||
val1 = parse_val(val1_str);
|
||||
|
||||
if (strcmp(operator_str, "==") == 0) {
|
||||
if (!(val0 == val1)) {
|
||||
goto fail;
|
||||
}
|
||||
} else if (strcmp(operator_str, "!=") == 0) {
|
||||
if (!(val0 != val1)) {
|
||||
goto fail;
|
||||
}
|
||||
} else if (strcmp(operator_str, ">=") == 0) {
|
||||
if (!(val0 >= val1)) {
|
||||
goto fail;
|
||||
}
|
||||
} else if (strcmp(operator_str, "<=") == 0) {
|
||||
if (!(val0 <= val1)) {
|
||||
goto fail;
|
||||
}
|
||||
} else if (strcmp(operator_str, "<") == 0) {
|
||||
if (!(val0 < val1)) {
|
||||
goto fail;
|
||||
}
|
||||
} else if (strcmp(operator_str, ">") == 0) {
|
||||
if (!(val0 > val1)) {
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
printf("%s", usage);
|
||||
}
|
||||
|
||||
return;
|
||||
fail:
|
||||
printf("ASSERT: %s %s %s\n", val0_str, operator_str, val1_str);
|
||||
printf("%s=%ld, %s=%ld\n", val0_str, val0, val1_str, val1);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void mem(char *arg_list)
|
||||
{
|
||||
uint16_t addr, bytes, i;
|
||||
@@ -246,12 +360,12 @@ static void mem(char *arg_list)
|
||||
return;
|
||||
}
|
||||
|
||||
addr = strtol(token, NULL, 0);
|
||||
addr = parse_val(token);
|
||||
token = strtok(NULL, " ");
|
||||
if (token == NULL) {
|
||||
bytes = 1;
|
||||
} else {
|
||||
bytes = strtol(token, NULL, 0);
|
||||
bytes = parse_val(token);
|
||||
}
|
||||
|
||||
token = strtok(NULL, " ");
|
||||
@@ -333,11 +447,11 @@ static void run(char *arg_list)
|
||||
}
|
||||
|
||||
if (cpu.halted) {
|
||||
printf("CPU halted after %ld cycles\n", cpu.metrics.cycles);
|
||||
gb_log("CPU halted after %ld cycles\n", cpu.metrics.cycles);
|
||||
} else if (paused) {
|
||||
printf("Interrupted.\n");
|
||||
gb_log("Interrupted.\n");
|
||||
} else {
|
||||
printf("Breakpoint hit\n");
|
||||
gb_log("Breakpoint hit\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +470,7 @@ static void set_breakpoint(char *arg_string)
|
||||
return;
|
||||
}
|
||||
|
||||
addr = strtol(token, NULL, 0);
|
||||
addr = parse_val(token);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(breakpoints); i++) {
|
||||
if (breakpoints[i].active == false) {
|
||||
@@ -379,7 +493,7 @@ static void delete_breakpoint(char *arg_string)
|
||||
return;
|
||||
}
|
||||
|
||||
bkpt = strtol(token, NULL, 0);
|
||||
bkpt = parse_val(token);
|
||||
|
||||
if (bkpt < 0 || bkpt >= ARRAY_SIZE(breakpoints) || !breakpoints[bkpt].active) {
|
||||
printf("%d is not a valid breakpoint number\n", bkpt);
|
||||
@@ -435,7 +549,7 @@ static void breakpoint(char *arg_list)
|
||||
|
||||
cmd = tri_get_string_autocomplete(&commands, arg_list, &ambiguous);
|
||||
|
||||
if(ambiguous) {
|
||||
if (ambiguous) {
|
||||
printf("ambiguous breakpoint command: '%s'\n", arg_list);
|
||||
return;
|
||||
}
|
||||
@@ -465,9 +579,90 @@ static bool cpu_at_breakpoint(void)
|
||||
return breakpoint_is_at_addr(lr35902_get_reg_16(&cpu, LR35902_REG_PC));
|
||||
}
|
||||
|
||||
static void process_cmd(char *cmd)
|
||||
static void process_cmd(const char *cmd_str, struct tri *commands)
|
||||
{
|
||||
char cmd_buffer[INPUT_MAX_LEN]; /* Buffer for command parsing */
|
||||
bool ambiguous;
|
||||
gbdb_cmd *cmd;
|
||||
|
||||
/* Current point in the line (there may be more than
|
||||
* one command on a line) */
|
||||
const char *process_str;
|
||||
|
||||
/* String containing the arguments following a command */
|
||||
char *arg_str;
|
||||
|
||||
process_str = cmd_str;
|
||||
|
||||
char *comment;
|
||||
comment = strchr(process_str, '#');
|
||||
if (comment != NULL) {
|
||||
*comment = '\0';
|
||||
}
|
||||
|
||||
do {
|
||||
int i;
|
||||
|
||||
while (process_str[0] == ' ') { process_str++; };
|
||||
for (i = 0;; i++) {
|
||||
if (process_str[i] == '\0' ||
|
||||
process_str[i] == ';') {
|
||||
cmd_buffer[i] = '\0';
|
||||
break;
|
||||
} else {
|
||||
cmd_buffer[i] = process_str[i];
|
||||
}
|
||||
}
|
||||
|
||||
strip_newline(cmd_buffer);
|
||||
arg_str = strchr(cmd_buffer, ' ');
|
||||
if (arg_str != NULL) {
|
||||
arg_str[0] = '\0';
|
||||
arg_str++;
|
||||
}
|
||||
|
||||
if (strlen(cmd_buffer) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd = tri_get_string_autocomplete(commands, cmd_buffer, &ambiguous);
|
||||
|
||||
if (ambiguous) {
|
||||
printf("ambiguous command: '%s'\n", cmd_buffer);
|
||||
break;
|
||||
} else if (cmd == NULL) {
|
||||
printf("unrecognized command: '%s'\n", cmd_buffer);
|
||||
break;
|
||||
} else {
|
||||
cmd(arg_str);
|
||||
}
|
||||
|
||||
process_str = strchr(process_str, ';');
|
||||
if (process_str != NULL) {
|
||||
process_str++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int load_initfile(const char *initfile_path, struct tri *commands)
|
||||
{
|
||||
FILE* file;
|
||||
char buffer[INPUT_MAX_LEN];
|
||||
|
||||
file = fopen(initfile_path, "r");
|
||||
|
||||
if (file == NULL) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
while(fgets(buffer, sizeof(buffer), file)) {
|
||||
process_cmd(buffer, commands);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -475,7 +670,6 @@ int main(int argc, char **argv)
|
||||
struct tri commands;
|
||||
char line_buffer[INPUT_MAX_LEN]; /* Buffer for input command */
|
||||
char old_buffer[INPUT_MAX_LEN]; /* Buffer for previous input */
|
||||
char cmd_buffer[INPUT_MAX_LEN]; /* Buffer for command parsing */
|
||||
|
||||
line_buffer[0] = '\0';
|
||||
old_buffer[0] = '\0';
|
||||
@@ -496,24 +690,23 @@ int main(int argc, char **argv)
|
||||
tri_add_string(&commands, "?", help);
|
||||
tri_add_string(&commands, "breakpoint", breakpoint);
|
||||
tri_add_string(&commands, "reset", init);
|
||||
tri_add_string(&commands, "assert", assert);
|
||||
tri_add_string(&commands, "set", set);
|
||||
tri_add_string(&commands, "echo", echo);
|
||||
|
||||
init();
|
||||
|
||||
while (1) {
|
||||
bool ambiguous;
|
||||
gbdb_cmd* cmd;
|
||||
if (argc > 1) {
|
||||
if (load_initfile(argv[1], &commands)) {
|
||||
printf("Failed to load initfile\n");
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
/* String to use for input (line_buffer or
|
||||
* old_buffer) */
|
||||
char *cmd_str;
|
||||
|
||||
/* Current point in the line (there may be more than
|
||||
* one command on a line) */
|
||||
char *process_str;
|
||||
|
||||
/* String containing the arguments following a command */
|
||||
char *arg_str;
|
||||
|
||||
show_prompt();
|
||||
cmd_str = fgets(line_buffer, INPUT_MAX_LEN, stdin);
|
||||
if (cmd_str == NULL) {
|
||||
@@ -528,47 +721,7 @@ int main(int argc, char **argv)
|
||||
cmd_str = old_buffer;
|
||||
}
|
||||
|
||||
process_str = cmd_str;
|
||||
do {
|
||||
int i;
|
||||
|
||||
while (process_str[0] == ' ') { process_str++; };
|
||||
for (i = 0;; i++) {
|
||||
if (process_str[i] == '\0' ||
|
||||
process_str[i] == ';') {
|
||||
cmd_buffer[i] = '\0';
|
||||
break;
|
||||
} else {
|
||||
cmd_buffer[i] = process_str[i];
|
||||
}
|
||||
}
|
||||
|
||||
strip_newline(cmd_buffer);
|
||||
arg_str = strchr(cmd_buffer, ' ');
|
||||
if (arg_str != NULL) {
|
||||
arg_str[0] = '\0';
|
||||
arg_str++;
|
||||
}
|
||||
|
||||
cmd = tri_get_string_autocomplete(&commands, cmd_buffer, &ambiguous);
|
||||
|
||||
if (ambiguous) {
|
||||
printf("ambiguous command: '%s'\n", cmd_buffer);
|
||||
break;
|
||||
} else if (cmd == NULL) {
|
||||
printf("unrecognized command: '%s'\n", cmd_buffer);
|
||||
break;
|
||||
} else {
|
||||
cmd(arg_str);
|
||||
}
|
||||
|
||||
process_str = strchr(process_str, ';');
|
||||
if (process_str != NULL) {
|
||||
process_str++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
process_cmd(cmd_str, &commands);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user