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

164
gbdb_boot_test Normal file
View File

@@ -0,0 +1,164 @@
bootrom ../bootrom.gb
echo Running test...
set quiet 1
assert $pc == 0
step
# Executed LD SP, D16 (0xfffe)
assert $pc == 3
assert $sp == 0xfffe
step
# Executed XOR A
assert $pc == 4
assert $a == 0
step
assert $pc == 7
assert $hl == 0x9fff
step
assert $pc == 8
assert $hl == 0x9ffe
step
assert $pc == 10
assert $f == 0x2
step
breakpoint add 0xc
step 100000
breakpoint del 0
assert $pc == 0xc
assert $hl == 0x7fff
# TODO: There is a bug in GBDB where breakpoints fire at the start of an
# instruction and "step" stops at the end of an instruction
step; step
assert $pc == 0xf
assert $hl == 0xff26
step
assert $pc == 0x11
assert $c == 0x11
step
assert $pc == 0x13
assert $a == 0x80
step
assert $pc == 0x14
assert $hl == 0xff25
step
assert $pc == 0x15
# LD (0xFF00 + $C), A
step
assert $pc == 0x16
assert $c == 0x12
break add 0x27
step 1000
assert $pc == 0x27
break del 0
step; step
assert $pc == 0x28
step
assert $pc == 0x95
step
assert $pc == 0x96
step
assert $pc == 0x98
step
assert $pc == 0x99
step
assert $pc == 0x9b
step
assert $pc == 0x9c
step
assert $pc == 0x9d
step
assert $pc == 0x9f
step
assert $pc == 0xa0
step
assert $pc == 0xa1
break add 0xa3
step 100000
break del 0
assert $pc == 0xa3
step #FIXME: bugs
step
assert $pc == 0xa4
step
assert $pc == 0xa5
step
assert $pc == 0xa6
step
assert $pc == 0xa7
step
assert $pc == 0x2b
step
assert $pc == 0x96
break add 0x34
step 10000
assert $pc == 0x34
break del 0
step; step
assert $pc == 0x37
assert $de == 0xd8
step;
assert $pc == 0x39
assert $b == 0x08
break add 0x40
step 10000
assert $pc == 0x40
assert $b == 0
break del 0
step; step
assert $pc == 0x42
assert $a == 0x19
step;
assert $pc == 0x45
step
assert $pc == 0x48
assert $hl == 0x992f
step
assert $pc == 0x4a
assert $c == 0x0c
echo Test passed!
regs
# exit

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,70 +579,27 @@ 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)
{
}
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';
tri_init(&commands);
tri_add_string(&commands, "step", step);
tri_add_string(&commands, "run", run);
tri_add_string(&commands, "regs", regs);
tri_add_string(&commands, "stats", stats);
tri_add_string(&commands, "exit", quit);
tri_add_string(&commands, "quit", quit);
tri_add_string(&commands, "load", load);
tri_add_string(&commands, "bootrom", load_bootrom);
tri_add_string(&commands, "mem", mem);
tri_add_string(&commands, "dump", mem_dump);
tri_add_string(&commands, "peek", peek);
tri_add_string(&commands, "help", help);
tri_add_string(&commands, "?", help);
tri_add_string(&commands, "breakpoint", breakpoint);
tri_add_string(&commands, "reset", init);
init();
while (1) {
bool ambiguous;
gbdb_cmd* cmd;
/* String to use for input (line_buffer or
* old_buffer) */
char *cmd_str;
gbdb_cmd *cmd;
/* Current point in the line (there may be more than
* one command on a line) */
char *process_str;
const 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) {
printf("\n");
return 0;
}
if (line_buffer[0] != '\n') {
cmd_str = line_buffer;
strncpy(old_buffer, line_buffer, INPUT_MAX_LEN);
} else {
cmd_str = old_buffer;
}
process_str = cmd_str;
char *comment;
comment = strchr(process_str, '#');
if (comment != NULL) {
*comment = '\0';
}
do {
int i;
@@ -550,7 +621,11 @@ int main(int argc, char **argv)
arg_str++;
}
cmd = tri_get_string_autocomplete(&commands, cmd_buffer, &ambiguous);
if (strlen(cmd_buffer) == 0) {
return;
}
cmd = tri_get_string_autocomplete(commands, cmd_buffer, &ambiguous);
if (ambiguous) {
printf("ambiguous command: '%s'\n", cmd_buffer);
@@ -569,6 +644,84 @@ int main(int argc, char **argv)
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)
{
struct tri commands;
char line_buffer[INPUT_MAX_LEN]; /* Buffer for input command */
char old_buffer[INPUT_MAX_LEN]; /* Buffer for previous input */
line_buffer[0] = '\0';
old_buffer[0] = '\0';
tri_init(&commands);
tri_add_string(&commands, "step", step);
tri_add_string(&commands, "run", run);
tri_add_string(&commands, "regs", regs);
tri_add_string(&commands, "stats", stats);
tri_add_string(&commands, "exit", quit);
tri_add_string(&commands, "quit", quit);
tri_add_string(&commands, "load", load);
tri_add_string(&commands, "bootrom", load_bootrom);
tri_add_string(&commands, "mem", mem);
tri_add_string(&commands, "dump", mem_dump);
tri_add_string(&commands, "peek", peek);
tri_add_string(&commands, "help", help);
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();
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;
show_prompt();
cmd_str = fgets(line_buffer, INPUT_MAX_LEN, stdin);
if (cmd_str == NULL) {
printf("\n");
return 0;
}
if (line_buffer[0] != '\n') {
cmd_str = line_buffer;
strncpy(old_buffer, line_buffer, INPUT_MAX_LEN);
} else {
cmd_str = old_buffer;
}
process_cmd(cmd_str, &commands);
}
return 0;