Repository navigation
feat(emulation): debug emulated GPU kernels with real ROCgdb - #9844
Conversation
… the daemon ## Motivation ROCgdb must issue DBG_TRAP ioctls against a different client process (the inferior). The landed dispatcher still rejected the first cross-process ENABLE because it compared against a debugger pid recorded only after ENABLE succeeds. ## Technical Details - Authorize non-self DBG_TRAP operations from the inferiors live `/proc/<pid>/status` `TracerPid`, matching the kernels ptrace-parent gate. - Implement SET_EXCEPTIONS_ENABLED by updating the debug sessions forwarded-exception mask. - Keep develops reworked daemon transport unchanged: DBG_TRAP remains marshaled by the landed RPC path, and ENABLE transfers the notifier with SCM_RIGHTS rather than the prototypes raw-fd approach. - Add a real ptrace test that rejects an unattached caller, authorizes it after PTRACE_ATTACH, uses a valid notifier fd, exercises SET_EXCEPTIONS_ENABLED, and cleans up the child deterministically. Prototype of #8364. ## Test Plan - Build rocjitsu and mirage. - Run the KFD, sysfs, and daemon suites. - Run the mirage workspace tests. ## Test Result - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='KfdIoctlTest.*:DbgTrapPtraceTest.*:RemoteDriverDbg*'`: 31/31 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed.
ROCgdb enables debugging on an inferior immediately after exec, before ROCr opens /dev/kfd. SimulatedKfd previously required a KfdProcess for the target pid and returned ESRCH. - Store DebugSession objects in SimulatedKfd keyed by inferior Linux pid, independent of KfdProcess and /dev/kfd connection lifetime. - Pin target and debugger identities with pidfds, revalidate them around authorization and session creation, and reject PID-reuse/exit races with ESRCH. - Preserve live debug sessions across target or debugger KFD connection close, while asynchronously disabling sessions and releasing notifier ownership when either process exits. - Match Linux target-liveness ordering by releasing an exited target's stale session while returning ESRCH from DISABLE. - Make reaper shutdown race-free with C++20 stop-token-aware condition_variable_any waits. - Retain the landed DBG_TRAP dispatcher, runtime-info marshalling, RPC path, and SCM_RIGHTS notifier ownership. - Add deterministic coverage for exited-target DISABLE cleanup, debugger-exit cleanup and re-enable, identity races, KFD-close retention, and reaper shutdown. Prototype of #8365. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='KfdIoctlTest.*:DbgTrap*'`: 41/41 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed.
Replayed the attach/detach configuration operations onto the landed SimulatedKfd DBG_TRAP dispatcher and SCM_RIGHTS/RPC transport. SET_FLAGS returns prior state; launch mode and override validate their enums and report the prior/supported masks expected by rocm-dbgapi; idle event queries return EAGAIN; queue snapshots remain empty; queue suspend/resume report zero operations; and address-watch placeholders retain the existing runtime and GPU validation gates. DISABLE continues to use HEAD’s hardened pidfd lifecycle: an exited target releases the session/notifier and returns ESRCH rather than bypassing target liveness. Fresh sessions reset all configuration state. Review fixes made the no-queue suspend/resume result truthful for non-empty requests, reported the full synthetic trap-support mask used by rocm-dbgapi probes, rejected invalid launch values, preserved the landed pid-identity/debugger-exit cleanup, and made the documentation explicit that GPU breakpoints and wave debugging remain pending. Deterministic tests cover success, validation errors, idle behavior, non-empty no-queue requests, state reset, and exited-target cleanup. Prototype of #8366. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='KfdIoctlTest.*:DbgTrap*'`: 42/42 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `mirage run --gdb` is introduced by stack row 12 and was not available at this step.
rocm-dbgapi locates a process's waves by walking each compute queue's context-save-restore area, discovered through GET_QUEUE_SNAPSHOT. Report the live queue geometry instead of an empty snapshot so wave discovery can build on the existing hardened debug-session lifecycle. Prototype of #8367. - Publish queue metadata after CP registration, update it with UPDATE_QUEUE, and remove it atomically on destroy/close under the allocation lock. - Match the amdkfd two-call ABI, creation order, input stride, entry-size clamping, total counts, EC_QUEUE_NEW reporting/clearing, and errno behavior. - Marshal queue snapshots through daemon RPC with checked inline lengths, pointer reconstruction, success-only strided copyback, and malformed-request rejection. - Preserve null queue and device snapshot pointers through daemon reconstruction, return EFAULT, and prevent null client copyback. - Add deterministic local, daemon-dispatch, transport failure, null-pointer, capacity, padding, lifecycle, and metadata tests. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='KfdIoctlTest.*:RemoteDriverDbg*'`: 45/45 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `mirage run --gdb` is introduced by stack row 12 and was not available at this step.
Prototype of #8368. Adapt the prototype to the landed dispatch and scheduler architecture. Preserve global-workgroup EXEC masking while tagging both CP and SPI dispatch paths with the KFD queue ID. Decode s_trap through the generated ISA mnemonic, preserve STATUS/TRAPSTS, save the post-trap PC, and quiesce debug-halted waves without retiring their slots or spinning CU/SPI schedulers. Keep CWSR serialization, event delivery, and asynchronous resume wiring for their later stack commits. Use runnable-wave idle semantics when the CP records CU state and schedules execution. This reactivates a quiesced CU when another CU's idle callback dispatches work beside a resident debug-halted wave, while avoiding activation when only stopped waves remain. Add a deterministic two-CU regression that halts one wave, drains its event loop, unblocks a new dispatch from another CU, and proves the new wave progresses; the regression fails against the prior active-wave check. Add deterministic CDNA4 and gfx12.5 tests for accepted, absent, and declined handlers, immediate decoding, PC/status preservation, stop/quiesce/resume state, queue identity, reset cleanup, and post-quiescence dispatch reactivation. Results: - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.*:CommandProcessorTest.DispatchToQuiescedDebugHaltedCuReactivatesEventLoop:IsaTest.*Trap*'`: 6/6 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `mirage run --gdb` is introduced by stack row 12 and was not available at this step.
Prototype of #8369. ## Motivation rocm-dbgapi reads stopped-wave registers from the inferior-visible queue context-save-restore area. Serialize the daemon-hosted emulator state using the exact gfx9.4 CWSR control-stack and wave-save layout it parses. ## Technical Details - Write the CWSR header, control stack, and wave64 HWREG/TTMP/SGPR/VGPR blocks at rocdbgapi-compatible offsets. - Encode gfx942/gfx950 packet, wave, workgroup, trap, and register metadata; explicitly exclude earlier gfx9 layouts whose ACC-VGPR and TTMP packet-id conventions differ. - Validate alignment, field widths, register limits, area bounds, and address overflow before writing; zero padding and publish payload before the header. - Preserve workgroup boundaries and document queue suspension, coherent mapping, locking, and wave-lifetime requirements for callers. - Add a deterministic rocdbgapi-formula round-trip covering multiple waves and workgroups plus invalid-input no-write cases. ## Test Results - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.Cwsr*'`: passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - Generator not run: no generated source changed. - `mirage run --gdb` is introduced by stack row 12 and was not available at this step.
…e traps With the CWSR serializer (prior PR) and the wave trap state in place, the driver can turn a GPU-kernel breakpoint into something rocm-dbgapi observes: when a wave executes `s_trap`, the emulated KFD must stop it, publish its state into the queue's CWSR area, and report an `EC_QUEUE_WAVE_TRAP` event through QUERY_DEBUG_EVENT — the sequence rocm-dbgapi drives on every wave stop. This wires those unit-tested pieces into the live driver path. rocm-dbgapi polls QUERY_DEBUG_EVENT for a queue with a pending exception, then reads that queue's CWSR area. A queue must also have been announced with `EC_QUEUE_NEW` before dbgapi accepts wave events on it. - Install `SimulatedKfd::on_wave_trap` as each compute unit's `s_trap` handler at CP-init. Running on the engine thread when a wave traps, it resolves the wave's process and its queue's CWSR area (returns false — s_trap is a no-op — if undebugged or no CWSR area), assigns the wave a stable debugger id, marks it debug-halted, serializes all debug-halted waves of the queue via `serialize_queue_cwsr`, raises `EC_QUEUE_WAVE_TRAP`, and wakes the debugger's notifier pipe. - Hold debug events per target, grouped by queue, in `debug_events_` (a separate lock from `debug_sessions_`, never held together on the trap path); `raise_debug_event` also reflects the exception onto the queue snapshot. - QUERY_DEBUG_EVENT drains real events: return one queue whose pending exception intersects the requested mask, set queue_id/gpu_id, clear the requested bits, and return -EAGAIN when none remain (matching `kfd_dbg_ev_query_debug_event`). - `Wavefront` gains a stable `debug_wave_id` (planted in TTMP4:5); `build_cwsr_wave_state` snapshots pc/exec/vcc/status(+HALT)/mode/m0/SGPRs/VGPRs and the debugger TTMP metadata for the serializer. - This is the driver wiring that connects the already-unit-tested wave-stop and CWSR-serialization pieces; the RUNTIME_ENABLE handshake and register write-back that complete a full ROCgdb breakpoint, plus the end-to-end CI harness that exercises this path with real ROCgdb, land in the next PR. Prototype of #8370. - Re-run the full gtest suite: the wave-trap and CWSR round-trip unit tests cover the pieces this wires together, and the QUERY_DEBUG_EVENT drain is on the attach/detach path already covered. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='KfdIoctlTest.DbgTrapRealWaveTrapPublishesCwsrAndEvent:WaveDebugTest.*:KfdIoctlTest.DbgTrap*'`: 32/32 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `mirage run --gdb` is introduced by stack row 12 and was not available at this step.
The prior PRs stop a wave, serialize it into the CWSR area, and raise the event. This closes the loop so real ROCgdb, through `mirage run`, sets a breakpoint on a GPU kernel, stops the trapping wave, reads its registers/PC/disassembly, and continues to a correct result — the first fully working GPU-kernel debug session on the emulator, with a CI harness that proves it. Two more pieces complete a breakpoint: ROCr's `RUNTIME_ENABLE` blocks under a debugger until it receives `SEND_RUNTIME_EVENT` (this is what makes a pending `break <kernel>` resolve as the code object loads), and on resume the debugger's register edits — written straight into the CWSR area — must be read back and applied to the live wave. - Implement the RUNTIME_ENABLE debugger handshake: raise `EC_PROCESS_RUNTIME`, wait (bounded) for the debugger's ack, and return the saved `kfd_runtime_info` from QUERY_EXCEPTION_INFO so dbgapi installs `r_brk` and tracks code objects. Pass the debugger's `dbg_fd` notifier pipe to the daemon out-of-band (SCM_RIGHTS on ENABLE) so a wave stop can wake it (mirage `daemon.rs`, `rocjitsu_sys`). - Add `deserialize_queue_cwsr` and `apply_cwsr_to_wave`: RESUME_QUEUES reloads a wave from its CWSR area (picking up the debugger's edits) and unhalts it. - Marshal QUERY_EXCEPTION_INFO over the daemon transport. - Add the `RocgdbDebug` CTest harness (`tests/rocgdb/debug_kernel.sh` + `add_one.hip`): it compiles a HIP kernel with device debug info and drives real ROCgdb through `mirage run` — break on the GPU kernel, stop at the wave, read PC/EXEC and the instruction at PC, and continue to a correct result — asserting each marker so it is a CI smoke test for the whole stack. It skips cleanly when hipcc/rocgdb/mirage are unavailable. - Deterministic single-step and stepping over a breakpoint (displaced stepping) are the next two PRs; here `continue` past the entry breakpoint already works. Prototype of #8371. - Add `CwsrDeserializeRecoversSerializedWaveState` (serialize -> deserialize round-trips the register state the resume path reads back). - Run the full gtest suite, and the `RocgdbDebug` CTest end to end. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.*:KfdIoctlTest.DbgTrap*:RemoteDriverDbg*'`: 43/43 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `ctest --test-dir build --output-on-failure -R '^RocgdbDebug\.'`: no tests registered because the venv ROCgdb was not yet discoverable on `PATH` at this step; final-stack real ROCgdb coverage is recorded in row 21.
Add `mirage run --gdb`, a one-flag shortcut that launches the workload under ROCgdb for GPU kernel debugging on the emulated GPU. Debugging an emulated GPU kernel meant hand-writing the full ROCgdb invocation (`mirage run -- rocgdb -ex 'set breakpoint pending on' --args ./app`) and knowing that kernel breakpoints must be pending because GPU code objects load lazily at dispatch. `mirage run --gdb` collapses that into one flag, making the emulated GPU debugger the first-class, discoverable way to debug a kernel with no AMD GPU. `mirage run` already starts a session daemon and launches the workload under the rocjitsu interposer; ROCgdb only needs to be spliced in front of the workload argv. Local (in-process) mode cannot host ROCgdb, so the daemon path (the default) is required. - Add `--gdb` and repeatable `--gdb-ex <CMD>` flags to `mirage run`; `--gdb-ex` implies `--gdb`. - `gdb_wrap_argv` rewrites `./app args...` to `rocgdb [--batch] -ex 'set breakpoint pending on' [-ex CMD]... --args ./app args...`: pending breakpoints so `break <kernel>` resolves at dispatch, `--gdb-ex` commands injected in order for scripted runs, and `--batch` added only when `-ex` commands are present so a bare `--gdb` stays interactive. - Add `scripts/record_demo.sh` (asciinema recorder) and the `rocgdb-quickstart` demo (`.md`/`.sh`/`.cast`). - Document the one-command form in `rocgdb-debugging.md`. Requires a profile whose container ships ROCgdb, and `hipcc`/`rocgdb` on PATH to build and debug. Reading kernel arguments (`print n`) needs private/scratch reads, which land in a later change; the quickstart demo inspects the wave PC and disassembly for now. Prototype of #8372. - Unit-test `gdb_wrap_argv`: bare wrap makes breakpoints pending; extra `--gdb-ex` commands are injected in order and enable `--batch`. - Drive the quickstart demo end to end against `add_one.hip`. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.*:KfdIoctlTest.DbgTrap*'`: passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `emulation/mirage/target/debug/mirage run --profile mi350x --gdb -- /bin/true`: CLI path executed and failed accurately with `command not found: rocgdb`; ROCgdb is not installed. `emulation/rocjitsu/demos/rocgdb-quickstart.{md,sh,cast}`: debug a real GPU kernel with `mirage run --gdb` -- no AMD GPU required.
…trap_after_inst Raise TRAPSTS.TRAP_AFTER_INST on single-step completion so rocm-dbgapi decodes the stop as a single-step instead of re-running the wave. Back-to-back `stepi` in ROCgdb was deterministically broken against the emulator: one `stepi` worked, but a second ran the wave away -- the engine executed the rest of the kernel and the debugger lost control. This blocked instruction single-stepping, the last gap in the core breakpoint/step/continue debugging loop. On gfx9.4 (MI300/MI350), single-step *completion* is signaled to rocm-dbgapi by TRAPSTS.TRAP_AFTER_INST (bit 25), not by the wave merely being stopped. dbgapi's `wave_get_state` maps that exact bit to `WAVE_STOP_REASON_SINGLE_STEP` (rocdbgapi architecture.cpp:1544, mask `sq_wave_trapsts_trap_after_inst_mask = 1u << 25` at :3782); it is the same bit dbgapi sets itself in `simulate_instruction_fixup` (:4044). - `SimulatedKfd::on_wave_single_step_complete` now sets TRAPSTS.TRAP_AFTER_INST (named constant `kSqWaveTrapstsTrapAfterInstMask`, cited to the dbgapi register definition) before serializing the post-step CWSR, so the wave the debugger reads back carries the single-step stop reason. - The bit never accumulates: dbgapi clears it on the next resume via `clear_stop_reasons` (:1418), and the breakpoint path (non-zero trap id) does not set it, so breakpoints still report as breakpoints. - `debug_kernel.sh` now single-steps three instructions and asserts the four sampled `$pc` values (breakpoint + each step) are strictly increasing -- a runaway would exit early or repeat a PC. Scoped to gfx9.4 single-step semantics; other stop reasons (watchpoints, faults) are added by later changes. Prototype of #8373. - `ninja librocjitsu.so rocjitsu_tests`; full unit suite. - `ctest -R RocgdbDebug` (break, run, 3x stepi with PC assertions, continue) against the freshly built librocjitsu.so. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.SingleStepExecutesOneInstructionThenReports:WaveDebugTest.Cwsr*'`: 3/3 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `emulation/mirage/target/debug/mirage run --profile mi350x --gdb -- /bin/true`: returned `command not found: rocgdb` because the venv ROCgdb was not yet discoverable on `PATH` at this step; final-stack real ROCgdb coverage is recorded in row 21.
…l breakpoints Advertise a per-queue debugger displaced-stepping region in the CWSR header so rocm-dbgapi can step over an interior breakpoint. Setting a breakpoint on an interior kernel line and issuing `continue` (or single-stepping an instruction rocm-dbgapi cannot simulate) aborted the debug session with "Per-queue memory reserved for the debugger is missing / amd_dbgapi_displaced_stepping_start failed". Only breakpoints on instructions dbgapi can emulate in software could be stepped over, so `continue` from a real interior breakpoint was unusable. Stepping over a breakpoint is a fundamental debugger operation. To resume past a breakpoint, rocm-dbgapi uses displaced stepping: it copies the instruction under the breakpoint into a per-queue scratch buffer, single-steps it there, then fixes up the PC. Those buffers are carved in 32-byte chunks from a "debugger memory" region the context-save header advertises via `debug_offset` (byte 16) and `debug_size` (byte 20); dbgapi aborts if it is absent (rocdbgapi queue.cpp:111, `allocate_displaced_instruction`). - `serialize_queue_cwsr` reserved `debug_offset = debug_size = 0`, so the region was missing. It now reserves the region in the tail slack of the save area (64-byte aligned, so each 32-byte chunk is aligned) and writes the header fields. The region lives inside the inferior-mapped context-save area, so it is backed by the same memory dbgapi reads and the emulator fetches the displaced instruction from directly. - Size follows the real KFD formula (amdgpu kfd_queue.c:398-460: `ALIGN(wave_num * DEBUGGER_BYTES_PER_WAVE, DEBUGGER_BYTES_ALIGN)`, `PER_WAVE = 32`, `ALIGN = 64`) plus headroom chunks for dbgapi's per-queue park and terminating instruction buffers. Constants are named and cited. - The region is skipped (fields stay 0) if it would not fit the advertised save-area size. `CwsrLayout` now reports the chosen `debug_offset`/`debug_size`. Sizing targets the gfx9 CDNA save-area layout; the headroom is fixed rather than negotiated per debug session. Prototype of #8374. - Unit: new `WaveDebugTest.CwsrReservesDebuggerMemoryForDisplacedStepping` asserts a non-zero, 64-aligned, in-bounds, non-overlapping region with enough chunks; CWSR round-trip/deserialize tests still pass. Full suite. - E2E: `debug_kernel.sh` (CTest `RocgdbDebug`) gains an interior-breakpoint (`add_one.hip:15`) + `continue` scenario, asserting the kernel completes and dbgapi never reports the missing-memory error. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.Cwsr*'`: 3/3 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `emulation/mirage/target/debug/mirage run --profile mi350x --gdb -- /bin/true`: returned `command not found: rocgdb` because the venv ROCgdb was not yet discoverable on `PATH` at this step; final-stack real ROCgdb coverage is recorded in row 21.
…trap mask
Validate the wave-launch trap-override request and report an accurate
device-supported trap mask, mirroring the real KFD contract.
`KFD_IOC_DBG_TRAP_SET_WAVE_LAUNCH_OVERRIDE` is how rocm-dbgapi enables the
address-watch exception trap and, at attach, discovers which exception
traps the device supports. The emulator echoed the request without
validation and reported a supported mask of 0 ("no traps supported"),
inconsistent with the hardware contract and misinforming any debugger that
relies on the reported mask before enabling watchpoints.
The real KFD validates in two layers: `kfd_dbg_validate_trap_override_request`
(amdgpu kfd_debug.c) starts from an all-ones supported mask, and the gfx9
backend `kgd_gfx_v9_validate_trap_override_request` (amdgpu_amdkfd_gfx_v9.c)
narrows it to `KFD_DBG_TRAP_MASK_DBG_ADDRESS_WATCH` and rejects any override
mode other than `OVERRIDE_OR` with `-EINVAL` (the `SPI_GDBG_TRAP_MASK`
register is global, so `REPLACE` could disturb other processes). The caller
then returns `-EACCES` when the requested support mask is not a subset of the
supported mask.
The `SET_WAVE_LAUNCH_OVERRIDE` handler now: rejects non-OR modes with
`-EINVAL`; rejects unsupported requested bits with `-EACCES`; records only
supported bits as enabled; and returns the previous enablement in
`enable_mask` and the supported mask (`DBG_ADDRESS_WATCH`) in
`support_request_mask`. Constants come from the vendored UAPI
(`KFD_DBG_TRAP_OVERRIDE_OR`, `KFD_DBG_TRAP_MASK_DBG_ADDRESS_WATCH`).
Reports the gfx9.4 supported mask (address-watch only); other gfx targets'
masks are not modeled. Enabling the bit is validated here; the watchpoint
trap itself is wired up in a following change.
Prototype of #8375.
- Unit: new `KfdIoctlTest.DbgTrapWaveLaunchOverrideValidatesRequest`
(REPLACE -> EINVAL; unsupported bit -> EACCES; supported request ->
success with correct previous/supported masks; second call sees the prior
enablement). `DbgTrapAttachDetachConfigOps` strengthened to assert the
supported mask. Full unit suite.
- E2E: `mirage run -- rocgdb` break/run/`info agents`/continue still clean.
- `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed.
- `./build/tests/rocjitsu_tests --gtest_filter='KfdIoctlTest.DbgTrapWaveLaunchOverrideValidatesRequest:KfdIoctlTest.DbgTrapAttachDetachConfigOpsValidateAndResetState'`: 2/2 passed.
- `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed.
- `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed.
- `emulation/mirage/target/debug/mirage run --profile mi350x --gdb -- /bin/true`: returned `command not found: rocgdb` because the venv ROCgdb was not yet discoverable on `PATH` at this step; final-stack real ROCgdb coverage is recorded in row 21.
Program the gfx9 four-slot address-watch model end to end so ROCgdb
hardware data watchpoints (watch/rwatch/awatch) trap the GPU wave that
accesses a watched address.
ROCgdb hardware data watchpoints on GPU memory did nothing on the
emulator: SET_NODE_ADDRESS_WATCH accepted the request but always returned
watch id 0 and never trapped an accessing wave, and the device snapshot
advertised zero watch registers, so a watchpoint could be created but
never inserted or fired. Data watchpoints are a core debugger capability
for catching who writes a buffer.
gfx9 has four TCP_WATCH slots; each holds an address, a compared-bit mask,
and an access mode. rocm-dbgapi learns how many watch registers the device
has from HSA_CAP_WATCH_POINTS_SUPPORTED + TOTALBITS in the agent
capability, programs a slot via SET_NODE_ADDRESS_WATCH, and the hardware
traps a wave whose access hits a watched address, setting
TRAPSTS.addr_watch<slot>.
- Advertise the four watch registers: `debug_topology_for` now sets
HSA_CAP_WATCH_POINTS_SUPPORTED + TOTALBITS = ilog2(4) so the sysfs
topology and GET_DEVICE_SNAPSHOT report a non-zero watch-register count.
develop's topology refactor had dropped this; without it rocm-dbgapi
refuses to insert a watchpoint ("too many hardware watchpoints").
- `DebugSession` stores four hardware watch slots (address/mask/mode).
SET_NODE_ADDRESS_WATCH allocates a free slot and returns its id
(-ENOMEM when all four are used); CLEAR_NODE_ADDRESS_WATCH frees it.
- The compute unit gained a `WatchpointHandler`, invoked from
`issue_instruction` for each active-lane global-memory access. The probe
is captured before the pipeline consumes the instruction and the match
test runs after the PC advances, so a wave stopped on a watchpoint
resumes past the watched access instead of re-triggering it.
- `on_wave_watchpoint` matches an access when the address block
{X : (X & mask) == (address & mask)} overlaps the access span and the
mode matches (READ / NONREAD write / ATOMIC / ALL). On a hit it raises
TRAPSTS.addr_watch<slot> (gfx9: watch0@7, watch1..3@12/13/14) and
MODE.excp_en.addr_watch (@19), then reports the wave through the trap
path. All bit positions are named constants with citations.
gfx9 four-slot model (MI300/MI350). Watchpoint stop location is
instruction-accurate but not sub-instruction precise (ROCgdb notes
"precise memory signal reporting is not enabled").
Prototype of #8376.
- Unit: new KfdIoctlTest.DbgTrapNodeAddressWatchAllocatesAndFreesSlots
(four distinct slots, ENOMEM on the fifth, freed slot reused,
out-of-range clear rejected); the device-snapshot test asserts the
watch-register capability. Full unit suite.
- E2E: debug_kernel.sh (CTest RocgdbDebug) gains a scenario that reads the
device buffer pointer at the launch line, sets a hardware watchpoint on
it from GPU context, and continues, asserting the watchpoint trips on the
kernel's store (old/new value, stop at the store line). No hard-coded VA.
- `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed.
- `./build/tests/rocjitsu_tests --gtest_filter='KfdIoctlTest.DbgTrapNodeAddressWatchAllocatesAndFreesSlots:WaveDebugTest.*'`: passed.
- `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed.
- `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed.
- `emulation/mirage/target/debug/mirage run --profile mi350x --gdb -- /bin/true`: returned `command not found: rocgdb` because the venv ROCgdb was not yet discoverable on `PATH` at this step; final-stack real ROCgdb coverage is recorded in row 21.
emulation/rocjitsu/demos/rocgdb-watchpoint.{md,sh,cast}: catch which GPU wave writes a buffer with a hardware data watchpoint -- no AMD GPU required.
Raise an illegal-instruction exception when a wave fetches undecodable code so ROCgdb stops at the faulting GPU PC instead of the wave silently vanishing. When an emulated wave fetched an undecodable instruction it was silently halted and retired. Under a debugger this is confusing: the wave simply disappears and the kernel "finishes" without the debugger ever learning that bad code executed. Real hardware raises an illegal-instruction exception that stops the wave at the faulting instruction. rocm-dbgapi maps `TRAPSTS.illegal_inst` (gfx9 bit 11) to `WAVE_STOP_REASON_ILLEGAL_INSTRUCTION` (architecture.cpp signaled_exceptions) and drives its event loop from the queue's `EC_QUEUE_WAVE_ILLEGAL_INSTRUCTION` exception (next_pending_event). - The compute unit gained an `IllegalInstHandler`, invoked from the two places `issue_instruction` fails to decode (the `InvalidInst` catch and the null-decode guard). The PC is left at the faulting instruction (not advanced) so the debugger reports the illegal instruction's own address. When no debugger is attached the handler is unset and the wave halts as before, so stray bad code still cannot abort the emulator. - `on_wave_illegal_instruction` sets `TRAPSTS.illegal_inst`, stops the wave, and reports an `EC_QUEUE_WAVE_ILLEGAL_INSTRUCTION` queue exception. - `report_wave_stopped` gained an exception-mask parameter (default = `EC_QUEUE_WAVE_TRAP`) so the s_trap, single-step, watchpoint and illegal-instruction paths share one serialize-and-notify helper. The bit position and exception code are named and cited. Memory-violation and math-error exceptions are separate changes: the emulator's memory model auto-populates any address (no fault signal), so a memory-violation model is larger and lands next. Prototype of #8377. - Unit: new `WaveDebugTest.IllegalInstructionStopsWaveUnderDebugger` (handler invoked, wave stopped at the faulting PC) and `IllegalInstructionWithoutDebuggerHalts` (no handler -> halts). Full suite. - E2E: `debug_kernel.sh` (CTest `RocgdbDebug`) gains a scenario that single-steps to a clean instruction boundary, overwrites that instruction with `0xffffffff`, continues, and asserts ROCgdb reports SIGILL at the faulting GPU PC with no misaligned/corrupted-state error. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.IllegalInstruction*'`: 2/2 passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `emulation/mirage/target/debug/mirage run --profile mi350x --gdb -- /bin/true`: returned `command not found: rocgdb` because the venv ROCgdb was not yet discoverable on `PATH` at this step; final-stack real ROCgdb coverage is recorded in row 21.
Fault a wave that dereferences an unmapped device address and report it to ROCgdb as SIGSEGV, instead of silently servicing the wild access from sparse backing store. A GPU wave that dereferenced an unmapped device address was silently serviced from sparse backing store, so a wild/out-of-bounds access looked like a normal (zeroed) access and the debugger never learned about it. Real hardware raises a memory-access fault that stops the wave. With illegal-instruction reporting this completes precise-exception handling for the two faults a memory model can detect. rocm-dbgapi maps `TRAPSTS.xnack_error` (gfx9 bit 28), not mode-gated, to `WAVE_STOP_REASON_MEMORY_VIOLATION` (architecture.cpp signaled_exceptions), and ROCgdb surfaces the queue's `EC_QUEUE_WAVE_MEMORY_VIOLATION` exception as SIGSEGV. - The compute unit gained a `MemoryViolationHandler`, invoked from `issue_instruction` for each active-lane global access whose address is not backed by any mapping (`GpuMemory::resolve_host_ptr` returns null). A memory fault outranks a watchpoint on the same instruction. The probe reuses the per-access address capture and runs after the PC advances. - `on_wave_memory_violation` sets `TRAPSTS.xnack_error` and reports an `EC_QUEUE_WAVE_MEMORY_VIOLATION` queue exception. Bit and code are cited. - Efficiency: the per-access watchpoint and memory-violation checks are now gated by a per-CU `debug_active_` flag the driver sets only while a debug session exists (ENABLE -> true, last DISABLE -> false), so undebugged execution pays no per-access debug cost. Previously the watchpoint capture ran for every global access unconditionally. Detects unmapped-address faults; misaligned/permission faults and math-error (FP-status) exceptions are not modeled. Stop location is imprecise (ROCgdb notes "precise memory violation signal reporting is not enabled"). Prototype of #8378. - Full unit suite (the `debug_active_` gate must not regress non-debug execution). - E2E: `debug_kernel.sh` (CTest `RocgdbDebug`) gains a scenario that runs a new `bad_access.hip` kernel (store through a wild pointer) and asserts ROCgdb reports SIGSEGV in the faulting kernel; the four existing scenarios (well-formed add_one) confirm no false-positive violations. - `cmake --build build --target rocjitsu_tests --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.*'`: passed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `emulation/mirage/target/debug/mirage run --profile mi350x --gdb -- /bin/true`: returned `command not found: rocgdb` because the venv ROCgdb was not yet discoverable on `PATH` at this step; final-stack real ROCgdb coverage is recorded in row 21. emulation/rocjitsu/demos/rocgdb-fault.{md,sh,cast}: catch a GPU memory-access fault (SIGSEGV) with ROCgdb -- no AMD GPU required.
… variables Reproduce the hardware dword-interleaved scratch layout and publish the per-wave scratch base so ROCgdb resolves scratch-resident kernel arguments and locals (`print`, `info args`). ROCgdb could inspect a stopped wave's registers, PC and disassembly, but scratch-resident kernel arguments and locals (e.g. `data`, `n`) reported "Cannot access memory at address private_lane#...". This was the last gap preventing ordinary source-level debugging: `print`, `info args`, and inspecting any spilled variable failed. rocm-dbgapi reads private memory with the hardware dword-interleaved swizzle, derives each wave's scratch base from the queue's COMPUTE_TMPRING_SIZE and scratch_backing_memory_location, and cross-checks it against the wave's architected FLAT_SCRATCH register. - Swizzle scratch to the HW layout. `flat_calculate_addresses` (dedicated SCRATCH and the FLAT private-aperture path) maps a lane's private byte offset `off` to `base + (off/4)*lane_count*4 + lane*4 + off%4`, and the L1 vector cache walks scratch with that per-dword stride. The interleave granule is a dword, matching rocdbgapi's private_lane interleave_size. Loads and stores share the layout, so execution stays self-consistent. - Publish the queue scratch fields. At dispatch the CP writes scratch_backing_memory_location and COMPUTE_TMPRING_SIZE into the ABI-stable amd_queue_t, mirroring CP firmware; the shader-engines-per-XCC divisor is plumbed from the topology so dbgapi's scratch_memory_region does not disable private access. - Serialize FLAT_SCRATCH. The CWSR serializer writes the wave's scratch base into flat_scratch's aliased SGPR slot (aliased_sgpr_end-6/-5, like VCC at -2), which dbgapi validates its computed base against (otherwise it prints "flat_scratch may be corrupted, private memory access is disabled"). gfx9 dword-interleaved private layout. Register/aperture derivation targets the gfx9.4 CDNA scratch model. Prototype of #8379. - Full engine suite (`rocjitsu_tests`): the swizzle changes every scratch access, so this guards execution correctness. `ScratchAddrCalcTest` updated to assert the swizzled per-lane addresses and swizzle metadata. - E2E: a new CTest RocgdbDebug scenario runs `print data`, `print n`, `print data[0]` and `info args` at a kernel breakpoint, asserting the values resolve with no "private_lane#" or "flat_scratch may be corrupted". - `cmake --build build --target all --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='L1VectorCacheTest.*:ScratchAddrCalcTest.*:WaveDebugTest.Cwsr*'`: 12/12 passed. - `./build/tests/rocjitsu_tests --gtest_filter='-*Benchmark*'`: 1675 passed, 1 skipped, 0 failed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - Real `mirage run --gdb` harness: passed private argument/pointer/value reads and all preceding breakpoint, step, watchpoint, SIGILL, and SIGSEGV scenarios. emulation/rocjitsu/demos/rocgdb-quickstart.{md,sh,cast} now inspects the kernel's source-level arguments (`info args`, `print n`, `print data[0]`).
Correlate every trapped wave to its real dispatch and workgroup and read each wave's own private memory, so ROCgdb can debug kernels with more than one wave. ROCgdb could debug only single-wave dispatches on the emulator. Any kernel with more than one wave (a workgroup over 64 work-items, or more than one workgroup) crashed rocm-dbgapi with "not in the same workgroup as the group_leader" or "os_queue_packet_id ... is not within [...]". Real GPU kernels are almost always multi-wave, so this made the debugger impractical. Five pieces on top of the single-wave debugger: 1. Real dispatch correlation. Report `kfd_runtime_info.ttmp_setup = 1`, set each stopped wave's spi_ttmps_setup, and serialize the SPI TTMPs: workgroup coordinates (TTMP8-10), position in workgroup (TTMP11[0:5]) and the dispatch's AQL packet id (queue ring index at fetch, threaded through DispatchEntry and Wavefront) in TTMP11[6:30], which dbgapi validates against the queue's [read_dispatch_id, write_dispatch_id) range. 2. Read-pointer lifecycle. The CP now keeps a private fetch_cursor (so held packets are not re-fetched) and, on a wave trap, the driver holds read_dispatch_id at the oldest trapped dispatch's packet id; SUSPEND_QUEUES clears the context save area once those waves complete so dbgapi prunes the wave and dispatch cleanly at process exit. Non-debug execution is unchanged. 3. Per-workgroup control-stack marking. The serializer orders waves by (dispatch, group ids, position) and marks the first/last wave of each workgroup, instead of the first/last of the whole queue (which mis-grouped every wave after the first). 4. Atomic capture. `on_wave_trap` defers the debugger report until the queue quiesces (no running waves), so all stopped waves are serialized once in one stable snapshot; re-serializing after each trap would shift every wave's save-area address and defeat dbgapi's register cache. Single-wave dispatches quiesce immediately, so their behavior is unchanged. 5. Per-wave scratch. The CP tags each wave with its scratch_scoreboard_id and rounds the per-wave scratch stride to the 1 KB COMPUTE_TMPRING_SIZE.WAVESIZE granule; the serializer writes the scoreboard id into the wave's COMPUTE_RELAUNCH record so dbgapi computes each wave's scratch_base and private reads work for every wave. gfx9.4 CDNA control-stack/scratch model. Very large dispatches are serialized in one snapshot at trap time (bounded by the queue's wave capacity). Prototype of #8380. - Full engine suite (`rocjitsu_tests`): CP/scratch/serializer changes must not regress non-debug execution. - E2E: a new `multi_wave.hip` (one workgroup, 128 threads = two waves) and a seventh CTest RocgdbDebug scenario that breaks inside the kernel, asserts `info threads` correlates both waves to workgroup (0,0,0) at positions /0 and /1, and that each wave's scratch-resident `local` reads its own value (thread 0 -> 3, thread 64 -> 451), with no grouping/packet-id fault. - `cmake --build build --target all --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='WaveDebugTest.Cwsr*:CommandProcessorTest.*'`: 4/4 passed. - `./build/tests/rocjitsu_tests --gtest_filter='-*Benchmark*'`: 1675 passed, 1 skipped, 0 failed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - Real `mirage run --gdb` harness: both waves stopped and correlated to workgroup `(0,0,0)` positions `/0` and `/1`; private `local` values were 3 and 451; kernel completed with `h[0]=3 h[64]=451 h[127]=892`. emulation/rocjitsu/demos/rocgdb-multiwave.{md,sh,cast}: debug a multi-wave GPU kernel and inspect each wave's private data.
Make the emulated GPU debugger discoverable now that it works end to end. ## Motivation The ROCgdb debugging guide and the four recorded demos were only reachable by knowing their paths. Linking them from the README index and collecting the demos in the guide makes GPU debugging with mirage a first-class, discoverable feature. ## Changes - Add a "Debugging with ROCgdb" row to the README's Getting-started docs table. - Add a Demos section to `rocgdb-debugging.md` listing the runnable quickstart / watchpoint / fault / multi-wave walkthroughs (each with a `.md`, `.sh`, and asciinema `.cast`), and how to regenerate a `.cast`. Prototype of #8381. ## Test Plan Documentation only; no code change. Markdown links verified against the in-tree doc and demo paths. ## Test Result - Markdown links verified against the in-tree guide and four demo triplets; section numbering remains sequential. - `cmake --build build --target all --parallel "$(nproc)"`: passed. - `./build/tests/rocjitsu_tests --gtest_filter='-*Benchmark*'`: 1675 passed, 1 skipped, 0 failed. - `cargo build --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - `cargo test --workspace --manifest-path emulation/mirage/Cargo.toml`: passed. - Real `mirage run --gdb` harness: passed all seven scenarios covering kernel breakpoints, three single-steps, displaced stepping, watchpoints, SIGILL, SIGSEGV, private variables, and two-wave correlation/private data.
The ROCgdb prototype previously converted s_trap directly into a host callback. That bypassed the trap base address registered by ROCr, so debugger-provided trap-handler code never executed and its architectural state transitions were not exercised. - Resolve per-process, per-GPU TBA/TMA state and enter configured handler code from s_trap. - Populate the gfx9.4 TTMP ABI and route TTMP operands to dedicated wave state. - Implement the scalar return, send-message, and hardware-register operations used by the ROCr handler. - Route doorbell and interrupt messages through KFD and publish CWSR only after the handler restores STATUS.HALT and returns. - Keep trap handlers opaque to the host: no direct C++ breakpoint callback remains. - Preserve debugger resume and single-step scheduling around handler execution. - Exercise assembled handler code in wave, command-processor, and KFD integration tests. - Build rocjitsu_shared and rocjitsu_tests. - Run focused WaveDebug, KFD real-wave trap, and quiesced-CU scheduling tests. - Run the non-benchmark rocjitsu suite. - Run real ROCgdb breakpoint and stepping scenarios through Mirage. - Build passed. - 13 focused tests passed. - 1675 non-benchmark tests passed with 1 skipped before final debugger-path refinements. - Real ROCgdb reached the runtime-provided trap handler, stopped at the GPU breakpoint, and completed three single-step stops; post-continue validation still exposes a displaced-step register restore issue.
## Motivation Executing the runtime-provided gfx9.4 trap handler exposed architectural state that the former host callback never exercised. ROCgdb could stop and single-step a wave, but continuing afterward corrupted helper-call targets, EXEC, and private-stack state, causing the wave to run away instead of completing. ## Technical Details - Resolve linked rel32 SETPC/SWAPPC targets against each code object load bias while preserving absolute 48-bit GPU addresses. - Preserve the interrupted EXEC mask across the ROCr handler doorbell exchange. - Initialize and retain the CDNA private stack/frame SGPRs through CWSR write-back. - Sign-extend scalar private-memory offsets and distinguish mapped GPU VAs from unavailable host translations. - Keep generator output and generated CDNA3/CDNA4 instruction semantics synchronized. - Discover Python ROCm SDK runtime libraries in the real ROCgdb harness. - Add regressions for linked helper calls, trap EXEC restoration, and signed scratch offsets. ## Test Plan - Build rocjitsu_shared and rocjitsu_tests. - Run focused wave-debug, scratch, KFD trap, and quiesced-CU tests. - Run the full non-benchmark rocjitsu test suite. - Run cargo test --workspace in Mirage. - Run all seven real ROCgdb scenarios through Mirage. ## Test Result - Build passed. - 18 focused tests passed. - 1678 non-benchmark tests passed with 1 skipped. - Mirage workspace tests and doc-tests passed. - All seven real ROCgdb scenarios passed: breakpoint/step/continue, displaced stepping, watchpoint, SIGILL, SIGSEGV, private memory, and multi-wave correlation.
Linked gfx950 helper calls form their target from GETPC followed by a signed rel32 addition and SWAPPC. Resolve the encoded target against the GETPC address rather than the SWAPPC instruction itself, and align the focused regression with the four-instruction linked-call sequence. Tested with two repeated real ROCgdb breakpoint/step/continue runs, all seven real ROCgdb scenarios, and the full non-benchmark rocjitsu suite (1678 passed, 1 skipped).
Classify enabled arithmetic traps in generated execute paths and propagate queue exceptions through the ROCr runtime-event ABI. Preserve stable CWSR state across queue suspension and resume, and accept all readable instruction mappings while rejecting true GPUVM faults.
Run each of the 89 gdb.rocm files in a bounded fresh daemon session, disable ROCjitsu backend logging, and record reproducible per-test summaries and backend hashes.
Keep illegal-instruction waves suspended when rocDBGAPI resumes their queue so ROCr can consume the fatal exception without the wave re-executing at the faulting PC. Track this lifecycle independently of debugger-restored architectural state and cover reset behavior.
Retain dispatch-relative workgroup coordinates on resident waves and publish the full tuple in CWSR records. Match restored records by the same tuple so rocDBGAPI reports correct dispatch and lane positions for multidimensional launches.
The CWSR codec reproduces the gfx9.4 record layout only, so a wave stop on any other part is declined. The topology still advertised HSA_CAP_TRAP_DEBUG_SUPPORT for every debug-capable GPU, which is the bit rocdbgapi keys agent_t::supports_debugging() on: a debugger could attach to an agent the simulator cannot service and only discover the mismatch once every individual stop came back declined. Clear the bit for parts the codec does not model, in debug_topology_for() and again in effective_topology_for() so a config that captured a real device's capability word cannot smuggle it back. The descriptive sub-capabilities stay driver-derived; they qualify a support that is no longer claimed. cwsr_layout_modelled_for_gc_ip_version() sits beside the arch-keyed predicate so the two spellings cannot drift apart. Also fix the TSan failure in DbgTrapDebuggerExitReapsSessionAndAllowsReenable: the reaper releases the debuggee after dropping debug_sessions_mutex_, so the closed notifier fd is not a happens-before for the wave release. Sample the wave under the CU's wave-state lock and poll for the release instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The TSan gate caught a data race in SimulationEngine::shutdown(): it clears contexts_ and async_queues_ on the VM thread while the host thread is inside request_exit(), which reads contexts_ and dereferences contexts_[0] to set the partition's idle_wakeup_ flag. rj_daemon's teardown() drives exactly this ordering, calling rj_vm_request_exit() before joining the engine thread, so a self-terminating engine can be mid-shutdown when the exit request lands. The result is a use-after-free on the partition context, not just a TSan warning. Guard the three foreign-thread wake paths (request_exit, primary_release, schedule_event_async) with a shared lock on a new contexts_mutex_, and take it exclusively around the teardown in shutdown(). Readers do not exclude one another, so async scheduling keeps its concurrency and only teardown blocks; the duplicated wake bodies collapse into wake_partition(). shutdown() takes the lock after joining the workers, so nothing it waits on can be holding it. run() had the same shape of bug on a different member: it returned exit_status_ by value without holding exit_mutex_, racing request_exit()'s assignment to it and copying the message string out from under the writer. Copy under the lock. Adds TerminationTest.RequestExitRacingShutdownIsSafe, which reproduces both races against the unfixed engine (TSan exit 66 at the same simulation.cpp lines the gate reported) and is clean across 60 consecutive runs after the fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kuhar
left a comment
There was a problem hiding this comment.
LGTM but maybe get an approval from Tony before landing
atgutier
left a comment
There was a problem hiding this comment.
MAJOR] emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/addr_calc_flat.h:143 —
Mixed FLAT accesses apply scratch addressing to global lanes
Reasoning: FLAT routing is lane-specific, but encountering one private-aperture lane sets the
instruction-wide scratch_swizzle flag. The memory pipeline then passes a scratch stride for every
active lane. For a wave64 multi-dword operation, a global lane’s second dword is accessed at address
- 256, rather than address + 4, whenever another lane targets scratch.
Evidence: memory_pipeline.cpp:497-505 forwards one stride for the whole request, and
l1_vector_cache.cpp:194-265 applies it to all lanes. Existing scratch tests cover uniformly private
lanes, not mixed private/global lanes.
Suggested fix: carry a per-lane scratch mask/stride, materialize per-element addresses, or split
private and global lanes into separate cache requests. Add mixed-lane flat_*_dwordx2 load and store
tests.
Confidence: high.
[MAJOR] emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/kmd/linux/simulated_kfd.cpp:3232 — CWSR resume
can restore into a recycled wave and expose a partially restored queue
Reasoning: resume_debug_queues() retains raw Wavefront* slot pointers after releasing their CU
locks, performs blocking CWSR reads, and later reads and mutates those slots without revalidating
their process, queue, or generation. The target can concurrently destroy its queue because cross-
process debug operations hold the debugger’s op_mutex_, not the target’s. Queue destruction resets
the permanent slot object, which can immediately be reused for another dispatch.
Additionally, apply_cwsr_to_wave() clears debug_suspended per wave. The engine can execute that wave
before the remaining waves and LDS records have been restored.
Evidence: unlocked matching and LDS restoration occur at 3296-3330; suspension is cleared at 3191-
3192. Queue destruction reaches command_processor.cpp:617-639, while wavefront.cpp:27-40 resets the
slot. The suspend failure rollback has the same retained-pointer problem at 3418-3457.
Suggested fix: keep all waves suspended until a transaction-wide commit, and identify waves by a
stable (CU, slot, process, queue, generation) descriptor that is revalidated under the CU lock
before every operation. Add destroy/reuse and multi-CU partial-restore race tests.
Confidence: high.
[MAJOR] emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/amdgpu/gpu_memory.h:324 — SDMA copies
dereference translated pointers after dropping their lifetime locks
Reasoning: copy_block() calls translate(), which returns a borrowed host pointer after releasing
vmid_mutex_ and the page-table lock, and then performs memcpy. Concurrent process teardown can
unregister the VMID and unmap the allocation before that copy.
The command processor holds hw_queue_mutex_ while processing the SDMA ring, but process close
unregisters the memory and unmaps allocations at simulated_kfd.cpp:858-885 before synchronizing
through unregister_queue() at 900-905. Thus the CP can dereference unmapped storage.
Evidence: the existing resolve_host_ptr() documentation at gpu_memory.h:173-176 explicitly says
translated pointers require remapping and teardown to be quiesced. Normal reads and writes instead
keep the page-table lock through their callback.
Suggested fix: perform each source read and destination write through a locked mapping callback, as
read_mapped() and write_mapped() do. Also consider unregistering/stopping queues before backing-
memory teardown. Add an SDMA-copy-versus-process-close sanitizer regression.
Confidence: high.
[MAJOR] emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/kmd/linux/simulated_kfd.cpp:4069 — Old detach
cleanup can corrupt a replacement debug session
Reasoning: DISABLE erases the session, unlocks debug_sessions_mutex_, performs target-wide cleanup,
and only afterward records handshake cancellation. Another debugger can ENABLE the same target
during that interval. The old cleanup can then erase the new session’s events, resume its stopped
waves, reopen its queue gates, revoke its newly installed target-memory routing, and cancel its
runtime handshake. The debugger-death reaper has the same erase-then-unlocked-cleanup window.
There is also a lost-cancellation race without reattachment: the runtime handshake validates the
session at 3595-3606; detach records cancellation; then the handshake erases that cancellation at
3621-3625, fails to notify the now-removed session, and waits for the 60-second deadline.
Evidence: replacement ENABLE commits at 4036-4043; target-wide cleanup is at 3012-3074;
acknowledgements and cancellations are keyed only by PID in simulated_kfd.h:481-491.
Suggested fix: retain a releasing tombstone or session generation until cleanup completes.
Generation-tag cleanup, events, memory routing, acknowledgements, and cancellation, and register the
handshake waiter atomically with session validation.
Confidence: high.
[MAJOR] emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/kmd/linux/simulated_kfd.cpp:3979 — Late failed
ENABLE double-closes the transferred notifier
Reasoning: daemon ENABLE adopts the transferred notifier into local sess.owned_dbg_fd before the
final target/debugger liveness checks at 4022-4027. A late failure destroys sess and closes the
descriptor. However, rj_vm.cpp:413-414 clears cmd->in_handle only when the ioctl succeeds, so
rj_daemon.cpp:421-424 leaves its ReceivedFds::notifier owning the same descriptor and closes it
again at request teardown.
Because daemon requests run concurrently, another thread can obtain the reused descriptor number
between those closes; the second close can then destroy an unrelated client socket, eventfd, or
transferred descriptor.
Suggested fix: defer notifier adoption until all failure-producing checks pass, or explicitly report
ownership transfer independently of the ioctl result and clear the transport handle whenever the
driver adopted it. Add an end-to-end late-liveness-failure test that verifies exactly one close.
Confidence: high.
…gdb/gold Conflicts were all in emulation/rocjitsu, between develop's cluster-barrier and decode-diagnostics work and this branch's ROCgdb support. Resolutions: - cdna5 vop3: develop refactored VBcnt onto the DecodeResult/emit_error decoder while this branch had moved it from vop3_alu.cpp to vop3_misc.cpp. Kept the relocation and ported develop's new-signature body, so no old-signature decode definition remains in the generated tree. - TTMPs stay in the wave's trap-temporary file (set_ttmp) rather than the SGPR allocation, and TTMP8 joins 6/7/9 there. Constants become file indices, and develop's two new TTMP8 tests read wf.ttmp(8). - Wavefront::halt keeps the CpCompletionNotice parameter alongside develop's named-barrier methods; release_wf keeps the deferred CP notification while gaining develop's barrier_wgs_ cleanup. - cluster_wg_placements_ and cluster_barriers_ are guarded by one mutex. Keeping both would have left the same map under two locks. Kept cluster_placements_mutex_ for its documented ordering, and mark_cluster_workgroup_complete now collects barrier peers and LDS pins under it, then acts after unlocking. - process_aql_packet takes both queue_packet_id and aql_packet_id; they are distinct fields in DispatchEntry. - Undecodable instructions still route through illegal_inst_handler_, now reporting via develop's decode_error.message(). Also updated for develop's renames: ROCJITSU_CODE_ARCH_GFX1250 -> ROCJITSU_CODE_ARCH_CDNA5 in wave_debug_test, the DecodeResult return in instruction_execution_harness_test, and "arch":"gfx1250" -> "cdna5" in config_test. 3336/3336 rocjitsu ctest tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FLAT routing is decided per lane against the private aperture, so a single wave can mix scratch lanes with global ones. The address calculation set scratch_swizzle and scratch_addr_stride on the whole instruction as soon as any one lane landed in the private aperture, and the memory pipeline then forwarded that stride for every active lane. For a multi-dword FLAT access this put a global lane's second dword at addr + wf_size*4 rather than addr + 4 whenever some other lane targeted scratch. Uniform waves were unaffected, which is why the existing scratch tests -- all of which use uniformly private lanes -- did not catch it. Record which lanes were actually swizzled in scratch_lane_mask (equal to the active mask for dedicated SCRATCH ops, where every lane is private) and issue the private and global lanes as separate cache requests. Both L1 paths index the register buffer by absolute lane, so splitting by mask leaves the data layout unchanged. Adds a mixed-aperture flat_store_dwordx2 regression test. Without the fix the global lane's second dword is found at addr + 256 and the test fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
copy_block() resolved each endpoint with translate(), which drops both vmid_mutex_ and the page-table lock before returning a borrowed host pointer, and only then ran the memcpy. gpu_memory.h already documents that a translated pointer is valid only while remapping and teardown are quiesced, and the SDMA ring does not quiesce them: the command processor holds hw_queue_mutex_, while process close unregisters memory and unmaps allocations before it synchronizes through unregister_queue(). A close racing an in-flight SDMA packet could therefore free the storage between resolution and the copy. Add with_translated_span(), which applies exactly translate()'s resolution rules -- page-bounded, contiguous, host-backed, addressable -- but hands the pointer to a callback that runs while the mapping is still held, plus copy_from_mapped()/copy_to_mapped() wrappers. copy_block() now uses those, so the accept/reject decision for every chunk is unchanged (including the passthrough and read_client_memory fallbacks) and no host pointer outlives the lock that keeps its allocation alive. This is the same discipline read_mapped()/write_mapped() already use for ordinary reads and writes. The teardown ordering in simulated_kfd -- stopping queues before releasing backing memory -- is worth tightening separately; this change removes the dereference-after-unmap on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nger fail Daemon-mode ENABLE moved the SCM_RIGHTS-transferred notifier into sess.owned_dbg_fd before its final target and debugger liveness checks, on the reasoning that a descriptor substituted into our fd space is ours on every path. The transport does not agree: rj_vm.cpp clears cmd->in_handle only when the ioctl returns 0, so on a late failure sess was destroyed (closing the fd) and the transport then closed the same number again. Daemon requests run concurrently, so another thread can be handed the reused descriptor number between those two closes, and the second close then destroys an unrelated client socket, eventfd, or transferred descriptor. Adopt the notifier where the authorized memory fd is already adopted -- past every failure check, immediately before the session is committed. Both transferred fds now follow one rule: the driver takes them only on success, and the caller reclaims them on every early return, which is exactly what the transport's cmd->result == 0 gate implements. No transport change is needed. Not covered by a new test: reaching the window needs daemon mode plus a target or debugger that exits between the entry checks and the commit checks, which there is no deterministic way to stage in the current harness. The existing ENABLE tests are local mode, where the session never owns dbg_fd. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
runtime_debugger_handshake() validated the session under debug_sessions_mutex_, released it, and only then cleared any stale ack or cancellation for the pid. An explicit DBG_TRAP_DISABLE erases the session, drops the same mutex, and calls cancel_runtime_handshake() afterwards, so a detach landing in that gap recorded its cancellation before the clear ran -- and the clear then erased it. Nothing was left to satisfy the wait, and the session it would have been delivered to was already gone, so no acker remained either: the inferior sat out the full 60-second liveness deadline on a detach that was supposed to release it immediately. Move the clear inside the same critical section as the lookup. A detach now either erases the session before the lookup, in which case the handshake returns early, or blocks on the mutex until after the clear, in which case its cancellation lands on the wait -- which is what the existing comment already claimed the ordering guaranteed. The lock order (debug_sessions_mutex_ then runtime_handshake_mutex_) is the one the debugger-death reaper already uses. The related reattachment hazard in the same area -- DISABLE erasing the session, unlocking, and running target-wide cleanup while a replacement ENABLE can commit a new session underneath it -- is not addressed here. It needs a releasing tombstone or session generation so cleanup can tell its own state from its successor's, which is a larger change than this one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
apply_cwsr_to_wave() cleared debug_suspended as the last step of applying each wave's record. Because resume_debug_queues() applies the records one wave at a time, the first wave became runnable while its queue-mates -- and the LDS images written in the same loop -- were still being restored, so the engine could execute a wave against partially restored queue state. The bit already had a natural commit point: the locked pass that follows the restore loop sets debug_suspended for every wave in the queue, and its comment already says the queue gate stays closed until all of them have consumed their CWSR state. Leave the bit set in apply_cwsr_to_wave() and let that pass be the only writer. Final state is unchanged on both the success and failure paths -- a rejected image still lands in the same pass, so a temporarily suspended wave is not stranded. Not addressed: resume_debug_queues() still holds raw Wavefront* and ComputeUnitCore* across the blocking CWSR reads and the unlocked match loop, so a queue destroyed concurrently by the target can recycle a slot underneath them. Closing that needs waves identified by a revalidated (CU, slot, process, queue, generation) descriptor rather than a pointer, which is a larger change than this one and wants destroy/reuse and multi-CU partial-restore tests alongside it. Verified against the 3337-test rocjitsu ctest suite. The ROCgdb .exp suite, which is what exercises this path end to end, is not in this repository and was not run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gdb/gold Develop moved 31 commits since the previous merge. GitHub had marked the PR dirty, which is why no pull_request-event workflow would dispatch. The substantive conflict is that both sides added a trailing optional parameter to L1VectorCache::load/store: this branch's addr_stride, which carries the scratch swizzle, and develop's element_lane_masks, which carries per-element lane validity. They are independent features, so both parameters are kept and the implementation composes them -- the swizzle path now materializes addresses per element and skips elements a lane is not valid for, instead of striding over them. With no element masks it walks exactly the lanes and bytes the uniform path would. memory_pipeline forwards both, keeping the private/global request split from the mixed-aperture fix. Other resolutions: - cdna5 addr_calc: keep develop's decode_buffer_resource() but read the SRD through read_scalar_selector() rather than read_sgpr(), so a descriptor sourced from TTMPs resolves against the trap-temporary file. - config_test: develop's 9-CU-per-SE and drm_cu_active_number assertions are correct -- the config really is 8x4x9 -- so this branch's stale 8u goes, and its assign_queue_cp assertions stay. - shared_infra_test: both sides' new L1 tests kept; develop's calls gain the addr_stride argument the merged signature inserts ahead of the element masks. Two breakages inherited from develop, fixed here: - xcd_distribution_test.cpp reads configs/gfx950_cdna4.json, which develop no longer ships -- it renamed the file to gfx950_mi355x.json in a different commit -- and its geometry constants still describe 8 CUs per SE when the config now has 9. The test fails on develop as it stands. Repointed at the current config and corrected to 4 SEs x 9 CUs, which leaves what it asserts unchanged. - The same rename left this branch's assign_queue_cp(queue_ordinal) unmatched by develop's new no-arg call sites; each fixture is fresh, so the ordinals that reproduce the old rotation counter are 0, qi, 0. 3359/3359 rocjitsu ctest tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pre-commit check failed⛔ pre-commit failed Please run locally:
This repo uses |
The merged signature fits on one line once queue_packet_id and aql_packet_id sit together, and the repo's pre-commit clang-format hook says so. This header was resolved by hand during the first develop merge and missed the format sweep that covered the other files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Motivation
Debugging a GPU kernel has required a physical AMD GPU. rocjitsu already
emulates
the AMD KFD closely enough to run real ROCr/HIP workloads, but it had no
debug
surface: `AMDKFD_IOC_DBG_TRAP` was unimplemented, waves could not be
stopped, and
there was no CWSR state for a debugger to read or write.
This PR closes that gap. **Real ROCgdb and real `librocm-dbgapi`,
unmodified, now
attach to a workload running on the emulated GPU and debug its kernels
end to end**,
with no AMD hardware present: breakpoints, single-step, watchpoints,
multi-wave
`info threads`, source-level locals out of private/scratch, SIGILL and
SIGSEGV on GPU
faults, and clean detach. One command drives it:
```bash
mirage run --profile mi350x --gdb -- ./my_hip_app
```
The bar this is measured against is the official ROCgdb `gdb.rocm` test
suite, which
is written for hardware and knows nothing about the emulator. All 89 of
its files
pass.
## Technical Details
All KFD debug behaviour is mirrored from the real driver source
(`amd/amdkfd/{kfd_chardev.c,kfd_debug.c,kfd_topology.c}`,
amdgpu-6.16.13),
cross-checked against a physical MI300X and against the in-tree
`projects/rocdbgapi` client. Changes are confined to `emulation/`.
**KFD debug protocol**
(`kmd/linux/{simulated_kfd,remote_driver,rj_daemon,interposer}`)
- `DBG_TRAP` sub-operation dispatcher with the driver's validation
ladder, authorized
by a *real* ptrace relationship and marshalled over the daemon. The
debugger and the
inferior are separate processes, so in-process mode cannot host this and
daemon mode
is mandatory.
- Debug sessions keyed by inferior pid, so ROCgdb can attach before the
inferior ever
opens `/dev/kfd`; full attach/detach lifecycle, `GET_DEVICE_SNAPSHOT`,
`GET_QUEUE_SNAPSHOT`, `SET_EXCEPTIONS_ENABLED` / `SET_FLAGS`, and
`SET_WAVE_LAUNCH_OVERRIDE` validated against the gfx9.4 trap mask.
- Debuggee memory access authorized through the daemon (debugger-opened
proc memory
and pinned process descriptors travel with the enable request),
preserving remote
`DONTFORK` semantics and naming the synthetic KFD marker as `/dev/kfd`
in proc-map
snapshots so fork and `gcore` behave as they do on hardware.
- Runtime enable/disable handshake reported only to an attached
debugger, `-ESRCH`
(not `-EPERM`) once the target's KFD state is gone, and suspend/resume
of
already-destroyed queues answered so rocdbgapi can retire them during
teardown.
**Engine wave-stop and CWSR**
(`vm/amdgpu/{compute_unit,wavefront,command_processor}`,
`kmd/linux/cwsr.{h,cpp}`)
- Waves stop on `s_trap` for the KFD debugger; stopped-wave state is
serialized into
the real CWSR area layout and written back on resume.
- The runtime's own gfx9.4 trap handler is executed rather than shimmed.
A wave frozen
inside it needs the *interrupted* architectural state published, not the
handler's,
so EXEC and STATUS are shadowed in both directions: a debugger's edit
lands on the
state `s_rfe` restores instead of the register the handler is still
running under.
- Deterministic single-step via `TRAPSTS.trap_after_inst`; reserved
debugger memory for
displaced stepping; GPU address watchpoints; illegal instruction
reported as SIGILL;
memory violation reported as SIGSEGV; private/scratch reads so `print`
and
`info args` resolve.
- Multi-wave: atomic queue resume, wave reclaim on queue destroy,
scheduler-locked
residency, 3D workgroup coordinates in CWSR records, and gfx950 LDS
preservation.
- Bounded functional-mode scheduling quantum while debugging so active
CUs make fair
progress without starving peer waves.
**ISA and memory fixes surfaced by the suite** (`isa/arch/amdgpu/`,
`vm/amdgpu/`)
Running real debugger workloads exercised paths nothing else had: linked
helper calls
resolved against the `GETPC` base, ALU exception classification in
generated execute
paths, scalar memory access faults, signed scratch offsets, unaligned
swizzled scratch,
cross-XCD atomic coherence, the `v_bcnt` accumulator, pageable host
copies through SDMA,
and `s_sleep`'s architected delay. Generator changes live in
`lib/python/amdisa/` with
the generated output regenerated, not hand-edited.
**Debugger UX, docs, demos.** `mirage run --gdb` / `--gdb-ex`;
`docs/rocgdb-debugging.md`; four recorded walkthroughs under `demos/`
(quickstart,
watchpoint, fault, multi-wave), each with a `.md` explainer, a runnable
`.sh`, and an
asciinema `.cast`.
**Test tooling.** `tools/run_rocgdb_official.py` runs each `gdb.rocm`
file in a fresh,
bounded, verified Mirage daemon session and records a per-test summary
plus backend
hashes; `tools/repeat_rocgdb_test.sh` repeats a subset serially under an
`flock` (two
concurrent Mirage sessions collide and manufacture failures) for chasing
intermittents.
Both archive the daemon's `host.log` alongside `gdb.log`. The emulated
KFD's
diagnostics land there and nowhere else, and three debugger defects
fixed in this PR
were invisible until it was collected.
## Issue Tracking
Epic: #8276
Closes #8364, closes #8365, closes #8366, closes #8367, closes #8368,
closes #8369,
closes #8370, closes #8371, closes #8372, closes #8373, closes #8374,
closes #8375,
closes #8376, closes #8377, closes #8378, closes #8379, closes #8380,
closes #8381.
| Item | Work |
|---|---|
| #8364 | Authorize `DBG_TRAP` by real ptrace + daemon transport |
| #8365 | Key debug sessions by inferior pid so rocgdb can attach |
| #8366 | Complete the `DBG_TRAP` attach/detach lifecycle |
| #8367 | Enumerate compute queues in `GET_QUEUE_SNAPSHOT` |
| #8368 | Stop a GPU wave on `s_trap` for the KFD debugger |
| #8369 | Serialize stopped-wave state into the CWSR area layout |
| #8370 | Raise a debug event and serialize the CWSR when a wave traps |
| #8371 | Drive GPU wave debugging end to end with ROCgdb |
| #8372 | `mirage run --gdb` one-command debugging |
| #8373 | Deterministic single-step via `TRAPSTS.trap_after_inst` |
| #8374 | Reserve debugger memory for step-over |
| #8375 | Validate `SET_WAVE_LAUNCH_OVERRIDE` against the gfx9.4 trap
mask |
| #8376 | GPU address watchpoints |
| #8377 | Illegal instruction reported as SIGILL |
| #8378 | Memory-access violation reported as SIGSEGV |
| #8379 | Private/scratch reads for `print` / `info args` |
| #8380 | Multi-wave GPU kernel debugging |
| #8381 | ROCgdb debugging guide and demos index |
Built on the already-merged foundations #8280, #8281, #8353 and #8363.
## Test Plan
1. **Official ROCgdb suite.** All 89 `gdb.rocm` test files, each in its
own fresh,
bounded Mirage daemon session, run serially via
`tools/run_rocgdb_official.py --profile mi350x`. The suite is upstream
ROCgdb's own,
written for hardware and unmodified.
2. **rocjitsu unit/integration suite.** Full non-benchmark `ctest` run,
including the 66
test cases added here (19 in the new `wave_debug_test.cpp`, 34 in
`kfd_ioctl_test.cpp`,
the rest across shared-infra, VM, interposer and decode tests).
3. **End-to-end smoke test in CI.** New `RocgdbDebug.KernelBreakpoint`
ctest target drives
real ROCgdb through `mirage run` to set a kernel breakpoint, stop, read
wave registers,
and continue to a correct result. Gated on `rocgdb` and `hipcc` being
present; skips
cleanly otherwise.
## Test Result
Both suites green at `c998c5cd` (branch tip):
| Suite | Result |
|---|---|
| Official ROCgdb `gdb.rocm` | **89/89 files pass, 0 FAIL**. 2376 PASS
over 2407 executed assertions (9 UNSUPPORTED, 7 KFAIL, 3 XFAIL, 6
WARNING, 6 UNTESTED) |
| rocjitsu `ctest` (non-benchmark) | **2492 tests, 0 failures** (4
skipped by design) |
## Submission Checklist
- [x] Look over the contributing guidelines at
https://github.com/ROCm/rocm-systems/blob/develop/CONTRIBUTING.md.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit e5109e3)
…t calls (#10444) ## Motivation hipTensor and rocThrust gfx1250 objects are refused at code-object load time with "indirect branch or call target recovery is not implemented for relocated kernel text" on `s_swap_pc_i64`. A refused object leaves HIP holding an invalid kernel handle, so the failure surfaces as a segfault rather than a diagnostic. The fix is upstream as #10376; this backports it to `release/bkc/therock-10.1-20260811`. ## Commits Cherry-Picked | # | Upstream | PR | Backport | Subject | |---|----------|-----|----------|---------| | 1 | `b6d838028e` | #10197 | `846fb16d12` | [rocjitsu][docs] Refresh architecture support documentation | | 2 | `5c8e33e391` | #10200 | `db5c58b1be` | [rocjitsu] Define standalone decoder ownership | | 3 | `be1f91998f` | #10199 | `ed102baf1c` | [rocjitsu] Apply async IOFFSET to LDS addresses | | 4 | `fda635e5b4` | — | `ad32ff7c18` | [rocjitsu] Extract per-scope rollback and object-level checks from translate() | | 5 | `8f75eb781e` | — | `4c0cb05207` | [rocjitsu] Emit leading V_NOPs ahead of a gfx1250 MODE write | | 6 | `00aa1e260f` | #9941 | `ded43b1787` | fix(rocjitsu): disable zero-count TDM descriptors | | 7 | `c810647609` | #10192 | `50ca7f1732` | [rocjitsu] Apply MUBUF LDS-load instruction offset to the LDS address | | 8 | `1d7866269d` | #9560 | `2e6bf26943` | [rocjitsu] code rewrite condition validator | | 9 | `95b6c410bb` | #10175 | `8418901076` | [rocjitsu] Fix ds_read_b64_tr_b16 lane layout in gfx950 simulator | | 10 | `bfaebcfb5c` | #10240 | `c4eac1e1f7` | [rocjitsu][util] Add result and diagnostic utilities | | 11 | `dbb7221fa7` | #9775 | `cdd0993a5f` | fix(rocjitsu): route tensor DMA through wave VMID | | 12 | `2779247222` | #9779 | `7bfeba6f5d` | feat(rocjitsu): Lazy vgpr allocation system | | 13 | `a659172a31` | #9936 | `180cf00325` | fix(rocjitsu): honor TDM tensor bounds across iterations | | 14 | `60c29efb39` | #10204 | `370bfb9796` | [rocjitsu] Fix gfx950 decoder extension handling | | 15 | `c1dba564e7` | #10345 | `536110a083` | [rocjitsu] Model gfx1250 wave ABI state | | 16 | `7f71be9f32` | #10346 | `3f922ad5f2` | [rocjitsu] Model gfx1250 barrier synchronization | | 17 | `bea7ac8937` | #10368 | `7b5ecf6c7b` | [rocjitsu] Replace decoder exceptions with results | | 18 | `f2e878694e` | #10371 | `8cf750d23f` | [rocjitsu] Dispatch operand validation by type | | 19 | `dd619bed77` | #9735 | `1a612c47c0` | fix(rocjitsu): respect same-wave LDS ordering | | 20 | `c5fae8d2b2` | #10137 | `4360c4b978` | [rocjitsu] Rename gfx1250 to cdna5 for enums, configs, amdisa codegen helpers and generated symbols. (NFC) | | 21 | `b05a587f89` | #10274 | `bfb5f9fab0` | [rocjitsu] Align CDNA4 MI350X configs with captured topology | | 22 | `f81f9d40db` | #10362 | `8177b5725c` | feat(rocjitsu): cdna5 OOB behavior implementation | | 23 | `53a939bd00` | #10283 | `141b109432` | [rocjitsu] add per-XCD workgroup dispatch observability | | 24 | `a0fb11bc17` | #10414 | `6bb4c5a959` | [rocjitsu] Refactor test encoding handling in execution tests (NFC) | | 25 | `e5109e315e` | #9844 | `322ac26b44` | feat(emulation): debug emulated GPU kernels with real ROCgdb | | 26 | `efa24f23c9` | — | `c3c9641073` | rocjitsu: Point the XCD distribution test at the config that exists | | 27 | `8b7763ef5e` | #10376 | `c3f419555a` | [rocjitsu] Translate gfx1250 lane-banked and kernarg-supplied indirect calls | ## Issue Tracking [ROCM-29436] ## Submission Checklist - [x] Look over the contributing guidelines at https://github.com/ROCm/rocm-systems/blob/develop/CONTRIBUTING.md. [ROCM-29346]: https://amd-hub.atlassian.net/browse/ROCM-29346?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [ROCM-29436]: https://amd-hub.atlassian.net/browse/ROCM-29436?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Signed-off-by: Tony Gutierrez <anthony.gutierrez@amd.com> Signed-off-by: hanhanW <hanhan0912@gmail.com> Co-authored-by: Jakub Kuderski <jakub@nod-labs.com> Co-authored-by: James Newling <james.newling@gmail.com> Co-authored-by: Fei Kou <feikou@meta.com> Co-authored-by: Austin Lu <austin.lu@amd.com> Co-authored-by: Fei Kou <feikou@anthropic.com> Co-authored-by: Erick Ochoa Lopez <eochoalo@amd.com> Co-authored-by: Han-Chung Wang <hanhan0912@gmail.com> Co-authored-by: Rosa, Ada <arosa@amd.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Motivation
Debugging a GPU kernel has required a physical AMD GPU. rocjitsu already
emulates
the AMD KFD closely enough to run real ROCr/HIP workloads, but it had no
debug
surface: `AMDKFD_IOC_DBG_TRAP` was unimplemented, waves could not be
stopped, and
there was no CWSR state for a debugger to read or write.
This PR closes that gap. **Real ROCgdb and real `librocm-dbgapi`,
unmodified, now
attach to a workload running on the emulated GPU and debug its kernels
end to end**,
with no AMD hardware present: breakpoints, single-step, watchpoints,
multi-wave
`info threads`, source-level locals out of private/scratch, SIGILL and
SIGSEGV on GPU
faults, and clean detach. One command drives it:
```bash
mirage run --profile mi350x --gdb -- ./my_hip_app
```
The bar this is measured against is the official ROCgdb `gdb.rocm` test
suite, which
is written for hardware and knows nothing about the emulator. All 89 of
its files
pass.
## Technical Details
All KFD debug behaviour is mirrored from the real driver source
(`amd/amdkfd/{kfd_chardev.c,kfd_debug.c,kfd_topology.c}`,
amdgpu-6.16.13),
cross-checked against a physical MI300X and against the in-tree
`projects/rocdbgapi` client. Changes are confined to `emulation/`.
**KFD debug protocol**
(`kmd/linux/{simulated_kfd,remote_driver,rj_daemon,interposer}`)
- `DBG_TRAP` sub-operation dispatcher with the driver's validation
ladder, authorized
by a *real* ptrace relationship and marshalled over the daemon. The
debugger and the
inferior are separate processes, so in-process mode cannot host this and
daemon mode
is mandatory.
- Debug sessions keyed by inferior pid, so ROCgdb can attach before the
inferior ever
opens `/dev/kfd`; full attach/detach lifecycle, `GET_DEVICE_SNAPSHOT`,
`GET_QUEUE_SNAPSHOT`, `SET_EXCEPTIONS_ENABLED` / `SET_FLAGS`, and
`SET_WAVE_LAUNCH_OVERRIDE` validated against the gfx9.4 trap mask.
- Debuggee memory access authorized through the daemon (debugger-opened
proc memory
and pinned process descriptors travel with the enable request),
preserving remote
`DONTFORK` semantics and naming the synthetic KFD marker as `/dev/kfd`
in proc-map
snapshots so fork and `gcore` behave as they do on hardware.
- Runtime enable/disable handshake reported only to an attached
debugger, `-ESRCH`
(not `-EPERM`) once the target's KFD state is gone, and suspend/resume
of
already-destroyed queues answered so rocdbgapi can retire them during
teardown.
**Engine wave-stop and CWSR**
(`vm/amdgpu/{compute_unit,wavefront,command_processor}`,
`kmd/linux/cwsr.{h,cpp}`)
- Waves stop on `s_trap` for the KFD debugger; stopped-wave state is
serialized into
the real CWSR area layout and written back on resume.
- The runtime's own gfx9.4 trap handler is executed rather than shimmed.
A wave frozen
inside it needs the *interrupted* architectural state published, not the
handler's,
so EXEC and STATUS are shadowed in both directions: a debugger's edit
lands on the
state `s_rfe` restores instead of the register the handler is still
running under.
- Deterministic single-step via `TRAPSTS.trap_after_inst`; reserved
debugger memory for
displaced stepping; GPU address watchpoints; illegal instruction
reported as SIGILL;
memory violation reported as SIGSEGV; private/scratch reads so `print`
and
`info args` resolve.
- Multi-wave: atomic queue resume, wave reclaim on queue destroy,
scheduler-locked
residency, 3D workgroup coordinates in CWSR records, and gfx950 LDS
preservation.
- Bounded functional-mode scheduling quantum while debugging so active
CUs make fair
progress without starving peer waves.
**ISA and memory fixes surfaced by the suite** (`isa/arch/amdgpu/`,
`vm/amdgpu/`)
Running real debugger workloads exercised paths nothing else had: linked
helper calls
resolved against the `GETPC` base, ALU exception classification in
generated execute
paths, scalar memory access faults, signed scratch offsets, unaligned
swizzled scratch,
cross-XCD atomic coherence, the `v_bcnt` accumulator, pageable host
copies through SDMA,
and `s_sleep`'s architected delay. Generator changes live in
`lib/python/amdisa/` with
the generated output regenerated, not hand-edited.
**Debugger UX, docs, demos.** `mirage run --gdb` / `--gdb-ex`;
`docs/rocgdb-debugging.md`; four recorded walkthroughs under `demos/`
(quickstart,
watchpoint, fault, multi-wave), each with a `.md` explainer, a runnable
`.sh`, and an
asciinema `.cast`.
**Test tooling.** `tools/run_rocgdb_official.py` runs each `gdb.rocm`
file in a fresh,
bounded, verified Mirage daemon session and records a per-test summary
plus backend
hashes; `tools/repeat_rocgdb_test.sh` repeats a subset serially under an
`flock` (two
concurrent Mirage sessions collide and manufacture failures) for chasing
intermittents.
Both archive the daemon's `host.log` alongside `gdb.log`. The emulated
KFD's
diagnostics land there and nowhere else, and three debugger defects
fixed in this PR
were invisible until it was collected.
## Issue Tracking
Epic: #8276
Closes #8364, closes #8365, closes #8366, closes #8367, closes #8368,
closes #8369,
closes #8370, closes #8371, closes #8372, closes #8373, closes #8374,
closes #8375,
closes #8376, closes #8377, closes #8378, closes #8379, closes #8380,
closes #8381.
| Item | Work |
|---|---|
| #8364 | Authorize `DBG_TRAP` by real ptrace + daemon transport |
| #8365 | Key debug sessions by inferior pid so rocgdb can attach |
| #8366 | Complete the `DBG_TRAP` attach/detach lifecycle |
| #8367 | Enumerate compute queues in `GET_QUEUE_SNAPSHOT` |
| #8368 | Stop a GPU wave on `s_trap` for the KFD debugger |
| #8369 | Serialize stopped-wave state into the CWSR area layout |
| #8370 | Raise a debug event and serialize the CWSR when a wave traps |
| #8371 | Drive GPU wave debugging end to end with ROCgdb |
| #8372 | `mirage run --gdb` one-command debugging |
| #8373 | Deterministic single-step via `TRAPSTS.trap_after_inst` |
| #8374 | Reserve debugger memory for step-over |
| #8375 | Validate `SET_WAVE_LAUNCH_OVERRIDE` against the gfx9.4 trap
mask |
| #8376 | GPU address watchpoints |
| #8377 | Illegal instruction reported as SIGILL |
| #8378 | Memory-access violation reported as SIGSEGV |
| #8379 | Private/scratch reads for `print` / `info args` |
| #8380 | Multi-wave GPU kernel debugging |
| #8381 | ROCgdb debugging guide and demos index |
Built on the already-merged foundations #8280, #8281, #8353 and #8363.
## Test Plan
1. **Official ROCgdb suite.** All 89 `gdb.rocm` test files, each in its
own fresh,
bounded Mirage daemon session, run serially via
`tools/run_rocgdb_official.py --profile mi350x`. The suite is upstream
ROCgdb's own,
written for hardware and unmodified.
2. **rocjitsu unit/integration suite.** Full non-benchmark `ctest` run,
including the 66
test cases added here (19 in the new `wave_debug_test.cpp`, 34 in
`kfd_ioctl_test.cpp`,
the rest across shared-infra, VM, interposer and decode tests).
3. **End-to-end smoke test in CI.** New `RocgdbDebug.KernelBreakpoint`
ctest target drives
real ROCgdb through `mirage run` to set a kernel breakpoint, stop, read
wave registers,
and continue to a correct result. Gated on `rocgdb` and `hipcc` being
present; skips
cleanly otherwise.
## Test Result
Both suites green at `c998c5cd` (branch tip):
| Suite | Result |
|---|---|
| Official ROCgdb `gdb.rocm` | **89/89 files pass, 0 FAIL**. 2376 PASS
over 2407 executed assertions (9 UNSUPPORTED, 7 KFAIL, 3 XFAIL, 6
WARNING, 6 UNTESTED) |
| rocjitsu `ctest` (non-benchmark) | **2492 tests, 0 failures** (4
skipped by design) |
## Submission Checklist
- [x] Look over the contributing guidelines at
https://github.com/ROCm/rocm-systems/blob/develop/CONTRIBUTING.md.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Motivation
Debugging a GPU kernel has required a physical AMD GPU. rocjitsu already
emulates
the AMD KFD closely enough to run real ROCr/HIP workloads, but it had no
debug
surface: `AMDKFD_IOC_DBG_TRAP` was unimplemented, waves could not be
stopped, and
there was no CWSR state for a debugger to read or write.
This PR closes that gap. **Real ROCgdb and real `librocm-dbgapi`,
unmodified, now
attach to a workload running on the emulated GPU and debug its kernels
end to end**,
with no AMD hardware present: breakpoints, single-step, watchpoints,
multi-wave
`info threads`, source-level locals out of private/scratch, SIGILL and
SIGSEGV on GPU
faults, and clean detach. One command drives it:
```bash
mirage run --profile mi350x --gdb -- ./my_hip_app
```
The bar this is measured against is the official ROCgdb `gdb.rocm` test
suite, which
is written for hardware and knows nothing about the emulator. All 89 of
its files
pass.
## Technical Details
All KFD debug behaviour is mirrored from the real driver source
(`amd/amdkfd/{kfd_chardev.c,kfd_debug.c,kfd_topology.c}`,
amdgpu-6.16.13),
cross-checked against a physical MI300X and against the in-tree
`projects/rocdbgapi` client. Changes are confined to `emulation/`.
**KFD debug protocol**
(`kmd/linux/{simulated_kfd,remote_driver,rj_daemon,interposer}`)
- `DBG_TRAP` sub-operation dispatcher with the driver's validation
ladder, authorized
by a *real* ptrace relationship and marshalled over the daemon. The
debugger and the
inferior are separate processes, so in-process mode cannot host this and
daemon mode
is mandatory.
- Debug sessions keyed by inferior pid, so ROCgdb can attach before the
inferior ever
opens `/dev/kfd`; full attach/detach lifecycle, `GET_DEVICE_SNAPSHOT`,
`GET_QUEUE_SNAPSHOT`, `SET_EXCEPTIONS_ENABLED` / `SET_FLAGS`, and
`SET_WAVE_LAUNCH_OVERRIDE` validated against the gfx9.4 trap mask.
- Debuggee memory access authorized through the daemon (debugger-opened
proc memory
and pinned process descriptors travel with the enable request),
preserving remote
`DONTFORK` semantics and naming the synthetic KFD marker as `/dev/kfd`
in proc-map
snapshots so fork and `gcore` behave as they do on hardware.
- Runtime enable/disable handshake reported only to an attached
debugger, `-ESRCH`
(not `-EPERM`) once the target's KFD state is gone, and suspend/resume
of
already-destroyed queues answered so rocdbgapi can retire them during
teardown.
**Engine wave-stop and CWSR**
(`vm/amdgpu/{compute_unit,wavefront,command_processor}`,
`kmd/linux/cwsr.{h,cpp}`)
- Waves stop on `s_trap` for the KFD debugger; stopped-wave state is
serialized into
the real CWSR area layout and written back on resume.
- The runtime's own gfx9.4 trap handler is executed rather than shimmed.
A wave frozen
inside it needs the *interrupted* architectural state published, not the
handler's,
so EXEC and STATUS are shadowed in both directions: a debugger's edit
lands on the
state `s_rfe` restores instead of the register the handler is still
running under.
- Deterministic single-step via `TRAPSTS.trap_after_inst`; reserved
debugger memory for
displaced stepping; GPU address watchpoints; illegal instruction
reported as SIGILL;
memory violation reported as SIGSEGV; private/scratch reads so `print`
and
`info args` resolve.
- Multi-wave: atomic queue resume, wave reclaim on queue destroy,
scheduler-locked
residency, 3D workgroup coordinates in CWSR records, and gfx950 LDS
preservation.
- Bounded functional-mode scheduling quantum while debugging so active
CUs make fair
progress without starving peer waves.
**ISA and memory fixes surfaced by the suite** (`isa/arch/amdgpu/`,
`vm/amdgpu/`)
Running real debugger workloads exercised paths nothing else had: linked
helper calls
resolved against the `GETPC` base, ALU exception classification in
generated execute
paths, scalar memory access faults, signed scratch offsets, unaligned
swizzled scratch,
cross-XCD atomic coherence, the `v_bcnt` accumulator, pageable host
copies through SDMA,
and `s_sleep`'s architected delay. Generator changes live in
`lib/python/amdisa/` with
the generated output regenerated, not hand-edited.
**Debugger UX, docs, demos.** `mirage run --gdb` / `--gdb-ex`;
`docs/rocgdb-debugging.md`; four recorded walkthroughs under `demos/`
(quickstart,
watchpoint, fault, multi-wave), each with a `.md` explainer, a runnable
`.sh`, and an
asciinema `.cast`.
**Test tooling.** `tools/run_rocgdb_official.py` runs each `gdb.rocm`
file in a fresh,
bounded, verified Mirage daemon session and records a per-test summary
plus backend
hashes; `tools/repeat_rocgdb_test.sh` repeats a subset serially under an
`flock` (two
concurrent Mirage sessions collide and manufacture failures) for chasing
intermittents.
Both archive the daemon's `host.log` alongside `gdb.log`. The emulated
KFD's
diagnostics land there and nowhere else, and three debugger defects
fixed in this PR
were invisible until it was collected.
## Issue Tracking
Epic: #8276
Closes #8364, closes #8365, closes #8366, closes #8367, closes #8368,
closes #8369,
closes #8370, closes #8371, closes #8372, closes #8373, closes #8374,
closes #8375,
closes #8376, closes #8377, closes #8378, closes #8379, closes #8380,
closes #8381.
| Item | Work |
|---|---|
| #8364 | Authorize `DBG_TRAP` by real ptrace + daemon transport |
| #8365 | Key debug sessions by inferior pid so rocgdb can attach |
| #8366 | Complete the `DBG_TRAP` attach/detach lifecycle |
| #8367 | Enumerate compute queues in `GET_QUEUE_SNAPSHOT` |
| #8368 | Stop a GPU wave on `s_trap` for the KFD debugger |
| #8369 | Serialize stopped-wave state into the CWSR area layout |
| #8370 | Raise a debug event and serialize the CWSR when a wave traps |
| #8371 | Drive GPU wave debugging end to end with ROCgdb |
| #8372 | `mirage run --gdb` one-command debugging |
| #8373 | Deterministic single-step via `TRAPSTS.trap_after_inst` |
| #8374 | Reserve debugger memory for step-over |
| #8375 | Validate `SET_WAVE_LAUNCH_OVERRIDE` against the gfx9.4 trap
mask |
| #8376 | GPU address watchpoints |
| #8377 | Illegal instruction reported as SIGILL |
| #8378 | Memory-access violation reported as SIGSEGV |
| #8379 | Private/scratch reads for `print` / `info args` |
| #8380 | Multi-wave GPU kernel debugging |
| #8381 | ROCgdb debugging guide and demos index |
Built on the already-merged foundations #8280, #8281, #8353 and #8363.
## Test Plan
1. **Official ROCgdb suite.** All 89 `gdb.rocm` test files, each in its
own fresh,
bounded Mirage daemon session, run serially via
`tools/run_rocgdb_official.py --profile mi350x`. The suite is upstream
ROCgdb's own,
written for hardware and unmodified.
2. **rocjitsu unit/integration suite.** Full non-benchmark `ctest` run,
including the 66
test cases added here (19 in the new `wave_debug_test.cpp`, 34 in
`kfd_ioctl_test.cpp`,
the rest across shared-infra, VM, interposer and decode tests).
3. **End-to-end smoke test in CI.** New `RocgdbDebug.KernelBreakpoint`
ctest target drives
real ROCgdb through `mirage run` to set a kernel breakpoint, stop, read
wave registers,
and continue to a correct result. Gated on `rocgdb` and `hipcc` being
present; skips
cleanly otherwise.
## Test Result
Both suites green at `c998c5cd` (branch tip):
| Suite | Result |
|---|---|
| Official ROCgdb `gdb.rocm` | **89/89 files pass, 0 FAIL**. 2376 PASS
over 2407 executed assertions (9 UNSUPPORTED, 7 KFAIL, 3 XFAIL, 6
WARNING, 6 UNTESTED) |
| rocjitsu `ctest` (non-benchmark) | **2492 tests, 0 failures** (4
skipped by design) |
## Submission Checklist
- [x] Look over the contributing guidelines at
https://github.com/ROCm/rocm-systems/blob/develop/CONTRIBUTING.md.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The CDNA5 descriptor tests in triton-lang/triton#12512 pass on hardware but fail under Rocjitsu. Crossing raw B128 accesses load and store one extra DWORD near NUM_RECORDS. - CDNA5 buffer bounds (PR #10362, f81f9d4): retain complete accesses, including exact fits, and apply the PR's stated end + 4 <= NUM_RECORDS rule to crossing raw vector accesses. Keep SOFFSET in the check and preserve structured and independent stride bounds. - GFX12 atomic returns (PR #7336, 3a4e98a): test TH[0] independently of the non-temporal and cascade bits. Returning modes 3, 5 and 7 otherwise omit the old value and select non-returning metadata and wait counters. - Signed scratch operands (CDNA5 PR #6673, f6aec50; RDNA4 PR #5053, ca4b97e): sign-extend scalar and vector offsets before combining them and before CDNA5 payload scaling. Compensated negative operands otherwise become large positive private offsets and fault. - CDNA GLOBAL operands (PR #5512, 5a107ce): zero-extend the 32-bit VGPR offset when a scalar base is present. High-bit offsets otherwise resolve four GiB below the intended address. Preserve absolute VGPR pairs and CDNA5 signed addressing. CDNA1-4 manuals and LLVM agree on the unsigned rule. - PC exports and targets (import handling #9844, e5109e3): apply the architecture's extension rule to GETPC, CALL and SWAPPC return addresses. RDNA4 zero-extends 48 bits; older targets sign-extend 48 bits and CDNA5 sign-extends 57 bits. Keep imported PCs as raw 48/57-bit addresses for instruction fetch, consistent with RFE, and preserve all CDNA5 target bits. Limit the positive linked-rel32 tag to 48-bit targets. Remove the negative tag on every target: it aliases ordinary sign-filled PC exports. Both forms otherwise corrupt valid 57-bit targets or older high-PC round trips when a load bias is present. For buffer bounds, the Triton PR measures aligned B128 crossings with four or eight bytes remaining. Other widths and byte-granular bounds follow its stated rule as a model choice pending gfx1250 qualification. Record that limit beside the implementation and regression expectations. Cover bounds, inactive lanes, signed and unsigned operand combinations, atomic writeback and wait counters, including image metadata. Validate 49 Gluon cases with matching ISA and 172 hardware comparisons each on gfx1201 and gfx1100. Audit gfx942/gfx950 buffer, scratch and atomic contracts against the manuals. The standard CTest sweep retains the same 31 baseline failures. Decode and execute PC regression cases on gfx1100, gfx1201, gfx950 and gfx1250, including high address bits, next-PC boundaries and operand aliasing. Follow SETPC/SWAPPC through real page tables to fetch a high mapped destination, including both relocation-tag collisions with a nonzero load bias. Older linked-rel32 debugger coverage is retained. These are manual/LLVM-backed simulation checks without physical high-address PC qualification. RDNA3/3.5 sign-filled exports follow LLVM against conflicting manual tables; record that choice beside the generator and test oracle. Add explicit test braces for GCC.
The CDNA5 descriptor tests in triton-lang/triton#12512 pass on hardware but fail under Rocjitsu. Crossing raw B128 accesses load and store one extra DWORD near NUM_RECORDS. - CDNA5 buffer bounds (PR #10362, f81f9d4): retain complete accesses, including exact fits, and apply the PR's stated end + 4 <= NUM_RECORDS rule to crossing raw vector accesses. Keep SOFFSET in the check and preserve structured and independent stride bounds. - GFX12 atomic returns (PR #7336, 3a4e98a): test TH[0] independently of the non-temporal and cascade bits. Returning modes 3, 5 and 7 otherwise omit the old value and select non-returning metadata and wait counters. - Signed scratch operands (CDNA5 PR #6673, f6aec50; RDNA4 PR #5053, ca4b97e): sign-extend scalar and vector offsets before combining them and before CDNA5 payload scaling. Compensated negative operands otherwise become large positive private offsets and fault. - CDNA GLOBAL operands (PR #5512, 5a107ce): zero-extend the 32-bit VGPR offset when a scalar base is present. High-bit offsets otherwise resolve four GiB below the intended address. Preserve absolute VGPR pairs and CDNA5 signed addressing. CDNA1-4 manuals and LLVM agree on the unsigned rule. - PC exports and targets (import handling #9844, e5109e3): apply the architecture's extension rule to GETPC, CALL and SWAPPC return addresses. RDNA4 zero-extends 48 bits; older targets sign-extend 48 bits and CDNA5 sign-extends 57 bits. Keep imported PCs as raw 48/57-bit addresses for instruction fetch, consistent with RFE, and preserve all CDNA5 target bits. Limit the positive linked-rel32 tag to 48-bit targets. Remove the negative tag on every target: it aliases ordinary sign-filled PC exports. Both forms otherwise corrupt valid 57-bit targets or older high-PC round trips when a load bias is present. For buffer bounds, the Triton PR measures aligned B128 crossings with four or eight bytes remaining. Other widths and byte-granular bounds follow its stated rule as a model choice pending gfx1250 qualification. Record that limit beside the implementation and regression expectations. Cover bounds, inactive lanes, signed and unsigned operand combinations, atomic writeback and wait counters, including image metadata. Validate 49 Gluon cases with matching ISA and 172 hardware comparisons each on gfx1201 and gfx1100. Audit gfx942/gfx950 buffer, scratch and atomic contracts against the manuals. The standard CTest sweep retains the same 31 baseline failures. Decode and execute PC regression cases on gfx1100, gfx1201, gfx950 and gfx1250, including high address bits, next-PC boundaries and operand aliasing. Follow SETPC/SWAPPC through real page tables to fetch a high mapped destination, including both relocation-tag collisions with a nonzero load bias. Older linked-rel32 debugger coverage is retained. These are manual/LLVM-backed simulation checks without physical high-address PC qualification. RDNA3/3.5 sign-filled exports follow LLVM against conflicting manual tables; record that choice beside the generator and test oracle. Add explicit test braces for GCC.
Motivation
Debugging a GPU kernel has required a physical AMD GPU. rocjitsu already emulates
the AMD KFD closely enough to run real ROCr/HIP workloads, but it had no debug
surface:
AMDKFD_IOC_DBG_TRAPwas unimplemented, waves could not be stopped, andthere was no CWSR state for a debugger to read or write.
This PR closes that gap. Real ROCgdb and real
librocm-dbgapi, unmodified, nowattach to a workload running on the emulated GPU and debug its kernels end to end,
with no AMD hardware present: breakpoints, single-step, watchpoints, multi-wave
info threads, source-level locals out of private/scratch, SIGILL and SIGSEGV on GPUfaults, and clean detach. One command drives it:
The bar this is measured against is the official ROCgdb
gdb.rocmtest suite, whichis written for hardware and knows nothing about the emulator. All 89 of its files
pass.
Technical Details
All KFD debug behaviour is mirrored from the real driver source
(
amd/amdkfd/{kfd_chardev.c,kfd_debug.c,kfd_topology.c}, amdgpu-6.16.13),cross-checked against a physical MI300X and against the in-tree
projects/rocdbgapiclient. Changes are confined toemulation/.KFD debug protocol (
kmd/linux/{simulated_kfd,remote_driver,rj_daemon,interposer})DBG_TRAPsub-operation dispatcher with the driver's validation ladder, authorizedby a real ptrace relationship and marshalled over the daemon. The debugger and the
inferior are separate processes, so in-process mode cannot host this and daemon mode
is mandatory.
opens
/dev/kfd; full attach/detach lifecycle,GET_DEVICE_SNAPSHOT,GET_QUEUE_SNAPSHOT,SET_EXCEPTIONS_ENABLED/SET_FLAGS, andSET_WAVE_LAUNCH_OVERRIDEvalidated against the gfx9.4 trap mask.and pinned process descriptors travel with the enable request), preserving remote
DONTFORKsemantics and naming the synthetic KFD marker as/dev/kfdin proc-mapsnapshots so fork and
gcorebehave as they do on hardware.-ESRCH(not
-EPERM) once the target's KFD state is gone, and suspend/resume ofalready-destroyed queues answered so rocdbgapi can retire them during teardown.
Engine wave-stop and CWSR (
vm/amdgpu/{compute_unit,wavefront,command_processor},kmd/linux/cwsr.{h,cpp})s_trapfor the KFD debugger; stopped-wave state is serialized intothe real CWSR area layout and written back on resume.
inside it needs the interrupted architectural state published, not the handler's,
so EXEC and STATUS are shadowed in both directions: a debugger's edit lands on the
state
s_rferestores instead of the register the handler is still running under.TRAPSTS.trap_after_inst; reserved debugger memory fordisplaced stepping; GPU address watchpoints; illegal instruction reported as SIGILL;
memory violation reported as SIGSEGV; private/scratch reads so
printandinfo argsresolve.residency, 3D workgroup coordinates in CWSR records, and gfx950 LDS preservation.
progress without starving peer waves.
ISA and memory fixes surfaced by the suite (
isa/arch/amdgpu/,vm/amdgpu/)Running real debugger workloads exercised paths nothing else had: linked helper calls
resolved against the
GETPCbase, ALU exception classification in generated executepaths, scalar memory access faults, signed scratch offsets, unaligned swizzled scratch,
cross-XCD atomic coherence, the
v_bcntaccumulator, pageable host copies through SDMA,and
s_sleep's architected delay. Generator changes live inlib/python/amdisa/withthe generated output regenerated, not hand-edited.
Debugger UX, docs, demos.
mirage run --gdb/--gdb-ex;docs/rocgdb-debugging.md; four recorded walkthroughs underdemos/(quickstart,watchpoint, fault, multi-wave), each with a
.mdexplainer, a runnable.sh, and anasciinema
.cast.Test tooling.
tools/run_rocgdb_official.pyruns eachgdb.rocmfile in a fresh,bounded, verified Mirage daemon session and records a per-test summary plus backend
hashes;
tools/repeat_rocgdb_test.shrepeats a subset serially under anflock(twoconcurrent Mirage sessions collide and manufacture failures) for chasing intermittents.
Both archive the daemon's
host.logalongsidegdb.log. The emulated KFD'sdiagnostics land there and nowhere else, and three debugger defects fixed in this PR
were invisible until it was collected.
Issue Tracking
Epic: #8276
Closes #8364, closes #8365, closes #8366, closes #8367, closes #8368, closes #8369,
closes #8370, closes #8371, closes #8372, closes #8373, closes #8374, closes #8375,
closes #8376, closes #8377, closes #8378, closes #8379, closes #8380, closes #8381.
DBG_TRAPby real ptrace + daemon transportDBG_TRAPattach/detach lifecycleGET_QUEUE_SNAPSHOTs_trapfor the KFD debuggermirage run --gdbone-command debuggingTRAPSTS.trap_after_instSET_WAVE_LAUNCH_OVERRIDEagainst the gfx9.4 trap maskprint/info argsBuilt on the already-merged foundations #8280, #8281, #8353 and #8363.
Test Plan
gdb.rocmtest files, each in its own fresh,bounded Mirage daemon session, run serially via
tools/run_rocgdb_official.py --profile mi350x. The suite is upstream ROCgdb's own,written for hardware and unmodified.
ctestrun, including the 66test cases added here (19 in the new
wave_debug_test.cpp, 34 inkfd_ioctl_test.cpp,the rest across shared-infra, VM, interposer and decode tests).
RocgdbDebug.KernelBreakpointctest target drivesreal ROCgdb through
mirage runto set a kernel breakpoint, stop, read wave registers,and continue to a correct result. Gated on
rocgdbandhipccbeing present; skipscleanly otherwise.
Test Result
Both suites green at
c998c5cd(branch tip):gdb.rocmctest(non-benchmark)Submission Checklist