Skip to content

Commit af37e9e

Browse files
committed
Move debug_interrupt AtomicBool into state AtomicU64
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent 9200b1b commit af37e9e

File tree

5 files changed

+38
-41
lines changed

5 files changed

+38
-41
lines changed

docs/cancellation.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ Hyperlight provides a mechanism to forcefully interrupt guest execution through
1212

1313
The `LinuxInterruptHandle` uses a packed atomic u64 to track execution state:
1414

15-
- **state (AtomicU64)**: Packs two bits:
15+
- **state (AtomicU64)**: Packs three bits:
16+
- **Bit 2 (DEBUG_INTERRUPT_BIT)**: Set when debugger interrupt is requested (gdb feature only)
1617
- **Bit 1 (RUNNING_BIT)**: Set when vCPU is actively running in guest mode
1718
- **Bit 0 (CANCEL_BIT)**: Set when cancellation has been requested via `kill()`
1819
- **tid (AtomicU64)**: Thread ID where the vCPU is running
19-
- **debug_interrupt (AtomicBool)**: Set when debugger interrupt is requested (gdb feature only)
2020
- **dropped (AtomicBool)**: Set when the corresponding VM has been dropped
2121

22-
The packed state enables atomic reads of both RUNNING_BIT and CANCEL_BIT simultaneously via `get_running_and_cancel()`. Within a single `VirtualCPU::run()` call, the CANCEL_BIT remains set across vcpu exits and re-entries (such as when calling host functions), ensuring cancellation persists until the guest call completes. However, `clear_cancel()` resets the CANCEL_BIT at the beginning of each new guest function call (specifically in `MultiUseSandbox::call`, before `VirtualCPU::run()` is called), preventing cancellation requests from affecting subsequent guest function calls.
22+
The packed state enables atomic reads of RUNNING_BIT, CANCEL_BIT and DEBUG_INTERRUPT_BIT simultaneously via `get_running_cancel_debug()`. Within a single `VirtualCPU::run()` call, the CANCEL_BIT remains set across vcpu exits and re-entries (such as when calling host functions), ensuring cancellation persists until the guest call completes. However, `clear_cancel()` resets the CANCEL_BIT at the beginning of each new guest function call (specifically in `MultiUseSandbox::call`, before `VirtualCPU::run()` is called), preventing cancellation requests from affecting subsequent guest function calls.
2323

2424
### Signal Mechanism
2525

@@ -176,9 +176,9 @@ sequenceDiagram
176176
- Ensures all writes before `kill()` are visible when vCPU thread checks `is_cancelled()` with `Acquire`
177177

178178
2. **Send Signals**: Enter retry loop via `send_signal()`
179-
- Atomically load both running and cancel flags via `get_running_and_cancel()` with `Acquire` ordering
180-
- Continue if `running=true AND cancel=true` (or `running=true AND debug_interrupt=true` with gdb)
181-
- Exit loop immediately if `running=false OR cancel=false`
179+
- Atomically load running, cancel and debug flags via `get_running_cancel_debug()` with `Acquire` ordering
180+
- Continue if `running=true AND cancel=true` (or `running=true AND debug=true` with gdb)
181+
- Exit loop immediately if `running=false OR (cancel=false AND debug=false)`
182182

183183
3. **Signal Delivery**: Send `SIGRTMIN+offset` via `pthread_kill`
184184
- Signal interrupts the `ioctl` that runs the vCPU, causing `EINTR`
@@ -209,7 +209,7 @@ graph TB
209209
F[send_signal<br/>Load running with Acquire]
210210
G[Load tid with Acquire]
211211
H[pthread_kill]
212-
I[kill_from_debugger<br/>Store debug_interrupt<br/>with Release]
212+
I[kill_from_debugger<br/>fetch_or DEBUG_INTERRUPT_BIT<br/>with Release]
213213
end
214214
215215
B -->|Synchronizes-with| F
@@ -255,8 +255,7 @@ While the core cancellation mechanism follows the same conceptual model on Windo
255255

256256
The `WindowsInterruptHandle` uses a simpler structure compared to Linux:
257257

258-
- **state (AtomicU64)**: Packs the same two bits (RUNNING_BIT and CANCEL_BIT)
259-
- **debug_interrupt (AtomicBool)**: Set when debugger interrupt is requested (gdb feature only)
258+
- **state (AtomicU64)**: Packs three bits (RUNNING_BIT, CANCEL_BIT and DEBUG_INTERRUPT_BIT)
260259
- **partition_handle**: Windows Hyper-V partition handle for the VM
261260
- **dropped (AtomicBool)**: Set when the corresponding VM has been dropped
262261

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,8 +376,6 @@ impl HypervLinuxDriver {
376376

377377
let interrupt_handle: Arc<dyn InterruptHandleImpl> = Arc::new(LinuxInterruptHandle {
378378
state: AtomicU64::new(0),
379-
#[cfg(gdb)]
380-
debug_interrupt: AtomicBool::new(false),
381379
#[cfg(all(
382380
target_arch = "x86_64",
383381
target_vendor = "unknown",

src/hyperlight_host/src/hypervisor/hyperv_windows.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,6 @@ impl HypervWindowsDriver {
325325

326326
let interrupt_handle = Arc::new(WindowsInterruptHandle {
327327
state: AtomicU64::new(0),
328-
#[cfg(gdb)]
329-
debug_interrupt: AtomicBool::new(false),
330328
partition_handle,
331329
dropped: AtomicBool::new(false),
332330
});

src/hyperlight_host/src/hypervisor/kvm.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,6 @@ impl KVMDriver {
334334

335335
let interrupt_handle: Arc<dyn InterruptHandleImpl> = Arc::new(LinuxInterruptHandle {
336336
state: AtomicU64::new(0),
337-
#[cfg(gdb)]
338-
debug_interrupt: AtomicBool::new(false),
339337
#[cfg(all(
340338
target_arch = "x86_64",
341339
target_vendor = "unknown",

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ pub(super) struct LinuxInterruptHandle {
582582
/// Atomic value packing vcpu execution state.
583583
///
584584
/// Bit layout:
585+
/// - Bit 2: DEBUG_INTERRUPT_BIT - set when debugger interrupt is requested
585586
/// - Bit 1: RUNNING_BIT - set when vcpu is actively running
586587
/// - Bit 0: CANCEL_BIT - set when cancellation has been requested
587588
///
@@ -595,11 +596,6 @@ pub(super) struct LinuxInterruptHandle {
595596
/// but at most one VM will have RUNNING_BIT set at any given time.
596597
tid: AtomicU64,
597598

598-
/// Debugger interrupt flag (gdb feature only).
599-
/// Set when `kill_from_debugger()` is called, cleared when vcpu stops running.
600-
#[cfg(gdb)]
601-
debug_interrupt: AtomicBool,
602-
603599
/// Whether the corresponding VM has been dropped.
604600
dropped: AtomicBool,
605601

@@ -614,33 +610,35 @@ pub(super) struct LinuxInterruptHandle {
614610
impl LinuxInterruptHandle {
615611
const RUNNING_BIT: u64 = 1 << 1;
616612
const CANCEL_BIT: u64 = 1 << 0;
613+
#[cfg(gdb)]
614+
const DEBUG_INTERRUPT_BIT: u64 = 1 << 2;
617615

618-
/// Get the running and cancel flags atomically.
616+
/// Get the running, cancel and debug flags atomically.
619617
///
620618
/// # Memory Ordering
621619
/// Uses `Acquire` ordering to synchronize with the `Release` in `set_running()` and `kill()`.
622620
/// This ensures that when we observe running=true, we also see the correct `tid` value.
623-
fn get_running_and_cancel(&self) -> (bool, bool) {
621+
fn get_running_cancel_debug(&self) -> (bool, bool, bool) {
624622
let state = self.state.load(Ordering::Acquire);
625623
let running = state & Self::RUNNING_BIT != 0;
626624
let cancel = state & Self::CANCEL_BIT != 0;
627-
(running, cancel)
625+
#[cfg(gdb)]
626+
let debug = state & Self::DEBUG_INTERRUPT_BIT != 0;
627+
#[cfg(not(gdb))]
628+
let debug = false;
629+
(running, cancel, debug)
628630
}
629631

630632
fn send_signal(&self) -> bool {
631633
let signal_number = libc::SIGRTMIN() + self.sig_rt_min_offset as libc::c_int;
632634
let mut sent_signal = false;
633635

634636
loop {
635-
let (running, cancel) = self.get_running_and_cancel();
637+
let (running, cancel, debug) = self.get_running_cancel_debug();
636638

637639
// Check if we should continue sending signals
638640
// Exit if not running OR if neither cancel nor debug_interrupt is set
639-
#[cfg(gdb)]
640-
let should_continue =
641-
running && (cancel || self.debug_interrupt.load(Ordering::Acquire));
642-
#[cfg(not(gdb))]
643-
let should_continue = running && cancel;
641+
let should_continue = running && (cancel || debug);
644642

645643
if !should_continue {
646644
break;
@@ -698,7 +696,7 @@ impl InterruptHandleImpl for LinuxInterruptHandle {
698696
fn is_debug_interrupted(&self) -> bool {
699697
#[cfg(gdb)]
700698
{
701-
self.debug_interrupt.load(Ordering::Acquire)
699+
self.state.load(Ordering::Acquire) & Self::DEBUG_INTERRUPT_BIT != 0
702700
}
703701
#[cfg(not(gdb))]
704702
{
@@ -708,7 +706,8 @@ impl InterruptHandleImpl for LinuxInterruptHandle {
708706

709707
#[cfg(gdb)]
710708
fn clear_debug_interrupt(&self) {
711-
self.debug_interrupt.store(false, Ordering::Release);
709+
self.state
710+
.fetch_and(!Self::DEBUG_INTERRUPT_BIT, Ordering::Release);
712711
}
713712

714713
fn set_dropped(&self) {
@@ -731,7 +730,8 @@ impl InterruptHandle for LinuxInterruptHandle {
731730

732731
#[cfg(gdb)]
733732
fn kill_from_debugger(&self) -> bool {
734-
self.debug_interrupt.store(true, Ordering::Release);
733+
self.state
734+
.fetch_or(Self::DEBUG_INTERRUPT_BIT, Ordering::Release);
735735
self.send_signal()
736736
}
737737
fn dropped(&self) -> bool {
@@ -747,6 +747,7 @@ pub(super) struct WindowsInterruptHandle {
747747
/// Atomic value packing vcpu execution state.
748748
///
749749
/// Bit layout:
750+
/// - Bit 2: DEBUG_INTERRUPT_BIT - set when debugger interrupt is requested
750751
/// - Bit 1: RUNNING_BIT - set when vcpu is actively running
751752
/// - Bit 0: CANCEL_BIT - set when cancellation has been requested
752753
///
@@ -757,9 +758,6 @@ pub(super) struct WindowsInterruptHandle {
757758
/// (e.g., during host function calls), but is cleared at the start of each new `VirtualCPU::run()` call.
758759
state: AtomicU64,
759760

760-
// This is used to signal the GDB thread to stop the vCPU
761-
#[cfg(gdb)]
762-
debug_interrupt: AtomicBool,
763761
partition_handle: windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE,
764762
dropped: AtomicBool,
765763
}
@@ -768,6 +766,8 @@ pub(super) struct WindowsInterruptHandle {
768766
impl WindowsInterruptHandle {
769767
const RUNNING_BIT: u64 = 1 << 1;
770768
const CANCEL_BIT: u64 = 1 << 0;
769+
#[cfg(gdb)]
770+
const DEBUG_INTERRUPT_BIT: u64 = 1 << 2;
771771
}
772772

773773
#[cfg(target_os = "windows")]
@@ -792,15 +792,18 @@ impl InterruptHandleImpl for WindowsInterruptHandle {
792792

793793
fn clear_running(&self) {
794794
// Release ordering to ensure all vcpu operations are visible before clearing running
795-
self.state.fetch_and(!Self::RUNNING_BIT, Ordering::Release);
795+
let mut mask = !Self::RUNNING_BIT;
796796
#[cfg(gdb)]
797-
self.debug_interrupt.store(false, Ordering::Release);
797+
{
798+
mask &= !Self::DEBUG_INTERRUPT_BIT;
799+
}
800+
self.state.fetch_and(mask, Ordering::Release);
798801
}
799802

800803
fn is_debug_interrupted(&self) -> bool {
801804
#[cfg(gdb)]
802805
{
803-
self.debug_interrupt.load(Ordering::Acquire)
806+
self.state.load(Ordering::Acquire) & Self::DEBUG_INTERRUPT_BIT != 0
804807
}
805808
#[cfg(not(gdb))]
806809
{
@@ -810,8 +813,8 @@ impl InterruptHandleImpl for WindowsInterruptHandle {
810813

811814
#[cfg(gdb)]
812815
fn clear_debug_interrupt(&self) {
813-
#[cfg(gdb)]
814-
self.debug_interrupt.store(false, Ordering::Release);
816+
self.state
817+
.fetch_and(!Self::DEBUG_INTERRUPT_BIT, Ordering::Release);
815818
}
816819

817820
fn set_dropped(&self) {
@@ -843,7 +846,8 @@ impl InterruptHandle for WindowsInterruptHandle {
843846
fn kill_from_debugger(&self) -> bool {
844847
use windows::Win32::System::Hypervisor::WHvCancelRunVirtualProcessor;
845848

846-
self.debug_interrupt.store(true, Ordering::Release);
849+
self.state
850+
.fetch_or(Self::DEBUG_INTERRUPT_BIT, Ordering::Release);
847851
// Acquire ordering to synchronize with the Release in set_running()
848852
let state = self.state.load(Ordering::Acquire);
849853
if state & Self::RUNNING_BIT != 0 {

0 commit comments

Comments
 (0)