Skip to content

Commit 51e923e

Browse files
committed
gdb: interrupt vcpu using the new InterruptHandle
- Previously, interrupting a running vCPU under debug meant directly sending a SIGINT signal, since we have a way to handle interrupt sending, there is no need to do that. Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent 2d3c878 commit 51e923e

File tree

7 files changed

+192
-101
lines changed

7 files changed

+192
-101
lines changed

src/hyperlight_host/src/hypervisor/gdb/event_loop.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ use gdbstub::conn::ConnectionExt;
1919
use gdbstub::stub::{
2020
BaseStopReason, DisconnectReason, GdbStub, SingleThreadStopReason, run_blocking,
2121
};
22-
use libc::{SIGRTMIN, pthread_kill};
2322

2423
use super::x86_64_target::HyperlightSandboxTarget;
2524
use super::{DebugResponse, GdbTargetError, VcpuStopReason};
25+
mod signals {
26+
pub use libc::{SIGINT, SIGSEGV};
27+
}
2628

2729
struct GdbBlockingEventLoop;
2830

@@ -56,11 +58,11 @@ impl run_blocking::BlockingEventLoop for GdbBlockingEventLoop {
5658
// to the target thread
5759
VcpuStopReason::Interrupt => BaseStopReason::SignalWithThread {
5860
tid: (),
59-
signal: Signal(SIGRTMIN() as u8),
61+
signal: Signal(signals::SIGINT as u8),
6062
},
6163
VcpuStopReason::Crash => BaseStopReason::SignalWithThread {
6264
tid: (),
63-
signal: Signal(11),
65+
signal: Signal(signals::SIGSEGV as u8),
6466
},
6567
VcpuStopReason::Unknown => {
6668
log::warn!("Unknown stop reason received");
@@ -105,11 +107,9 @@ impl run_blocking::BlockingEventLoop for GdbBlockingEventLoop {
105107
log::info!("Received interrupt from GDB client - sending signal to target thread");
106108

107109
// Send a signal to the target thread to interrupt it
108-
let ret = unsafe { pthread_kill(target.get_thread_id(), SIGRTMIN()) };
109-
110-
log::info!("pthread_kill returned {}", ret);
110+
let res = target.interrupt_vcpu();
111111

112-
if ret < 0 && ret != libc::ESRCH {
112+
if !res {
113113
log::error!("Failed to send signal to target thread");
114114
return Err(GdbTargetError::SendSignalError);
115115
}

src/hyperlight_host/src/hypervisor/gdb/mod.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub(crate) use mshv_debug::MshvDebug;
4141
use thiserror::Error;
4242
use x86_64_target::HyperlightSandboxTarget;
4343

44+
use super::InterruptHandle;
4445
use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
4546
use crate::mem::layout::SandboxMemoryLayout;
4647
use crate::{HyperlightError, new_error};
@@ -147,6 +148,7 @@ pub(crate) enum DebugResponse {
147148
ErrorOccurred,
148149
GetCodeSectionOffset(u64),
149150
NotAllowed,
151+
InterruptHandle(Arc<dyn InterruptHandle>),
150152
ReadAddr(Vec<u8>),
151153
ReadRegisters(X86_64Regs),
152154
RemoveHwBreakpoint(bool),
@@ -158,7 +160,7 @@ pub(crate) enum DebugResponse {
158160
}
159161

160162
/// This trait is used to define common debugging functionality for Hypervisors
161-
pub(crate) trait GuestDebug {
163+
pub(super) trait GuestDebug {
162164
/// Type that wraps the vCPU functionality
163165
type Vcpu;
164166

@@ -380,7 +382,6 @@ impl<T, U> DebugCommChannel<T, U> {
380382
/// Creates a thread that handles gdb protocol
381383
pub(crate) fn create_gdb_thread(
382384
port: u16,
383-
thread_id: u64,
384385
) -> Result<DebugCommChannel<DebugResponse, DebugMsg>, GdbTargetError> {
385386
let (gdb_conn, hyp_conn) = DebugCommChannel::unbounded();
386387
let socket = format!("localhost:{}", port);
@@ -398,12 +399,23 @@ pub(crate) fn create_gdb_thread(
398399
let conn: Box<dyn ConnectionExt<Error = io::Error>> = Box::new(conn);
399400
let debugger = GdbStub::new(conn);
400401

401-
let mut target = HyperlightSandboxTarget::new(hyp_conn, thread_id);
402+
let mut target = HyperlightSandboxTarget::new(hyp_conn);
402403

403404
// Waits for vCPU to stop at entrypoint breakpoint
404-
let res = target.recv()?;
405-
if let DebugResponse::VcpuStopped(_) = res {
405+
let msg = target.recv()?;
406+
if let DebugResponse::InterruptHandle(handle) = msg {
407+
log::info!("Received interrupt handle: {:?}", handle);
408+
target.set_interrupt_handle(handle);
409+
} else {
410+
return Err(GdbTargetError::UnexpectedMessage);
411+
}
412+
413+
// Waits for vCPU to stop at entrypoint breakpoint
414+
let msg = target.recv()?;
415+
if let DebugResponse::VcpuStopped(_) = msg {
406416
event_loop_thread(debugger, &mut target);
417+
} else {
418+
return Err(GdbTargetError::UnexpectedMessage);
407419
}
408420

409421
Ok(())

src/hyperlight_host/src/hypervisor/gdb/x86_64_target.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use std::sync::Arc;
18+
1719
use crossbeam_channel::TryRecvError;
1820
use gdbstub::arch::Arch;
1921
use gdbstub::common::Signal;
@@ -30,20 +32,21 @@ use gdbstub::target::{Target, TargetError, TargetResult};
3032
use gdbstub_arch::x86::X86_64_SSE as GdbTargetArch;
3133

3234
use super::{DebugCommChannel, DebugMsg, DebugResponse, GdbTargetError, X86_64Regs};
35+
use crate::hypervisor::InterruptHandle;
3336

3437
/// Gdbstub target used by the gdbstub crate to provide GDB protocol implementation
3538
pub(crate) struct HyperlightSandboxTarget {
3639
/// Hypervisor communication channels
3740
hyp_conn: DebugCommChannel<DebugMsg, DebugResponse>,
38-
/// Thread ID
39-
thread_id: u64,
41+
/// Interrupt handle for the vCPU thread
42+
interrupt_handle: Option<Arc<dyn InterruptHandle>>,
4043
}
4144

4245
impl HyperlightSandboxTarget {
43-
pub(crate) fn new(hyp_conn: DebugCommChannel<DebugMsg, DebugResponse>, thread_id: u64) -> Self {
46+
pub(crate) fn new(hyp_conn: DebugCommChannel<DebugMsg, DebugResponse>) -> Self {
4447
HyperlightSandboxTarget {
4548
hyp_conn,
46-
thread_id,
49+
interrupt_handle: None,
4750
}
4851
}
4952

@@ -60,9 +63,9 @@ impl HyperlightSandboxTarget {
6063
self.hyp_conn.send(ev)
6164
}
6265

63-
/// Returns the thread ID
64-
pub(crate) fn get_thread_id(&self) -> u64 {
65-
self.thread_id
66+
/// Set the interrupt handle for the vCPU thread
67+
pub(crate) fn set_interrupt_handle(&mut self, handle: Arc<dyn InterruptHandle>) {
68+
self.interrupt_handle = Some(handle);
6669
}
6770

6871
/// Waits for a response over the communication channel
@@ -113,6 +116,17 @@ impl HyperlightSandboxTarget {
113116
}
114117
}
115118
}
119+
120+
/// Interrupts the vCPU execution
121+
pub(crate) fn interrupt_vcpu(&mut self) -> bool {
122+
if let Some(handle) = &self.interrupt_handle {
123+
handle.kill_from_debugger()
124+
} else {
125+
log::warn!("No interrupt handle set, cannot interrupt vCPU");
126+
127+
false
128+
}
129+
}
116130
}
117131

118132
impl Target for HyperlightSandboxTarget {
@@ -464,7 +478,7 @@ mod tests {
464478
fn test_gdb_target() {
465479
let (gdb_conn, hyp_conn) = DebugCommChannel::unbounded();
466480

467-
let mut target = HyperlightSandboxTarget::new(hyp_conn, 0);
481+
let mut target = HyperlightSandboxTarget::new(hyp_conn);
468482

469483
// Check response to read registers - send the response first to not be blocked
470484
// by the recv call in the target

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -397,42 +397,53 @@ impl HypervLinuxDriver {
397397

398398
Self::setup_initial_sregs(&mut vcpu_fd, pml4_ptr.absolute()?)?;
399399

400-
Ok(Self {
400+
let interrupt_handle = Arc::new(LinuxInterruptHandle {
401+
running: AtomicU64::new(0),
402+
cancel_requested: AtomicBool::new(false),
403+
#[cfg(gdb)]
404+
debug_interrupt: AtomicBool::new(false),
405+
#[cfg(all(
406+
target_arch = "x86_64",
407+
target_vendor = "unknown",
408+
target_os = "linux",
409+
target_env = "musl"
410+
))]
411+
tid: AtomicU64::new(unsafe { libc::pthread_self() as u64 }),
412+
#[cfg(not(all(
413+
target_arch = "x86_64",
414+
target_vendor = "unknown",
415+
target_os = "linux",
416+
target_env = "musl"
417+
)))]
418+
tid: AtomicU64::new(unsafe { libc::pthread_self() }),
419+
retry_delay: config.get_interrupt_retry_delay(),
420+
sig_rt_min_offset: config.get_interrupt_vcpu_sigrtmin_offset(),
421+
dropped: AtomicBool::new(false),
422+
});
423+
424+
#[allow(unused_mut)]
425+
let mut hv = Self {
401426
_mshv: mshv,
402427
vm_fd,
403428
vcpu_fd,
404429
mem_regions,
405430
entrypoint: entrypoint_ptr.absolute()?,
406431
orig_rsp: rsp_ptr,
407-
interrupt_handle: Arc::new(LinuxInterruptHandle {
408-
running: AtomicU64::new(0),
409-
cancel_requested: AtomicBool::new(false),
410-
#[cfg(all(
411-
target_arch = "x86_64",
412-
target_vendor = "unknown",
413-
target_os = "linux",
414-
target_env = "musl"
415-
))]
416-
tid: AtomicU64::new(unsafe { libc::pthread_self() as u64 }),
417-
#[cfg(not(all(
418-
target_arch = "x86_64",
419-
target_vendor = "unknown",
420-
target_os = "linux",
421-
target_env = "musl"
422-
)))]
423-
tid: AtomicU64::new(unsafe { libc::pthread_self() }),
424-
retry_delay: config.get_interrupt_retry_delay(),
425-
sig_rt_min_offset: config.get_interrupt_vcpu_sigrtmin_offset(),
426-
dropped: AtomicBool::new(false),
427-
}),
428-
432+
interrupt_handle: interrupt_handle.clone(),
429433
#[cfg(gdb)]
430434
debug,
431435
#[cfg(gdb)]
432436
gdb_conn,
433437
#[cfg(crashdump)]
434438
rt_cfg,
435-
})
439+
};
440+
441+
// Send the interrupt handle to the GDB thread if debugging is enabled
442+
// This is used to allow the GDB thread to stop the vCPU
443+
#[cfg(gdb)]
444+
hv.send_dbg_msg(DebugResponse::InterruptHandle(interrupt_handle))?;
445+
446+
Ok(hv)
436447
}
437448

438449
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
@@ -634,6 +645,14 @@ impl Hypervisor for HypervLinuxDriver {
634645
e
635646
)
636647
})?;
648+
#[cfg(not(gdb))]
649+
let debug_interrupt = false;
650+
#[cfg(gdb)]
651+
let debug_interrupt = self
652+
.interrupt_handle
653+
.debug_interrupt
654+
.load(Ordering::Relaxed);
655+
637656
// Don't run the vcpu if `cancel_requested` is true
638657
//
639658
// Note: if a `InterruptHandle::kill()` called while this thread is **here**
@@ -642,6 +661,7 @@ impl Hypervisor for HypervLinuxDriver {
642661
.interrupt_handle
643662
.cancel_requested
644663
.load(Ordering::Relaxed)
664+
|| debug_interrupt
645665
{
646666
Err(MshvError::Errno(vmm_sys_util::errno::Error::new(
647667
libc::EINTR,
@@ -667,6 +687,11 @@ impl Hypervisor for HypervLinuxDriver {
667687
.interrupt_handle
668688
.cancel_requested
669689
.load(Ordering::Relaxed);
690+
#[cfg(gdb)]
691+
let debug_interrupt = self
692+
.interrupt_handle
693+
.debug_interrupt
694+
.load(Ordering::Relaxed);
670695
// Note: if a `InterruptHandle::kill()` called while this thread is **here**
671696
// Then `cancel_requested` will be set to true again, which will cancel the **next vcpu run**.
672697
// Additionally signals will be sent to this thread until `running` is set to false.
@@ -754,27 +779,23 @@ impl Hypervisor for HypervLinuxDriver {
754779
Err(e) => match e.errno() {
755780
// we send a signal to the thread to cancel execution this results in EINTR being returned by KVM so we return Cancelled
756781
libc::EINTR => {
757-
// If cancellation was not requested for this specific vm, the vcpu was interrupted because of stale signal
758-
// that was meant to be delivered to a previous/other vcpu on this same thread, so let's ignore it
782+
// If cancellation was not requested for this specific vm, the vcpu was interrupted because of debug interrupt or
783+
// a stale signal that meant to be delivered to a previous/other vcpu on this same thread, so let's ignore it
759784
if cancel_requested {
760785
self.interrupt_handle
761786
.cancel_requested
762787
.store(false, Ordering::Relaxed);
763788
HyperlightExit::Cancelled()
764789
} else {
765-
// In case of the gdb feature, if no cancellation was requested,
766-
// and the debugging is enabled it means the vCPU was stopped because
767-
// of an interrupt coming from the debugger thread
768790
#[cfg(gdb)]
769-
if self.debug.is_some() {
791+
if debug_interrupt {
792+
self.interrupt_handle
793+
.debug_interrupt
794+
.store(false, Ordering::Relaxed);
795+
770796
// If the vCPU was stopped because of an interrupt, we need to
771797
// return a special exit reason so that the gdb thread can handle it
772798
// and resume execution
773-
// NOTE: There is a chance that the vCPU was stopped because of a stale
774-
// signal that was meant to be delivered to a previous/other vCPU on this
775-
// same thread, however, we cannot distinguish between the two cases, so
776-
// we assume that the vCPU was stopped because of an interrupt.
777-
// This is fine, because the debugger will be notified about an interrupt
778799
HyperlightExit::Debug(VcpuStopReason::Interrupt)
779800
} else {
780801
HyperlightExit::Retry()

0 commit comments

Comments
 (0)