| 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::*; |
| 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>; |
| } |
| |
| 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 ext { |
| $( |
| #[allow(non_camel_case_types)] |
| pub enum [<$ext:camel>] $(<$lt>)? { |
| $($command(super::$mod::$command<$($lifetime)?>),)* |
| } |
| )* |
| } |
| |
| /// GDB commands |
| pub enum Command<'a> { |
| $( |
| [<$ext:camel>](ext::[<$ext:camel>]$(<$lt>)?), |
| )* |
| Unknown(&'a str), |
| } |
| |
| impl<'a> Command<'a> { |
| pub fn from_packet( |
| target: &mut impl Target, |
| buf: PacketBuf<'a> |
| ) -> Result<Command<'a>, CommandParseError<'a>> { |
| if buf.as_body().is_empty() { |
| return Err(CommandParseError::Empty); |
| } |
| |
| let body = buf.as_body(); |
| |
| // This scoped extension trait enables using `base` as an |
| // `$ext`, even through the `base` method on `Target` doesn't |
| // return an Option. |
| trait Hack { fn base(&mut self) -> Option<()> { Some(()) } } |
| impl<T: Target> Hack for T {} |
| |
| $( |
| if target.$ext().is_some() { |
| // TODO?: use tries for more efficient longest prefix matching |
| #[allow(clippy::string_lit_as_bytes)] |
| match body { |
| $(_ if body.starts_with($name.as_bytes()) => { |
| crate::__dead_code_marker!($name, "prefix_match"); |
| |
| let buf = buf.trim_start_body_bytes($name.len()); |
| let cmd = $mod::$command::from_packet(buf) |
| .ok_or(CommandParseError::MalformedCommand($name))?; |
| |
| return Ok( |
| Command::[<$ext:camel>]( |
| ext::[<$ext:camel>]::$command(cmd) |
| ) |
| ) |
| })* |
| _ => {}, |
| } |
| } |
| )* |
| |
| Ok(Command::Unknown(buf.into_body_str())) |
| } |
| } |
| }}; |
| } |
| |
| /// Command parse error |
| // TODO?: add more granular errors to command parsing code |
| pub enum CommandParseError<'a> { |
| Empty, |
| /// catch-all |
| MalformedCommand(&'a str), |
| } |
| |
| 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>, |
| "p" => _p::p, |
| "P" => _p_upcase::P<'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, |
| "z" => _z::z<'a>, |
| "Z" => _z_upcase::Z<'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, |
| } |
| } |