/* Hatari - breakcond.c Copyright (c) 2009-2012 by Eero Tamminen This file is distributed under the GNU General Public License, version 2 or at your option any later version. Read the file gpl.txt for details. breakcond.c - code for breakpoint conditions that can check variable and memory values against each other, mask them etc. before deciding whether the breakpoint should be triggered. See BreakCond_Help() for the syntax. */ const char BreakCond_fileid[] = "Hatari breakcond.c : " __DATE__ " " __TIME__; #include #include #include "config.h" #include "main.h" #include "file.h" #include "m68000.h" #include "memorySnapShot.h" #include "dsp.h" #include "stMemory.h" #include "str.h" #include "screen.h" /* for defines needed by video.h */ #include "video.h" /* for Hatari video variable addresses */ #include "debug_priv.h" #include "breakcond.h" #include "debugcpu.h" #include "debugdsp.h" #include "debugInfo.h" #include "debugui.h" #include "evaluate.h" #include "history.h" #include "symbols.h" #include "68kDisass.h" /* set to 1 to enable parsing function tracing / debug output */ #define DEBUG 0 /* needs to go through long long to handle x=32 */ #define BITMASK(x) ((Uint32)(((unsigned long long)1<<(x))-1)) #define BC_DEFAULT_DSP_SPACE 'P' typedef enum { /* plain number */ VALUE_TYPE_NUMBER = 0, /* functions to call to get value */ VALUE_TYPE_FUNCTION32 = 2, /* internal Hatari value variables */ VALUE_TYPE_VAR32 = 4, /* size must match register size used in BreakCond_ParseRegister() */ VALUE_TYPE_REG16 = 16, VALUE_TYPE_REG32 = 32 } value_t; static inline bool is_register_type(value_t vtype) { /* type used for CPU/DSP registers */ return (vtype == VALUE_TYPE_REG16 || vtype == VALUE_TYPE_REG32); } typedef struct { bool is_indirect; char dsp_space; /* DSP has P, X, Y address spaces, zero if not DSP */ value_t valuetype; /* Hatari value variable type */ union { Uint32 number; Uint16 (*func16)(void); Uint32 (*func32)(void); Uint16 *reg16; Uint32 *reg32; } value; Uint32 bits; /* CPU has 8/16/32 bit address widths */ Uint32 mask; /* && */ } bc_value_t; typedef struct { bc_value_t lvalue; bc_value_t rvalue; char comparison; bool track; /* track value changes */ } bc_condition_t; typedef struct { char *filename; /* file where to read commands to do on hit */ int skip; /* how many times to hit before breaking */ bool once; /* remove after hit&break */ bool quiet; /* no output from setting & hitting */ bool trace; /* trace mode, don't break */ bool noinit; /* prevent debugger inits on break */ bool lock; /* tracing + show locked info */ bool deleted; /* delayed delete flag */ } bc_options_t; typedef struct { char *expression; bc_options_t options; bc_condition_t *conditions; int ccount; /* condition count */ int hits; /* how many times breakpoint hit */ } bc_breakpoint_t; typedef struct { bc_breakpoint_t *breakpoint; bc_breakpoint_t *breakpoint2delete; /* delayed delete of old alloc */ const char *name; int count; int allocated; bool delayed_change; const debug_reason_t reason; } bc_breakpoints_t; static bc_breakpoints_t CpuBreakPoints = { .name = "CPU", .reason = REASON_CPU_BREAKPOINT }; static bc_breakpoints_t DspBreakPoints = { .name = "DSP", .reason = REASON_DSP_BREAKPOINT }; /* forward declarations */ static int BreakCond_DoDelayedActions(bc_breakpoints_t *bps, int triggered); static bool BreakCond_Remove(bc_breakpoints_t *bps, int position); static void BreakCond_Print(bc_breakpoint_t *bp); /** * Save breakpoints as debugger input file * return true for success, false for failure */ bool BreakCond_Save(const char *filename) { FILE *fp; int i; if (!(CpuBreakPoints.count || DspBreakPoints.count)) { if (File_Exists(filename)) { if (remove(filename)) { perror("ERROR"); return false; } } return true; } fprintf(stderr, "Saving breakpoints to '%s'...\n", filename); fp = fopen(filename, "w"); if (!fp) { perror("ERROR"); return false; } /* save conditional breakpoints as debugger input file */ for (i = 0; i < CpuBreakPoints.count; i++) { fprintf(fp, "b %s\n", CpuBreakPoints.breakpoint[i].expression); } for (i = 0; i < DspBreakPoints.count; i++) { fprintf(fp, "db %s\n", DspBreakPoints.breakpoint[i].expression); } fclose(fp); return true; } /* --------------------- debugging code ------------------- */ #if DEBUG /* see parsing code for usage examples */ static int _traceIndent; static void _spaces(void) { int spaces = _traceIndent; while(spaces-- > 0) { putchar(' '); /* fputc(' ',stdout); */ } } #define ENTERFUNC(args) { _traceIndent += 2; _spaces(); printf args ; fflush(stdout); } #define EXITFUNC(args) { _spaces(); printf args ; fflush(stdout); _traceIndent -= 2; } #else #define ENTERFUNC(args) #define EXITFUNC(args) #endif /* ------------- breakpoint condition checking, internals ------------- */ /** * Return value from given DSP memory space/address */ static Uint32 BreakCond_ReadDspMemory(Uint32 addr, const bc_value_t *bc_value) { const char *dummy; return DSP_ReadMemory(addr, bc_value->dsp_space, &dummy) & BITMASK(24); } /** * Return value of given size read from given ST memory address */ static Uint32 BreakCond_ReadSTMemory(Uint32 addr, const bc_value_t *bc_value) { switch (bc_value->bits) { case 32: return STMemory_ReadLong(addr); case 16: return STMemory_ReadWord(addr); case 8: return STMemory_ReadByte(addr); default: fprintf(stderr, "ERROR: unknown ST address size %d!\n", bc_value->bits); abort(); } } /** * Return Uint32 value according to given bc_value_t specification */ static Uint32 BreakCond_GetValue(const bc_value_t *bc_value) { Uint32 value; switch (bc_value->valuetype) { case VALUE_TYPE_NUMBER: value = bc_value->value.number; break; case VALUE_TYPE_FUNCTION32: value = bc_value->value.func32(); break; case VALUE_TYPE_REG16: value = *(bc_value->value.reg16); break; case VALUE_TYPE_VAR32: case VALUE_TYPE_REG32: value = *(bc_value->value.reg32); break; default: fprintf(stderr, "ERROR: unknown condition value size/type %d!\n", bc_value->valuetype); abort(); } if (bc_value->is_indirect) { if (bc_value->dsp_space) { value = BreakCond_ReadDspMemory(value, bc_value); } else { value = BreakCond_ReadSTMemory(value, bc_value); } } return (value & bc_value->mask); } /** * Show & update rvalue for a tracked breakpoint condition to lvalue */ static void BreakCond_UpdateTracked(bc_condition_t *condition, Uint32 value) { Uint32 addr; /* next monitor changes to this new value */ condition->rvalue.value.number = value; if (condition->lvalue.is_indirect && condition->lvalue.valuetype == VALUE_TYPE_NUMBER) { /* simple memory address */ addr = condition->lvalue.value.number; fprintf(stderr, " $%x = $%x\n", addr, value); } else { /* register tms. */ fprintf(stderr, " $%x\n", value); } } /** * Return true if all of the given breakpoint's conditions match */ static bool BreakCond_MatchConditions(bc_condition_t *condition, int count) { Uint32 lvalue, rvalue; bool hit = false; int i; for (i = 0; i < count; condition++, i++) { lvalue = BreakCond_GetValue(&(condition->lvalue)); rvalue = BreakCond_GetValue(&(condition->rvalue)); switch (condition->comparison) { case '<': hit = (lvalue < rvalue); break; case '>': hit = (lvalue > rvalue); break; case '=': hit = (lvalue == rvalue); break; case '!': hit = (lvalue != rvalue); break; default: fprintf(stderr, "ERROR: Unknown breakpoint value comparison operator '%c'!\n", condition->comparison); abort(); } if (likely(!hit)) { return false; } if (condition->track) { BreakCond_UpdateTracked(condition, lvalue); } } /* all conditions matched */ return true; } /** * Show all breakpoints which conditions matched and return which matched * @return index to last matching (non-tracing) breakpoint, * or zero if none matched */ static int BreakCond_MatchBreakPoints(bc_breakpoints_t *bps) { bc_breakpoint_t *bp; bool changes = false; int i, ret = 0; /* array should not be changed while it's being traversed */ assert(likely(!bps->delayed_change)); bps->delayed_change = true; bp = bps->breakpoint; for (i = 0; i < bps->count; bp++, i++) { if (BreakCond_MatchConditions(bp->conditions, bp->ccount)) { bp->hits++; if (bp->options.skip) { if (bp->hits % bp->options.skip) { /* check next */ continue; } } if (!bp->options.quiet) { fprintf(stderr, "%d. %s breakpoint condition(s) matched %d times.\n", i+1, bps->name, bp->hits); BreakCond_Print(bp); } History_Mark(bps->reason); if (bp->options.lock || bp->options.filename) { bool reinit = !bp->options.noinit; if (reinit) { DebugCpu_InitSession(); DebugDsp_InitSession(); } if (bp->options.lock) { DebugInfo_ShowSessionInfo(); } if (bp->options.filename) { DebugUI_ParseFile(bp->options.filename, reinit); changes = true; } } if (bp->options.once) { BreakCond_Remove(bps, i+1); changes = true; } if (!bp->options.trace) { /* index for current hit, they start from 1 */ ret = i + 1; } /* continue checking breakpoints to make sure all relevant actions get performed */ } } bps->delayed_change = false; if (unlikely(changes)) { ret = BreakCond_DoDelayedActions(bps, ret); } return ret; } /* ------------- breakpoint condition checking, public API ------------- */ /** * Return matched CPU breakpoint index or zero for no hits. */ int BreakCond_MatchCpu(void) { return BreakCond_MatchBreakPoints(&CpuBreakPoints); } /** * Return matched DSP breakpoint index or zero for no hits. */ int BreakCond_MatchDsp(void) { return BreakCond_MatchBreakPoints(&DspBreakPoints); } /** * Return number of CPU condition breakpoints */ int BreakCond_CpuBreakPointCount(void) { return CpuBreakPoints.count; } /** * Return number of DSP condition breakpoints */ int BreakCond_DspBreakPointCount(void) { return DspBreakPoints.count; } /* -------------- breakpoint condition parsing, internals ------------- */ /* struct for passing around breakpoint conditions parsing state */ typedef struct { int arg; /* current arg */ int argc; /* arg count */ const char **argv; /* arg pointer array (+ strings) */ const char *error; /* error from parsing args */ } parser_state_t; /* Hatari variable name & address array items */ typedef struct { const char *name; Uint32 *addr; value_t vtype; size_t bits; const char *constraints; } var_addr_t; /* Accessor functions for calculated Hatari values */ static Uint32 GetLineCycles(void) { int dummy1, dummy2, lcycles; Video_GetPosition(&dummy1, &dummy2 , &lcycles); return lcycles; } static Uint32 GetFrameCycles(void) { int dummy1, dummy2, fcycles; Video_GetPosition(&fcycles, &dummy1, &dummy2); return fcycles; } /* helpers for TOS OS call opcode accessor functions */ #define INVALID_OPCODE 0xFFFFu static inline Uint16 getLineOpcode(Uint8 line) { Uint32 pc; Uint16 instr; pc = M68000_GetPC(); instr = STMemory_ReadWord(pc); /* for opcode X, Line-A = 0xA00X, Line-F = 0xF00X */ if ((instr >> 12) == line) { return instr & 0xFF; } return INVALID_OPCODE; } static inline bool isTrap(Uint8 trap) { Uint32 pc; Uint16 instr; pc = M68000_GetPC(); instr = STMemory_ReadWord(pc); return (instr == (Uint16)0x4e40u + trap); } static inline Uint16 getControlOpcode(void) { /* Control[] address from D1, opcode in Control[0] */ return STMemory_ReadWord(STMemory_ReadLong(Regs[REG_D1])); } static inline Uint16 getStackOpcode(void) { return STMemory_ReadWord(Regs[REG_A7]); } /* Actual TOS OS call opcode accessor functions */ static Uint32 GetLineAOpcode(void) { return getLineOpcode(0xA); } static Uint32 GetLineFOpcode(void) { return getLineOpcode(0xF); } static Uint32 GetGemdosOpcode(void) { if (isTrap(1)) { return getStackOpcode(); } return INVALID_OPCODE; } static Uint32 GetBiosOpcode(void) { if (isTrap(13)) { return getStackOpcode(); } return INVALID_OPCODE; } static Uint32 GetXbiosOpcode(void) { if (isTrap(14)) { return getStackOpcode(); } return INVALID_OPCODE; } static Uint32 GetAesOpcode(void) { if (isTrap(2)) { Uint16 d0 = Regs[REG_D0]; if (d0 == 0xC8) { return getControlOpcode(); } else if (d0 == 0xC9) { /* same as appl_yield() */ return 0x11; } } return INVALID_OPCODE; } static Uint32 GetVdiOpcode(void) { if (isTrap(2)) { Uint16 d0 = Regs[REG_D0]; if (d0 == 0x73) { return getControlOpcode(); } else if (d0 == 0xFFFE) { /* -2 = vq_[v]gdos() */ return 0xFFFE; } } return INVALID_OPCODE; } static Uint32 GetNextPC(void) { return Disasm_GetNextPC(M68000_GetPC()); } /* sorted by variable name so that this can be bisected */ static const var_addr_t hatari_vars[] = { { "AesOpcode", (Uint32*)GetAesOpcode, VALUE_TYPE_FUNCTION32, 16, "by default FFFF" }, { "Basepage", (Uint32*)DebugInfo_GetBASEPAGE, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, { "BiosOpcode", (Uint32*)GetBiosOpcode, VALUE_TYPE_FUNCTION32, 16, "by default FFFF" }, { "BSS", (Uint32*)DebugInfo_GetBSS, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, { "CpuInstr", (Uint32*)DebugCpu_InstrCount, VALUE_TYPE_FUNCTION32, 0, "CPU instructions count" }, { "CpuOpcodeType", (Uint32*)DebugCpu_OpcodeType, VALUE_TYPE_FUNCTION32, 0, "CPU instruction type" }, { "DATA", (Uint32*)DebugInfo_GetDATA, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, #if ENABLE_DSP_EMU { "DspInstr", (Uint32*)DebugDsp_InstrCount, VALUE_TYPE_FUNCTION32, 0, "DSP instructions count" }, { "DspOpcodeType", (Uint32*)DebugDsp_OpcodeType, VALUE_TYPE_FUNCTION32, 0, "DSP instruction type" }, #endif { "FrameCycles", (Uint32*)GetFrameCycles, VALUE_TYPE_FUNCTION32, 0, NULL }, { "GemdosOpcode", (Uint32*)GetGemdosOpcode, VALUE_TYPE_FUNCTION32, 16, "by default FFFF" }, { "HBL", (Uint32*)&nHBL, VALUE_TYPE_VAR32, sizeof(nHBL)*8, NULL }, { "LineAOpcode", (Uint32*)GetLineAOpcode, VALUE_TYPE_FUNCTION32, 16, "by default FFFF" }, { "LineCycles", (Uint32*)GetLineCycles, VALUE_TYPE_FUNCTION32, 0, "is always divisable by 4" }, { "LineFOpcode", (Uint32*)GetLineFOpcode, VALUE_TYPE_FUNCTION32, 16, "by default FFFF" }, { "NextPC", (Uint32*)GetNextPC, VALUE_TYPE_FUNCTION32, 0, NULL }, { "TEXT", (Uint32*)DebugInfo_GetTEXT, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, { "TEXTEnd", (Uint32*)DebugInfo_GetTEXTEnd, VALUE_TYPE_FUNCTION32, 0, "invalid before Desktop is up" }, { "VBL", (Uint32*)&nVBLs, VALUE_TYPE_VAR32, sizeof(nVBLs)*8, NULL }, { "VdiOpcode", (Uint32*)GetVdiOpcode, VALUE_TYPE_FUNCTION32, 16, "by default FFFF" }, { "XbiosOpcode", (Uint32*)GetXbiosOpcode, VALUE_TYPE_FUNCTION32, 16, "by default FFFF" } }; /** * Readline match callback for CPU variable/symbol name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *BreakCond_MatchCpuVariable(const char *text, int state) { static int i, len; const char *name; if (!state) { /* first match */ len = strlen(text); i = 0; } /* next match */ while (i < ARRAYSIZE(hatari_vars)) { name = hatari_vars[i++].name; if (strncasecmp(name, text, len) == 0) return (strdup(name)); } /* no variable match, check all CPU symbols */ return Symbols_MatchCpuAddress(text, state); } /** * Readline match callback for DSP variable/symbol name completion. * STATE = 0 -> different text from previous one. * Return next match or NULL if no matches. */ char *BreakCond_MatchDspVariable(const char *text, int state) { /* currently no DSP variables, check all DSP symbols */ return Symbols_MatchDspAddress(text, state); } /** * If given string is a Hatari variable name, set bc_value * fields accordingly and return true, otherwise return false. */ static bool BreakCond_ParseVariable(const char *name, bc_value_t *bc_value) { const var_addr_t *hvar; /* left, right, middle, direction */ int l, r, m, dir; ENTERFUNC(("BreakCond_ParseVariable('%s')\n", name)); /* bisect */ l = 0; r = ARRAYSIZE(hatari_vars) - 1; do { m = (l+r) >> 1; hvar = hatari_vars + m; dir = strcasecmp(name, hvar->name); if (dir == 0) { bc_value->value.reg32 = hvar->addr; bc_value->valuetype = hvar->vtype; bc_value->bits = hvar->bits; assert(bc_value->bits == 32 || bc_value->valuetype != VALUE_TYPE_VAR32); EXITFUNC(("-> true\n")); return true; } if (dir < 0) { r = m-1; } else { l = m+1; } } while (l <= r); EXITFUNC(("-> false\n")); return false; } /** * If given string is a Hatari variable name, set value to given * variable value and return true, otherwise return false. */ bool BreakCond_GetHatariVariable(const char *name, Uint32 *value) { bc_value_t bc_value; if (!BreakCond_ParseVariable(name, &bc_value)) { return false; } bc_value.mask = 0xffffffff; bc_value.is_indirect = false; *value = BreakCond_GetValue(&bc_value); return true; } /** * If given string matches a suitable symbol, set bc_value * fields accordingly and return true, otherwise return false. */ static bool BreakCond_ParseSymbol(const char *name, bc_value_t *bc_value) { symtype_t symtype; Uint32 addr; ENTERFUNC(("BreakCond_ParseSymbol('%s')\n", name)); if (bc_value->is_indirect) { /* indirect use of address makes sense only for data */ symtype = SYMTYPE_DATA|SYMTYPE_BSS; } else { /* direct value can be compared for anything */ symtype = SYMTYPE_ALL; } if (bc_value->dsp_space) { if (!Symbols_GetDspAddress(symtype, name, &addr)) { EXITFUNC(("-> false (DSP)\n")); return false; } /* all DSP memory values are 24-bits */ bc_value->bits = 24; bc_value->value.number = addr; bc_value->valuetype = VALUE_TYPE_NUMBER; EXITFUNC(("-> true (DSP)\n")); return true; } if (!Symbols_GetCpuAddress(symtype, name, &addr)) { EXITFUNC(("-> false (CPU)\n")); return false; } if (addr & 1) { /* only bytes can be at odd addresses */ bc_value->bits = 8; } else { bc_value->bits = 32; } bc_value->value.number = addr; bc_value->valuetype = VALUE_TYPE_NUMBER; EXITFUNC(("-> true (CPU)\n")); return true; } /** * Helper function to get CPU PC register value with static inline as Uint32 */ static Uint32 GetCpuPC(void) { return M68000_GetPC(); } /** * Helper function to get CPU SR register value with static inline as Uint32 */ static Uint32 GetCpuSR(void) { return M68000_GetSR(); } /** * If given string is register name (for DSP or CPU), set bc_value * fields accordingly and return true, otherwise return false. */ static bool BreakCond_ParseRegister(const char *regname, bc_value_t *bc_value) { int regsize; ENTERFUNC(("BreakCond_ParseRegister('%s')\n", regname)); if (bc_value->dsp_space) { regsize = DSP_GetRegisterAddress(regname, &(bc_value->value.reg32), &(bc_value->mask)); if (regsize) { if (bc_value->is_indirect && toupper((unsigned char)regname[0]) != 'R') { fprintf(stderr, "ERROR: only R0-R7 DSP registers can be used for indirect addressing!\n"); EXITFUNC(("-> false (DSP)\n")); return false; } /* all DSP memory values are 24-bits */ bc_value->bits = 24; bc_value->valuetype = regsize; EXITFUNC(("-> true (DSP)\n")); return true; } EXITFUNC(("-> false (DSP)\n")); return false; } regsize = DebugCpu_GetRegisterAddress(regname, &(bc_value->value.reg32)); if (regsize) { bc_value->bits = regsize; /* valuetypes for registers are 16 & 32 */ bc_value->valuetype = regsize; EXITFUNC(("-> true (CPU)\n")); return true; } /* Exact UAE core 32-bit PC & 16-bit SR register values * can be gotten only through UAE accessors, not directly */ if (strcasecmp(regname, "PC") == 0) { bc_value->bits = 32; bc_value->value.func32 = GetCpuPC; bc_value->valuetype = VALUE_TYPE_FUNCTION32; EXITFUNC(("-> true (CPU)\n")); return true; } if (strcasecmp(regname, "SR") == 0) { bc_value->bits = 16; bc_value->value.func32 = GetCpuSR; bc_value->valuetype = VALUE_TYPE_FUNCTION32; EXITFUNC(("-> true (CPU)\n")); return true; } EXITFUNC(("-> false (CPU)\n")); return false; } /** * If given address is valid (for DSP or CPU), return true. */ static bool BreakCond_CheckAddress(bc_value_t *bc_value) { Uint32 addr = bc_value->value.number; int size = bc_value->bits >> 8; ENTERFUNC(("BreakCond_CheckAddress(%x)\n", addr)); if (bc_value->dsp_space) { if (addr+size > 0xFFFF) { EXITFUNC(("-> false (DSP)\n")); return false; } EXITFUNC(("-> true (DSP)\n")); return true; } if (!STMemory_CheckAreaType(addr, size, ABFLAG_RAM | ABFLAG_ROM | ABFLAG_IO)) { EXITFUNC(("-> false (CPU)\n")); return false; } EXITFUNC(("-> true (CPU)\n")); return true; } /** * Check for and parse a condition value address space/width modifier. * Modify pstate according to parsing (arg index and error string). * Return false for error and true for no or successfully parsed modifier. */ static bool BreakCond_ParseAddressModifier(parser_state_t *pstate, bc_value_t *bc_value) { char mode; ENTERFUNC(("BreakCond_ParseAddressModifier()\n")); if (pstate->arg+2 > pstate->argc || strcmp(pstate->argv[pstate->arg], ".") != 0) { if (bc_value->dsp_space && bc_value->is_indirect) { pstate->error = "DSP memory addresses need to specify address space"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } EXITFUNC(("arg:%d -> true (missing)\n", pstate->arg)); return true; } if (!bc_value->is_indirect) { pstate->error = "space/width modifier makes sense only for an address (register)"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } pstate->arg++; if (bc_value->dsp_space) { switch (pstate->argv[pstate->arg][0]) { case 'p': case 'x': case 'y': mode = toupper((unsigned char)pstate->argv[pstate->arg][0]); break; default: pstate->error = "invalid address space modifier"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } else { switch (pstate->argv[pstate->arg][0]) { case 'l': mode = 32; break; case 'w': mode = 16; break; case 'b': mode = 8; break; default: pstate->error = "invalid address width modifier"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } if (pstate->argv[pstate->arg][1]) { pstate->error = "invalid address space/width modifier"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } if (bc_value->dsp_space) { bc_value->dsp_space = mode; EXITFUNC(("arg:%d -> space:%c, true\n", pstate->arg, mode)); } else { bc_value->bits = mode; EXITFUNC(("arg:%d -> width:%d, true\n", pstate->arg, mode)); } pstate->arg++; return true; } /** * Check for and parse a condition value mask. * Modify pstate according to parsing (arg index and error string). * Return false for error and true for no or successfully parsed modifier. */ static bool BreakCond_ParseMaskModifier(parser_state_t *pstate, bc_value_t *bc_value) { ENTERFUNC(("BreakCond_ParseMaskModifier()\n")); if (pstate->arg+2 > pstate->argc || strcmp(pstate->argv[pstate->arg], "&") != 0) { EXITFUNC(("arg:%d -> true (missing)\n", pstate->arg)); return true; } if (bc_value->valuetype == VALUE_TYPE_NUMBER && !bc_value->is_indirect) { fprintf(stderr, "WARNING: plain numbers shouldn't need masks.\n"); } pstate->arg++; if (!Eval_Number(pstate->argv[pstate->arg], &(bc_value->mask))) { pstate->error = "invalid dec/hex/bin value"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } if (bc_value->mask == 0 || (bc_value->valuetype == VALUE_TYPE_NUMBER && !bc_value->is_indirect && bc_value->value.number && !(bc_value->value.number & bc_value->mask))) { pstate->error = "mask zeroes value"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } EXITFUNC(("arg:%d -> true (%x)\n", pstate->arg, bc_value->mask)); pstate->arg++; return true; } /** * Parse a breakpoint condition value. * Modify pstate according to parsing (arg index and error string). * Return true for success and false for error. */ static bool BreakCond_ParseValue(parser_state_t *pstate, bc_value_t *bc_value) { const char *str; int skip = 1; ENTERFUNC(("BreakCond_Value()\n")); if (pstate->arg >= pstate->argc) { pstate->error = "value missing"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } /* parse indirection */ if (pstate->arg+3 <= pstate->argc) { if (strcmp(pstate->argv[pstate->arg+0], "(") == 0 && strcmp(pstate->argv[pstate->arg+2], ")") == 0) { bc_value->is_indirect = true; pstate->arg++; skip = 2; } } str = pstate->argv[pstate->arg]; if (isalpha((unsigned char)*str) || *str == '_') { /* parse direct or indirect variable/register/symbol name */ if (bc_value->is_indirect) { /* a valid register or data symbol name? */ if (!BreakCond_ParseRegister(str, bc_value) && !BreakCond_ParseSymbol(str, bc_value)) { pstate->error = "invalid register/symbol name for indirection"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } else { /* a valid Hatari variable or register name? * variables cannot be used for ST memory indirection. */ if (!BreakCond_ParseVariable(str, bc_value) && !BreakCond_ParseRegister(str, bc_value) && !BreakCond_ParseSymbol(str, bc_value)) { pstate->error = "invalid variable/register/symbol name"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } } else { /* a number */ if (!Eval_Number(str, &(bc_value->value.number))) { pstate->error = "invalid dec/hex/bin value"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } } /* memory address (indirect value) -> OK as address? */ if (bc_value->is_indirect && bc_value->valuetype == VALUE_TYPE_NUMBER && !BreakCond_CheckAddress(bc_value)) { pstate->error = "invalid address"; EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } pstate->arg += skip; /* parse modifiers */ if (!BreakCond_ParseAddressModifier(pstate, bc_value)) { EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } if (!BreakCond_ParseMaskModifier(pstate, bc_value)) { EXITFUNC(("arg:%d -> false\n", pstate->arg)); return false; } EXITFUNC(("arg:%d -> true (%s value)\n", pstate->arg, (bc_value->is_indirect ? "indirect" : "direct"))); return true; } /** * Parse a breakpoint comparison character. * Modify pstate according to parsing (arg index and error string). * Return the character or nil for an error. */ static char BreakCond_ParseComparison(parser_state_t *pstate) { const char *comparison; ENTERFUNC(("BreakCond_ParseComparison(), arg:%d\n", pstate->arg)); if (pstate->arg >= pstate->argc) { pstate->error = "breakpoint comparison missing"; EXITFUNC(("-> false\n")); return false; } comparison = pstate->argv[pstate->arg]; switch (comparison[0]) { case '<': case '>': case '=': case '!': break; default: pstate->error = "invalid comparison character"; EXITFUNC(("-> false\n")); return false; } if (comparison[1]) { pstate->error = "trailing comparison character(s)"; EXITFUNC(("-> false\n")); return false; } pstate->arg++; if (pstate->arg >= pstate->argc) { pstate->error = "right side missing"; EXITFUNC(("-> false\n")); return false; } EXITFUNC(("-> '%c'\n", *comparison)); return *comparison; } /** * If no value, use the other value, if that also missing, use default */ static void BreakCond_InheritDefault(Uint32 *value1, Uint32 value2, Uint32 defvalue) { if (!*value1) { if (value2) { *value1 = value2; } else { *value1 = defvalue; } } } /** * Check & ensure that the masks and address sizes are sane * and allow comparison with the other side. * If yes, return true, otherwise false. */ static bool BreakCond_CrossCheckValues(parser_state_t *pstate, bc_value_t *bc_value1, bc_value_t *bc_value2) { Uint32 mask1, mask2, defbits; ENTERFUNC(("BreakCond_CrossCheckValues()\n")); /* make sure there're valid bit widths and that masks have some value */ if (bc_value1->dsp_space) { defbits = 24; } else { defbits = 32; } BreakCond_InheritDefault(&(bc_value1->bits), bc_value2->bits, defbits); BreakCond_InheritDefault(&(bc_value2->bits), bc_value1->bits, defbits); BreakCond_InheritDefault(&(bc_value1->mask), bc_value2->mask, BITMASK(bc_value1->bits)); BreakCond_InheritDefault(&(bc_value2->mask), bc_value1->mask, BITMASK(bc_value2->bits)); /* check first value mask & bit width */ mask1 = BITMASK(bc_value1->bits) & bc_value1->mask; if (mask1 != bc_value1->mask) { fprintf(stderr, "WARNING: mask 0x%x doesn't fit into %d address/register bits.\n", bc_value1->mask, bc_value1->bits); } if (!bc_value1->dsp_space && bc_value1->is_indirect && (bc_value1->value.number & 1) && bc_value1->bits > 8) { fprintf(stderr, "WARNING: odd CPU address 0x%x given without using byte (.b) width.\n", bc_value1->value.number); } /* cross-check both values masks */ mask2 = BITMASK(bc_value2->bits) & bc_value2->mask; if ((mask1 & mask2) == 0) { pstate->error = "values masks cancel each other"; EXITFUNC(("-> false\n")); return false; } if (bc_value2->is_indirect || bc_value2->value.number == 0 || bc_value2->valuetype != VALUE_TYPE_NUMBER) { EXITFUNC(("-> true (no problematic direct types)\n")); return true; } if ((bc_value2->value.number & mask1) != bc_value2->value.number) { pstate->error = "number doesn't fit the other side address width&mask"; EXITFUNC(("-> false\n")); return false; } EXITFUNC(("-> true\n")); return true; } /** * Parse given breakpoint conditions and append them to breakpoints. * Modify pstate according to parsing (arg index and error string). * Return number of added conditions or zero for failure. */ static int BreakCond_ParseCondition(parser_state_t *pstate, bool bForDsp, bc_breakpoint_t *bp, int ccount) { bc_condition_t condition; ENTERFUNC(("BreakCond_ParseCondition(...)\n")); /* setup condition */ memset(&condition, 0, sizeof(bc_condition_t)); if (bForDsp) { /* used also for checking whether value is for DSP */ condition.lvalue.dsp_space = BC_DEFAULT_DSP_SPACE; condition.rvalue.dsp_space = BC_DEFAULT_DSP_SPACE; } /* parse condition */ if (!BreakCond_ParseValue(pstate, &(condition.lvalue))) { EXITFUNC(("-> 0\n")); return 0; } condition.comparison = BreakCond_ParseComparison(pstate); if (!condition.comparison) { EXITFUNC(("-> 0\n")); return 0; } if (!BreakCond_ParseValue(pstate, &(condition.rvalue))) { EXITFUNC(("-> 0\n")); return 0; } if (!(BreakCond_CrossCheckValues(pstate, &(condition.lvalue), &(condition.rvalue)) && BreakCond_CrossCheckValues(pstate, &(condition.rvalue), &(condition.lvalue)))) { EXITFUNC(("-> 0\n")); return 0; } /* copy new condition */ ccount += 1; bp->conditions = realloc(bp->conditions, sizeof(bc_condition_t)*(ccount)); if (!bp->conditions) { pstate->error = "failed to allocate space for breakpoint condition"; EXITFUNC(("-> 0\n")); return 0; } bp->conditions[ccount-1] = condition; /* continue with next condition? */ if (pstate->arg == pstate->argc) { EXITFUNC(("-> %d conditions (all args parsed)\n", ccount)); return ccount; } if (strcmp(pstate->argv[pstate->arg], "&&") != 0) { pstate->error = "trailing content for breakpoint condition"; EXITFUNC(("-> 0\n")); return 0; } pstate->arg++; /* recurse conditions parsing */ ccount = BreakCond_ParseCondition(pstate, bForDsp, bp, ccount); if (!ccount) { EXITFUNC(("-> 0\n")); return 0; } EXITFUNC(("-> %d conditions (recursed)\n", ccount)); return ccount; } /** * Tokenize given breakpoint expression to given parser struct. * Return normalized expression string that corresponds to tokenization * or NULL on error. On error, pstate->error contains the error message * and pstate->arg index to invalid character (instead of to token like * after parsing). */ static char *BreakCond_TokenizeExpression(const char *expression, parser_state_t *pstate) { char separator[] = { '=', '!', '<', '>', /* comparison operators */ '(', ')', '.', '&', /* other separators */ '\0' /* terminator */ }; bool is_separated, has_comparison; char sep, *dst, *normalized; const char *src; int i, tokens; memset(pstate, 0, sizeof(parser_state_t)); /* _minimum_ safe size for normalized expression is 2x+1 */ normalized = malloc(2*strlen(expression)+1); if (!normalized) { pstate->error = "alloc failed"; return NULL; } /* check characters & normalize string */ dst = normalized; is_separated = false; has_comparison = false; for (src = expression; *src; src++) { /* discard white space in source */ if (isspace((unsigned char)*src)) { continue; } /* separate tokens with single space in destination */ for (i = 0; (sep = separator[i]); i++) { if (*src == sep) { if (dst > normalized) { /* don't separate boolean AND '&&' */ if (*src == '&' && *(src-1) == '&') { dst--; } else { if (!is_separated) { *dst++ = ' '; } } } *dst++ = *src; *dst++ = ' '; is_separated = true; if (i < 4) { has_comparison = true; } break; } } /* validate & copy other characters */ if (!sep) { /* variable/register/symbol or number prefix? */ if (!(isalnum((unsigned char)*src) || *src == '_' || *src == '$' || *src == '#' || *src == '%')) { pstate->error = "invalid character"; pstate->arg = src-expression; free(normalized); return NULL; } *dst++ = *src; is_separated = false; } } if (is_separated) { dst--; /* no trailing space */ } *dst = '\0'; if (!has_comparison) { pstate->error = "condition comparison missing"; pstate->arg = strlen(expression)/2; free(normalized); return NULL; } /* allocate exact space for tokenized string array + strings */ tokens = 1; for (dst = normalized; *dst; dst++) { if (*dst == ' ') { tokens++; } } pstate->argv = malloc(tokens*sizeof(char*)+strlen(normalized)+1); if (!pstate->argv) { pstate->error = "alloc failed"; free(normalized); return NULL; } /* and copy/tokenize... */ dst = (char*)(pstate->argv) + tokens*sizeof(char*); strcpy(dst, normalized); pstate->argv[0] = strtok(dst, " "); for (i = 1; (dst = strtok(NULL, " ")); i++) { pstate->argv[i] = dst; } assert(i == tokens); pstate->argc = tokens; #if DEBUG fprintf(stderr, "args->"); for (i = 0; i < tokens; i++) { fprintf(stderr, " %d: %s,", i, pstate->argv[i]); } fprintf(stderr, "\n"); #endif return normalized; } /** * Select corrent breakpoints struct and provide name for it. * Make sure there's always space for at least one additional breakpoint. * Return pointer to the breakpoints struct */ static bc_breakpoints_t* BreakCond_GetListInfo(bool bForDsp) { bc_breakpoints_t *bps; if (bForDsp) { bps = &DspBreakPoints; } else { bps = &CpuBreakPoints; } /* allocate (more) space for breakpoints when needed */ if (bps->count + 1 >= bps->allocated) { if (!bps->allocated) { /* initial count of available breakpoints */ bps->allocated = 16; } else { bps->allocated *= 2; } if (bps->delayed_change) { if(bps->breakpoint2delete) { /* getting second re-alloc within same breakpoint handler is really * unlikely, this would require adding dozens of new breakpoints. */ fprintf(stderr, "ERROR: too many new breakpoints added within single breakpoint hit!\n"); abort(); } bps->breakpoint2delete = bps->breakpoint; bps->breakpoint = malloc(bps->allocated * sizeof(bc_breakpoint_t)); } else { bps->breakpoint = realloc(bps->breakpoint, bps->allocated * sizeof(bc_breakpoint_t)); } assert(bps->breakpoint); } return bps; } /** * Check whether any of the breakpoint conditions is such that it's * intended for tracking given value changes (inequality comparison * on identical values) or for retrieving the current value to break * on next value change (other comparisons on identical values). * * On former case, mark it for tracking, on other cases, just * retrieve the value. */ static void BreakCond_CheckTracking(bc_breakpoint_t *bp) { bc_condition_t *condition; bool track = false; Uint32 value; int i; condition = bp->conditions; for (i = 0; i < bp->ccount; condition++, i++) { if (memcmp(&(condition->lvalue), &(condition->rvalue), sizeof(bc_value_t)) == 0) { /* set current value to right side */ value = BreakCond_GetValue(&(condition->rvalue)); condition->rvalue.value.number = value; condition->rvalue.valuetype = VALUE_TYPE_NUMBER; condition->rvalue.is_indirect = false; /* track those changes */ if (condition->comparison != '=') { condition->track = true; track = true; } else { fprintf(stderr, "\t%d. condition: %c $%x\n", i+1, condition->comparison, value); } } } if (track) { fprintf(stderr, "-> Track value changes, show value(s) when matched.\n"); } } /** * Parse given breakpoint expression and store it. * Return true for success and false for failure. */ static bool BreakCond_Parse(const char *expression, bc_options_t *options, bool bForDsp) { parser_state_t pstate; bc_breakpoints_t *bps; bc_breakpoint_t *bp; char *normalized; int ccount; bps = BreakCond_GetListInfo(bForDsp); bp = bps->breakpoint + bps->count; memset(bp, 0, sizeof(bc_breakpoint_t)); normalized = BreakCond_TokenizeExpression(expression, &pstate); if (normalized) { bp->expression = normalized; ccount = BreakCond_ParseCondition(&pstate, bForDsp, bp, 0); /* fail? */ if (!ccount) { bp->expression = NULL; if (bp->conditions) { /* free what was allocated by ParseCondition */ free(bp->conditions); bp->conditions = NULL; } } bp->ccount = ccount; } else { ccount = 0; } if (pstate.argv) { free(pstate.argv); } if (ccount > 0) { bps->count++; if (!options->quiet) { fprintf(stderr, "%s condition breakpoint %d with %d condition(s) added:\n\t%s\n", bps->name, bps->count, ccount, bp->expression); if (options->skip) { fprintf(stderr, "-> Break only on every %d hit.\n", options->skip); } if (options->once) { fprintf(stderr, "-> Once, delete after breaking.\n"); } if (options->trace) { fprintf(stderr, "-> Trace instead of breaking, but show still hits.\n"); if (options->lock) { fprintf(stderr, "-> Show also info selected with lock command.\n"); } if (options->noinit) { fprintf(stderr, "-> Skip debugger inits on hit.\n"); } } if (options->filename) { fprintf(stderr, "-> Execute debugger commands from '%s' file on hit.\n", options->filename); } } BreakCond_CheckTracking(bp); bp->options.quiet = options->quiet; bp->options.skip = options->skip; bp->options.once = options->once; bp->options.trace = options->trace; bp->options.lock = options->lock; bp->options.noinit = options->noinit; if (options->filename) { bp->options.filename = strdup(options->filename); } } else { if (normalized) { int offset, i = 0; char *s = normalized; while (*s && i < pstate.arg) { if (*s++ == ' ') { i++; } } offset = s - normalized; /* show tokenized string and point out * the token where the error was encountered */ fprintf(stderr, "ERROR in tokenized string:\n'%s'\n%*c-%s\n", normalized, offset+2, '^', pstate.error); free(normalized); } else { /* show original string and point out the character * where the error was encountered */ fprintf(stderr, "ERROR in parsed string:\n'%s'\n%*c-%s\n", expression, pstate.arg+2, '^', pstate.error); } } return (ccount > 0); } /** * print single breakpoint */ static void BreakCond_Print(bc_breakpoint_t *bp) { fprintf(stderr, "\t%s", bp->expression); if (bp->options.skip) { fprintf(stderr, " :%d", bp->options.skip); } if (bp->options.once) { fprintf(stderr, " :once"); } if (bp->options.trace) { if (bp->options.lock) { fprintf(stderr, " :lock"); } else { fprintf(stderr, " :trace"); } if (bp->options.noinit) { fprintf(stderr, " :noinit"); } } if (bp->options.filename) { fprintf(stderr, " :file %s", bp->options.filename); } if (bp->options.deleted) { fprintf(stderr, " (deleted)"); } fprintf(stderr, "\n"); } /** * List condition breakpoints */ static void BreakCond_List(bc_breakpoints_t *bps) { bc_breakpoint_t *bp; int i; if (!bps->count) { fprintf(stderr, "No conditional %s breakpoints.\n", bps->name); return; } fprintf(stderr, "%d conditional %s breakpoints:\n", bps->count, bps->name); bp = bps->breakpoint; for (i = 1; i <= bps->count; bp++, i++) { fprintf(stderr, "%4d:", i); BreakCond_Print(bp); } } /** * Remove condition breakpoint at given position */ static bool BreakCond_Remove(bc_breakpoints_t *bps, int position) { bc_breakpoint_t *bp; if (!bps->count) { fprintf(stderr, "No (more) %s breakpoints to remove.\n", bps->name); return false; } if (position < 1 || position > bps->count) { fprintf(stderr, "ERROR: No such %s breakpoint.\n", bps->name); return false; } bp = bps->breakpoint + (position - 1); if (bps->delayed_change) { bp->options.deleted = true; return true; } if (!bp->options.quiet) { fprintf(stderr, "Removed %s breakpoint %d:\n", bps->name, position); BreakCond_Print(bp); } free(bp->expression); free(bp->conditions); bp->expression = NULL; bp->conditions = NULL; if (bp->options.filename) { free(bp->options.filename); } if (position < bps->count) { memmove(bp, bp + 1, (bps->count - position) * sizeof(bc_breakpoint_t)); } bps->count--; return true; } /** * Remove all conditional breakpoints */ static void BreakCond_RemoveAll(bc_breakpoints_t *bps) { bool removed; int i; for (i = bps->count; i > 0; i--) { removed = BreakCond_Remove(bps, i); ASSERT_VARIABLE(removed); } fprintf(stderr, "%s breakpoints: %d\n", bps->name, bps->count); } /** * Do delayed actions (remove breakpoints and old array alloc) * * If those removals affect the triggered breakpoint index, update it. * * Return updated breakpoint index. */ static int BreakCond_DoDelayedActions(bc_breakpoints_t *bps, int triggered) { bc_options_t *options; bool removed; int i; assert(!bps->delayed_change); if (bps->breakpoint2delete) { free(bps->breakpoint2delete); bps->breakpoint2delete = NULL; } for (i = bps->count; i > 0; i--) { options = &(bps->breakpoint[i-1].options); if (options->deleted) { options->deleted = false; removed = BreakCond_Remove(bps, i); ASSERT_VARIABLE(removed); if (triggered >= i) { triggered--; } } } return triggered; } /** * Return true if given CPU breakpoint has given CPU expression. * Used by the test code. */ int BreakCond_MatchCpuExpression(int position, const char *expression) { if (position < 1 || position > CpuBreakPoints.count) { return false; } if (strcmp(expression, CpuBreakPoints.breakpoint[position-1].expression)) { return false; } return true; } /** * help */ static void BreakCond_Help(void) { Uint32 value; int i; fputs( " condition = [.mode] [& ] [.mode]\n" "\n" " where:\n" " value = [(] [)]\n" " number/mask = [#|$|%]\n" " comparison = '<' | '>' | '=' | '!'\n" " addressing mode (width) = 'b' | 'w' | 'l'\n" " addressing mode (space) = 'p' | 'x' | 'y'\n" "\n" " If the value is in parenthesis like in '($ff820)' or '(a0)', then\n" " the used value will be read from the memory address pointed by it.\n" "\n" " If the parsed value expressions on both sides of it are exactly\n" " the same, right side is replaced with its current value. For\n" " inequality ('!') comparison, the breakpoint will additionally track\n" " all further changes for the given address/register expression value.\n" " (This is useful for tracking register and memory value changes.)\n" "\n" " M68k addresses can have byte (b), word (w) or long (l, default) width.\n" " DSP addresses belong to different address spaces: P, X or Y. Note that\n" " on DSP only R0-R7 registers can be used for memory addressing.\n" "\n" " Valid Hatari variable names (and their current values) are:\n", stderr); for (i = 0; i < ARRAYSIZE(hatari_vars); i++) { const var_addr_t *hvar = hatari_vars + i; switch (hvar->vtype) { case VALUE_TYPE_FUNCTION32: value = ((Uint32(*)(void))(hvar->addr))(); break; case VALUE_TYPE_VAR32: value = *(hvar->addr); break; default: fprintf(stderr, "ERROR: variable '%s' has unsupported type '%d'\n", hvar->name, hvar->vtype); continue; } fprintf(stderr, " - %s ($%x)", hvar->name, value); if (hvar->constraints) { fprintf(stderr, ", %s\n", hvar->constraints); } else { fprintf(stderr, "\n"); } } fputs( "\n" " Examples:\n" " pc = $64543 && ($ff820).w & 3 = (a0) && d0 = %1100\n" " ($ffff9202).w ! ($ffff9202).w :trace\n" " (r0).x = 1 && (r0).y = 2\n", stderr); } /* ------------- breakpoint condition parsing, public API ------------ */ const char BreakCond_Description[] = " [&& ...] [: