diff --git a/README.md b/README.md index 1ac63bdb..473a04d8 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Of course, most use-cases will want to support additional debugging features as - Configure tracepoints and actions to perform when hit - Select and interrogate collected trace frames - _Note:_ Feature support is not exhaustive, and many feature haven't been implemented yet. +- Flash operations (`load`) _Note:_ GDB features are implemented on an as-needed basis by `gdbstub`'s contributors. If there's a missing GDB feature that you'd like `gdbstub` to implement, please file an issue and/or open a PR! diff --git a/examples/armv4t/gdb/flash.rs b/examples/armv4t/gdb/flash.rs new file mode 100644 index 00000000..697a0d89 --- /dev/null +++ b/examples/armv4t/gdb/flash.rs @@ -0,0 +1,21 @@ +use crate::emu::Emu; +use gdbstub::target; +use gdbstub::target::TargetResult; +use log::info; + +impl target::ext::flash::Flash for Emu { + fn flash_erase(&mut self, start_addr: u32, length: u32) -> TargetResult<(), Self> { + info!("flash_erase start_addr: {start_addr:08x}, length: {length:08x}"); + Ok(()) + } + + fn flash_write(&mut self, start_addr: u32, _data: &[u8]) -> TargetResult<(), Self> { + info!("flash_write start_addr: {start_addr:08x}"); + Ok(()) + } + + fn flash_done(&mut self) -> TargetResult<(), Self> { + info!("flash_done"); + Ok(()) + } +} diff --git a/examples/armv4t/gdb/memory_map.rs b/examples/armv4t/gdb/memory_map.rs index 0aa5c08d..408155d0 100644 --- a/examples/armv4t/gdb/memory_map.rs +++ b/examples/armv4t/gdb/memory_map.rs @@ -10,14 +10,23 @@ impl target::ext::memory_map::MemoryMap for Emu { length: usize, buf: &mut [u8], ) -> TargetResult { - // Sample memory map, with RAM coverying the whole - // memory space. + // Sample memory map, modeled on part of STM32F446 memory map. + // A real memory map is necessary to test the flash commands. let memory_map = r#" - + + + 0x4000 + + + 0x10000 + + + 0x20000 + "# .trim() .as_bytes(); diff --git a/examples/armv4t/gdb/mod.rs b/examples/armv4t/gdb/mod.rs index ab8a8c06..bcb966fa 100644 --- a/examples/armv4t/gdb/mod.rs +++ b/examples/armv4t/gdb/mod.rs @@ -19,6 +19,7 @@ mod breakpoints; mod catch_syscalls; mod exec_file; mod extended_mode; +mod flash; mod host_io; mod libraries; mod lldb_register_info_override; @@ -169,6 +170,11 @@ impl Target for Emu { ) -> Option> { Some(self) } + + #[inline(always)] + fn support_flash_operations(&mut self) -> Option> { + Some(self) + } } impl SingleThreadBase for Emu { diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs index b18f860e..ceab04b5 100644 --- a/src/protocol/commands.rs +++ b/src/protocol/commands.rs @@ -302,6 +302,12 @@ commands! { "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>, } diff --git a/src/protocol/commands/_vFlashDone.rs b/src/protocol/commands/_vFlashDone.rs new file mode 100644 index 00000000..cfa5e2f7 --- /dev/null +++ b/src/protocol/commands/_vFlashDone.rs @@ -0,0 +1,11 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFlashDone; + +impl<'a> ParseCommand<'a> for vFlashDone { + #[inline(always)] + fn from_packet(_buf: PacketBuf<'a>) -> Option { + Some(vFlashDone) + } +} diff --git a/src/protocol/commands/_vFlashErase.rs b/src/protocol/commands/_vFlashErase.rs new file mode 100644 index 00000000..a634bff8 --- /dev/null +++ b/src/protocol/commands/_vFlashErase.rs @@ -0,0 +1,76 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFlashErase<'a> { + pub addr: &'a [u8], + pub length: &'a [u8], +} + +impl<'a> ParseCommand<'a> for vFlashErase<'a> { + #[inline(always)] + fn from_packet(buf: PacketBuf<'a>) -> Option { + let body = buf.into_body(); + + let mut body = body.splitn_mut(3, |&b| b == b',' || b == b':'); + let _first_colon = body.next()?; + let addr = decode_hex_buf(body.next()?).ok()?; + let length = decode_hex_buf(body.next()?) + .ok() + .filter(|l| !l.is_empty())?; + Some(Self { addr, length }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! test_buf { + ($bufname:ident, $body:literal) => { + let mut test = $body.to_vec(); + let mut buf = PacketBuf::new_with_raw_body(&mut test).unwrap(); + if !buf.strip_prefix(b"vFlashErase") { + panic!("invalid test"); + } + let $bufname = buf; + }; + } + + #[test] + fn valid_vFlashErase() { + test_buf!(buf, b"vFlashErase:08000000,00004000"); + + let pkt = vFlashErase::from_packet(buf).unwrap(); + + assert_eq!(pkt.addr, [0x08, 0, 0, 0]); + assert_eq!(pkt.length, [0, 0, 0x40, 0]); + } + + #[test] + fn invalid_vFlashErase_wrong_address() { + test_buf!(buf, b"vFlashErase:abcdefg:00004000"); + + assert!(vFlashErase::from_packet(buf).is_none()); + } + + #[test] + fn invalid_vFlashErase_wrong_length() { + test_buf!(buf, b"vFlashErase:08000000:abcdefg"); + + assert!(vFlashErase::from_packet(buf).is_none()); + } + + #[test] + fn invalid_vFlashErase_missing_address() { + test_buf!(buf, b"vFlashErase:"); + + assert!(vFlashErase::from_packet(buf).is_none()); + } + + #[test] + fn invalid_vFlashErase_missing_length() { + test_buf!(buf, b"vFlashErase:08000000:"); + + assert!(vFlashErase::from_packet(buf).is_none()); + } +} diff --git a/src/protocol/commands/_vFlashWrite.rs b/src/protocol/commands/_vFlashWrite.rs new file mode 100644 index 00000000..bae5ea0f --- /dev/null +++ b/src/protocol/commands/_vFlashWrite.rs @@ -0,0 +1,77 @@ +use super::prelude::*; +use crate::protocol::common::hex::decode_bin_buf; + +#[derive(Debug)] +pub struct vFlashWrite<'a> { + pub addr: &'a [u8], + pub val: &'a [u8], +} + +impl<'a> ParseCommand<'a> for vFlashWrite<'a> { + #[inline(always)] + fn from_packet(buf: PacketBuf<'a>) -> Option { + let body = buf.into_body(); + + let mut body = body.splitn_mut(3, |&b| b == b':'); + let _first_colon = body.next()?; + let addr = decode_hex_buf(body.next()?) + .ok() + .filter(|a| !a.is_empty())?; + let val = decode_bin_buf(body.next()?)?; + + Some(vFlashWrite { addr, val }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! test_buf { + ($bufname:ident, $body:literal) => { + let mut test = $body.to_vec(); + let mut buf = PacketBuf::new_with_raw_body(&mut test).unwrap(); + if !buf.strip_prefix(b"vFlashWrite") { + panic!("invalid test"); + } + let $bufname = buf; + }; + } + + #[test] + fn valid_vFlashWrite() { + test_buf!( + buf, + b"vFlashWrite:08000000:\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A" + ); + + let pkt = vFlashWrite::from_packet(buf).unwrap(); + + assert_eq!(pkt.addr, [0x08, 0, 0, 0]); + assert_eq!(pkt.val, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + } + + #[test] + fn invalid_vFlashWrite_wrong_address() { + test_buf!( + buf, + b"vFlashWrite:abcdefg:\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A" + ); + + assert!(vFlashWrite::from_packet(buf).is_none()) + } + + #[test] + fn invalid_vFlashWrite_missing_data() { + test_buf!(buf, b"vFlashWrite:abcdefg:"); + + assert!(vFlashWrite::from_packet(buf).is_none()) + } + + #[test] + fn invalid_vFlashWrite_missing_address() { + test_buf!(buf, b"vFlashWrite:"); + + assert!(vFlashWrite::from_packet(buf).is_none()) + } +} diff --git a/src/stub/core_impl.rs b/src/stub/core_impl.rs index d5ca4d5a..90cc5f44 100644 --- a/src/stub/core_impl.rs +++ b/src/stub/core_impl.rs @@ -30,6 +30,7 @@ mod breakpoints; mod catch_syscalls; mod exec_file; mod extended_mode; +mod flash; mod host_io; mod libraries; mod lldb_register_info; @@ -213,6 +214,7 @@ impl GdbStubImpl { Command::ReverseCont(cmd) => self.handle_reverse_cont(res, target, cmd), Command::ReverseStep(cmd) => self.handle_reverse_step(res, target, cmd), Command::MemoryMap(cmd) => self.handle_memory_map(res, target, cmd), + Command::FlashOperations(cmd) => self.handle_flash_operations(res, target, cmd), Command::HostIo(cmd) => self.handle_host_io(res, target, cmd), Command::ExecFile(cmd) => self.handle_exec_file(res, target, cmd), Command::Auxv(cmd) => self.handle_auxv(res, target, cmd), diff --git a/src/stub/core_impl/flash.rs b/src/stub/core_impl/flash.rs new file mode 100644 index 00000000..ed0e9c4a --- /dev/null +++ b/src/stub/core_impl/flash.rs @@ -0,0 +1,42 @@ +use super::prelude::*; +use crate::arch::Arch; +use crate::protocol::commands::ext::FlashOperations; + +impl GdbStubImpl { + pub(crate) fn handle_flash_operations( + &mut self, + _res: &mut ResponseWriter<'_, C>, + target: &mut T, + command: FlashOperations<'_>, + ) -> Result> { + let ops = match target.support_flash_operations() { + Some(ops) => ops, + None => return Ok(HandlerStatus::Handled), + }; + let handler_status = match command { + FlashOperations::vFlashErase(cmd) => { + let addr = ::Usize::from_be_bytes(cmd.addr) + .ok_or(Error::TargetMismatch)?; + + let length = ::Usize::from_be_bytes(cmd.length) + .ok_or(Error::TargetMismatch)?; + + ops.flash_erase(addr, length).handle_error()?; + HandlerStatus::NeedsOk + } + FlashOperations::vFlashWrite(cmd) => { + let addr = ::Usize::from_be_bytes(cmd.addr) + .ok_or(Error::TargetMismatch)?; + + ops.flash_write(addr, cmd.val).handle_error()?; + HandlerStatus::NeedsOk + } + FlashOperations::vFlashDone(_) => { + ops.flash_done().handle_error()?; + HandlerStatus::NeedsOk + } + }; + + Ok(handler_status) + } +} diff --git a/src/target/ext/flash.rs b/src/target/ext/flash.rs new file mode 100644 index 00000000..4b95e8c9 --- /dev/null +++ b/src/target/ext/flash.rs @@ -0,0 +1,50 @@ +//! Provide flash operations on the target. +use crate::arch::Arch; +use crate::target::Target; +use crate::target::TargetResult; + +/// Flash memory operations. +/// It's necessary to implement this extension to support GDB `load` command. +/// +/// Typically, a GDB `load` command sequence starts by issuing a `flash_erase` +/// command, followed by multiple `flash_write` commands (typically one for each +/// loadable ELF section), and ends with a `flash_done` command. +/// +/// The regions containing the addresses to be flashed must be specified as +/// "flash" regions in the memory map xml, returned by +/// [MemoryMap::memory_map_xml][crate::target::ext::memory_map::MemoryMap::memory_map_xml]. +pub trait Flash: Target { + /// Erase `length` bytes of the target's flash memory starting from + /// `start_addr`. + /// + /// GDB ensures `start_addr` and `length` are aligned to flash memory + /// block boundaries as defined by the memory map xml. + fn flash_erase( + &mut self, + start_addr: ::Usize, + length: ::Usize, + ) -> TargetResult<(), Self>; + + /// Write bytes to the target's flash memory. + /// + /// GDB guarantees that the memory ranges specified by `flash_write` + /// commands sent before a `flash_done` do not overlap and appear in + /// order of increasing addresses. + /// + /// See [GDB Documentation] for more details. + /// + /// [GDB Documentation]: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html + fn flash_write( + &mut self, + start_addr: ::Usize, + data: &[u8], + ) -> TargetResult<(), Self>; + + /// Indicate to the target that flash programming is finished. + /// + /// By GDB documentation, you can batch flash erase and write operations + /// until this is called. + fn flash_done(&mut self) -> TargetResult<(), Self>; +} + +define_ext!(FlashOps, Flash); diff --git a/src/target/ext/mod.rs b/src/target/ext/mod.rs index ffdbdabd..3720bc28 100644 --- a/src/target/ext/mod.rs +++ b/src/target/ext/mod.rs @@ -264,6 +264,7 @@ pub mod breakpoints; pub mod catch_syscalls; pub mod exec_file; pub mod extended_mode; +pub mod flash; pub mod host_io; pub mod libraries; pub mod lldb_register_info_override; diff --git a/src/target/mod.rs b/src/target/mod.rs index f7a5b517..3143e878 100644 --- a/src/target/mod.rs +++ b/src/target/mod.rs @@ -657,6 +657,12 @@ pub trait Target { None } + /// Support for flash memory operations. + #[inline(always)] + fn support_flash_operations(&mut self) -> Option> { + None + } + /// Support for setting / removing syscall catchpoints. #[inline(always)] fn support_catch_syscalls(