Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 246 additions & 2 deletions src/hyperlight_host/src/hypervisor/gdb/mshv_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,255 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

#[cfg(mshv2)]
extern crate mshv_bindings2 as mshv_bindings;
#[cfg(mshv2)]
extern crate mshv_ioctls2 as mshv_ioctls;

#[cfg(mshv3)]
extern crate mshv_bindings3 as mshv_bindings;
#[cfg(mshv3)]
extern crate mshv_ioctls3 as mshv_ioctls;

use mshv_bindings::{
DebugRegisters, StandardRegisters, HV_TRANSLATE_GVA_VALIDATE_READ,
HV_TRANSLATE_GVA_VALIDATE_WRITE,
};
use mshv_ioctls::VcpuFd;

use super::{
GuestDebug, VcpuStopReason, X86_64Regs, DR6_BS_FLAG_MASK, DR6_HW_BP_FLAGS_MASK, MAX_NO_OF_HW_BP,
};
use crate::{new_error, HyperlightError, Result};

#[derive(Debug, Default)]
pub(crate) struct MshvDebug {}
pub(crate) struct MshvDebug {
/// vCPU stepping state
single_step: bool,

/// Array of addresses for HW breakpoints
hw_breakpoints: Vec<u64>,
/// Debug registers
dbg_cfg: DebugRegisters,
}

impl MshvDebug {
pub(crate) fn new() -> Self {
Self {}
Self {
single_step: false,
hw_breakpoints: vec![],
dbg_cfg: DebugRegisters::default(),
}
}

/// Returns the instruction pointer from the stopped vCPU
fn get_instruction_pointer(&self, vcpu_fd: &VcpuFd) -> Result<u64> {
let regs = vcpu_fd
.get_regs()
.map_err(|e| new_error!("Could not retrieve registers from vCPU: {:?}", e))?;

Ok(regs.rip)
}

/// This method sets the vCPU debug register fields to enable breakpoints at
/// specific addresses
///
/// The first 4 debug registers are used to set the addresses
/// The 4th and 5th debug registers are obsolete and not used
/// The 7th debug register is used to enable the breakpoints
/// For more information see: DEBUG REGISTERS chapter in the architecture
/// manual
fn set_debug_config(&mut self, vcpu_fd: &VcpuFd, step: bool) -> Result<()> {
let addrs = &self.hw_breakpoints;

let mut dbg_cfg = DebugRegisters::default();
for (k, addr) in addrs.iter().enumerate() {
match k {
0 => {
dbg_cfg.dr0 = *addr;
}
1 => {
dbg_cfg.dr1 = *addr;
}
2 => {
dbg_cfg.dr2 = *addr;
}
3 => {
dbg_cfg.dr3 = *addr;
}
_ => {
Err(new_error!("Tried to set more than 4 HW breakpoints"))?;
}
}
dbg_cfg.dr7 |= 1 << (k * 2);
}

self.dbg_cfg = dbg_cfg;
vcpu_fd
.set_debug_regs(&self.dbg_cfg)
.map_err(|e| new_error!("Could not set guest debug: {:?}", e))?;

self.single_step = step;

let mut regs = vcpu_fd
.get_regs()
.map_err(|e| new_error!("Could not get registers: {:?}", e))?;

// Set TF Flag to enable Traps
if self.single_step {
regs.rflags |= 1 << 8;
} else {
regs.rflags &= !(1 << 8);
}

vcpu_fd
.set_regs(&regs)
.map_err(|e| new_error!("Could not set registers: {:?}", e))?;

Ok(())
}

/// Returns the vCPU stop reason
pub(crate) fn get_stop_reason(
&mut self,
vcpu_fd: &VcpuFd,
entrypoint: u64,
) -> Result<VcpuStopReason> {
// MSHV does not provide info on the vCPU exits but the debug
// information can be retrieved from the DEBUG REGISTERS
let regs = vcpu_fd
.get_debug_regs()
.map_err(|e| new_error!("Cannot retrieve debug registers from vCPU: {}", e))?;

// DR6 register contains debug state related information
let debug_status = regs.dr6;

// If the BS flag in DR6 register is set, it means a single step
// instruction triggered the exit
// Check page 19-4 Vol. 3B of Intel 64 and IA-32
// Architectures Software Developer's Manual
if debug_status & DR6_BS_FLAG_MASK != 0 && self.single_step {
return Ok(VcpuStopReason::DoneStep);
}

let ip = self.get_instruction_pointer(vcpu_fd)?;
let gpa = self.translate_gva(vcpu_fd, ip)?;

// If any of the B0-B3 flags in DR6 register is set, it means a
// hardware breakpoint triggered the exit
// Check page 19-4 Vol. 3B of Intel 64 and IA-32
// Architectures Software Developer's Manual
if debug_status & DR6_HW_BP_FLAGS_MASK != 0 && self.hw_breakpoints.contains(&gpa) {
// In case the hw breakpoint is the entry point, remove it to
// avoid hanging here as gdb does not remove breakpoints it
// has not set.
// Gdb expects the target to be stopped when connected.
if gpa == entrypoint {
self.remove_hw_breakpoint(vcpu_fd, entrypoint)?;
}
return Ok(VcpuStopReason::HwBp);
}

Ok(VcpuStopReason::Unknown)
}
}

impl GuestDebug for MshvDebug {
type Vcpu = VcpuFd;

fn is_hw_breakpoint(&self, addr: &u64) -> bool {
self.hw_breakpoints.contains(addr)
}
fn is_sw_breakpoint(&self, _addr: &u64) -> bool {
unimplemented!()
}
fn save_hw_breakpoint(&mut self, addr: &u64) -> bool {
if self.hw_breakpoints.len() >= MAX_NO_OF_HW_BP {
false
} else {
self.hw_breakpoints.push(*addr);

true
}
}
fn save_sw_breakpoint_data(&mut self, _addr: u64, _data: [u8; 1]) {
unimplemented!()
}
fn delete_hw_breakpoint(&mut self, addr: &u64) {
self.hw_breakpoints.retain(|&a| a != *addr);
}
fn delete_sw_breakpoint_data(&mut self, _addr: &u64) -> Option<[u8; 1]> {
unimplemented!()
}

fn read_regs(&self, vcpu_fd: &Self::Vcpu, regs: &mut X86_64Regs) -> Result<()> {
log::debug!("Read registers");
let vcpu_regs = vcpu_fd
.get_regs()
.map_err(|e| new_error!("Could not read guest registers: {:?}", e))?;

regs.rax = vcpu_regs.rax;
regs.rbx = vcpu_regs.rbx;
regs.rcx = vcpu_regs.rcx;
regs.rdx = vcpu_regs.rdx;
regs.rsi = vcpu_regs.rsi;
regs.rdi = vcpu_regs.rdi;
regs.rbp = vcpu_regs.rbp;
regs.rsp = vcpu_regs.rsp;
regs.r8 = vcpu_regs.r8;
regs.r9 = vcpu_regs.r9;
regs.r10 = vcpu_regs.r10;
regs.r11 = vcpu_regs.r11;
regs.r12 = vcpu_regs.r12;
regs.r13 = vcpu_regs.r13;
regs.r14 = vcpu_regs.r14;
regs.r15 = vcpu_regs.r15;

regs.rip = vcpu_regs.rip;
regs.rflags = vcpu_regs.rflags;

Ok(())
}

fn set_single_step(&mut self, vcpu_fd: &Self::Vcpu, enable: bool) -> Result<()> {
self.set_debug_config(vcpu_fd, enable)
}

fn translate_gva(&self, vcpu_fd: &Self::Vcpu, gva: u64) -> Result<u64> {
let flags = (HV_TRANSLATE_GVA_VALIDATE_READ | HV_TRANSLATE_GVA_VALIDATE_WRITE) as u64;
let (addr, _) = vcpu_fd
.translate_gva(gva, flags)
.map_err(|_| HyperlightError::TranslateGuestAddress(gva))?;

Ok(addr)
}

fn write_regs(&self, vcpu_fd: &Self::Vcpu, regs: &X86_64Regs) -> Result<()> {
log::debug!("Write registers");
let new_regs = StandardRegisters {
rax: regs.rax,
rbx: regs.rbx,
rcx: regs.rcx,
rdx: regs.rdx,
rsi: regs.rsi,
rdi: regs.rdi,
rbp: regs.rbp,
rsp: regs.rsp,
r8: regs.r8,
r9: regs.r9,
r10: regs.r10,
r11: regs.r11,
r12: regs.r12,
r13: regs.r13,
r14: regs.r14,
r15: regs.r15,

rip: regs.rip,
rflags: regs.rflags,
};

vcpu_fd
.set_regs(&new_regs)
.map_err(|e| new_error!("Could not write guest registers: {:?}", e))
}
}
88 changes: 74 additions & 14 deletions src/hyperlight_host/src/hypervisor/hyperv_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ use tracing::{instrument, Span};

use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
#[cfg(gdb)]
use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, MshvDebug};
use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, MshvDebug};
#[cfg(gdb)]
use super::handlers::DbgMemAccessHandlerWrapper;
use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper};
Expand All @@ -71,26 +71,87 @@ use crate::{log_then_return, new_error, Result};
mod debug {
use std::sync::{Arc, Mutex};

use super::HypervLinuxDriver;
use crate::hypervisor::gdb::{DebugMsg, DebugResponse, VcpuStopReason};
use super::{HypervLinuxDriver, *};
use crate::hypervisor::gdb::{DebugMsg, DebugResponse, VcpuStopReason, X86_64Regs};
use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
use crate::{new_error, Result};

impl HypervLinuxDriver {
/// Resets the debug information to disable debugging
fn disable_debug(&mut self) -> Result<()> {
let mut debug = MshvDebug::default();

debug.set_single_step(&self.vcpu_fd, false)?;

self.debug = Some(debug);

Ok(())
}

/// Get the reason the vCPU has stopped
pub fn get_stop_reason(&mut self) -> Result<VcpuStopReason> {
unimplemented!()
pub(crate) fn get_stop_reason(&mut self) -> Result<VcpuStopReason> {
let debug = self
.debug
.as_mut()
.ok_or_else(|| new_error!("Debug is not enabled"))?;

debug.get_stop_reason(&self.vcpu_fd, self.entrypoint)
}

pub fn process_dbg_request(
pub(crate) fn process_dbg_request(
&mut self,
_req: DebugMsg,
_dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
req: DebugMsg,
dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
) -> Result<DebugResponse> {
unimplemented!()
if let Some(debug) = self.debug.as_mut() {
match req {
DebugMsg::AddHwBreakpoint(addr) => Ok(DebugResponse::AddHwBreakpoint(
debug.add_hw_breakpoint(&self.vcpu_fd, addr).is_ok(),
)),
DebugMsg::Continue => {
debug.set_single_step(&self.vcpu_fd, false)?;
Ok(DebugResponse::Continue)
}
DebugMsg::DisableDebug => {
self.disable_debug()?;

Ok(DebugResponse::DisableDebug)
}
DebugMsg::GetCodeSectionOffset => {
let offset = dbg_mem_access_fn
.try_lock()
.map_err(|e| {
new_error!("Error locking at {}:{}: {}", file!(), line!(), e)
})?
.get_code_offset()?;

Ok(DebugResponse::GetCodeSectionOffset(offset as u64))
}
DebugMsg::ReadRegisters => {
let mut regs = X86_64Regs::default();

debug
.read_regs(&self.vcpu_fd, &mut regs)
.map(|_| DebugResponse::ReadRegisters(regs))
}
DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint(
debug.remove_hw_breakpoint(&self.vcpu_fd, addr).is_ok(),
)),
DebugMsg::Step => {
debug.set_single_step(&self.vcpu_fd, true)?;
Ok(DebugResponse::Step)
}
DebugMsg::WriteRegisters(regs) => debug
.write_regs(&self.vcpu_fd, &regs)
.map(|_| DebugResponse::WriteRegisters),
_ => Err(new_error!("Not yet implemented")),
}
} else {
Err(new_error!("Debugging is not enabled"))
}
}

pub fn recv_dbg_msg(&mut self) -> Result<DebugMsg> {
pub(crate) fn recv_dbg_msg(&mut self) -> Result<DebugMsg> {
let gdb_conn = self
.gdb_conn
.as_mut()
Expand All @@ -105,7 +166,7 @@ mod debug {
})
}

pub fn send_dbg_msg(&mut self, cmd: DebugResponse) -> Result<()> {
pub(crate) fn send_dbg_msg(&mut self, cmd: DebugResponse) -> Result<()> {
log::debug!("Sending {:?}", cmd);

let gdb_conn = self
Expand Down Expand Up @@ -148,10 +209,8 @@ pub(super) struct HypervLinuxDriver {
mem_regions: Vec<MemoryRegion>,
orig_rsp: GuestPtr,

#[allow(dead_code)]
#[cfg(gdb)]
debug: Option<MshvDebug>,
#[allow(dead_code)]
#[cfg(gdb)]
gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
}
Expand Down Expand Up @@ -197,7 +256,8 @@ impl HypervLinuxDriver {

#[cfg(gdb)]
let (debug, gdb_conn) = if let Some(gdb_conn) = gdb_conn {
let debug = MshvDebug::new();
let mut debug = MshvDebug::new();
debug.add_hw_breakpoint(&vcpu_fd, entrypoint_ptr.absolute()?)?;

// The bellow intercepts make the vCPU exit with the Exception Intercept exit code
// Check Table 6-1. Exceptions and Interrupts at Page 6-13 Vol. 1
Expand Down