Skip to content

Commit bd2cc61

Browse files
cshungCopilot
andcommitted
feat(aarch64): add GDB debugging for Windows WHP
Guest debug exceptions reach WHP through HVC. Saved exception context, hardware breakpoints and single-step survive cache synchronization and vCPU reset. The cache-sync trampoline is read-only to guests. Shared debugger behavior belongs to the parent commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cshung <3410332+cshung@users.noreply.github.com>
1 parent 1790d3c commit bd2cc61

8 files changed

Lines changed: 844 additions & 14 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
44

55
## [Prerelease] - Unreleased
66

7+
### Added
8+
* Windows ARM64 WHP guest debugging supports software and hardware breakpoints, register and memory access, and hardware single-step.
9+
710
### Fixed
811
* Fix restore on WHP not rewinding the guest TSC on hosts with `TSC_ADJUST` support.
912
* GDB memory reads and writes translate each guest page on hosts with larger pages.
1013
* ARM64 GDB snapshot recovery handles cancellation and crash detach. Snapshot checkpointing rejects software breakpoints before replacing memory.
14+
* WHP ARM64 cache synchronization code is read-only to guests.
1115

1216
## [v0.19.0] - 2026-11-04
1317

‎Justfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ test-no-surrogate target=default-target:
335335
# runs tests that exercise gdb debugging
336336
test-rust-gdb-debugging target=default-target features="":
337337
{{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --example guest-debugging {{ if features =="" {'--features gdb'} else { "--features gdb," + features } }}
338-
{{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib {{ if features =="" {'--features gdb'} else { "--features gdb," + features } }} -- hypervisor::gdb:: hypervisor::hyperlight_vm::debug::tests:: gdb_recovery_tests::
338+
{{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib {{ if features =="" {'--features gdb'} else { "--features gdb," + features } }} -- hypervisor::gdb:: hypervisor::hyperlight_vm::debug::tests:: gdb_recovery_tests:: hypervisor::virtual_machine::whp::aarch64::debug_tests::
339339

340340
# rust test for crashdump
341341
test-rust-crashdump target=default-target features="":

‎docs/how-to-debug-a-hyperlight-guest.md‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,26 @@ to start listening for a gdb connection.
77
## Supported features
88

99
The Hyperlight `gdb` feature enables guest debugging to:
10-
- stop at an entry point breakpoint which is automatically set by Hyperlight
11-
- add and remove HW breakpoints (maximum 4 set breakpoints at a time)
10+
- stop at the entry point
1211
- add and remove SW breakpoints
1312
- read and write registers
1413
- read and write addresses
15-
- step/continue
14+
- continue
15+
- single-step
1616
- get code offset from target
1717
- stop when a crash occurs and only allow read access to the guest memory and registers
1818

19+
On x86_64, gdb supports up to four hardware breakpoints.
20+
Windows ARM64 WHP supports two hardware breakpoints, four-byte `BRK` software
21+
breakpoints, and hardware single-step. Guest exception vectors forward debug
22+
exceptions through HVC. Guest binaries must retain the
23+
`hyperlight_debug_vectors` symbol so the host can install these vectors before
24+
the initial debug stop. Watchpoints are not advertised.
25+
26+
ARM64 snapshots reject installed software breakpoints, including those added
27+
during checkpointing. Restoring a poisoned sandbox discards its software
28+
breakpoint state and restores clean snapshot memory.
29+
1930
## Expected behavior
2031

2132
Below is a list describing some cases of expected behavior from a gdb debug

‎src/hyperlight_host/build.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ fn main() -> Result<()> {
151151
// Essentially the kvm and mshv3 features are ignored on windows as long as you use #[cfg(kvm)] and not #[cfg(feature = "kvm")].
152152
// You should never use #[cfg(feature = "kvm")] or #[cfg(feature = "mshv3")] in the codebase.
153153
cfg_aliases::cfg_aliases! {
154-
gdb: { all(feature = "gdb", debug_assertions, target_arch = "x86_64") },
154+
gdb_platform: { any(target_arch = "x86_64", all(target_arch = "aarch64", target_os = "windows")) },
155+
gdb: { all(feature = "gdb", debug_assertions, gdb_platform) },
155156
kvm: { all(feature = "kvm", target_os = "linux") },
156157
mshv3: { all(feature = "mshv3", target_os = "linux") },
157158
hvf: { all(feature = "hvf", target_os = "macos") },

‎src/hyperlight_host/src/hypervisor/hyperlight_vm/mod.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,23 @@ impl HyperlightVm {
492492
}
493493
}
494494

495+
// Check against the hidden debug cache-sync page
496+
#[cfg(all(gdb, target_arch = "aarch64", target_os = "windows"))]
497+
{
498+
use crate::hypervisor::virtual_machine::whp::DEBUG_CACHE_SYNC_GPA;
499+
500+
let reserved_start = DEBUG_CACHE_SYNC_GPA as usize;
501+
let reserved_end = reserved_start + self.page_size;
502+
if new_start < reserved_end && new_end > reserved_start {
503+
return Err(MapRegionError::Overlapping {
504+
new_start,
505+
new_end,
506+
existing_start: reserved_start,
507+
existing_end: reserved_end,
508+
});
509+
}
510+
}
511+
495512
// Try to reuse a freed slot first, otherwise use next_slot
496513
let slot = self.allocate_slot();
497514

‎src/hyperlight_host/src/hypervisor/regs/aarch64/whp_reg.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ pub(crate) const WHV_ARM64_REGISTER_MAIR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_N
3131
pub(crate) const WHV_ARM64_REGISTER_VBAR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x0004000C);
3232
pub(crate) const WHV_ARM64_REGISTER_FPCR: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040012);
3333
pub(crate) const WHV_ARM64_REGISTER_FPSR: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040013);
34+
#[cfg(gdb)]
35+
pub(crate) const WHV_ARM64_REGISTER_SPSR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040014);
36+
#[cfg(gdb)]
37+
pub(crate) const WHV_ARM64_REGISTER_ELR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040015);
38+
#[cfg(gdb)]
39+
pub(crate) const WHV_ARM64_REGISTER_ESR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00040008);
40+
#[cfg(gdb)]
41+
pub(crate) const WHV_ARM64_REGISTER_MDSCR_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x0005004D);
42+
#[cfg(gdb)]
43+
pub(crate) const WHV_ARM64_REGISTER_DBGBCR0_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00050000);
44+
#[cfg(gdb)]
45+
pub(crate) const WHV_ARM64_REGISTER_DBGBVR0_EL1: WHV_REGISTER_NAME = WHV_REGISTER_NAME(0x00050020);
3446
pub(crate) const WHV_ARM64_REGISTER_GICR_BASE_GPA: WHV_REGISTER_NAME =
3547
WHV_REGISTER_NAME(0x00063000);
3648

0 commit comments

Comments
 (0)