use std::sync::Arc;
use crossbeam_channel::TryRecvError;
use gdbstub::arch::Arch;
use gdbstub::common::Signal;
use gdbstub::target::ext::base::BaseOps;
use gdbstub::target::ext::base::singlethread::{
SingleThreadBase, SingleThreadResume, SingleThreadResumeOps, SingleThreadSingleStep,
SingleThreadSingleStepOps,
};
use gdbstub::target::ext::breakpoints::{
Breakpoints, BreakpointsOps, HwBreakpoint, HwBreakpointOps, SwBreakpoint, SwBreakpointOps,
};
use gdbstub::target::ext::section_offsets::{Offsets, SectionOffsets};
use gdbstub::target::{Target, TargetError, TargetResult};
use gdbstub_arch::x86::X86_64_SSE as GdbTargetArch;
use super::{DebugCommChannel, DebugMsg, DebugResponse, GdbTargetError};
use crate::hypervisor::InterruptHandle;
use crate::hypervisor::regs::{CommonFpu, CommonRegisters};
pub(crate) struct HyperlightSandboxTarget {
hyp_conn: DebugCommChannel<DebugMsg, DebugResponse>,
interrupt_handle: Option<Arc<dyn InterruptHandle>>,
}
impl HyperlightSandboxTarget {
pub(crate) fn new(hyp_conn: DebugCommChannel<DebugMsg, DebugResponse>) -> Self {
HyperlightSandboxTarget {
hyp_conn,
interrupt_handle: None,
}
}
fn send_command(&self, cmd: DebugMsg) -> Result<DebugResponse, GdbTargetError> {
self.send(cmd)?;
self.recv()
}
fn send(&self, ev: DebugMsg) -> Result<(), GdbTargetError> {
self.hyp_conn.send(ev)
}
pub(crate) fn set_interrupt_handle(&mut self, handle: Arc<dyn InterruptHandle>) {
self.interrupt_handle = Some(handle);
}
pub(crate) fn recv(&self) -> Result<DebugResponse, GdbTargetError> {
self.hyp_conn.recv()
}
pub(crate) fn resume_vcpu(&mut self) -> Result<(), GdbTargetError> {
tracing::info!("Resume vCPU execution");
match self.send_command(DebugMsg::Continue)? {
DebugResponse::Continue => Ok(()),
DebugResponse::NotAllowed => {
tracing::error!("Action not allowed at this time, crash might have occurred");
Ok(())
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(GdbTargetError::UnexpectedMessage)
}
}
}
pub(crate) fn try_recv(&self) -> Result<DebugResponse, TryRecvError> {
self.hyp_conn.try_recv()
}
pub(crate) fn disable_debug(&mut self) -> Result<(), GdbTargetError> {
tracing::info!("Disable debugging and resume execution");
match self.send_command(DebugMsg::DisableDebug)? {
DebugResponse::DisableDebug => Ok(()),
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(GdbTargetError::UnexpectedError)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(GdbTargetError::UnexpectedMessage)
}
}
}
pub(crate) fn interrupt_vcpu(&mut self) -> bool {
if let Some(handle) = &self.interrupt_handle {
handle.kill_from_debugger()
} else {
tracing::warn!("No interrupt handle set, cannot interrupt vCPU");
false
}
}
}
impl Target for HyperlightSandboxTarget {
type Arch = GdbTargetArch;
type Error = GdbTargetError;
fn support_breakpoints(&mut self) -> Option<BreakpointsOps<'_, Self>> {
Some(self)
}
#[inline(always)]
fn base_ops(&mut self) -> BaseOps<'_, Self::Arch, Self::Error> {
BaseOps::SingleThread(self)
}
fn support_section_offsets(
&mut self,
) -> Option<gdbstub::target::ext::section_offsets::SectionOffsetsOps<'_, Self>> {
Some(self)
}
}
impl SingleThreadBase for HyperlightSandboxTarget {
fn read_addrs(
&mut self,
gva: <Self::Arch as Arch>::Usize,
data: &mut [u8],
) -> TargetResult<usize, Self> {
tracing::debug!("Read addr: {:X} len: {:X}", gva, data.len());
match self.send_command(DebugMsg::ReadAddr(gva, data.len()))? {
DebugResponse::ReadAddr(v) => {
data.copy_from_slice(&v);
Ok(v.len())
}
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(TargetError::NonFatal)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(TargetError::Fatal(GdbTargetError::UnexpectedMessage))
}
}
}
fn write_addrs(
&mut self,
gva: <Self::Arch as Arch>::Usize,
data: &[u8],
) -> TargetResult<(), Self> {
tracing::debug!("Write addr: {:X} len: {:X}", gva, data.len());
let v = Vec::from(data);
match self.send_command(DebugMsg::WriteAddr(gva, v))? {
DebugResponse::WriteAddr => Ok(()),
DebugResponse::NotAllowed => {
tracing::error!("Action not allowed at this time, crash might have occurred");
Ok(())
}
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(TargetError::NonFatal)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(TargetError::Fatal(GdbTargetError::UnexpectedMessage))
}
}
}
fn read_registers(
&mut self,
regs: &mut <Self::Arch as Arch>::Registers,
) -> TargetResult<(), Self> {
tracing::debug!("Read regs");
match self.send_command(DebugMsg::ReadRegisters)? {
DebugResponse::ReadRegisters(boxed_regs) => {
let (read_regs, read_fpu) = boxed_regs.as_ref();
regs.regs[0] = read_regs.rax;
regs.regs[1] = read_regs.rbp;
regs.regs[2] = read_regs.rcx;
regs.regs[3] = read_regs.rdx;
regs.regs[4] = read_regs.rsi;
regs.regs[5] = read_regs.rdi;
regs.regs[6] = read_regs.rbp;
regs.regs[7] = read_regs.rsp;
regs.regs[8] = read_regs.r8;
regs.regs[9] = read_regs.r9;
regs.regs[10] = read_regs.r10;
regs.regs[11] = read_regs.r11;
regs.regs[12] = read_regs.r12;
regs.regs[13] = read_regs.r13;
regs.regs[14] = read_regs.r14;
regs.regs[15] = read_regs.r15;
regs.rip = read_regs.rip;
regs.eflags = read_regs.rflags as u32;
regs.xmm = read_fpu.xmm.map(u128::from_le_bytes);
regs.mxcsr = read_fpu.mxcsr;
Ok(())
}
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(TargetError::NonFatal)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(TargetError::Fatal(GdbTargetError::UnexpectedMessage))
}
}
}
fn write_registers(
&mut self,
regs: &<Self::Arch as Arch>::Registers,
) -> TargetResult<(), Self> {
tracing::debug!("Write regs");
let common_regs = CommonRegisters {
rax: regs.regs[0],
rbx: regs.regs[1],
rcx: regs.regs[2],
rdx: regs.regs[3],
rsi: regs.regs[4],
rdi: regs.regs[5],
rbp: regs.regs[6],
rsp: regs.regs[7],
r8: regs.regs[8],
r9: regs.regs[9],
r10: regs.regs[10],
r11: regs.regs[11],
r12: regs.regs[12],
r13: regs.regs[13],
r14: regs.regs[14],
r15: regs.regs[15],
rip: regs.rip,
rflags: u64::from(regs.eflags),
};
let mut xmm = [[0u8; 16]; 16];
for (i, ®) in regs.xmm.iter().enumerate() {
xmm[i] = reg.to_le_bytes();
}
let common_fpu = CommonFpu {
xmm,
mxcsr: regs.mxcsr,
..Default::default()
};
match self.send_command(DebugMsg::WriteRegisters(Box::new((
common_regs,
common_fpu,
))))? {
DebugResponse::WriteRegisters => Ok(()),
DebugResponse::NotAllowed => {
tracing::error!("Action not allowed at this time, crash might have occurred");
Ok(())
}
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(TargetError::NonFatal)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(TargetError::Fatal(GdbTargetError::UnexpectedMessage))
}
}
}
fn support_resume(&mut self) -> Option<SingleThreadResumeOps<'_, Self>> {
Some(self)
}
}
impl SectionOffsets for HyperlightSandboxTarget {
fn get_section_offsets(&mut self) -> Result<Offsets<<Self::Arch as Arch>::Usize>, Self::Error> {
tracing::debug!("Get section offsets");
match self.send_command(DebugMsg::GetCodeSectionOffset)? {
DebugResponse::GetCodeSectionOffset(text) => Ok(Offsets::Segments {
text_seg: text,
data_seg: None,
}),
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(GdbTargetError::UnexpectedError)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(GdbTargetError::UnexpectedMessage)
}
}
}
}
impl Breakpoints for HyperlightSandboxTarget {
fn support_hw_breakpoint(&mut self) -> Option<HwBreakpointOps<'_, Self>> {
Some(self)
}
fn support_sw_breakpoint(&mut self) -> Option<SwBreakpointOps<'_, Self>> {
Some(self)
}
}
impl HwBreakpoint for HyperlightSandboxTarget {
fn add_hw_breakpoint(
&mut self,
addr: <Self::Arch as Arch>::Usize,
_kind: <Self::Arch as Arch>::BreakpointKind,
) -> TargetResult<bool, Self> {
tracing::debug!("Add hw breakpoint at address {:X}", addr);
match self.send_command(DebugMsg::AddHwBreakpoint(addr))? {
DebugResponse::AddHwBreakpoint(rsp) => Ok(rsp),
DebugResponse::NotAllowed => {
tracing::error!("Action not allowed at this time, crash might have occurred");
Err(TargetError::NonFatal)
}
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(TargetError::NonFatal)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(TargetError::Fatal(GdbTargetError::UnexpectedMessage))
}
}
}
fn remove_hw_breakpoint(
&mut self,
addr: <Self::Arch as Arch>::Usize,
_kind: <Self::Arch as Arch>::BreakpointKind,
) -> TargetResult<bool, Self> {
tracing::debug!("Remove hw breakpoint at address {:X}", addr);
match self.send_command(DebugMsg::RemoveHwBreakpoint(addr))? {
DebugResponse::RemoveHwBreakpoint(rsp) => Ok(rsp),
DebugResponse::NotAllowed => {
tracing::error!("Action not allowed at this time, crash might have occurred");
Err(TargetError::NonFatal)
}
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(TargetError::NonFatal)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(TargetError::Fatal(GdbTargetError::UnexpectedMessage))
}
}
}
}
impl SwBreakpoint for HyperlightSandboxTarget {
fn add_sw_breakpoint(
&mut self,
addr: <Self::Arch as Arch>::Usize,
_kind: <Self::Arch as Arch>::BreakpointKind,
) -> TargetResult<bool, Self> {
tracing::debug!("Add sw breakpoint at address {:X}", addr);
match self.send_command(DebugMsg::AddSwBreakpoint(addr))? {
DebugResponse::AddSwBreakpoint(rsp) => Ok(rsp),
DebugResponse::NotAllowed => {
tracing::error!("Action not allowed at this time, crash might have occurred");
Err(TargetError::NonFatal)
}
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(TargetError::NonFatal)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(TargetError::Fatal(GdbTargetError::UnexpectedMessage))
}
}
}
fn remove_sw_breakpoint(
&mut self,
addr: <Self::Arch as Arch>::Usize,
_kind: <Self::Arch as Arch>::BreakpointKind,
) -> TargetResult<bool, Self> {
tracing::debug!("Remove sw breakpoint at address {:X}", addr);
match self.send_command(DebugMsg::RemoveSwBreakpoint(addr))? {
DebugResponse::RemoveSwBreakpoint(rsp) => Ok(rsp),
DebugResponse::NotAllowed => {
tracing::error!("Action not allowed at this time, crash might have occurred");
Err(TargetError::NonFatal)
}
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(TargetError::NonFatal)
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(TargetError::Fatal(GdbTargetError::UnexpectedMessage))
}
}
}
}
impl SingleThreadResume for HyperlightSandboxTarget {
fn resume(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
tracing::debug!("Resume");
self.resume_vcpu()
}
fn support_single_step(&mut self) -> Option<SingleThreadSingleStepOps<'_, Self>> {
Some(self)
}
}
impl SingleThreadSingleStep for HyperlightSandboxTarget {
fn step(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
tracing::debug!("Step");
match self.send_command(DebugMsg::Step)? {
DebugResponse::Step => Ok(()),
DebugResponse::ErrorOccurred => {
tracing::error!("Error occurred");
Err(GdbTargetError::UnexpectedError)
}
DebugResponse::NotAllowed => {
tracing::error!("Action not allowed at this time, crash might have occurred");
Ok(())
}
msg => {
tracing::error!("Unexpected message received: {:?}", msg);
Err(GdbTargetError::UnexpectedMessage)
}
}
}
}
#[cfg(test)]
mod tests {
use gdbstub_arch::x86::reg::X86_64CoreRegs;
use super::*;
#[test]
fn test_gdb_target() {
let (gdb_conn, hyp_conn) = DebugCommChannel::unbounded();
let mut target = HyperlightSandboxTarget::new(hyp_conn);
let msg = DebugResponse::ReadRegisters(Box::default());
let res = gdb_conn.send(msg);
assert!(res.is_ok());
let mut regs = X86_64CoreRegs::default();
assert!(
target.read_registers(&mut regs).is_ok(),
"Failed to read registers"
);
let msg = DebugResponse::WriteRegisters;
let res = gdb_conn.send(msg);
assert!(res.is_ok());
assert!(
target.write_registers(®s).is_ok(),
"Failed to write registers"
);
drop(gdb_conn);
assert!(
target.read_registers(&mut regs).is_err(),
"Succeeded to read registers when
expected to fail"
);
}
}