/* ========== DEBUGGER.C ========== */ #include #include "v9t9_common.h" #include "9900.h" #include "9900st.h" #include "memory.h" #include "vdp.h" #include "grom.h" #include "speech.h" #include "system.h" #include "debugger.h" #define _L LOG_CPU static char * decR(char *buf, int val) { *buf++ = 'R'; if (val >= 10) { *buf++ = '1'; val -= 10; } *buf++ = (val + '0'); return buf; } #define NO_DOMAIN (mem_domain)(-1) static Memory views[MEMORY_VIEW_COUNT]; int debugger_memory_view_size[MEMORY_VIEW_COUNT] = { 16, 16, 16, 16, 16 }; bool debugger_operand_view_verbose = true; static u16 register_view; static void instruction_decode(u16 op, u16 pc, u16 wp, u16 st, Instruction *ins); static u8 MEMORY_READ_MM_BYTE(u16 x) { x &= 0x9c02; return x == 0x8800 ? domain_read_byte(md_video, vdp_mmio_get_addr()) : x == 0x8802 ? vdp_mmio_get_status() : x == 0x9000 ? domain_read_byte(md_speech, speech_mmio_get_addr()) : x == 0x9800 ? domain_read_byte(md_graphics, grom_mmio_get_addr()) : x == 0x9802 ? grom_mmio_get_addr_byte() : 0; } #define flatmem(x) (byteop ? flatmem8(x)&0xff : flatmem16(x)&0xffff) #define flatmem8(x) (((x)>=0x8400 && (x)<0xa000 ? MEMORY_READ_MM_BYTE(x) : memory_read_byte(x))&0xff) #define flatmem16(x) (((x)>=0x8400 && (x)<0xa000 ? MEMORY_READ_MM_BYTE(x)<<8 : memory_read_word(x))&0xffff) /* * Complete an operand by fixing up val and ea as needed. * * addr is the address of the PC. */ static void operand_complete(Operand *op, u16 *addr) { switch (op->type) { case OP_NONE: break; case OP_REG: // Rx op->ea = (op->val<<1) + wp; break; case OP_IND: // *Rx case OP_INC: // *Rx+ op->ea = flatmem16((op->val<<1) + wp); break; case OP_ADDR: // @>xxxx or @>xxxx(Rx) op->ea = op->immed = MEMORY_READ_WORD(*addr); *addr += 2; if (op->val != 0) { op->ea += flatmem16((op->val<<1) + wp); } break; case OP_IMMED: // immediate op->ea = *addr; op->immed = MEMORY_READ_WORD(*addr); *addr += 2; break; case OP_CNT: // shift count break; case OP_OFFS: // offset from R12 op->ea = flatmem16((12<<1) + wp) + op->val; break; case OP_JUMP: // jump target op->val <<= 1; // byte -> word op->ea = op->val + *addr; break; case OP_STATUS: // status word break; case OP_INST: break; // can't handle here } } /* * Print out an operand into a disassembler operand */ char * debugger_instruction_operand_print(Operand *op, char *buffer) { switch (op->type) { case OP_REG: sprintf(buffer, "R%d", op->val); break; case OP_IND: sprintf(buffer, "*R%d", op->val); break; case OP_ADDR: if (op->val == 0) { sprintf(buffer, "@>%04X", op->immed); } else { sprintf(buffer, "@>%04X(R%d)", op->immed, op->val); } break; case OP_INC: sprintf(buffer, "*R%d+", op->val); break; case OP_IMMED: sprintf(buffer, ">%04X", op->immed); break; case OP_CNT: sprintf(buffer, "%d", op->val); break; case OP_OFFS: sprintf(buffer, ">%s%02X", (op->val & 0x8000) ? "-" : "", (op->val & 0x8000) ? -op->val : op->val); break; case OP_JUMP: sprintf(buffer, "$+>%04X", op->val); break; case OP_STATUS: // not real operands case OP_INST: default: return 0L; } return buffer; } /* * Print value of operand to buffer * * verbose==true means to print extra info * after==true means this operand as the destination of * previous instruction */ char * debugger_operand_value_print(Instruction *inst, Operand *op, bool verbose, bool after, char *buffer) { const char *equ = after ? ":=" : "="; // is operand not a destination? if (after && !op->dest) return NULL; // if source operand is killed, we don't care to see it if (!after && op->dest == OP_DEST_KILLED) return NULL; // ignore this operand? if (op->ignore) return NULL; switch (op->type) { case OP_REG: if (inst->opcode >= 0x3800 && inst->opcode < 0x3C00) { // MPY uses two adjacent registers if (after) if (verbose) sprintf(buffer, "R%d,R%d%s>%04X%04X", op->val, op->val+1, equ, flatmem16(op->ea), flatmem16(op->ea + 2)); else sprintf(buffer, ">%04X%04X", flatmem16(op->ea), flatmem16(op->ea + 2)); else if (verbose) sprintf(buffer, "R%d%s>%04X", op->val, equ, flatmem16(op->ea)); else sprintf(buffer, ">%04X", flatmem16(op->ea)); } else if (inst->opcode >= 0x3C00 && inst->opcode < 0x4000) { // DIV uses two adjacent registers if (!after) if (verbose) sprintf(buffer, "R%d,R%d%s>%04X%04X", op->val, op->val+1, equ, flatmem16(op->ea), flatmem16(op->ea + 2)); else sprintf(buffer, ">%04X%04X", flatmem16(op->ea), flatmem16(op->ea + 2)); else if (verbose) sprintf(buffer, "qR%d%s>%04X,rR%d%s>%04X", op->val, equ, flatmem16(op->ea), op->val+1, equ, flatmem16(op->ea + 2)); else sprintf(buffer, "Q>%04X R>%04X", flatmem16(op->ea), flatmem16(op->ea + 2)); } else { if (op->byteop) if (verbose) sprintf(buffer, "R%d%s>%02X", op->val, equ, flatmem8(op->ea)); else sprintf(buffer, ">%02X", flatmem8(op->ea)); else if (verbose) sprintf(buffer, "R%d%s>%04X", op->val, equ, flatmem16(op->ea)); else sprintf(buffer, ">%04X", flatmem16(op->ea)); } break; case OP_INC: case OP_IND: if (after) { if (op->byteop) if (verbose) sprintf(buffer, "R%d%s>%02X", op->val, equ, flatmem8(op->ea)); else sprintf(buffer, ">%02X", flatmem8(op->ea)); else if (verbose) sprintf(buffer, "R%d%s>%04X", op->val, equ, flatmem16(op->ea)); else sprintf(buffer, ">%04X", flatmem16(op->ea)); break; } // else show address case OP_ADDR: if (op->byteop) if (verbose) // if address points to a register, point this out if (op->ea >= inst->wp && op->ea < inst->wp+32) sprintf(buffer, "%c%d%s>%02X", (op->ea&1) ? 'r' : 'R', // low or high byte (op->ea - inst->wp)>>1, equ, flatmem8(op->ea)); else sprintf(buffer, ">%04X%s>%02X", op->ea, equ, flatmem8(op->ea)); else sprintf(buffer, ">%02X", flatmem8(op->ea)); else if (verbose) // if address points to a register, point this out if (op->ea >= inst->wp && op->ea < inst->wp+32) sprintf(buffer, "R%d%s>%04X", (op->ea - inst->wp)>>1, equ, flatmem16(op->ea)); else sprintf(buffer, ">%04X%s>%04X", op->ea, equ, flatmem16(op->ea)); else sprintf(buffer, ">%04X", flatmem16(op->ea)); break; /* case OP_IMMED: sprintf(buffer, ">%04X",op->immed); break; case OP_CNT: sprintf(buffer, ">%04X",op->val); break; */ case OP_OFFS: case OP_JUMP: sprintf(buffer, ">%04X",op->ea); break; case OP_STATUS: sprintf(buffer, "<%s%s%s%s%s%s%s|%x>", (inst->status&ST_L) ? "L" : "", (inst->status&ST_A) ? "A" : "", (inst->status&ST_E) ? "E" : "", (inst->status&ST_C) ? "C" : "", (inst->status&ST_O) ? "O" : "", (inst->status&ST_P) ? "P" : "", (inst->status&ST_X) ? "X" : "", inst->status&ST_INTLEVEL); break; case OP_INST: { // sub-instruction! Instruction xinst; char op1[32], *op1ptr, op2[32], *op2ptr; instruction_decode(op->val, inst->pc, inst->wp, inst->status, &xinst); if (verbose) { op1ptr = debugger_instruction_operand_print(&xinst.op1, op1); op2ptr = debugger_instruction_operand_print(&xinst.op2, op2); sprintf(buffer, "(>%04X %s %s%s%s)", xinst.opcode, xinst.name, op1ptr ? op1ptr : "", op2ptr ? "," : "", op2ptr ? op2ptr : ""); } else { sprintf(buffer, "(>%04X %s)", xinst.opcode, xinst.name); } } break; default: return 0L; } return buffer; } /* * Decode an instruction with opcode 'op' at 'addr' * into 'ins' */ static void instruction_decode(u16 op, u16 pc, u16 wp, u16 st, Instruction *ins) { Instruction inst; memset(&inst, 0, sizeof(inst)); inst.opcode = op; inst.name = "????"; inst.op1.type = inst.op2.type = OP_NONE; inst.pc = pc; inst.wp = wp; inst.status = st; // Collect the instruction name // and operand structure. pc += 2; // point to operands // Initially, inst.op?.val is incomplete, and is whatever // raw data from the opcode we can decode; // inst.op?.ea is that of the instruction or immediate // if the operand needs it. // after decoding the instruction, we complete // the operand, making inst.op?.val and inst.op?.ea valid. if (op < 0x200) // data { inst.op1.type = OP_IMMED; pc -= 2; // instruction itself is value inst.name = "DATA"; } else if (op < 0x2a0) { inst.op1.type = OP_REG; inst.op1.val = op & 15; inst.op1.dest = true; inst.op2.type = OP_IMMED; switch ((op & 0x1e0) >> 5) { case 0: inst.name = "LI "; inst.op1.dest = OP_DEST_KILLED; break; case 1: inst.name = "AI "; break; case 2: inst.name = "ANDI"; break; case 3: inst.name = "ORI "; break; case 4: inst.name = "CI "; inst.op1.dest = false; break; } } else if (op < 0x2e0) { inst.op1.type = OP_REG; inst.op1.val = op & 15; inst.op1.dest = OP_DEST_KILLED; switch ((op & 0x1e0) >> 5) { case 5: inst.name = "STWP"; break; case 6: inst.name = "STST"; inst.op2.type = OP_STATUS; inst.op2.val = st; break; } } else if (op < 0x320) { inst.op1.type = OP_IMMED; switch ((op & 0x1e0) >> 5) { case 7: inst.name = "LWPI"; break; case 8: inst.name = "LIMI"; break; } } else if (op < 0x400) { switch ((op & 0x1e0) >> 5) { case 10: inst.name = "IDLE"; break; case 11: inst.name = "RSET"; break; case 12: inst.name = "RTWP"; inst.op1.type = OP_STATUS; inst.op1.val = st; break; case 13: inst.name = "CKON"; break; case 14: inst.name = "CKOF"; break; case 15: inst.name = "LREX"; break; } } else if (op < 0x800) { inst.op1.type = (op & 0x30) >> 4; inst.op1.val = op & 15; inst.op1.dest = true; switch ((op & 0x3c0) >> 6) { case 0: inst.name = "BLWP"; inst.op1.dest = false; inst.op1.ignore = true; break; case 1: inst.name = "B "; inst.op1.dest = false; inst.op1.ignore = true; break; case 2: inst.name = "X "; inst.op1.dest = false; inst.op2.type = OP_INST; break; case 3: inst.name = "CLR "; inst.op1.dest = OP_DEST_KILLED; break; case 4: inst.name = "NEG "; break; case 5: inst.name = "INV "; break; case 6: inst.name = "INC "; break; case 7: inst.name = "INCT"; break; case 8: inst.name = "DEC "; break; case 9: inst.name = "DECT"; break; case 10: inst.name = "BL "; inst.op1.dest = false; inst.op1.ignore = true; break; case 11: inst.name = "SWPB"; break; case 12: inst.name = "SETO"; inst.op1.dest = OP_DEST_KILLED; break; case 13: inst.name = "ABS "; break; } } else if (op < 0xc00) { inst.op1.type = OP_REG; inst.op1.val = op & 15; inst.op1.dest = true; inst.op2.type = OP_CNT; inst.op2.val = (op & 0xf0) >> 4; // shift of zero comes from R0 if (inst.op2.val == 0) { inst.op2.type = OP_REG; inst.op2.val = 0; } switch ((op & 0x700) >> 8) { case 0: inst.name = "SRA "; break; case 1: inst.name = "SRL "; break; case 2: inst.name = "SLA "; break; case 3: inst.name = "SRC "; break; } } else if (op < 0x1000) { switch ((op & 0x7e0) >> 5) { // !!! extended instructions } } else if (op < 0x2000) { if (op < 0x1d00) { inst.op1.type = OP_JUMP; inst.op1.val = ((s8) (op & 0xff)); inst.op2.type = OP_STATUS; inst.op2.val = st; } else { inst.op1.type = OP_OFFS; inst.op1.val = ((s8) (op & 0xff)); } switch ((op & 0xf00) >> 8) { case 0: inst.name = "JMP "; break; case 1: inst.name = "JLT "; break; case 2: inst.name = "JLE "; break; case 3: inst.name = "JEQ "; break; case 4: inst.name = "JHE "; break; case 5: inst.name = "JGT "; break; case 6: inst.name = "JNE "; break; case 7: inst.name = "JNC "; break; case 8: inst.name = "JOC "; break; case 9: inst.name = "JNO "; break; case 10: inst.name = "JL "; break; case 11: inst.name = "JH "; break; case 12: inst.name = "JOP "; break; case 13: inst.name = "SBO "; break; case 14: inst.name = "SBZ "; break; case 15: inst.name = "TB "; break; } } else if (op < 0x4000 && !(op >= 0x3000 && op < 0x3800)) { inst.op1.type = (op & 0x30) >> 4; inst.op1.val = (op & 15); inst.op1.dest = false; inst.op2.type = OP_REG; inst.op2.val = (op & 0x3c0) >> 6; inst.op2.dest = true; switch ((op & 0x1c00) >> 10) { case 0: inst.name = "COC "; inst.op2.dest = false; break; case 1: inst.name = "CZC "; inst.op2.dest = false; break; case 2: inst.name = "XOR "; break; case 3: inst.name = "XOP "; break; case 6: inst.name = "MPY "; // inst.op2.type = OP_MPY; break; case 7: inst.name = "DIV "; // inst.op2.type = OP_DIV; break; } } else if (op >= 0x3000 && op < 0x3800) { inst.op1.type = (op & 0x30) >> 4; inst.op1.val = (op & 15); inst.op2.type = OP_CNT; inst.op2.val = (op & 0x3c0) >> 6; if (inst.op2.val == 0) inst.op2.val = 16; inst.op1.byteop = (inst.op2.val <= 8); inst.name = (op < 0x3400 ? "LDCR" : "STCR"); inst.op1.dest = (op >= 0x3400); } else { inst.op1.type = (op & 0x30) >> 4; inst.op1.val = (op & 15); inst.op2.type = (op & 0x0c00) >> 10; inst.op2.val = (op & 0x3c0) >> 6; inst.op2.dest = true; inst.op1.byteop = inst.op2.byteop = ((op & 0x1000) != 0); switch ((op & 0xf000) >> 12) { case 4: inst.name = "SZC "; break; case 5: inst.name = "SZCB"; break; case 6: inst.name = "S "; break; case 7: inst.name = "SB "; break; case 8: inst.name = "C "; inst.op2.dest = false; break; case 9: inst.name = "CB "; inst.op2.dest = false; break; case 10: inst.name = "A "; break; case 11: inst.name = "AB "; break; case 12: inst.name = "MOV "; inst.op2.dest = OP_DEST_KILLED; break; case 13: inst.name = "MOVB"; inst.op2.dest = OP_DEST_KILLED; break; case 14: inst.name = "SOC "; break; case 15: inst.name = "SOCB"; break; } } // Figure out the ea for the operands operand_complete(&inst.op1, &pc); operand_complete(&inst.op2, &pc); // And the instruction for X if (inst.op2.type == OP_INST) { inst.op2.val = MEMORY_READ_WORD(inst.op1.ea); } *ins = inst; } /* * Tell if the operand has any effect on a register; * return a bitmap for each */ static void derive_register_access(Instruction *inst, Operand *op, int *read, int *written) { switch (op->type) { case OP_REG: if (op->dest != OP_DEST_KILLED) *read |= (1 << op->val); if (op->dest) *written |= (1 << op->val); // multiply writes two registers if ((inst->opcode >= 0x3800 && inst->opcode < 0x3C00) && op->dest) *written |= (1 << (op->val+1)); // divide reads and writes two registers if (inst->opcode >= 0x3C00 && inst->opcode < 0x4000) { if (op->dest) *read |= (1 << (op->val+1)); else *written |= (1 << (op->val+1)); } break; case OP_IND: case OP_ADDR: case OP_INC: if (op->type != OP_ADDR || op->val != 0) *read |= (1 << op->val); if (op->type == OP_INC) *written |= (1 << op->val); // memory write to register? if (op->ea >= inst->wp && op->ea < inst->wp + 32) { if (op->dest != OP_DEST_KILLED) *read |= (1 << ((op->ea - inst->wp) >> 1)); if (op->dest) *written |= (1 << ((op->ea - inst->wp) >> 1)); } break; case OP_INST: { Instruction xinst; instruction_decode(op->val, inst->pc, inst->wp, inst->status, &xinst); // watch out for recursion if (xinst.op1.type != OP_INST && xinst.op2.type != OP_INST) { derive_register_access(&xinst, &xinst.op1, read, written); derive_register_access(&xinst, &xinst.op2, read, written); } else { // panic *read = *written = -1; } } break; } } /* * Update register view according to effects of * previously executed instruction, including change to WP, * reads and writes to register. */ static void register_update_view(Instruction *inst, u16 wp) { int reg; int read, written; u16 regs[16]; // is new register set changing? if (wp != register_view) { register_view = wp; for (reg = 0; reg < 16; reg++) { regs[reg] = MEMORY_READ_WORD((reg<<1) + register_view); } report_status(STATUS_CPU_REGISTER_VIEW, register_view, regs); // don't bother reporting effects of previous instruction return; } // report accessed registers from prevous instruction read = written = 0; derive_register_access(inst, &inst->op1, &read, &written); derive_register_access(inst, &inst->op2, &read, &written); for (reg = 0; reg < 16; reg++) { if (written & (1 << reg)) { report_status( STATUS_CPU_REGISTER_WRITE, reg, MEMORY_READ_WORD((reg<<1) + inst->wp)); } else if (read & (1 << reg)) { report_status( STATUS_CPU_REGISTER_READ, reg, MEMORY_READ_WORD((reg<<1) + inst->wp)); } } } void debugger_register_clear_view(void) { u16 regs[16]; int reg; for (reg = 0; reg < 16; reg++) { regs[reg] = MEMORY_READ_WORD((reg<<1) + register_view); } report_status(STATUS_CPU_REGISTER_VIEW, register_view, regs); } static char *hexstr = "0123456789ABCDEF"; static char * hex2(char *buf, u8 val) { *buf++ = hexstr[(val & 0xf0) >> 4]; *buf++ = hexstr[val & 0xf]; return buf; } static char * hex4(char *buf, u16 val) { *buf++ = hexstr[(val & 0xf000) >> 12]; *buf++ = hexstr[(val & 0xf00) >> 8]; *buf++ = hexstr[(val & 0xf0) >> 4]; *buf++ = hexstr[val & 0xf]; return buf; } /* * Setup a memory view, returning a bool telling * whether the view changed. */ static bool memory_view_setup(Memory *s, MemoryView view, u16 addr, int len) { bool changed = false; mem_domain dmn; u8 *mem; mrstruct *area; s->which = view; s->addr = addr; s->len = len; if (debugger_memory_view_size[s->which] <= 0) { debugger_memory_view_size[s->which] = 16; } // get base address fixed at multiple of view size if ((s->addr < s->base || s->addr + len >= s->base + debugger_memory_view_size[s->which]) || s->base % debugger_memory_view_size[s->which]) { s->base = s->addr - (s->addr % debugger_memory_view_size[s->which]); changed = true; } // if not enough room for operand, fudge base address if (s->base + debugger_memory_view_size[s->which] < s->addr + len) { s->base = s->addr; changed = true; } // set up memory pointer dmn = (view == MEMORY_VIEW_VIDEO) ? md_video : (view == MEMORY_VIEW_GRAPHICS) ? md_graphics : (view == MEMORY_VIEW_SPEECH) ? md_speech : md_cpu; //mem = FLAT_MEMORY_PTR(dmn, s->base); area = THE_AREA(dmn, s->base); if (area->areamemory) mem = area->areamemory + (s->base & (AREASIZE-1)); else mem = zeroes; if (mem != s->mem) changed = true; s->mem = mem; return changed; } /* * For a given address reference, select a view for the * type of memory it is referencing. */ INLINE int memory_distance(u16 a, u16 b) { return (a < 0x8000 && b < 0x8000) ? a - b : (a < 0x8000 && b >= 0x8000) ? a - (0x10000 - b) : (a >= 0x8000 && b < 0x8000) ? (0x10000 - a) - b : (0x10000 - a) - (0x10000 - b); } static Memory * memory_view_get(u16 addr, int len, bool dest, Memory *using, bool *changed) { MemoryView view = dest ? MEMORY_VIEW_CPU_2 : MEMORY_VIEW_CPU_1; Memory *s; // !!! warning, this assumes a 99/4A with this memory // configuration if (addr >= 0x8400 && addr < 0xa000) { addr &= 0x9c02; switch (addr) { case 0x8800: case 0x8c00: case 0x8c02: if (!vdp_mmio_addr_is_complete()) return 0L; addr = vdp_mmio_get_addr(); view = MEMORY_VIEW_VIDEO; break; case 0x9000: case 0x9400: if (!speech_mmio_addr_is_complete()) return 0L; addr = speech_mmio_get_addr(); view = MEMORY_VIEW_SPEECH; break; case 0x9800: case 0x9802: case 0x9c00: case 0x9c02: if (!grom_mmio_addr_is_complete()) return 0L; addr = grom_mmio_get_addr(); view = MEMORY_VIEW_GRAPHICS; break; } } // divide cpu views 1 and 2 into source and // destination, or, based on distance from previous view if (view == MEMORY_VIEW_CPU_1 || view == MEMORY_VIEW_CPU_2) { int dist1 = memory_distance(views[MEMORY_VIEW_CPU_1].addr, addr); int dist2 = memory_distance(views[MEMORY_VIEW_CPU_2].addr, addr); if (dist1 > dist2 + 32 || (views[MEMORY_VIEW_CPU_1].coverage > views[MEMORY_VIEW_CPU_2].coverage + 32)) view = MEMORY_VIEW_CPU_2; else if (dist2 > dist1 + 32 || (views[MEMORY_VIEW_CPU_2].coverage > views[MEMORY_VIEW_CPU_1].coverage + 32)) view = MEMORY_VIEW_CPU_1; views[view].coverage++; } // setup the view info s = &views[view]; *changed = memory_view_setup(s, view, addr, len); return s; } /* * Update views of memory according to effect of * previous instruction */ static void memory_update_views(Instruction *inst) { Memory *view1 = 0L, *view2 = 0L; bool view1changed, view2changed; // pick a view for each memory operand if (OP_IS_MEMORY(inst->op1)) { view1 = memory_view_get(inst->op1.ea, inst->op1.byteop ? 1 : 2, inst->op1.dest, NULL, &view1changed); } if (OP_IS_MEMORY(inst->op2)) { view2 = memory_view_get(inst->op2.ea, inst->op2.byteop ? 1 : 2, inst->op2.dest, view1, &view2changed); } // don't show the same one twice if (view1 && view1 == view2) view2 = 0L; // update each view if (view1) { if (view1changed) report_status(STATUS_MEMORY_VIEW, view1); report_status(inst->op1.dest ? STATUS_MEMORY_WRITE : STATUS_MEMORY_READ, view1); } if (view2) { if (view2changed) report_status(STATUS_MEMORY_VIEW, view2); report_status(inst->op2.dest ? STATUS_MEMORY_WRITE : STATUS_MEMORY_READ, view2); } } static u16 memory_view_real_address(Memory *s) { switch (s->which) { case MEMORY_VIEW_VIDEO: s->len = 1; if (vdp_mmio_addr_is_complete()) s->addr = vdp_mmio_get_addr(); return true; case MEMORY_VIEW_GRAPHICS: s->len = 1; if (grom_mmio_addr_is_complete()) s->addr = grom_mmio_get_addr(); return true; case MEMORY_VIEW_SPEECH: s->len = 1; if (speech_mmio_addr_is_complete()) s->addr = speech_mmio_get_addr(); return true; } return false; } void debugger_memory_clear_views(void) { MemoryView view; Memory *s; bool memory_mapped; for (view = MEMORY_VIEW_CPU_1; view < MEMORY_VIEW_COUNT; view++) { s = &views[view]; memory_mapped = memory_view_real_address(s); memory_view_setup(s, view, s->addr, 0); report_status(STATUS_MEMORY_VIEW, s); } } /* * Update view of instruction. When a previous instruction * is being viewed, we send any destination operands changed. * For a current instruction, we preview the values of the operands * and send those, as well as a disassembly. */ static void instruction_update_view(Instruction *inst, bool after) { char hex[16]; char disasm[64]; char op1[32], *op1ptr; char op2[32], *op2ptr; if (!after) { // tell about the system registers report_status(STATUS_CPU_PC, inst->pc); report_status(STATUS_CPU_WP, inst->wp); report_status(STATUS_CPU_STATUS, inst->status); // get hex representation of instruction sprintf(hex, "%04X=%04X", inst->pc, inst->opcode); // get disassembly with operand representations op1ptr = debugger_instruction_operand_print(&inst->op1, op1); op2ptr = debugger_instruction_operand_print(&inst->op2, op2); if (!op1ptr) { op1ptr = op2ptr; op2ptr = 0L; } sprintf(disasm, "%s%s%s", op1ptr ? op1ptr : "", op2ptr ? "," : "", op2ptr ? op2ptr : ""); // get operand values op1ptr = debugger_operand_value_print( inst, &inst->op1, debugger_operand_view_verbose, false /*after*/, op1); op2ptr = debugger_operand_value_print( inst, &inst->op2, debugger_operand_view_verbose, false /*after*/, op2); if (!op1ptr) { op1ptr = op2ptr; op2ptr = 0L; } // send it to frontend report_status(STATUS_CPU_INSTRUCTION, inst, hex, disasm, op1ptr, op2ptr); } else { // afterwards, show destination operands // get operand values op1ptr = debugger_operand_value_print( inst, &inst->op1, debugger_operand_view_verbose, true /*after*/, op1); op2ptr = debugger_operand_value_print( inst, &inst->op2, debugger_operand_view_verbose, true /*after*/, op2); if (!op1ptr) { op1ptr = op2ptr; op2ptr = 0L; } // send it to frontend report_status(STATUS_CPU_INSTRUCTION_LAST, inst, op1ptr, op2ptr); } } void debugger_instruction_clear_view(void) { // send refresh signal to frontend report_status(STATUS_CPU_INSTRUCTION_LAST, 0L, 0L, 0L); } /* * Utility for status reporters. Given the slot, it writes a one-line hex dump to * the given buffer, and sets start/astart and end/aend to point to the extent * of the last memory access within the buffer. (These will be spaces.) * * addr_separator: char appearing between address and bytes * byte_separator: char appearing between each hex byte * ascii_separator: char appearing between hex field and ascii field * line_separator: char appearing between lines, and at end */ void debugger_hex_dump_line(Memory * slot, int offset, int length, char addr_separator, char byte_separator, char ascii_separator, char line_separator, char *buffer, int bufsz, char **start, char **end, char **astart, char **aend) { char *dumpptr = buffer, *asciiptr = dumpptr + 6+length*3; u16 idx, addr; u8 *bytes; if (asciiptr + length + 1 >= buffer + bufsz) { length = debugger_hex_dump_chars_to_bytes(bufsz-1); asciiptr = dumpptr + 6+length*3; my_assert(asciiptr + length < buffer + bufsz); } *dumpptr = 0; *start = *end = *astart = *aend = 0L; bytes = slot->mem + offset; addr = slot->base + offset; *dumpptr++ = MEMORY_VIEW_TOKEN(slot->which); dumpptr = hex4(dumpptr, addr); *dumpptr++ = addr_separator; if (slot->len && addr > slot->addr && addr < slot->addr + slot->len) { *start = dumpptr; *astart = asciiptr; } idx = 0; while (idx < length) { u8 ch; if (slot->len && addr + idx == slot->addr) { *start = dumpptr; *astart = asciiptr; } ch = bytes[idx]; dumpptr = hex2(dumpptr, ch); *asciiptr++ = isprint(ch) ? ch : '.'; idx++; if (slot->len && addr + idx == slot->addr + slot->len) { *end = dumpptr; *aend = asciiptr; } if (idx < length) *dumpptr++ = byte_separator; } if (slot->len && addr + length > slot->addr && addr + length < slot->addr + slot->len) { *end = dumpptr; *aend = asciiptr; } *dumpptr++ = ascii_separator; *asciiptr++ = line_separator; *asciiptr = 0; } /* * How long will this text be? */ int debugger_hex_dump_bytes_to_chars(int bytes) { return bytes * 4 + 6 + 1 + 1; } /* * How many bytes fit in this length? */ int debugger_hex_dump_chars_to_bytes(int chars) { return (chars - 6 - 1 - 1) / 4; } /* * Breakpoint manager */ typedef struct bkpt { u16 pc; // location of bkpt struct bkpt *next; } bkpt; static bkpt *breakpoints; // check to see if the address matches a breakpoint int debugger_check_breakpoint(u16 pc) { bkpt *ptr = breakpoints; while (ptr) if (ptr->pc == pc) return 1; else ptr = ptr->next; return 0; } // add the address to the list of breakpoints static void debugger_set_breakpoint(u16 pc) { bkpt *ptr; if (debugger_check_breakpoint(pc)) return; ptr = (bkpt *)xmalloc(sizeof(bkpt)); ptr->pc = pc; ptr->next = breakpoints; breakpoints = ptr; } // remove the address from the list of breakpoints static void debugger_reset_breakpoint(u16 pc) { bkpt *ptr, *prev; prev = 0L; ptr = breakpoints; while (ptr && ptr->pc != pc) { prev = ptr; ptr = ptr->next; } if (!ptr) return; if (prev) prev->next = ptr->next; else breakpoints = ptr->next; } DECL_SYMBOL_ACTION(debugger_breakpoint) { if (task == csa_READ) { static bkpt *list; if (!iter) list = breakpoints; if (list == 0L) return 0; command_arg_set_num(SYM_ARG_1st, list->pc); list = list->next; } else { int val; command_arg_get_num(SYM_ARG_1st, &val); debugger_set_breakpoint(val); } return 1; } DECL_SYMBOL_ACTION(debugger_clear_breakpoint) { int val; command_arg_get_num(SYM_ARG_1st, &val); debugger_reset_breakpoint(val); return 1; } DECL_SYMBOL_ACTION(debugger_list_breakpoints) { static bkpt *list; list = breakpoints; logger(_L|LOG_USER, "Active breakpoints:\n"); if (list == 0L) { logger(_L|LOG_USER, "\t\n"); return 1; } while (list) { logger(_L|LOG_USER, "\t>%04X\n", list->pc); list = list->next; } return 1; } /* * Entry point for debugger backend, entered before every instruction * executed when ST_DEBUG is set in the stateflag. */ static Instruction last; static bool last_valid; void debugger(void) { Instruction inst; report_status(STATUS_DEBUG_REFRESH); // Show effects of previous instruction if (last_valid) { memory_update_views(&last); register_update_view(&last, wp); instruction_update_view(&last, true /*after*/); } else { debugger_memory_clear_views(); debugger_register_clear_view(); debugger_instruction_clear_view(); } // Get a status word for this instruction statusto9900(); // Decode the current instruction instruction_decode(MEMORY_READ_WORD(pc), pc, wp, status, &inst); if (last.pc != inst.pc) { // Show current instruction instruction_update_view(&inst, false /*after*/); } // Save instruction last = inst; last_valid = true; } void debugger_init(void) { command_symbol_table *debugcommands = command_symbol_table_new("Debugger Options", "These commands control the debugger", command_symbol_new("BreakPoint", "Add a breakpoint at the given PC", c_DYNAMIC|c_SESSION_ONLY, debugger_breakpoint, RET_FIRST_ARG, command_arg_new_num ("address", "PC address at which to break", NULL /* action */ , NEW_ARG_NUM(u16), NULL /* next */ ) , command_symbol_new("ClearBreakPoint", "Remove breakpoint at the given PC", c_STATIC|c_DONT_SAVE, debugger_clear_breakpoint, RET_FIRST_ARG, command_arg_new_num ("address", "PC address of breakpoint", NULL /* action */ , NEW_ARG_NUM(u16), NULL /* next */ ) , command_symbol_new("ListBreakPoints", "List active breakpoints", c_STATIC|c_DONT_SAVE, debugger_list_breakpoints, NULL /*ret*/, NULL /*args*/, NULL /*next*/))), NULL /* sub */, NULL /* next */ ); command_symbol_table_add_subtable(universe, debugcommands); memset((void *)&views, 0, sizeof(views)); register_view = 0; } /* * Force next update to refresh all status items */ void debugger_refresh(void) { last_valid = false; } void debugger_enable(bool enable) { if (enable) { system_debugger_enabled(true); if (!(stateflag & ST_DEBUG)) { stateflag |= ST_DEBUG | ST_SINGLESTEP; debugger_refresh(); debugger(); } } else if (!enable && (stateflag & ST_DEBUG)) { stateflag &= ~ST_DEBUG; system_debugger_enabled(false); debugger_refresh(); } }