diff --git a/src/target/riscv/riscv-011.c b/src/target/riscv/riscv-011.c index f2a68698b..b35a6a4c6 100644 --- a/src/target/riscv/riscv-011.c +++ b/src/target/riscv/riscv-011.c @@ -1848,9 +1848,13 @@ static int handle_halt(struct target *target, bool announce) break; case DCSR_CAUSE_HWBP: target->debug_reason = DBG_REASON_WPTANDBKPT; - /* If we halted because of a data trigger, gdb doesn't know to do - * the disable-breakpoints-step-enable-breakpoints dance. */ - info->need_strict_step = true; + /* If we halted because of a watchpoint trigger, gdb doesn't know + * which watchpoint to step over. Setting need_strict_step to true + * flags OpenOCD to disable triggers -> step -> enable triggers on + * the next step or resume*/ + if (! riscv_breakpoint_hit(target, 0, info->dpc)) { + info->need_strict_step = true; + } break; case DCSR_CAUSE_DEBUGINT: target->debug_reason = DBG_REASON_DBGRQ; diff --git a/src/target/riscv/riscv.c b/src/target/riscv/riscv.c index 8d76c4aa4..f1679cdde 100644 --- a/src/target/riscv/riscv.c +++ b/src/target/riscv/riscv.c @@ -1986,6 +1986,64 @@ int riscv_enumerate_triggers(struct target *target) return ERROR_OK; } +/** + * Check if dpc matches the address of a breakpoint set on hartid + **/ +bool riscv_breakpoint_hit(struct target *target, int hartid, uint64_t dpc) +{ + if (!riscv_hart_enabled(target, hartid)) + return false; + + riscv_reg_t tselect; + int result = riscv_get_register_on_hart(target, &tselect, hartid, + GDB_REGNO_TSELECT); + if (result != ERROR_OK) + return false; + + bool hit = false; + + for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) { + riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t); + uint64_t tselect_rb; + result = riscv_get_register_on_hart(target, &tselect_rb, hartid, + GDB_REGNO_TSELECT); + if (result != ERROR_OK) + break; + /* Mask off the top bit, which is used as tdrmode in old + * implementations. */ + tselect_rb &= ~(1ULL << (riscv_xlen(target)-1)); + if (tselect_rb != t) + break; + uint64_t tdata1; + result = riscv_get_register_on_hart(target, &tdata1, hartid, + GDB_REGNO_TDATA1); + if (result != ERROR_OK) + break; + + int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target))); + if (!(type == 2 && (tdata1 & MCONTROL_EXECUTE))) + continue; + + uint64_t tdata2; + result = riscv_get_register_on_hart(target, &tdata2, hartid, + GDB_REGNO_TDATA2); + if (result != ERROR_OK) + break; + + if (tdata2 == dpc) { + LOG_DEBUG("breakpoint %d matches dpc", t); + hit = true; + break; + } + } + + // restore tselect + riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect); + LOG_DEBUG("no breakpoint matches dpc"); + + return hit; +} + const char *gdb_regno_name(enum gdb_regno regno) { static char buf[32]; diff --git a/src/target/riscv/riscv.h b/src/target/riscv/riscv.h index 31f3cf63c..b470f7c61 100644 --- a/src/target/riscv/riscv.h +++ b/src/target/riscv/riscv.h @@ -253,6 +253,7 @@ int riscv_remove_breakpoint(struct target *target, int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint); int riscv_remove_watchpoint(struct target *target, struct watchpoint *watchpoint); +bool riscv_breakpoint_hit(struct target *target, int hartid, uint64_t dpc); int riscv_init_registers(struct target *target);