Skip to content

Commit daaf95b

Browse files
committed
Generic approach to gdb support
- To be easier to add Hyper-V gdb support Signed-off-by: Doru Blânzeanu <[email protected]>
1 parent ca61def commit daaf95b

File tree

4 files changed

+168
-112
lines changed

4 files changed

+168
-112
lines changed

src/hyperlight_host/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ cfg_aliases = "0.2.1"
115115
built = { version = "0.7.0", features = ["chrono", "git2"] }
116116

117117
[features]
118-
default = ["kvm", "mshv", "seccomp"]
118+
default = ["gdb", "kvm", "mshv", "seccomp"]
119119
seccomp = ["dep:seccompiler"]
120120
function_call_metrics = []
121121
executable_heap = []

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

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1+
use std::marker::PhantomData;
2+
3+
use gdbstub::arch::Arch;
14
use gdbstub::common::Signal;
25
use gdbstub::conn::ConnectionExt;
36
use gdbstub::stub::run_blocking::{self, WaitForStopReasonError};
47
use gdbstub::stub::{DisconnectReason, GdbStub, SingleThreadStopReason};
8+
use gdbstub::target::Target;
59

6-
use super::target::HyperlightKvmSandboxTarget;
7-
use super::GdbTargetError;
810
use crate::hypervisor::gdb::GdbDebug;
911

10-
pub struct GdbBlockingEventLoop;
12+
pub struct GdbBlockingEventLoop<T> {
13+
_phantom: PhantomData<T>,
14+
}
1115

12-
impl run_blocking::BlockingEventLoop for GdbBlockingEventLoop {
16+
impl<T: GdbDebug> run_blocking::BlockingEventLoop for GdbBlockingEventLoop<T>
17+
where
18+
<T as Target>::Error: From<crossbeam_channel::TryRecvError>,
19+
{
1320
type Connection = Box<dyn ConnectionExt<Error = std::io::Error>>;
14-
type StopReason = SingleThreadStopReason<u64>;
15-
type Target = HyperlightKvmSandboxTarget;
21+
type StopReason = SingleThreadStopReason<<T::Arch as Arch>::Usize>;
22+
type Target = T;
1623

1724
fn wait_for_stop_reason(
1825
target: &mut Self::Target,
@@ -46,10 +53,8 @@ impl run_blocking::BlockingEventLoop for GdbBlockingEventLoop {
4653
return Ok(run_blocking::Event::TargetStopped(stop_response));
4754
}
4855
Err(crossbeam_channel::TryRecvError::Empty) => (),
49-
Err(_) => {
50-
return Err(run_blocking::WaitForStopReasonError::Target(
51-
GdbTargetError::QueueError,
52-
));
56+
Err(e) => {
57+
return Err(run_blocking::WaitForStopReasonError::Target(e.into()));
5358
}
5459
}
5560

@@ -75,11 +80,14 @@ impl run_blocking::BlockingEventLoop for GdbBlockingEventLoop {
7580
}
7681
}
7782

78-
pub fn event_loop_thread(
79-
debugger: GdbStub<HyperlightKvmSandboxTarget, Box<dyn ConnectionExt<Error = std::io::Error>>>,
80-
target: &mut HyperlightKvmSandboxTarget,
81-
) {
82-
match debugger.run_blocking::<GdbBlockingEventLoop>(target) {
83+
pub fn event_loop_thread<T: GdbDebug>(
84+
debugger: GdbStub<T, Box<dyn ConnectionExt<Error = std::io::Error>>>,
85+
target: &mut T,
86+
) where
87+
<T as Target>::Error: std::fmt::Debug,
88+
<T as Target>::Error: From<crossbeam_channel::TryRecvError>,
89+
{
90+
match debugger.run_blocking::<GdbBlockingEventLoop<T>>(target) {
8391
Ok(disconnect_reason) => match disconnect_reason {
8492
DisconnectReason::Disconnect => log::info!("Gdb client disconnected"),
8593
DisconnectReason::TargetExited(code) => {
@@ -94,9 +102,9 @@ pub fn event_loop_thread(
94102
if e.is_target_error() {
95103
log::error!("Target encountered a fatal error: {e:?}");
96104
} else if e.is_connection_error() {
97-
log::error!("connection error: {:?}", e);
105+
log::error!("connection error: {e:?}");
98106
} else {
99-
log::error!("gdbstub got a fatal error {:?}", e);
107+
log::error!("gdbstub got a fatal error: {e:?}");
100108
}
101109
}
102110
}

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

Lines changed: 83 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,80 @@
11
mod event_loop;
22
pub mod target;
33

4+
use std::io::{self, ErrorKind};
45
use std::net::TcpListener;
56
use std::thread;
67

78
use crossbeam_channel::{Receiver, Sender, TryRecvError};
89
use event_loop::event_loop_thread;
10+
use gdbstub::arch::Arch;
911
use gdbstub::conn::ConnectionExt;
10-
use gdbstub::stub::GdbStub;
11-
use target::HyperlightKvmSandboxTarget;
12+
use gdbstub::stub::{BaseStopReason, GdbStub};
13+
use gdbstub::target::Target;
1214

1315
#[derive(Debug)]
1416
pub enum GdbTargetError {
1517
BindError,
1618
InstructionPointerError,
1719
ListenerError,
18-
QueueError,
1920
ReadRegistersError,
2021
ReceiveMsgError,
2122
CannotResume,
23+
QueueError,
2224
SendMsgError,
2325
SetGuestDebugError,
24-
SpawnThreadError,
2526
InvalidGva,
2627
UnexpectedMessageError,
2728
WriteRegistersError,
29+
UnexpectedError,
30+
}
31+
32+
impl From<io::Error> for GdbTargetError {
33+
fn from(err: io::Error) -> Self {
34+
match err.kind() {
35+
ErrorKind::AddrInUse => Self::BindError,
36+
ErrorKind::AddrNotAvailable => Self::BindError,
37+
ErrorKind::ConnectionReset
38+
| ErrorKind::ConnectionAborted
39+
| ErrorKind::ConnectionRefused => Self::ListenerError,
40+
_ => Self::UnexpectedError,
41+
}
42+
}
43+
}
44+
45+
impl From<DebugMessage> for GdbTargetError {
46+
fn from(value: DebugMessage) -> Self {
47+
match value {
48+
DebugMessage::VcpuStoppedEv => GdbTargetError::UnexpectedMessageError,
49+
_ => GdbTargetError::UnexpectedMessageError,
50+
}
51+
}
52+
}
53+
54+
impl From<TryRecvError> for GdbTargetError {
55+
fn from(_value: TryRecvError) -> Self {
56+
GdbTargetError::QueueError
57+
}
2858
}
2959

3060
/// Trait that provides common communication methods for targets
31-
pub trait GdbDebug {
61+
pub trait GdbDebug: Target {
3262
/// Sends a message to the Hypervisor
33-
fn send(&self, ev: DebugMessage) -> Result<(), GdbTargetError>;
63+
fn send(&self, ev: DebugMessage) -> Result<(), <Self as Target>::Error>;
3464
/// Waits for a message from the Hypervisor
35-
fn recv(&self) -> Result<DebugMessage, GdbTargetError>;
65+
fn recv(&self) -> Result<DebugMessage, <Self as Target>::Error>;
3666
/// Checks for a pending message from the Hypervisor
3767
fn try_recv(&self) -> Result<DebugMessage, TryRecvError>;
68+
69+
/// Marks the vCPU as paused
70+
fn pause_vcpu(&mut self);
71+
/// Resumes the vCPU
72+
fn resume_vcpu(&mut self) -> Result<(), <Self as Target>::Error>;
73+
/// Returns the reason why vCPU stopped
74+
#[allow(clippy::type_complexity)]
75+
fn get_stop_reason(
76+
&self,
77+
) -> Result<Option<BaseStopReason<(), <Self::Arch as Arch>::Usize>>, Self::Error>;
3878
}
3979

4080
/// Event sent to the VCPU execution loop
@@ -93,46 +133,52 @@ impl GdbConnection {
93133
}
94134

95135
/// Creates a thread that handles gdb protocol
96-
pub fn create_gdb_thread(mut target: HyperlightKvmSandboxTarget) -> Result<(), GdbTargetError> {
136+
pub fn create_gdb_thread<T: GdbDebug + Send + 'static>(
137+
mut target: T,
138+
) -> Result<(), <T as Target>::Error>
139+
where
140+
<T as Target>::Error:
141+
std::fmt::Debug + Send + From<io::Error> + From<DebugMessage> + From<TryRecvError>,
142+
{
97143
// TODO: Address multiple sandboxes scenario
98144
let socket = format!("localhost:{}", 8081);
99145

100146
log::info!("Listening on {:?}", socket);
101-
let listener = TcpListener::bind(socket).map_err(|_| GdbTargetError::BindError)?;
147+
let listener = TcpListener::bind(socket)?;
102148

103149
log::info!("Starting GDB thread");
104150
let _handle = thread::Builder::new()
105151
.name("GDB handler".to_string())
106-
.spawn(move || -> Result<(), GdbTargetError> {
107-
let mut initial_conn = true;
108-
let result = loop {
109-
log::info!("Waiting for GDB connection ... ");
110-
let (conn, _) = listener
111-
.accept()
112-
.map_err(|_| GdbTargetError::ListenerError)?;
113-
114-
let conn: Box<dyn ConnectionExt<Error = std::io::Error>> = Box::new(conn);
115-
let debugger = GdbStub::new(conn);
116-
117-
if initial_conn {
118-
// Waits for vCPU to stop at entrypoint breakpoint
119-
if let DebugMessage::VcpuStoppedEv = target.recv()? {
120-
target.pause_vcpu();
121-
122-
event_loop_thread(debugger, &mut target);
123-
initial_conn = false;
152+
.spawn(
153+
move || -> Result<(), <T as gdbstub::target::Target>::Error> {
154+
let mut initial_conn = true;
155+
let result = loop {
156+
log::info!("Waiting for GDB connection ... ");
157+
let (conn, _) = listener.accept().map_err(<T as Target>::Error::from)?;
158+
159+
let conn: Box<dyn ConnectionExt<Error = io::Error>> = Box::new(conn);
160+
let debugger = GdbStub::new(conn);
161+
162+
if initial_conn {
163+
// Waits for vCPU to stop at entrypoint breakpoint
164+
let res = target.recv()?;
165+
if let DebugMessage::VcpuStoppedEv = res {
166+
target.pause_vcpu();
167+
168+
event_loop_thread(debugger, &mut target);
169+
initial_conn = false;
170+
} else {
171+
break Err(res)?;
172+
}
124173
} else {
125-
break Err(GdbTargetError::UnexpectedMessageError);
174+
log::info!("Reattaching GDB connection ... ");
175+
event_loop_thread(debugger, &mut target);
126176
}
127-
} else {
128-
log::info!("Reattaching GDB connection ... ");
129-
event_loop_thread(debugger, &mut target);
130-
}
131-
};
132-
133-
result
134-
})
135-
.map_err(|_| GdbTargetError::SpawnThreadError)?;
177+
};
178+
179+
result
180+
},
181+
);
136182

137183
Ok(())
138184
}

0 commit comments

Comments
 (0)