Skip to content

Commit bb7c679

Browse files
committed
gdb/amd-dbgapi: disable forward progress requirement in amd_dbgapi_target_breakpoint::check_status
ROCgdb handles target events very slowly when running a test case like this, where a breakpoint is preset on HipTest::vectorADD: for (int i=0; i < numDevices; ++i) { HIPCHECK(hipSetDevice(i)); hipLaunchKernelGGL(HipTest::vectorADD, dim3(blocks), dim3(threadsPerBlock), 0, stream[i], static_cast<const int*>(A_d[i]), static_cast<const int*>(B_d[i]), C_d[i], N); } What happens is: - A kernel is launched - The internal runtime breakpoint is hit during the second hipLaunchKernelGGL call, which causes amd_dbgapi_target_breakpoint::check_status to be called - Meanwhile, all waves of the kernel hit the breakpoint on vectorADD - amd_dbgapi_target_breakpoint::check_status calls process_event_queue, which pulls the thousand of breakpoint hit events from the kernel - As part of handling the breakpoint hit events, we write the PC of the waves that stopped to decrement it. Because the forward progress requirement is not disabled, this causes a suspend/resume of the queue each time, which is time-consuming. The stack trace where this all happens is: #32 0x00007ffff6b9abda in amd_dbgapi_write_register (wave_id=..., register_id=..., offset=0, value_size=8, value=0x7fffea9fdcc0) at /home/smarchi/src/amd-dbgapi/src/register.cpp:587 #33 0x00005555588c0bed in amd_dbgapi_target::store_registers (this=0x55555c7b1d20 <the_amd_dbgapi_target>, regcache=0x507000002240, regno=470) at /home/smarchi/src/wt/amd/gdb/amd-dbgapi-target.c:2504 #34 0x000055555a5186a1 in target_store_registers (regcache=0x507000002240, regno=470) at /home/smarchi/src/wt/amd/gdb/target.c:3973 #35 0x0000555559fab831 in regcache::raw_write (this=0x507000002240, regnum=470, src=...) at /home/smarchi/src/wt/amd/gdb/regcache.c:890 #36 0x0000555559fabd2b in regcache::cooked_write (this=0x507000002240, regnum=470, src=...) at /home/smarchi/src/wt/amd/gdb/regcache.c:915 #37 0x0000555559fc3ca5 in regcache::cooked_write<unsigned long, void> (this=0x507000002240, regnum=470, val=140737323456768) at /home/smarchi/src/wt/amd/gdb/regcache.c:850 #38 0x0000555559fab09a in regcache_cooked_write_unsigned (regcache=0x507000002240, regnum=470, val=140737323456768) at /home/smarchi/src/wt/amd/gdb/regcache.c:858 #39 0x0000555559fb0678 in regcache_write_pc (regcache=0x507000002240, pc=0x7ffff62bd900) at /home/smarchi/src/wt/amd/gdb/regcache.c:1460 #40 0x00005555588bb37d in process_one_event (event_id=..., event_kind=AMD_DBGAPI_EVENT_KIND_WAVE_STOP) at /home/smarchi/src/wt/amd/gdb/amd-dbgapi-target.c:1873 #41 0x00005555588bbf7b in process_event_queue (process_id=..., until_event_kind=AMD_DBGAPI_EVENT_KIND_BREAKPOINT_RESUME) at /home/smarchi/src/wt/amd/gdb/amd-dbgapi-target.c:2006 #42 0x00005555588b1aca in amd_dbgapi_target_breakpoint::check_status (this=0x511000140900, bs=0x50600014ed00) at /home/smarchi/src/wt/amd/gdb/amd-dbgapi-target.c:890 #43 0x0000555558c50080 in bpstat_stop_status (aspace=0x5070000061b0, bp_addr=0x7fffed0b9ab0, thread=0x518000026c80, ws=..., stop_chain=0x50600014ed00) at /home/smarchi/src/wt/amd/gdb/breakpoint.c:6126 #44 0x000055555984f4ff in handle_signal_stop (ecs=0x7fffeaa40ef0) at /home/smarchi/src/wt/amd/gdb/infrun.c:7169 #45 0x000055555984b889 in handle_inferior_event (ecs=0x7fffeaa40ef0) at /home/smarchi/src/wt/amd/gdb/infrun.c:6621 #46 0x000055555983eab6 in fetch_inferior_event () at /home/smarchi/src/wt/amd/gdb/infrun.c:4750 #47 0x00005555597caa5f in inferior_event_handler (event_type=INF_REG_EVENT) at /home/smarchi/src/wt/amd/gdb/inf-loop.c:42 #48 0x00005555588b838e in handle_target_event (client_data=0x0) at /home/smarchi/src/wt/amd/gdb/amd-dbgapi-target.c:1513 Fix that performance problem by disabling the forward progress requirement in amd_dbgapi_target_breakpoint::check_status, before calling process_event_queue, so that we can process all events efficiently. Since the same performance problem could theoritically happen any time process_event_queue is called with forward progress requirement enabled, add an assert to ensure that forward progress requirement is disabled when process_event_queue is invoked. This makes it necessary to add a require_forward_progress call to amd_dbgapi_finalize_core_attach. It looks a bit strange, since core files don't have execution, but it doesn't hurt. Add a test that replicates this scenario. The test launches a kernel that hits a breakpoint (with an always false condition) repeatedly. Meanwhile, the host process loads an unloads a code object, causing check_status to be called. Bug: SWDEV-482511 Change-Id: Ida86340d679e6bd8462712953458c07ba3fd49ec Approved-by: Lancelot Six <[email protected]>
1 parent 9e8e5dd commit bb7c679

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

gdb/amd-dbgapi-target.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ amd_dbgapi_target_breakpoint::check_status (struct bpstat *bs)
568568
if (action == AMD_DBGAPI_BREAKPOINT_ACTION_RESUME)
569569
return;
570570

571+
require_forward_progress (*info, false);
572+
571573
/* If the action is AMD_DBGAPI_BREAKPOINT_ACTION_HALT, we need to wait until
572574
a breakpoint resume event for this breakpoint_id is seen. */
573575
amd_dbgapi_event_id_t resume_event_id
@@ -1335,6 +1337,10 @@ static amd_dbgapi_event_id_t
13351337
process_event_queue (amd_dbgapi_inferior_info &info,
13361338
amd_dbgapi_event_kind_t until_event_kind)
13371339
{
1340+
/* Pulling events with forward progress required may result in bad
1341+
performance, make sure it is not required. */
1342+
gdb_assert (!info.forward_progress_required);
1343+
13381344
while (true)
13391345
{
13401346
amd_dbgapi_event_id_t event_id;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/* This testcase is part of GDB, the GNU debugger.
2+
3+
Copyright 2025 Free Software Foundation, Inc.
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation; either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>. */
17+
18+
#ifdef DEVICE
19+
20+
#include <hip/hip_runtime.h>
21+
22+
constexpr unsigned int NUM_BREAKPOINT_HITS = 5;
23+
24+
static __device__ void
25+
break_here ()
26+
{
27+
}
28+
29+
extern "C" __global__ void
30+
kernel ()
31+
{
32+
for (int n = 0; n < NUM_BREAKPOINT_HITS; ++n)
33+
break_here ();
34+
}
35+
36+
#else
37+
38+
#include <hip/hip_runtime.h>
39+
#include <unistd.h>
40+
41+
constexpr unsigned int NUM_ITEMS_PER_BLOCK = 256;
42+
constexpr unsigned int NUM_BLOCKS = 128;
43+
constexpr unsigned int NUM_ITEMS = NUM_ITEMS_PER_BLOCK * NUM_BLOCKS;
44+
constexpr unsigned int NUM_LOAD_UNLOADS = 5;
45+
46+
#define CHECK(cmd) \
47+
{ \
48+
hipError_t error = cmd; \
49+
if (error != hipSuccess) \
50+
{ \
51+
fprintf (stderr, "error: '%s'(%d) at %s:%d\n", \
52+
hipGetErrorString (error), error, __FILE__, __LINE__); \
53+
exit (EXIT_FAILURE); \
54+
} \
55+
}
56+
57+
int
58+
main (int argc, const char **argv)
59+
{
60+
if (argc != 2)
61+
{
62+
fprintf (stderr, "Usage: %s <hip_module_path>\n", argv[0]);
63+
return 1;
64+
}
65+
66+
const auto module_path = argv[1];
67+
hipModule_t module;
68+
CHECK (hipModuleLoad (&module, module_path));
69+
70+
/* Launch the kernel. */
71+
hipFunction_t function;
72+
CHECK (hipModuleGetFunction (&function, module, "kernel"));
73+
CHECK (hipModuleLaunchKernel (function, NUM_BLOCKS, 1, 1,
74+
NUM_ITEMS_PER_BLOCK, 1, 1, 0, nullptr, nullptr,
75+
nullptr));
76+
77+
/* Load and unload the module many times. */
78+
for (int i = 0; i < NUM_LOAD_UNLOADS; ++i)
79+
{
80+
hipModule_t dummy_module;
81+
CHECK (hipModuleLoad (&dummy_module, module_path));
82+
CHECK (hipModuleUnload (dummy_module));
83+
}
84+
}
85+
86+
#endif
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2025 Free Software Foundation, Inc.
2+
3+
# This file is part of GDB.
4+
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 3 of the License, or
8+
# (at your option) any later version.
9+
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
# This test verifies what happens when a code object list update happens at the
19+
# same time as some wave stop events are reported. It was added following a
20+
# performance bug fix, where forward progress requirement disabled when
21+
# pulling events from amd-dbgapi in amd_dbgapi_target_breakpoint::check_status.
22+
#
23+
# The test launches a kernel that hits a breakpoint with an always false
24+
# condition a certain number of times. Meanwhile, the host loads and unloads
25+
# a code object in a loop, causing check_status to be called. The hope is that
26+
# check_status, when calling process_event_queue, will pull many WAVE_STOP
27+
# events from the kernel hitting the breakpoint.
28+
#
29+
# Without the appropriate fix (of disabling forward progress requirement in
30+
# check_status), GDB would hit the newly-added assert in process_event_queue,
31+
# which verifies that forward progress requirement is disabled. Even without
32+
# this assert, the test would likely time out (depending on the actual timeout
33+
# value).
34+
35+
load_lib rocm.exp
36+
standard_testfile .cpp
37+
require allow_hipcc_tests
38+
39+
# Build the host executable.
40+
if { [build_executable "failed to prepare" \
41+
$testfile $srcfile {debug hip}] == -1 } {
42+
return -1
43+
}
44+
45+
set hipmodule_path [standard_output_file ${testfile}.co]
46+
47+
# Build the kernel object file.
48+
if { [gdb_compile $srcdir/$subdir/$srcfile \
49+
$hipmodule_path object \
50+
{ debug hip additional_flags=--genco additional_flags=-DDEVICE } ] != "" } {
51+
return -1
52+
}
53+
54+
proc do_test { } {
55+
with_rocm_gpu_lock {
56+
clean_restart $::binfile
57+
gdb_test_no_output "set args $::hipmodule_path" "set args"
58+
59+
if { ![runto_main] } {
60+
return
61+
}
62+
63+
gdb_test "with breakpoint pending on -- break break_here if 0"
64+
gdb_continue_to_end "continue to end" "continue" 1
65+
}
66+
}
67+
68+
do_test

0 commit comments

Comments
 (0)