#[cfg(feature = "capi")]
mod capi;
mod arch;
mod emu;
mod reg;
use gdbstub::conn::ConnectionExt;
use gdbstub::stub::state_machine::GdbStubStateMachine;
use gdbstub::stub::{DisconnectReason, GdbStubBuilder, SingleThreadStopReason};
use gdbstub::target::ext::breakpoints::WatchKind;
use singlyton::SingletonOption;
use std::any::Any;
use std::borrow::BorrowMut;
use std::io::ErrorKind;
use std::net::{TcpListener, TcpStream};
use unicorn_engine::unicorn_const::HookType;
use unicorn_engine::Unicorn;
type DynResult<T> = Result<T, Box<dyn std::error::Error>>;
static GDBSTUB: SingletonOption<Box<dyn Any>> = SingletonOption::new();
static EMU: SingletonOption<Box<dyn Any>> = SingletonOption::new();
fn wait_for_tcp(port: u16) -> DynResult<TcpStream> {
let sockaddr = format!("0.0.0.0:{}", 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)
}
pub fn udbserver<T: 'static>(uc: &mut Unicorn<T>, port: u16, start_addr: u64) -> DynResult<()> {
let code_hook = uc.add_code_hook(1, 0, |_, _, _| {}).expect("Failed to add empty code hook");
let mem_hook = uc
.add_mem_hook(HookType::MEM_READ, 1, 0, |_, _, _, _, _| true)
.expect("Failed to add empty mem hook");
if start_addr != 0 {
uc.add_code_hook(start_addr, start_addr, move |_, _, _| {
udbserver_start::<T>(port).expect("Failed to start udbserver")
})
.expect("Failed to add udbserver hook");
}
let emu = emu::Emu::new(
unsafe { std::mem::transmute::<&mut Unicorn<T>, &'static mut Unicorn<'static, T>>(uc) },
code_hook,
mem_hook,
)?;
EMU.replace(Box::new(emu));
if start_addr == 0 {
udbserver_start::<T>(port).expect("Failed to start udbserver");
}
Ok(())
}
fn udbserver_start<T: 'static>(port: u16) -> DynResult<()> {
if GDBSTUB.is_some() {
return Ok(());
}
{
let mut emu_any = EMU.get_mut();
let emu = emu_any.downcast_mut::<emu::Emu<T>>().expect("Failed to downcast EMU");
let gdbstub = GdbStubBuilder::new(wait_for_tcp(port)?).build()?.run_state_machine(emu)?;
GDBSTUB.replace(Box::new(gdbstub));
}
udbserver_loop::<T>()
}
pub(crate) fn udbserver_resume<T: 'static>(addr: Option<u64>) -> DynResult<()> {
let gdb_any = GDBSTUB.take().unwrap();
let mut gdb = *gdb_any
.downcast::<GdbStubStateMachine<emu::Emu<T>, TcpStream>>()
.expect("Failed to downcast GDBSTUB");
let reason = if let Some(watch_addr) = addr {
SingleThreadStopReason::Watch {
tid: (),
kind: WatchKind::Write,
addr: watch_addr,
}
} else {
SingleThreadStopReason::DoneStep
};
if let GdbStubStateMachine::Running(gdb_inner) = gdb {
let mut emu_any = EMU.get_mut();
let emu = emu_any.downcast_mut::<emu::Emu<T>>().expect("Failed to downcast EMU");
match gdb_inner.report_stop(emu.borrow_mut(), reason) {
Ok(gdb_state) => gdb = gdb_state,
Err(_) => return Ok(()),
}
}
GDBSTUB.replace(Box::new(gdb));
udbserver_loop::<T>()
}
fn is_disconnect_error(error: &std::io::Error) -> bool {
matches!(
error.kind(),
ErrorKind::UnexpectedEof | ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted | ErrorKind::BrokenPipe | ErrorKind::NotConnected
)
}
fn udbserver_loop<T: 'static>() -> DynResult<()> {
let gdb_any = GDBSTUB.take().unwrap();
let mut gdb = *gdb_any
.downcast::<GdbStubStateMachine<emu::Emu<T>, TcpStream>>()
.expect("Failed to downcast GDBSTUB");
loop {
gdb = match gdb {
GdbStubStateMachine::Idle(mut gdb_inner) => {
let byte = match gdb_inner.borrow_conn().read() {
Ok(byte) => byte,
Err(error) if is_disconnect_error(&error) => return handle_disconnect(DisconnectReason::Disconnect),
Err(error) => return Err(Box::new(error)),
};
let mut emu_any = EMU.get_mut();
let emu = emu_any.downcast_mut::<emu::Emu<T>>().expect("Failed to downcast EMU");
gdb_inner.incoming_data(emu.borrow_mut(), byte)?
}
GdbStubStateMachine::Running(_) => break,
GdbStubStateMachine::CtrlCInterrupt(_) => break,
GdbStubStateMachine::Disconnected(gdb_inner) => return handle_disconnect(gdb_inner.get_reason()),
}
}
GDBSTUB.replace(Box::new(gdb));
Ok(())
}
fn handle_disconnect(reason: DisconnectReason) -> DynResult<()> {
EMU.take();
#[cfg(feature = "capi")]
capi::clean();
match reason {
DisconnectReason::Disconnect => {
println!("Disconnect!");
Ok(())
}
DisconnectReason::TargetExited(code) => {
println!("Target exited with code {}!", code);
Ok(())
}
DisconnectReason::TargetTerminated(sig) => {
println!("Target terminated with signal {}!", sig);
Ok(())
}
DisconnectReason::Kill => {
println!("GDB sent a kill command!");
Ok(())
}
}
}