1 use paste::paste; 2 3 use crate::protocol::packet::PacketBuf; 4 use crate::target::Target; 5 6 pub(self) mod prelude { 7 pub use super::ParseCommand; 8 pub use crate::common::*; 9 pub use crate::protocol::common::*; 10 pub use crate::protocol::packet::PacketBuf; 11 pub use core::convert::{TryFrom, TryInto}; 12 } 13 14 pub trait ParseCommand<'a>: Sized { 15 /// Try to parse a packet from the packet buffer. from_packet(buf: PacketBuf<'a>) -> Option<Self>16 fn from_packet(buf: PacketBuf<'a>) -> Option<Self>; 17 } 18 19 macro_rules! commands { 20 ( 21 $( 22 $ext:ident $(use $lt:lifetime)? { 23 $($name:literal => $mod:ident::$command:ident$(<$lifetime:lifetime>)?,)* 24 } 25 )* 26 ) => {paste! { 27 $($( 28 #[allow(non_snake_case, non_camel_case_types)] 29 pub mod $mod; 30 )*)* 31 32 pub mod ext { 33 $( 34 #[allow(non_camel_case_types)] 35 pub enum [<$ext:camel>] $(<$lt>)? { 36 $($command(super::$mod::$command<$($lifetime)?>),)* 37 } 38 )* 39 } 40 41 /// GDB commands 42 pub enum Command<'a> { 43 $( 44 [<$ext:camel>](ext::[<$ext:camel>]$(<$lt>)?), 45 )* 46 Unknown(&'a str), 47 } 48 49 impl<'a> Command<'a> { 50 pub fn from_packet( 51 target: &mut impl Target, 52 buf: PacketBuf<'a> 53 ) -> Result<Command<'a>, CommandParseError<'a>> { 54 if buf.as_body().is_empty() { 55 return Err(CommandParseError::Empty); 56 } 57 58 let body = buf.as_body(); 59 60 // This scoped extension trait enables using `base` as an 61 // `$ext`, even through the `base` method on `Target` doesn't 62 // return an Option. 63 trait Hack { fn base(&mut self) -> Option<()> { Some(()) } } 64 impl<T: Target> Hack for T {} 65 66 $( 67 if target.$ext().is_some() { 68 // TODO?: use tries for more efficient longest prefix matching 69 #[allow(clippy::string_lit_as_bytes)] 70 match body { 71 $(_ if body.starts_with($name.as_bytes()) => { 72 crate::__dead_code_marker!($name, "prefix_match"); 73 74 let buf = buf.trim_start_body_bytes($name.len()); 75 let cmd = $mod::$command::from_packet(buf) 76 .ok_or(CommandParseError::MalformedCommand($name))?; 77 78 return Ok( 79 Command::[<$ext:camel>]( 80 ext::[<$ext:camel>]::$command(cmd) 81 ) 82 ) 83 })* 84 _ => {}, 85 } 86 } 87 )* 88 89 Ok(Command::Unknown(buf.into_body_str())) 90 } 91 } 92 }}; 93 } 94 95 /// Command parse error 96 // TODO?: add more granular errors to command parsing code 97 pub enum CommandParseError<'a> { 98 Empty, 99 /// catch-all 100 MalformedCommand(&'a str), 101 } 102 103 commands! { 104 base use 'a { 105 "?" => question_mark::QuestionMark, 106 "c" => _c::c<'a>, 107 "D" => _d_upcase::D, 108 "g" => _g::g, 109 "G" => _g_upcase::G<'a>, 110 "H" => _h_upcase::H, 111 "k" => _k::k, 112 "m" => _m::m<'a>, 113 "M" => _m_upcase::M<'a>, 114 "p" => _p::p, 115 "P" => _p_upcase::P<'a>, 116 "qAttached" => _qAttached::qAttached, 117 "qfThreadInfo" => _qfThreadInfo::qfThreadInfo, 118 "QStartNoAckMode" => _QStartNoAckMode::QStartNoAckMode, 119 "qsThreadInfo" => _qsThreadInfo::qsThreadInfo, 120 "qSupported" => _qSupported::qSupported<'a>, 121 "qXfer:features:read" => _qXfer_features_read::qXferFeaturesRead, 122 "s" => _s::s<'a>, 123 "T" => _t_upcase::T, 124 "vCont" => _vCont::vCont<'a>, 125 "vKill" => _vKill::vKill, 126 "z" => _z::z<'a>, 127 "Z" => _z_upcase::Z<'a>, 128 } 129 130 extended_mode use 'a { 131 "!" => exclamation_mark::ExclamationMark, 132 "QDisableRandomization" => _QDisableRandomization::QDisableRandomization, 133 "QEnvironmentHexEncoded" => _QEnvironmentHexEncoded::QEnvironmentHexEncoded<'a>, 134 "QEnvironmentReset" => _QEnvironmentReset::QEnvironmentReset, 135 "QEnvironmentUnset" => _QEnvironmentUnset::QEnvironmentUnset<'a>, 136 "QSetWorkingDir" => _QSetWorkingDir::QSetWorkingDir<'a>, 137 "QStartupWithShell" => _QStartupWithShell::QStartupWithShell, 138 "R" => _r_upcase::R, 139 "vAttach" => _vAttach::vAttach, 140 "vRun" => _vRun::vRun<'a>, 141 } 142 143 monitor_cmd use 'a { 144 "qRcmd" => _qRcmd::qRcmd<'a>, 145 } 146 147 section_offsets { 148 "qOffsets" => _qOffsets::qOffsets, 149 } 150 } 151