Initializes CPU interruption from debugger (#1035)
Some checks are pending
Development Build / Build (push) Waiting to run
Development Build / Update PRs (push) Waiting to run

This commit is contained in:
Putta Khunchalee 2024-10-13 23:56:26 +07:00 committed by GitHub
parent e101b8b7eb
commit fa844896cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 234 additions and 72 deletions

View File

@ -1,19 +1,57 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::mem::ManuallyDrop;
use std::sync::{Arc, Mutex};
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Condvar, Mutex};
use std::thread::JoinHandle;
/// Contains objects to control a CPU from outside.
pub struct CpuController {
thread: ManuallyDrop<JoinHandle<()>>,
state: Arc<Mutex<CpuState>>,
debug: Arc<(Mutex<DebugStates>, Condvar)>,
wakeup: bool,
}
impl CpuController {
pub fn new(thread: JoinHandle<()>, state: Arc<Mutex<CpuState>>) -> Self {
pub fn new(thread: JoinHandle<()>, debug: Arc<(Mutex<DebugStates>, Condvar)>) -> Self {
Self {
thread: ManuallyDrop::new(thread),
state,
debug,
wakeup: false,
}
}
pub fn debug_states<R>(&mut self, f: impl FnOnce(&GdbRegs) -> R) -> R {
// Request from the CPU if not available.
let mut s = self.debug.0.lock().unwrap();
loop {
s = match s.deref() {
DebugStates::None => {
*s = DebugStates::Request;
self.wakeup = true;
self.debug.1.wait(s).unwrap()
}
DebugStates::Request => self.debug.1.wait(s).unwrap(),
DebugStates::DebuggerOwned(v) => break f(v),
DebugStates::CpuOwned(_) => {
// The CPU is not pickup the previous value yet.
self.debug.1.wait(s).unwrap()
}
};
}
}
pub fn release(&mut self) {
let mut s = self.debug.0.lock().unwrap();
match std::mem::take(s.deref_mut()) {
DebugStates::DebuggerOwned(v) => *s = DebugStates::CpuOwned(v),
_ => unreachable!(),
}
if std::mem::take(&mut self.wakeup) {
self.debug.1.notify_one();
}
}
}
@ -24,7 +62,18 @@ impl Drop for CpuController {
}
}
/// State of a CPU.
pub enum CpuState {
Running,
/// Debugging states of a CPU.
#[derive(Default)]
pub enum DebugStates {
#[default]
None,
Request,
DebuggerOwned(GdbRegs),
CpuOwned(GdbRegs),
}
#[cfg(target_arch = "aarch64")]
type GdbRegs = gdbstub_arch::aarch64::reg::AArch64CoreRegs;
#[cfg(target_arch = "x86_64")]
type GdbRegs = gdbstub_arch::x86::reg::X86_64CoreRegs;

View File

@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pub use self::controller::CpuState;
pub use self::controller::DebugStates;
use self::controller::CpuController;
use super::hv::{Cpu, CpuExit, CpuIo, Hypervisor};
use super::hv::{Cpu, CpuExit, CpuIo, CpuRun, Hypervisor};
use super::hw::{DeviceContext, DeviceTree};
use super::ram::RamMap;
use super::screen::Screen;
@ -13,7 +13,7 @@ use std::error::Error;
use std::num::NonZero;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Condvar, Mutex};
#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
@ -59,24 +59,29 @@ impl<H: Hypervisor, S: Screen> CpuManager<H, S> {
};
// Spawn thread to drive vCPU.
let state = Arc::new(Mutex::new(CpuState::Running));
let debug = Arc::new((Mutex::default(), Condvar::new()));
let t = match map {
Some(map) => std::thread::spawn({
let state = state.clone();
let debug = debug.clone();
move || Self::main_cpu(args, state, start, map)
move || Self::main_cpu(args, debug, start, map)
}),
None => todo!(),
};
self.cpus.push(CpuController::new(t, state));
self.cpus.push(CpuController::new(t, debug));
}
pub fn debug_lock(&mut self) -> DebugLock<H, S> {
DebugLock(self)
}
fn main_cpu(args: Args<H, S>, state: Arc<Mutex<CpuState>>, entry: usize, map: RamMap) {
fn main_cpu(
args: Args<H, S>,
debug: Arc<(Mutex<DebugStates>, Condvar)>,
entry: usize,
map: RamMap,
) {
let mut cpu = match args.hv.create_cpu(0) {
Ok(v) => v,
Err(e) => {
@ -92,10 +97,14 @@ impl<H: Hypervisor, S: Screen> CpuManager<H, S> {
return;
}
Self::run_cpu(&args, &state, cpu);
Self::run_cpu(&args, &debug, cpu);
}
fn run_cpu<'a>(args: &'a Args<H, S>, state: &'a Mutex<CpuState>, mut cpu: H::Cpu<'a>) {
fn run_cpu<'a>(
args: &'a Args<H, S>,
debug: &'a (Mutex<DebugStates>, Condvar),
mut cpu: H::Cpu<'a>,
) {
// Build device contexts for this CPU.
let mut devices = args
.devices
@ -103,7 +112,7 @@ impl<H: Hypervisor, S: Screen> CpuManager<H, S> {
.map(|(addr, dev)| {
let end = dev.len().checked_add(addr).unwrap();
(addr, (dev.create_context(&args.hv, state), end))
(addr, (dev.create_context(&args.hv, debug), end))
})
.collect::<BTreeMap<usize, (Box<dyn DeviceContext<H::Cpu<'a>>>, NonZero<usize>)>>();

View File

@ -2,7 +2,7 @@
use super::arch::{KvmStates, StatesError};
use super::ffi::kvm_run;
use super::run::KvmRun;
use crate::vmm::hv::{Cpu, CpuExit, CpuIo, IoBuf};
use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuRun, IoBuf};
use libc::munmap;
use std::os::fd::{AsRawFd, OwnedFd};
use std::sync::MutexGuard;
@ -38,11 +38,14 @@ impl<'a> Cpu for KvmCpu<'a> {
type States<'b> = KvmStates<'b> where Self: 'b;
type GetStatesErr = StatesError;
type Exit<'b> = KvmExit<'b, 'a> where Self: 'b;
type RunErr = std::io::Error;
fn states(&mut self) -> Result<Self::States<'_>, Self::GetStatesErr> {
KvmStates::from_cpu(&mut self.fd)
}
}
impl<'a> CpuRun for KvmCpu<'a> {
type RunErr = std::io::Error;
fn run(&mut self) -> Result<Self::Exit<'_>, Self::RunErr> {
match unsafe { kvm_run(self.fd.as_raw_fd()) } {
@ -56,6 +59,7 @@ impl<'a> Cpu for KvmCpu<'a> {
pub struct KvmExit<'a, 'b>(&'a mut KvmCpu<'b>);
impl<'a, 'b> CpuExit for KvmExit<'a, 'b> {
type Cpu = KvmCpu<'b>;
type Io = KvmIo<'a, 'b>;
#[cfg(target_arch = "x86_64")]
@ -80,6 +84,7 @@ impl<'a, 'b> CpuExit for KvmExit<'a, 'b> {
pub struct KvmIo<'a, 'b>(&'a mut KvmCpu<'b>);
impl<'a, 'b> CpuIo for KvmIo<'a, 'b> {
type Cpu = KvmCpu<'b>;
type TranslateErr = std::io::Error;
fn addr(&self) -> usize {
@ -118,6 +123,10 @@ impl<'a, 'b> CpuIo for KvmIo<'a, 'b> {
_ => return Err(std::io::Error::last_os_error()),
}
}
fn cpu(&mut self) -> &mut Self::Cpu {
self.0
}
}
#[cfg(target_arch = "x86_64")]

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuStates, IoBuf};
use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuRun, CpuStates, IoBuf};
use applevisor_sys::hv_reg_t::{HV_REG_CPSR, HV_REG_PC, HV_REG_X0, HV_REG_X1};
use applevisor_sys::hv_sys_reg_t::{
HV_SYS_REG_MAIR_EL1, HV_SYS_REG_SCTLR_EL1, HV_SYS_REG_SP_EL1, HV_SYS_REG_TCR_EL1,
@ -30,11 +30,20 @@ impl<'a> HvfCpu<'a> {
}
}
impl<'a> Drop for HvfCpu<'a> {
fn drop(&mut self) {
let ret = unsafe { hv_vcpu_destroy(self.instance) };
if ret != 0 {
panic!("hv_vcpu_destroy() fails with {ret:#x}");
}
}
}
impl<'a> Cpu for HvfCpu<'a> {
type States<'b> = HvfStates<'b, 'a> where Self: 'b;
type GetStatesErr = StatesError;
type Exit<'b> = HvfExit<'b, 'a> where Self: 'b;
type RunErr = RunError;
fn states(&mut self) -> Result<Self::States<'_>, Self::GetStatesErr> {
Ok(HvfStates {
@ -51,6 +60,10 @@ impl<'a> Cpu for HvfCpu<'a> {
x1: State::None,
})
}
}
impl<'a> CpuRun for HvfCpu<'a> {
type RunErr = RunError;
fn run(&mut self) -> Result<Self::Exit<'_>, Self::RunErr> {
match NonZero::new(unsafe { hv_vcpu_run(self.instance) }) {
@ -60,16 +73,6 @@ impl<'a> Cpu for HvfCpu<'a> {
}
}
impl<'a> Drop for HvfCpu<'a> {
fn drop(&mut self) {
let ret = unsafe { hv_vcpu_destroy(self.instance) };
if ret != 0 {
panic!("hv_vcpu_destroy() fails with {ret:#x}");
}
}
}
/// Implementation of [`Cpu::States`] for Hypervisor Framework.
pub struct HvfStates<'a, 'b> {
cpu: &'a mut HvfCpu<'b>,
@ -207,7 +210,8 @@ impl<'a, 'b> HvfExit<'a, 'b> {
}
impl<'a, 'b> CpuExit for HvfExit<'a, 'b> {
type Io = HvfIo;
type Cpu = HvfCpu<'b>;
type Io = HvfIo<'a, 'b>;
fn into_io(self) -> Result<Self::Io, Self> {
todo!();
@ -215,9 +219,10 @@ impl<'a, 'b> CpuExit for HvfExit<'a, 'b> {
}
/// Implementation of [`CpuIo`] for Hypervisor Framework.
pub struct HvfIo {}
pub struct HvfIo<'a, 'b>(&'a mut HvfCpu<'b>);
impl CpuIo for HvfIo {
impl<'a, 'b> CpuIo for HvfIo<'a, 'b> {
type Cpu = HvfCpu<'b>;
type TranslateErr = std::io::Error;
fn addr(&self) -> usize {
@ -231,6 +236,10 @@ impl CpuIo for HvfIo {
fn translate(&self, vaddr: usize) -> Result<usize, std::io::Error> {
todo!();
}
fn cpu(&mut self) -> &mut Self::Cpu {
self.0
}
}
/// Implementation of [`Cpu::RunErr`].

View File

@ -24,7 +24,7 @@ pub type Default = self::os::Whp;
/// Underlying hypervisor (e.g. KVM on Linux).
pub trait Hypervisor: Send + Sync + 'static {
type Cpu<'a>: Cpu
type Cpu<'a>: CpuRun
where
Self: 'a;
type CpuErr: Error + Send + 'static;
@ -45,12 +45,17 @@ pub trait Cpu {
where
Self: 'a;
type GetStatesErr: Error + Send + 'static;
type Exit<'a>: CpuExit
type Exit<'a>: CpuExit<Cpu = Self>
where
Self: 'a;
type RunErr: Error + Send + 'static;
fn states(&mut self) -> Result<Self::States<'_>, Self::GetStatesErr>;
}
/// Provides a method to run the CPU.
pub trait CpuRun: Cpu {
type RunErr: Error + Send + 'static;
fn run(&mut self) -> Result<Self::Exit<'_>, Self::RunErr>;
}
@ -139,7 +144,8 @@ pub trait CpuStates {
/// Contains information when VM exited.
pub trait CpuExit: Sized {
type Io: CpuIo;
type Cpu: Cpu;
type Io: CpuIo<Cpu = Self::Cpu>;
#[cfg(target_arch = "x86_64")]
fn into_hlt(self) -> Result<(), Self>;
@ -149,12 +155,14 @@ pub trait CpuExit: Sized {
/// Contains information when a VM exited because of memory-mapped I/O.
pub trait CpuIo {
type Cpu: Cpu;
type TranslateErr: Error + Send + 'static;
/// Returns physical address where the VM try to access.
fn addr(&self) -> usize;
fn buffer(&mut self) -> IoBuf;
fn translate(&self, vaddr: usize) -> Result<usize, Self::TranslateErr>;
fn cpu(&mut self) -> &mut Self::Cpu;
}
/// Encapsulates a buffer for memory-mapped I/O.

View File

@ -1,4 +1,4 @@
use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuStates, IoBuf};
use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuRun, CpuStates, IoBuf};
use std::error::Error;
use std::marker::PhantomData;
use std::mem::{size_of, zeroed, MaybeUninit};
@ -46,7 +46,6 @@ impl<'a> Cpu for WhpCpu<'a> {
type States<'b> = WhpStates<'b, 'a> where Self: 'b;
type GetStatesErr = StatesError;
type Exit<'b> = WhpExit<'b, 'a> where Self: 'b;
type RunErr = RunError;
fn states(&mut self) -> Result<Self::States<'_>, Self::GetStatesErr> {
let mut values: [WHV_REGISTER_VALUE; REGISTERS] = unsafe { zeroed() };
@ -70,6 +69,10 @@ impl<'a> Cpu for WhpCpu<'a> {
})
}
}
}
impl<'a> CpuRun for WhpCpu<'a> {
type RunErr = RunError;
fn run(&mut self) -> Result<Self::Exit<'_>, Self::RunErr> {
let mut cx = MaybeUninit::<WHV_RUN_VP_EXIT_CONTEXT>::uninit();
@ -279,6 +282,7 @@ pub struct WhpExit<'a, 'b> {
}
impl<'a, 'b> CpuExit for WhpExit<'a, 'b> {
type Cpu = WhpCpu<'b>;
type Io = WhpIo<'a, 'b>;
#[cfg(target_arch = "x86_64")]
@ -301,6 +305,7 @@ pub struct WhpIo<'a, 'b> {
}
impl<'a, 'b> CpuIo for WhpIo<'a, 'b> {
type Cpu = WhpCpu<'b>;
type TranslateErr = std::io::Error;
fn addr(&self) -> usize {
@ -314,6 +319,10 @@ impl<'a, 'b> CpuIo for WhpIo<'a, 'b> {
fn translate(&self, vaddr: usize) -> Result<usize, std::io::Error> {
todo!()
}
fn cpu(&mut self) -> &mut Self::Cpu {
todo!();
}
}
/// Implementation of [`Cpu::GetStatesErr`] and [`CpuStates::Err`].

View File

@ -1,12 +1,12 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use self::context::Context;
use super::{Device, DeviceContext};
use crate::vmm::cpu::CpuState;
use crate::vmm::cpu::DebugStates;
use crate::vmm::hv::Hypervisor;
use crate::vmm::VmmEventHandler;
use obconf::ConsoleMemory;
use std::num::NonZero;
use std::sync::Mutex;
use std::sync::{Condvar, Mutex};
mod context;
@ -40,7 +40,7 @@ impl<H: Hypervisor> Device<H> for Console {
fn create_context<'a>(
&'a self,
hv: &'a H,
_: &'a Mutex<CpuState>,
_: &'a (Mutex<DebugStates>, Condvar),
) -> Box<dyn DeviceContext<H::Cpu<'a>> + 'a> {
Box::new(Context::new(self, hv))
}

View File

@ -1,25 +1,105 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use super::Debugger;
use crate::vmm::cpu::CpuState;
use crate::vmm::cpu::DebugStates;
use crate::vmm::hv::{Cpu, CpuExit, CpuIo};
use crate::vmm::hw::{read_u8, DeviceContext, MmioError};
use crate::vmm::VmmEvent;
use obconf::{DebuggerMemory, StopReason};
use std::error::Error;
use std::mem::offset_of;
use std::ops::Deref;
use std::ptr::null_mut;
use std::sync::Mutex;
use std::sync::{Condvar, Mutex};
use thiserror::Error;
/// Implementation of [`DeviceContext`].
pub struct Context<'a> {
dev: &'a Debugger,
state: &'a Mutex<CpuState>,
debug: &'a (Mutex<DebugStates>, Condvar),
}
impl<'a> Context<'a> {
pub fn new(dev: &'a Debugger, state: &'a Mutex<CpuState>) -> Self {
Self { dev, state }
pub fn new(dev: &'a Debugger, debug: &'a (Mutex<DebugStates>, Condvar)) -> Self {
Self { dev, debug }
}
fn exec_stop<C: Cpu>(
&mut self,
exit: &mut <C::Exit<'_> as CpuExit>::Io,
off: usize,
) -> Result<(), ExecError> {
// Read stop reason.
let stop = read_u8(exit).map_err(|e| ExecError::ReadFailed(off, e))?;
let stop: StopReason = stop.try_into().map_err(|_| ExecError::InvalidStop(stop))?;
self.set_states::<C>(exit, stop)?;
// Notify GUI. This will block until the debugger works are completed.
let stop = match stop {
StopReason::WaitForDebugger => null_mut(),
};
unsafe { self.dev.event.invoke(VmmEvent::Breakpoint { stop }) };
Ok(())
}
fn set_states<C: Cpu>(
&mut self,
exit: &mut <C::Exit<'_> as CpuExit>::Io,
r: StopReason,
) -> Result<(), ExecError> {
// Get states.
let next = match r {
StopReason::WaitForDebugger => Self::get_states(exit.cpu())?,
};
// Set states.
let mut s = self.debug.0.lock().unwrap();
if matches!(s.deref(), DebugStates::None) {
*s = DebugStates::DebuggerOwned(next);
return Ok(());
}
// Wait until the debugger release us.
assert!(matches!(s.deref(), DebugStates::Request));
*s = DebugStates::DebuggerOwned(next);
self.debug.1.notify_one();
loop {
s = match s.deref() {
DebugStates::DebuggerOwned(_) => self.debug.1.wait(s).unwrap(),
DebugStates::CpuOwned(v) => {
// Two possible cases here:
//
// 1. CpuController::debug_states waiting for us to unlock.
// 2. CpuController::debug_states waiting for us to notify.
//
// Condvar::notify_one only wakeup the thread that already waiting.
*s = DebugStates::DebuggerOwned(v.clone());
self.debug.1.notify_one();
break;
}
_ => unreachable!(),
};
}
Ok(())
}
#[cfg(target_arch = "aarch64")]
fn get_states(
_: &mut impl Cpu,
) -> Result<gdbstub_arch::aarch64::reg::AArch64CoreRegs, ExecError> {
todo!()
}
#[cfg(target_arch = "x86_64")]
fn get_states(_: &mut impl Cpu) -> Result<gdbstub_arch::x86::reg::X86_64CoreRegs, ExecError> {
todo!()
}
}
@ -29,18 +109,7 @@ impl<'a, C: Cpu> DeviceContext<C> for Context<'a> {
let off = exit.addr() - self.dev.addr;
if off == offset_of!(DebuggerMemory, stop) {
// Read stop reason.
let stop = read_u8(exit).map_err(|e| ExecError::ReadFailed(off, e))?;
let stop: StopReason = stop
.try_into()
.map_err(|_| Box::new(ExecError::InvalidStop(stop)))?;
// Notify GUI.
let stop = match stop {
StopReason::WaitForDebugger => null_mut(),
};
unsafe { self.dev.event.invoke(VmmEvent::Breakpoint { stop }) };
self.exec_stop::<C>(exit, off)?;
} else {
return Err(Box::new(ExecError::UnknownField(off)));
}

View File

@ -1,12 +1,12 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use self::context::Context;
use super::{Device, DeviceContext};
use crate::vmm::cpu::CpuState;
use crate::vmm::cpu::DebugStates;
use crate::vmm::hv::Hypervisor;
use crate::vmm::VmmEventHandler;
use obconf::DebuggerMemory;
use std::num::NonZero;
use std::sync::Mutex;
use std::sync::{Condvar, Mutex};
mod context;
@ -40,8 +40,8 @@ impl<H: Hypervisor> Device<H> for Debugger {
fn create_context<'a>(
&'a self,
_: &'a H,
state: &'a Mutex<CpuState>,
debug: &'a (Mutex<DebugStates>, Condvar),
) -> Box<dyn DeviceContext<H::Cpu<'a>> + 'a> {
Box::new(Context::new(self, state))
Box::new(Context::new(self, debug))
}
}

View File

@ -3,13 +3,13 @@ pub use self::console::*;
pub use self::debugger::*;
pub use self::vmm::*;
use super::cpu::CpuState;
use super::cpu::DebugStates;
use super::hv::{Cpu, CpuExit, CpuIo, Hypervisor, IoBuf};
use super::VmmEventHandler;
use std::collections::BTreeMap;
use std::error::Error;
use std::num::NonZero;
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Condvar, Mutex};
use thiserror::Error;
mod console;
@ -130,7 +130,7 @@ pub trait Device<H: Hypervisor>: Send + Sync {
fn create_context<'a>(
&'a self,
hv: &'a H,
state: &'a Mutex<CpuState>,
debug: &'a (Mutex<DebugStates>, Condvar),
) -> Box<dyn DeviceContext<H::Cpu<'a>> + 'a>;
}
@ -139,7 +139,7 @@ pub trait DeviceContext<C: Cpu> {
fn exec(&mut self, exit: &mut <C::Exit<'_> as CpuExit>::Io) -> Result<bool, Box<dyn Error>>;
}
/// Struct to build virtual device map.
/// Struct to build a map of virtual device.
struct MapBuilder<H: Hypervisor> {
map: BTreeMap<usize, Arc<dyn Device<H>>>,
next: usize,

View File

@ -1,12 +1,12 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use self::context::Context;
use super::{Device, DeviceContext};
use crate::vmm::cpu::CpuState;
use crate::vmm::cpu::DebugStates;
use crate::vmm::hv::Hypervisor;
use crate::vmm::VmmEventHandler;
use obconf::VmmMemory;
use std::num::NonZero;
use std::sync::Mutex;
use std::sync::{Condvar, Mutex};
mod context;
@ -40,7 +40,7 @@ impl<H: Hypervisor> Device<H> for Vmm {
fn create_context<'a>(
&'a self,
_: &'a H,
_: &'a Mutex<CpuState>,
_: &'a (Mutex<DebugStates>, Condvar),
) -> Box<dyn DeviceContext<H::Cpu<'a>> + 'a> {
Box::new(Context::new(self))
}