This series brings guest debugging to the MSHV accelerator by
implementing software breakpoints and single-stepping, so that gdb can
attach to an MSHV guest through QEMU's gdbstub.
It follows the KVM gdbstub implementation closely, and borrows the WHPX
approach where the MSHV interface differs from KVM:
- it installs a partition-wide intercept for the #DB exception
(HVCALL_INSTALL_INTERCEPT). #BP (INT3) is deliberately left to the
guest's own IDT and is never intercepted, as WHPX does.
- only software breakpoints are supported. A breakpoint is an INT1
(opcode 0xf1) patched into guest memory, which raises a #DB when
executed. Hardware breakpoints and watchpoints are not supported.
- it never touches the guest debug registers (DR0-DR7), so guest state
is not clobbered. The only guest register used is RFLAGS.TF, toggled
around the vCPU run for single-stepping and kept out of env->eflags
so it cannot be read back and re-armed.
- it re-injects the #DB back into the guest (via
HV_REGISTER_PENDING_EVENT0) when the faulting RIP does not match a
debugger breakpoint, so a guest-owned #DB is delivered through the
guest's own IDT.
When a vCPU is created we register the accel ops that gdb drives once it
is attached:
- mshv_update_guest_debug
- mshv_insert_gdbstub_breakpoint
- mshv_remove_gdbstub_breakpoint
- mshv_remove_all_gdbstub_breakpoints
For each insert request from gdb the ops save the original byte at the
target address and patch in 0xf1; on a remove request the saved byte is
restored, after which gdb single-steps over the original instruction and
re-inserts the breakpoint. When the vCPU executes the 0xf1 byte a #DB
vmexits to QEMU. mshv_handle_debug() then checks whether the RIP where
the vCPU stopped matches a breakpoint installed by gdb, or whether
single-stepping is active, and if so reports the stop to gdb. If the RIP
does not match any debugger breakpoint the #DB is assumed to be
guest-owned and is re-injected so the guest can handle it.
Known limitations:
- Once installed, the #DB intercept stays for the lifetime
of the VM and every #DB - including those the guest raises itself -
vmexits to QEMU.
- Because attribution relies on matching the faulting RIP against the
debugger's breakpoint list, a guest that legitimately executes INT1 or
raises its own #DB at an address that also holds a debugger breakpoint
cannot be disambiguated; such overlaps are not expected in practice.
The series is organised as:
1. include/hw/hyperv: add ABI for exception intercepts and pending
events
2. accel/mshv: add gdbstub software breakpoint support
3. target/i386/mshv: support single-stepping
I have tested this patch series by using both a guest that doesn't use debugging
and one that triggers the #DB and #BP exceptions (#DB is re-injected and #BP is
not intercepted at all).
Doru Blânzeanu (3):
include/hw/hyperv: add ABI for exception intercepts and pending events
accel/mshv: add gdbstub software breakpoint support
target/i386/mshv: support single-stepping
accel/mshv/mshv-all.c | 165 ++++++++++++++++++++++++++++++++-
include/hw/hyperv/hvgdk_mini.h | 56 +++++++++++
include/system/mshv_int.h | 14 +++
target/i386/mshv/mshv-cpu.c | 136 +++++++++++++++++++++++++++
4 files changed, 370 insertions(+), 1 deletion(-)
--
2.53.0