gdbstub 0.7.10

An implementation of the GDB Remote Serial Protocol in Rust
Documentation
use crate::protocol::packet::PacketBuf;
use crate::target::Target;
use pastey::paste;

/// Common imports used by >50% of all packet parsers.
///
/// Do not clutter this prelude with types only used by a few packets.
pub mod prelude {
    pub use crate::protocol::commands::ParseCommand;
    pub use crate::protocol::common::hex::decode_hex;
    pub use crate::protocol::common::hex::decode_hex_buf;
    pub use crate::protocol::packet::PacketBuf;
    pub use core::convert::TryFrom;
    pub use core::convert::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! {
        // Most packets follow a consistent model of "only enabled when a
        // particular IDET is implemented", but there are some exceptions to
        // this rule that need to be special-cased:
        //
        // # Breakpoint packets (z, Z)
        //
        // 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.


        $($(
            #[allow(non_snake_case, non_camel_case_types)]
            pub mod $mod;
        )*)*
        pub mod breakpoint;

        pub mod ext {
            $(
                #[allow(non_camel_case_types, clippy::enum_variant_names, clippy::upper_case_acronyms)]
                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>),
                // Bytecode hasn't yet been plumbed all the way through
                #[allow(dead_code)]
                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 support_base(&mut self) -> Option<()>;
                    fn support_target_xml(&mut self) -> Option<()>;
                    fn support_lldb_register_info(&mut self) -> Option<()>;
                    fn support_resume(&mut self) -> Option<()>;
                    fn support_single_register_access(&mut self) -> Option<()>;
                    fn support_reverse_step(&mut self) -> Option<()>;
                    fn support_reverse_cont(&mut self) -> Option<()>;
                    fn support_no_ack_mode(&mut self) -> Option<()>;
                    fn support_x_upcase_packet(&mut self) -> Option<()>;
                    fn support_thread_extra_info(&mut self) -> Option<()>;
                }

                impl<T: Target> Hack for T {
                    fn support_base(&mut self) -> Option<()> {
                        Some(())
                    }

                    fn support_target_xml(&mut self) -> Option<()> {
                        use crate::arch::Arch;
                        if self.use_target_description_xml()
                            && (T::Arch::target_description_xml().is_some()
                                || self.support_target_description_xml_override().is_some())
                        {
                            Some(())
                        } else {
                            None
                        }
                    }

                    fn support_lldb_register_info(&mut self) -> Option<()> {
                        use crate::arch::Arch;
                        if self.use_lldb_register_info()
                            && (T::Arch::lldb_register_info(usize::MAX).is_some()
                                || self.support_lldb_register_info_override().is_some())
                        {
                            Some(())
                        } else {
                            None
                        }
                    }

                    fn support_resume(&mut self) -> Option<()> {
                        self.base_ops().resume_ops().map(drop)
                    }

                    fn support_single_register_access(&mut self) -> Option<()> {
                        use crate::target::ext::base::BaseOps;
                        match self.base_ops() {
                            BaseOps::SingleThread(ops) => ops.support_single_register_access().map(drop),
                            BaseOps::MultiThread(ops) => ops.support_single_register_access().map(drop),
                        }
                    }

                    fn support_reverse_step(&mut self) -> Option<()> {
                        use crate::target::ext::base::ResumeOps;
                        match self.base_ops().resume_ops()? {
                            ResumeOps::SingleThread(ops) => ops.support_reverse_step().map(drop),
                            ResumeOps::MultiThread(ops) => ops.support_reverse_step().map(drop),
                        }
                    }

                    fn support_reverse_cont(&mut self) -> Option<()> {
                        use crate::target::ext::base::ResumeOps;
                        match self.base_ops().resume_ops()? {
                            ResumeOps::SingleThread(ops) => ops.support_reverse_cont().map(drop),
                            ResumeOps::MultiThread(ops) => ops.support_reverse_cont().map(drop),
                        }
                    }

                    fn support_x_upcase_packet(&mut self) -> Option<()> {
                        if self.use_x_upcase_packet() {
                            Some(())
                        } else {
                            None
                        }
                    }

                    fn support_no_ack_mode(&mut self) -> Option<()> {
                        if self.use_no_ack_mode() {
                            Some(())
                        } else {
                            None
                        }
                    }

                    fn support_thread_extra_info(&mut self) -> Option<()> {
                        use crate::target::ext::base::BaseOps;
                        match self.base_ops() {
                            BaseOps::SingleThread(_) => None,
                            BaseOps::MultiThread(ops) => ops.support_thread_extra_info().map(drop),
                        }
                    }
                }

                // TODO?: use tries for more efficient longest prefix matching

                $(
                #[allow(clippy::string_lit_as_bytes)]
                if target.[< support_ $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.support_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,
        "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,
        "qsThreadInfo" => _qsThreadInfo::qsThreadInfo,
        "qSupported" => _qSupported::qSupported<'a>,
        "T" => _t_upcase::T,
        "vKill" => _vKill::vKill,
    }

    target_xml use 'a {
        "qXfer:features:read" => _qXfer_features_read::qXferFeaturesRead<'a>,
    }

    resume use 'a {
        "c" => _c::c<'a>,
        "s" => _s::s<'a>,
        "vCont" => _vCont::vCont<'a>,
    }

    x_upcase_packet use 'a {
        "X" => _x_upcase::X<'a>,
    }

    no_ack_mode {
        "QStartNoAckMode" => _QStartNoAckMode::QStartNoAckMode,
    }

    single_register_access use 'a {
        "p" => _p::p<'a>,
        "P" => _p_upcase::P<'a>,
    }

    extended_mode use 'a {
        "!" => exclamation_mark::ExclamationMark,
        "qC" => _qC::qC,
        "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,
    }

    memory_map use 'a {
        "qXfer:memory-map:read" => _qXfer_memory_map::qXferMemoryMapRead<'a>,
    }

    flash_operations use 'a {
        "vFlashErase" => _vFlashErase::vFlashErase<'a>,
        "vFlashWrite" => _vFlashWrite::vFlashWrite<'a>,
        "vFlashDone" => _vFlashDone::vFlashDone,
    }

    auxv use 'a {
        "qXfer:auxv:read" => _qXfer_auxv_read::qXferAuxvRead<'a>,
    }

    exec_file use 'a {
        "qXfer:exec-file:read" => _qXfer_exec_file::qXferExecFileRead<'a>,
    }

    host_io use 'a {
        "vFile:open" => _vFile_open::vFileOpen<'a>,
        "vFile:close" => _vFile_close::vFileClose,
        "vFile:pread" => _vFile_pread::vFilePread<'a>,
        "vFile:pwrite" => _vFile_pwrite::vFilePwrite<'a>,
        "vFile:fstat" => _vFile_fstat::vFileFstat,
        "vFile:unlink" => _vFile_unlink::vFileUnlink<'a>,
        "vFile:readlink" => _vFile_readlink::vFileReadlink<'a>,
        "vFile:setfs" => _vFile_setfs::vFileSetfs,
    }

    catch_syscalls use 'a {
        "QCatchSyscalls" => _QCatchSyscalls::QCatchSyscalls<'a>,
    }

    thread_extra_info use 'a {
        "qThreadExtraInfo" => _qThreadExtraInfo::qThreadExtraInfo<'a>,
    }

    lldb_register_info {
        "qRegisterInfo" => _qRegisterInfo::qRegisterInfo,
    }

    libraries_svr4 use 'a {
        "qXfer:libraries-svr4:read" => _qXfer_libraries_svr4_read::qXferLibrariesSvr4Read<'a>,
    }

    libraries use 'a {
        "qXfer:libraries:read" => _qXfer_libraries_read::qXferLibrariesRead<'a>,
    }

    tracepoints use 'a {
        "QTDPsrc" => _QTDPsrc::QTDPsrc<'a>,
        "QTDP" => _QTDP::QTDP<'a>,
        "QTinit" => _QTinit::QTinit,
        "QTBuffer" => _QTBuffer_upcase::QTBuffer,
        "QTStart" => _QTStart::QTStart,
        "QTStop" => _QTStop::QTStop,
        "QTFrame" => _QTFrame::QTFrame<'a>,

        "qTBuffer" => _qTBuffer::qTBuffer,
        "qTStatus" => _qTStatus::qTStatus,
        "qTP" => _qTP::qTP<'a>,
        "qTfP" => _qTfP::qTfP,
        "qTsP" => _qTsP::qTsP,

        // QTDV is unimplemented.
        "qTfV" => _qTfV::qTfV,
        "qTsV" => _qTsV::qTsV,
    }

    host_info {
        "qHostInfo" => _qHostInfo::qHostInfo,
    }

    process_info {
        "qProcessInfo" => _qProcessInfo::qProcessInfo,
    }

    wasm use 'a {
        "qWasmCallStack" => _qWasmCallStack::qWasmCallStack,
        "qWasmLocal" => _qWasmLocal::qWasmLocal<'a>,
        "qWasmGlobal" => _qWasmGlobal::qWasmGlobal<'a>,
        "qWasmStackValue" => _qWasmStackValue::qWasmStackValue<'a>,
    }
}