pub mod arch;
pub mod packet;
pub mod stub;
pub mod target;
use std::io::{ErrorKind, Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs};
use std::time::Duration;
use crate::machine::Machine;
pub use stub::Outcome;
pub use target::{DebugTarget, MachineTarget, Stop, StopKind, TargetError, TargetResult};
const IDLE_SLEEP: Duration = Duration::from_millis(1);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Progress {
Waiting,
Halted,
Running,
Detached,
Kill,
}
#[derive(Debug)]
struct Conn {
stream: TcpStream,
peer: Option<SocketAddr>,
framer: packet::Framer,
stub: stub::Stub,
pending: Vec<u8>,
}
#[derive(Debug)]
pub struct GdbServer {
listener: TcpListener,
conn: Option<Conn>,
wait_for_attach: bool,
detached: bool,
}
impl GdbServer {
pub fn bind(addr: &str) -> std::io::Result<GdbServer> {
let resolved = resolve(addr)?;
let listener = TcpListener::bind(&resolved[..])?;
listener.set_nonblocking(true)?;
Ok(GdbServer {
listener,
conn: None,
wait_for_attach: true,
detached: false,
})
}
#[must_use]
pub fn without_waiting(mut self) -> GdbServer {
self.wait_for_attach = false;
self
}
pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
self.listener.local_addr()
}
#[must_use]
pub fn is_attached(&self) -> bool {
self.conn.is_some()
}
pub fn poll(&mut self, target: &mut dyn DebugTarget) -> std::io::Result<Progress> {
self.accept()?;
let Some(conn) = self.conn.as_mut() else {
std::thread::sleep(IDLE_SLEEP);
return Ok(if self.wait_for_attach && !self.detached {
Progress::Waiting
} else {
Progress::Detached
});
};
let mut out = Vec::new();
let mut outcome = Outcome::Continue;
let mut closed = false;
let mut buf = [0u8; 1024];
match conn.stream.read(&mut buf) {
Ok(0) => closed = true,
Ok(n) => {
for byte in buf.get(..n).unwrap_or(&[]) {
if let Some(event) = conn.framer.push(*byte) {
match conn.stub.on_event(event, target, &mut out) {
Outcome::Continue => {}
other => outcome = other,
}
}
}
}
Err(e) if e.kind() == ErrorKind::WouldBlock => {}
Err(e) if e.kind() == ErrorKind::Interrupted => {}
Err(_) => closed = true,
}
if !closed && outcome == Outcome::Continue {
conn.stub.drive(target, &mut out);
}
conn.pending.extend_from_slice(&out);
if !closed && !Self::flush(conn) {
closed = true;
}
let running = conn.stub.is_running();
match outcome {
Outcome::Kill => {
self.conn = None;
return Ok(Progress::Kill);
}
Outcome::Detach => {
let _ = conn.stream.flush();
closed = true;
}
Outcome::Continue => {}
}
if closed {
self.conn = None;
self.detached = true;
return Ok(Progress::Detached);
}
if running {
Ok(Progress::Running)
} else {
std::thread::sleep(IDLE_SLEEP);
Ok(Progress::Halted)
}
}
fn accept(&mut self) -> std::io::Result<()> {
if self.conn.is_some() {
return Ok(());
}
match self.listener.accept() {
Ok((stream, peer)) => {
stream.set_nonblocking(true)?;
let _ = stream.set_nodelay(true);
self.conn = Some(Conn {
stream,
peer: Some(peer),
framer: packet::Framer::new(),
stub: stub::Stub::new(),
pending: Vec::new(),
});
Ok(())
}
Err(e) if e.kind() == ErrorKind::WouldBlock => Ok(()),
Err(e) if e.kind() == ErrorKind::Interrupted => Ok(()),
Err(e) => Err(e),
}
}
fn flush(conn: &mut Conn) -> bool {
while !conn.pending.is_empty() {
match conn.stream.write(&conn.pending) {
Ok(0) => return false,
Ok(n) => {
conn.pending.drain(..n);
}
Err(e) if e.kind() == ErrorKind::WouldBlock => return true,
Err(e) if e.kind() == ErrorKind::Interrupted => {}
Err(_) => return false,
}
}
let _ = conn.stream.flush();
true
}
#[must_use]
pub fn peer(&self) -> Option<SocketAddr> {
self.conn.as_ref().and_then(|c| c.peer)
}
}
fn resolve(addr: &str) -> std::io::Result<Vec<SocketAddr>> {
let spec = if addr.starts_with(':') {
format!("127.0.0.1{addr}")
} else if addr.chars().all(|c| c.is_ascii_digit()) && !addr.is_empty() {
format!("127.0.0.1:{addr}")
} else {
addr.to_string()
};
let list: Vec<SocketAddr> = spec.to_socket_addrs()?.collect();
if list.is_empty() {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
format!("`{addr}` resolved to no address"),
));
}
Ok(list)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitReason {
Killed,
Stopped,
}
pub fn serve(
machine: &mut Machine,
server: &mut GdbServer,
mut keep_going: impl FnMut(&mut Machine) -> bool,
) -> std::io::Result<ExitReason> {
let mut target = MachineTarget::new(machine);
loop {
let progress = server.poll(&mut target)?;
if progress == Progress::Kill {
return Ok(ExitReason::Killed);
}
if progress == Progress::Detached {
if let Err(e) = target.resume() {
return Err(std::io::Error::other(e.to_string()));
}
}
if !keep_going(target.machine_mut()) {
return Ok(ExitReason::Stopped);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_bare_port_binds_the_loopback_only() {
let addrs = resolve("1234").expect("a bare port");
assert!(addrs.iter().all(|a| a.ip().is_loopback()), "{addrs:?}");
let addrs = resolve(":1234").expect("a leading colon");
assert!(addrs.iter().all(|a| a.ip().is_loopback()), "{addrs:?}");
let addrs = resolve("0.0.0.0:1234").expect("an explicit address");
assert!(addrs.iter().any(|a| a.ip().is_unspecified()), "{addrs:?}");
}
#[test]
fn a_nonsense_address_is_an_error_not_a_panic() {
assert!(resolve("").is_err());
assert!(resolve("not a host name at all:1").is_err());
}
#[test]
fn an_ephemeral_port_reports_where_it_landed() {
let server = GdbServer::bind(":0").expect("bind");
let addr = server.local_addr().expect("local_addr");
assert!(addr.port() != 0);
assert!(addr.ip().is_loopback());
assert!(!server.is_attached());
}
}