--- hatari/src/debug/breakcond.c 2019/04/09 08:48:37 1.1.1.1 +++ hatari/src/debug/breakcond.c 2019/04/09 08:55:33 1.1.1.7 @@ -1,10 +1,10 @@ /* Hatari - breakcond.c - Copyright (c) 2009-2010 by Eero Tamminen + Copyright (c) 2009-2012 by Eero Tamminen - This file is distributed under the GNU Public License, version 2 or at - your option any later version. Read the file gpl.txt for details. + 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 @@ -17,18 +17,25 @@ const char BreakCond_fileid[] = "Hatari #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 */ @@ -37,12 +44,9 @@ const char BreakCond_fileid[] = "Hatari /* needs to go through long long to handle x=32 */ #define BITMASK(x) ((Uint32)(((unsigned long long)1<<(x))-1)) -#define BC_MAX_CONDITION_BREAKPOINTS 16 -#define BC_MAX_CONDITIONS_PER_BREAKPOINT 4 - #define BC_DEFAULT_DSP_SPACE 'P' -enum { +typedef enum { /* plain number */ VALUE_TYPE_NUMBER = 0, @@ -55,7 +59,7 @@ enum { /* size must match register size used in BreakCond_ParseRegister() */ VALUE_TYPE_REG16 = 16, VALUE_TYPE_REG32 = 32 -} typedef value_t; +} value_t; static inline bool is_register_type(value_t vtype) { /* type used for CPU/DSP registers */ @@ -85,23 +89,47 @@ typedef struct { } bc_condition_t; typedef struct { - char *expression; - bc_condition_t conditions[BC_MAX_CONDITIONS_PER_BREAKPOINT]; - int ccount; /* condition count */ - int hits; /* how many times breakpoint hit */ + 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; -static bc_breakpoint_t BreakPointsCpu[BC_MAX_CONDITION_BREAKPOINTS]; -static bc_breakpoint_t BreakPointsDsp[BC_MAX_CONDITION_BREAKPOINTS]; -static int BreakPointCpuCount; -static int BreakPointDspCount; +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 bool BreakCond_Remove(int position, bool bForDsp); +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); @@ -114,10 +142,12 @@ bool BreakCond_Save(const char *filename FILE *fp; int i; - if (!(BreakPointCpuCount || BreakPointDspCount)) { - if (remove(filename)) { - perror("ERROR"); - return false; + if (!(CpuBreakPoints.count || DspBreakPoints.count)) { + if (File_Exists(filename)) { + if (remove(filename)) { + perror("ERROR"); + return false; + } } return true; } @@ -129,11 +159,11 @@ bool BreakCond_Save(const char *filename return false; } /* save conditional breakpoints as debugger input file */ - for (i = 0; i < BreakPointCpuCount; i++) { - fprintf(fp, "b %s\n", BreakPointsCpu[i].expression); + for (i = 0; i < CpuBreakPoints.count; i++) { + fprintf(fp, "b %s\n", CpuBreakPoints.breakpoint[i].expression); } - for (i = 0; i < BreakPointDspCount; i++) { - fprintf(fp, "db %s\n", BreakPointsDsp[i].expression); + for (i = 0; i < DspBreakPoints.count; i++) { + fprintf(fp, "db %s\n", DspBreakPoints.breakpoint[i].expression); } fclose(fp); return true; @@ -176,11 +206,6 @@ static Uint32 BreakCond_ReadDspMemory(Ui */ static Uint32 BreakCond_ReadSTMemory(Uint32 addr, const bc_value_t *bc_value) { - /* Mask to a 24 bit address. With this e.g. $ffff820a is also - * recognized as IO mem $ff820a (which is the same in the 68000). - */ - addr &= 0x00ffffff; - switch (bc_value->bits) { case 32: return STMemory_ReadLong(addr); @@ -232,9 +257,31 @@ static Uint32 BreakCond_GetValue(const b /** + * 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(const bc_condition_t *condition, int count) +static bool BreakCond_MatchConditions(bc_condition_t *condition, int count) { Uint32 lvalue, rvalue; bool hit = false; @@ -263,9 +310,12 @@ static bool BreakCond_MatchConditions(co condition->comparison); abort(); } - if (!hit) { + if (likely(!hit)) { return false; } + if (condition->track) { + BreakCond_UpdateTracked(condition, lvalue); + } } /* all conditions matched */ return true; @@ -273,99 +323,104 @@ static bool BreakCond_MatchConditions(co /** - * Show values for the tracked breakpoint conditions + * Show all breakpoints which conditions matched and return which matched + * @return index to last matching (non-tracing) breakpoint, + * or zero if none matched */ -static void BreakCond_ShowTracked(bc_condition_t *condition, int count) +static int BreakCond_MatchBreakPoints(bc_breakpoints_t *bps) { - Uint32 addr, value; - char sep; - int i; - - sep = ' '; - for (i = 0; i < count; condition++, i++) { - if (!condition->track) { - continue; - } + bc_breakpoint_t *bp; + bool changes = false; + int i, ret = 0; - /* get the new value in address */ - value = BreakCond_GetValue(&(condition->lvalue)); - /* 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, "%c $%x = $%x", sep, addr, value); - } else { - /* register tms. */ - fprintf(stderr, "%c $%x", sep, value); - } - sep = ','; - } - fprintf(stderr, "\n"); -} + /* 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++) { -/** - * Return which of the given condition breakpoints match - * or zero if none matched - */ -static int BreakCond_MatchBreakPoints(bc_breakpoint_t *bp, int count, const char *name) -{ - int i; - - for (i = 0; i < count; bp++, i++) { if (BreakCond_MatchConditions(bp->conditions, bp->ccount)) { - BreakCond_ShowTracked(bp->conditions, bp->ccount); bp->hits++; - if (bp->skip && (bp->hits % bp->skip) == 0) { - return 0; + if (bp->options.skip) { + if (bp->hits % bp->options.skip) { + /* check next */ + continue; + } } - fprintf(stderr, "%d. %s breakpoint condition(s) matched %d times.\n", - i+1, name, bp->hits); - if (bp->trace) { - return 0; + if (!bp->options.quiet) { + fprintf(stderr, "%d. %s breakpoint condition(s) matched %d times.\n", + i+1, bps->name, bp->hits); + BreakCond_Print(bp); } - BreakCond_Print(bp); - if (bp->once) { - BreakCond_Remove(i+1, (bp-i == BreakPointsDsp)); + 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; } - /* indexes for BreakCond_Remove() start from 1 */ - return i + 1; + /* continue checking breakpoints to make sure all relevant actions get performed */ } } - return 0; + 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 an error. + * Return matched CPU breakpoint index or zero for no hits. */ int BreakCond_MatchCpu(void) { - return BreakCond_MatchBreakPoints(BreakPointsCpu, BreakPointCpuCount, "CPU"); + return BreakCond_MatchBreakPoints(&CpuBreakPoints); } /** - * Return matched DSP breakpoint index or zero for an error. + * Return matched DSP breakpoint index or zero for no hits. */ int BreakCond_MatchDsp(void) { - return BreakCond_MatchBreakPoints(BreakPointsDsp, BreakPointDspCount, "DSP"); + return BreakCond_MatchBreakPoints(&DspBreakPoints); } /** - * Return number of condition breakpoints + * Return number of CPU condition breakpoints */ -int BreakCond_BreakPointCount(bool bForDsp) +int BreakCond_CpuBreakPointCount(void) { - if (bForDsp) { - return BreakPointDspCount; - } else { - return BreakPointCpuCount; - } + return CpuBreakPoints.count; +} + +/** + * Return number of DSP condition breakpoints + */ +int BreakCond_DspBreakPointCount(void) +{ + return DspBreakPoints.count; } @@ -403,12 +458,126 @@ static Uint32 GetFrameCycles(void) 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" }, - { "VBL", (Uint32*)&nVBLs, VALUE_TYPE_VAR32, sizeof(nVBLs)*8, NULL } + { "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" } }; @@ -455,6 +624,7 @@ char *BreakCond_MatchDspVariable(const c */ 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; @@ -464,11 +634,12 @@ static bool BreakCond_ParseVariable(cons r = ARRAYSIZE(hatari_vars) - 1; do { m = (l+r) >> 1; - dir = strcasecmp(name, hatari_vars[m].name); + hvar = hatari_vars + m; + dir = strcasecmp(name, hvar->name); if (dir == 0) { - bc_value->value.reg32 = hatari_vars[m].addr; - bc_value->valuetype = hatari_vars[m].vtype; - bc_value->bits = hatari_vars[m].bits; + 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; @@ -483,6 +654,22 @@ static bool BreakCond_ParseVariable(cons 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 @@ -560,7 +747,8 @@ static bool BreakCond_ParseRegister(cons &(bc_value->value.reg32), &(bc_value->mask)); if (regsize) { - if (bc_value->is_indirect && toupper(regname[0]) != 'R') { + 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; @@ -583,7 +771,7 @@ static bool BreakCond_ParseRegister(cons return true; } /* Exact UAE core 32-bit PC & 16-bit SR register values - * can be gotten only through AUE accessors, not directly + * can be gotten only through UAE accessors, not directly */ if (strcasecmp(regname, "PC") == 0) { bc_value->bits = 32; @@ -608,28 +796,19 @@ static bool BreakCond_ParseRegister(cons */ static bool BreakCond_CheckAddress(bc_value_t *bc_value) { - Uint32 highbyte, bit23, addr = bc_value->value.number; + 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 > 0xFFFF) { + if (addr+size > 0xFFFF) { EXITFUNC(("-> false (DSP)\n")); return false; } EXITFUNC(("-> true (DSP)\n")); return true; } - - bit23 = (addr >> 23) & 1; - highbyte = (addr >> 24) & 0xff; - if ((bit23 == 0 && highbyte != 0) || - (bit23 == 1 && highbyte != 0xff)) { - fprintf(stderr, "WARNING: address 0x%x 23th bit isn't extended to bits 24-31.\n", addr); - } - /* use a 24-bit address */ - addr &= 0x00ffffff; - if ((addr > STRamEnd && addr < 0xe00000) || - (addr >= 0xff0000 && addr < 0xff8000)) { + if (!STMemory_CheckAreaType(addr, size, ABFLAG_RAM | ABFLAG_ROM | ABFLAG_IO)) { EXITFUNC(("-> false (CPU)\n")); return false; } @@ -669,7 +848,7 @@ static bool BreakCond_ParseAddressModifi case 'p': case 'x': case 'y': - mode = toupper(pstate->argv[pstate->arg][0]); + mode = toupper((unsigned char)pstate->argv[pstate->arg][0]); break; default: pstate->error = "invalid address space modifier"; @@ -773,7 +952,7 @@ static bool BreakCond_ParseValue(parser_ } str = pstate->argv[pstate->arg]; - if (isalpha(*str) || *str == '_') { + 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? */ @@ -953,16 +1132,11 @@ static bool BreakCond_CrossCheckValues(p * Return number of added conditions or zero for failure. */ static int BreakCond_ParseCondition(parser_state_t *pstate, bool bForDsp, - bc_condition_t *conditions, int ccount) + bc_breakpoint_t *bp, int ccount) { bc_condition_t condition; ENTERFUNC(("BreakCond_ParseCondition(...)\n")); - if (ccount >= BC_MAX_CONDITIONS_PER_BREAKPOINT) { - pstate->error = "max number of conditions exceeded"; - EXITFUNC(("-> 0 (no conditions free)\n")); - return 0; - } /* setup condition */ memset(&condition, 0, sizeof(bc_condition_t)); @@ -991,12 +1165,19 @@ static int BreakCond_ParseCondition(pars EXITFUNC(("-> 0\n")); return 0; } - /* new condition */ - conditions[ccount++] = condition; + /* 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)\n", ccount-1)); + EXITFUNC(("-> %d conditions (all args parsed)\n", ccount)); return ccount; } if (strcmp(pstate->argv[pstate->arg], "&&") != 0) { @@ -1007,12 +1188,12 @@ static int BreakCond_ParseCondition(pars pstate->arg++; /* recurse conditions parsing */ - ccount = BreakCond_ParseCondition(pstate, bForDsp, conditions, ccount); + ccount = BreakCond_ParseCondition(pstate, bForDsp, bp, ccount); if (!ccount) { EXITFUNC(("-> 0\n")); return 0; } - EXITFUNC(("-> %d (conditions)\n", ccount-1)); + EXITFUNC(("-> %d conditions (recursed)\n", ccount)); return ccount; } @@ -1052,7 +1233,7 @@ static char *BreakCond_TokenizeExpressio has_comparison = false; for (src = expression; *src; src++) { /* discard white space in source */ - if (isspace(*src)) { + if (isspace((unsigned char)*src)) { continue; } /* separate tokens with single space in destination */ @@ -1080,7 +1261,7 @@ static char *BreakCond_TokenizeExpressio /* validate & copy other characters */ if (!sep) { /* variable/register/symbol or number prefix? */ - if (!(isalnum(*src) || *src == '_' || + if (!(isalnum((unsigned char)*src) || *src == '_' || *src == '$' || *src == '#' || *src == '%')) { pstate->error = "invalid character"; pstate->arg = src-expression; @@ -1137,23 +1318,42 @@ static char *BreakCond_TokenizeExpressio /** - * Helper to set corrent breakpoint list and type name to given variables. - * Return pointer to breakpoint list count + * 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 int* BreakCond_GetListInfo(bc_breakpoint_t **bp, - const char **name, bool bForDsp) +static bc_breakpoints_t* BreakCond_GetListInfo(bool bForDsp) { - int *bcount; + bc_breakpoints_t *bps; if (bForDsp) { - bcount = &BreakPointDspCount; - *bp = BreakPointsDsp; - *name = "DSP"; + bps = &DspBreakPoints; } else { - bcount = &BreakPointCpuCount; - *bp = BreakPointsCpu; - *name = "CPU"; + 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 bcount; + return bps; } @@ -1182,8 +1382,8 @@ static void BreakCond_CheckTracking(bc_b condition->rvalue.value.number = value; condition->rvalue.valuetype = VALUE_TYPE_NUMBER; condition->rvalue.is_indirect = false; - if (condition->comparison == '!') { - /* which changes will be traced */ + /* track those changes */ + if (condition->comparison != '=') { condition->track = true; track = true; } else { @@ -1202,28 +1402,32 @@ static void BreakCond_CheckTracking(bc_b * Parse given breakpoint expression and store it. * Return true for success and false for failure. */ -static bool BreakCond_Parse(const char *expression, bool bForDsp, bool trace, bool once, int skip) +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; - const char *name; char *normalized; - int *bcount; int ccount; - bcount = BreakCond_GetListInfo(&bp, &name, bForDsp); - if (*bcount >= BC_MAX_CONDITION_BREAKPOINTS) { - fprintf(stderr, "ERROR: no free %s condition breakpoints left.\n", name); - return false; - } - bp += *bcount; + 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->conditions, 0); + 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; @@ -1232,21 +1436,39 @@ static bool BreakCond_Parse(const char * free(pstate.argv); } if (ccount > 0) { - (*bcount)++; - fprintf(stderr, "%s condition breakpoint %d with %d condition(s) added:\n\t%s\n", - name, *bcount, ccount, bp->expression); + 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); - if (skip) { - fprintf(stderr, "-> Break only on every %d hit.\n", skip); - bp->skip = skip; - } - if (once) { - fprintf(stderr, "-> Once, delete after breaking.\n"); - bp->once = once; - } - if (trace) { - fprintf(stderr, "-> Trace instead of breaking, but show still hits.\n"); - bp->trace = trace; + + 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) { @@ -1264,7 +1486,6 @@ static bool BreakCond_Parse(const char * fprintf(stderr, "ERROR in tokenized string:\n'%s'\n%*c-%s\n", normalized, offset+2, '^', pstate.error); free(normalized); - bp->expression = NULL; } else { /* show original string and point out the character * where the error was encountered @@ -1283,14 +1504,27 @@ static bool BreakCond_Parse(const char * static void BreakCond_Print(bc_breakpoint_t *bp) { fprintf(stderr, "\t%s", bp->expression); - if (bp->skip) { - fprintf(stderr, " :%d", bp->skip); + if (bp->options.skip) { + fprintf(stderr, " :%d", bp->options.skip); } - if (bp->once) { + if (bp->options.once) { fprintf(stderr, " :once"); } - if (bp->trace) { - fprintf(stderr, " :trace"); + 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"); } @@ -1298,20 +1532,18 @@ static void BreakCond_Print(bc_breakpoin /** * List condition breakpoints */ -static void BreakCond_List(bool bForDsp) +static void BreakCond_List(bc_breakpoints_t *bps) { - const char *name; bc_breakpoint_t *bp; - int i, bcount; - - bcount = *BreakCond_GetListInfo(&bp, &name, bForDsp); - if (!bcount) { - fprintf(stderr, "No conditional %s breakpoints.\n", name); + int i; + + if (!bps->count) { + fprintf(stderr, "No conditional %s breakpoints.\n", bps->name); return; } - - fprintf(stderr, "%d conditional %s breakpoints:\n", bcount, name); - for (i = 1; i <= bcount; bp++, i++) { + 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); } @@ -1321,43 +1553,89 @@ static void BreakCond_List(bool bForDsp) /** * Remove condition breakpoint at given position */ -static bool BreakCond_Remove(int position, bool bForDsp) +static bool BreakCond_Remove(bc_breakpoints_t *bps, int position) { - const char *name; bc_breakpoint_t *bp; - int *bcount, offset; - - bcount = BreakCond_GetListInfo(&bp, &name, bForDsp); - if (!*bcount) { - fprintf(stderr, "No (more) breakpoints to remove.\n"); + + if (!bps->count) { + fprintf(stderr, "No (more) %s breakpoints to remove.\n", bps->name); return false; } - if (position < 1 || position > *bcount) { - fprintf(stderr, "ERROR: No such %s breakpoint.\n", name); + if (position < 1 || position > bps->count) { + fprintf(stderr, "ERROR: No such %s breakpoint.\n", bps->name); return false; } - offset = position - 1; - fprintf(stderr, "Removed %s breakpoint %d:\n", name, position); - BreakCond_Print(&(bp[offset])); - free(bp[offset].expression); - bp[offset].expression = NULL; + 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 (position < *bcount) { - memmove(bp+offset, bp+position, - (*bcount-position)*sizeof(bc_breakpoint_t)); + if (bp->options.filename) { + free(bp->options.filename); } - (*bcount)--; + + if (position < bps->count) { + memmove(bp, bp + 1, (bps->count - position) * sizeof(bc_breakpoint_t)); + } + bps->count--; return true; } /** - * Remove all condition breakpoints + * Remove all conditional breakpoints */ -static void BreakCond_RemoveAll(bool bForDsp) +static void BreakCond_RemoveAll(bc_breakpoints_t *bps) { - while (BreakCond_Remove(1, bForDsp)) - ; + 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; } @@ -1367,10 +1645,10 @@ static void BreakCond_RemoveAll(bool bFo */ int BreakCond_MatchCpuExpression(int position, const char *expression) { - if (position < 1 || position > BreakPointCpuCount) { + if (position < 1 || position > CpuBreakPoints.count) { return false; } - if (strcmp(expression, BreakPointsCpu[position-1].expression)) { + if (strcmp(expression, CpuBreakPoints.breakpoint[position-1].expression)) { return false; } return true; @@ -1385,26 +1663,23 @@ static void BreakCond_Help(void) Uint32 value; int i; fputs( -" breakpoint = [ && ... ] [option]\n" -" condition = [.mode] [& ] [.mode]\n" +" condition = [.mode] [& ] [.mode]\n" "\n" " where:\n" " value = [(] [)]\n" -" number = [#|$|%]\n" +" number/mask = [#|$|%]\n" " comparison = '<' | '>' | '=' | '!'\n" " addressing mode (width) = 'b' | 'w' | 'l'\n" " addressing mode (space) = 'p' | 'x' | 'y'\n" -" option = : | 'once' | 'trace'\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 value expressions on both sides of the comparison are exactly\n" -" the same, right side is replaced with its current value and for\n" -" inequality ('!') comparison, the breakpoint tracks all further changes\n" -" for the given address/register expression. 'trace' option for continuing\n" -" without breaking can be useful with this. 'once' option removes breakpoint\n" -" after hit and giving count as option will break only on every hit.\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" @@ -1412,21 +1687,22 @@ static void BreakCond_Help(void) "\n" " Valid Hatari variable names (and their current values) are:\n", stderr); for (i = 0; i < ARRAYSIZE(hatari_vars); i++) { - switch (hatari_vars[i].vtype) { + const var_addr_t *hvar = hatari_vars + i; + switch (hvar->vtype) { case VALUE_TYPE_FUNCTION32: - value = ((Uint32(*)(void))(hatari_vars[i].addr))(); + value = ((Uint32(*)(void))(hvar->addr))(); break; case VALUE_TYPE_VAR32: - value = *(hatari_vars[i].addr); + value = *(hvar->addr); break; default: fprintf(stderr, "ERROR: variable '%s' has unsupported type '%d'\n", - hatari_vars[i].name, hatari_vars[i].vtype); + hvar->name, hvar->vtype); continue; } - fprintf(stderr, " - %s (%d)", hatari_vars[i].name, value); - if (hatari_vars[i].constraints) { - fprintf(stderr, ", %s\n", hatari_vars[i].constraints); + fprintf(stderr, " - %s ($%x)", hvar->name, value); + if (hvar->constraints) { + fprintf(stderr, ", %s\n", hvar->constraints); } else { fprintf(stderr, "\n"); } @@ -1443,14 +1719,84 @@ static void BreakCond_Help(void) /* ------------- breakpoint condition parsing, public API ------------ */ const char BreakCond_Description[] = - "[ [:|once|trace] | | help | all ]\n" - "\tSet breakpoint with given , remove breakpoint with\n" - "\tgiven or list all breakpoints when no args are given.\n" - "\tAdding ':trace' to end of condition causes breakpoint match\n" - "\tjust to be printed, not break. Adding ':once' will delete\n" - "\tthe breakpoint after it's hit. Adding ':' will break\n" - "\tonly on every hit. 'help' outputs breakpoint condition\n" - "\tsyntax help, 'all' removes all breakpoints."; + " [&& ...] [: