Skip to content

Commit 9afdaba

Browse files
committed
gdb: Update debugging to take into account the new threading model
Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent 3a12b3c commit 9afdaba

File tree

11 files changed

+122
-1177
lines changed

11 files changed

+122
-1177
lines changed

src/hyperlight_host/examples/guest-debugging/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ fn get_sandbox_cfg() -> Option<SandboxConfiguration> {
3939
}
4040

4141
fn main() -> hyperlight_host::Result<()> {
42+
env_logger::init();
4243
let cfg = get_sandbox_cfg();
4344

4445
// Create an uninitialized sandbox with a guest binary

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

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use gdbstub::stub::{
2020
run_blocking, BaseStopReason, DisconnectReason, GdbStub, SingleThreadStopReason,
2121
};
2222
#[cfg(target_os = "linux")]
23-
use libc::{pthread_kill, SIGRTMIN};
23+
use libc::SIGRTMIN;
2424

2525
use super::x86_64_target::HyperlightSandboxTarget;
2626
use super::{DebugResponse, GdbTargetError, VcpuStopReason};
@@ -55,14 +55,14 @@ impl run_blocking::BlockingEventLoop for GdbBlockingEventLoop {
5555
VcpuStopReason::EntryPointBp => BaseStopReason::HwBreak(()),
5656
// This is a consequence of the GDB client sending an interrupt signal
5757
// to the target thread
58-
VcpuStopReason::Interrupt => BaseStopReason::SignalWithThread {
59-
tid: (),
58+
VcpuStopReason::Interrupt => {
6059
#[cfg(target_os = "linux")]
61-
signal: Signal(SIGRTMIN() as u8),
60+
let signal = Signal(SIGRTMIN() as u8);
6261
#[cfg(target_os = "windows")]
63-
// TODO: Handle the signal properly
64-
signal: Signal(53u8),
65-
},
62+
let signal = Signal(53u8); // SIGINT on Windows
63+
64+
BaseStopReason::SignalWithThread { tid: (), signal }
65+
}
6666
VcpuStopReason::Unknown => {
6767
log::warn!("Unknown stop reason received");
6868

@@ -106,18 +106,9 @@ impl run_blocking::BlockingEventLoop for GdbBlockingEventLoop {
106106
log::info!("Received interrupt from GDB client - sending signal to target thread");
107107

108108
// Send a signal to the target thread to interrupt it
109-
#[cfg(target_os = "linux")]
110-
let ret = unsafe { pthread_kill(target.get_thread_id(), SIGRTMIN()) };
111-
112-
#[cfg(target_os = "windows")]
113-
let ret = {
114-
// TODO: Implement Windows signal sending
115-
libc::ESRCH
116-
};
117-
118-
log::info!("pthread_kill returned {}", ret);
109+
let res = target.interrupt_vcpu();
119110

120-
if ret < 0 && ret != libc::ESRCH {
111+
if !res {
121112
log::error!("Failed to send signal to target thread");
122113
return Err(GdbTargetError::SendSignalError);
123114
}

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
4949
use crate::mem::layout::SandboxMemoryLayout;
5050
use crate::{new_error, HyperlightError};
5151

52+
use super::InterruptHandle;
53+
5254
#[derive(Debug, Error)]
5355
pub(crate) enum GdbTargetError {
5456
#[error("Error encountered while binding to address and port")]
@@ -149,6 +151,7 @@ pub(crate) enum DebugResponse {
149151
DisableDebug,
150152
ErrorOccurred,
151153
GetCodeSectionOffset(u64),
154+
InterruptHandle(Arc<dyn InterruptHandle>),
152155
ReadAddr(Vec<u8>),
153156
ReadRegisters(X86_64Regs),
154157
RemoveHwBreakpoint(bool),
@@ -382,7 +385,6 @@ impl<T, U> DebugCommChannel<T, U> {
382385
/// Creates a thread that handles gdb protocol
383386
pub(crate) fn create_gdb_thread(
384387
port: u16,
385-
thread_id: u64,
386388
) -> Result<DebugCommChannel<DebugResponse, DebugMsg>, GdbTargetError> {
387389
let (gdb_conn, hyp_conn) = DebugCommChannel::unbounded();
388390
let socket = format!("localhost:{}", port);
@@ -400,12 +402,23 @@ pub(crate) fn create_gdb_thread(
400402
let conn: Box<dyn ConnectionExt<Error = io::Error>> = Box::new(conn);
401403
let debugger = GdbStub::new(conn);
402404

403-
let mut target = HyperlightSandboxTarget::new(hyp_conn, thread_id);
405+
let mut target = HyperlightSandboxTarget::new(hyp_conn);
406+
407+
// Waits for vCPU to stop at entrypoint breakpoint
408+
let msg = target.recv()?;
409+
if let DebugResponse::InterruptHandle(handle) = msg {
410+
log::info!("Received interrupt handle: {:?}", handle);
411+
target.set_interrupt_handle(handle);
412+
} else {
413+
return Err(GdbTargetError::UnexpectedMessage);
414+
}
404415

405416
// Waits for vCPU to stop at entrypoint breakpoint
406-
let res = target.recv()?;
407-
if let DebugResponse::VcpuStopped(_) = res {
417+
let msg = target.recv()?;
418+
if let DebugResponse::VcpuStopped(_) = msg {
408419
event_loop_thread(debugger, &mut target);
420+
} else {
421+
return Err(GdbTargetError::UnexpectedMessage);
409422
}
410423

411424
Ok(())

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

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,25 @@ use gdbstub::target::ext::breakpoints::{
2828
use gdbstub::target::ext::section_offsets::{Offsets, SectionOffsets};
2929
use gdbstub::target::{Target, TargetError, TargetResult};
3030
use gdbstub_arch::x86::X86_64_SSE as GdbTargetArch;
31+
use std::sync::Arc;
32+
33+
use crate::hypervisor::InterruptHandle;
3134

3235
use super::{DebugCommChannel, DebugMsg, DebugResponse, GdbTargetError, X86_64Regs};
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-
#[allow(dead_code)]
40-
thread_id: u64,
41+
/// Interrupt handle for the vCPU thread
42+
interrupt_handle: Option<Arc<dyn InterruptHandle>>,
4143
}
4244

4345
impl HyperlightSandboxTarget {
44-
pub(crate) fn new(hyp_conn: DebugCommChannel<DebugMsg, DebugResponse>, thread_id: u64) -> Self {
46+
pub(crate) fn new(hyp_conn: DebugCommChannel<DebugMsg, DebugResponse>) -> Self {
4547
HyperlightSandboxTarget {
4648
hyp_conn,
47-
thread_id,
49+
interrupt_handle: None,
4850
}
4951
}
5052

@@ -61,10 +63,9 @@ impl HyperlightSandboxTarget {
6163
self.hyp_conn.send(ev)
6264
}
6365

64-
/// Returns the thread ID
65-
#[allow(dead_code)]
66-
pub(crate) fn get_thread_id(&self) -> u64 {
67-
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);
6869
}
6970

7071
/// Waits for a response over the communication channel
@@ -109,6 +110,17 @@ impl HyperlightSandboxTarget {
109110
}
110111
}
111112
}
113+
114+
/// Interrupts the vCPU execution
115+
pub(crate) fn interrupt_vcpu(&mut self) -> bool {
116+
if let Some(handle) = &self.interrupt_handle {
117+
handle.kill()
118+
} else {
119+
log::warn!("No interrupt handle set, cannot interrupt vCPU");
120+
121+
false
122+
}
123+
}
112124
}
113125

114126
impl Target for HyperlightSandboxTarget {
@@ -418,7 +430,7 @@ mod tests {
418430
fn test_gdb_target() {
419431
let (gdb_conn, hyp_conn) = DebugCommChannel::unbounded();
420432

421-
let mut target = HyperlightSandboxTarget::new(hyp_conn, 0);
433+
let mut target = HyperlightSandboxTarget::new(hyp_conn);
422434

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

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -388,27 +388,36 @@ impl HypervLinuxDriver {
388388

389389
Self::setup_initial_sregs(&mut vcpu_fd, pml4_ptr.absolute()?)?;
390390

391-
Ok(Self {
391+
let interrupt_handle = Arc::new(LinuxInterruptHandle {
392+
running: AtomicU64::new(0),
393+
cancel_requested: AtomicBool::new(false),
394+
tid: AtomicU64::new(unsafe { libc::pthread_self() }),
395+
retry_delay: config.get_interrupt_retry_delay(),
396+
sig_rt_min_offset: config.get_interrupt_vcpu_sigrtmin_offset(),
397+
dropped: AtomicBool::new(false),
398+
});
399+
400+
#[allow(unused_mut)]
401+
let mut hv = Self {
392402
_mshv: mshv,
393403
vm_fd,
394404
vcpu_fd,
395405
mem_regions,
396406
entrypoint: entrypoint_ptr.absolute()?,
397407
orig_rsp: rsp_ptr,
398-
interrupt_handle: Arc::new(LinuxInterruptHandle {
399-
running: AtomicU64::new(0),
400-
cancel_requested: AtomicBool::new(false),
401-
tid: AtomicU64::new(unsafe { libc::pthread_self() }),
402-
retry_delay: config.get_interrupt_retry_delay(),
403-
sig_rt_min_offset: config.get_interrupt_vcpu_sigrtmin_offset(),
404-
dropped: AtomicBool::new(false),
405-
}),
406-
408+
interrupt_handle: interrupt_handle.clone(),
407409
#[cfg(gdb)]
408410
debug,
409411
#[cfg(gdb)]
410412
gdb_conn,
411-
})
413+
};
414+
415+
// Send the interrupt handle to the GDB thread if debugging is enabled
416+
// This is used to allow the GDB thread to stop the vCPU
417+
#[cfg(gdb)]
418+
hv.send_dbg_msg(DebugResponse::InterruptHandle(interrupt_handle))?;
419+
420+
Ok(hv)
412421
}
413422

414423
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]

src/hyperlight_host/src/hypervisor/hyperv_windows.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use {
3636
super::handlers::DbgMemAccessHandlerWrapper,
3737
crate::hypervisor::handlers::DbgMemAccessHandlerCaller,
3838
crate::{log_then_return, HyperlightError},
39-
std::sync::{Arc, Mutex},
39+
std::sync::Mutex,
4040
};
4141

4242
use super::fpu::{FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
@@ -327,25 +327,36 @@ impl HypervWindowsDriver {
327327
// subtract 2 pages for the guard pages, since when we copy memory to and from surrogate process,
328328
// we don't want to copy the guard pages themselves (that would cause access violation)
329329
let mem_size = raw_size - 2 * PAGE_SIZE_USIZE;
330-
Ok(Self {
330+
331+
let interrupt_handle = Arc::new(WindowsInterruptHandle {
332+
running: AtomicBool::new(false),
333+
cancel_requested: AtomicBool::new(false),
334+
partition_handle,
335+
dropped: AtomicBool::new(false),
336+
});
337+
338+
#[allow(unused_mut)]
339+
let mut hv = Self {
331340
size: mem_size,
332341
processor: proc,
333342
_surrogate_process: surrogate_process,
334343
source_address: raw_source_address,
335344
entrypoint,
336345
orig_rsp: GuestPtr::try_from(RawPtr::from(rsp))?,
337346
mem_regions,
338-
interrupt_handle: Arc::new(WindowsInterruptHandle {
339-
running: AtomicBool::new(false),
340-
cancel_requested: AtomicBool::new(false),
341-
partition_handle,
342-
dropped: AtomicBool::new(false),
343-
}),
347+
interrupt_handle: interrupt_handle.clone(),
344348
#[cfg(gdb)]
345349
debug,
346350
#[cfg(gdb)]
347351
gdb_conn,
348-
})
352+
};
353+
354+
// Send the interrupt handle to the GDB thread if debugging is enabled
355+
// This is used to allow the GDB thread to stop the vCPU
356+
#[cfg(gdb)]
357+
hv.send_dbg_msg(DebugResponse::InterruptHandle(interrupt_handle))?;
358+
359+
Ok(hv)
349360
}
350361

351362
fn setup_initial_sregs(proc: &mut VMProcessor, pml4_addr: u64) -> Result<()> {
@@ -813,6 +824,7 @@ impl Drop for HypervWindowsDriver {
813824
}
814825
}
815826

827+
#[derive(Debug)]
816828
pub struct WindowsInterruptHandle {
817829
// `WHvCancelRunVirtualProcessor()` will return Ok even if the vcpu is not running, which is the reason we need this flag.
818830
running: AtomicBool,

0 commit comments

Comments
 (0)