GDB hardware watchpoints hang on macOS Apple Silicon
## Summary GDB hardware watchpoints never trigger when debugging a guest under QEMU on macOS Apple Silicon. Setting a watchpoint via GDB (`watch *0xaddr`) is accepted by QEMU, but continuing execution causes QEMU to spin at 100% CPU without ever reporting the watchpoint hit to GDB. ## Disclaimer I don't have any experience with QEMU internals, so I used an LLM to assist me in debugging & identifying this. I came up with a very small patch that appears to fix the problem, but I don't plan on submitting it since I believe that to be a violation of the [code provenance](https://www.qemu.org/docs/master/devel/code-provenance.html#use-of-ai-generated-content). ## Environment - Host: macOS on Apple Silicon (ARM64) - QEMU versions: 10.2.2 (Homebrew), 11.0.0 (Homebrew), master 4bde339ecb (built from source) - Guest: RISC-V rv32 and rv64, virt machine (likely affects all TCG targets) - GDB: riscv64-elf-gdb 16.3 (`have_nonsteppable_watchpoint = 1`) ## Reproduction bash script: https://gist.github.com/benburkert/a3ef1887b81446f144ed2603ddca081c or manual: ### Terminal 1 ```shell qemu-system-riscv32 -machine virt -smp 1 -bios none -m 4M -nographic \ -kernel <any-elf-binary> -s -S ``` ### Terminal 2 ```shell riscv64-elf-gdb <elf-binary> -ex 'target remote :1234' \ -ex 'watch *(int *)0x<any-writable-addr>' -ex 'continue' ``` GDB sends `Z2` (write watchpoint), QEMU responds `OK`. GDB sends `vCont;c`, QEMU resumes. The guest writes to the watched address but QEMU never sends `T05watch: stop` reply. QEMU spins at 100% CPU. GDB hangs waiting. Software breakpoints (`Z0`) work correctly in the same configuration. ## Investigation By instrumenting QEMU's source, I traced the execution through the watchpoint path: 1. TLB fill correctly sets `TLB_WATCHPOINT` and `TLB_FORCE_SLOW` for the watched page ✓ 2. Store slow path (`do_st4_mmu` → `mmu_lookup1`) sees `TLB_WATCHPOINT` in `slow_flags` ✓ 3. `mmu_watch_or_dirty` calls `cpu_check_watchpoint` ✓ 4. `cpu_check_watchpoint` matches the address, sets `cpu->watchpoint_hit`, calls `tb_check_watchpoint` ✓ 5. `tb_check_watchpoint` calls `cpu_restore_state_from_tb` (returns normally), then calls `tb_phys_invalidate(tb, -1)` ✓ 6. `tb_phys_invalidate` calls `do_tb_phys_invalidate` ✗ — **never returns** 7. Inside `do_tb_phys_invalidate`, `qemu_spin_lock(&tb->jmp_lock)` spins forever despite `jmp_lock.value == 0` The `qatomic_xchg` in the spinlock appears to silently fail — the lock value reads as 0 (unlocked) but the exchange never succeeds. This prevents `EXCP_DEBUG` from being raised, so the VM never stops and GDB never receives the watchpoint hit notification. The `__locked` variant (`tb_phys_invalidate__locked`) works correctly in other code paths — it wraps the call with `qemu_thread_jit_write()`/`qemu_thread_jit_execute()`. The public `tb_phys_invalidate()` function does not.
issue