cpu,gbdb: implement better breakpoints
Previously, gbdb had to check several signals, including the current PC, to determine when do_run() should stop. This code was hotpath and unecessarily slow. Instead, leverage a n undefined instruction (0xd3) as a breakpoint instruction. When the CPU emulation encounters this instruction, it will call a callback which is implemented by gbdb. This can set a simple flag which is less expensive to query. Finally, both the signal handler and the breakpoint callback set specific "paused" flags and a generic "pause" flag. Now, do_run() can simply check the generic "paused" flag, then use the specific flags to determine the stop reason. This change increased performance by ~10% on Raspberry Pi.
This commit is contained in:
@@ -25,8 +25,9 @@
|
|||||||
|
|
||||||
typedef void (gbdb_cmd)(char *arg_string);
|
typedef void (gbdb_cmd)(char *arg_string);
|
||||||
|
|
||||||
static bool cpu_at_breakpoint(void);
|
|
||||||
static void breakpoint_addr_hit(uint16_t id);
|
static void breakpoint_addr_hit(uint16_t id);
|
||||||
|
static void breakpoint_cb(struct lr35902_state *lr);
|
||||||
|
static void reset_breakpoints(void);
|
||||||
|
|
||||||
static uint8_t ram[MAX_RAM_LEN];
|
static uint8_t ram[MAX_RAM_LEN];
|
||||||
static const char prompt[] = "gbdb >";
|
static const char prompt[] = "gbdb >";
|
||||||
@@ -40,12 +41,16 @@ static const char usage[] =
|
|||||||
"peek: view the next instruction to run\n"
|
"peek: view the next instruction to run\n"
|
||||||
"exit: quit the program\n";
|
"exit: quit the program\n";
|
||||||
|
|
||||||
|
static struct lr35902_ops cpu_ops;
|
||||||
static struct lr35902_state cpu;
|
static struct lr35902_state cpu;
|
||||||
static struct gb_video video;
|
static struct gb_video video;
|
||||||
|
|
||||||
static unsigned char bootrom[0x100] = {0};
|
static unsigned char bootrom[0x100] = {0};
|
||||||
|
|
||||||
static int bootrom_mapped = 1;
|
static int bootrom_mapped = 1;
|
||||||
|
|
||||||
|
static int paused_breakpoint = 0;
|
||||||
|
static volatile sig_atomic_t paused_signal = 0;
|
||||||
static volatile sig_atomic_t paused = 0;
|
static volatile sig_atomic_t paused = 0;
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
@@ -151,7 +156,11 @@ static void mem_dump(char *filename)
|
|||||||
|
|
||||||
static void init(void)
|
static void init(void)
|
||||||
{
|
{
|
||||||
lr35902_init(&cpu, mem_read, mem_write);
|
cpu_ops.mem_read = mem_read;
|
||||||
|
cpu_ops.mem_write = mem_write;
|
||||||
|
cpu_ops.undef_d3 = breakpoint_cb;
|
||||||
|
|
||||||
|
lr35902_init(&cpu, &cpu_ops);
|
||||||
gb_video_init(&video);
|
gb_video_init(&video);
|
||||||
memset(&ram, 0, MAX_RAM_LEN);
|
memset(&ram, 0, MAX_RAM_LEN);
|
||||||
bootrom_mapped = 1;
|
bootrom_mapped = 1;
|
||||||
@@ -184,6 +193,17 @@ static void echo(char *arg_list)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void breakpoint_cb(struct lr35902_state *lr)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
paused_breakpoint = 1;
|
||||||
|
paused = 1;
|
||||||
|
|
||||||
|
breakpoint_addr_hit(cpu.pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void step(char *arg_list)
|
static void step(char *arg_list)
|
||||||
{
|
{
|
||||||
uint64_t steps, end_steps;
|
uint64_t steps, end_steps;
|
||||||
@@ -199,22 +219,27 @@ static void step(char *arg_list)
|
|||||||
paused = 0;
|
paused = 0;
|
||||||
signal(SIGINT, break_execution_handler);
|
signal(SIGINT, break_execution_handler);
|
||||||
end_steps = cpu.metrics.retired_instrs + steps;
|
end_steps = cpu.metrics.retired_instrs + steps;
|
||||||
do {
|
|
||||||
cycle();
|
|
||||||
} while (end_steps > cpu.metrics.retired_instrs &&
|
|
||||||
!cpu_at_breakpoint() &&
|
|
||||||
!paused);
|
|
||||||
|
|
||||||
|
cycle();
|
||||||
|
reset_breakpoints();
|
||||||
|
|
||||||
|
while(end_steps > cpu.metrics.retired_instrs &&
|
||||||
|
!paused) {
|
||||||
|
cycle();
|
||||||
|
}
|
||||||
|
|
||||||
if (end_steps <= cpu.metrics.retired_instrs) {
|
if (end_steps <= cpu.metrics.retired_instrs) {
|
||||||
gb_log("CPU stopped after %" PRId64 " instructions\n", steps);
|
gb_log("CPU stopped after %" PRId64 " instructions\n", steps);
|
||||||
} else if (cpu_at_breakpoint()) {
|
} else if (paused_breakpoint) {
|
||||||
breakpoint_addr_hit(cpu.pc);
|
breakpoint_addr_hit(cpu.pc);
|
||||||
gb_log("Breakpoint hit\n");
|
gb_log("Breakpoint hit\n");
|
||||||
} else if (cpu.halted) {
|
} else if (paused_signal){
|
||||||
gb_log("CPU halted\n");
|
|
||||||
} else {
|
|
||||||
gb_log("Interrupted\n");
|
gb_log("Interrupted\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paused = 0;
|
||||||
|
paused_signal = 0;
|
||||||
|
paused_breakpoint = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void regs(char *arg_list)
|
static void regs(char *arg_list)
|
||||||
@@ -447,18 +472,24 @@ static void quit(char *arg_list)
|
|||||||
static void do_run(void) {
|
static void do_run(void) {
|
||||||
paused = 0;
|
paused = 0;
|
||||||
signal(SIGINT, break_execution_handler);
|
signal(SIGINT, break_execution_handler);
|
||||||
while(!cpu.halted && !cpu_at_breakpoint() && !paused) {
|
|
||||||
|
cycle();
|
||||||
|
reset_breakpoints();
|
||||||
|
|
||||||
|
while(!paused) {
|
||||||
cycle();
|
cycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cpu.halted) {
|
if (paused_signal) {
|
||||||
gb_log("CPU halted after %ld cycles\n", cpu.metrics.cycles);
|
|
||||||
} else if (paused) {
|
|
||||||
gb_log("Interrupted.\n");
|
gb_log("Interrupted.\n");
|
||||||
} else {
|
} else if (paused_breakpoint) {
|
||||||
breakpoint_addr_hit(cpu.pc);
|
breakpoint_addr_hit(cpu.pc);
|
||||||
gb_log("Breakpoint hit\n");
|
gb_log("Breakpoint hit\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paused = 0;
|
||||||
|
paused_breakpoint = 0;
|
||||||
|
paused_signal = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void run(char *arg_list)
|
static void run(char *arg_list)
|
||||||
@@ -470,6 +501,8 @@ static struct breakpoint {
|
|||||||
uint16_t addr;
|
uint16_t addr;
|
||||||
int id;
|
int id;
|
||||||
bool temp;
|
bool temp;
|
||||||
|
uint8_t op;
|
||||||
|
uint8_t *ptr;
|
||||||
} breakpoints[MAX_BREAKPTS];
|
} breakpoints[MAX_BREAKPTS];
|
||||||
|
|
||||||
static int num_breakpoints = 0;
|
static int num_breakpoints = 0;
|
||||||
@@ -487,11 +520,29 @@ static struct breakpoint *get_breakpoint(int id)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void reset_breakpoints(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < num_breakpoints; i++) {
|
||||||
|
struct breakpoint *bkpt = &breakpoints[i];
|
||||||
|
*bkpt->ptr = 0xd3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int do_set_breakpoint(uint16_t addr, bool temp)
|
static int do_set_breakpoint(uint16_t addr, bool temp)
|
||||||
{
|
{
|
||||||
static int id = 0;
|
static int id = 0;
|
||||||
|
|
||||||
if (num_breakpoints < ARRAY_SIZE(breakpoints)) {
|
if (num_breakpoints < ARRAY_SIZE(breakpoints)) {
|
||||||
breakpoints[num_breakpoints].addr = addr;
|
breakpoints[num_breakpoints].addr = addr;
|
||||||
|
breakpoints[num_breakpoints].op = mem_read(NULL, addr);
|
||||||
|
if (bootrom_mapped && addr < 0x100) {
|
||||||
|
breakpoints[num_breakpoints].ptr = &bootrom[addr];
|
||||||
|
} else {
|
||||||
|
breakpoints[num_breakpoints].ptr = &ram[addr];
|
||||||
|
}
|
||||||
|
*breakpoints[num_breakpoints].ptr = 0xd3;
|
||||||
breakpoints[num_breakpoints].id = id++;
|
breakpoints[num_breakpoints].id = id++;
|
||||||
breakpoints[num_breakpoints].temp = temp;
|
breakpoints[num_breakpoints].temp = temp;
|
||||||
num_breakpoints++;
|
num_breakpoints++;
|
||||||
@@ -543,6 +594,8 @@ static int do_delete_breakpoint(int id)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*bkpt->ptr = bkpt->op;
|
||||||
|
|
||||||
index = bkpt - breakpoints;
|
index = bkpt - breakpoints;
|
||||||
memmove(&breakpoints[index], &breakpoints[index + 1],
|
memmove(&breakpoints[index], &breakpoints[index + 1],
|
||||||
num_breakpoints - index - 1);
|
num_breakpoints - index - 1);
|
||||||
@@ -659,11 +712,6 @@ static bool breakpoint_is_at_addr(uint16_t addr) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool cpu_at_breakpoint(void)
|
|
||||||
{
|
|
||||||
return breakpoint_is_at_addr(cpu.pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_cmd(const char *cmd_str, struct tri *commands)
|
static void process_cmd(const char *cmd_str, struct tri *commands)
|
||||||
{
|
{
|
||||||
char cmd_buffer[INPUT_MAX_LEN]; /* Buffer for command parsing */
|
char cmd_buffer[INPUT_MAX_LEN]; /* Buffer for command parsing */
|
||||||
|
|||||||
@@ -72,13 +72,13 @@ void lr35902_set_reg_8(struct lr35902_state *cpu, lr35902_regs_8 reg,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void lr35902_init(struct lr35902_state *cpu,
|
void lr35902_init(struct lr35902_state *cpu,
|
||||||
lr35902_mem_read_fn mem_read,
|
const struct lr35902_ops *ops)
|
||||||
lr35902_mem_write_fn mem_write)
|
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
cpu->mem_read = mem_read;
|
cpu->mem_read = ops->mem_read;
|
||||||
cpu->mem_write = mem_write;
|
cpu->mem_write = ops->mem_write;
|
||||||
|
cpu->undef_d3 = ops->undef_d3;
|
||||||
|
|
||||||
cpu->int_state = LR35902_INT_OFF;
|
cpu->int_state = LR35902_INT_OFF;
|
||||||
cpu->stall_cycles = 0;
|
cpu->stall_cycles = 0;
|
||||||
@@ -1353,7 +1353,16 @@ void lr35902_cycle(struct lr35902_state *cpu)
|
|||||||
POP_16(cpu, cpu->de);
|
POP_16(cpu, cpu->de);
|
||||||
break;
|
break;
|
||||||
case 0xd2:
|
case 0xd2:
|
||||||
|
break;
|
||||||
case 0xd3: /* UNDEF */
|
case 0xd3: /* UNDEF */
|
||||||
|
if (cpu->undef_d3) {
|
||||||
|
/* Undo the incremented PC so the op is called
|
||||||
|
with the PC at the D3 instruction */
|
||||||
|
cpu->metrics.cycles--;
|
||||||
|
cpu->stall_cycles = 0;
|
||||||
|
cpu->pc--;
|
||||||
|
cpu->undef_d3(cpu);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 0xd4: /* CALL NC */
|
case 0xd4: /* CALL NC */
|
||||||
if (!cpu->nf) {
|
if (!cpu->nf) {
|
||||||
|
|||||||
@@ -94,8 +94,11 @@ struct lr35902_state {
|
|||||||
|
|
||||||
int stall_cycles;
|
int stall_cycles;
|
||||||
int halted;
|
int halted;
|
||||||
|
|
||||||
lr35902_mem_read_fn mem_read;
|
lr35902_mem_read_fn mem_read;
|
||||||
lr35902_mem_write_fn mem_write;
|
lr35902_mem_write_fn mem_write;
|
||||||
|
void (*undef_d3)(struct lr35902_state *reg);
|
||||||
|
|
||||||
lr35902_interrupt_state int_state;
|
lr35902_interrupt_state int_state;
|
||||||
struct lr35902_event events[LR35902_MAX_EVENTS];
|
struct lr35902_event events[LR35902_MAX_EVENTS];
|
||||||
|
|
||||||
@@ -108,9 +111,15 @@ struct lr35902_state {
|
|||||||
} metrics;
|
} metrics;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct lr35902_ops {
|
||||||
|
lr35902_mem_read_fn mem_read;
|
||||||
|
lr35902_mem_write_fn mem_write;
|
||||||
|
|
||||||
|
void (*undef_d3)(struct lr35902_state *reg);
|
||||||
|
};
|
||||||
|
|
||||||
void lr35902_init(struct lr35902_state *cpu,
|
void lr35902_init(struct lr35902_state *cpu,
|
||||||
lr35902_mem_read_fn mem_read,
|
const struct lr35902_ops *ops);
|
||||||
lr35902_mem_write_fn mem_write);
|
|
||||||
|
|
||||||
uint16_t lr35902_get_reg_16(const struct lr35902_state *cpu,
|
uint16_t lr35902_get_reg_16(const struct lr35902_state *cpu,
|
||||||
lr35902_regs_16 reg);
|
lr35902_regs_16 reg);
|
||||||
|
|||||||
Reference in New Issue
Block a user