#[cfg(target_arch = "x86_64")]
pub mod x86_64;
pub(crate) mod gdb;
pub(crate) type DebugExitInfo = kvm_bindings::kvm_debug_exit_arch;
use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener, TcpStream},
sync::LazyLock,
};
use async_io::block_on;
use core_affinity::CoreId;
use gdbstub::{
conn::ConnectionExt,
stub::{DisconnectReason, GdbStub, MultiThreadStopReason, state_machine::GdbStubStateMachine},
};
use kvm_ioctls::Kvm;
use libc::{SIGRTMAX, SIGRTMIN};
use nix::sys::pthread::Pthread;
use crate::{
linux::x86_64::kvm_cpu::KvmVm,
serial::Destination,
vm::{UhyveVm, VmResult},
};
static KVM: LazyLock<Kvm> = LazyLock::new(|| Kvm::new().unwrap());
pub(crate) struct KickSignal;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct PthreadWrapper(pub Pthread);
unsafe impl Send for PthreadWrapper {}
unsafe impl Sync for PthreadWrapper {}
impl KickSignal {
const RTSIG_OFFSET: libc::c_int = 0;
fn get() -> libc::c_int {
let kick_signal: libc::c_int = SIGRTMIN() + Self::RTSIG_OFFSET;
assert!(kick_signal <= SIGRTMAX());
kick_signal
}
pub(crate) fn register_handler() -> nix::Result<()> {
extern "C" fn handle_signal(_signal: libc::c_int) {}
let res = unsafe {
libc::signal(
Self::get(),
handle_signal as *const () as libc::sighandler_t,
)
};
nix::errno::Errno::result(res).map(drop)
}
pub(crate) fn pthread_kill(pthread: Pthread) -> nix::Result<()> {
let res = unsafe { libc::pthread_kill(pthread, Self::get()) };
nix::errno::Errno::result(res).map(drop)
}
}
impl UhyveVm<KvmVm> {
pub fn run(self, cpu_affinity: Option<Vec<CoreId>>) -> VmResult {
KickSignal::register_handler().unwrap();
if self.kernel_info.params.gdb_port.is_none() {
self.run_no_gdb(cpu_affinity)
} else {
self.run_gdb(cpu_affinity)
}
}
fn run_gdb(self, cpu_affinity: Option<Vec<CoreId>>) -> VmResult {
let connection =
wait_for_gdb_connection(self.kernel_info.params.gdb_port.unwrap()).unwrap();
let debugger = GdbStub::new(connection);
let mut vcpu_manager = self.spawn_cpu_manager_for_gdb(cpu_affinity);
let mut gdb = debugger
.run_state_machine(&mut vcpu_manager)
.expect("GDB run_state_machine initialization failed");
let exit_code = loop {
gdb = match gdb {
GdbStubStateMachine::Idle(mut gdb) => {
let byte = gdb.borrow_conn().read().expect("GDB connection read error");
gdb.incoming_data(&mut vcpu_manager, byte)
.expect("GDB incoming_data error")
}
GdbStubStateMachine::Disconnected(gdb) => {
break match gdb.get_reason() {
DisconnectReason::TargetExited(exit_code) => exit_code.into(),
DisconnectReason::TargetTerminated(_) => unreachable!(),
DisconnectReason::Disconnect => {
eprintln!("Debugger disconnected.");
0
}
DisconnectReason::Kill => {
eprintln!("Kill command received.");
0
}
};
}
GdbStubStateMachine::CtrlCInterrupt(gdb) => {
for i in &vcpu_manager.vcpus {
i.kick();
}
gdb.interrupt_handled(&mut vcpu_manager, None::<MultiThreadStopReason<u64>>)
.expect("GDB interrupt_handled packet write failed")
}
GdbStubStateMachine::Running(mut gdb) => {
use futures_lite::AsyncReadExt;
enum UhyveOrGdb<X, Y> {
Uhyve(X),
Gdb(Y),
}
vcpu_manager.set_finished_initializing();
let borrow_conn = gdb.borrow_conn();
let inp = block_on(futures_lite::future::or(
async move {
let mut gdb_conn_async = async_io::Async::new(borrow_conn)
.expect("unable to asynchronize gdb connection");
let mut data_from_gdb_buf = [0u8];
let ret = gdb_conn_async
.read_exact(&mut data_from_gdb_buf)
.await
.map(|_| data_from_gdb_buf[0]);
let _ = gdb_conn_async.into_inner();
UhyveOrGdb::Gdb(ret)
},
async { UhyveOrGdb::Uhyve(vcpu_manager.stops.recv().await) },
));
match inp {
UhyveOrGdb::Gdb(byte) => {
let byte = byte.expect("error during GDB recv");
gdb.incoming_data(&mut vcpu_manager, byte)
.expect("GDB incoming_data error")
}
UhyveOrGdb::Uhyve(stop_reason) => {
let stop_reason = stop_reason.expect("error during stop packet recv");
gdb.report_stop(&mut vcpu_manager, stop_reason)
.expect("GDB report_stop error")
}
}
}
}
};
for i in &vcpu_manager.vcpus {
i.kick();
}
let output = if let Destination::Buffer(b) = &vcpu_manager.peripherals.serial.destination {
Some(String::from_utf8_lossy(&b.lock().unwrap()).into_owned())
} else {
None
};
VmResult {
code: exit_code,
output,
stats: None,
}
}
}
const LOCALHOST: [IpAddr; 2] = [
IpAddr::V4(Ipv4Addr::LOCALHOST),
IpAddr::V6(Ipv6Addr::LOCALHOST),
];
fn wait_for_gdb_connection(port: u16) -> io::Result<TcpStream> {
let sock = TcpListener::bind(
[
SocketAddr::new(LOCALHOST[0], port),
SocketAddr::new(LOCALHOST[1], port),
]
.as_ref(),
)?;
eprintln!(
"Waiting for a local GDB connection on port {}...",
sock.local_addr().unwrap().port()
);
let (stream, addr) = sock.accept()?;
eprintln!("Debugger connected from {addr}");
Ok(stream) }