Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!

Expand Down
21 changes: 21 additions & 0 deletions examples/armv4t/gdb/flash.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
15 changes: 12 additions & 3 deletions examples/armv4t/gdb/memory_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ impl target::ext::memory_map::MemoryMap for Emu {
length: usize,
buf: &mut [u8],
) -> TargetResult<usize, Self> {
// 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#"<?xml version="1.0"?>
<!DOCTYPE memory-map
PUBLIC "+//IDN gnu.org//DTD GDB Memory Map V1.0//EN"
"http://sourceware.org/gdb/gdb-memory-map.dtd">
<memory-map>
<memory type="ram" start="0x0" length="0x100000000"/>
<memory type="ram" start="0x20000000" length="0x20000"/>
<memory type="flash" start="0x08000000" length="0x10000">
<property name="blocksize">0x4000</property>
</memory>
<memory type="flash" start="0x08010000" length="0x10000">
<property name="blocksize">0x10000</property>
</memory>
<memory type="flash" start="0x08020000" length="0x60000">
<property name="blocksize">0x20000</property>
</memory>
</memory-map>"#
.trim()
.as_bytes();
Expand Down
6 changes: 6 additions & 0 deletions examples/armv4t/gdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -169,6 +170,11 @@ impl Target for Emu {
) -> Option<target::ext::tracepoints::TracepointsOps<'_, Self>> {
Some(self)
}

#[inline(always)]
fn support_flash_operations(&mut self) -> Option<target::ext::flash::FlashOps<'_, Self>> {
Some(self)
}
}

impl SingleThreadBase for Emu {
Expand Down
6 changes: 6 additions & 0 deletions src/protocol/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}
Expand Down
11 changes: 11 additions & 0 deletions src/protocol/commands/_vFlashDone.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
Some(vFlashDone)
}
}
76 changes: 76 additions & 0 deletions src/protocol/commands/_vFlashErase.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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());
}
}
77 changes: 77 additions & 0 deletions src/protocol/commands/_vFlashWrite.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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())
}
}
2 changes: 2 additions & 0 deletions src/stub/core_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -213,6 +214,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
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),
Expand Down
42 changes: 42 additions & 0 deletions src/stub/core_impl/flash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use super::prelude::*;
use crate::arch::Arch;
use crate::protocol::commands::ext::FlashOperations;

impl<T: Target, C: Connection> GdbStubImpl<T, C> {
pub(crate) fn handle_flash_operations(
&mut self,
_res: &mut ResponseWriter<'_, C>,
target: &mut T,
command: FlashOperations<'_>,
) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
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 = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
.ok_or(Error::TargetMismatch)?;

let length = <T::Arch as Arch>::Usize::from_be_bytes(cmd.length)
.ok_or(Error::TargetMismatch)?;

ops.flash_erase(addr, length).handle_error()?;
HandlerStatus::NeedsOk
}
FlashOperations::vFlashWrite(cmd) => {
let addr = <T::Arch as Arch>::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)
}
}
50 changes: 50 additions & 0 deletions src/target/ext/flash.rs
Original file line number Diff line number Diff line change
@@ -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: <Self::Arch as Arch>::Usize,
length: <Self::Arch as Arch>::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: <Self::Arch as Arch>::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);
1 change: 1 addition & 0 deletions src/target/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/target/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,12 @@ pub trait Target {
None
}

/// Support for flash memory operations.
#[inline(always)]
fn support_flash_operations(&mut self) -> Option<ext::flash::FlashOps<'_, Self>> {
None
}

/// Support for setting / removing syscall catchpoints.
#[inline(always)]
fn support_catch_syscalls(
Expand Down
Loading