use std::collections::HashMap;
use kvm_bindings::{
kvm_debug_exit_arch, kvm_guest_debug, kvm_regs, KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP,
KVM_GUESTDBG_USE_HW_BP, KVM_GUESTDBG_USE_SW_BP,
};
use kvm_ioctls::VcpuFd;
use super::{
GuestDebug, VcpuStopReason, X86_64Regs, DR6_BS_FLAG_MASK, DR6_HW_BP_FLAGS_MASK,
MAX_NO_OF_HW_BP, SW_BP_SIZE,
};
use crate::{new_error, HyperlightError, Result};
const SW_BP_ID: u32 = 3;
#[derive(Default)]
pub(crate) struct KvmDebug {
single_step: bool,
hw_breakpoints: Vec<u64>,
sw_breakpoints: HashMap<u64, [u8; SW_BP_SIZE]>,
dbg_cfg: kvm_guest_debug,
}
impl KvmDebug {
pub(crate) fn new() -> Self {
let dbg = kvm_guest_debug {
control: KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP,
..Default::default()
};
Self {
single_step: false,
hw_breakpoints: vec![],
sw_breakpoints: HashMap::new(),
dbg_cfg: dbg,
}
}
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)
}
fn set_debug_config(&mut self, vcpu_fd: &VcpuFd, step: bool) -> Result<()> {
let addrs = &self.hw_breakpoints;
self.dbg_cfg.arch.debugreg = [0; 8];
for (k, addr) in addrs.iter().enumerate() {
self.dbg_cfg.arch.debugreg[k] = *addr;
self.dbg_cfg.arch.debugreg[7] |= 1 << (k * 2);
}
if !addrs.is_empty() {
self.dbg_cfg.control |= KVM_GUESTDBG_USE_HW_BP;
} else {
self.dbg_cfg.control &= !KVM_GUESTDBG_USE_HW_BP;
}
if step {
self.dbg_cfg.control |= KVM_GUESTDBG_SINGLESTEP;
} else {
self.dbg_cfg.control &= !KVM_GUESTDBG_SINGLESTEP;
}
log::debug!("Setting bp: {:?} cfg: {:?}", addrs, self.dbg_cfg);
vcpu_fd
.set_guest_debug(&self.dbg_cfg)
.map_err(|e| new_error!("Could not set guest debug: {:?}", e))?;
self.single_step = step;
Ok(())
}
pub(crate) fn get_stop_reason(
&mut self,
vcpu_fd: &VcpuFd,
debug_exit: kvm_debug_exit_arch,
entrypoint: u64,
) -> Result<VcpuStopReason> {
if debug_exit.dr6 & 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 DR6_HW_BP_FLAGS_MASK & debug_exit.dr6 != 0 && self.hw_breakpoints.contains(&gpa) {
if gpa == entrypoint {
self.remove_hw_breakpoint(vcpu_fd, entrypoint)?;
}
return Ok(VcpuStopReason::HwBp);
}
if SW_BP_ID == debug_exit.exception && self.sw_breakpoints.contains_key(&gpa) {
return Ok(VcpuStopReason::SwBp);
}
log::error!(
r"The vCPU exited because of an unknown reason:
kvm_debug_exit_arch: {:?}
single_step: {:?}
hw_breakpoints: {:?}
sw_breakpoints: {:?}",
debug_exit,
self.single_step,
self.hw_breakpoints,
self.sw_breakpoints,
);
Ok(VcpuStopReason::Unknown)
}
}
impl GuestDebug for KvmDebug {
type Vcpu = VcpuFd;
fn is_hw_breakpoint(&self, addr: &u64) -> bool {
self.hw_breakpoints.contains(addr)
}
fn is_sw_breakpoint(&self, addr: &u64) -> bool {
self.sw_breakpoints.contains_key(addr)
}
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]) {
_ = self.sw_breakpoints.insert(addr, data);
}
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]> {
self.sw_breakpoints.remove(addr)
}
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 tr = vcpu_fd
.translate_gva(gva)
.map_err(|_| HyperlightError::TranslateGuestAddress(gva))?;
if tr.valid == 0 {
Err(HyperlightError::TranslateGuestAddress(gva))
} else {
Ok(tr.physical_address)
}
}
fn write_regs(&self, vcpu_fd: &Self::Vcpu, regs: &X86_64Regs) -> Result<()> {
log::debug!("Write registers");
let new_regs = kvm_regs {
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))
}
}