gbdb: add scripting features for automatic testing

This commit is contained in:
2018-01-01 16:50:14 -08:00
parent 3f11a3a167
commit 92f9ef591b
2 changed files with 426 additions and 109 deletions

View File

@@ -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;