use super::core_impl::FinishExecStatus;
use super::core_impl::GdbStubImpl;
use super::core_impl::State;
use super::DisconnectReason;
use super::GdbStub;
use crate::arch::Arch;
use crate::arch::RegId;
use crate::conn::Connection;
use crate::protocol::recv_packet::RecvPacketStateMachine;
use crate::protocol::Packet;
use crate::protocol::ResponseWriter;
use crate::stub::error::GdbStubError;
use crate::stub::error::InternalError;
use crate::stub::stop_reason::IntoStopReason;
use crate::stub::BaseStopReason;
use crate::target::Target;
use managed::ManagedSlice;
pub enum GdbStubStateMachine<'a, T, C>
where
T: Target,
C: Connection,
{
Idle(GdbStubStateMachineInner<'a, state::Idle<T>, T, C>),
Running(GdbStubStateMachineInner<'a, state::Running, T, C>),
CtrlCInterrupt(GdbStubStateMachineInner<'a, state::CtrlCInterrupt, T, C>),
Disconnected(GdbStubStateMachineInner<'a, state::Disconnected, T, C>),
}
pub mod state {
use super::*;
use crate::stub::stop_reason::MultiThreadStopReason;
pub(crate) const MODULE_PATH: &str = concat!(module_path!(), "::");
#[non_exhaustive]
pub struct Idle<T: Target> {
pub(crate) deferred_ctrlc_stop_reason:
Option<MultiThreadStopReason<<<T as Target>::Arch as Arch>::Usize>>,
}
#[non_exhaustive]
pub struct Running {}
#[non_exhaustive]
pub struct CtrlCInterrupt {
pub(crate) from_idle: bool,
}
#[non_exhaustive]
pub struct Disconnected {
pub(crate) reason: DisconnectReason,
}
}
macro_rules! impl_from_inner {
($state:ident $($tt:tt)*) => {
impl<'a, T, C> From<GdbStubStateMachineInner<'a, state::$state $($tt)*, T, C>>
for GdbStubStateMachine<'a, T, C>
where
T: Target,
C: Connection,
{
fn from(inner: GdbStubStateMachineInner<'a, state::$state $($tt)*, T, C>) -> Self {
GdbStubStateMachine::$state(inner)
}
}
};
}
impl_from_inner!(Idle<T>);
impl_from_inner!(Running);
impl_from_inner!(CtrlCInterrupt);
impl_from_inner!(Disconnected);
trait Transition<'a, T, C>
where
T: Target,
C: Connection,
{
fn transition<S2>(self, state: S2) -> GdbStubStateMachineInner<'a, S2, T, C>;
}
impl<'a, S1, T, C> Transition<'a, T, C> for GdbStubStateMachineInner<'a, S1, T, C>
where
T: Target,
C: Connection,
{
#[inline(always)]
fn transition<S2>(self, state: S2) -> GdbStubStateMachineInner<'a, S2, T, C> {
if log::log_enabled!(log::Level::Trace) {
let s1 = core::any::type_name::<S1>();
let s2 = core::any::type_name::<S2>();
log::trace!(
"transition: {:?} --> {:?}",
s1.strip_prefix(state::MODULE_PATH).unwrap_or(s1),
s2.strip_prefix(state::MODULE_PATH).unwrap_or(s2)
);
}
GdbStubStateMachineInner { i: self.i, state }
}
}
struct GdbStubStateMachineReallyInner<'a, T: Target, C: Connection> {
conn: C,
packet_buffer: ManagedSlice<'a, u8>,
recv_packet: RecvPacketStateMachine,
inner: GdbStubImpl<T, C>,
}
pub struct GdbStubStateMachineInner<'a, S, T: Target, C: Connection> {
i: GdbStubStateMachineReallyInner<'a, T, C>,
state: S,
}
impl<S, T: Target, C: Connection> GdbStubStateMachineInner<'_, S, T, C> {
pub fn borrow_conn(&mut self) -> &mut C {
&mut self.i.conn
}
}
impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Idle<T>, T, C> {
pub(crate) fn from_plain_gdbstub(
stub: GdbStub<'a, T, C>,
) -> GdbStubStateMachineInner<'a, state::Idle<T>, T, C> {
GdbStubStateMachineInner {
i: GdbStubStateMachineReallyInner {
conn: stub.conn,
packet_buffer: stub.packet_buffer,
recv_packet: RecvPacketStateMachine::new(),
inner: stub.inner,
},
state: state::Idle {
deferred_ctrlc_stop_reason: None,
},
}
}
pub fn incoming_data(
mut self,
target: &mut T,
byte: u8,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
let packet_buffer = match self.i.recv_packet.pump(&mut self.i.packet_buffer, byte)? {
Some(buf) => buf,
None => return Ok(self.into()),
};
let packet = Packet::from_buf(target, packet_buffer).map_err(InternalError::PacketParse)?;
let state = self
.i
.inner
.handle_packet(target, &mut self.i.conn, packet)?;
Ok(match state {
State::Pump => self.into(),
State::Disconnect(reason) => self.transition(state::Disconnected { reason }).into(),
State::DeferredStopReason => {
match self.state.deferred_ctrlc_stop_reason {
Some(reason) => {
return self
.transition(state::Running {})
.report_stop(target, reason)
}
None => self.transition(state::Running {}).into(),
}
}
State::CtrlCInterrupt => self
.transition(state::CtrlCInterrupt { from_idle: true })
.into(),
})
}
}
impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Running, T, C> {
pub fn report_stop(
self,
target: &mut T,
reason: impl IntoStopReason<T>,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
self.report_stop_impl(target, reason, None)
}
pub fn report_stop_with_regs(
self,
target: &mut T,
reason: impl IntoStopReason<T>,
regs: &mut dyn Iterator<Item = (<<T as Target>::Arch as Arch>::RegId, &[u8])>,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
self.report_stop_impl(target, reason, Some(regs))
}
fn report_stop_impl(
mut self,
target: &mut T,
reason: impl IntoStopReason<T>,
regs: Option<&mut dyn Iterator<Item = (<<T as Target>::Arch as Arch>::RegId, &[u8])>>,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
let reason: BaseStopReason<_, _> = reason.into();
let mut res = ResponseWriter::new(&mut self.i.conn, target.use_rle());
let event = self.i.inner.finish_exec(&mut res, target, reason)?;
if let Some(regs) = regs {
if reason.is_t_packet() {
for (reg_id, value) in regs {
let reg = reg_id.to_raw_id().ok_or(InternalError::MissingToRawId)?;
res.write_num(reg).map_err(InternalError::from)?;
res.write_str(":").map_err(InternalError::from)?;
res.write_hex_buf(value).map_err(InternalError::from)?;
res.write_str(";").map_err(InternalError::from)?;
}
}
}
res.flush().map_err(InternalError::from)?;
Ok(match event {
FinishExecStatus::Handled => self
.transition(state::Idle {
deferred_ctrlc_stop_reason: None,
})
.into(),
FinishExecStatus::Disconnect(reason) => {
self.transition(state::Disconnected { reason }).into()
}
})
}
pub fn incoming_data(
mut self,
target: &mut T,
byte: u8,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
let packet_buffer = match self.i.recv_packet.pump(&mut self.i.packet_buffer, byte)? {
Some(buf) => buf,
None => return Ok(self.into()),
};
let packet = Packet::from_buf(target, packet_buffer).map_err(InternalError::PacketParse)?;
let state = self
.i
.inner
.handle_packet(target, &mut self.i.conn, packet)?;
Ok(match state {
State::Pump => self.transition(state::Running {}).into(),
State::Disconnect(reason) => self.transition(state::Disconnected { reason }).into(),
State::DeferredStopReason => self.transition(state::Running {}).into(),
State::CtrlCInterrupt => self
.transition(state::CtrlCInterrupt { from_idle: false })
.into(),
})
}
}
impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::CtrlCInterrupt, T, C> {
pub fn interrupt_handled(
self,
target: &mut T,
stop_reason: Option<impl IntoStopReason<T>>,
) -> Result<GdbStubStateMachine<'a, T, C>, GdbStubError<T::Error, C::Error>> {
if self.state.from_idle {
Ok(self
.transition(state::Idle {
deferred_ctrlc_stop_reason: stop_reason.map(Into::into),
})
.into())
} else {
let gdb = self.transition(state::Running {});
match stop_reason {
Some(reason) => gdb.report_stop(target, reason),
None => Ok(gdb.into()),
}
}
}
}
impl<'a, T: Target, C: Connection> GdbStubStateMachineInner<'a, state::Disconnected, T, C> {
pub fn get_reason(&self) -> DisconnectReason {
self.state.reason
}
pub fn return_to_idle(self) -> GdbStubStateMachine<'a, T, C> {
self.transition(state::Idle {
deferred_ctrlc_stop_reason: None,
})
.into()
}
}