diff --git a/gbdb_boot_test b/gbdb_boot_test new file mode 100644 index 0000000..fd54db0 --- /dev/null +++ b/gbdb_boot_test @@ -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 \ No newline at end of file diff --git a/src/apps/gbdb.c b/src/apps/gbdb.c index ecc924a..95a8642 100644 --- a/src/apps/gbdb.c +++ b/src/apps/gbdb.c @@ -7,8 +7,10 @@ #include #include #include +#include #include #include +#include #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 \n"); + gb_log("usage: load \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 \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;