Skip to content

Commit

Permalink
Support Host I/O operations (#66)
Browse files Browse the repository at this point in the history
* Support Host I/O operations

* Fix argument types

* Add missing Host I/O operations

* Some fixes

* Change return type to HostIoResult

* Improve handle_hostio_result macro

* Implement real filesystem access in example

* Supply example with a complete real filesystem access implementation

* Store files in Vec

* Simplify code

* Allow non-ASCII characters in packet

* Optimize away the bounds checks in decode_bin_buf

* Fix style
  • Loading branch information
bet4it committed Aug 20, 2021
1 parent c40145c commit 9227dfd
Show file tree
Hide file tree
Showing 23 changed files with 1,081 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -14,6 +14,7 @@ categories = ["development-tools::debugging", "embedded", "emulators", "network-
exclude = ["examples/**/*.elf", "examples/**/*.o"]

[dependencies]
bitflags = "1.3"
cfg-if = "0.1.10"
log = "0.4"
managed = { version = "0.8", default-features = false }
Expand Down
4 changes: 3 additions & 1 deletion README.md
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 Expand Up @@ -160,7 +162,7 @@ If you happen to stumble across this crate and end up using it to debug some bar

- When the `paranoid_unsafe` feature is enabled, the following `unsafe` code is _removed_:
- `src/protocol/packet.rs`: Swaps a couple slice-index methods in `PacketBuf` to use `get_unchecked_mut`. The public API of struct ensures that the bounds used to index into the array remain in-bounds.
- `src/protocol/common/hex`: Use an alternate implementation of `decode_hex_buf` which uses unsafe slice indexing.
- `src/protocol/common/hex`: Use an alternate implementation of `decode_hex_buf`/`decode_bin_buf` which uses unsafe slice indexing.

- When the `std` feature is enabled:
- `src/connection/impls/unixstream.rs`: An implementation of `UnixStream::peek` which uses `libc::recv`. This manual implementation will be removed once [rust-lang/rust#76923](https://github.com/rust-lang/rust/issues/76923) is stabilized.
Expand Down
2 changes: 2 additions & 0 deletions examples/armv4t/emu.rs
Expand Up @@ -25,6 +25,7 @@ pub struct Emu {

pub(crate) watchpoints: Vec<u32>,
pub(crate) breakpoints: Vec<u32>,
pub(crate) files: Vec<Option<std::fs::File>>,
}

impl Emu {
Expand Down Expand Up @@ -72,6 +73,7 @@ impl Emu {

watchpoints: Vec::new(),
breakpoints: Vec::new(),
files: Vec::new(),
})
}

Expand Down
228 changes: 228 additions & 0 deletions examples/armv4t/gdb/host_io.rs
@@ -0,0 +1,228 @@
use std::io::{Read, Seek, Write};

use gdbstub::target;
use gdbstub::target::ext::host_io::{
FsKind, HostIoErrno, HostIoError, HostIoOpenFlags, HostIoOpenMode, HostIoOutput, HostIoResult,
HostIoStat, HostIoToken,
};

use crate::emu::Emu;

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_close(&mut self) -> Option<target::ext::host_io::HostIoCloseOps<Self>> {
Some(self)
}

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

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

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

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

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

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

impl target::ext::host_io::HostIoOpen for Emu {
fn open(
&mut self,
filename: &[u8],
flags: HostIoOpenFlags,
_mode: HostIoOpenMode,
) -> HostIoResult<u32, Self> {
if filename.starts_with(b"/proc") {
return Err(HostIoError::Errno(HostIoErrno::ENOENT));
}

let path =
std::str::from_utf8(filename).map_err(|_| HostIoError::Errno(HostIoErrno::ENOENT))?;

let mut read = false;
let mut write = false;
if flags.contains(HostIoOpenFlags::O_RDWR) {
read = true;
write = true;
} else if flags.contains(HostIoOpenFlags::O_WRONLY) {
write = true;
} else {
read = true;
}

let file = std::fs::OpenOptions::new()
.read(read)
.write(write)
.append(flags.contains(HostIoOpenFlags::O_APPEND))
.create(flags.contains(HostIoOpenFlags::O_CREAT))
.truncate(flags.contains(HostIoOpenFlags::O_TRUNC))
.create_new(flags.contains(HostIoOpenFlags::O_EXCL))
.open(path)?;

let n = match self.files.iter_mut().enumerate().find(|(_, f)| f.is_none()) {
Some((n, free_file)) => {
*free_file = Some(file);
n
}
None => {
self.files.push(Some(file));
self.files.len() - 1
}
};

Ok(n as u32)
}
}

impl target::ext::host_io::HostIoClose for Emu {
fn close(&mut self, fd: u32) -> HostIoResult<(), Self> {
let file = match self.files.get_mut(fd as usize) {
Some(file) => file,
_ => return Err(HostIoError::Errno(HostIoErrno::EBADF)),
};

file.take().ok_or(HostIoError::Errno(HostIoErrno::EBADF))?;
while let Some(None) = self.files.last() {
self.files.pop();
}
Ok(())
}
}

impl target::ext::host_io::HostIoPread for Emu {
fn pread<'a>(
&mut self,
fd: u32,
count: u32,
offset: u32,
output: HostIoOutput<'a>,
) -> HostIoResult<HostIoToken<'a>, Self> {
let file = match self.files.get_mut(fd as usize) {
Some(Some(file)) => file,
_ => return Err(HostIoError::Errno(HostIoErrno::EBADF)),
};

let mut buffer = vec![0; count as usize];
file.seek(std::io::SeekFrom::Start(offset as u64))?;
let n = file.read(&mut buffer)?;
Ok(output.write(&buffer[..n]))
}
}

impl target::ext::host_io::HostIoPwrite for Emu {
fn pwrite(&mut self, fd: u32, offset: u32, data: &[u8]) -> HostIoResult<u32, Self> {
let file = match self.files.get_mut(fd as usize) {
Some(Some(file)) => file,
_ => return Err(HostIoError::Errno(HostIoErrno::EBADF)),
};

file.seek(std::io::SeekFrom::Start(offset as u64))?;
let n = file.write(data)?;
Ok(n as u32)
}
}

impl target::ext::host_io::HostIoFstat for Emu {
fn fstat(&mut self, fd: u32) -> HostIoResult<HostIoStat, Self> {
let metadata = match self.files.get(fd as usize) {
Some(Some(file)) => file.metadata()?,
_ => return Err(HostIoError::Errno(HostIoErrno::EBADF)),
};

macro_rules! time_to_secs {
($time:expr) => {
$time
.map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))?
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))?
.as_secs() as u32
};
}
let atime = time_to_secs!(metadata.accessed());
let mtime = time_to_secs!(metadata.modified());
let ctime = time_to_secs!(metadata.created());

Ok(HostIoStat {
st_dev: 0,
st_ino: 0,
st_mode: HostIoOpenMode::empty(),
st_nlink: 0,
st_uid: 0,
st_gid: 0,
st_rdev: 0,
st_size: metadata.len(),
st_blksize: 0,
st_blocks: 0,
st_atime: atime,
st_mtime: mtime,
st_ctime: ctime,
})
}
}

impl target::ext::host_io::HostIoUnlink for Emu {
fn unlink(&mut self, filename: &[u8]) -> HostIoResult<(), Self> {
let path =
std::str::from_utf8(filename).map_err(|_| HostIoError::Errno(HostIoErrno::ENOENT))?;
std::fs::remove_file(path)?;
Ok(())
}
}

impl target::ext::host_io::HostIoReadlink for Emu {
fn readlink<'a>(
&mut self,
filename: &[u8],
output: HostIoOutput<'a>,
) -> HostIoResult<HostIoToken<'a>, Self> {
if filename == b"/proc/1/exe" {
// Support `info proc exe` command
return Ok(output.write(b"/test.elf"));
} else if filename == b"/proc/1/cwd" {
// Support `info proc cwd` command
return Ok(output.write(b"/"));
} else if filename.starts_with(b"/proc") {
return Err(HostIoError::Errno(HostIoErrno::ENOENT));
}

let path =
std::str::from_utf8(filename).map_err(|_| HostIoError::Errno(HostIoErrno::ENOENT))?;
Ok(output.write(
std::fs::read_link(path)?
.to_str()
.ok_or(HostIoError::Errno(HostIoErrno::ENOENT))?
.as_bytes(),
))
}
}

impl target::ext::host_io::HostIoSetfs for Emu {
fn setfs(&mut self, _fs: FsKind) -> HostIoResult<(), Self> {
Ok(())
}
}
6 changes: 6 additions & 0 deletions examples/armv4t/gdb/mod.rs
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

0 comments on commit 9227dfd

Please sign in to comment.