| use paste::paste; |
| |
| use crate::protocol::packet::PacketBuf; |
| use crate::target::Target; |
| |
| pub(self) mod prelude { |
| pub use super::ParseCommand; |
| pub use crate::common::*; |
| pub use crate::protocol::common::hex::{decode_hex, decode_hex_buf, is_hex, HexString}; |
| pub use crate::protocol::common::thread_id::{ |
| IdKind, SpecificIdKind, SpecificThreadId, ThreadId, |
| }; |
| pub use crate::protocol::common::Bstr; |
| pub use crate::protocol::packet::PacketBuf; |
| pub use core::convert::{TryFrom, TryInto}; |
| } |
| |
| pub trait ParseCommand<'a>: Sized { |
| /// Try to parse a packet from the packet buffer. |
| fn from_packet(buf: PacketBuf<'a>) -> Option<Self>; |
| } |
| |
| // Breakpoint packets are special-cased, as the "Z" packet is parsed differently |
| // depending on whether or not the target implements the `Agent` extension. |
| // |
| // While it's entirely possible to eagerly parse the "Z" packet for bytecode, |
| // doing so would unnecessary bloat implementations that do not support |
| // evaluating agent expressions. |
| |
| macro_rules! commands { |
| ( |
| $( |
| $ext:ident $(use $lt:lifetime)? { |
| $($name:literal => $mod:ident::$command:ident$(<$lifetime:lifetime>)?,)* |
| } |
| )* |
| ) => {paste! { |
| $($( |
| #[allow(non_snake_case, non_camel_case_types)] |
| pub mod $mod; |
| )*)* |
| pub mod breakpoint; |
| |
| pub mod ext { |
| $( |
| #[allow(non_camel_case_types)] |
| pub enum [<$ext:camel>] $(<$lt>)? { |
| $($command(super::$mod::$command<$($lifetime)?>),)* |
| } |
| )* |
| |
| use super::breakpoint::{BasicBreakpoint, BytecodeBreakpoint}; |
| #[allow(non_camel_case_types)] |
| pub enum Breakpoints<'a> { |
| z(BasicBreakpoint<'a>), |
| Z(BasicBreakpoint<'a>), |
| ZWithBytecode(BytecodeBreakpoint<'a>), |
| } |
| |
| } |
| |
| /// GDB commands |
| pub enum Command<'a> { |
| $( |
| [<$ext:camel>](ext::[<$ext:camel>]$(<$lt>)?), |
| )* |
| Breakpoints(ext::Breakpoints<'a>), |
| Unknown(&'a [u8]), |
| } |
| |
| impl<'a> Command<'a> { |
| pub fn from_packet( |
| target: &mut impl Target, |
| mut buf: PacketBuf<'a> |
| ) -> Option<Command<'a>> { |
| // HACK: this locally-scoped trait enables using identifiers |
| // that aren't top-level `Target` IDETs to split-up the packet |
| // parsing code. |
| trait Hack { |
| fn base(&mut self) -> Option<()>; |
| fn single_register_access(&mut self) -> Option<()>; |
| fn reverse_step(&mut self) -> Option<()>; |
| fn reverse_cont(&mut self) -> Option<()>; |
| } |
| |
| impl<T: Target> Hack for T { |
| fn base(&mut self) -> Option<()> { |
| Some(()) |
| } |
| |
| fn single_register_access(&mut self) -> Option<()> { |
| use crate::target::ext::base::BaseOps; |
| match self.base_ops() { |
| BaseOps::SingleThread(ops) => ops.single_register_access().map(drop), |
| BaseOps::MultiThread(ops) => ops.single_register_access().map(drop), |
| } |
| } |
| |
| fn reverse_step(&mut self) -> Option<()> { |
| use crate::target::ext::base::BaseOps; |
| match self.base_ops() { |
| BaseOps::SingleThread(ops) => ops.support_reverse_step().map(drop), |
| BaseOps::MultiThread(ops) => ops.support_reverse_step().map(drop), |
| } |
| } |
| |
| fn reverse_cont(&mut self) -> Option<()> { |
| use crate::target::ext::base::BaseOps; |
| match self.base_ops() { |
| BaseOps::SingleThread(ops) => ops.support_reverse_cont().map(drop), |
| BaseOps::MultiThread(ops) => ops.support_reverse_cont().map(drop), |
| } |
| } |
| } |
| |
| // TODO?: use tries for more efficient longest prefix matching |
| |
| $( |
| #[allow(clippy::string_lit_as_bytes)] |
| if target.$ext().is_some() { |
| $( |
| if buf.strip_prefix($name.as_bytes()) { |
| crate::__dead_code_marker!($name, "prefix_match"); |
| |
| let cmd = $mod::$command::from_packet(buf)?; |
| |
| return Some( |
| Command::[<$ext:camel>]( |
| ext::[<$ext:camel>]::$command(cmd) |
| ) |
| ) |
| } |
| )* |
| } |
| )* |
| |
| if let Some(_breakpoint_ops) = target.breakpoints() { |
| use breakpoint::{BasicBreakpoint, BytecodeBreakpoint}; |
| |
| if buf.strip_prefix(b"z") { |
| let cmd = BasicBreakpoint::from_slice(buf.into_body())?; |
| return Some(Command::Breakpoints(ext::Breakpoints::z(cmd))) |
| } |
| |
| if buf.strip_prefix(b"Z") { |
| // TODO: agent bytecode currently unimplemented |
| if true { |
| let cmd = BasicBreakpoint::from_slice(buf.into_body())?; |
| return Some(Command::Breakpoints(ext::Breakpoints::Z(cmd))) |
| } else { |
| let cmd = BytecodeBreakpoint::from_slice(buf.into_body())?; |
| return Some(Command::Breakpoints(ext::Breakpoints::ZWithBytecode(cmd))) |
| } |
| } |
| } |
| |
| Some(Command::Unknown(buf.into_body())) |
| } |
| } |
| }}; |
| } |
| |
| commands! { |
| base use 'a { |
| "?" => question_mark::QuestionMark, |
| "c" => _c::c<'a>, |
| "D" => _d_upcase::D, |
| "g" => _g::g, |
| "G" => _g_upcase::G<'a>, |
| "H" => _h_upcase::H, |
| "k" => _k::k, |
| "m" => _m::m<'a>, |
| "M" => _m_upcase::M<'a>, |
| "qAttached" => _qAttached::qAttached, |
| "qfThreadInfo" => _qfThreadInfo::qfThreadInfo, |
| "QStartNoAckMode" => _QStartNoAckMode::QStartNoAckMode, |
| "qsThreadInfo" => _qsThreadInfo::qsThreadInfo, |
| "qSupported" => _qSupported::qSupported<'a>, |
| "qXfer:features:read" => _qXfer_features_read::qXferFeaturesRead, |
| "s" => _s::s<'a>, |
| "T" => _t_upcase::T, |
| "vCont" => _vCont::vCont<'a>, |
| "vKill" => _vKill::vKill, |
| } |
| |
| single_register_access use 'a { |
| "p" => _p::p, |
| "P" => _p_upcase::P<'a>, |
| } |
| |
| extended_mode use 'a { |
| "!" => exclamation_mark::ExclamationMark, |
| "QDisableRandomization" => _QDisableRandomization::QDisableRandomization, |
| "QEnvironmentHexEncoded" => _QEnvironmentHexEncoded::QEnvironmentHexEncoded<'a>, |
| "QEnvironmentReset" => _QEnvironmentReset::QEnvironmentReset, |
| "QEnvironmentUnset" => _QEnvironmentUnset::QEnvironmentUnset<'a>, |
| "QSetWorkingDir" => _QSetWorkingDir::QSetWorkingDir<'a>, |
| "QStartupWithShell" => _QStartupWithShell::QStartupWithShell, |
| "R" => _r_upcase::R, |
| "vAttach" => _vAttach::vAttach, |
| "vRun" => _vRun::vRun<'a>, |
| } |
| |
| monitor_cmd use 'a { |
| "qRcmd" => _qRcmd::qRcmd<'a>, |
| } |
| |
| section_offsets { |
| "qOffsets" => _qOffsets::qOffsets, |
| } |
| |
| reverse_cont { |
| "bc" => _bc::bc, |
| } |
| |
| reverse_step { |
| "bs" => _bs::bs, |
| } |
| } |