|  | 
|  | 1 | +/* | 
|  | 2 | +Copyright 2024 The Hyperlight Authors. | 
|  | 3 | +
 | 
|  | 4 | +Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 5 | +you may not use this file except in compliance with the License. | 
|  | 6 | +You may obtain a copy of the License at | 
|  | 7 | +
 | 
|  | 8 | +    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 9 | +
 | 
|  | 10 | +Unless required by applicable law or agreed to in writing, software | 
|  | 11 | +distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 13 | +See the License for the specific language governing permissions and | 
|  | 14 | +limitations under the License. | 
|  | 15 | +*/ | 
|  | 16 | + | 
|  | 17 | +use std::collections::HashMap; | 
|  | 18 | + | 
|  | 19 | +use windows::Win32::System::Hypervisor::WHV_VP_EXCEPTION_CONTEXT; | 
|  | 20 | + | 
|  | 21 | +use super::arch::{MAX_NO_OF_HW_BP, vcpu_stop_reason}; | 
|  | 22 | +use super::{GuestDebug, SW_BP_SIZE, VcpuStopReason, X86_64Regs}; | 
|  | 23 | +use crate::hypervisor::windows_hypervisor_platform::VMProcessor; | 
|  | 24 | +use crate::hypervisor::wrappers::{WHvDebugRegisters, WHvGeneralRegisters}; | 
|  | 25 | +use crate::{HyperlightError, Result, new_error}; | 
|  | 26 | + | 
|  | 27 | +/// KVM Debug struct | 
|  | 28 | +/// This struct is used to abstract the internal details of the kvm | 
|  | 29 | +/// guest debugging settings | 
|  | 30 | +#[derive(Default)] | 
|  | 31 | +pub(crate) struct HypervDebug { | 
|  | 32 | +    /// vCPU stepping state | 
|  | 33 | +    single_step: bool, | 
|  | 34 | + | 
|  | 35 | +    /// Array of addresses for HW breakpoints | 
|  | 36 | +    hw_breakpoints: Vec<u64>, | 
|  | 37 | +    /// Saves the bytes modified to enable SW breakpoints | 
|  | 38 | +    sw_breakpoints: HashMap<u64, [u8; SW_BP_SIZE]>, | 
|  | 39 | + | 
|  | 40 | +    /// Debug registers | 
|  | 41 | +    dbg_cfg: WHvDebugRegisters, | 
|  | 42 | +} | 
|  | 43 | + | 
|  | 44 | +impl HypervDebug { | 
|  | 45 | +    pub(crate) fn new() -> Self { | 
|  | 46 | +        Self { | 
|  | 47 | +            single_step: false, | 
|  | 48 | +            hw_breakpoints: vec![], | 
|  | 49 | +            sw_breakpoints: HashMap::new(), | 
|  | 50 | +            dbg_cfg: WHvDebugRegisters::default(), | 
|  | 51 | +        } | 
|  | 52 | +    } | 
|  | 53 | + | 
|  | 54 | +    /// Returns the instruction pointer from the stopped vCPU | 
|  | 55 | +    fn get_instruction_pointer(&self, vcpu_fd: &VMProcessor) -> Result<u64> { | 
|  | 56 | +        let regs = vcpu_fd | 
|  | 57 | +            .get_regs() | 
|  | 58 | +            .map_err(|e| new_error!("Could not retrieve registers from vCPU: {:?}", e))?; | 
|  | 59 | + | 
|  | 60 | +        Ok(regs.rip) | 
|  | 61 | +    } | 
|  | 62 | + | 
|  | 63 | +    /// This method sets the kvm debugreg fields to enable breakpoints at | 
|  | 64 | +    /// specific addresses | 
|  | 65 | +    /// | 
|  | 66 | +    /// The first 4 debug registers are used to set the addresses | 
|  | 67 | +    /// The 4th and 5th debug registers are obsolete and not used | 
|  | 68 | +    /// The 7th debug register is used to enable the breakpoints | 
|  | 69 | +    /// For more information see: DEBUG REGISTERS chapter in the architecture | 
|  | 70 | +    /// manual | 
|  | 71 | +    fn set_debug_config(&mut self, vcpu_fd: &VMProcessor, step: bool) -> Result<()> { | 
|  | 72 | +        let addrs = &self.hw_breakpoints; | 
|  | 73 | + | 
|  | 74 | +        let mut dbg_cfg = WHvDebugRegisters::default(); | 
|  | 75 | + | 
|  | 76 | +        for (k, addr) in addrs.iter().enumerate() { | 
|  | 77 | +            match k { | 
|  | 78 | +                0 => { | 
|  | 79 | +                    dbg_cfg.dr0 = *addr; | 
|  | 80 | +                } | 
|  | 81 | +                1 => { | 
|  | 82 | +                    dbg_cfg.dr1 = *addr; | 
|  | 83 | +                } | 
|  | 84 | +                2 => { | 
|  | 85 | +                    dbg_cfg.dr2 = *addr; | 
|  | 86 | +                } | 
|  | 87 | +                3 => { | 
|  | 88 | +                    dbg_cfg.dr3 = *addr; | 
|  | 89 | +                } | 
|  | 90 | +                _ => { | 
|  | 91 | +                    Err(new_error!("Tried to set more than 4 HW breakpoints"))?; | 
|  | 92 | +                } | 
|  | 93 | +            } | 
|  | 94 | +            dbg_cfg.dr7 |= 1 << (k * 2); | 
|  | 95 | +        } | 
|  | 96 | + | 
|  | 97 | +        self.dbg_cfg = dbg_cfg; | 
|  | 98 | + | 
|  | 99 | +        vcpu_fd | 
|  | 100 | +            .set_debug_regs(&self.dbg_cfg) | 
|  | 101 | +            .map_err(|e| new_error!("Could not set guest debug: {:?}", e))?; | 
|  | 102 | + | 
|  | 103 | +        self.single_step = step; | 
|  | 104 | + | 
|  | 105 | +        let mut regs = vcpu_fd | 
|  | 106 | +            .get_regs() | 
|  | 107 | +            .map_err(|e| new_error!("Could not get registers: {:?}", e))?; | 
|  | 108 | + | 
|  | 109 | +        // Set TF Flag to enable Traps | 
|  | 110 | +        if self.single_step { | 
|  | 111 | +            regs.rflags |= 1 << 8; // Set the TF flag | 
|  | 112 | +        } else { | 
|  | 113 | +            regs.rflags &= !(1 << 8); // Clear the TF flag | 
|  | 114 | +        } | 
|  | 115 | + | 
|  | 116 | +        vcpu_fd | 
|  | 117 | +            .set_general_purpose_registers(®s) | 
|  | 118 | +            .map_err(|e| new_error!("Could not set guest registers: {:?}", e))?; | 
|  | 119 | + | 
|  | 120 | +        Ok(()) | 
|  | 121 | +    } | 
|  | 122 | + | 
|  | 123 | +    /// Get the reason the vCPU has stopped | 
|  | 124 | +    pub(crate) fn get_stop_reason( | 
|  | 125 | +        &mut self, | 
|  | 126 | +        vcpu_fd: &VMProcessor, | 
|  | 127 | +        exception: WHV_VP_EXCEPTION_CONTEXT, | 
|  | 128 | +        entrypoint: u64, | 
|  | 129 | +    ) -> Result<VcpuStopReason> { | 
|  | 130 | +        let rip = self.get_instruction_pointer(vcpu_fd)?; | 
|  | 131 | +        let rip = self.translate_gva(vcpu_fd, rip)?; | 
|  | 132 | + | 
|  | 133 | +        let debug_regs = vcpu_fd | 
|  | 134 | +            .get_debug_regs() | 
|  | 135 | +            .map_err(|e| new_error!("Could not retrieve registers from vCPU: {:?}", e))?; | 
|  | 136 | + | 
|  | 137 | +        // Check if the vCPU stopped because of a hardware breakpoint | 
|  | 138 | +        let reason = vcpu_stop_reason( | 
|  | 139 | +            self.single_step, | 
|  | 140 | +            rip, | 
|  | 141 | +            debug_regs.dr6, | 
|  | 142 | +            entrypoint, | 
|  | 143 | +            exception.ExceptionType as u32, | 
|  | 144 | +            &self.hw_breakpoints, | 
|  | 145 | +            &self.sw_breakpoints, | 
|  | 146 | +        ); | 
|  | 147 | + | 
|  | 148 | +        if let VcpuStopReason::EntryPointBp = reason { | 
|  | 149 | +            // In case the hw breakpoint is the entry point, remove it to | 
|  | 150 | +            // avoid hanging here as gdb does not remove breakpoints it | 
|  | 151 | +            // has not set. | 
|  | 152 | +            // Gdb expects the target to be stopped when connected. | 
|  | 153 | +            self.remove_hw_breakpoint(vcpu_fd, entrypoint)?; | 
|  | 154 | +        } | 
|  | 155 | + | 
|  | 156 | +        Ok(reason) | 
|  | 157 | +    } | 
|  | 158 | +} | 
|  | 159 | + | 
|  | 160 | +impl GuestDebug for HypervDebug { | 
|  | 161 | +    type Vcpu = VMProcessor; | 
|  | 162 | + | 
|  | 163 | +    fn is_hw_breakpoint(&self, addr: &u64) -> bool { | 
|  | 164 | +        self.hw_breakpoints.contains(addr) | 
|  | 165 | +    } | 
|  | 166 | +    fn is_sw_breakpoint(&self, addr: &u64) -> bool { | 
|  | 167 | +        self.sw_breakpoints.contains_key(addr) | 
|  | 168 | +    } | 
|  | 169 | +    fn save_hw_breakpoint(&mut self, addr: &u64) -> bool { | 
|  | 170 | +        if self.hw_breakpoints.len() >= MAX_NO_OF_HW_BP { | 
|  | 171 | +            false | 
|  | 172 | +        } else { | 
|  | 173 | +            self.hw_breakpoints.push(*addr); | 
|  | 174 | + | 
|  | 175 | +            true | 
|  | 176 | +        } | 
|  | 177 | +    } | 
|  | 178 | +    fn save_sw_breakpoint_data(&mut self, addr: u64, data: [u8; 1]) { | 
|  | 179 | +        _ = self.sw_breakpoints.insert(addr, data); | 
|  | 180 | +    } | 
|  | 181 | +    fn delete_hw_breakpoint(&mut self, addr: &u64) { | 
|  | 182 | +        self.hw_breakpoints.retain(|&a| a != *addr); | 
|  | 183 | +    } | 
|  | 184 | +    fn delete_sw_breakpoint_data(&mut self, addr: &u64) -> Option<[u8; 1]> { | 
|  | 185 | +        self.sw_breakpoints.remove(addr) | 
|  | 186 | +    } | 
|  | 187 | + | 
|  | 188 | +    fn read_regs(&self, vcpu_fd: &Self::Vcpu, regs: &mut X86_64Regs) -> Result<()> { | 
|  | 189 | +        log::debug!("Read registers"); | 
|  | 190 | +        let vcpu_regs = vcpu_fd | 
|  | 191 | +            .get_regs() | 
|  | 192 | +            .map_err(|e| new_error!("Could not read guest registers: {:?}", e))?; | 
|  | 193 | + | 
|  | 194 | +        regs.rax = vcpu_regs.rax; | 
|  | 195 | +        regs.rbx = vcpu_regs.rbx; | 
|  | 196 | +        regs.rcx = vcpu_regs.rcx; | 
|  | 197 | +        regs.rdx = vcpu_regs.rdx; | 
|  | 198 | +        regs.rsi = vcpu_regs.rsi; | 
|  | 199 | +        regs.rdi = vcpu_regs.rdi; | 
|  | 200 | +        regs.rbp = vcpu_regs.rbp; | 
|  | 201 | +        regs.rsp = vcpu_regs.rsp; | 
|  | 202 | +        regs.r8 = vcpu_regs.r8; | 
|  | 203 | +        regs.r9 = vcpu_regs.r9; | 
|  | 204 | +        regs.r10 = vcpu_regs.r10; | 
|  | 205 | +        regs.r11 = vcpu_regs.r11; | 
|  | 206 | +        regs.r12 = vcpu_regs.r12; | 
|  | 207 | +        regs.r13 = vcpu_regs.r13; | 
|  | 208 | +        regs.r14 = vcpu_regs.r14; | 
|  | 209 | +        regs.r15 = vcpu_regs.r15; | 
|  | 210 | + | 
|  | 211 | +        regs.rip = vcpu_regs.rip; | 
|  | 212 | +        regs.rflags = vcpu_regs.rflags; | 
|  | 213 | + | 
|  | 214 | +        Ok(()) | 
|  | 215 | +    } | 
|  | 216 | + | 
|  | 217 | +    fn set_single_step(&mut self, vcpu_fd: &Self::Vcpu, enable: bool) -> Result<()> { | 
|  | 218 | +        self.set_debug_config(vcpu_fd, enable) | 
|  | 219 | +    } | 
|  | 220 | + | 
|  | 221 | +    fn translate_gva(&self, vcpu_fd: &Self::Vcpu, gva: u64) -> Result<u64> { | 
|  | 222 | +        vcpu_fd | 
|  | 223 | +            .translate_gva(gva) | 
|  | 224 | +            .map_err(|_| HyperlightError::TranslateGuestAddress(gva)) | 
|  | 225 | +    } | 
|  | 226 | + | 
|  | 227 | +    fn write_regs(&self, vcpu_fd: &Self::Vcpu, regs: &X86_64Regs) -> Result<()> { | 
|  | 228 | +        log::debug!("Write registers"); | 
|  | 229 | +        let regs = WHvGeneralRegisters { | 
|  | 230 | +            rax: regs.rax, | 
|  | 231 | +            rbx: regs.rbx, | 
|  | 232 | +            rcx: regs.rcx, | 
|  | 233 | +            rdx: regs.rdx, | 
|  | 234 | +            rsi: regs.rsi, | 
|  | 235 | +            rdi: regs.rdi, | 
|  | 236 | +            rbp: regs.rbp, | 
|  | 237 | +            rsp: regs.rsp, | 
|  | 238 | +            r8: regs.r8, | 
|  | 239 | +            r9: regs.r9, | 
|  | 240 | +            r10: regs.r10, | 
|  | 241 | +            r11: regs.r11, | 
|  | 242 | +            r12: regs.r12, | 
|  | 243 | +            r13: regs.r13, | 
|  | 244 | +            r14: regs.r14, | 
|  | 245 | +            r15: regs.r15, | 
|  | 246 | + | 
|  | 247 | +            rip: regs.rip, | 
|  | 248 | +            rflags: regs.rflags, | 
|  | 249 | +        }; | 
|  | 250 | + | 
|  | 251 | +        vcpu_fd | 
|  | 252 | +            .set_general_purpose_registers(®s) | 
|  | 253 | +            .map_err(|e| new_error!("Could not write guest registers: {:?}", e)) | 
|  | 254 | +    } | 
|  | 255 | +} | 
0 commit comments