pub mod gdb;
pub mod uhyve;
pub mod vcpu;
pub mod virtio;
pub mod virtqueue;
pub type HypervisorError = kvm_ioctls::Error;
pub type DebugExitInfo = kvm_bindings::kvm_debug_exit_arch;
use std::{
io, mem,
net::{TcpListener, TcpStream},
os::unix::prelude::JoinHandleExt,
sync::{Arc, Barrier},
thread,
};
use core_affinity::CoreId;
use gdbstub::stub::{DisconnectReason, GdbStub};
use kvm_ioctls::Kvm;
use lazy_static::lazy_static;
use libc::{SIGRTMAX, SIGRTMIN};
use nix::sys::{
pthread::{pthread_kill, Pthread},
signal::{signal, SigHandler, Signal},
};
use crate::{
linux::gdb::{GdbUhyve, UhyveGdbEventLoop},
vm::{VirtualCPU, Vm},
Uhyve,
};
lazy_static! {
static ref KVM: Kvm = Kvm::new().unwrap();
}
struct KickSignal;
impl KickSignal {
const RTSIG_OFFSET: libc::c_int = 0;
fn get() -> Signal {
let kick_signal = SIGRTMIN() + Self::RTSIG_OFFSET;
assert!(kick_signal <= SIGRTMAX());
unsafe { mem::transmute(kick_signal) }
}
fn register_handler() -> nix::Result<()> {
extern "C" fn handle_signal(_signal: libc::c_int) {}
unsafe {
signal(Self::get(), SigHandler::Handler(handle_signal))?;
}
Ok(())
}
fn pthread_kill(pthread: Pthread) -> nix::Result<()> {
pthread_kill(pthread, Self::get())
}
}
impl Uhyve {
pub fn run(mut self, cpu_affinity: Option<Vec<CoreId>>) -> i32 {
KickSignal::register_handler().unwrap();
unsafe {
self.load_kernel().expect("Unabled to load the kernel");
}
if self.gdb_port.is_none() {
self.run_no_gdb(cpu_affinity)
} else {
self.run_gdb(cpu_affinity)
}
}
fn run_no_gdb(self, cpu_affinity: Option<Vec<CoreId>>) -> i32 {
let barrier = Arc::new(Barrier::new(2));
let this = Arc::new(self);
let threads = (0..this.num_cpus())
.map(|cpu_id| {
let vm = this.clone();
let barrier = barrier.clone();
let local_cpu_affinity = cpu_affinity
.as_ref()
.and_then(|core_ids| core_ids.get(cpu_id as usize).copied());
thread::spawn(move || {
debug!("Create thread for CPU {}", cpu_id);
match local_cpu_affinity {
Some(core_id) => {
debug!("Trying to pin thread {} to CPU {}", cpu_id, core_id.id);
core_affinity::set_for_current(core_id); }
None => debug!("No affinity specified, not binding thread"),
}
let mut cpu = vm.create_cpu(cpu_id).unwrap();
cpu.init(vm.get_entry_point(), vm.stack_address(), cpu_id)
.unwrap();
thread::sleep(std::time::Duration::from_millis(cpu_id as u64 * 50));
match cpu.run() {
Ok(code) => {
if code.is_some() {
barrier.wait();
}
code
}
Err(err) => {
error!("CPU {} crashed with {:?}", cpu_id, err);
None
}
}
})
})
.collect::<Vec<_>>();
barrier.wait();
for thread in &threads {
KickSignal::pthread_kill(thread.as_pthread_t()).unwrap();
}
let code = threads
.into_iter()
.filter_map(|thread| thread.join().unwrap())
.collect::<Vec<_>>();
assert_eq!(
1,
code.len(),
"more than one thread finished with an exit code"
);
code[0]
}
fn run_gdb(self, cpu_affinity: Option<Vec<CoreId>>) -> i32 {
let cpu_id = 0;
let local_cpu_affinity = cpu_affinity
.as_ref()
.and_then(|core_ids| core_ids.get(cpu_id as usize).copied());
match local_cpu_affinity {
Some(core_id) => {
debug!("Trying to pin thread {} to CPU {}", cpu_id, core_id.id);
core_affinity::set_for_current(core_id); }
None => debug!("No affinity specified, not binding thread"),
}
let mut cpu = self.create_cpu(cpu_id).unwrap();
cpu.init(self.get_entry_point(), self.stack_address(), cpu_id)
.unwrap();
let connection = wait_for_gdb_connection(self.gdb_port.unwrap()).unwrap();
let debugger = GdbStub::new(connection);
let mut debuggable_vcpu = GdbUhyve::new(self, cpu);
match debugger
.run_blocking::<UhyveGdbEventLoop>(&mut debuggable_vcpu)
.unwrap()
{
DisconnectReason::TargetExited(code) => code.into(),
DisconnectReason::TargetTerminated(_) => unreachable!(),
DisconnectReason::Disconnect => {
eprintln!("Debugger disconnected.");
0
}
DisconnectReason::Kill => {
eprintln!("Kill command received.");
0
}
}
}
}
fn wait_for_gdb_connection(port: u16) -> io::Result<TcpStream> {
let sockaddr = format!("localhost:{}", port);
eprintln!("Waiting for a GDB connection on {:?}...", sockaddr);
let sock = TcpListener::bind(sockaddr)?;
let (stream, addr) = sock.accept()?;
eprintln!("Debugger connected from {}", addr);
Ok(stream) }