// SPDX-License-Identifier: MIT OR Apache-2.0 use super::cpu::GdbError; use super::{MainCpuError, RamMap, Vmm}; use gdbstub::target::ext::base::BaseOps; use gdbstub::target::ext::breakpoints::{ Breakpoints, BreakpointsOps, SwBreakpoint, SwBreakpointOps, }; use gdbstub::target::{TargetError, TargetResult}; use gdbstub_arch::x86::X86_64_SSE; use hv::{Cpu, CpuCommit, CpuStates, Hypervisor}; use std::num::NonZero; use x86_64::Efer; pub type GdbRegs = gdbstub_arch::x86::reg::X86_64CoreRegs; pub const BREAKPOINT_SIZE: NonZero = NonZero::new(1).unwrap(); pub const RELOCATE_TYPE: usize = 8; pub fn setup_main_cpu( _: &H, cpu: &mut H::Cpu<'_>, entry: usize, map: RamMap, ) -> Result<(), MainCpuError> { // Set CR3 to page-map level-4 table. let mut states = cpu .states() .map_err(|e| MainCpuError::GetCpuStatesFailed(Box::new(e)))?; assert_eq!(map.page_table & 0xFFF0000000000FFF, 0); states.set_cr3(map.page_table); // Set CR4. let mut cr4 = 0; cr4 |= 0x20; // Physical-address extensions (PAE). states.set_cr4(cr4); // Set EFER to enable long mode with 64-bit. states.set_efer(Efer::new().with_lme(true).with_lma(true)); // Set CR0. let mut cr0 = 0; cr0 |= 0x00000001; // Protected Mode Enable (PE). cr0 |= 0x80000000; // Paging (PG). states.set_cr0(cr0); // Set CS to 64-bit mode with ring 0. Although x86-64 specs from AMD ignore the Code/Data flag // on 64-bit mode but Intel CPU violate this spec so we need to enable it. states.set_cs(0b1000, 0, true, true, false); // Set data segments. The only fields used on 64-bit mode is P. states.set_ds(true); states.set_es(true); states.set_fs(true); states.set_gs(true); states.set_ss(true); // Set entry point, its argument and stack pointer. states.set_rdi(map.env_vaddr); states.set_rsi(map.conf_vaddr); states.set_rsp(map.stack_vaddr.checked_add(map.stack_len.get()).unwrap()); // Top-down. states.set_rip(entry); states .commit() .map_err(|e| MainCpuError::CommitCpuStatesFailed(Box::new(e))) } impl gdbstub::target::Target for Vmm { type Arch = X86_64_SSE; type Error = GdbError; fn base_ops(&mut self) -> BaseOps<'_, Self::Arch, Self::Error> { BaseOps::MultiThread(self) } fn support_breakpoints(&mut self) -> Option> { Some(self) } } impl Breakpoints for Vmm { fn support_sw_breakpoint(&mut self) -> Option> { Some(self) } } impl SwBreakpoint for Vmm { fn add_sw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult { let std::collections::hash_map::Entry::Vacant(entry) = self.sw_breakpoints.entry(addr) else { return Ok(false); }; let cpu = self.cpus.get_mut(&0).unwrap(); let translated_addr = cpu .debug .as_mut() .unwrap() .translate_address(addr.try_into().unwrap()) .ok_or(TargetError::Fatal(GdbError::MainCpuExited))?; // Get data. let mut src = self .hv .ram() .lock(translated_addr, BREAKPOINT_SIZE) .ok_or(TargetError::Errno(Self::GDB_EFAULT))?; let code_slice = src.as_mut_ptr(); let code_bytes = std::mem::replace(unsafe { &mut *code_slice }, 0xcc); entry.insert([code_bytes]); Ok(true) } fn remove_sw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult { let Some(code_bytes) = self.sw_breakpoints.remove(&addr) else { return Ok(false); }; let cpu = self.cpus.get_mut(&0).unwrap(); let translated_addr = cpu .debug .as_mut() .unwrap() .translate_address(addr.try_into().unwrap()) .ok_or(TargetError::Fatal(GdbError::MainCpuExited))?; // Get data. let mut src = self .hv .ram() .lock(translated_addr, BREAKPOINT_SIZE) .ok_or(TargetError::Errno(Self::GDB_EFAULT))?; let code_slice = unsafe { std::slice::from_raw_parts_mut(src.as_mut_ptr(), BREAKPOINT_SIZE.get()) }; code_slice.copy_from_slice(&code_bytes); Ok(true) } }