Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
26 changes: 26 additions & 0 deletions examples/armv4t/gdb/flash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::emu::Emu;
use gdbstub::arch::Arch;
use gdbstub::target;
use gdbstub::target::TargetResult;

impl target::ext::flash::Flash for Emu {
fn flash_erase(
&mut self,
_start_addr: <Self::Arch as Arch>::Usize,
_length: <Self::Arch as Arch>::Usize,
) -> TargetResult<(), Self> {
Ok(())
}

fn flash_write(
&mut self,
_start_addr: <Self::Arch as Arch>::Usize,
_data: &[u8],
) -> TargetResult<(), Self> {
Ok(())
}

fn flash_done(&mut self) -> TargetResult<(), Self> {
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, it's 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)
}
}
33 changes: 33 additions & 0 deletions src/target/ext/flash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Provide flash operations on the target.
use crate::arch::Arch;
use crate::target::Target;
use crate::target::TargetResult;

/// Flash memory operations.
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
/// sectors 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'a flash memory.
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