apple2ix/src/meta/debugger.c

1529 lines
49 KiB
C

/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 1997, 1998 Aaron Culliney
* Copyright 1998, 1999, 2000 Michael Deutschmann
* Copyright 2013-2015 Aaron Culliney
*
*/
#include "common.h"
#include <test/sha1.h>
#define SW_TEXT 0xC050
#define SW_MIXED 0xC052
#define SW_PAGE2 0xC054
#define SW_HIRES 0xC056
#define SW_80STORE 0xC000
#define SW_RAMRD 0xC002
#define SW_RAMWRT 0xC004
#define SW_ALTZP 0xC008
#define SW_80COL 0xC00C
#define SW_ALTCHAR 0xC00E
#define SW_SLOTC3ROM 0xC00B /* anomaly */
#define SW_SLOTCXROM 0xC006
#define SW_DHIRES 0xC05E
#define SW_IOUDIS 0xC07E
const struct opcode_struct *opcodes;
static stepping_struct_t stepping_struct = { 0 };
static unsigned int stepping_timeout = 0;
volatile bool is_debugging = false;
extern pthread_mutex_t interface_mutex;
extern pthread_cond_t cpu_thread_cond;
extern pthread_cond_t dbg_thread_cond;
#warning ^^^ HACK FIXME TODO ... debugger should not have raw access to mutex variables
#define BUF_X DEBUGGER_BUF_X
#define BUF_Y DEBUGGER_BUF_Y
#define SCREEN_X 81 // 80col + 1
#define SCREEN_Y 24
#define PROMPT_X 2
#define PROMPT_Y BUF_Y - 1
#define PROMPT_END_X BUF_X - 2
#define command_line command_buf[PROMPT_Y]
char second_buf[BUF_Y][BUF_X]; /* scratch buffer for output */
int num_buffer_lines; /* num lines of output */
int arg1, arg2, arg3; /* command arguments */
int breakpoints[MAX_BRKPTS]; /* memory breakpoints */
int watchpoints[MAX_BRKPTS]; /* memory watchpoints */
#ifdef INTERFACE_CLASSIC
/* debugger globals */
//1. 5. 10. 15. 20. 25. 30. 35. 40. 45. 50. 55. 60. 65. 70. 75. 80.",
static char screen[SCREEN_Y][SCREEN_X] =
{ "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"| | |",
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||" };
static char command_buf[BUF_Y][BUF_X]; /* command line prompt */
#endif
char lexbuf[BUF_X+2]; /* comman line to be flex'ed */
uint8_t current_opcode;
int op_breakpoints[256]; /* opcode breakpoints */
/* in debug.l */
extern int yylex();
extern void init_lex(char *buf, int size);
/* -------------------------------------------------------------------------
c_get_current_rambank (addrs) - return the current ram bank for addrs.
returns 0 = bank 0
1 = bank 1
------------------------------------------------------------------------- */
int c_get_current_rambank(int addrs) {
if ((addrs >= 0x200) && (addrs < 0xD000))
{
/* SLOTROM */
if ((addrs >= 0xC100) && (addrs < 0xD000))
{
/* expansion rom */
if ((addrs >= 0xC800) && (addrs < 0xD000))
{
return 1; /* always internal
* (the real rules are more complex, but
* this'll suffice since the slot 3 pseudo-card
* is the only one with c8 space) */
}
/* if SLOTCXROM, then internal rom (regardless of
SLOTC3ROM setting). */
if (softswitches & SS_CXROM)
{
return 1;
}
/* slot 3 rom */
if ((addrs >= 0xC300) && (addrs < 0xC400))
{
return !!(softswitches & SS_C3ROM);
}
return 0; /* peripheral rom */
}
/* text page 1 */
if ((addrs >= 0x400) && (addrs < 0x800))
{
return !!(softswitches & SS_TEXTRD);
}
/* hires page 1 with 80STORE and HIRES on */
if ((addrs >= 0x2000) && (addrs < 0x4000))
{
return !!(softswitches & SS_HGRRD);
}
/* otherwise return RAMRD flag */
return !!(softswitches & SS_RAMRD);
}
/* executing in ALTZP space. */
return !!(softswitches & SS_ALTZP);
}
/* -------------------------------------------------------------------------
get_last_opcode () - returns the last executed opcode
------------------------------------------------------------------------- */
uint8_t get_last_opcode() {
return cpu65_opcode;
}
/* -------------------------------------------------------------------------
get_current_opcode () - returns the opcode from the address that
the PC is currently reading from.
------------------------------------------------------------------------- */
uint8_t get_current_opcode() {
int bank = c_get_current_rambank(cpu65_pc);
int lcbank = 0;
/* main RAM */
if (cpu65_pc < 0xD000)
{
return apple_ii_64k[bank][cpu65_pc];
}
/* LC RAM */
if (cpu65_pc >= 0xE000)
{
if (softswitches & SS_LCRAM)
{
return language_card[bank][cpu65_pc-0xE000];
}
else
{
return apple_ii_64k[bank][cpu65_pc];
}
}
/* LC BANK RAM */
if (softswitches & SS_BANK2)
{
lcbank = 0x1000;
}
if (softswitches & SS_LCRAM)
{
return language_banks[bank][cpu65_pc-0xD000+lcbank];
}
else
{
return apple_ii_64k[bank][cpu65_pc];
}
}
/* -------------------------------------------------------------------------
dump_mem () - hexdump of memory to debug console
we DO NOT wrap the display : 0xffff -> 0x0 (programs can't wrap)
------------------------------------------------------------------------- */
void dump_mem(int addrs, int len, int lc, int do_ascii, int rambank) {
int i, j, mod, end;
uint8_t op;
int orig_addrs = addrs; /* address for display */
/* check which rambank */
if (rambank == -1)
{
rambank = c_get_current_rambank(addrs);
}
if (!lc && (softswitches & SS_LCRAM) && (addrs >= 0xd000))
{
/* read lc anyway */
lc = 1 + !!(softswitches & SS_BANK2);
}
if ((addrs < 0) || (addrs > 0xffff))
{
addrs = cpu65_pc;
orig_addrs = addrs;
}
if (lc)
{
orig_addrs = addrs;
if ((addrs >= 0xd000) && (addrs <= 0xffff))
{
addrs -= 0xd000;
}
if ((addrs < 0) || (addrs > 0x2fff))
{
addrs = 0;
}
}
if ((len < 1) || (len > 256))
{
len = 256;
}
if (do_ascii && (len > 128))
{
len = 128;
}
/* save hexdump in second_buf */
end = (lc) ? 0x3000 : 0x10000;
for (i = num_buffer_lines-1, j = 0; ((j < len) && (addrs+j < end)); j++)
{
mod = j % (16 >> do_ascii);
if (lc)
{
op = (addrs+j >= 0x1000) ? language_card[rambank][(addrs+j)-0x1000] : (lc == 1) ? language_banks[rambank][addrs+j] : language_banks[rambank][0x1000+addrs+j];
}
else
{
op = apple_ii_64k[rambank][addrs+j];
}
if (!mod)
{
if (++i)
{
for (mod=0; mod<BUF_X; mod++)
{
if (second_buf[i-1][mod] == '\0')
{
second_buf[i-1][mod] = ' ';
}
}
}
memset(second_buf[i], ' ', BUF_X);
sprintf(second_buf[i], "%04X:%02X", orig_addrs+j, op);
if (do_ascii)
{
sprintf(second_buf[i]+23, "%c", ((op&0x7f) > 31) ? (op&0x7f) : '.');
}
continue;
}
sprintf(second_buf[i]+5+mod*2, "%02X", op);
if (do_ascii)
{
sprintf(second_buf[i]+23+mod, "%c", ((op&0x7f) > 31) ? (op&0x7f) : '.');
}
}
for (mod=0; mod<BUF_X; mod++)
{
if (second_buf[i][mod] == '\0')
{
second_buf[i][mod] = ' ';
}
}
num_buffer_lines = i + 1;
}
/* -------------------------------------------------------------------------
search_mem () - search memory for bytes
------------------------------------------------------------------------- */
void search_mem(char *hexstr, int lc, int rambank) {
int i = 0, j = 0, end, op;
static char scratch[3];
uint8_t byte;
end = (lc) ? 0x3000 : 0x10000;
/* check which rambank for cpu65_pc */
if (rambank == -1)
{
rambank = c_get_current_rambank(cpu65_pc);
}
/* iterate over memory */
for (i = 0; i < end; i++)
{
strncpy(scratch, hexstr+j, 2); /* extract a byte */
byte = (uint8_t) strtol(scratch, (char**)NULL, 16);
if (lc)
{
op = (i >= 0x1000) ? language_card[rambank][i-0x1000] : (lc == 1) ? language_banks[rambank][i] : language_banks[rambank][0x1000+i];
}
else
{
op = apple_ii_64k[rambank][i];
}
if (byte == op) /* matched byte? */
{
++j; /* increment */
if (!isxdigit(*(hexstr+j))) /* end of bytes? */
{ /* then we found a match */
sprintf(second_buf[num_buffer_lines], "%04X: %s", i-(j>>1), hexstr);
num_buffer_lines = (num_buffer_lines + 1) % (BUF_Y-2);
j = 0; continue;
}
++j;
if (!isxdigit(*(hexstr+j))) /* end of bytes? */
{ /* then we found a match */
sprintf(second_buf[num_buffer_lines], "%04X: %s", i-(j>>1)+1, hexstr);
num_buffer_lines = (num_buffer_lines + 1) % (BUF_Y-2);
j = 0; continue;
}
continue;
}
j = 0;
}
}
/* -------------------------------------------------------------------------
set_mem () - write to memory. we use the do_write_memory routine
to "safely" set memory...
------------------------------------------------------------------------- */
void set_mem(int addrs, char *hexstr) {
static char scratch[3];
uint8_t data;
if ((addrs < 0) || (addrs > 0xffff))
{
sprintf(second_buf[num_buffer_lines++], "invalid address");
return;
}
while (*hexstr)
{
strncpy(scratch, hexstr, 2);
data = (uint8_t) strtol(scratch, (char**)NULL, 16);
/* call the set_memory routine, which knows how to route the
request */
cpu65_direct_write(addrs,data);
++hexstr;
if (!*hexstr)
{
break;
}
++hexstr;
if (++addrs > 0xffff)
{
return; /* don't overwrite memory */
}
}
}
/* -------------------------------------------------------------------------
set_lc_mem () - specifically write to apple II language card RAM memory
------------------------------------------------------------------------- */
void set_lc_mem(int addrs, int lcbank, char *hexstr) {
static char scratch[3];
uint8_t data;
if ((addrs >= 0xd000) && (addrs <= 0xffff))
{
addrs -= 0xd000;
}
if ((addrs < 0) || (addrs > 0x2fff))
{
sprintf(second_buf[num_buffer_lines++], "invalid LC address");
return;
}
while (*hexstr)
{
strncpy(scratch, hexstr, 2);
data = (uint8_t) strtol(scratch, (char**)NULL, 16);
/* ??? no way to write to aux LC banks */
if (addrs >= 0x1000)
{
language_card[0][addrs - 0x1000] = data;
}
else if (lcbank)
{
language_banks[0][addrs] = data;
}
else
{
language_banks[0][addrs + 0x1000] = data;
}
++hexstr;
if (!*hexstr)
{
break;
}
++hexstr;
if (++addrs > 0x2fff)
{
return;
}
}
}
/* -------------------------------------------------------------------------
bload () - bload file data into emulator. this is essentially the
same as the set_mem routine. we use the do_write_memory routine to
"safely" set memory...
------------------------------------------------------------------------- */
void bload(FILE *f, char *name, int bank, int addrs) {
uint8_t *hexstr = NULL;
size_t len = -1;
uint8_t data;
if ((addrs < 0) || (addrs > 0xffff))
{
sprintf(second_buf[num_buffer_lines++], "problem: invalid address");
return;
}
#define TEMPSIZE 1024
char temp[TEMPSIZE];
while ((len = fread(temp, 1, TEMPSIZE, f)))
{
hexstr = (uint8_t*)temp;
for (; len > 0; len--)
{
data = *hexstr;
if (addrs+len >= 0x10000) {
sprintf(second_buf[num_buffer_lines++], "problem: bload will overflow");
return;
}
/* call the set_memory routine, which knows how to route
the request */
cpu65_direct_write(addrs,data);
++hexstr;
if (++addrs > 0xffff)
{
return; /* don't overwrite memory */
}
}
}
sprintf(second_buf[num_buffer_lines++], "bloaded: %s", name);
}
/* -------------------------------------------------------------------------
disasm () - disassemble instructions
we DO NOT wrap the display : 0xffff -> 0x0
------------------------------------------------------------------------- */
void disasm(int addrs, int len, int lc, int rambank) {
static char fmt[64];
uint8_t op;
char arg1, arg2;
int i, j, k, end, orig_addrs = addrs;
/* check which rambank for cpu65_pc */
if (rambank == -1)
{
rambank = c_get_current_rambank(addrs);
}
if (!lc && (softswitches & SS_LCRAM) && (addrs >= 0xd000))
{
/* read lc anyway */
lc = 1 + !!(softswitches & SS_BANK2);
}
/* handle invalid address request */
if ((addrs < 0) || (addrs > 0xffff))
{
addrs = cpu65_pc;
orig_addrs = addrs;
}
/* disassembling from language card */
if (lc)
{
if ((addrs >= 0xd000) && (addrs <= 0xffff))
{
addrs -= 0xd000;
}
if ((addrs < 0) || (addrs > 0x2fff))
{
addrs = 0;
}
}
if (len > BUF_Y - 2)
{
len = BUF_Y - 2 - num_buffer_lines;
}
/* save hexdump in second_buf */
end = (lc) ? 0x3000 : 0x10000;
for (i = num_buffer_lines, j = addrs, k=orig_addrs; ((i<len) && (j<end)); i++, j++)
{
if (lc)
{
op = (j >= 0x1000) ? language_card[rambank][j-0x1000] : (lc == 1) ? language_banks[rambank][j] : language_banks[rambank][0x1000+j];
}
else
{
op = apple_ii_64k[rambank][j];
}
switch (opcodes[op].mode)
{
case addr_implied:
case addr_accumulator: /* no arg */
sprintf(second_buf[i], "/%02X/%04X: %02X %s %s", rambank, k++, op, opcodes[op].mnemonic, disasm_templates[opcodes[op].mode]);
break;
case addr_immediate:
case addr_zeropage:
case addr_zeropage_x:
case addr_zeropage_y:
case addr_indirect:
case addr_indirect_x:
case addr_indirect_y: /* byte arg */
if (k == 0xffff)
{
num_buffer_lines = i;
return;
}
if (lc)
{
arg1 = (j >= 0x1000) ? language_card[rambank][++j-0x1000] : (lc == 1) ? language_banks[rambank][++j] : language_banks[rambank][++j+0x1000];
}
else
{
arg1 = apple_ii_64k[rambank][++j];
}
sprintf(fmt, "/%02X/%04X: %02X%02X %s %s", rambank, k, op, (uint8_t)arg1, opcodes[op].mnemonic, disasm_templates[opcodes[op].mode]);
sprintf(second_buf[i], fmt, (uint8_t)arg1);
k+=2;
break;
case addr_absolute:
case addr_absolute_x:
case addr_absolute_y:
case addr_j_indirect:
case addr_j_indirect_x: /* word arg */
if (k >= 0xfffe)
{
num_buffer_lines = i;
return;
}
if (lc)
{
arg1 = (j >= 0x1000) ? language_card[rambank][++j-0x1000] : (lc == 1) ? language_banks[rambank][++j] : language_banks[rambank][++j+0x1000];
arg2 = (j >= 0x1000) ? language_card[rambank][++j-0x1000] : (lc == 1) ? language_banks[rambank][++j] : language_banks[rambank][++j+0x1000];
}
else
{
arg1 = apple_ii_64k[rambank][++j];
arg2 = apple_ii_64k[rambank][++j];
}
sprintf(fmt, "/%02X/%04X: %02X%02X%02X %s %s", rambank, k, op, (uint8_t)arg1, (uint8_t)arg2, opcodes[op].mnemonic, disasm_templates[opcodes[op].mode]);
sprintf(second_buf[i], fmt, (uint8_t)arg2, (uint8_t)arg1);
k+=3;
break;
case addr_relative: /* offset */
if (k == 0xffff)
{
num_buffer_lines = i;
return;
}
if (lc)
{
arg1 = (j >= 0x1000) ? language_card[rambank][++j-0x1000] : (lc == 1) ? language_banks[rambank][++j] : language_banks[rambank][++j+0x1000];
}
else
{
arg1 = apple_ii_64k[rambank][++j];
}
sprintf(fmt, "/%02X/%04X: %02X%02X %s %s", rambank, k, op, (uint8_t)arg1, opcodes[op].mnemonic, disasm_templates[opcodes[op].mode]);
if (arg1 < 0)
{
sprintf(second_buf[i], fmt, k + arg1 + 2, '-', (uint8_t)(-arg1));
}
else
{
sprintf(second_buf[i], fmt, k + arg1 + 2, '+', (uint8_t)arg1);
}
k+=2;
break;
default: /* shouldn't happen */
sprintf(second_buf[i], "args to opcode incorrect!");
break;
}
}
num_buffer_lines = i;
}
/* -------------------------------------------------------------------------
show_regs () - shows 6502 registers
------------------------------------------------------------------------- */
void show_regs() {
sprintf(second_buf[num_buffer_lines++], "PC = %04X EA = %04X SP = %04X", cpu65_pc, cpu65_ea, cpu65_sp + 0x0100);
sprintf(second_buf[num_buffer_lines++], "X = %02X Y = %02X A = %02X F = %02X", cpu65_x, cpu65_y, cpu65_a, cpu65_f);
memset(second_buf[num_buffer_lines], ' ', BUF_X);
if (cpu65_f & C_Flag_6502)
{
second_buf[num_buffer_lines][0]='C';
}
if (cpu65_f & X_Flag_6502)
{
second_buf[num_buffer_lines][1]='X';
}
if (cpu65_f & I_Flag_6502)
{
second_buf[num_buffer_lines][2]='I';
}
if (cpu65_f & V_Flag_6502)
{
second_buf[num_buffer_lines][3]='V';
}
if (cpu65_f & B_Flag_6502)
{
second_buf[num_buffer_lines][4]='B';
}
if (cpu65_f & D_Flag_6502)
{
second_buf[num_buffer_lines][5]='D';
}
if (cpu65_f & Z_Flag_6502)
{
second_buf[num_buffer_lines][6]='Z';
}
if (cpu65_f & N_Flag_6502)
{
second_buf[num_buffer_lines][7]='N';
}
++num_buffer_lines;
}
#if !TESTING
/* -------------------------------------------------------------------------
will_branch () = will instruction branch?
-1 - n/a
0 - no it won't
>0 - yes it will
------------------------------------------------------------------------- */
#define BRANCH_NA -1
static int will_branch() {
uint8_t op = get_current_opcode();
switch (op)
{
case 0x10: /* BPL */
return (int) !(cpu65_f & N_Flag_6502);
case 0x30: /* BMI */
return (int) (cpu65_f & N_Flag_6502);
case 0x50: /* BVC */
return (int) !(cpu65_f & V_Flag_6502);
case 0x70: /* BVS */
return (int) (cpu65_f & V_Flag_6502);
case 0x80: /* BRA */
return 1;
case 0x90: /* BCC */
return (int) !(cpu65_f & C_Flag_6502);
case 0xb0: /* BCS */
return (int) (cpu65_f & C_Flag_6502);
case 0xd0: /* BNE */
return (int) !(cpu65_f & Z_Flag_6502);
case 0xf0: /* BEQ */
return (int) (cpu65_f & Z_Flag_6502);
}
return BRANCH_NA;
}
#endif
/* -------------------------------------------------------------------------
set_halt () = set a breakpoint or watchpoint in memory
type = points to "watchpoints" or "breakpoints" array
------------------------------------------------------------------------- */
bool set_halt(int *type, int addrs) {
int i;
for (i = 0; i < MAX_BRKPTS; i++)
{
if (type[i] == -1)
{
type[i] = addrs;
sprintf(second_buf[num_buffer_lines++], "set at %04X", addrs);
return true;
}
}
sprintf(second_buf[num_buffer_lines++], "too many!");
return false;
}
/* -------------------------------------------------------------------------
clear_halt () = unset a critical breakpoint or watchpoint in memory
type = points to "watchpoints" or "breakpoints" array
pt = (pt - 1) into type. 0 indicates clear all.
------------------------------------------------------------------------- */
void clear_halt(int *type, int pt) {
int i;
if (!pt) /* unset all */
{
for (i = 0; i < MAX_BRKPTS; i++)
{
type[i] = -1;
}
return;
}
type[pt-1] = -1; /* unset single */
}
/* -------------------------------------------------------------------------
set_halt_opcode () = set a breakpoint on a particular opcode.
------------------------------------------------------------------------- */
void set_halt_opcode(uint8_t opcode) {
op_breakpoints[opcode] = 1;
}
/* -------------------------------------------------------------------------
clear_halt_opcode () = unset an opcode breakpoint.
------------------------------------------------------------------------- */
void clear_halt_opcode(uint8_t opcode) {
op_breakpoints[opcode] = 0;
}
/* -------------------------------------------------------------------------
set_halt_65c02 () = set a breakpoint on all 65c02 instructions.
assumes that you are in //e mode...
------------------------------------------------------------------------- */
void set_halt_65c02() {
set_halt_opcode((uint8_t)0x02); set_halt_opcode((uint8_t)0x04);
set_halt_opcode((uint8_t)0x0C); set_halt_opcode((uint8_t)0x12);
set_halt_opcode((uint8_t)0x14); set_halt_opcode((uint8_t)0x1A);
set_halt_opcode((uint8_t)0x1C); set_halt_opcode((uint8_t)0x32);
set_halt_opcode((uint8_t)0x34); set_halt_opcode((uint8_t)0x3A);
set_halt_opcode((uint8_t)0x3C); set_halt_opcode((uint8_t)0x52);
set_halt_opcode((uint8_t)0x5A); set_halt_opcode((uint8_t)0x64);
set_halt_opcode((uint8_t)0x72); set_halt_opcode((uint8_t)0x74);
set_halt_opcode((uint8_t)0x7A); set_halt_opcode((uint8_t)0x7C);
set_halt_opcode((uint8_t)0x80); set_halt_opcode((uint8_t)0x89);
set_halt_opcode((uint8_t)0x92); set_halt_opcode((uint8_t)0x9C);
set_halt_opcode((uint8_t)0x9E); set_halt_opcode((uint8_t)0xB2);
set_halt_opcode((uint8_t)0xD2); set_halt_opcode((uint8_t)0xDA);
set_halt_opcode((uint8_t)0xF2); set_halt_opcode((uint8_t)0xFA);
}
/* -------------------------------------------------------------------------
clear_halt_65c02 () = clear all 65c02 instructions
------------------------------------------------------------------------- */
void clear_halt_65c02() {
clear_halt_opcode((uint8_t)0x02); clear_halt_opcode((uint8_t)0x04);
clear_halt_opcode((uint8_t)0x0C); clear_halt_opcode((uint8_t)0x12);
clear_halt_opcode((uint8_t)0x14); clear_halt_opcode((uint8_t)0x1A);
clear_halt_opcode((uint8_t)0x1C); clear_halt_opcode((uint8_t)0x32);
clear_halt_opcode((uint8_t)0x34); clear_halt_opcode((uint8_t)0x3A);
clear_halt_opcode((uint8_t)0x3C); clear_halt_opcode((uint8_t)0x52);
clear_halt_opcode((uint8_t)0x5A); clear_halt_opcode((uint8_t)0x64);
clear_halt_opcode((uint8_t)0x72); clear_halt_opcode((uint8_t)0x74);
clear_halt_opcode((uint8_t)0x7A); clear_halt_opcode((uint8_t)0x7C);
clear_halt_opcode((uint8_t)0x80); clear_halt_opcode((uint8_t)0x89);
clear_halt_opcode((uint8_t)0x92); clear_halt_opcode((uint8_t)0x9C);
clear_halt_opcode((uint8_t)0x9E); clear_halt_opcode((uint8_t)0xB2);
clear_halt_opcode((uint8_t)0xD2); clear_halt_opcode((uint8_t)0xDA);
clear_halt_opcode((uint8_t)0xF2); clear_halt_opcode((uint8_t)0xFA);
}
/* -------------------------------------------------------------------------
at_haltpt () - tests if at haltpt
returns 0 = no breaks or watches
1 = one break or watchpoint fired
n = two or more breaks and/or watches fired
------------------------------------------------------------------------- */
int at_haltpt() {
int count = 0;
/* check op_breakpoints */
uint8_t op = get_last_opcode();
if (op_breakpoints[op])
{
sprintf(second_buf[num_buffer_lines++], "stopped at %04X bank %d instruction %02X", cpu65_pc, c_get_current_rambank(cpu65_pc), op);
++count;
}
for (int i = 0; i < MAX_BRKPTS; i++)
{
if (cpu65_pc == breakpoints[i])
{
sprintf(second_buf[num_buffer_lines++], "stopped at %04X bank %d", breakpoints[i], c_get_current_rambank(cpu65_pc));
++count;
}
}
if (cpu65_rw) /* only check watchpoints if read/write occured */
{
for (int i = 0; i < MAX_BRKPTS; i++)
{
if (cpu65_ea == watchpoints[i])
{
if (cpu65_rw & 0x2)
{
sprintf(second_buf[num_buffer_lines++], "wrote: %04X: %02X", watchpoints[i], cpu65_d);
++count;
}
else
{
sprintf(second_buf[num_buffer_lines++], "read: %04X", watchpoints[i]);
++count;
}
cpu65_rw = 0; /* only allow WP to trip once */
}
}
}
return count; /* 0 indicates nothing happened */
}
/* -------------------------------------------------------------------------
show_breakpts () - show breakpoints and watchpoints
------------------------------------------------------------------------- */
void show_breakpts() {
int i=num_buffer_lines, k;
for (k = 0; k < MAX_BRKPTS; k++)
{
if ((breakpoints[k] >= 0) && (watchpoints[k] >= 0))
{
sprintf(second_buf[i++], "break %02d at %04X watch %02d at %04X", k+1, breakpoints[k], k+1, watchpoints[k]);
}
else if (breakpoints[k] >= 0)
{
sprintf(second_buf[i++], "break %02d at %04X", k+1, breakpoints[k]);
}
else if (watchpoints[k] >= 0)
{
memset(second_buf[i], ' ', BUF_X);
sprintf(second_buf[i++]+16, " watch %02d at %04X", k+1, watchpoints[k]);
}
}
num_buffer_lines = i;
}
/* -------------------------------------------------------------------------
show_opcode_breakpts () - show opcode breakpoints
------------------------------------------------------------------------- */
void show_opcode_breakpts() {
int i=num_buffer_lines, k;
sprintf(second_buf[i++], " 0 1 2 3 4 5 6 7 8 9 A B C D E F");
sprintf(second_buf[i++], " |-------------------------------|");
for (k = 0; k < 0x10; k++)
{
sprintf(second_buf[i++],
" %X|%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s|", k,
op_breakpoints[k ] ? "x" : " ",
op_breakpoints[k+0x10] ? "x" : " ",
op_breakpoints[k+0x20] ? "x" : " ",
op_breakpoints[k+0x30] ? "x" : " ",
op_breakpoints[k+0x40] ? "x" : " ",
op_breakpoints[k+0x50] ? "x" : " ",
op_breakpoints[k+0x60] ? "x" : " ",
op_breakpoints[k+0x70] ? "x" : " ",
op_breakpoints[k+0x80] ? "x" : " ",
op_breakpoints[k+0x90] ? "x" : " ",
op_breakpoints[k+0xA0] ? "x" : " ",
op_breakpoints[k+0xB0] ? "x" : " ",
op_breakpoints[k+0xC0] ? "x" : " ",
op_breakpoints[k+0xD0] ? "x" : " ",
op_breakpoints[k+0xE0] ? "x" : " ",
op_breakpoints[k+0xF0] ? "x" : " ");
}
sprintf(second_buf[i++], " |-------------------------------|");
num_buffer_lines = i;
}
/* -------------------------------------------------------------------------
show_lc_info () - show language card info
------------------------------------------------------------------------- */
void show_lc_info() {
int i = num_buffer_lines;
#warning FIXME TODO ... investigate/refactor all uses of !! here and elsewhere
sprintf(second_buf[i++], "lc bank = %d", 1 + !!(softswitches & SS_BANK2));
(softswitches & SS_LCWRT) ? sprintf(second_buf[i++], "write LC") : sprintf(second_buf[i++], "LC write protected");
(softswitches & SS_LCRAM) ? sprintf(second_buf[i++], "read LC") : sprintf(second_buf[i++], "read ROM");
sprintf(second_buf[i++], "second = %d", !!(softswitches & SS_LCSEC));
num_buffer_lines = i;
}
void show_misc_info() {
int i = num_buffer_lines;
sprintf(second_buf[i++], "TEXT (%04X): %s", SW_TEXT + !!(softswitches & SS_TEXT), (softswitches & SS_TEXT) ? "on" : "off");
sprintf(second_buf[i++], "MIXED (%04X): %s", SW_MIXED + !!(softswitches & SS_MIXED), (softswitches & SS_MIXED) ? "on" : "off");
sprintf(second_buf[i++], "PAGE2 (%04X): %s", SW_PAGE2 + !!(softswitches & SS_PAGE2), (softswitches & SS_PAGE2) ? "on" : "off");
sprintf(second_buf[i++], "HIRES (%04X): %s", SW_HIRES + !!(softswitches & SS_HIRES), (softswitches & SS_HIRES) ? "on" : "off");
sprintf(second_buf[i++], "80STORE (%04X): %s", SW_80STORE + !!(softswitches & SS_80STORE), (softswitches & SS_80STORE) ? "on" : "off");
sprintf(second_buf[i++], "RAMRD (%04X): %s", SW_RAMRD + !!(softswitches & SS_RAMRD), (softswitches & SS_RAMRD) ? "on" : "off");
sprintf(second_buf[i++], "RAMWRT (%04X): %s", SW_RAMWRT + !!(softswitches & SS_RAMWRT), (softswitches & SS_RAMWRT) ? "on" : "off");
sprintf(second_buf[i++], "ALTZP (%04X): %s", SW_ALTZP + !!(softswitches & SS_ALTZP), (softswitches & SS_ALTZP) ? "on" : "off");
sprintf(second_buf[i++], "80COL (%04X): %s", SW_80COL + !!(softswitches & SS_80COL), (softswitches & SS_80COL) ? "on" : "off");
sprintf(second_buf[i++], "ALTCHAR (%04X): %s", SW_ALTCHAR + !!(softswitches & SS_ALTCHAR), (softswitches & SS_ALTCHAR) ? "on" : "off");
sprintf(second_buf[i++], "SLOTC3ROM (%04X): %s", SW_SLOTC3ROM -/*anomaly*/ !!(softswitches & SS_C3ROM), (softswitches & SS_C3ROM) ? "on" : "off");
sprintf(second_buf[i++], "SLOTCXROM (%04X): %s", SW_SLOTCXROM + !!(softswitches & SS_CXROM), (softswitches & SS_CXROM) ? "on" : "off");
sprintf(second_buf[i++], "DHIRES (%04X): %s", SW_DHIRES + !!(softswitches & SS_DHIRES), (softswitches & SS_DHIRES) ? "on" : "off");
sprintf(second_buf[i++], "IOUDIS (%04X): %s", SW_IOUDIS + !!(softswitches & SS_IOUDIS), (softswitches & SS_IOUDIS) ? "on" : "off");
/* sprintf(second_buf[i++], "RDVBLBAR: %s", (SLOTCXROM & 0x80) */
/* ? "on" : "off"); */
num_buffer_lines = i;
}
/* -------------------------------------------------------------------------
show_disk_info () - disk II info
------------------------------------------------------------------------- */
void show_disk_info() {
static char tmp[32];
int i = num_buffer_lines;
size_t len = 0;
int off = 19;
/* generic information */
sprintf(second_buf[i++], "drive %s", (disk6.drive) ? "B" : "A");
sprintf(second_buf[i++], "motor %s", (disk6.motor_off) ? "off" : "on");
sprintf(second_buf[i++], "%s", (disk6.ddrw) ? "write" : "read");
sprintf(second_buf[i++], "byte = %02X", disk6.disk_byte);
if (!disk6.disk[disk6.drive].nibblized)
{
//sprintf(second_buf[i++], "volume = %d", disk6.volume);
//sprintf(second_buf[i++], "checksum = %d", disk6.checksum);
}
sprintf(second_buf[i++], "-------------------------------------");
/* drive / image specific information */
memset(second_buf[i], ' ', BUF_X);
if (disk6.disk[0].file_name && (len = strlen(disk6.disk[0].file_name)))
{
while ((--len) && (disk6.disk[0].file_name[len] != '/'))
{
}
strncpy(tmp, disk6.disk[0].file_name + len + 1, 31);
*(second_buf[i] + sprintf(second_buf[i], "%s", tmp)) = ' ';
}
if (disk6.disk[1].file_name && (len = strlen(disk6.disk[1].file_name)))
{
while ((--len) && (disk6.disk[1].file_name[len] != '/'))
{
}
strncpy(tmp, disk6.disk[1].file_name + len + 1, 31);
sprintf(second_buf[i]+off, "%s", tmp);
}
memset(second_buf[++i], ' ', BUF_X);
*(second_buf[i] + sprintf(second_buf[i], "%s", (disk6.disk[0].nibblized) ? ".nib" : ".dsk")) = ' ';
sprintf(second_buf[i++]+off, "%s", (disk6.disk[1].nibblized) ? ".nib" : ".dsk");
memset(second_buf[i], ' ', BUF_X);
*(second_buf[i] + sprintf(second_buf[i], "write %s", (disk6.disk[0].is_protected) ? "protected" : "enabled")) = ' ';
sprintf(second_buf[i++]+off, "write %s", (disk6.disk[1].is_protected) ? "protected" : "enabled");
memset(second_buf[i], ' ', BUF_X);
*(second_buf[i] + sprintf(second_buf[i], "phase %d", disk6.disk[0].phase)) = ' ';
sprintf(second_buf[i++]+off, "phase %d", disk6.disk[1].phase);
memset(second_buf[i], ' ', BUF_X);
num_buffer_lines = i;
}
/* -------------------------------------------------------------------------
clear_debugger_screen () - clears the screen of graphics artifacts.
------------------------------------------------------------------------- */
void clear_debugger_screen() {
#ifdef INTERFACE_CLASSIC
int i;
video_setpage( 0 );
for (i = 0; i < 24; i++)
{
c_interface_print(0, i, 2, screen[ i ] );
}
#endif
}
/* -------------------------------------------------------------------------
fb_sha1 () -- prints SHA1 of the current Apple // framebuffer
------------------------------------------------------------------------- */
void fb_sha1() {
uint8_t md[SHA_DIGEST_LENGTH];
char buf[(SHA_DIGEST_LENGTH*2)+1];
video_setpage(!!(softswitches & SS_SCREEN));
video_redraw();
const uint8_t * const fb = video_current_framebuffer();
SHA1(fb, SCANWIDTH*SCANHEIGHT, md);
int i=0;
for (int j=0; j<SHA_DIGEST_LENGTH; j++, i+=2) {
sprintf(buf+i, "%02X", md[j]);
}
sprintf(buf+i, "%c", '\0');
LOG("SHA1 : %s", buf);
int ch = -1;
#ifdef INTERFACE_CLASSIC
while ((ch = c_mygetch(1)) == -1) {
// ...
}
#endif
clear_debugger_screen();
sprintf(second_buf[num_buffer_lines++], "%s", buf);
}
/* -------------------------------------------------------------------------
begin_cpu_stepping() - step the CPU
set the CPU into stepping mode and yield to CPU thread
------------------------------------------------------------------------- */
static int begin_cpu_stepping() {
int ch = -1;
int err = 0;
// kludgey set max CPU speed...
double saved_scale = cpu_scale_factor;
double saved_altscale = cpu_altscale_factor;
bool saved_fullspeed = is_fullspeed;
cpu_scale_factor = CPU_SCALE_FASTEST;
cpu_altscale_factor = CPU_SCALE_FASTEST;
timing_initialize();
unsigned int idx = 0;
size_t textlen = 0;
if (stepping_struct.step_text) {
textlen = strlen(stepping_struct.step_text);
}
do {
if (textlen && !((apple_ii_64k[0][0xC000] & 0x80) || (apple_ii_64k[1][0xC000] & 0x80)) ) {
uint8_t text_ch = (uint8_t)stepping_struct.step_text[idx];
if (text_ch == '\n') {
text_ch = '\r';
}
apple_ii_64k[0][0xC000] = text_ch | 0x80;
apple_ii_64k[1][0xC000] = text_ch | 0x80;
++idx;
if (idx >= textlen) {
textlen = 0;
}
}
if ((err = pthread_cond_signal(&cpu_thread_cond))) {
ERRLOG("pthread_cond_signal : %d", err);
}
if ((err = pthread_cond_wait(&dbg_thread_cond, &interface_mutex))) {
ERRLOG("pthread_cond_wait : %d", err);
}
#if TESTING
#warning FIXME TODO : this is mis-named now ... GLVideo pushes sync state so we do not need to force poll ... but we need this to type the testing strings ... should refactor to leverage a common codepath, preferablly using the 'typing' mechanism here...
extern void testing_video_sync();
testing_video_sync();
#elif defined(INTERFACE_CLASSIC)
if ((ch = c_mygetch(0)) != -1) {
break;
}
#endif
if ( (stepping_struct.step_type != TYPING) && (idx > textlen) ) {
break; // finished typing
}
if (stepping_timeout && (stepping_struct.timeout < time(NULL))) {
break; // timeout
}
} while (!stepping_struct.should_break);
if ((err = pthread_cond_signal(&cpu_thread_cond))) {
ERRLOG("pthread_cond_signal : %d", err);
}
cpu_scale_factor = saved_scale;
cpu_altscale_factor = saved_altscale;
timing_initialize();
return ch;
}
/* -------------------------------------------------------------------------
c_debugger_should_break()
------------------------------------------------------------------------- */
bool c_debugger_should_break() {
if (pthread_self() != cpu_thread_id) {
// OOPS ...
ERRLOG("should only call this from cpu thread, bailing...");
void *ptr = NULL;
free(ptr);
}
if (at_haltpt()) {
stepping_struct.should_break = true;
} else {
uint8_t op = get_last_opcode();
switch (stepping_struct.step_type) {
case STEPPING:
{
--stepping_struct.step_count;
if (stepping_struct.step_count == 0) {
stepping_struct.should_break = true;
}
}
break;
case NEXTING:
{
if (stepping_struct.step_count > 0) {
--stepping_struct.step_count;
}
if (op == 0x20) {
++stepping_struct.step_frame; // JSR
}
if (op == 0x60) {
--stepping_struct.step_frame; // RTS
}
if ((stepping_struct.step_frame == 0) && (stepping_struct.step_count == 0)) {
stepping_struct.should_break = true;
}
}
break;
case UNTILING:
{
if (stepping_struct.step_pc == cpu65_pc) {
stepping_struct.should_break = true;
}
}
break;
case FINISHING:
{
if (op == 0x20) {
++stepping_struct.step_frame; // JSR
}
if (op == 0x60) {
--stepping_struct.step_frame; // RTS
}
if (stepping_struct.step_frame == 0) {
stepping_struct.should_break = true;
}
}
break;
case GOING:
case TYPING:
case LOADING:
break;
}
}
return stepping_struct.should_break;
}
/* -------------------------------------------------------------------------
debugger_go () - step into or step over commands
------------------------------------------------------------------------- */
int debugger_go(stepping_struct_t s) {
stepping_struct = s;
int ch = begin_cpu_stepping();
#if !TESTING
if (stepping_struct.step_type != LOADING) {
clear_debugger_screen();
disasm(cpu65_pc, 1, 0, -1);
int branch = will_branch();
if (branch != BRANCH_NA) {
sprintf(second_buf[num_buffer_lines++], "%s", (branch) ? "will branch" : "will not branch");
}
}
#endif
stepping_struct = (stepping_struct_t){ 0 };
return ch;
}
/* -------------------------------------------------------------------------
display_help ()
show quick reference command usage
------------------------------------------------------------------------- */
void display_help() {
/* "|||||||||||||||||||||||||||||||||||||" */
int i = num_buffer_lines;
sprintf(second_buf[i++], "d{is} {lc1|lc2} {/bank/addr} {+}{len}");
sprintf(second_buf[i++], "m{em} {lc1|lc2} {/bank/addr} {+}{len}");
sprintf(second_buf[i++], "a{sc} {lc1|lc2} {/bank/addr} {+}{len}");
sprintf(second_buf[i++], "r{egs} ");
sprintf(second_buf[i++], "<addr> {lc1|lc2} : <byteseq> ");
sprintf(second_buf[i++], "(s{tep} | n{ext}) {len} ");
sprintf(second_buf[i++], "f{inish} ");
sprintf(second_buf[i++], "u{ntil} ");
sprintf(second_buf[i++], "g{o} {addr} ");
sprintf(second_buf[i++], "sea{rch} {lc1|lc2} {bank} <bytes> ");
sprintf(second_buf[i++], "(b{reak} | w{atch}) {addr} ");
sprintf(second_buf[i++], "b{reak} op <byte> ");
sprintf(second_buf[i++], "(c{lear} | i{gnore}) {num} ");
sprintf(second_buf[i++], "c{lear} op <byte> ");
sprintf(second_buf[i++], "(sta{tus} | op{codes}) ");
sprintf(second_buf[i++], "(l{ang} | d{rive} | vm) ");
sprintf(second_buf[i++], "bsave <filename> </bank/addr> <len> ");
sprintf(second_buf[i++], "bload <filename> </bank/addr> ");
sprintf(second_buf[i++], "fr{esh} ");
sprintf(second_buf[i++], "(h{elp} | ?) ");
num_buffer_lines = i;
}
__attribute__((constructor(CTOR_PRIORITY_LATE)))
static void _init_debugger(void) {
LOG("Initializing virtual machine debugger subsystem");
for (unsigned int i=0; i<MAX_BRKPTS; i++)
{
breakpoints[i] = -1;
watchpoints[i] = -1;
}
for (unsigned int i=0; i<0x100; i++)
{
op_breakpoints[(unsigned char)i] = 0;
}
}
#ifdef INTERFACE_CLASSIC
/* -------------------------------------------------------------------------
do_debug_command ()
perform a debugger command
------------------------------------------------------------------------- */
static void do_debug_command() {
int i = 0, j = 0, k = 0;
/* reset key local vars */
num_buffer_lines = 0;
/* call lex to perform the command.*/
strncpy(lexbuf, command_line + PROMPT_X, BUF_X);
init_lex(lexbuf, BUF_X+2);
yylex();
/* set up to copy results into main buffer */
if (num_buffer_lines >= PROMPT_Y)
{
k = BUF_Y - PROMPT_Y;
}
else
{
/* scroll buffer */
for (i = 0, j = 0; i < PROMPT_Y - num_buffer_lines; i++, j = 0)
{
memcpy(command_buf[i], command_buf[num_buffer_lines+1+i], BUF_X);
while ((j < BUF_X) && (command_buf[i][j] != '\0'))
{
j++;
}
memset(command_buf[i] + j, ' ', BUF_X - j - 1);
command_buf[i][BUF_X - 1] = '\0';
}
}
/* copy the debug results into debug console window. change '\0's
to ' 's and cap with a single '\0' */
while (i < PROMPT_Y)
{
j = 0;
memcpy(command_buf[i], second_buf[k++], BUF_X);
while ((j < BUF_X) && (command_buf[i][j] != '\0'))
{
++j;
}
memset(command_buf[i] + j, ' ', BUF_X - j/* - 1*/);
command_buf[i++][BUF_X - 1] = '\0';
}
/* new prompt */
memset(command_line, ' ', BUF_X);
command_line[0] = '>';
command_line[BUF_X - 1] = '\0';
/* display the new information */
for (i=0; i<BUF_Y; i++)
{
c_interface_print(1, 1+i, 0, command_buf[i]);
}
/* reset before we return */
num_buffer_lines = 0;
}
/* -------------------------------------------------------------------------
c_interface_debugging()
main debugging console
------------------------------------------------------------------------- */
void c_interface_debugging() {
static char lex_initted = 0;
int err = 0;
int i;
int ch;
int command_pos = PROMPT_X;
opcodes = (apple_mode == 0) ? opcodes_6502 :
(apple_mode == 1) ? opcodes_undoc :
opcodes_65c02;
/* initialize the buffers */
for (i=0; i<BUF_Y; i++)
{
memset(command_buf[i], '\0', BUF_X);
memset(second_buf [i], '\0', BUF_X);
}
memset(command_line, ' ', BUF_X);
command_line[0] = '>';
command_line[BUF_X - 1] = '\0';
/* initialize the lexical scanner */
if (!lex_initted)
{
memset(lexbuf, '\0', BUF_X+2);
/*init_lex(lexbuf, BUF_X+2);*/
lex_initted = 1;
}
video_setpage( 0 );
c_interface_translate_screen( screen );
for (i = 0; i < 24; i++)
{
c_interface_print(0, i, 2, screen[ i ] );
}
is_debugging = true;
for (;;)
{
/* print command line */
c_interface_print(1, 1+PROMPT_Y, 0, command_line);
/* highlight cursor */
video_plotchar(1+command_pos, 1+PROMPT_Y, 1, command_line[command_pos]);
while ((ch = c_mygetch(1)) == -1)
{
}
if (ch == kESC)
{
break;
}
else
{
/* backspace */
if ((ch == 127 || ch == 8) && (command_pos > PROMPT_X))
{
command_line[--command_pos] = ' ';
}
/* return */
else if (ch == 13)
{
command_line[command_pos] = '\0';
do_debug_command();
command_pos = PROMPT_X;
}
/* normal character */
else if ((ch >= ' ') && (ch < 127) && (command_pos < PROMPT_END_X))
{
command_line[command_pos++] = ch;
}
}
}
c_interface_exit(-1);
is_debugging = false;
if ((err = pthread_cond_signal(&cpu_thread_cond)))
{
ERRLOG("pthread_cond_signal : %d", err);
}
return;
}
#endif
/* -------------------------------------------------------------------------
debugger testing-driven API
------------------------------------------------------------------------- */
void c_debugger_go() {
stepping_struct_t s = (stepping_struct_t){
.step_type = GOING,
.timeout = time(NULL) + stepping_timeout
};
num_buffer_lines = 0;
is_debugging = true;
debugger_go(s);
is_debugging = false;
num_buffer_lines = 0;
}
void c_debugger_set_timeout(const unsigned int secs) {
stepping_timeout = secs;
}
bool c_debugger_set_watchpoint(const uint16_t addr) {
return set_halt(watchpoints, addr);
}