use core::marker::PhantomData;
use managed::ManagedSlice;
use crate::{
arch::{Arch, RegId, Registers},
connection::Connection,
internal::*,
protocol::{Command, ConsoleOutput, Packet, ResponseWriter, Tid, TidSelector},
target::{Actions, BreakOp, ResumeAction, StopReason, Target, WatchKind},
util::managed_vec::ManagedVec,
};
mod builder;
mod error;
pub use builder::{GdbStubBuilder, GdbStubBuilderError};
pub use error::GdbStubError;
use GdbStubError as Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DisconnectReason {
TargetHalted,
Disconnect,
Kill,
}
pub struct GdbStub<'a, T: Target, C: Connection> {
conn: C,
packet_buffer: ManagedSlice<'a, u8>,
state: GdbStubImpl<T, C>,
}
impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> {
pub fn builder(conn: C) -> GdbStubBuilder<'a, T, C> {
GdbStubBuilder::new(conn)
}
#[cfg(feature = "alloc")]
pub fn new(conn: C) -> GdbStub<'a, T, C> {
GdbStubBuilder::new(conn).build().unwrap()
}
pub fn run<'b>(
&mut self,
target: &'b mut T,
) -> Result<DisconnectReason, Error<T::Error, C::Error>> {
self.state
.run(target, &mut self.conn, &mut self.packet_buffer)
}
}
struct GdbStubImpl<T: Target, C: Connection> {
_target: PhantomData<T>,
_connection: PhantomData<C>,
packet_buffer_len: usize,
current_tid: Tid,
multithread: bool,
}
impl<T: Target, C: Connection> GdbStubImpl<T, C> {
fn new(packet_buffer_len: usize) -> GdbStubImpl<T, C> {
GdbStubImpl {
_target: PhantomData,
_connection: PhantomData,
packet_buffer_len,
current_tid: Tid {
pid: None,
tid: TidSelector::Any,
},
multithread: false,
}
}
fn run(
&mut self,
target: &mut T,
conn: &mut C,
packet_buffer: &mut ManagedSlice<u8>,
) -> Result<DisconnectReason, Error<T::Error, C::Error>> {
loop {
match Self::recv_packet(conn, packet_buffer)? {
Packet::Ack => {}
Packet::Nack => {
unimplemented!("GDB nack'd the packet, but retry isn't implemented yet")
}
Packet::Interrupt => {
debug!("<-- interrupt packet");
let mut res = ResponseWriter::new(conn);
res.write_str("S05")?;
res.flush()?;
}
Packet::Command(command) => {
conn.write(b'+').map_err(Error::ConnectionRead)?;
let mut res = ResponseWriter::new(conn);
let disconnect = match self.handle_command(&mut res, target, command) {
Ok(reason) => reason,
Err(Error::TargetError(e)) => {
let mut res = ResponseWriter::new(conn);
res.write_str("T06")?;
res.flush()?;
return Err(Error::TargetError(e));
}
Err(e) => return Err(e),
};
if disconnect != Some(DisconnectReason::Kill) {
res.flush()?;
}
if let Some(disconnect_reason) = disconnect {
return Ok(disconnect_reason);
}
}
};
}
}
fn recv_packet<'a, 'b>(
conn: &mut C,
pkt_buf: &'a mut ManagedSlice<'b, u8>,
) -> Result<Packet<'a>, Error<T::Error, C::Error>> {
let header_byte = conn.read().map_err(Error::ConnectionRead)?;
let mut buf = ManagedVec::new(pkt_buf);
buf.clear();
buf.push(header_byte)?;
if header_byte == b'$' {
loop {
let c = conn.read().map_err(Error::ConnectionRead)?;
buf.push(c)?;
if c == b'#' {
break;
}
}
buf.push(conn.read().map_err(Error::ConnectionRead)?)?;
buf.push(conn.read().map_err(Error::ConnectionRead)?)?;
}
drop(buf);
let len = pkt_buf.len();
match Packet::from_buf(&mut pkt_buf.as_mut()[..len]) {
Ok(packet) => Ok(packet),
Err(e) => {
error!("Could not parse packet: {:?}", e);
Err(Error::PacketParse)
}
}
}
fn handle_command(
&mut self,
res: &mut ResponseWriter<C>,
target: &mut T,
command: Command<'_>,
) -> Result<Option<DisconnectReason>, Error<T::Error, C::Error>> {
match command {
Command::qSupported(cmd) => {
let _features = cmd.features.into_iter();
res.write_str("PacketSize=")?;
res.write_num(self.packet_buffer_len)?;
res.write_str(";vContSupported+")?;
res.write_str(";multiprocess+")?;
res.write_str(";swbreak+")?;
let mut supports_hwbreak = false;
let test_addr = num_traits::zero();
if target
.update_hw_breakpoint(test_addr, BreakOp::Add)
.maybe_missing_impl()?
.is_some()
{
let _ = target.update_hw_breakpoint(test_addr, BreakOp::Remove);
supports_hwbreak = true;
}
if (target.update_hw_watchpoint(test_addr, BreakOp::Add, WatchKind::Write))
.maybe_missing_impl()?
.is_some()
{
let _ =
target.update_hw_watchpoint(test_addr, BreakOp::Remove, WatchKind::Write);
supports_hwbreak = true;
}
if supports_hwbreak {
res.write_str(";hwbreak+")?;
}
if T::Arch::target_description_xml().is_some() {
res.write_str(";qXfer:features:read+")?;
}
}
Command::vContQuestionMark(_) => res.write_str("vCont;c;s")?,
Command::qXferFeaturesRead(cmd) => {
assert_eq!(cmd.annex, "target.xml");
match T::Arch::target_description_xml() {
Some(xml) => {
let xml = xml.trim();
if cmd.offset >= xml.len() {
res.write_str("l")?;
} else if cmd.offset + cmd.len >= xml.len() {
res.write_str("l")?;
res.write_binary(&xml.as_bytes()[cmd.offset..])?
} else {
res.write_str("m")?;
res.write_binary(&xml.as_bytes()[cmd.offset..(cmd.offset + cmd.len)])?
}
}
None => return Err(Error::PacketUnexpected),
}
}
Command::QuestionMark(_) => res.write_str("S05")?,
Command::qAttached(_) => res.write_str("1")?,
Command::g(_) => {
if self.multithread {
if let TidSelector::WithID(tid) = self.current_tid.tid {
target
.set_current_thread(tid)
.maybe_missing_impl()?
.ok_or(Error::MissingSetCurrentTid)?
}
}
let mut regs: <T::Arch as Arch>::Registers = Default::default();
target
.read_registers(&mut regs)
.map_err(Error::TargetError)?;
let mut err = Ok(());
regs.gdb_serialize(|val| {
let res = match val {
Some(b) => res.write_hex_buf(&[b]),
None => res.write_str("xx"),
};
if let Err(e) = res {
err = Err(e);
}
});
err?;
}
Command::G(cmd) => {
if self.multithread {
if let TidSelector::WithID(tid) = self.current_tid.tid {
target
.set_current_thread(tid)
.maybe_missing_impl()?
.ok_or(Error::MissingSetCurrentTid)?
}
}
let mut regs: <T::Arch as Arch>::Registers = Default::default();
regs.gdb_deserialize(cmd.vals)
.map_err(|_| Error::PacketParse)?;
target.write_registers(®s).map_err(Error::TargetError)?;
res.write_str("OK")?;
}
Command::m(cmd) => {
let buf = cmd.buf;
let mut i = 0;
let mut n = cmd.len;
while n != 0 {
let chunk_size = n.min(buf.len());
let start_addr = Self::to_target_usize(cmd.addr + i as u64)?;
let data = &mut buf[..chunk_size];
let success = target
.read_addrs(start_addr, data)
.map_err(Error::TargetError)?;
if !success {
debug!("invalid memory read!");
break;
}
n -= chunk_size;
i += chunk_size;
res.write_hex_buf(data)?;
}
}
Command::M(cmd) => {
let success = target
.write_addrs(Self::to_target_usize(cmd.addr)?, cmd.val)
.map_err(Error::TargetError)?;
if !success {
res.write_str("E14")?
} else {
res.write_str("OK")?;
}
}
Command::k(_) | Command::vKill(_) => {
return Ok(Some(DisconnectReason::Kill));
}
Command::D(_) => {
res.write_str("OK")?;
return Ok(Some(DisconnectReason::Disconnect));
}
Command::Z(cmd) => {
let addr = Self::to_target_usize(cmd.addr)?;
use BreakOp::*;
let supported = match cmd.type_ {
0 => target
.update_sw_breakpoint(addr, Add)
.map(|_| true)
.map_err(MaybeUnimpl::error),
1 => target.update_hw_breakpoint(addr, Add),
2 => target.update_hw_watchpoint(addr, Add, WatchKind::Write),
3 => target.update_hw_watchpoint(addr, Add, WatchKind::Read),
4 => target.update_hw_watchpoint(addr, Add, WatchKind::ReadWrite),
_ => Err(MaybeUnimpl::no_impl()),
}
.maybe_missing_impl()?;
match supported {
None => {}
Some(true) => res.write_str("OK")?,
Some(false) => res.write_str("E22")?,
}
}
Command::z(cmd) => {
let addr = Self::to_target_usize(cmd.addr)?;
use BreakOp::*;
let supported = match cmd.type_ {
0 => target
.update_sw_breakpoint(addr, Remove)
.map(|_| true)
.map_err(MaybeUnimpl::error),
1 => target.update_hw_breakpoint(addr, Remove),
2 => target.update_hw_watchpoint(addr, Remove, WatchKind::Write),
3 => target.update_hw_watchpoint(addr, Remove, WatchKind::Read),
4 => target.update_hw_watchpoint(addr, Remove, WatchKind::ReadWrite),
_ => Err(MaybeUnimpl::no_impl()),
}
.maybe_missing_impl()?;
match supported {
None => {}
Some(true) => res.write_str("OK")?,
Some(false) => res.write_str("E22")?,
}
}
Command::p(p) => {
let mut dst = [0u8; 16];
let reg = <<T::Arch as Arch>::Registers as Registers>::RegId::from_raw_id(p.reg_id);
let (reg_id, reg_size) = match reg {
Some(v) => v,
None => return Ok(None),
};
let dst = &mut dst[0..reg_size];
target.read_register(reg_id, dst).maybe_missing_impl()?;
res.write_hex_buf(dst)?;
}
Command::P(p) => {
let reg = <<T::Arch as Arch>::Registers as Registers>::RegId::from_raw_id(p.reg_id);
let supported = match reg {
Some((reg_id, _)) => target
.write_register(reg_id, p.val)
.maybe_missing_impl()?
.is_some(),
None => false,
};
if supported {
res.write_str("OK")?;
} else {
res.write_str("E01")?;
}
}
Command::vCont(cmd) => {
use crate::protocol::_vCont::VContKind;
let mut err = Ok(());
let mut actions = cmd.actions.into_iter().filter_map(|action| {
let action = match action {
Ok(action) => action,
Err(e) => {
err = Err(e);
return None;
}
};
let resume_action = match action.kind {
VContKind::Step => ResumeAction::Step,
VContKind::Continue => ResumeAction::Continue,
_ => unimplemented!("unimplemented vCont action {:?}", action.kind),
};
let tid = match action.tid {
Some(tid) => tid.tid,
None => TidSelector::Any,
};
Some((tid, resume_action))
});
let ret = self.do_vcont(res, target, &mut actions);
err.map_err(|_| Error::PacketParse)?;
return ret;
}
Command::c(_) => {
return self.do_vcont(
res,
target,
&mut core::iter::once((self.current_tid.tid, ResumeAction::Continue)),
);
}
Command::s(_) => {
return self.do_vcont(
res,
target,
&mut core::iter::once((self.current_tid.tid, ResumeAction::Step)),
);
}
Command::H(cmd) => {
self.current_tid = cmd.tid;
match self.current_tid.tid {
TidSelector::WithID(id) => {
target.set_current_thread(id).maybe_missing_impl()?;
}
TidSelector::Any => {}
TidSelector::All => {}
}
res.write_str("OK")?
}
Command::qfThreadInfo(_) => {
res.write_str("m")?;
let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
let mut first = true;
target
.list_active_threads(&mut |tid| {
let e = (|| {
if !first {
self.multithread = true;
res.write_str(",")?
}
first = false;
res.write_num(tid.get())?;
Ok(())
})();
if let Err(e) = e {
err = Err(e)
}
})
.map_err(Error::TargetError)?;
err?;
}
Command::qsThreadInfo(_) => res.write_str("l")?,
Command::qC(_) => {
res.write_str("QC")?;
res.write_tid(self.current_tid)?;
}
Command::T(cmd) => {
let alive = match cmd.tid.tid {
TidSelector::WithID(tid) => target
.is_thread_alive(tid)
.maybe_missing_impl()?
.unwrap_or(true),
_ => unimplemented!(),
};
if alive {
res.write_str("OK")?;
} else {
res.write_str("E00")?;
}
}
Command::qRcmd(cmd) => {
let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
let mut callback = |msg: &[u8]| {
let e = (|| {
let mut res = ResponseWriter::new(res.as_conn());
res.write_str("O")?;
res.write_hex_buf(msg)?;
res.flush()?;
Ok(())
})();
if let Err(e) = e {
err = Err(e)
}
};
let supported = target
.handle_monitor_cmd(cmd.hex_cmd, ConsoleOutput::new(&mut callback))
.maybe_missing_impl()?;
err?;
if supported.is_some() {
res.write_str("OK")?
}
}
Command::Unknown(cmd) => info!("Unknown command: {}", cmd),
#[allow(unreachable_patterns)]
c => warn!("Unimplemented command: {:?}", c),
}
Ok(None)
}
fn do_vcont(
&mut self,
res: &mut ResponseWriter<C>,
target: &mut T,
actions: &mut dyn Iterator<Item = (TidSelector, ResumeAction)>,
) -> Result<Option<DisconnectReason>, Error<T::Error, C::Error>> {
let mut err = Ok(());
let (tid, stop_reason) = target
.resume(Actions::new(actions), &mut || match res.as_conn().peek() {
Ok(Some(0x03)) => true,
Ok(Some(_)) => false,
Ok(None) => false,
Err(e) => {
err = Err(Error::ConnectionRead(e));
true
}
})
.map_err(Error::TargetError)?;
err?;
self.current_tid.tid = TidSelector::WithID(tid);
target.set_current_thread(tid).maybe_missing_impl()?;
match stop_reason {
StopReason::DoneStep | StopReason::GdbInterrupt => {
res.write_str("S05")?;
Ok(None)
}
StopReason::Signal(code) => {
res.write_str("S")?;
res.write_num(code)?;
Ok(None)
}
StopReason::Halted => {
res.write_str("W00")?;
Ok(Some(DisconnectReason::TargetHalted))
}
stop_reason => {
res.write_str("T05")?;
res.write_str("thread:")?;
res.write_tid(self.current_tid)?;
res.write_str(";")?;
match stop_reason {
StopReason::SwBreak => res.write_str("swbreak:")?,
StopReason::HwBreak => res.write_str("hwbreak:")?,
StopReason::Watch { kind, addr } => {
match kind {
WatchKind::Write => res.write_str("watch:")?,
WatchKind::Read => res.write_str("rwatch:")?,
WatchKind::ReadWrite => res.write_str("awatch:")?,
}
res.write_num(addr)?;
}
_ => unreachable!(),
};
res.write_str(";")?;
Ok(None)
}
}
}
#[allow(clippy::wrong_self_convention, clippy::type_complexity)]
fn to_target_usize(
n: impl BeBytes,
) -> Result<<T::Arch as Arch>::Usize, Error<T::Error, C::Error>> {
let mut buf = [0; 16];
let len = n.to_be_bytes(&mut buf).ok_or(Error::PacketParse)?;
<T::Arch as Arch>::Usize::from_be_bytes(&buf[..len]).ok_or(Error::PacketParse)
}
}