aboutsummaryrefslogtreecommitdiff
path: root/gdb/infrun.c
diff options
context:
space:
mode:
authorYour Name <you@example.com>2025-07-11 11:52:53 -0400
committerAndrew Burgess <aburgess@redhat.com>2025-08-07 14:43:49 +0100
commit21c90ca166ee384d271e92a409c690a7faf945a2 (patch)
treeb01cb1d4d6d8faba9b48e84cb8babd98780160e7 /gdb/infrun.c
parent97b6ffe44b98479f991ab38b4c59f4fd0f30198e (diff)
downloadbinutils-users/aburgess/try-watchpoint-api-changes.zip
binutils-users/aburgess/try-watchpoint-api-changes.tar.gz
binutils-users/aburgess/try-watchpoint-api-changes.tar.bz2
gdb/aarch64: restore in-order watchpoint matchingusers/aburgess/try-watchpoint-api-changes
At Red Hat we have an out of tree AArch64 watchpoint test which broke after this commit: commit cf16ab724a41e4cbaf723b5633d4e7b29f61372b Date: Tue Mar 12 17:08:18 2024 +0100 [gdb/tdep] Fix gdb.base/watch-bitfields.exp on aarch64 The problem with AArch64 hardware watchpoints is that they (as I understand it) are restricted to a minimum of 8 bytes. This means that, if the thing you are watching is less than 8-bytes, then there is always scope for invalid watchpoint triggers caused by activity in the part of the 8-bytes that are not being watched. Or, as is the case in this RH test, multiple watchpoint are created within an 8-byte region, and GDB can miss-identify which watchpoint actually triggered. Prior to the above commit the RH test was passing. However, the test was relying on, in the case of ambiguity, GDB selecting the first created watchpoint. That behaviour changed with the above commit. Now GDB favours reporting non write breakpoints, and will only report a write breakpoint if no non-write breakpoint exists in the same region. I originally posted a patch to try and tweak the existing logic to restore enough of the original behaviour that the RH test would pass, this can be found here (2 iterations): https://inbox.sourceware.org/gdb-patches/65e746b6394f04faa027e778f733eda95d20f368.1753115072.git.aburgess@redhat.com https://inbox.sourceware.org/gdb-patches/638cbe9b738c0c529f6370f90ba4a395711f63ae.1753971315.git.aburgess@redhat.com Neither of these really resolved the problem, they fixed some cases, but broke others. Ultimately, the problem on AArch64 is that for a single watchpoint trap, there could be multiple watchpoints that are potentially responsible. The existing API defined by the target_ops methods stopped_by_watchpoint() and stopped_data_address() only allow for two possible options: 1. If stopped_by_watchpoint() is true then stopped_data_address() can return true and a single address which identifies all watchpoints at that single address, or 2. If stopped_by_watchpoint() is true then stopped_data_address() can return false, in which case GDB will check all write watchpoints to see if any have changed, if they have, then GDB tells the user that that was the triggering watchpoint. If we are in a situation where we have to choose between multiple write and read watchpoints then the current API doesn't allow the architecture specific code to tell GDB core about this case. In this commit I propose that we change the target_ops API, specifically, the method: bool target_ops::stopped_data_address (CORE_ADDR *); will change to: std::vector<CORE_ADDR> target_ops::stopped_data_addresses (); The architecture specific code can now return a set of watchpoint addresses, allowing GDB to identify a set of watchpoints that might have triggered. GDB core can then select the most likely watchpoint, and present that to the user. As with the old API, target_ops::stopped_data_addresses should only be called when target_ops::stopped_by_watchpoint is true, in which case it's return values can be interpreted like this: a. An empty vector; this replaces the old case where false was returned. GDB should check all the write watchpoints and select the one that changed as the responsible watchpoint. b. A single entry vector; all targets except AArch64 currently return at most a single entry vector. The single address indicates the watchpoint(s) that triggered. c. A multi-entry vector; currently AArch64 only. These addresses indicate the set of watchpoints that might have triggered. GDB will check the write watchpoints to see which (if any) changed, and if no write watchpoints changed, GDB will present the first access watchpoint. In the future, we might want to improve the handling of (c) so that GDB tells the user that multiple access watchpoints might have triggered, and then list all of them. This might clear up some confusion. But I think that can be done in the future (I don't have an immediate plan to work on this). I think this change is already a good improvement. The changes for this are pretty extensive, but here's a basic summary: * Within gdb/ changing the API name from stopped_data_address to stopped_data_addresses throughout. Comments are updated too where needed. * For targets other than AArch64, the existing code is retained with as few changes as possible, we only allow for a single address to be returned, the address is now wrapped in a vector. Where we used to return false, we now return the empty vector. * For AArch64, the return a vector logic is pushed through to gdb/nat/aarch64-hw-point.{c,h}, and aarch64_stopped_data_address changes to aarch64_stopped_data_addresses, and is updated to return a vector of addresses. * In infrun.c there's some updates to some debug output. * In breakpoint.c the interesting changes are in watchpoints_triggered. The existing code has three cases to handle: (i) target_stopped_by_watchpoint returns false. This case is unchanged. (ii) target_stopped_data_address returns false. This case is now calling target_stopped_data_addresses, and checks for the empty vector, but otherwise is unchanged. (iii) target_stopped_data_address returns true, and a single address. This code calls target_stopped_data_addresses, and now handles the possibility of a vector containing multiple entries. We need to first loop over every watchpoint setting its triggered status to 'no', then we check every address in the vector setting matching watchpoint's triggered status to 'yes'. But the actual logic for if a watchpoint matches an address or not is unchanged. The important thing to notice here is that in case (iii), before this patch, GDB could already set _multiple_ watchpoints to triggered. For example, setting a read and write watchpoint on the same address would result in multiple watchpoints being marked as triggered. This patch just extends this so that multiple watchpoints, at multiple addresses, can now be marked as triggered. * In remote.c there is an interesting change. We need to allow gdbserver to pass the multiple addresses back to GDB. To achieve this, I now allow multiple 'watch', 'rwatch', and 'awatch' tokens in a 'T' stop reply packet. This change is largely backward compatible. For old versions of GDB, GDB will just use the last such token as the watchpoint stop address. For new GDBs, all of the addresses are collected and returned from the target_ops::stopped_data_addresses call. If a new GDB connects to an old gdbserver then it'll only get a single watchpoint address in the 'T' packet, but that's no worse than we are now, and will not cause a GDB crash, GDB will just end up checking a restricted set of watchpoints (which is where we are right now). * In gdbserver/ the changes are pretty similar. The API is renamed from ::stopped_data_address to ::stopped_data_addresses, and ::low_stopped_data_address to ::low_stopped_data_addresses. * For all targets except AArch64, the existing code is retained, we just wrap the single address into a vector. * For AArch64, we call aarch64_stopped_data_addresses, which returns the required vector. For testing, I've built GDB on GNU/Linux for i386, x86-64, PPC64le, ARM, and AArch64. That still leaves a lot of targets possibly impacted by this change as untested. Which is a risk. I certainly wouldn't want to push this patch until after GDB 17 branches so we have time to find and fix any regressions that are introduced. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33240 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33252
Diffstat (limited to 'gdb/infrun.c')
-rw-r--r--gdb/infrun.c26
1 files changed, 18 insertions, 8 deletions
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 9d3e1b7..32c6a34 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -6889,16 +6889,26 @@ handle_signal_stop (struct execution_control_state *ecs)
("stop_pc=%s", paddress (reg_gdbarch, ecs->event_thread->stop_pc ()));
if (target_stopped_by_watchpoint ())
{
- CORE_ADDR addr;
+ auto inf_target = current_inferior ()->top_target ();
+ std::vector<CORE_ADDR> addr_list
+ = target_stopped_data_addresses (inf_target);
- infrun_debug_printf ("stopped by watchpoint");
-
- if (target_stopped_data_address (current_inferior ()->top_target (),
- &addr))
- infrun_debug_printf ("stopped data address=%s",
- paddress (reg_gdbarch, addr));
+ std::string addr_str;
+ if (addr_list.empty ())
+ addr_str = "(no data addressses available)";
else
- infrun_debug_printf ("(no data address available)");
+ {
+ for (const CORE_ADDR addr : addr_list)
+ {
+ if (addr_str.length () > 0)
+ addr_str += ", ";
+
+ addr_str += paddress (reg_gdbarch, addr);
+ }
+ }
+
+ infrun_debug_printf ("stopped by watchpoint, data addresses = %s",
+ addr_str.c_str ());
}
}