target/breakpoints: Clear software breakpoints from available targets

If a target where a software breakpoint was set is not currently
available, but there are other targets in the same SMP group that are
available, then we can use those to remove the software breakpoint.

Change-Id: I9faa427c7b3aee31504e6e6599539e6f29b58d8f
Signed-off-by: Tim Newsome <tim@sifive.com>
This commit is contained in:
Tim Newsome 2023-06-30 12:03:38 -07:00
parent 162cc1e79d
commit 39a4f37f84
1 changed files with 93 additions and 39 deletions

View File

@ -220,6 +220,8 @@ int breakpoint_add(struct target *target,
return ERROR_OK; return ERROR_OK;
} else { } else {
/* For software breakpoints on SMP targets, only set them on a
* single target. We assume that SMP targets share memory. */
return breakpoint_add_internal(target, address, length, type); return breakpoint_add_internal(target, address, length, type);
} }
} }
@ -270,11 +272,17 @@ int hybrid_breakpoint_add(struct target *target,
return hybrid_breakpoint_add_internal(target, address, asid, length, type); return hybrid_breakpoint_add_internal(target, address, asid, length, type);
} }
/* free up a breakpoint */ /* Free the data structures we use to track a breakpoint on data_target.
static void breakpoint_free(struct target *target, struct breakpoint *breakpoint_to_remove) * Remove the actual breakpoint from breakpoint_target.
* This separation is useful when a software breakpoint is tracked on a target
* that is currently unavailable, but the breakpoint also affects a target that
* is available.
*/
static void breakpoint_free(struct target *data_target, struct target *breakpoint_target,
struct breakpoint *breakpoint_to_remove)
{ {
struct breakpoint *breakpoint = target->breakpoints; struct breakpoint *breakpoint = data_target->breakpoints;
struct breakpoint **breakpoint_p = &target->breakpoints; struct breakpoint **breakpoint_p = &data_target->breakpoints;
int retval; int retval;
while (breakpoint) { while (breakpoint) {
@ -287,7 +295,7 @@ static void breakpoint_free(struct target *target, struct breakpoint *breakpoint
if (!breakpoint) if (!breakpoint)
return; return;
retval = target_remove_breakpoint(target, breakpoint); retval = target_remove_breakpoint(breakpoint_target, breakpoint);
LOG_DEBUG("free BPID: %" PRIu32 " --> %d", breakpoint->unique_id, retval); LOG_DEBUG("free BPID: %" PRIu32 " --> %d", breakpoint->unique_id, retval);
(*breakpoint_p) = breakpoint->next; (*breakpoint_p) = breakpoint->next;
@ -295,27 +303,6 @@ static void breakpoint_free(struct target *target, struct breakpoint *breakpoint
free(breakpoint); free(breakpoint);
} }
static int breakpoint_remove_internal(struct target *target, target_addr_t address)
{
struct breakpoint *breakpoint = target->breakpoints;
while (breakpoint) {
if ((breakpoint->address == address) ||
(breakpoint->address == 0 && breakpoint->asid == address))
break;
breakpoint = breakpoint->next;
}
if (breakpoint) {
breakpoint_free(target, breakpoint);
return 1;
} else {
if (!target->smp)
LOG_ERROR("no breakpoint at address " TARGET_ADDR_FMT " found", address);
return 0;
}
}
static void breakpoint_remove_all_internal(struct target *target) static void breakpoint_remove_all_internal(struct target *target)
{ {
struct breakpoint *breakpoint = target->breakpoints; struct breakpoint *breakpoint = target->breakpoints;
@ -323,24 +310,90 @@ static void breakpoint_remove_all_internal(struct target *target)
while (breakpoint) { while (breakpoint) {
struct breakpoint *tmp = breakpoint; struct breakpoint *tmp = breakpoint;
breakpoint = breakpoint->next; breakpoint = breakpoint->next;
breakpoint_free(target, tmp); breakpoint_free(target, target, tmp);
} }
} }
void breakpoint_remove(struct target *target, target_addr_t address) void breakpoint_remove(struct target *target, target_addr_t address)
{ {
if (target->smp) { if (!target->smp) {
unsigned int num_breakpoints = 0; struct breakpoint *breakpoint = breakpoint_find(target, address);
if (breakpoint)
breakpoint_free(target, target, breakpoint);
return;
}
unsigned int found = 0;
struct target_list *head; struct target_list *head;
/* Target where we found a software breakpoint. */
struct target *software_breakpoint_target = NULL;
struct breakpoint *software_breakpoint = NULL;
/* Target that is available. */
struct target *available_target = NULL;
/* Target that is available and halted. */
struct target *halted_target = NULL;
foreach_smp_target(head, target->smp_targets) { foreach_smp_target(head, target->smp_targets) {
struct target *curr = head->target; struct target *curr = head->target;
num_breakpoints += breakpoint_remove_internal(curr, address);
} if (!available_target && curr->state != TARGET_UNAVAILABLE)
if (!num_breakpoints) available_target = curr;
LOG_ERROR("no breakpoint at address " TARGET_ADDR_FMT " found", address); if (!halted_target && curr->state == TARGET_HALTED)
halted_target = curr;
struct breakpoint *breakpoint = breakpoint_find(curr, address);
if (!breakpoint)
continue;
found++;
if (breakpoint->type == BKPT_SOFT) {
/* Software breakpoints are set on only one of the SMP
* targets. We can remove them through any of the SMP
* targets. */
if (software_breakpoint_target) {
LOG_TARGET_WARNING(curr, "Already found software breakpoint at "
TARGET_ADDR_FMT " on %s.", address, target_name(software_breakpoint_target));
} else { } else {
breakpoint_remove_internal(target, address); assert(!software_breakpoint_target);
software_breakpoint_target = curr;
software_breakpoint = breakpoint;
}
} else {
breakpoint_free(curr, curr, breakpoint);
}
}
if (!found) {
LOG_ERROR("no breakpoint at address " TARGET_ADDR_FMT " found", address);
return;
}
if (software_breakpoint) {
struct target *remove_target;
if (software_breakpoint_target->state == TARGET_HALTED)
remove_target = software_breakpoint_target;
else if (halted_target)
remove_target = halted_target;
else
remove_target = available_target;
if (remove_target) {
LOG_DEBUG("Removing software breakpoint found on %s using %s (address="
TARGET_ADDR_FMT ").",
target_name(software_breakpoint_target),
target_name(remove_target),
address);
/* Remove the software breakpoint through
* remove_target, but update the breakpoints structure
* of software_breakpoint_target. */
/* TODO: If there is an error, can we try to remove the
* same breakpoint from a different target? */
breakpoint_free(software_breakpoint_target, remove_target, software_breakpoint);
} else {
LOG_WARNING("No halted target found to remove software breakpoint at "
TARGET_ADDR_FMT ".", address);
}
} }
} }
@ -363,7 +416,7 @@ static void breakpoint_clear_target_internal(struct target *target)
LOG_DEBUG("Delete all breakpoints for target: %s", LOG_DEBUG("Delete all breakpoints for target: %s",
target_name(target)); target_name(target));
while (target->breakpoints) while (target->breakpoints)
breakpoint_free(target, target->breakpoints); breakpoint_free(target, target, target->breakpoints);
} }
void breakpoint_clear_target(struct target *target) void breakpoint_clear_target(struct target *target)
@ -385,7 +438,8 @@ struct breakpoint *breakpoint_find(struct target *target, target_addr_t address)
struct breakpoint *breakpoint = target->breakpoints; struct breakpoint *breakpoint = target->breakpoints;
while (breakpoint) { while (breakpoint) {
if (breakpoint->address == address) if (breakpoint->address == address ||
(breakpoint->address == 0 && breakpoint->asid == address))
return breakpoint; return breakpoint;
breakpoint = breakpoint->next; breakpoint = breakpoint->next;
} }