gdb/amd-dbgapi: add basic watchpoint support
Add basic watchpoint support for the amd-dbgapi target. This means
placing write watchpoints on globally addressable memory. More
complexity will come eventually to allow placing watchpoints on the
various other address spaces, but that will require adding proper
support for non-default address spaces first.
Implementation
--------------
I think the implementation is not too surprising, just adding the
required target methods. But there are some things worthy of mention:
- amd-dbgapi does not support read watchpoints. If the core attempts
to insert a read (or access, which means read/write) watchpoint,
amd_dbgapi_target::insert_watchpoint returns an error.
If we silently let the beneath target (linux-nat) install the read
watchpoint, it would be potentially confusing. Everything would look
fine to the user, but a read from the GPU would not be caught, so it
would look like the watchpoint doesn't work.
There is a loophole though: read watchpoints created before the
runtime is loaded (and therefore the amd-dbgapi target is pushed)
will still be inserted. Only when execution stops, and the user
tries to resume again, will the check in
amd_dbgapi_target::insert_watchpoint be hit.
Another option would be to allow the host read watchpoint to go
through, but warn that the reads from the AMD GPU device will not be
watched. We would need to be smart to avoid flooding the user with
warnings. But I decided to upstream the current ROCgdb behavior
first, we can always change it later.
- When the amd-dbgapi target gets pushed, we create amd-dbgapi
watchpoints for any existing hardware write watchpoint location.
- When the core asks the target to insert a watchpoint, we ask the
target beneath to insert it first. If the beneath target fails, we
return immediately with an error.
- When the core asks to remove a watchpoint, we ask the target beneath
to to remove it first. Even if it fails, we still try to remove the
amd-dbgapi watchpoint.
- When stopping after a watchpoint hit while the "precise-memory"
setting is not enabled, it is possible for the wave to stop a few
instructions later than the instruction that made the write that
triggered the watchpoint. We print a warning in that case, similar
to what we do when a memory violation happens while "precis-memory"
is disabled.
Testing
-------
- Tests precise-memory-warning-watchpoint.exp and
watchpoint-at-end-of-shader.exp are more or less brought as-is from
downstream ROCgdb. I modified precise-memory-warning-watchpoint.exp
to watch a hipMalloc'ed region instead of a `__device__` global
variable. The latter doesn't work upstream, because we don't yet
support the DWARF constructs that describe the variable location.
- I added test watchpoint-basic.exp with various simple cases to
exercises different code paths added by this patch.
Differences from downstream ROCgdb
----------------------------------
While extracting this code from ROCgdb, I made a few minor but possibly
significant (read: erroneous) changes. Those should be reviewed
carefully. I think that some code in ROCgdb was written at a time where
the amd-dbgapi target was always pushed at the very start of the
inferior execution, so assumptions were different.
- The value type for the `amd_dbgapi_inferior_info::watchpoint_map` map
is now a structure, instead of an std::pair, just because it makes
the code more readable.
- The insert_watchpoint and remove_watchpoint methods (and perhaps
others) now assume that if they are called, the runtime is in the
"enabled" state.
- insert_initial_watchpoints has one more check (loc->owner->type !=
bp_hardware_watchpoint), to filter out non-write watchpoints.
Otherwise, I think that we could mistakenly insert some write
watchpoints for some pre-existing read watchpoints.
- Because it is possible for read watchpoints to be created before the
target is pushed, remove_watchpoint returns early if it sees that the
code asks for the removal of a read watchpoint, instead of asserting
"type == hw_write" (this was caught by the new test).
- In ROCgdb, remove_watchpoint does:
if (addr < it->first || (addr + len) > it->second.first)
return 1;
I replaced it with some assertions.
The first half of this condition should always be true, due to how
std::upper_bound works.
For the second part: if the watchpoint was created successfully, it
is because it did fully cover the requested region (see
insert_one_watchpoint). I don't see why the core would ask us to
remove a watchpoint that wasn't successfully inserted. I am not 100%
sure about that one, there might be some edge cases where this is not
true.
- I changed a manual free in stopped_by_watchpoint to a
gdb::unique_xmalloc_ptr, even though it changes nothing functionally.
- I merged some conditions in amd_dbgapi_target_normal_stop.
Change-Id: Ia15fb7434dc0c142a5a32997ada2e3a163c89f98
Approved-by: Lancelot Six <lancelot.six@amd.com>
Co-Authored-By: Laurent Morichetti <laurent.morichetti@amd.com>
10 files changed