Skip to content
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ categories = ["development-tools::debugging", "embedded", "emulators", "network-
exclude = ["examples/**/*.elf", "examples/**/*.o"]

[dependencies]
bitflags = "1.2.1"
cfg-if = "0.1.10"
log = "0.4"
managed = { version = "0.8", default-features = false }
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Of course, most use-cases will want to support additional debugging features as
- Get section/segment relocation offsets from the target
- Custom `monitor` Commands
- Extend the GDB protocol with custom debug commands using GDB's `monitor` command
- Get target memory map
- Perform Host I/O operations

_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
70 changes: 70 additions & 0 deletions examples/armv4t/gdb/host_io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use gdbstub::target;

use crate::emu::Emu;

use gdbstub::target::ext::host_io::{
HostIoErrno, HostIoError, HostIoMode, HostIoOpenFlags, HostIoResult, PreadOutput, PreadToken,
};

impl target::ext::host_io::HostIo for Emu {
#[inline(always)]
fn enable_open(&mut self) -> Option<target::ext::host_io::HostIoOpenOps<Self>> {
Some(self)
}

#[inline(always)]
fn enable_pread(&mut self) -> Option<target::ext::host_io::HostIoPreadOps<Self>> {
Some(self)
}

#[inline(always)]
fn enable_close(&mut self) -> Option<target::ext::host_io::HostIoCloseOps<Self>> {
Some(self)
}
}

impl target::ext::host_io::HostIoOpen for Emu {
fn open(
&mut self,
filename: &[u8],
_flags: HostIoOpenFlags,
_mode: HostIoMode,
) -> HostIoResult<u32, Self> {
// Support `info proc mappings` command
if filename == b"/proc/1/maps" {
Ok(1)
} else {
Err(HostIoError::Errno(HostIoErrno::EPERM))
}
}
}

impl target::ext::host_io::HostIoPread for Emu {
fn pread<'a>(
&mut self,
fd: i32,
count: u32,
offset: u32,
output: PreadOutput<'a>,
) -> HostIoResult<PreadToken<'a>, Self> {
if fd == 1 {
let maps = b"0x55550000-0x55550078 r-x 0 0 0\n";
let len = maps.len();
let count: usize = count as usize;
let offset: usize = offset as usize;
Ok(output.write(&maps[offset.min(len)..(offset + count).min(len)]))
} else {
Err(HostIoError::Errno(HostIoErrno::EPERM))
}
}
}

impl target::ext::host_io::HostIoClose for Emu {
fn close(&mut self, fd: i32) -> HostIoResult<u32, Self> {
if fd == 1 {
Ok(0)
} else {
Err(HostIoError::Errno(HostIoErrno::EPERM))
}
}
}
6 changes: 6 additions & 0 deletions examples/armv4t/gdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::emu::{Emu, Event};
mod breakpoints;
mod catch_syscalls;
mod extended_mode;
mod host_io;
mod memory_map;
mod monitor_cmd;
mod section_offsets;
Expand Down Expand Up @@ -94,6 +95,11 @@ impl Target for Emu {
fn catch_syscalls(&mut self) -> Option<target::ext::catch_syscalls::CatchSyscallsOps<Self>> {
Some(self)
}

#[inline(always)]
fn host_io(&mut self) -> Option<target::ext::host_io::HostIoOps<Self>> {
Some(self)
}
}

impl Emu {
Expand Down
158 changes: 158 additions & 0 deletions src/gdbstub_impl/ext/host_io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use super::prelude::*;
use crate::arch::Arch;
use crate::protocol::commands::ext::HostIo;
use crate::target::ext::host_io::{HostIoError, HostStat, PreadOutput};
use crate::GdbStubError;

macro_rules! handle_hostio_result {
( $ret:ident, $res:ident, $callback:expr) => {{
match $ret {
Ok(fd) => $callback(fd)?,
Err(HostIoError::Errno(errno)) => {
$res.write_str("F-1,")?;
$res.write_num(errno as i32)?;
}
Err(HostIoError::Fatal(e)) => return Err(GdbStubError::TargetError(e)),
}
}};
}

impl<T: Target, C: Connection> GdbStubImpl<T, C> {
pub(crate) fn handle_host_io(
&mut self,
res: &mut ResponseWriter<C>,
target: &mut T,
command: HostIo,
) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
let ops = match target.host_io() {
Some(ops) => ops,
None => return Ok(HandlerStatus::Handled),
};

crate::__dead_code_marker!("host_io", "impl");

let handler_status = match command {
HostIo::vFileOpen(cmd) if ops.enable_open().is_some() => {
let ops = ops.enable_open().unwrap();
let result = ops.open(cmd.filename, cmd.flags, cmd.mode);
handle_hostio_result!(result, res, |fd| -> Result<_, Error<T::Error, C::Error>> {
res.write_str("F")?;
res.write_num(fd)?;
Ok(())
});
HandlerStatus::Handled
}
HostIo::vFileClose(cmd) if ops.enable_close().is_some() => {
let ops = ops.enable_close().unwrap();
let result = ops.close(cmd.fd);
handle_hostio_result!(result, res, |ret| -> Result<_, Error<T::Error, C::Error>> {
res.write_str("F")?;
res.write_num(ret)?;
Ok(())
});
HandlerStatus::Handled
}
HostIo::vFilePread(cmd) if ops.enable_pread().is_some() => {
let count = <T::Arch as Arch>::Usize::from_be_bytes(cmd.count)
.ok_or(Error::TargetMismatch)?;
let offset = <T::Arch as Arch>::Usize::from_be_bytes(cmd.offset)
.ok_or(Error::TargetMismatch)?;
let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
let mut callback = |data: &[u8]| {
let e = (|| {
res.write_str("F")?;
res.write_num(data.len())?;
res.write_str(";")?;
res.write_binary(data)?;
Ok(())
})();

if let Err(e) = e {
err = Err(e)
}
};

let ops = ops.enable_pread().unwrap();
let result = ops.pread(cmd.fd, count, offset, PreadOutput::new(&mut callback));
handle_hostio_result!(result, res, |_| -> Result<_, Error<T::Error, C::Error>> {
Ok(())
});
err?;

HandlerStatus::Handled
}
HostIo::vFilePwrite(cmd) if ops.enable_pwrite().is_some() => {
let offset = <T::Arch as Arch>::Usize::from_be_bytes(cmd.offset)
.ok_or(Error::TargetMismatch)?;
let ops = ops.enable_pwrite().unwrap();
let result = ops.pwrite(cmd.fd, offset, cmd.data);
handle_hostio_result!(result, res, |ret| -> Result<_, Error<T::Error, C::Error>> {
res.write_str("F")?;
res.write_num(ret)?;
Ok(())
});
HandlerStatus::Handled
}
HostIo::vFileFstat(cmd) if ops.enable_fstat().is_some() => {
let ops = ops.enable_fstat().unwrap();
let result = ops.fstat(cmd.fd);
handle_hostio_result!(
result,
res,
|stat: HostStat| -> Result<_, Error<T::Error, C::Error>> {
let size = core::mem::size_of::<HostStat>();
res.write_str("F")?;
res.write_num(size)?;
res.write_str(";")?;
res.write_binary(&stat.st_dev.to_le_bytes())?;
res.write_binary(&stat.st_ino.to_le_bytes())?;
res.write_binary(&(stat.st_mode.bits()).to_le_bytes())?;
res.write_binary(&stat.st_nlink.to_le_bytes())?;
res.write_binary(&stat.st_uid.to_le_bytes())?;
res.write_binary(&stat.st_gid.to_le_bytes())?;
res.write_binary(&stat.st_rdev.to_le_bytes())?;
res.write_binary(&stat.st_size.to_le_bytes())?;
res.write_binary(&stat.st_blksize.to_le_bytes())?;
res.write_binary(&stat.st_blocks.to_le_bytes())?;
res.write_binary(&stat.st_atime.to_le_bytes())?;
res.write_binary(&stat.st_mtime.to_le_bytes())?;
res.write_binary(&stat.st_ctime.to_le_bytes())?;
Ok(())
}
);
HandlerStatus::Handled
}
HostIo::vFileUnlink(cmd) if ops.enable_unlink().is_some() => {
let ops = ops.enable_unlink().unwrap();
let result = ops.unlink(cmd.filename);
handle_hostio_result!(result, res, |ret| -> Result<_, Error<T::Error, C::Error>> {
res.write_str("F")?;
res.write_num(ret)?;
Ok(())
});
HandlerStatus::Handled
}
HostIo::vFileReadlink(cmd) if ops.enable_readlink().is_some() => {
let ops = ops.enable_readlink().unwrap();
let result = ops.readlink(cmd.filename);
handle_hostio_result!(result, res, |ret| -> Result<_, Error<T::Error, C::Error>> {
res.write_str("F")?;
res.write_num(ret)?;
Ok(())
});
HandlerStatus::Handled
}
HostIo::vFileSetfs(cmd) if ops.enable_setfs().is_some() => {
let ops = ops.enable_setfs().unwrap();
let result = ops.setfs(cmd.fs);
handle_hostio_result!(result, res, |_| -> Result<_, Error<T::Error, C::Error>> {
Ok(())
});
HandlerStatus::Handled
}
_ => HandlerStatus::Handled,
};

Ok(handler_status)
}
}
1 change: 1 addition & 0 deletions src/gdbstub_impl/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod base;
mod breakpoints;
mod catch_syscalls;
mod extended_mode;
mod host_io;
mod memory_map;
mod monitor_cmd;
mod reverse_exec;
Expand Down
1 change: 1 addition & 0 deletions src/gdbstub_impl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,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::HostIo(cmd) => self.handle_host_io(res, target, cmd),
}
}
}
11 changes: 11 additions & 0 deletions src/protocol/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,17 @@ commands! {
"qXfer:memory-map:read" => _qXfer_memory_map::qXferMemoryMapRead,
}

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>,
}
Expand Down
23 changes: 23 additions & 0 deletions src/protocol/commands/_vFile_close.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::prelude::*;

#[derive(Debug)]
pub struct vFileClose {
pub fd: i32,
}

impl<'a> ParseCommand<'a> for vFileClose {
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
return None;
}

match body {
[b':', body @ ..] => {
let fd = decode_hex(body).ok()?;
Some(vFileClose{fd})
},
_ => None,
}
}
}
24 changes: 24 additions & 0 deletions src/protocol/commands/_vFile_fstat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use super::prelude::*;

#[derive(Debug)]
pub struct vFileFstat {
pub fd: i32,
}

impl<'a> ParseCommand<'a> for vFileFstat {
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
return None;
}

match body {
[b':', body @ ..] => {
let mut body = body.splitn_mut_no_panic(3, |b| *b == b',');
let fd = decode_hex(body.next()?).ok()?;
Some(vFileFstat{fd})
},
_ => None,
}
}
}
30 changes: 30 additions & 0 deletions src/protocol/commands/_vFile_open.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use super::prelude::*;

use crate::target::ext::host_io::{HostIoOpenFlags, HostIoMode};

#[derive(Debug)]
pub struct vFileOpen<'a> {
pub filename: &'a [u8],
pub flags: HostIoOpenFlags,
pub mode: HostIoMode,
}

impl<'a> ParseCommand<'a> for vFileOpen<'a> {
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();
if body.is_empty() {
return None;
}

match body {
[b':', body @ ..] => {
let mut body = body.splitn_mut_no_panic(3, |b| *b == b',');
let filename = decode_hex_buf(body.next()?).ok()?;
let flags = HostIoOpenFlags::from_bits(decode_hex(body.next()?).ok()?).unwrap();
let mode = HostIoMode::from_bits(decode_hex(body.next()?).ok()?).unwrap();
Some(vFileOpen{filename, flags, mode})
},
_ => None,
}
}
}
Loading