Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/dep_build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ jobs:
- name: Build
run: just build ${{ inputs.config }}

- name: Run ARM64 KVM Gdb tests
if: inputs.arch == 'arm64' && inputs.hypervisor == 'kvm' && inputs.config == 'debug'
env:
RUST_LOG: debug
run: |
if ! command -v gdb > /dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y --no-install-recommends gdb
fi
just test-rust-gdb-debugging debug

- name: Run Rust tests
run: |
# with default features
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added
* Add `Sandbox::recreate()`, which rebuilds a sandbox's VM and memory from a snapshot. It recovers unrecoverable sandboxes and keeps configuration, host functions, and interrupt handles by @ludfjig in https://github.com/hyperlight-dev/hyperlight/pull/1751
* ARM64 Linux/KVM guest debugging supports software and hardware breakpoints, register and memory access, and hardware single-step.

### Fixed
* Fix restore on WHP not rewinding the guest TSC on hosts with `TSC_ADJUST` support.
* GDB memory reads and writes translate each guest page on hosts with larger pages.
* ARM64 GDB snapshot recovery handles cancellation and crash detach. Snapshot checkpointing rejects software breakpoints before replacing memory.

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

Expand Down
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ test-no-surrogate target=default-target:
# runs tests that exercise gdb debugging
test-rust-gdb-debugging target=default-target features="":
{{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --example guest-debugging {{ if features =="" {'--features gdb'} else { "--features gdb," + features } }}
{{ 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::
{{ 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::

# rust test for crashdump
test-rust-crashdump target=default-target features="":
Expand Down
54 changes: 54 additions & 0 deletions docs/gdb-commit-layers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# GDB commit layers

Each layer is one signed commit. Backend layers depend on shared GDB support,
not on another backend's debugger.

```text
main
|-- A: shared GDB support
| |-- B: WHP ARM64 GDB
| `-- C: KVM ARM64 GDB
`-- D: ARM64 MSHV backend and CI
`-- E: cherry-pick of A
`-- F: MSHV ARM64 GDB
```

| Layer | Branch | Parent | Contents |
|---|---|---|---|
| A | `cshung/gdb-shared` | `main` | Architecture adapters, protocol handling, software breakpoints, page-sized memory access, guest debug vectors, snapshot recovery, common tests |
| B | `cshung/whp-aarch64-gdb` | A | WHP register access, HVC exits, debug registers, cache synchronization, platform enablement and tests |
| C | `cshung/kvm-aarch64-gdb` | A | KVM debug exits, debug registers, platform enablement and CI |
| D | `cshung/mshv-aarch64` | `main` | MSHV VM implementation, register access, reset, runner configuration and bounded CI scheduling |
| E | `cshung/mshv-aarch64-gdb-shared` | D | A's shared patch |
| F | `cshung/mshv-aarch64-gdb` | E | MSHV HVC exits, exception context, debug registers, platform enablement and tests |

A retains the existing x86_64 GDB platform guard. B, C and F enable their ARM64
backend. D and E provide an MSHV backend without ARM64 GDB enablement.
F rejects KVM debugging until C is integrated.

## Updating layers

* Update A for shared behavior. Rebuild B and C on A, cherry-pick A onto D as E,
then rebuild F on E. Validate all three backend tips before publication.
* Update B or C for a backend-only debugger change. The sibling branch is independent.
* Update D for MSHV backend or CI changes. Rebuild E and F on D.
* Update F for an MSHV-only debugger change.
* Keep A and E patch-equivalent. E's parent and commit ID differ from A's.
* Squash each layer to one commit. Keep historical tips in local archive branches
before rewriting published refs. Push with an explicit lease on each old remote tip.

Run formatting and Clippy on each affected tip. Rebuild guests for guest changes.
Run the common debugger tests, backend tests and real GDB client tests on WHP,
KVM and MSHV for shared updates. Validate both the default Linux driver selection
and the backend-only feature configuration. A history-only rewrite can reuse
runtime results when the corresponding source files are identical.

## Pull requests

A has a shared-support PR. B uses #1907, C uses #2016, and D uses #1626.
F has an MSHV GDB PR. E is a cherry-pick, not a second shared-support PR.

The branches live in `cshung/hyperlight`. Native GitHub stacks require head
branches in the upstream repository. These fork PRs target upstream `main` and
link their prerequisites and backend-only comparisons in their descriptions.
Their full PR diffs include unmerged prerequisites.
15 changes: 12 additions & 3 deletions docs/how-to-debug-a-hyperlight-guest.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@ to start listening for a gdb connection.
## Supported features

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

On x86_64, gdb supports up to four hardware breakpoints.
ARM64 Linux/KVM supports two hardware breakpoints, four-byte `BRK` software
breakpoints, and hardware single-step. KVM reports debug exceptions through
`KVM_EXIT_DEBUG`. Watchpoints are not advertised.

ARM64 snapshots reject installed software breakpoints, including those added
during checkpointing. Restoring a poisoned sandbox discards its software
breakpoint state and restores clean snapshot memory.

## Expected behavior

Below is a list describing some cases of expected behavior from a gdb debug
Expand Down
3 changes: 3 additions & 0 deletions src/hyperlight_common/src/arch/aarch64/exn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

use crate::vmem::bits;

/// HVC immediate for forwarding guest exceptions to the Hyper-V debugger.
pub const DEBUG_EXCEPTION_HVC: u16 = 0x485a;

const ESR_EC_UNKNOWN: u64 = 0b000000; // udf generates this
const ESR_EC_DATA_ABORT_LOWER_EL: u64 = 0b100100;
const ESR_EC_DATA_ABORT_SAME_EL: u64 = 0b100101;
Expand Down
17 changes: 17 additions & 0 deletions src/hyperlight_guest_bin/src/arch/aarch64/exception/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,24 @@ context_restore:\n
q_off = const offset_of!(ExceptionContext, q),
);

// Only synchronous exceptions have a syndrome for debug forwarding.
// The host resumes debug events at ERET and other synchronous exceptions at the prologue.
macro_rules! vbar_entry {
($et:literal, $ef:literal) => {
concat!(
"
.if {ExceptionType_",
$et,
"} == {ExceptionType_Synchronous}\n
stp x0, x1, [sp, #-16]!\n
mrs x0, mdscr_el1\n
tbz x0, #13, 1f\n
ldp x0, x1, [sp], #16\n
hvc #{debug_hvc}\n
eret\n
1:\n
ldp x0, x1, [sp], #16\n
.endif\n
sub sp, sp, #{ctx_size}\n
str x30, [sp, #{x30_off}]\n
bl context_save\n
Expand All @@ -129,6 +143,8 @@ macro_rules! vbar_entry {
global_asm!("
.balign 0x800\n
.global vbar\n
.global hyperlight_debug_vectors\n
hyperlight_debug_vectors:\n
vbar:\n",
vbar_entry!("Synchronous", "CurrentSP0"),
vbar_entry!("IRQ", "CurrentSP0"),
Expand All @@ -148,6 +164,7 @@ vbar:\n",
vbar_entry!("SError", "LowerAArch32"),
ctx_size = const size_of::<ExceptionContext>(),
x30_off = const offset_of!(ExceptionContext, x) + 15 * 0x010,
debug_hvc = const hyperlight_common::arch::exn::DEBUG_EXCEPTION_HVC,
handler = sym super::handle::handle_exception,
ExceptionType_Synchronous = const ExceptionType::Synchronous as u64,
ExceptionType_IRQ = const ExceptionType::Irq as u64,
Expand Down
3 changes: 2 additions & 1 deletion src/hyperlight_host/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ fn main() -> Result<()> {
// Essentially the kvm and mshv3 features are ignored on windows as long as you use #[cfg(kvm)] and not #[cfg(feature = "kvm")].
// You should never use #[cfg(feature = "kvm")] or #[cfg(feature = "mshv3")] in the codebase.
cfg_aliases::cfg_aliases! {
gdb: { all(feature = "gdb", debug_assertions, target_arch = "x86_64") },
gdb_platform: { any(target_arch = "x86_64", all(target_arch = "aarch64", target_os = "linux", feature = "kvm")) },
gdb: { all(feature = "gdb", debug_assertions, gdb_platform) },
kvm: { all(feature = "kvm", target_os = "linux") },
mshv3: { all(feature = "mshv3", target_os = "linux") },
hvf: { all(feature = "hvf", target_os = "macos") },
Expand Down
Loading
Loading