Its been too long since a checkin, but here's some of the improvements: - Support for diffing with other emulators - Better disassmbed output - New CPU instructions implemented - Lots of CPU fixes
915 lines
18 KiB
C
915 lines
18 KiB
C
/*
|
|
* A CLI-based tester and debugger for the Gameboy's LR35902 CPU
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "common/common.h"
|
|
#include "gb_disas.h"
|
|
#include "gbemu/cpu.h"
|
|
#include "gbemu/video.h"
|
|
#include "common/tri.h"
|
|
|
|
#define INPUT_MAX_LEN 512
|
|
#define MAX_BREAKPTS 16 /* Should be plenty for anyone */
|
|
|
|
typedef void (gbdb_cmd)(struct tokens **tokens);
|
|
|
|
static void breakpoint_addr_hit(uint16_t id);
|
|
static void breakpoint_cb(struct lr35902_state *lr);
|
|
static void reset_breakpoints(void);
|
|
static int64_t parse_val(const char *str);
|
|
|
|
static const char prompt[] = "gbdb >";
|
|
static const char usage[] =
|
|
"Available commands:\n"
|
|
"load <file>: loads the given file as the gameboy cartridge\n"
|
|
"run: runs the CPU until a breakpoint or halt is hit\n"
|
|
"break <addr>: Adds a breakpoint for the given addess\n"
|
|
"step <cycles>: executes \"cycle\" instructions (default 1)\n"
|
|
"regs: dumps the state of the registers\n"
|
|
"disas: view the next instruction to run\n"
|
|
"exit: quit the program\n";
|
|
|
|
static struct lr35902_ops cpu_ops;
|
|
static struct lr35902_state cpu;
|
|
static struct gb_video video;
|
|
static void *memory = NULL;
|
|
|
|
static int paused_breakpoint = 0;
|
|
static volatile sig_atomic_t paused_signal = 0;
|
|
static volatile sig_atomic_t paused = 0;
|
|
|
|
static struct {
|
|
bool quiet;
|
|
int log_fd;
|
|
int trace_fd;
|
|
} config;
|
|
|
|
void gb_log(const char *fmt, ...)
|
|
{
|
|
va_list args, args_copy;
|
|
|
|
va_start(args, fmt);
|
|
va_copy(args_copy, args);
|
|
if (!config.quiet) {
|
|
vprintf(fmt, args);
|
|
}
|
|
va_end(args);
|
|
|
|
if (config.log_fd != -1) {
|
|
vdprintf(config.log_fd, fmt, args_copy);
|
|
}
|
|
}
|
|
|
|
void gb_error(const char *fmt, ...)
|
|
{
|
|
va_list args, args_copy;
|
|
|
|
va_start(args, fmt);
|
|
va_copy(args_copy, args);
|
|
vprintf(fmt, args);
|
|
va_end(args);
|
|
|
|
if (config.log_fd != -1) {
|
|
vdprintf(config.log_fd, fmt, args_copy);
|
|
}
|
|
}
|
|
|
|
|
|
static void break_execution_handler(int signum)
|
|
{
|
|
paused = 1;
|
|
}
|
|
|
|
static void strip_newline(char *string)
|
|
{
|
|
char *pos;
|
|
|
|
if (string == NULL)
|
|
return;
|
|
|
|
if ((pos=strchr(string, '\n')) != NULL) {
|
|
*pos = '\0';
|
|
}
|
|
}
|
|
|
|
static void init(void)
|
|
{
|
|
cpu_ops.undef_d3 = breakpoint_cb;
|
|
|
|
lr35902_init(&cpu, memory, &cpu_ops);
|
|
gb_video_init(&video, memory);
|
|
gb_mem_init(memory, &video);
|
|
|
|
config.log_fd = -1;
|
|
config.trace_fd = -1;
|
|
}
|
|
|
|
static void print_trace(void)
|
|
{
|
|
if (config.trace_fd > -1) {
|
|
uint8_t instr = gb_mem_read(memory, cpu.pc);
|
|
|
|
dprintf(config.trace_fd,
|
|
"A:%02X F:%c%c%c%c BC:%04X DE:%04x HL:%04x SP:%04x PC:%04x 0x%02x",
|
|
cpu.a,
|
|
(cpu.zf) ? 'Z' : '-', (cpu.nf) ? 'N' : '-',
|
|
(cpu.hf) ? 'H' : '-', (cpu.cf) ? 'C' : '-',
|
|
cpu.bc, cpu.de, cpu.hl, cpu.sp, cpu.pc, instr);
|
|
|
|
if (instr == 0xcb) {
|
|
dprintf(config.trace_fd, " 0x%02x", gb_mem_read(memory, cpu.pc + 1));
|
|
}
|
|
|
|
dprintf(config.trace_fd, "\n");
|
|
}
|
|
}
|
|
|
|
static void cycle()
|
|
{
|
|
int cycles;
|
|
|
|
print_trace();
|
|
cycles = lr35902_cycle(&cpu);
|
|
gb_video_cycle(&video, cycles);
|
|
}
|
|
|
|
static void show_prompt()
|
|
{
|
|
printf("%s ", prompt);
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void call_next_cmd(struct tokens** tokens,
|
|
struct tri *commands,
|
|
const char *cmdname)
|
|
{
|
|
char * remainder;
|
|
gbdb_cmd *cmd;
|
|
bool ambiguous = false;
|
|
|
|
char *token = token_next(tokens);
|
|
if (!token) {
|
|
return;
|
|
}
|
|
|
|
cmd = tri_get_string_autocomplete(commands, token, &ambiguous);
|
|
if (ambiguous) {
|
|
gb_error("ambiguous %s command: '%s'\n", cmdname, token);
|
|
return;
|
|
}
|
|
if (cmd == NULL) {
|
|
gb_error("unrecognized %s command: '%s'\n", cmdname, token);
|
|
return;
|
|
}
|
|
|
|
cmd(tokens);
|
|
}
|
|
|
|
static void set_logfile(struct tokens **tokens)
|
|
{
|
|
char *token = token_next(tokens);
|
|
int fd;
|
|
|
|
|
|
if (token == NULL || strcmp(token, "") == 0) {
|
|
if (config.log_fd != -1) {
|
|
close(config.log_fd);
|
|
}
|
|
config.log_fd = -1;
|
|
return;
|
|
}
|
|
|
|
fd = open(token,
|
|
O_WRONLY | O_TRUNC | O_CREAT,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (fd < 0) {
|
|
gb_error("Failed to open logfile %s, %s\n", token, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if (config.log_fd != -1) {
|
|
close(config.log_fd);
|
|
}
|
|
|
|
config.log_fd = fd;
|
|
}
|
|
|
|
static void set_tracefile(struct tokens **tokens)
|
|
{
|
|
char *token = token_next(tokens);
|
|
int fd;
|
|
|
|
|
|
if (token == NULL || strcmp(token, "") == 0) {
|
|
if (config.trace_fd >= -1) {
|
|
close(config.trace_fd);
|
|
}
|
|
config.trace_fd = -1;
|
|
return;
|
|
}
|
|
|
|
fd = open(token,
|
|
O_WRONLY | O_TRUNC | O_CREAT,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (fd < 0) {
|
|
gb_error("Failed to open tracefile %s, %s\n", token, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if (config.trace_fd != -1) {
|
|
close(config.trace_fd);
|
|
}
|
|
|
|
config.trace_fd = fd;
|
|
gb_log("Trace fd=%d\n", config.trace_fd);
|
|
}
|
|
|
|
static void set_quiet_on(struct tokens **tokens)
|
|
{
|
|
config.quiet = true;
|
|
}
|
|
|
|
static void set_quiet_off(struct tokens **tokens)
|
|
{
|
|
config.quiet = false;
|
|
}
|
|
|
|
static void set_quiet(struct tokens **tokens)
|
|
{
|
|
static bool init = false;
|
|
static struct tri commands;
|
|
|
|
if (!init) {
|
|
tri_init(&commands);
|
|
tri_add_string(&commands, "on", set_quiet_on);
|
|
tri_add_string(&commands, "true", set_quiet_on);
|
|
tri_add_string(&commands, "off", set_quiet_off);
|
|
tri_add_string(&commands, "false", set_quiet_off);
|
|
init = true;
|
|
}
|
|
|
|
call_next_cmd(tokens, &commands, "set quiet");
|
|
}
|
|
|
|
static void set_video_debug(struct tokens **tokens)
|
|
{
|
|
video.debug_logging = true;
|
|
}
|
|
|
|
static void set(struct tokens **tokens)
|
|
{
|
|
static bool init = false;
|
|
static struct tri commands;
|
|
|
|
if (!init) {
|
|
tri_init(&commands);
|
|
tri_add_string(&commands, "quiet", set_quiet);
|
|
tri_add_string(&commands, "logfile", set_logfile);
|
|
tri_add_string(&commands, "trace", set_tracefile);
|
|
tri_add_string(&commands, "video_debug", set_video_debug);
|
|
init = true;
|
|
}
|
|
|
|
call_next_cmd(tokens, &commands, "set");
|
|
}
|
|
|
|
static void echo(struct tokens **tokens)
|
|
{
|
|
char *token;
|
|
while ((token = token_next(tokens)) != NULL) {
|
|
gb_log("%s ", token);
|
|
}
|
|
|
|
gb_log("\n");
|
|
}
|
|
|
|
static void breakpoint_cb(struct lr35902_state *lr)
|
|
{
|
|
int i;
|
|
|
|
paused_breakpoint = 1;
|
|
paused = 1;
|
|
|
|
breakpoint_addr_hit(cpu.pc);
|
|
}
|
|
|
|
|
|
static void step(struct tokens **tokens)
|
|
{
|
|
uint64_t steps, end_steps;
|
|
char *token;
|
|
|
|
token = token_next(tokens);
|
|
if (token == NULL) {
|
|
steps = 1;
|
|
} else {
|
|
steps = strtol(token, NULL, 0);
|
|
}
|
|
|
|
paused = 0;
|
|
signal(SIGINT, break_execution_handler);
|
|
end_steps = cpu.metrics.retired_instrs + steps;
|
|
|
|
cycle();
|
|
reset_breakpoints();
|
|
|
|
while(end_steps > cpu.metrics.retired_instrs &&
|
|
!paused) {
|
|
cycle();
|
|
}
|
|
|
|
if (end_steps <= cpu.metrics.retired_instrs) {
|
|
gb_log("CPU stopped after %" PRId64 " instructions\n", steps);
|
|
} else if (paused_breakpoint) {
|
|
gb_log("Breakpoint hit\n");
|
|
} else if (paused_signal){
|
|
gb_log("Interrupted\n");
|
|
}
|
|
|
|
paused = 0;
|
|
paused_signal = 0;
|
|
paused_breakpoint = 0;
|
|
}
|
|
|
|
static void regs(struct tokens **tokens)
|
|
{
|
|
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);
|
|
|
|
gb_log("AF: 0x%04x A: 0x%02x F: 0x%02x\n", (uint16_t) (cpu.a << 8) | f, cpu.a, f);
|
|
gb_log("BC: 0x%04x B: 0x%02x C: 0x%02x\n", cpu.bc, cpu.b, cpu.c);
|
|
gb_log("DE: 0x%04x D: 0x%02x E: 0x%02x\n", cpu.de, cpu.d, cpu.e);
|
|
gb_log("HL: 0x%04x H: 0x%02x L: 0x%02x\n", cpu.hl, cpu.h, cpu.l);
|
|
gb_log("PC: 0x%04x\n", cpu.pc);
|
|
gb_log("SP: 0x%04x\n", cpu.sp);
|
|
}
|
|
|
|
static uint8_t decode_fetch_mem(void *opaque, uint16_t addr)
|
|
{
|
|
return gb_mem_read(memory, addr);
|
|
}
|
|
|
|
static void disas(struct tokens **tokens)
|
|
{
|
|
uint16_t pc = lr35902_get_reg_16(&cpu, LR35902_REG_PC);
|
|
uint16_t old_pc = pc;
|
|
const char *disas_cnt_str;
|
|
int64_t num_disas, i;
|
|
|
|
|
|
if (tokens) {
|
|
disas_cnt_str = token_next(tokens);
|
|
if (disas_cnt_str == NULL) {
|
|
num_disas = 1;
|
|
} else {
|
|
num_disas = parse_val(disas_cnt_str);
|
|
}
|
|
} else {
|
|
num_disas = 1;
|
|
}
|
|
|
|
for (i = 0; i < num_disas; i++) {
|
|
const char *opcode;
|
|
old_pc = pc;
|
|
opcode = gb_byte_to_opcode(&pc, decode_fetch_mem, &memory);
|
|
gb_log("0x%04x:%s\n", old_pc, opcode);
|
|
free((void *) opcode);
|
|
}
|
|
}
|
|
|
|
static void vstep(struct tokens **tokens)
|
|
{
|
|
disas(NULL);
|
|
step(tokens);
|
|
regs(NULL);
|
|
}
|
|
|
|
static void stats(struct tokens **tokens)
|
|
{
|
|
gb_log("Cycles: %" PRId64 "\n", cpu.metrics.cycles);
|
|
gb_log("Retired Insructions %" PRId64 "\n", cpu.metrics.retired_instrs);
|
|
gb_log("Memory Reads: %" PRId64 " \n", cpu.metrics.mem_reads);
|
|
gb_log("Memory Writes: %" PRId64 "\n", cpu.metrics.mem_writes);
|
|
}
|
|
|
|
static void comment(struct tokens **tokens)
|
|
{
|
|
/* Gobble up everything */
|
|
do {} while (token_next(tokens));
|
|
}
|
|
|
|
static void help(struct tokens **tokens)
|
|
{
|
|
gb_log(usage);
|
|
}
|
|
|
|
static int64_t parse_reg_str(const char *str)
|
|
{
|
|
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);
|
|
|
|
const struct {
|
|
const char *str;
|
|
int64_t value;
|
|
} entries[] = {
|
|
{ "af", (cpu.a << 8) | f },
|
|
{ "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", f },
|
|
};
|
|
|
|
|
|
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(struct tokens **tokens)
|
|
{
|
|
char *val0_str = NULL, *val1_str = NULL, *operator_str = NULL;
|
|
const char *usage = "usage: assert <value> <operator> <value>\n";
|
|
int64_t val0, val1;
|
|
|
|
val0_str = strdup(token_next(tokens));
|
|
if (val0_str == NULL) {
|
|
gb_error("%s", usage);
|
|
return;
|
|
}
|
|
val0 = parse_val(val0_str);
|
|
|
|
operator_str = strdup(token_next(tokens));
|
|
if (operator_str == NULL) {
|
|
gb_error("%s", usage);
|
|
return;
|
|
}
|
|
|
|
val1_str = strdup(token_next(tokens));
|
|
if (val1_str == NULL) {
|
|
gb_error("%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 {
|
|
gb_error("%s", usage);
|
|
}
|
|
|
|
return;
|
|
fail:
|
|
gb_error("ASSERT: %s %s %s\n", val0_str, operator_str, val1_str);
|
|
gb_error("%s=0x%" PRIx64 ", %s=0x%" PRIx64 "\n", val0_str, val0, val1_str, val1);
|
|
free(val0_str);
|
|
free(val1_str);
|
|
free(operator_str);
|
|
regs(NULL);
|
|
stats(NULL);
|
|
exit(1);
|
|
}
|
|
|
|
static void mem(struct tokens **tokens)
|
|
{
|
|
uint16_t addr, bytes, addr_end;
|
|
char *token = token_next(tokens);
|
|
int i;
|
|
|
|
if (token == NULL) {
|
|
gb_error("usage: mem <addr> (num bytes) (format)\n");
|
|
return;
|
|
}
|
|
|
|
addr = parse_val(token);
|
|
token = token_next(tokens);
|
|
if (token == NULL) {
|
|
bytes = 1;
|
|
} else {
|
|
bytes = parse_val(token);
|
|
}
|
|
|
|
for (i = 0; i < bytes; i++) {
|
|
//TODO: Make sure this has no side effects
|
|
gb_log("0x%04x: 0x%02x\n", addr + i, gb_mem_read(memory, addr + i));
|
|
}
|
|
}
|
|
|
|
static void load(struct tokens **tokens)
|
|
{
|
|
char *token = token_next(tokens);
|
|
if (token == NULL) {
|
|
gb_error("usage: load <file>\n");
|
|
return;
|
|
}
|
|
|
|
gb_log("loading %s\n", token);
|
|
|
|
gb_memory_load_file(memory, GB_MEMORY_CART, token);
|
|
}
|
|
|
|
static void load_bootrom(struct tokens **tokens)
|
|
{
|
|
char *token = token_next(tokens);
|
|
if (token == NULL) {
|
|
gb_error("usage: bootrom <file>\n");
|
|
return;
|
|
}
|
|
|
|
gb_memory_load_file(memory, GB_MEMORY_BOOTROM, token);
|
|
}
|
|
|
|
|
|
static void quit(struct tokens **tokens)
|
|
{
|
|
exit(0);
|
|
}
|
|
|
|
static __attribute__((hot)) void do_run(void) {
|
|
paused = 0;
|
|
signal(SIGINT, break_execution_handler);
|
|
|
|
cycle();
|
|
reset_breakpoints();
|
|
|
|
while(!paused) {
|
|
cycle();
|
|
}
|
|
|
|
if (paused_signal) {
|
|
gb_log("Interrupted.\n");
|
|
} else if (paused_breakpoint) {
|
|
gb_log("Breakpoint hit\n");
|
|
}
|
|
|
|
paused = 0;
|
|
paused_breakpoint = 0;
|
|
paused_signal = 0;
|
|
}
|
|
|
|
static void run(struct tokens **tokens )
|
|
{
|
|
do_run();
|
|
}
|
|
|
|
static struct breakpoint {
|
|
uint16_t addr;
|
|
int id;
|
|
bool temp;
|
|
uint8_t op;
|
|
} breakpoints[MAX_BREAKPTS];
|
|
|
|
static int num_breakpoints = 0;
|
|
|
|
static struct breakpoint *get_breakpoint(int id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < num_breakpoints; i++) {
|
|
if (breakpoints[i].id == id) {
|
|
return &breakpoints[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void reset_breakpoints(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < num_breakpoints; i++) {
|
|
struct breakpoint *bkpt = &breakpoints[i];
|
|
gb_mem_force_write(memory, bkpt->addr, 0xd3);
|
|
}
|
|
}
|
|
|
|
static int do_set_breakpoint(uint16_t addr, bool temp)
|
|
{
|
|
static int id = 0;
|
|
|
|
if (num_breakpoints < ARRAY_SIZE(breakpoints)) {
|
|
breakpoints[num_breakpoints].addr = addr;
|
|
breakpoints[num_breakpoints].op = gb_mem_read(memory, addr);
|
|
breakpoints[num_breakpoints].id = id++;
|
|
breakpoints[num_breakpoints].temp = temp;
|
|
gb_mem_force_write(memory, addr, 0xd3);
|
|
num_breakpoints++;
|
|
} else {
|
|
gb_error("maximum number of breakpoints reached\n");
|
|
}
|
|
}
|
|
|
|
static void set_breakpoint(struct tokens **tokens)
|
|
{
|
|
uint16_t addr, i;
|
|
char *token = token_next(tokens);
|
|
|
|
if (token == NULL) {
|
|
gb_error("usage: breakpoint add <addr>\n");
|
|
return;
|
|
}
|
|
|
|
addr = parse_val(token);
|
|
do_set_breakpoint(addr, false);
|
|
}
|
|
|
|
static void runto(struct tokens **tokens)
|
|
{
|
|
uint16_t addr, i, rc;
|
|
char *token = token_next(tokens);
|
|
|
|
if (token == NULL) {
|
|
gb_error("usage: runto <addr>\n");
|
|
return;
|
|
}
|
|
|
|
addr = parse_val(token);
|
|
rc = do_set_breakpoint(addr, true);
|
|
if (rc < 0) {
|
|
gb_error("failed to set breakpoint\n");
|
|
return;
|
|
}
|
|
|
|
do_run();
|
|
}
|
|
|
|
static int do_delete_breakpoint(int id)
|
|
{
|
|
struct breakpoint *bkpt = get_breakpoint(id);
|
|
int index;
|
|
|
|
if (bkpt == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
gb_mem_force_write(memory, bkpt->addr, bkpt->op);
|
|
|
|
index = bkpt - breakpoints;
|
|
memmove(&breakpoints[index], &breakpoints[index + 1],
|
|
num_breakpoints - index - 1);
|
|
num_breakpoints--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void breakpoint_addr_hit(uint16_t addr)
|
|
{
|
|
struct breakpoint *bkpt = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < num_breakpoints; i++) {
|
|
if (breakpoints[i].addr == addr) {
|
|
bkpt = &breakpoints[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (bkpt == NULL) {
|
|
gb_error("No breakpoint found at addr=%d\n", addr);
|
|
return;
|
|
}
|
|
|
|
if (bkpt->temp) {
|
|
do_delete_breakpoint(bkpt->id);
|
|
}
|
|
}
|
|
|
|
static void delete_breakpoint(struct tokens **tokens)
|
|
{
|
|
char *token = token_next(tokens);
|
|
int rc, bkpt;
|
|
|
|
if (token == NULL) {
|
|
gb_error("usage: breakpoint rm <bkpt num>\n");
|
|
return;
|
|
}
|
|
|
|
bkpt = parse_val(token);
|
|
|
|
rc = do_delete_breakpoint(bkpt);
|
|
if (rc < 0) {
|
|
gb_error("%d is not a valid breakpoint number\n", bkpt);
|
|
}
|
|
}
|
|
|
|
static void display_breakpoints(struct tokens **tokens)
|
|
{
|
|
int i;
|
|
|
|
if (num_breakpoints) {
|
|
for (i = 0; i < num_breakpoints; i++) {
|
|
gb_log("#%d: 0x%04x\n", breakpoints[i].id, breakpoints[i].addr);
|
|
}
|
|
} else {
|
|
gb_log("No breakpoints set\n");
|
|
}
|
|
}
|
|
|
|
static void breakpoint(struct tokens **tokens)
|
|
{
|
|
static bool init = false;
|
|
static struct tri commands;
|
|
|
|
if (!init) {
|
|
memset(breakpoints, 0, sizeof(breakpoints));
|
|
tri_init(&commands);
|
|
tri_add_string(&commands, "add", set_breakpoint);
|
|
tri_add_string(&commands, "set", set_breakpoint);
|
|
tri_add_string(&commands, "delete", delete_breakpoint);
|
|
tri_add_string(&commands, "remove", delete_breakpoint);
|
|
tri_add_string(&commands, "info", display_breakpoints);
|
|
tri_add_string(&commands, "display", display_breakpoints);
|
|
tri_add_string(&commands, "list", display_breakpoints);
|
|
init = true;
|
|
}
|
|
|
|
call_next_cmd(tokens, &commands, "breakpoint");
|
|
}
|
|
|
|
static bool breakpoint_is_at_addr(uint16_t addr) {
|
|
int i;
|
|
|
|
for (i = 0; i < num_breakpoints; i++) {
|
|
if (breakpoints[i].addr == addr) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void process_cmd(const char *cmd_str, struct tri *commands)
|
|
{
|
|
struct tokens *statements;
|
|
struct tokens *tokens;
|
|
char *statement, *token;
|
|
|
|
statements = tokenize(cmd_str, ";\n");
|
|
if (!statements) {
|
|
return;
|
|
}
|
|
|
|
while ((statement = token_next(&statements)) != NULL) {
|
|
tokens = tokenize(statement, " ");
|
|
if (!tokens) {
|
|
continue;
|
|
}
|
|
|
|
call_next_cmd(&tokens, commands, "");
|
|
|
|
/* Ensure there are no leftover tokens to leak */
|
|
do {} while (token_next(&tokens));
|
|
}
|
|
|
|
}
|
|
|
|
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, "#", comment);
|
|
tri_add_string(&commands, "step", step);
|
|
tri_add_string(&commands, "vstep", vstep);
|
|
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);
|
|
/* FIXME */
|
|
/* tri_add_string(&commands, "dump", mem_dump); */
|
|
tri_add_string(&commands, "disassemble", disas);
|
|
tri_add_string(&commands, "help", help);
|
|
tri_add_string(&commands, "?", help);
|
|
tri_add_string(&commands, "breakpoint", breakpoint);
|
|
tri_add_string(&commands, "runto", runto);
|
|
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)) {
|
|
gb_error("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);
|
|
}
|
|
|
|
if (config.log_fd != -1) {
|
|
close(config.log_fd);
|
|
}
|
|
|
|
if (config.trace_fd != -1) {
|
|
close(config.log_fd);
|
|
}
|
|
|
|
return 0;
|
|
}
|