use std::net::{TcpListener, TcpStream};
#[cfg(unix)]
use std::os::unix::net::{UnixListener, UnixStream};
use gdbstub::common::Signal;
use gdbstub::conn::{Connection, ConnectionExt};
use gdbstub::stub::SingleThreadStopReason;
use gdbstub::stub::{run_blocking, DisconnectReason, GdbStub, GdbStubError};
use gdbstub::target::Target;
type DynResult<T> = Result<T, Box<dyn std::error::Error>>;
const TEST_PROGRAM_ELF: &[u8] = include_bytes!("test_bin/test.elf");
mod emu;
mod gdb;
mod mem_sniffer;
fn wait_for_tcp(port: u16) -> DynResult<TcpStream> {
let sockaddr = format!("127.0.0.1:{}", 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)
}
#[cfg(unix)]
fn wait_for_uds(path: &str) -> DynResult<UnixStream> {
match std::fs::remove_file(path) {
Ok(_) => {}
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => {}
_ => return Err(e.into()),
},
}
eprintln!("Waiting for a GDB connection on {}...", path);
let sock = UnixListener::bind(path)?;
let (stream, addr) = sock.accept()?;
eprintln!("Debugger connected from {:?}", addr);
Ok(stream)
}
enum EmuGdbEventLoop {}
impl run_blocking::BlockingEventLoop for EmuGdbEventLoop {
type Target = emu::Emu;
type Connection = Box<dyn ConnectionExt<Error = std::io::Error>>;
type StopReason = SingleThreadStopReason<u32>;
#[allow(clippy::type_complexity)]
fn wait_for_stop_reason(
target: &mut emu::Emu,
conn: &mut Self::Connection,
) -> Result<
run_blocking::Event<SingleThreadStopReason<u32>>,
run_blocking::WaitForStopReasonError<
<Self::Target as Target>::Error,
<Self::Connection as Connection>::Error,
>,
> {
let poll_incoming_data = || {
conn.peek().map(|b| b.is_some()).unwrap_or(true)
};
match target.run(poll_incoming_data) {
emu::RunEvent::IncomingData => {
let byte = conn
.read()
.map_err(run_blocking::WaitForStopReasonError::Connection)?;
Ok(run_blocking::Event::IncomingData(byte))
}
emu::RunEvent::Event(event) => {
use gdbstub::target::ext::breakpoints::WatchKind;
let stop_reason = match event {
emu::Event::DoneStep => SingleThreadStopReason::DoneStep,
emu::Event::Halted => SingleThreadStopReason::Terminated(Signal::SIGSTOP),
emu::Event::Break => SingleThreadStopReason::SwBreak(()),
emu::Event::WatchWrite(addr) => SingleThreadStopReason::Watch {
tid: (),
kind: WatchKind::Write,
addr,
},
emu::Event::WatchRead(addr) => SingleThreadStopReason::Watch {
tid: (),
kind: WatchKind::Read,
addr,
},
};
Ok(run_blocking::Event::TargetStopped(stop_reason))
}
}
}
fn on_interrupt(
_target: &mut emu::Emu,
) -> Result<Option<SingleThreadStopReason<u32>>, <emu::Emu as Target>::Error> {
Ok(Some(SingleThreadStopReason::Signal(Signal::SIGINT)))
}
}
fn main() -> DynResult<()> {
pretty_env_logger::init();
let mut emu = emu::Emu::new(TEST_PROGRAM_ELF)?;
let connection: Box<dyn ConnectionExt<Error = std::io::Error>> = {
if std::env::args().nth(1) == Some("--uds".to_string()) {
#[cfg(not(unix))]
{
return Err("Unix Domain Sockets can only be used on Unix".into());
}
#[cfg(unix)]
{
Box::new(wait_for_uds("/tmp/armv4t_gdb")?)
}
} else {
Box::new(wait_for_tcp(9001)?)
}
};
let gdb = GdbStub::new(connection);
match gdb.run_blocking::<EmuGdbEventLoop>(&mut emu) {
Ok(disconnect_reason) => match disconnect_reason {
DisconnectReason::Disconnect => {
println!("GDB client has disconnected. Running to completion...");
while emu.step() != Some(emu::Event::Halted) {}
}
DisconnectReason::TargetExited(code) => {
println!("Target exited with code {}!", code)
}
DisconnectReason::TargetTerminated(sig) => {
println!("Target terminated with signal {}!", sig)
}
DisconnectReason::Kill => println!("GDB sent a kill command!"),
},
Err(GdbStubError::TargetError(e)) => {
println!("target encountered a fatal error: {}", e)
}
Err(e) => {
println!("gdbstub encountered a fatal error: {}", e)
}
}
let ret = emu.cpu.reg_get(armv4t_emu::Mode::User, 0);
println!("Program completed. Return value: {}", ret);
Ok(())
}