Skip to content

Commit

Permalink
Initializes GdbSession (#1254)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Jan 21, 2025
1 parent 1f7d5ce commit b824e9e
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 161 deletions.
19 changes: 19 additions & 0 deletions gui/src/gdb/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use super::{GdbError, GdbExecutor, GdbHandler, GdbSession};

/// Implementation of [`GdbExecutor`] to execute requests from GDB client.
pub struct ClientHandler<'a, H> {
session: &'a mut GdbSession,
handler: &'a mut H,
}

impl<'a, H> ClientHandler<'a, H> {
pub fn new(session: &'a mut GdbSession, handler: &'a mut H) -> Self {
Self { session, handler }
}
}

impl<'a, H: GdbHandler> GdbExecutor for ClientHandler<'a, H> {
fn pump(&mut self) -> Result<Option<impl AsRef<[u8]> + '_>, GdbError> {
Ok(Some(self.session.res.drain(..)))
}
}
2 changes: 2 additions & 0 deletions gui/src/gdb/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// Provides methods to handle debug events.
pub trait GdbHandler {}
120 changes: 28 additions & 92 deletions gui/src/gdb/mod.rs
Original file line number Diff line number Diff line change
@@ -1,102 +1,38 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::io::{Error, ErrorKind, Read, Write};
use std::net::TcpStream;
pub use self::handler::*;

/// Encapsulate a debugger connection.
pub struct DebugClient {
sock: TcpStream,
buf: Vec<u8>,
next: usize,
}

impl DebugClient {
pub(super) fn new(sock: TcpStream) -> Self {
Self {
sock,
buf: Vec::new(),
next: 0,
}
}

#[cfg(unix)]
pub fn socket(&self) -> std::os::fd::RawFd {
std::os::fd::AsRawFd::as_raw_fd(&self.sock)
}

#[cfg(windows)]
pub fn socket(&self) -> std::os::windows::io::RawSocket {
std::os::windows::io::AsRawSocket::as_raw_socket(&self.sock)
}

pub fn read(&mut self) -> Result<u8, Error> {
// Fill the buffer if needed.
while self.next == self.buf.len() {
// Clear previous data.
self.buf.clear();
self.next = 0;

// Read.
let mut buf = [0; 1024];
let len = match self.sock.read(&mut buf) {
Ok(v) => v,
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};

if len == 0 {
return Err(Error::from(ErrorKind::UnexpectedEof));
}

self.buf.extend_from_slice(&buf[..len]);
}

// Get byte.
let b = self.buf[self.next];
use self::client::ClientHandler;
use thiserror::Error;

self.next += 1;
mod client;
mod handler;

Ok(b)
}
/// Contains states for a GDB remote session.
#[derive(Default)]
pub struct GdbSession {
req: Vec<u8>,
res: Vec<u8>,
}

impl gdbstub::conn::Connection for DebugClient {
type Error = std::io::Error;

fn write(&mut self, byte: u8) -> Result<(), Self::Error> {
self.write_all(std::slice::from_ref(&byte))
}

fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Self::Error> {
while !buf.is_empty() {
let written = match Write::write(&mut self.sock, buf) {
Ok(v) => v,
Err(e) if matches!(e.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock) => {
continue;
}
Err(e) => return Err(e),
};

if written == 0 {
return Err(std::io::Error::from(ErrorKind::WriteZero));
}

buf = &buf[written..];
}
impl GdbSession {
#[must_use]
pub fn dispatch_client<'a, H: GdbHandler>(
&'a mut self,
data: &[u8],
h: &'a mut H,
) -> impl GdbExecutor + 'a {
self.req.extend_from_slice(data);

Ok(())
}

fn flush(&mut self) -> Result<(), Self::Error> {
while let Err(e) = Write::flush(&mut self.sock) {
if !matches!(e.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock) {
return Err(e);
}
}

Ok(())
ClientHandler::new(self, h)
}
}

fn on_session_start(&mut self) -> Result<(), Self::Error> {
self.sock.set_nodelay(true)
}
/// Provides method to execute debug operations.
pub trait GdbExecutor {
/// The returned response can be empty if this pump does not produce any response.
fn pump(&mut self) -> Result<Option<impl AsRef<[u8]> + '_>, GdbError>;
}

/// Represents an error when [`GdbExecutor`] fails.
#[derive(Debug, Error)]
pub enum GdbError {}
136 changes: 87 additions & 49 deletions gui/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#![windows_subsystem = "windows"]

use self::data::{DataError, DataMgr};
use self::gdb::{GdbError, GdbExecutor, GdbSession};
use self::graphics::{EngineBuilder, GraphicsError, PhysicalDevice};
use self::hv::Hypervisor;
use self::log::LogWriter;
use self::profile::{DisplayResolution, Profile};
use self::setup::{run_setup, SetupError};
Expand All @@ -13,17 +15,16 @@ use self::vmm::{CpuError, Vmm, VmmError, VmmEvent};
use async_net::{TcpListener, TcpStream};
use clap::{Parser, ValueEnum};
use erdp::ErrorDisplay;
use futures::{select_biased, AsyncReadExt, FutureExt};
use futures::{
select_biased, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, FutureExt, TryStreamExt,
};
use slint::{ComponentHandle, ModelRc, SharedString, ToSharedString, VecModel};
use std::cell::Cell;
use std::future::Future;
use std::net::SocketAddrV4;
use std::path::PathBuf;
use std::pin::pin;
use std::process::ExitCode;
use std::rc::Rc;
use std::sync::Arc;
use std::task::Poll;
use thiserror::Error;
use winit::dpi::PhysicalSize;
use winit::window::Window;
Expand Down Expand Up @@ -228,17 +229,24 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> {
};

// Wait for debugger.
let mut gdb_con = if let Some(addr) = debug {
let v = wait_for_debugger(addr).await?;

if v.is_none() {
return Ok(());
let mut gdb_read: Box<dyn AsyncRead + Unpin>;
let mut gdb_write: Box<dyn AsyncWrite + Unpin>;

match debug {
Some(addr) => {
let (r, w) = match wait_for_debugger(addr).await? {
Some(v) => v.split(),
None => return Ok(()),
};

gdb_read = Box::new(r);
gdb_write = Box::new(w);
}

v
} else {
None
};
None => {
gdb_read = Box::new(futures::stream::pending::<Result<&[u8], _>>().into_async_read());
gdb_write = Box::new(futures::io::sink());
}
}

// Setup WindowAttributes for VMM screen.
let attrs = Window::default_attributes()
Expand All @@ -258,50 +266,26 @@ async fn run(args: ProgramArgs, exe: PathBuf) -> Result<(), ProgramError> {
let graphics = graphics
.build(&profile, attrs, &shutdown)
.map_err(ProgramError::BuildGraphicsEngine)?;
let mut gdb_in = [0; 1024];
let mut gdb = GdbSession::default();
let mut gdb_buf = [0; 1024];

// Start VMM.
let mut vmm = match Vmm::new(&profile, &kernel, None, &shutdown) {
let mut vmm = match Vmm::new(&profile, &kernel, &shutdown) {
Ok(v) => v,
Err(e) => return Err(ProgramError::StartVmm(kernel, e)),
};

loop {
// Prepare futures to poll.
let mut vmm = pin!(vmm.recv());
let mut debug = gdb_con.as_mut().map(|v| v.read(&mut gdb_in));

// Poll all futures.
let (vmm, debug) = std::future::poll_fn(move |cx| {
let vmm = vmm.as_mut().poll(cx);
let debug = debug.as_mut().map_or(Poll::Pending, |d| d.poll_unpin(cx));

match (vmm, debug) {
(Poll::Ready(v), Poll::Ready(d)) => Poll::Ready((Some(v), Some(d))),
(Poll::Ready(v), Poll::Pending) => Poll::Ready((Some(v), None)),
(Poll::Pending, Poll::Ready(d)) => Poll::Ready((None, Some(d))),
(Poll::Pending, Poll::Pending) => Poll::Pending,
// Wait for event.
let r = select_biased! {
v = gdb_read.read(&mut gdb_buf).fuse() => {
dispatch_gdb(v, &mut gdb, &gdb_buf, &mut vmm, &mut gdb_write).await?
}
})
.await;

// Process VMM event.
if let Some(vmm) = vmm {
match vmm {
VmmEvent::Exit(id, r) => {
if !r.map_err(ProgramError::CpuThread)? {
return Err(ProgramError::CpuPanic(id, logs.path().into()));
} else if id == 0 {
break;
}
}
VmmEvent::Log(t, m) => logs.write(t, m),
}
}
v = vmm.recv().fuse() => dispatch_vmm(v, &mut logs).await?,
};

// Process debugger requests.
if let Some(debug) = debug {
todo!()
if !r {
break;
}
}

Expand Down Expand Up @@ -442,6 +426,51 @@ async fn wait_for_debugger(addr: SocketAddrV4) -> Result<Option<TcpStream>, Prog
Ok(Some(client))
}

async fn dispatch_gdb<H: Hypervisor>(
res: Result<usize, std::io::Error>,
gdb: &mut GdbSession,
buf: &[u8],
vmm: &mut Vmm<H>,
con: &mut (dyn AsyncWrite + Unpin),
) -> Result<bool, ProgramError> {
// Check status.
let len = res.map_err(ProgramError::ReadDebuggerSocket)?;

if len == 0 {
return Ok(false);
}

// Dispatch the requests.
let mut exe = gdb.dispatch_client(&buf[..len], vmm);

while let Some(res) = exe.pump().map_err(ProgramError::DispatchDebugger)? {
let res = res.as_ref();

if !res.is_empty() {
con.write_all(res)
.await
.map_err(ProgramError::WriteDebuggerSocket)?;
}
}

Ok(true)
}

async fn dispatch_vmm(ev: VmmEvent, logs: &mut LogWriter) -> Result<bool, ProgramError> {
match ev {
VmmEvent::Exit(id, r) => {
if !r.map_err(ProgramError::CpuThread)? {
return Err(ProgramError::CpuPanic(id, logs.path().into()));
} else if id == 0 {
return Ok(false);
}
}
VmmEvent::Log(t, m) => logs.write(t, m),
}

Ok(true)
}

/// Program arguments parsed from command line.
#[derive(Parser)]
#[command(about = None)]
Expand Down Expand Up @@ -540,4 +569,13 @@ enum ProgramError {

#[error("vCPU #{0} panicked, see {1} for more information")]
CpuPanic(usize, PathBuf),

#[error("couldn't read debugger connection")]
ReadDebuggerSocket(#[source] std::io::Error),

#[error("couldn't dispatch debugger requests")]
DispatchDebugger(#[source] GdbError),

#[error("couldn't write debugger connection")]
WriteDebuggerSocket(#[source] std::io::Error),
}
Loading

0 comments on commit b824e9e

Please sign in to comment.