From 4a814afd0721a39eb93c7bff05bddbaa0c09f0fb Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Mon, 2 Aug 2021 10:10:44 +0800 Subject: [PATCH 01/13] Support Host I/O operations --- README.md | 2 ++ examples/armv4t/gdb/host_io.rs | 35 +++++++++++++++++++ examples/armv4t/gdb/mod.rs | 6 ++++ src/gdbstub_impl/ext/host_io.rs | 49 +++++++++++++++++++++++++++ src/gdbstub_impl/ext/mod.rs | 1 + src/gdbstub_impl/mod.rs | 1 + src/protocol/commands.rs | 7 ++++ src/protocol/commands/_vFile_close.rs | 23 +++++++++++++ src/protocol/commands/_vFile_open.rs | 28 +++++++++++++++ src/protocol/commands/_vFile_pread.rs | 28 +++++++++++++++ src/protocol/commands/_vFile_setfs.rs | 23 +++++++++++++ src/target/ext/host_io.rs | 19 +++++++++++ src/target/ext/mod.rs | 1 + src/target/mod.rs | 5 +++ 14 files changed, 228 insertions(+) create mode 100644 examples/armv4t/gdb/host_io.rs create mode 100644 src/gdbstub_impl/ext/host_io.rs create mode 100644 src/protocol/commands/_vFile_close.rs create mode 100644 src/protocol/commands/_vFile_open.rs create mode 100644 src/protocol/commands/_vFile_pread.rs create mode 100644 src/protocol/commands/_vFile_setfs.rs create mode 100644 src/target/ext/host_io.rs diff --git a/README.md b/README.md index 573bc66e..da567901 100644 --- a/README.md +++ b/README.md @@ -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! diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs new file mode 100644 index 00000000..7c627de8 --- /dev/null +++ b/examples/armv4t/gdb/host_io.rs @@ -0,0 +1,35 @@ +use gdbstub::target; + +use crate::emu::Emu; + +impl target::ext::host_io::HostIo for Emu { + fn open(&self, filename: &[u8], _flags: u64, _mode: u64) -> i64 { + if filename == b"/proc/1/maps" { + 1 + } else { + -1 + } + } + + fn pread(&self, fd: usize, count: usize, offset: usize) -> &[u8] { + if fd == 1 { + let maps = b"0x55550000-0x55550078 r-x 0 0 0\n"; + let len = maps.len(); + &maps[offset.min(len)..(offset + count).min(len)] + } else { + b"" + } + } + + fn close(&self, fd: usize) -> i64 { + if fd == 1 { + 0 + } else { + -1 + } + } + + fn setfs(&self, _fd: usize) -> i64 { + 0 + } +} diff --git a/examples/armv4t/gdb/mod.rs b/examples/armv4t/gdb/mod.rs index 1d239894..6b8896a9 100644 --- a/examples/armv4t/gdb/mod.rs +++ b/examples/armv4t/gdb/mod.rs @@ -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; @@ -94,6 +95,11 @@ impl Target for Emu { fn catch_syscalls(&mut self) -> Option> { Some(self) } + + #[inline(always)] + fn host_io(&mut self) -> Option> { + Some(self) + } } impl Emu { diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs new file mode 100644 index 00000000..0c32efa3 --- /dev/null +++ b/src/gdbstub_impl/ext/host_io.rs @@ -0,0 +1,49 @@ +use super::prelude::*; +use crate::protocol::commands::ext::HostIo; + +impl GdbStubImpl { + pub(crate) fn handle_host_io( + &mut self, + res: &mut ResponseWriter, + target: &mut T, + command: HostIo, + ) -> Result> { + 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) => { + let ret = ops.open(cmd.filename, cmd.flags, cmd.mode); + res.write_str("F")?; + res.write_num(ret)?; + HandlerStatus::Handled + } + HostIo::vFileClose(cmd) => { + let ret = ops.close(cmd.fd); + res.write_str("F")?; + res.write_num(ret)?; + HandlerStatus::Handled + } + HostIo::vFilePread(cmd) => { + let data = ops.pread(cmd.fd, cmd.count, cmd.offset); + res.write_str("F")?; + res.write_num(data.len())?; + res.write_str(";")?; + res.write_binary(data)?; + HandlerStatus::Handled + } + HostIo::vFileSetfs(cmd) => { + let ret = ops.setfs(cmd.fd); + res.write_str("F")?; + res.write_num(ret)?; + HandlerStatus::Handled + } + }; + + Ok(handler_status) + } +} diff --git a/src/gdbstub_impl/ext/mod.rs b/src/gdbstub_impl/ext/mod.rs index 227e3d8d..2aae2fdf 100644 --- a/src/gdbstub_impl/ext/mod.rs +++ b/src/gdbstub_impl/ext/mod.rs @@ -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; diff --git a/src/gdbstub_impl/mod.rs b/src/gdbstub_impl/mod.rs index 221537b4..fc598cd8 100644 --- a/src/gdbstub_impl/mod.rs +++ b/src/gdbstub_impl/mod.rs @@ -579,6 +579,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::HostIo(cmd) => self.handle_host_io(res, target, cmd), } } } diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs index e70e71a1..f5a93eaa 100644 --- a/src/protocol/commands.rs +++ b/src/protocol/commands.rs @@ -223,6 +223,13 @@ 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, + "vFile:setfs" => _vFile_setfs::vFileSetfs, + } + catch_syscalls use 'a { "QCatchSyscalls" => _QCatchSyscalls::QCatchSyscalls<'a>, } diff --git a/src/protocol/commands/_vFile_close.rs b/src/protocol/commands/_vFile_close.rs new file mode 100644 index 00000000..75d4fb4c --- /dev/null +++ b/src/protocol/commands/_vFile_close.rs @@ -0,0 +1,23 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFileClose { + pub fd: usize, +} + +impl<'a> ParseCommand<'a> for vFileClose { + fn from_packet(buf: PacketBuf<'a>) -> Option { + 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, + } + } +} diff --git a/src/protocol/commands/_vFile_open.rs b/src/protocol/commands/_vFile_open.rs new file mode 100644 index 00000000..5a872f4e --- /dev/null +++ b/src/protocol/commands/_vFile_open.rs @@ -0,0 +1,28 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFileOpen<'a> { + pub filename: &'a [u8], + pub flags: u64, + pub mode: u64, +} + +impl<'a> ParseCommand<'a> for vFileOpen<'a> { + fn from_packet(buf: PacketBuf<'a>) -> Option { + 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= decode_hex(body.next()?).ok()?; + let mode= decode_hex(body.next()?).ok()?; + Some(vFileOpen{filename, flags, mode}) + }, + _ => None, + } + } +} diff --git a/src/protocol/commands/_vFile_pread.rs b/src/protocol/commands/_vFile_pread.rs new file mode 100644 index 00000000..efcc6ead --- /dev/null +++ b/src/protocol/commands/_vFile_pread.rs @@ -0,0 +1,28 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFilePread { + pub fd: usize, + pub count: usize, + pub offset: usize, +} + +impl<'a> ParseCommand<'a> for vFilePread { + fn from_packet(buf: PacketBuf<'a>) -> Option { + 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()?; + let count= decode_hex(body.next()?).ok()?; + let offset= decode_hex(body.next()?).ok()?; + Some(vFilePread{fd, count, offset}) + }, + _ => None, + } + } +} diff --git a/src/protocol/commands/_vFile_setfs.rs b/src/protocol/commands/_vFile_setfs.rs new file mode 100644 index 00000000..9891616f --- /dev/null +++ b/src/protocol/commands/_vFile_setfs.rs @@ -0,0 +1,23 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFileSetfs { + pub fd: usize, +} + +impl<'a> ParseCommand<'a> for vFileSetfs { + fn from_packet(buf: PacketBuf<'a>) -> Option { + let body = buf.into_body(); + if body.is_empty() { + return None; + } + + match body { + [b':', body @ ..] => { + let fd = decode_hex(body).ok()?; + Some(vFileSetfs{fd}) + }, + _ => None, + } + } +} diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs new file mode 100644 index 00000000..bcbe1df6 --- /dev/null +++ b/src/target/ext/host_io.rs @@ -0,0 +1,19 @@ +//! Provide Host I/O operations for the target. +use crate::target::Target; + +/// Target Extension - Perform I/O operations on host +pub trait HostIo: Target { + /// Open a file at filename and return a file descriptor for it, or return + /// -1 if an error occurs. + fn open(&self, filename: &[u8], flags: u64, mode: u64) -> i64; + /// Close the open file corresponding to fd and return 0, or -1 if an error + /// occurs. + fn close(&self, fd: usize) -> i64; + /// Read data from the open file corresponding to fd. + fn pread(&self, fd: usize, count: usize, offset: usize) -> &[u8]; + /// Select the filesystem on which vFile operations with filename arguments + /// will operate. + fn setfs(&self, fd: usize) -> i64; +} + +define_ext!(HostIoOps, HostIo); diff --git a/src/target/ext/mod.rs b/src/target/ext/mod.rs index 5bcd2163..99f02809 100644 --- a/src/target/ext/mod.rs +++ b/src/target/ext/mod.rs @@ -260,6 +260,7 @@ pub mod base; pub mod breakpoints; pub mod catch_syscalls; pub mod extended_mode; +pub mod host_io; pub mod memory_map; pub mod monitor_cmd; pub mod section_offsets; diff --git a/src/target/mod.rs b/src/target/mod.rs index 02ca29cb..c2516291 100644 --- a/src/target/mod.rs +++ b/src/target/mod.rs @@ -358,6 +358,11 @@ pub trait Target { fn catch_syscalls(&mut self) -> Option> { None } + + /// Support Host I/O operations. + fn host_io(&mut self) -> Option> { + None + } } macro_rules! impl_dyn_target { From 6823d00500084987fb399400bf81091cc2ce1cef Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Wed, 4 Aug 2021 17:05:49 +0800 Subject: [PATCH 02/13] Fix argument types --- Cargo.toml | 1 + examples/armv4t/gdb/host_io.rs | 20 ++++++++--- src/common.rs | 52 +++++++++++++++++++++++++++ src/gdbstub_impl/ext/host_io.rs | 30 +++++++++++++--- src/protocol/commands.rs | 4 +-- src/protocol/commands/_vFile_open.rs | 10 +++--- src/protocol/commands/_vFile_pread.rs | 12 +++---- src/protocol/console_output.rs | 2 +- src/target/ext/host_io.rs | 28 +++++++++++++-- 9 files changed, 135 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c180951a..6351294e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index 7c627de8..c81962c8 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -2,8 +2,11 @@ use gdbstub::target; use crate::emu::Emu; +use gdbstub::common::{HostMode, HostOpenFlags}; +use gdbstub::target::ext::host_io::PreadOutput; + impl target::ext::host_io::HostIo for Emu { - fn open(&self, filename: &[u8], _flags: u64, _mode: u64) -> i64 { + fn open(&self, filename: &[u8], _flags: HostOpenFlags, _mode: HostMode) -> i64 { if filename == b"/proc/1/maps" { 1 } else { @@ -11,13 +14,22 @@ impl target::ext::host_io::HostIo for Emu { } } - fn pread(&self, fd: usize, count: usize, offset: usize) -> &[u8] { + fn pread( + &self, + fd: usize, + count: u32, + offset: u32, + output: &mut PreadOutput<'_>, + ) -> Result<(), Self::Error> { if fd == 1 { let maps = b"0x55550000-0x55550078 r-x 0 0 0\n"; let len = maps.len(); - &maps[offset.min(len)..(offset + count).min(len)] + let count: usize = count as usize; + let offset: usize = offset as usize; + output.write(&maps[offset.min(len)..(offset + count).min(len)]); + Ok(()) } else { - b"" + Err("pread failed") } } diff --git a/src/common.rs b/src/common.rs index d9ba55cc..c1779ccd 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,7 +1,59 @@ //! Common types and definitions. +use bitflags::bitflags; + /// Thread ID pub type Tid = core::num::NonZeroUsize; /// Process ID pub type Pid = core::num::NonZeroUsize; + +bitflags! { + // The read/write flags below may look a little weird, but that is the way + // they are defined in the protocol. + /// Host flags for opening files. + pub struct HostOpenFlags: u32 { + /// A read-only file. + const O_RDONLY = 0x0; + /// A write-only file. + const O_WRONLY = 0x1; + /// A read-write file. + const O_RDWR = 0x2; + /// Append to an existing file. + const O_APPEND = 0x8; + /// Create a non-existent file. + const O_CREAT = 0x200; + /// Truncate an existing file. + const O_TRUNC = 0x400; + /// Exclusive access. + const O_EXCL = 0x800; + } +} + +bitflags! { + /// Host file permissions. + pub struct HostMode: u32 { + /// A regular file. + const S_IFREG = 0o100000; + /// A directory. + const S_IFDIR = 0o40000; + /// User read permissions. + const S_IRUSR = 0o400; + /// User write permissions. + const S_IWUSR = 0o200; + /// User execute permissions. + const S_IXUSR = 0o100; + /// Group read permissions. + const S_IRGRP = 0o40; + /// Group write permissions + const S_IWGRP = 0o20; + /// Group execute permissions. + const S_IXGRP = 0o10; + /// World read permissions. + const S_IROTH = 0o4; + /// World write permissions + const S_IWOTH = 0o2; + /// World execute permissions. + const S_IXOTH = 0o1; + } +} diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index 0c32efa3..696d2ba4 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -1,5 +1,7 @@ use super::prelude::*; +use crate::arch::Arch; use crate::protocol::commands::ext::HostIo; +use crate::target::ext::host_io::PreadOutput; impl GdbStubImpl { pub(crate) fn handle_host_io( @@ -29,11 +31,29 @@ impl GdbStubImpl { HandlerStatus::Handled } HostIo::vFilePread(cmd) => { - let data = ops.pread(cmd.fd, cmd.count, cmd.offset); - res.write_str("F")?; - res.write_num(data.len())?; - res.write_str(";")?; - res.write_binary(data)?; + let count = ::Usize::from_be_bytes(cmd.count) + .ok_or(Error::TargetMismatch)?; + let offset = ::Usize::from_be_bytes(cmd.offset) + .ok_or(Error::TargetMismatch)?; + let mut err: Result<_, 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) + } + }; + + ops.pread(cmd.fd, count, offset, &mut PreadOutput::new(&mut callback)) + .map_err(Error::TargetError)?; + err?; + HandlerStatus::Handled } HostIo::vFileSetfs(cmd) => { diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs index f5a93eaa..66490036 100644 --- a/src/protocol/commands.rs +++ b/src/protocol/commands.rs @@ -223,10 +223,10 @@ commands! { "qXfer:memory-map:read" => _qXfer_memory_map::qXferMemoryMapRead, } - host_io use 'a{ + host_io use 'a { "vFile:open" => _vFile_open::vFileOpen<'a>, "vFile:close" => _vFile_close::vFileClose, - "vFile:pread" => _vFile_pread::vFilePread, + "vFile:pread" => _vFile_pread::vFilePread<'a>, "vFile:setfs" => _vFile_setfs::vFileSetfs, } diff --git a/src/protocol/commands/_vFile_open.rs b/src/protocol/commands/_vFile_open.rs index 5a872f4e..3ff4756e 100644 --- a/src/protocol/commands/_vFile_open.rs +++ b/src/protocol/commands/_vFile_open.rs @@ -1,10 +1,12 @@ use super::prelude::*; +use crate::common::{HostOpenFlags, HostMode}; + #[derive(Debug)] pub struct vFileOpen<'a> { pub filename: &'a [u8], - pub flags: u64, - pub mode: u64, + pub flags: HostOpenFlags, + pub mode: HostMode, } impl<'a> ParseCommand<'a> for vFileOpen<'a> { @@ -18,8 +20,8 @@ impl<'a> ParseCommand<'a> for vFileOpen<'a> { [b':', body @ ..] => { let mut body = body.splitn_mut_no_panic(3, |b| *b == b','); let filename = decode_hex_buf(body.next()?).ok()?; - let flags= decode_hex(body.next()?).ok()?; - let mode= decode_hex(body.next()?).ok()?; + let flags = HostOpenFlags::from_bits_truncate(decode_hex(body.next()?).ok()?); + let mode = HostMode::from_bits_truncate(decode_hex(body.next()?).ok()?); Some(vFileOpen{filename, flags, mode}) }, _ => None, diff --git a/src/protocol/commands/_vFile_pread.rs b/src/protocol/commands/_vFile_pread.rs index efcc6ead..4d19f00a 100644 --- a/src/protocol/commands/_vFile_pread.rs +++ b/src/protocol/commands/_vFile_pread.rs @@ -1,13 +1,13 @@ use super::prelude::*; #[derive(Debug)] -pub struct vFilePread { +pub struct vFilePread<'a> { pub fd: usize, - pub count: usize, - pub offset: usize, + pub count: &'a [u8], + pub offset: &'a [u8], } -impl<'a> ParseCommand<'a> for vFilePread { +impl<'a> ParseCommand<'a> for vFilePread<'a> { fn from_packet(buf: PacketBuf<'a>) -> Option { let body = buf.into_body(); if body.is_empty() { @@ -18,8 +18,8 @@ impl<'a> ParseCommand<'a> for vFilePread { [b':', body @ ..] => { let mut body = body.splitn_mut_no_panic(3, |b| *b == b','); let fd = decode_hex(body.next()?).ok()?; - let count= decode_hex(body.next()?).ok()?; - let offset= decode_hex(body.next()?).ok()?; + let count = decode_hex_buf(body.next()?).ok()?; + let offset = decode_hex_buf(body.next()?).ok()?; Some(vFilePread{fd, count, offset}) }, _ => None, diff --git a/src/protocol/console_output.rs b/src/protocol/console_output.rs index 36c0ce98..9a8d65ad 100644 --- a/src/protocol/console_output.rs +++ b/src/protocol/console_output.rs @@ -30,7 +30,7 @@ impl<'a> fmt::Write for ConsoleOutput<'a> { } impl<'a> ConsoleOutput<'a> { - pub(crate) fn new(callback: &'a mut dyn FnMut(&[u8])) -> ConsoleOutput<'a> { + pub(crate) fn new(callback: &'a mut dyn FnMut(&[u8])) -> Self { ConsoleOutput { #[cfg(feature = "alloc")] buf: Vec::new(), diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index bcbe1df6..a549302c 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -1,16 +1,40 @@ //! Provide Host I/O operations for the target. +use crate::arch::Arch; +use crate::common::{HostMode, HostOpenFlags}; use crate::target::Target; +/// An interface to send pread data back to the GDB client. +pub struct PreadOutput<'a> { + cb: &'a mut dyn FnMut(&[u8]), +} + +impl<'a> PreadOutput<'a> { + pub(crate) fn new(cb: &'a mut dyn FnMut(&[u8])) -> Self { + Self { cb } + } + + /// Write out raw file bytes to the GDB debugger. + pub fn write(&mut self, buf: &[u8]) { + (self.cb)(buf) + } +} + /// Target Extension - Perform I/O operations on host pub trait HostIo: Target { /// Open a file at filename and return a file descriptor for it, or return /// -1 if an error occurs. - fn open(&self, filename: &[u8], flags: u64, mode: u64) -> i64; + fn open(&self, filename: &[u8], flags: HostOpenFlags, mode: HostMode) -> i64; /// Close the open file corresponding to fd and return 0, or -1 if an error /// occurs. fn close(&self, fd: usize) -> i64; /// Read data from the open file corresponding to fd. - fn pread(&self, fd: usize, count: usize, offset: usize) -> &[u8]; + fn pread( + &self, + fd: usize, + count: ::Usize, + offset: ::Usize, + output: &mut PreadOutput<'_>, + ) -> Result<(), Self::Error>; /// Select the filesystem on which vFile operations with filename arguments /// will operate. fn setfs(&self, fd: usize) -> i64; From 774e78016f456b6560ebf8f8d425c9f6ef2a7684 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Thu, 5 Aug 2021 16:07:25 +0800 Subject: [PATCH 03/13] Add missing Host I/O operations --- examples/armv4t/gdb/host_io.rs | 56 ++++-- src/common.rs | 52 ------ src/gdbstub_impl/ext/host_io.rs | 69 +++++-- src/protocol/commands.rs | 4 + src/protocol/commands/_vFile_close.rs | 2 +- src/protocol/commands/_vFile_fstat.rs | 24 +++ src/protocol/commands/_vFile_open.rs | 6 +- src/protocol/commands/_vFile_pread.rs | 2 +- src/protocol/commands/_vFile_pwrite.rs | 28 +++ src/protocol/commands/_vFile_readlink.rs | 23 +++ src/protocol/commands/_vFile_setfs.rs | 6 +- src/protocol/commands/_vFile_unlink.rs | 23 +++ src/protocol/console_output.rs | 2 +- src/target/ext/host_io.rs | 219 +++++++++++++++++++++-- src/target/mod.rs | 10 ++ 15 files changed, 421 insertions(+), 105 deletions(-) create mode 100644 src/protocol/commands/_vFile_fstat.rs create mode 100644 src/protocol/commands/_vFile_pwrite.rs create mode 100644 src/protocol/commands/_vFile_readlink.rs create mode 100644 src/protocol/commands/_vFile_unlink.rs diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index c81962c8..d2dd4ab1 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -2,25 +2,49 @@ use gdbstub::target; use crate::emu::Emu; -use gdbstub::common::{HostMode, HostOpenFlags}; -use gdbstub::target::ext::host_io::PreadOutput; +use gdbstub::target::ext::host_io::{HostIoOutput, HostMode, HostOpenFlags}; +use gdbstub::target::TargetResult; impl target::ext::host_io::HostIo for Emu { - fn open(&self, filename: &[u8], _flags: HostOpenFlags, _mode: HostMode) -> i64 { + #[inline(always)] + fn enable_open(&mut self) -> Option> { + Some(self) + } + + #[inline(always)] + fn enable_pread(&mut self) -> Option> { + Some(self) + } + + #[inline(always)] + fn enable_close(&mut self) -> Option> { + Some(self) + } +} + +impl target::ext::host_io::HostIoOpen for Emu { + fn open( + &mut self, + filename: &[u8], + _flags: HostOpenFlags, + _mode: HostMode, + ) -> TargetResult { if filename == b"/proc/1/maps" { - 1 + Ok(1) } else { - -1 + Ok(-1) } } +} +impl target::ext::host_io::HostIoPread for Emu { fn pread( - &self, - fd: usize, + &mut self, + fd: i32, count: u32, offset: u32, - output: &mut PreadOutput<'_>, - ) -> Result<(), Self::Error> { + output: HostIoOutput<'_>, + ) -> TargetResult<(), Self> { if fd == 1 { let maps = b"0x55550000-0x55550078 r-x 0 0 0\n"; let len = maps.len(); @@ -29,19 +53,17 @@ impl target::ext::host_io::HostIo for Emu { output.write(&maps[offset.min(len)..(offset + count).min(len)]); Ok(()) } else { - Err("pread failed") + Err(().into()) } } +} - fn close(&self, fd: usize) -> i64 { +impl target::ext::host_io::HostIoClose for Emu { + fn close(&mut self, fd: i32) -> TargetResult { if fd == 1 { - 0 + Ok(0) } else { - -1 + Ok(-1) } } - - fn setfs(&self, _fd: usize) -> i64 { - 0 - } } diff --git a/src/common.rs b/src/common.rs index c1779ccd..d9ba55cc 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,59 +1,7 @@ //! Common types and definitions. -use bitflags::bitflags; - /// Thread ID pub type Tid = core::num::NonZeroUsize; /// Process ID pub type Pid = core::num::NonZeroUsize; - -bitflags! { - // The read/write flags below may look a little weird, but that is the way - // they are defined in the protocol. - /// Host flags for opening files. - pub struct HostOpenFlags: u32 { - /// A read-only file. - const O_RDONLY = 0x0; - /// A write-only file. - const O_WRONLY = 0x1; - /// A read-write file. - const O_RDWR = 0x2; - /// Append to an existing file. - const O_APPEND = 0x8; - /// Create a non-existent file. - const O_CREAT = 0x200; - /// Truncate an existing file. - const O_TRUNC = 0x400; - /// Exclusive access. - const O_EXCL = 0x800; - } -} - -bitflags! { - /// Host file permissions. - pub struct HostMode: u32 { - /// A regular file. - const S_IFREG = 0o100000; - /// A directory. - const S_IFDIR = 0o40000; - /// User read permissions. - const S_IRUSR = 0o400; - /// User write permissions. - const S_IWUSR = 0o200; - /// User execute permissions. - const S_IXUSR = 0o100; - /// Group read permissions. - const S_IRGRP = 0o40; - /// Group write permissions - const S_IWGRP = 0o20; - /// Group execute permissions. - const S_IXGRP = 0o10; - /// World read permissions. - const S_IROTH = 0o4; - /// World write permissions - const S_IWOTH = 0o2; - /// World execute permissions. - const S_IXOTH = 0o1; - } -} diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index 696d2ba4..2dab7d0c 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -1,7 +1,7 @@ use super::prelude::*; use crate::arch::Arch; use crate::protocol::commands::ext::HostIo; -use crate::target::ext::host_io::PreadOutput; +use crate::target::ext::host_io::HostIoOutput; impl GdbStubImpl { pub(crate) fn handle_host_io( @@ -18,19 +18,21 @@ impl GdbStubImpl { crate::__dead_code_marker!("host_io", "impl"); let handler_status = match command { - HostIo::vFileOpen(cmd) => { - let ret = ops.open(cmd.filename, cmd.flags, cmd.mode); + HostIo::vFileOpen(cmd) if ops.enable_open().is_some() => { + let ops = ops.enable_open().unwrap(); + let ret = ops.open(cmd.filename, cmd.flags, cmd.mode).handle_error()?; res.write_str("F")?; res.write_num(ret)?; HandlerStatus::Handled } - HostIo::vFileClose(cmd) => { - let ret = ops.close(cmd.fd); + HostIo::vFileClose(cmd) if ops.enable_close().is_some() => { + let ops = ops.enable_close().unwrap(); + let ret = ops.close(cmd.fd).handle_error()?; res.write_str("F")?; res.write_num(ret)?; HandlerStatus::Handled } - HostIo::vFilePread(cmd) => { + HostIo::vFilePread(cmd) if ops.enable_pread().is_some() => { let count = ::Usize::from_be_bytes(cmd.count) .ok_or(Error::TargetMismatch)?; let offset = ::Usize::from_be_bytes(cmd.offset) @@ -50,18 +52,65 @@ impl GdbStubImpl { } }; - ops.pread(cmd.fd, count, offset, &mut PreadOutput::new(&mut callback)) - .map_err(Error::TargetError)?; + let ops = ops.enable_pread().unwrap(); + ops.pread(cmd.fd, count, offset, HostIoOutput::new(&mut callback)) + .handle_error()?; err?; HandlerStatus::Handled } - HostIo::vFileSetfs(cmd) => { - let ret = ops.setfs(cmd.fd); + HostIo::vFilePwrite(cmd) if ops.enable_pwrite().is_some() => { + let offset = ::Usize::from_be_bytes(cmd.offset) + .ok_or(Error::TargetMismatch)?; + let ops = ops.enable_pwrite().unwrap(); + let ret = ops.pwrite(cmd.fd, offset, cmd.data).handle_error()?; res.write_str("F")?; res.write_num(ret)?; HandlerStatus::Handled } + HostIo::vFileFstat(cmd) if ops.enable_fstat().is_some() => { + let mut err: Result<_, 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_fstat().unwrap(); + ops.fstat(cmd.fd, HostIoOutput::new(&mut callback)) + .handle_error()?; + err?; + + HandlerStatus::Handled + } + HostIo::vFileUnlink(cmd) if ops.enable_unlink().is_some() => { + let ops = ops.enable_unlink().unwrap(); + let ret = ops.unlink(cmd.filename).handle_error()?; + res.write_str("F")?; + res.write_num(ret)?; + HandlerStatus::Handled + } + HostIo::vFileReadlink(cmd) if ops.enable_readlink().is_some() => { + let ops = ops.enable_readlink().unwrap(); + let ret = ops.readlink(cmd.filename).handle_error()?; + res.write_str("F")?; + res.write_num(ret)?; + HandlerStatus::Handled + } + HostIo::vFileSetfs(cmd) if ops.enable_setfs().is_some() => { + let ops = ops.enable_setfs().unwrap(); + ops.setfs(cmd.pid).handle_error()?; + HandlerStatus::Handled + } + _ => HandlerStatus::Handled, }; Ok(handler_status) diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs index 66490036..843b4cba 100644 --- a/src/protocol/commands.rs +++ b/src/protocol/commands.rs @@ -227,6 +227,10 @@ commands! { "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, } diff --git a/src/protocol/commands/_vFile_close.rs b/src/protocol/commands/_vFile_close.rs index 75d4fb4c..3b511279 100644 --- a/src/protocol/commands/_vFile_close.rs +++ b/src/protocol/commands/_vFile_close.rs @@ -2,7 +2,7 @@ use super::prelude::*; #[derive(Debug)] pub struct vFileClose { - pub fd: usize, + pub fd: i32, } impl<'a> ParseCommand<'a> for vFileClose { diff --git a/src/protocol/commands/_vFile_fstat.rs b/src/protocol/commands/_vFile_fstat.rs new file mode 100644 index 00000000..80e6ca93 --- /dev/null +++ b/src/protocol/commands/_vFile_fstat.rs @@ -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 { + 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, + } + } +} diff --git a/src/protocol/commands/_vFile_open.rs b/src/protocol/commands/_vFile_open.rs index 3ff4756e..5822bed9 100644 --- a/src/protocol/commands/_vFile_open.rs +++ b/src/protocol/commands/_vFile_open.rs @@ -1,6 +1,6 @@ use super::prelude::*; -use crate::common::{HostOpenFlags, HostMode}; +use crate::target::ext::host_io::{HostOpenFlags, HostMode}; #[derive(Debug)] pub struct vFileOpen<'a> { @@ -20,8 +20,8 @@ impl<'a> ParseCommand<'a> for vFileOpen<'a> { [b':', body @ ..] => { let mut body = body.splitn_mut_no_panic(3, |b| *b == b','); let filename = decode_hex_buf(body.next()?).ok()?; - let flags = HostOpenFlags::from_bits_truncate(decode_hex(body.next()?).ok()?); - let mode = HostMode::from_bits_truncate(decode_hex(body.next()?).ok()?); + let flags = HostOpenFlags::from_bits(decode_hex(body.next()?).ok()?).unwrap(); + let mode = HostMode::from_bits(decode_hex(body.next()?).ok()?).unwrap(); Some(vFileOpen{filename, flags, mode}) }, _ => None, diff --git a/src/protocol/commands/_vFile_pread.rs b/src/protocol/commands/_vFile_pread.rs index 4d19f00a..df9b05af 100644 --- a/src/protocol/commands/_vFile_pread.rs +++ b/src/protocol/commands/_vFile_pread.rs @@ -2,7 +2,7 @@ use super::prelude::*; #[derive(Debug)] pub struct vFilePread<'a> { - pub fd: usize, + pub fd: i32, pub count: &'a [u8], pub offset: &'a [u8], } diff --git a/src/protocol/commands/_vFile_pwrite.rs b/src/protocol/commands/_vFile_pwrite.rs new file mode 100644 index 00000000..94df1792 --- /dev/null +++ b/src/protocol/commands/_vFile_pwrite.rs @@ -0,0 +1,28 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFilePwrite<'a> { + pub fd: i32, + pub offset: &'a [u8], + pub data: &'a [u8], +} + +impl<'a> ParseCommand<'a> for vFilePwrite<'a> { + fn from_packet(buf: PacketBuf<'a>) -> Option { + 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()?; + let offset = decode_hex_buf(body.next()?).ok()?; + let data = decode_hex_buf(body.next()?).ok()?; + Some(vFilePwrite{fd, offset, data}) + }, + _ => None, + } + } +} diff --git a/src/protocol/commands/_vFile_readlink.rs b/src/protocol/commands/_vFile_readlink.rs new file mode 100644 index 00000000..1223bed5 --- /dev/null +++ b/src/protocol/commands/_vFile_readlink.rs @@ -0,0 +1,23 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFileReadlink<'a> { + pub filename: &'a [u8], +} + +impl<'a> ParseCommand<'a> for vFileReadlink<'a> { + fn from_packet(buf: PacketBuf<'a>) -> Option { + let body = buf.into_body(); + if body.is_empty() { + return None; + } + + match body { + [b':', body @ ..] => { + let filename = decode_hex_buf(body).ok()?; + Some(vFileReadlink{filename}) + }, + _ => None, + } + } +} diff --git a/src/protocol/commands/_vFile_setfs.rs b/src/protocol/commands/_vFile_setfs.rs index 9891616f..0731fb0a 100644 --- a/src/protocol/commands/_vFile_setfs.rs +++ b/src/protocol/commands/_vFile_setfs.rs @@ -2,7 +2,7 @@ use super::prelude::*; #[derive(Debug)] pub struct vFileSetfs { - pub fd: usize, + pub pid: usize, } impl<'a> ParseCommand<'a> for vFileSetfs { @@ -14,8 +14,8 @@ impl<'a> ParseCommand<'a> for vFileSetfs { match body { [b':', body @ ..] => { - let fd = decode_hex(body).ok()?; - Some(vFileSetfs{fd}) + let pid = decode_hex(body).ok()?; + Some(vFileSetfs{pid}) }, _ => None, } diff --git a/src/protocol/commands/_vFile_unlink.rs b/src/protocol/commands/_vFile_unlink.rs new file mode 100644 index 00000000..a7137b5b --- /dev/null +++ b/src/protocol/commands/_vFile_unlink.rs @@ -0,0 +1,23 @@ +use super::prelude::*; + +#[derive(Debug)] +pub struct vFileUnlink<'a> { + pub filename: &'a [u8], +} + +impl<'a> ParseCommand<'a> for vFileUnlink<'a> { + fn from_packet(buf: PacketBuf<'a>) -> Option { + let body = buf.into_body(); + if body.is_empty() { + return None; + } + + match body { + [b':', body @ ..] => { + let filename = decode_hex_buf(body).ok()?; + Some(vFileUnlink{filename}) + }, + _ => None, + } + } +} diff --git a/src/protocol/console_output.rs b/src/protocol/console_output.rs index 9a8d65ad..36c0ce98 100644 --- a/src/protocol/console_output.rs +++ b/src/protocol/console_output.rs @@ -30,7 +30,7 @@ impl<'a> fmt::Write for ConsoleOutput<'a> { } impl<'a> ConsoleOutput<'a> { - pub(crate) fn new(callback: &'a mut dyn FnMut(&[u8])) -> Self { + pub(crate) fn new(callback: &'a mut dyn FnMut(&[u8])) -> ConsoleOutput<'a> { ConsoleOutput { #[cfg(feature = "alloc")] buf: Vec::new(), diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index a549302c..7e616a73 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -1,43 +1,228 @@ //! Provide Host I/O operations for the target. use crate::arch::Arch; -use crate::common::{HostMode, HostOpenFlags}; -use crate::target::Target; +use crate::target::{Target, TargetResult}; +use bitflags::bitflags; + +bitflags! { + /// Host flags for opening files. + /// [Open Flags]: https://sourceware.org/gdb/onlinedocs/gdb/Open-Flags.html + pub struct HostOpenFlags: u32 { + /// A read-only file. + const O_RDONLY = 0x0; + /// A write-only file. + const O_WRONLY = 0x1; + /// A read-write file. + const O_RDWR = 0x2; + /// Append to an existing file. + const O_APPEND = 0x8; + /// Create a non-existent file. + const O_CREAT = 0x200; + /// Truncate an existing file. + const O_TRUNC = 0x400; + /// Exclusive access. + const O_EXCL = 0x800; + } +} + +bitflags! { + /// Host file permissions. + /// [mode_t Values]: https://sourceware.org/gdb/onlinedocs/gdb/mode_005ft-Values.html + pub struct HostMode: u32 { + /// A regular file. + const S_IFREG = 0o100000; + /// A directory. + const S_IFDIR = 0o40000; + /// User read permissions. + const S_IRUSR = 0o400; + /// User write permissions. + const S_IWUSR = 0o200; + /// User execute permissions. + const S_IXUSR = 0o100; + /// Group read permissions. + const S_IRGRP = 0o40; + /// Group write permissions + const S_IWGRP = 0o20; + /// Group execute permissions. + const S_IXGRP = 0o10; + /// World read permissions. + const S_IROTH = 0o4; + /// World write permissions + const S_IWOTH = 0o2; + /// World execute permissions. + const S_IXOTH = 0o1; + } +} /// An interface to send pread data back to the GDB client. -pub struct PreadOutput<'a> { +pub struct HostIoOutput<'a> { cb: &'a mut dyn FnMut(&[u8]), } -impl<'a> PreadOutput<'a> { +impl<'a> HostIoOutput<'a> { pub(crate) fn new(cb: &'a mut dyn FnMut(&[u8])) -> Self { Self { cb } } /// Write out raw file bytes to the GDB debugger. - pub fn write(&mut self, buf: &[u8]) { + pub fn write(self, buf: &[u8]) { (self.cb)(buf) } } /// Target Extension - Perform I/O operations on host pub trait HostIo: Target { - /// Open a file at filename and return a file descriptor for it, or return - /// -1 if an error occurs. - fn open(&self, filename: &[u8], flags: HostOpenFlags, mode: HostMode) -> i64; + /// Enable open operation. + #[inline(always)] + fn enable_open(&mut self) -> Option> { + None + } + /// Enable close operation. + #[inline(always)] + fn enable_close(&mut self) -> Option> { + None + } + /// Enable pread operation. + #[inline(always)] + fn enable_pread(&mut self) -> Option> { + None + } + /// Enable pwrite operation. + #[inline(always)] + fn enable_pwrite(&mut self) -> Option> { + None + } + /// Enable fstat operation. + #[inline(always)] + fn enable_fstat(&mut self) -> Option> { + None + } + /// Enable unlink operation. + #[inline(always)] + fn enable_unlink(&mut self) -> Option> { + None + } + /// Enable readlink operation. + #[inline(always)] + fn enable_readlink(&mut self) -> Option> { + None + } + /// Enable setfs operation. + #[inline(always)] + fn enable_setfs(&mut self) -> Option> { + None + } +} + +define_ext!(HostIoOps, HostIo); + +/// Nested Target Extension - Host I/O open operation. +pub trait HostIoOpen: HostIo { + /// Close the open file corresponding to fd and return 0, or -1 if an error + /// occurs. + fn open( + &mut self, + filename: &[u8], + flags: HostOpenFlags, + mode: HostMode, + ) -> TargetResult; +} + +define_ext!(HostIoOpenOps, HostIoOpen); + +/// Nested Target Extension - Host I/O close operation. +pub trait HostIoClose: HostIo { /// Close the open file corresponding to fd and return 0, or -1 if an error /// occurs. - fn close(&self, fd: usize) -> i64; - /// Read data from the open file corresponding to fd. + fn close(&mut self, fd: i32) -> TargetResult; +} + +define_ext!(HostIoCloseOps, HostIoClose); + +/// Nested Target Extension - Host I/O pread operation. +pub trait HostIoPread: HostIo { + /// Read data from the open file corresponding to fd. Up to count bytes will + /// be read from the file, starting at offset relative to the start of the + /// file. The target may read fewer bytes; common reasons include packet + /// size limits and an end-of-file condition. The number of bytes read is + /// returned. Zero should only be returned for a successful read at the end + /// of the file, or if count was zero. fn pread( - &self, - fd: usize, + &mut self, + fd: i32, count: ::Usize, offset: ::Usize, - output: &mut PreadOutput<'_>, - ) -> Result<(), Self::Error>; + output: HostIoOutput<'_>, + ) -> TargetResult<(), Self>; +} + +define_ext!(HostIoPreadOps, HostIoPread); + +/// Nested Target Extension - Host I/O pwrite operation. +pub trait HostIoPwrite: HostIo { + /// Write data (a binary buffer) to the open file corresponding to fd. Start + /// the write at offset from the start of the file. Unlike many write system + /// calls, there is no separate count argument; the length of data in the + /// packet is used. ‘vFile:pwrite’ returns the number of bytes written, + /// which may be shorter than the length of data, or -1 if an error + /// occurred. + fn pwrite( + &mut self, + fd: i32, + offset: ::Usize, + data: &[u8], + ) -> TargetResult; +} + +define_ext!(HostIoPwriteOps, HostIoPwrite); + +/// Nested Target Extension - Host I/O fstat operation. +pub trait HostIoFstat: HostIo { + /// Get information about the open file corresponding to fd. On success the + /// information is returned as a binary attachment and the return value is + /// the size of this attachment in bytes. If an error occurs the return + /// value is -1. + fn fstat(&mut self, fd: i32, output: HostIoOutput<'_>) -> TargetResult; +} + +define_ext!(HostIoFstatOps, HostIoFstat); + +/// Nested Target Extension - Host I/O unlink operation. +pub trait HostIoUnlink: HostIo { + /// Delete the file at filename on the target. Return 0, or -1 if an error + /// occurs. The filename is a string. + fn unlink(&mut self, filename: &[u8]) -> TargetResult; +} + +define_ext!(HostIoUnlinkOps, HostIoUnlink); + +/// Nested Target Extension - Host I/O readlink operation. +pub trait HostIoReadlink: HostIo { + /// Read value of symbolic link filename on the target. Return the number of + /// bytes read, or -1 if an error occurs. + + /// The data read should be returned as a binary attachment on success. If + /// zero bytes were read, the response should include an empty binary + /// attachment (i.e. a trailing semicolon). The return value is the number + /// of target bytes read; the binary attachment may be longer if some + /// characters were escaped. + fn readlink(&mut self, filename: &[u8]) -> TargetResult; +} + +define_ext!(HostIoReadlinkOps, HostIoReadlink); + +/// Nested Target Extension - Host I/O setfs operation. +pub trait HostIoSetfs: HostIo { /// Select the filesystem on which vFile operations with filename arguments - /// will operate. - fn setfs(&self, fd: usize) -> i64; + /// will operate. This is required for GDB to be able to access files on + /// remote targets where the remote stub does not share a common filesystem + /// with the inferior(s). + + /// If pid is nonzero, select the filesystem as seen by process pid. If pid + /// is zero, select the filesystem as seen by the remote stub. Return 0 on + /// success, or -1 if an error occurs. If vFile:setfs: indicates success, + /// the selected filesystem remains selected until the next successful + /// vFile:setfs: operation. + fn setfs(&mut self, pid: usize) -> TargetResult; } -define_ext!(HostIoOps, HostIo); +define_ext!(HostIoSetfsOps, HostIoSetfs); diff --git a/src/target/mod.rs b/src/target/mod.rs index c2516291..7c78ce80 100644 --- a/src/target/mod.rs +++ b/src/target/mod.rs @@ -399,6 +399,16 @@ macro_rules! impl_dyn_target { (**self).extended_mode() } + #[inline(always)] + fn host_io(&mut self) -> Option> { + (**self).host_io() + } + + #[inline(always)] + fn memory_map(&mut self) -> Option> { + (**self).memory_map() + } + #[inline(always)] fn section_offsets(&mut self) -> Option> { (**self).section_offsets() From 1e750db0aad03cf8c869edd9e19a2f6c15fe9873 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Fri, 6 Aug 2021 16:23:14 +0800 Subject: [PATCH 04/13] Some fixes --- examples/armv4t/gdb/host_io.rs | 12 +- src/gdbstub_impl/ext/host_io.rs | 33 ++---- src/protocol/commands/_vFile_setfs.rs | 11 +- src/target/ext/host_io.rs | 153 +++++++++++++++++++------- src/target/mod.rs | 1 + 5 files changed, 138 insertions(+), 72 deletions(-) diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index d2dd4ab1..2deaef42 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -2,7 +2,7 @@ use gdbstub::target; use crate::emu::Emu; -use gdbstub::target::ext::host_io::{HostIoOutput, HostMode, HostOpenFlags}; +use gdbstub::target::ext::host_io::{HostMode, HostOpenFlags, PreadOutput, PreadToken}; use gdbstub::target::TargetResult; impl target::ext::host_io::HostIo for Emu { @@ -29,6 +29,7 @@ impl target::ext::host_io::HostIoOpen for Emu { _flags: HostOpenFlags, _mode: HostMode, ) -> TargetResult { + // Support `info proc mappings` command if filename == b"/proc/1/maps" { Ok(1) } else { @@ -38,20 +39,19 @@ impl target::ext::host_io::HostIoOpen for Emu { } impl target::ext::host_io::HostIoPread for Emu { - fn pread( + fn pread<'a>( &mut self, fd: i32, count: u32, offset: u32, - output: HostIoOutput<'_>, - ) -> TargetResult<(), Self> { + output: PreadOutput<'a>, + ) -> TargetResult, 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; - output.write(&maps[offset.min(len)..(offset + count).min(len)]); - Ok(()) + Ok(output.write(&maps[offset.min(len)..(offset + count).min(len)])) } else { Err(().into()) } diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index 2dab7d0c..ca62cbfb 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -1,7 +1,7 @@ use super::prelude::*; use crate::arch::Arch; use crate::protocol::commands::ext::HostIo; -use crate::target::ext::host_io::HostIoOutput; +use crate::target::ext::host_io::{HostStat, PreadOutput}; impl GdbStubImpl { pub(crate) fn handle_host_io( @@ -53,7 +53,7 @@ impl GdbStubImpl { }; let ops = ops.enable_pread().unwrap(); - ops.pread(cmd.fd, count, offset, HostIoOutput::new(&mut callback)) + ops.pread(cmd.fd, count, offset, PreadOutput::new(&mut callback)) .handle_error()?; err?; @@ -69,26 +69,15 @@ impl GdbStubImpl { HandlerStatus::Handled } HostIo::vFileFstat(cmd) if ops.enable_fstat().is_some() => { - let mut err: Result<_, 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_fstat().unwrap(); - ops.fstat(cmd.fd, HostIoOutput::new(&mut callback)) - .handle_error()?; - err?; - + let stat = ops.fstat(cmd.fd).handle_error()?; + let size = core::mem::size_of_val(&stat); + let p: *const HostStat = &stat; + let p: *const u8 = p as *const u8; + res.write_str("F")?; + res.write_num(size)?; + res.write_str(";")?; + res.write_binary(unsafe { core::slice::from_raw_parts(p, size) })?; HandlerStatus::Handled } HostIo::vFileUnlink(cmd) if ops.enable_unlink().is_some() => { @@ -107,7 +96,7 @@ impl GdbStubImpl { } HostIo::vFileSetfs(cmd) if ops.enable_setfs().is_some() => { let ops = ops.enable_setfs().unwrap(); - ops.setfs(cmd.pid).handle_error()?; + ops.setfs(cmd.fs).handle_error()?; HandlerStatus::Handled } _ => HandlerStatus::Handled, diff --git a/src/protocol/commands/_vFile_setfs.rs b/src/protocol/commands/_vFile_setfs.rs index 0731fb0a..71b680af 100644 --- a/src/protocol/commands/_vFile_setfs.rs +++ b/src/protocol/commands/_vFile_setfs.rs @@ -1,8 +1,10 @@ use super::prelude::*; +use crate::target::ext::host_io::FsKind; +use core::num::NonZeroUsize; #[derive(Debug)] pub struct vFileSetfs { - pub pid: usize, + pub fs: FsKind, } impl<'a> ParseCommand<'a> for vFileSetfs { @@ -14,8 +16,11 @@ impl<'a> ParseCommand<'a> for vFileSetfs { match body { [b':', body @ ..] => { - let pid = decode_hex(body).ok()?; - Some(vFileSetfs{pid}) + let fs = match decode_hex(body).ok()? { + 0 => FsKind::Stub, + pid => FsKind::Pid(NonZeroUsize::new(pid).unwrap()), + }; + Some(vFileSetfs{fs}) }, _ => None, } diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index 7e616a73..57ca7c62 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -5,7 +5,9 @@ use bitflags::bitflags; bitflags! { /// Host flags for opening files. - /// [Open Flags]: https://sourceware.org/gdb/onlinedocs/gdb/Open-Flags.html + /// + /// Extracted from the GDB documentation at + /// [Open Flags](https://sourceware.org/gdb/current/onlinedocs/gdb/Open-Flags.html#Open-Flags) pub struct HostOpenFlags: u32 { /// A read-only file. const O_RDONLY = 0x0; @@ -26,7 +28,9 @@ bitflags! { bitflags! { /// Host file permissions. - /// [mode_t Values]: https://sourceware.org/gdb/onlinedocs/gdb/mode_005ft-Values.html + /// + /// Extracted from the GDB documentation at + /// [mode_t Values](https://sourceware.org/gdb/current/onlinedocs/gdb/mode_005ft-Values.html#mode_005ft-Values) pub struct HostMode: u32 { /// A regular file. const S_IFREG = 0o100000; @@ -53,19 +57,71 @@ bitflags! { } } +/// Data returned by a host fstat request. +/// +/// Extracted from the GDB documentation at +/// [struct stat](https://sourceware.org/gdb/current/onlinedocs/gdb/struct-stat.html#struct-stat) +#[derive(Debug)] +pub struct HostStat { + /// The device. + pub st_dev: u32, + /// The inode. + pub st_ino: u32, + /// Protection bits. + pub st_mode: HostMode, + /// The number of hard links. + pub st_nlink: u32, + /// The user id of the owner. + pub st_uid: u32, + /// The group id of the owner. + pub st_gid: u32, + /// The device type, if an inode device. + pub st_rdev: u32, + /// The size of the file in bytes. + pub st_size: u64, + /// The blocksize for the filesystem. + pub st_blksize: u64, + /// The number of blocks allocated. + pub st_blocks: u64, + /// The last time the file was accessed, in seconds since the epoch. + pub st_atime: u32, + /// The last time the file was modified, in seconds since the epoch. + pub st_mtime: u32, + /// The last time the file was changed, in seconds since the epoch. + pub st_ctime: u32, +} + +/// Select the filesystem vFile operations will operate on. Used by vFile setfs +/// command. +#[derive(Debug)] +pub enum FsKind { + /// select the filesystem as seen by the remote stub + Stub, + /// select the filesystem as seen by process pid + Pid(crate::common::Pid), +} + +/// Token to ensure PreadOutput is used +pub struct PreadToken<'a>(core::marker::PhantomData<&'a *mut ()>); + /// An interface to send pread data back to the GDB client. -pub struct HostIoOutput<'a> { +pub struct PreadOutput<'a> { cb: &'a mut dyn FnMut(&[u8]), + token: PreadToken<'a>, } -impl<'a> HostIoOutput<'a> { +impl<'a> PreadOutput<'a> { pub(crate) fn new(cb: &'a mut dyn FnMut(&[u8])) -> Self { - Self { cb } + Self { + cb, + token: PreadToken(core::marker::PhantomData), + } } /// Write out raw file bytes to the GDB debugger. - pub fn write(self, buf: &[u8]) { - (self.cb)(buf) + pub fn write(self, buf: &[u8]) -> PreadToken<'a> { + (self.cb)(buf); + self.token } } @@ -117,8 +173,12 @@ define_ext!(HostIoOps, HostIo); /// Nested Target Extension - Host I/O open operation. pub trait HostIoOpen: HostIo { - /// Close the open file corresponding to fd and return 0, or -1 if an error - /// occurs. + /// Open a file at filename and return a file descriptor for it, or return + /// -1 if an error occurs. + /// + /// The filename is a string, flags is an integer indicating a mask of open + /// flags (see [`HostOpenFlags`]), and mode is an integer indicating a mask + /// of mode bits to use if the file is created (see [`HostMode`]). fn open( &mut self, filename: &[u8], @@ -140,31 +200,38 @@ define_ext!(HostIoCloseOps, HostIoClose); /// Nested Target Extension - Host I/O pread operation. pub trait HostIoPread: HostIo { - /// Read data from the open file corresponding to fd. Up to count bytes will - /// be read from the file, starting at offset relative to the start of the - /// file. The target may read fewer bytes; common reasons include packet - /// size limits and an end-of-file condition. The number of bytes read is - /// returned. Zero should only be returned for a successful read at the end - /// of the file, or if count was zero. - fn pread( + /// Read data from the open file corresponding to fd. + /// + /// Up to count bytes will be read from the file, starting at offset + /// relative to the start of the file. + /// + /// The target may read fewer bytes; common reasons include packet size + /// limits and an end-of-file condition. + /// + /// The number of bytes read is returned. Zero should only be returned for a + /// successful read at the end of the file, or if count was zero. + fn pread<'a>( &mut self, fd: i32, count: ::Usize, offset: ::Usize, - output: HostIoOutput<'_>, - ) -> TargetResult<(), Self>; + output: PreadOutput<'a>, + ) -> TargetResult, Self>; } define_ext!(HostIoPreadOps, HostIoPread); /// Nested Target Extension - Host I/O pwrite operation. pub trait HostIoPwrite: HostIo { - /// Write data (a binary buffer) to the open file corresponding to fd. Start - /// the write at offset from the start of the file. Unlike many write system - /// calls, there is no separate count argument; the length of data in the - /// packet is used. ‘vFile:pwrite’ returns the number of bytes written, - /// which may be shorter than the length of data, or -1 if an error - /// occurred. + /// Write data (a binary buffer) to the open file corresponding to fd. + /// + /// Start the write at offset from the start of the file. + /// + /// Unlike many write system calls, there is no separate count argument; the + /// length of data in the packet is used. + /// + /// ‘vFile:pwrite’ returns the number of bytes written, which may be shorter + /// than the length of data, or -1 if an error occurred. fn pwrite( &mut self, fd: i32, @@ -177,19 +244,21 @@ define_ext!(HostIoPwriteOps, HostIoPwrite); /// Nested Target Extension - Host I/O fstat operation. pub trait HostIoFstat: HostIo { - /// Get information about the open file corresponding to fd. On success the - /// information is returned as a binary attachment and the return value is - /// the size of this attachment in bytes. If an error occurs the return - /// value is -1. - fn fstat(&mut self, fd: i32, output: HostIoOutput<'_>) -> TargetResult; + /// Get information about the open file corresponding to fd. + /// + /// On success the information is returned as a binary attachment and the + /// return value is the size of this attachment in bytes. If an error occurs + /// the return value is -1. + fn fstat(&mut self, fd: i32) -> TargetResult; } define_ext!(HostIoFstatOps, HostIoFstat); /// Nested Target Extension - Host I/O unlink operation. pub trait HostIoUnlink: HostIo { - /// Delete the file at filename on the target. Return 0, or -1 if an error - /// occurs. The filename is a string. + /// Delete the file at filename on the target. + /// + /// Return 0, or -1 if an error occurs. fn unlink(&mut self, filename: &[u8]) -> TargetResult; } @@ -199,12 +268,13 @@ define_ext!(HostIoUnlinkOps, HostIoUnlink); pub trait HostIoReadlink: HostIo { /// Read value of symbolic link filename on the target. Return the number of /// bytes read, or -1 if an error occurs. - + /// /// The data read should be returned as a binary attachment on success. If /// zero bytes were read, the response should include an empty binary - /// attachment (i.e. a trailing semicolon). The return value is the number - /// of target bytes read; the binary attachment may be longer if some - /// characters were escaped. + /// attachment (i.e. a trailing semicolon). + /// + /// The return value is the number of target bytes read; the binary + /// attachment may be longer if some characters were escaped. fn readlink(&mut self, filename: &[u8]) -> TargetResult; } @@ -216,13 +286,14 @@ pub trait HostIoSetfs: HostIo { /// will operate. This is required for GDB to be able to access files on /// remote targets where the remote stub does not share a common filesystem /// with the inferior(s). - + /// /// If pid is nonzero, select the filesystem as seen by process pid. If pid - /// is zero, select the filesystem as seen by the remote stub. Return 0 on - /// success, or -1 if an error occurs. If vFile:setfs: indicates success, - /// the selected filesystem remains selected until the next successful - /// vFile:setfs: operation. - fn setfs(&mut self, pid: usize) -> TargetResult; + /// is zero, select the filesystem as seen by the remote stub. + /// + /// Return 0 on success, or -1 if an error occurs. If vFile:setfs: indicates + /// success, the selected filesystem remains selected until the next + /// successful vFile:setfs: operation. + fn setfs(&mut self, fs: FsKind) -> TargetResult; } define_ext!(HostIoSetfsOps, HostIoSetfs); diff --git a/src/target/mod.rs b/src/target/mod.rs index 7c78ce80..0f262bee 100644 --- a/src/target/mod.rs +++ b/src/target/mod.rs @@ -360,6 +360,7 @@ pub trait Target { } /// Support Host I/O operations. + #[inline(always)] fn host_io(&mut self) -> Option> { None } From 2465d6deec461029ccbc2ef20960d4d6b852e652 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Wed, 11 Aug 2021 17:00:01 +0800 Subject: [PATCH 05/13] Change return type to HostIoResult --- examples/armv4t/gdb/host_io.rs | 21 ++--- src/gdbstub_impl/ext/host_io.rs | 105 +++++++++++++++++------- src/protocol/commands/_vFile_open.rs | 10 +-- src/protocol/commands/_vFile_setfs.rs | 6 +- src/target/ext/host_io.rs | 112 ++++++++++++++++++++++---- 5 files changed, 192 insertions(+), 62 deletions(-) diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index 2deaef42..b99097fd 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -2,8 +2,9 @@ use gdbstub::target; use crate::emu::Emu; -use gdbstub::target::ext::host_io::{HostMode, HostOpenFlags, PreadOutput, PreadToken}; -use gdbstub::target::TargetResult; +use gdbstub::target::ext::host_io::{ + HostIoErrno, HostIoError, HostIoMode, HostIoOpenFlags, HostIoResult, PreadOutput, PreadToken, +}; impl target::ext::host_io::HostIo for Emu { #[inline(always)] @@ -26,14 +27,14 @@ impl target::ext::host_io::HostIoOpen for Emu { fn open( &mut self, filename: &[u8], - _flags: HostOpenFlags, - _mode: HostMode, - ) -> TargetResult { + _flags: HostIoOpenFlags, + _mode: HostIoMode, + ) -> HostIoResult { // Support `info proc mappings` command if filename == b"/proc/1/maps" { Ok(1) } else { - Ok(-1) + Err(HostIoError::Errno(HostIoErrno::EPERM)) } } } @@ -45,7 +46,7 @@ impl target::ext::host_io::HostIoPread for Emu { count: u32, offset: u32, output: PreadOutput<'a>, - ) -> TargetResult, Self> { + ) -> HostIoResult, Self> { if fd == 1 { let maps = b"0x55550000-0x55550078 r-x 0 0 0\n"; let len = maps.len(); @@ -53,17 +54,17 @@ impl target::ext::host_io::HostIoPread for Emu { let offset: usize = offset as usize; Ok(output.write(&maps[offset.min(len)..(offset + count).min(len)])) } else { - Err(().into()) + Err(HostIoError::Errno(HostIoErrno::EPERM)) } } } impl target::ext::host_io::HostIoClose for Emu { - fn close(&mut self, fd: i32) -> TargetResult { + fn close(&mut self, fd: i32) -> HostIoResult { if fd == 1 { Ok(0) } else { - Ok(-1) + Err(HostIoError::Errno(HostIoErrno::EPERM)) } } } diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index ca62cbfb..ef73c580 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -1,7 +1,21 @@ use super::prelude::*; use crate::arch::Arch; use crate::protocol::commands::ext::HostIo; -use crate::target::ext::host_io::{HostStat, PreadOutput}; +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 GdbStubImpl { pub(crate) fn handle_host_io( @@ -20,16 +34,22 @@ impl GdbStubImpl { let handler_status = match command { HostIo::vFileOpen(cmd) if ops.enable_open().is_some() => { let ops = ops.enable_open().unwrap(); - let ret = ops.open(cmd.filename, cmd.flags, cmd.mode).handle_error()?; - res.write_str("F")?; - res.write_num(ret)?; + let result = ops.open(cmd.filename, cmd.flags, cmd.mode); + handle_hostio_result!(result, res, |fd| -> Result<_, 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 ret = ops.close(cmd.fd).handle_error()?; - res.write_str("F")?; - res.write_num(ret)?; + let result = ops.close(cmd.fd); + handle_hostio_result!(result, res, |ret| -> Result<_, Error> { + res.write_str("F")?; + res.write_num(ret)?; + Ok(()) + }); HandlerStatus::Handled } HostIo::vFilePread(cmd) if ops.enable_pread().is_some() => { @@ -53,8 +73,10 @@ impl GdbStubImpl { }; let ops = ops.enable_pread().unwrap(); - ops.pread(cmd.fd, count, offset, PreadOutput::new(&mut callback)) - .handle_error()?; + let result = ops.pread(cmd.fd, count, offset, PreadOutput::new(&mut callback)); + handle_hostio_result!(result, res, |_| -> Result<_, Error> { + Ok(()) + }); err?; HandlerStatus::Handled @@ -63,40 +85,69 @@ impl GdbStubImpl { let offset = ::Usize::from_be_bytes(cmd.offset) .ok_or(Error::TargetMismatch)?; let ops = ops.enable_pwrite().unwrap(); - let ret = ops.pwrite(cmd.fd, offset, cmd.data).handle_error()?; - res.write_str("F")?; - res.write_num(ret)?; + let result = ops.pwrite(cmd.fd, offset, cmd.data); + handle_hostio_result!(result, res, |ret| -> Result<_, 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 stat = ops.fstat(cmd.fd).handle_error()?; - let size = core::mem::size_of_val(&stat); - let p: *const HostStat = &stat; - let p: *const u8 = p as *const u8; - res.write_str("F")?; - res.write_num(size)?; - res.write_str(";")?; - res.write_binary(unsafe { core::slice::from_raw_parts(p, size) })?; + let result = ops.fstat(cmd.fd); + handle_hostio_result!( + result, + res, + |stat: HostStat| -> Result<_, Error> { + let size = core::mem::size_of::(); + 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 ret = ops.unlink(cmd.filename).handle_error()?; - res.write_str("F")?; - res.write_num(ret)?; + let result = ops.unlink(cmd.filename); + handle_hostio_result!(result, res, |ret| -> Result<_, 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 ret = ops.readlink(cmd.filename).handle_error()?; - res.write_str("F")?; - res.write_num(ret)?; + let result = ops.readlink(cmd.filename); + handle_hostio_result!(result, res, |ret| -> Result<_, 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(); - ops.setfs(cmd.fs).handle_error()?; + let result = ops.setfs(cmd.fs); + handle_hostio_result!(result, res, |_| -> Result<_, Error> { + Ok(()) + }); HandlerStatus::Handled } _ => HandlerStatus::Handled, diff --git a/src/protocol/commands/_vFile_open.rs b/src/protocol/commands/_vFile_open.rs index 5822bed9..ae6aca45 100644 --- a/src/protocol/commands/_vFile_open.rs +++ b/src/protocol/commands/_vFile_open.rs @@ -1,12 +1,12 @@ use super::prelude::*; -use crate::target::ext::host_io::{HostOpenFlags, HostMode}; +use crate::target::ext::host_io::{HostIoOpenFlags, HostIoMode}; #[derive(Debug)] pub struct vFileOpen<'a> { pub filename: &'a [u8], - pub flags: HostOpenFlags, - pub mode: HostMode, + pub flags: HostIoOpenFlags, + pub mode: HostIoMode, } impl<'a> ParseCommand<'a> for vFileOpen<'a> { @@ -20,8 +20,8 @@ impl<'a> ParseCommand<'a> for vFileOpen<'a> { [b':', body @ ..] => { let mut body = body.splitn_mut_no_panic(3, |b| *b == b','); let filename = decode_hex_buf(body.next()?).ok()?; - let flags = HostOpenFlags::from_bits(decode_hex(body.next()?).ok()?).unwrap(); - let mode = HostMode::from_bits(decode_hex(body.next()?).ok()?).unwrap(); + 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, diff --git a/src/protocol/commands/_vFile_setfs.rs b/src/protocol/commands/_vFile_setfs.rs index 71b680af..5081089b 100644 --- a/src/protocol/commands/_vFile_setfs.rs +++ b/src/protocol/commands/_vFile_setfs.rs @@ -16,9 +16,9 @@ impl<'a> ParseCommand<'a> for vFileSetfs { match body { [b':', body @ ..] => { - let fs = match decode_hex(body).ok()? { - 0 => FsKind::Stub, - pid => FsKind::Pid(NonZeroUsize::new(pid).unwrap()), + let fs = match NonZeroUsize::new(decode_hex(body).ok()?) { + None => FsKind::Stub, + Some(pid) => FsKind::Pid(pid), }; Some(vFileSetfs{fs}) }, diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index 57ca7c62..6f322842 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -1,6 +1,6 @@ //! Provide Host I/O operations for the target. use crate::arch::Arch; -use crate::target::{Target, TargetResult}; +use crate::target::Target; use bitflags::bitflags; bitflags! { @@ -8,7 +8,7 @@ bitflags! { /// /// Extracted from the GDB documentation at /// [Open Flags](https://sourceware.org/gdb/current/onlinedocs/gdb/Open-Flags.html#Open-Flags) - pub struct HostOpenFlags: u32 { + pub struct HostIoOpenFlags: u32 { /// A read-only file. const O_RDONLY = 0x0; /// A write-only file. @@ -31,7 +31,7 @@ bitflags! { /// /// Extracted from the GDB documentation at /// [mode_t Values](https://sourceware.org/gdb/current/onlinedocs/gdb/mode_005ft-Values.html#mode_005ft-Values) - pub struct HostMode: u32 { + pub struct HostIoMode: u32 { /// A regular file. const S_IFREG = 0o100000; /// A directory. @@ -68,7 +68,7 @@ pub struct HostStat { /// The inode. pub st_ino: u32, /// Protection bits. - pub st_mode: HostMode, + pub st_mode: HostIoMode, /// The number of hard links. pub st_nlink: u32, /// The user id of the owner. @@ -101,7 +101,84 @@ pub enum FsKind { Pid(crate::common::Pid), } -/// Token to ensure PreadOutput is used +/// Errno values for Host I/O operations. +/// +/// Extracted from the GDB documentation at +/// [Errno Values]: https://sourceware.org/gdb/onlinedocs/gdb/Errno-Values.html +#[derive(Debug)] +pub enum HostIoErrno { + /// Operation not permitted (POSIX.1-2001). + EPERM = 1, + /// No such file or directory (POSIX.1-2001). + /// + /// Typically, this error results when a specified pathname does not exist, + /// or one of the components in the directory prefix of a pathname does not + /// exist, or the specified pathname is a dangling symbolic link. + ENOENT = 2, + /// Interrupted function call (POSIX.1-2001); see signal(7). + EINTR = 4, + /// Bad file descriptor (POSIX.1-2001). + EBADF = 9, + /// Permission denied (POSIX.1-2001). + EACCES = 13, + /// Bad address (POSIX.1-2001). + EFAULT = 14, + /// Device or resource busy (POSIX.1-2001). + EBUSY = 16, + /// File exists (POSIX.1-2001). + EEXIST = 17, + /// No such device (POSIX.1-2001). + ENODEV = 19, + /// Not a directory (POSIX.1-2001). + ENOTDIR = 20, + /// Is a directory (POSIX.1-2001). + EISDIR = 21, + /// Invalid argument (POSIX.1-2001). + EINVAL = 22, + /// Too many open files in system (POSIX.1-2001). On Linux, this is probably + /// a result of encountering the /proc/sys/fs/file-max limit (see proc(5)). + ENFILE = 23, + /// Too many open files (POSIX.1-2001). Commonly caused by exceeding the + /// RLIMIT_NOFILE resource limit described in getrlimit(2). + EMFILE = 24, + /// File too large (POSIX.1-2001). + EFBIG = 27, + /// No space left on device (POSIX.1-2001). + ENOSPC = 28, + /// Invalid seek (POSIX.1-2001). + ESPIPE = 29, + /// Read-only filesystem (POSIX.1-2001). + EROFS = 30, + /// Filename too long (POSIX.1-2001). + ENAMETOOLONG = 91, + /// Unknown errno - there may not be a GDB mapping for this value + EUNKNOWN = 9999, +} + +/// The error type for Host I/O operations. +pub enum HostIoError { + /// An operation-specific non-fatal error code. + /// + /// See [`HostIoErrno`] for more details. + Errno(HostIoErrno), + /// A target-specific fatal error. + /// + /// **WARNING:** Returning this error will immediately halt the target's + /// execution and return a `GdbStubError::TargetError` from `GdbStub::run`! + /// + /// Note that the debugging session will will _not_ be terminated, and can + /// be resumed by calling `GdbStub::run` after resolving the error and/or + /// setting up a post-mortem debugging environment. + Fatal(E), +} + +/// A specialized `Result` type for Host I/O operations. Supports reporting +/// non-fatal errors back to the GDB client. +/// +/// See [`HostIoError`] for more details. +pub type HostIoResult = Result::Error>>; + +/// Zero-sized type token that ensures PreadOutput::write is called pub struct PreadToken<'a>(core::marker::PhantomData<&'a *mut ()>); /// An interface to send pread data back to the GDB client. @@ -177,14 +254,15 @@ pub trait HostIoOpen: HostIo { /// -1 if an error occurs. /// /// The filename is a string, flags is an integer indicating a mask of open - /// flags (see [`HostOpenFlags`]), and mode is an integer indicating a mask - /// of mode bits to use if the file is created (see [`HostMode`]). + /// flags (see [`HostIoOpenFlags`]), and mode is an integer indicating a + /// mask of mode bits to use if the file is created (see + /// [`HostIoMode`]). fn open( &mut self, filename: &[u8], - flags: HostOpenFlags, - mode: HostMode, - ) -> TargetResult; + flags: HostIoOpenFlags, + mode: HostIoMode, + ) -> HostIoResult; } define_ext!(HostIoOpenOps, HostIoOpen); @@ -193,7 +271,7 @@ define_ext!(HostIoOpenOps, HostIoOpen); pub trait HostIoClose: HostIo { /// Close the open file corresponding to fd and return 0, or -1 if an error /// occurs. - fn close(&mut self, fd: i32) -> TargetResult; + fn close(&mut self, fd: i32) -> HostIoResult; } define_ext!(HostIoCloseOps, HostIoClose); @@ -216,7 +294,7 @@ pub trait HostIoPread: HostIo { count: ::Usize, offset: ::Usize, output: PreadOutput<'a>, - ) -> TargetResult, Self>; + ) -> HostIoResult, Self>; } define_ext!(HostIoPreadOps, HostIoPread); @@ -237,7 +315,7 @@ pub trait HostIoPwrite: HostIo { fd: i32, offset: ::Usize, data: &[u8], - ) -> TargetResult; + ) -> HostIoResult; } define_ext!(HostIoPwriteOps, HostIoPwrite); @@ -249,7 +327,7 @@ pub trait HostIoFstat: HostIo { /// On success the information is returned as a binary attachment and the /// return value is the size of this attachment in bytes. If an error occurs /// the return value is -1. - fn fstat(&mut self, fd: i32) -> TargetResult; + fn fstat(&mut self, fd: i32) -> HostIoResult; } define_ext!(HostIoFstatOps, HostIoFstat); @@ -259,7 +337,7 @@ pub trait HostIoUnlink: HostIo { /// Delete the file at filename on the target. /// /// Return 0, or -1 if an error occurs. - fn unlink(&mut self, filename: &[u8]) -> TargetResult; + fn unlink(&mut self, filename: &[u8]) -> HostIoResult; } define_ext!(HostIoUnlinkOps, HostIoUnlink); @@ -275,7 +353,7 @@ pub trait HostIoReadlink: HostIo { /// /// The return value is the number of target bytes read; the binary /// attachment may be longer if some characters were escaped. - fn readlink(&mut self, filename: &[u8]) -> TargetResult; + fn readlink(&mut self, filename: &[u8]) -> HostIoResult; } define_ext!(HostIoReadlinkOps, HostIoReadlink); @@ -293,7 +371,7 @@ pub trait HostIoSetfs: HostIo { /// Return 0 on success, or -1 if an error occurs. If vFile:setfs: indicates /// success, the selected filesystem remains selected until the next /// successful vFile:setfs: operation. - fn setfs(&mut self, fs: FsKind) -> TargetResult; + fn setfs(&mut self, fs: FsKind) -> HostIoResult; } define_ext!(HostIoSetfsOps, HostIoSetfs); From 41751fedebbf5ef13adc4a8717a435f7fe68a49d Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Thu, 12 Aug 2021 16:57:31 +0800 Subject: [PATCH 06/13] Improve handle_hostio_result macro --- Cargo.toml | 2 +- examples/armv4t/gdb/host_io.rs | 13 +-- src/gdbstub_impl/ext/host_io.rs | 131 ++++++++++++++----------- src/protocol/commands/_vFile_close.rs | 2 +- src/protocol/commands/_vFile_fstat.rs | 2 +- src/protocol/commands/_vFile_open.rs | 6 +- src/protocol/commands/_vFile_pread.rs | 2 +- src/protocol/commands/_vFile_pwrite.rs | 2 +- src/target/ext/host_io.rs | 87 ++++++++-------- 9 files changed, 125 insertions(+), 122 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6351294e..4960a0be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ categories = ["development-tools::debugging", "embedded", "emulators", "network- exclude = ["examples/**/*.elf", "examples/**/*.o"] [dependencies] -bitflags = "1.2.1" +bitflags = "1.3" cfg-if = "0.1.10" log = "0.4" managed = { version = "0.8", default-features = false } diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index b99097fd..df8c2ce0 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -3,7 +3,8 @@ use gdbstub::target; use crate::emu::Emu; use gdbstub::target::ext::host_io::{ - HostIoErrno, HostIoError, HostIoMode, HostIoOpenFlags, HostIoResult, PreadOutput, PreadToken, + HostIoErrno, HostIoError, HostIoOpenFlags, HostIoOpenMode, HostIoOutput, HostIoResult, + HostIoToken, }; impl target::ext::host_io::HostIo for Emu { @@ -28,7 +29,7 @@ impl target::ext::host_io::HostIoOpen for Emu { &mut self, filename: &[u8], _flags: HostIoOpenFlags, - _mode: HostIoMode, + _mode: HostIoOpenMode, ) -> HostIoResult { // Support `info proc mappings` command if filename == b"/proc/1/maps" { @@ -42,11 +43,11 @@ impl target::ext::host_io::HostIoOpen for Emu { impl target::ext::host_io::HostIoPread for Emu { fn pread<'a>( &mut self, - fd: i32, + fd: u32, count: u32, offset: u32, - output: PreadOutput<'a>, - ) -> HostIoResult, Self> { + output: HostIoOutput<'a>, + ) -> HostIoResult, Self> { if fd == 1 { let maps = b"0x55550000-0x55550078 r-x 0 0 0\n"; let len = maps.len(); @@ -60,7 +61,7 @@ impl target::ext::host_io::HostIoPread for Emu { } impl target::ext::host_io::HostIoClose for Emu { - fn close(&mut self, fd: i32) -> HostIoResult { + fn close(&mut self, fd: u32) -> HostIoResult { if fd == 1 { Ok(0) } else { diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index ef73c580..fd7d3aa4 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -1,22 +1,9 @@ use super::prelude::*; use crate::arch::Arch; use crate::protocol::commands::ext::HostIo; -use crate::target::ext::host_io::{HostIoError, HostStat, PreadOutput}; +use crate::target::ext::host_io::{HostIoError, HostIoOutput, HostIoStat}; 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 GdbStubImpl { pub(crate) fn handle_host_io( &mut self, @@ -31,25 +18,38 @@ impl GdbStubImpl { crate::__dead_code_marker!("host_io", "impl"); + macro_rules! handle_hostio_result { + ( if let Ok($val:pat) = $ret:expr => $callback:block ) => {{ + match $ret { + Ok($val) => $callback, + Err(HostIoError::Errno(errno)) => { + res.write_str("F-1,")?; + res.write_num(errno as i32)?; + } + Err(HostIoError::Fatal(e)) => return Err(GdbStubError::TargetError(e)), + } + }}; + } + 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> { - res.write_str("F")?; - res.write_num(fd)?; - Ok(()) - }); + handle_hostio_result! { + if let Ok(fd) = ops.open(cmd.filename, cmd.flags, cmd.mode) => { + res.write_str("F")?; + res.write_num(fd)?; + } + } 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> { - res.write_str("F")?; - res.write_num(ret)?; - Ok(()) - }); + handle_hostio_result! { + if let Ok(ret) = ops.close(cmd.fd) => { + res.write_str("F")?; + res.write_num(ret)?; + } + } HandlerStatus::Handled } HostIo::vFilePread(cmd) if ops.enable_pread().is_some() => { @@ -73,10 +73,9 @@ impl GdbStubImpl { }; 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> { - Ok(()) - }); + handle_hostio_result! { + if let Ok(_) = ops.pread(cmd.fd, count, offset, HostIoOutput::new(&mut callback)) => {} + }; err?; HandlerStatus::Handled @@ -85,22 +84,19 @@ impl GdbStubImpl { let offset = ::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> { - res.write_str("F")?; - res.write_num(ret)?; - Ok(()) - }); + handle_hostio_result! { + if let Ok(ret) = ops.pwrite(cmd.fd, offset, cmd.data) => { + res.write_str("F")?; + res.write_num(ret)?; + } + }; 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> { - let size = core::mem::size_of::(); + handle_hostio_result! { + if let Ok(stat) = ops.fstat(cmd.fd) => { + let size = core::mem::size_of::(); res.write_str("F")?; res.write_num(size)?; res.write_str(";")?; @@ -117,37 +113,52 @@ impl GdbStubImpl { 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> { - res.write_str("F")?; - res.write_num(ret)?; - Ok(()) - }); + handle_hostio_result! { + if let Ok(ret) = ops.unlink(cmd.filename) => { + res.write_str("F")?; + res.write_num(ret)?; + } + }; HandlerStatus::Handled } HostIo::vFileReadlink(cmd) if ops.enable_readlink().is_some() => { + let mut err: Result<_, 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_readlink().unwrap(); - let result = ops.readlink(cmd.filename); - handle_hostio_result!(result, res, |ret| -> Result<_, Error> { - res.write_str("F")?; - res.write_num(ret)?; - Ok(()) - }); + handle_hostio_result! { + if let Ok(_) = ops.readlink(cmd.filename, HostIoOutput::new(&mut callback)) => {} + }; + err?; + 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> { - Ok(()) - }); + handle_hostio_result! { + if let Ok(ret) = ops.setfs(cmd.fs) => { + res.write_str("F")?; + res.write_num(ret)?; + } + }; HandlerStatus::Handled } _ => HandlerStatus::Handled, diff --git a/src/protocol/commands/_vFile_close.rs b/src/protocol/commands/_vFile_close.rs index 3b511279..8f2e69ad 100644 --- a/src/protocol/commands/_vFile_close.rs +++ b/src/protocol/commands/_vFile_close.rs @@ -2,7 +2,7 @@ use super::prelude::*; #[derive(Debug)] pub struct vFileClose { - pub fd: i32, + pub fd: u32, } impl<'a> ParseCommand<'a> for vFileClose { diff --git a/src/protocol/commands/_vFile_fstat.rs b/src/protocol/commands/_vFile_fstat.rs index 80e6ca93..a73cd5cd 100644 --- a/src/protocol/commands/_vFile_fstat.rs +++ b/src/protocol/commands/_vFile_fstat.rs @@ -2,7 +2,7 @@ use super::prelude::*; #[derive(Debug)] pub struct vFileFstat { - pub fd: i32, + pub fd: u32, } impl<'a> ParseCommand<'a> for vFileFstat { diff --git a/src/protocol/commands/_vFile_open.rs b/src/protocol/commands/_vFile_open.rs index ae6aca45..9c7b9a84 100644 --- a/src/protocol/commands/_vFile_open.rs +++ b/src/protocol/commands/_vFile_open.rs @@ -1,12 +1,12 @@ use super::prelude::*; -use crate::target::ext::host_io::{HostIoOpenFlags, HostIoMode}; +use crate::target::ext::host_io::{HostIoOpenFlags, HostIoOpenMode}; #[derive(Debug)] pub struct vFileOpen<'a> { pub filename: &'a [u8], pub flags: HostIoOpenFlags, - pub mode: HostIoMode, + pub mode: HostIoOpenMode, } impl<'a> ParseCommand<'a> for vFileOpen<'a> { @@ -21,7 +21,7 @@ impl<'a> ParseCommand<'a> for vFileOpen<'a> { 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(); + let mode = HostIoOpenMode::from_bits(decode_hex(body.next()?).ok()?).unwrap(); Some(vFileOpen{filename, flags, mode}) }, _ => None, diff --git a/src/protocol/commands/_vFile_pread.rs b/src/protocol/commands/_vFile_pread.rs index df9b05af..4b795952 100644 --- a/src/protocol/commands/_vFile_pread.rs +++ b/src/protocol/commands/_vFile_pread.rs @@ -2,7 +2,7 @@ use super::prelude::*; #[derive(Debug)] pub struct vFilePread<'a> { - pub fd: i32, + pub fd: u32, pub count: &'a [u8], pub offset: &'a [u8], } diff --git a/src/protocol/commands/_vFile_pwrite.rs b/src/protocol/commands/_vFile_pwrite.rs index 94df1792..c474aa69 100644 --- a/src/protocol/commands/_vFile_pwrite.rs +++ b/src/protocol/commands/_vFile_pwrite.rs @@ -2,7 +2,7 @@ use super::prelude::*; #[derive(Debug)] pub struct vFilePwrite<'a> { - pub fd: i32, + pub fd: u32, pub offset: &'a [u8], pub data: &'a [u8], } diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index 6f322842..44a11862 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -31,7 +31,7 @@ bitflags! { /// /// Extracted from the GDB documentation at /// [mode_t Values](https://sourceware.org/gdb/current/onlinedocs/gdb/mode_005ft-Values.html#mode_005ft-Values) - pub struct HostIoMode: u32 { + pub struct HostIoOpenMode: u32 { /// A regular file. const S_IFREG = 0o100000; /// A directory. @@ -62,13 +62,13 @@ bitflags! { /// Extracted from the GDB documentation at /// [struct stat](https://sourceware.org/gdb/current/onlinedocs/gdb/struct-stat.html#struct-stat) #[derive(Debug)] -pub struct HostStat { +pub struct HostIoStat { /// The device. pub st_dev: u32, /// The inode. pub st_ino: u32, /// Protection bits. - pub st_mode: HostIoMode, + pub st_mode: HostIoOpenMode, /// The number of hard links. pub st_nlink: u32, /// The user id of the owner. @@ -178,25 +178,25 @@ pub enum HostIoError { /// See [`HostIoError`] for more details. pub type HostIoResult = Result::Error>>; -/// Zero-sized type token that ensures PreadOutput::write is called -pub struct PreadToken<'a>(core::marker::PhantomData<&'a *mut ()>); +/// Zero-sized type token that ensures HostIoOutput::write is called +pub struct HostIoToken<'a>(core::marker::PhantomData<&'a *mut ()>); /// An interface to send pread data back to the GDB client. -pub struct PreadOutput<'a> { +pub struct HostIoOutput<'a> { cb: &'a mut dyn FnMut(&[u8]), - token: PreadToken<'a>, + token: HostIoToken<'a>, } -impl<'a> PreadOutput<'a> { +impl<'a> HostIoOutput<'a> { pub(crate) fn new(cb: &'a mut dyn FnMut(&[u8])) -> Self { Self { cb, - token: PreadToken(core::marker::PhantomData), + token: HostIoToken(core::marker::PhantomData), } } /// Write out raw file bytes to the GDB debugger. - pub fn write(self, buf: &[u8]) -> PreadToken<'a> { + pub fn write(self, buf: &[u8]) -> HostIoToken<'a> { (self.cb)(buf); self.token } @@ -251,17 +251,17 @@ define_ext!(HostIoOps, HostIo); /// Nested Target Extension - Host I/O open operation. pub trait HostIoOpen: HostIo { /// Open a file at filename and return a file descriptor for it, or return - /// -1 if an error occurs. + /// [`HostIoError::Errno`] if an error occurs. /// /// The filename is a string, flags is an integer indicating a mask of open /// flags (see [`HostIoOpenFlags`]), and mode is an integer indicating a /// mask of mode bits to use if the file is created (see - /// [`HostIoMode`]). + /// [`HostIoOpenMode`]). fn open( &mut self, filename: &[u8], flags: HostIoOpenFlags, - mode: HostIoMode, + mode: HostIoOpenMode, ) -> HostIoResult; } @@ -269,9 +269,9 @@ define_ext!(HostIoOpenOps, HostIoOpen); /// Nested Target Extension - Host I/O close operation. pub trait HostIoClose: HostIo { - /// Close the open file corresponding to fd and return 0, or -1 if an error - /// occurs. - fn close(&mut self, fd: i32) -> HostIoResult; + /// Close the open file corresponding to fd and return 0, or + /// [`HostIoError::Errno`] if an error occurs. + fn close(&mut self, fd: u32) -> HostIoResult; } define_ext!(HostIoCloseOps, HostIoClose); @@ -283,18 +283,14 @@ pub trait HostIoPread: HostIo { /// Up to count bytes will be read from the file, starting at offset /// relative to the start of the file. /// - /// The target may read fewer bytes; common reasons include packet size - /// limits and an end-of-file condition. - /// - /// The number of bytes read is returned. Zero should only be returned for a - /// successful read at the end of the file, or if count was zero. + /// The data read should be sent with [`HostIoOutput`]. fn pread<'a>( &mut self, - fd: i32, + fd: u32, count: ::Usize, offset: ::Usize, - output: PreadOutput<'a>, - ) -> HostIoResult, Self>; + output: HostIoOutput<'a>, + ) -> HostIoResult, Self>; } define_ext!(HostIoPreadOps, HostIoPread); @@ -308,11 +304,11 @@ pub trait HostIoPwrite: HostIo { /// Unlike many write system calls, there is no separate count argument; the /// length of data in the packet is used. /// - /// ‘vFile:pwrite’ returns the number of bytes written, which may be shorter - /// than the length of data, or -1 if an error occurred. + /// Return the number of bytes written, which may be shorter + /// than the length of data, or [`HostIoError::Errno`] if an error occurred. fn pwrite( &mut self, - fd: i32, + fd: u32, offset: ::Usize, data: &[u8], ) -> HostIoResult; @@ -324,19 +320,17 @@ define_ext!(HostIoPwriteOps, HostIoPwrite); pub trait HostIoFstat: HostIo { /// Get information about the open file corresponding to fd. /// - /// On success the information is returned as a binary attachment and the - /// return value is the size of this attachment in bytes. If an error occurs - /// the return value is -1. - fn fstat(&mut self, fd: i32) -> HostIoResult; + /// On success return a [`HostIoStat`] struct. + /// Return [`HostIoError::Errno`] if an error occurs. + fn fstat(&mut self, fd: u32) -> HostIoResult; } define_ext!(HostIoFstatOps, HostIoFstat); /// Nested Target Extension - Host I/O unlink operation. pub trait HostIoUnlink: HostIo { - /// Delete the file at filename on the target. - /// - /// Return 0, or -1 if an error occurs. + /// Delete the file at filename on the target and return 0, or + /// [`HostIoError::Errno`] if an error occurs. fn unlink(&mut self, filename: &[u8]) -> HostIoResult; } @@ -344,16 +338,14 @@ define_ext!(HostIoUnlinkOps, HostIoUnlink); /// Nested Target Extension - Host I/O readlink operation. pub trait HostIoReadlink: HostIo { - /// Read value of symbolic link filename on the target. Return the number of - /// bytes read, or -1 if an error occurs. - /// - /// The data read should be returned as a binary attachment on success. If - /// zero bytes were read, the response should include an empty binary - /// attachment (i.e. a trailing semicolon). + /// Read value of symbolic link filename on the target. /// - /// The return value is the number of target bytes read; the binary - /// attachment may be longer if some characters were escaped. - fn readlink(&mut self, filename: &[u8]) -> HostIoResult; + /// The data read should be sent with [`HostIoOutput`]. + fn readlink<'a>( + &mut self, + filename: &[u8], + output: HostIoOutput<'a>, + ) -> HostIoResult; } define_ext!(HostIoReadlinkOps, HostIoReadlink); @@ -365,12 +357,11 @@ pub trait HostIoSetfs: HostIo { /// remote targets where the remote stub does not share a common filesystem /// with the inferior(s). /// - /// If pid is nonzero, select the filesystem as seen by process pid. If pid - /// is zero, select the filesystem as seen by the remote stub. + /// See [`FsKind`] for the meaning of argument. /// - /// Return 0 on success, or -1 if an error occurs. If vFile:setfs: indicates - /// success, the selected filesystem remains selected until the next - /// successful vFile:setfs: operation. + /// Return 0 on success, or [`HostIoError::Errno`] if an error occurs. If + /// setfs indicates success, the selected filesystem remains selected + /// until the next successful setfs operation. fn setfs(&mut self, fs: FsKind) -> HostIoResult; } From dc1f56e18b2dba737b3cc7807c8001cf92b2d4b4 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Fri, 13 Aug 2021 16:40:49 +0800 Subject: [PATCH 07/13] Implement real filesystem access in example --- examples/armv4t/emu.rs | 4 + examples/armv4t/gdb/host_io.rs | 176 +++++++++++++++++++++++-- src/gdbstub_impl/ext/host_io.rs | 17 +-- src/protocol/commands/_vFile_pwrite.rs | 2 +- src/target/ext/host_io.rs | 32 +++-- 5 files changed, 198 insertions(+), 33 deletions(-) diff --git a/examples/armv4t/emu.rs b/examples/armv4t/emu.rs index e40cbb43..3982ca4d 100644 --- a/examples/armv4t/emu.rs +++ b/examples/armv4t/emu.rs @@ -4,6 +4,7 @@ use crate::mem_sniffer::{AccessKind, MemSniffer}; use crate::DynResult; const HLE_RETURN_ADDR: u32 = 0x12345678; +pub const FD_MAX: u32 = 256; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Event { @@ -25,10 +26,12 @@ pub struct Emu { pub(crate) watchpoints: Vec, pub(crate) breakpoints: Vec, + pub(crate) files: [Option; FD_MAX as usize], } impl Emu { pub fn new(program_elf: &[u8]) -> DynResult { + const FILE_INIT: Option = None; // set up emulated system let mut cpu = Cpu::new(); let mut mem = ExampleMem::new(); @@ -72,6 +75,7 @@ impl Emu { watchpoints: Vec::new(), breakpoints: Vec::new(), + files: [FILE_INIT; FD_MAX as usize], }) } diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index df8c2ce0..2bb89105 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -1,10 +1,11 @@ use gdbstub::target; +use std::io::{Read, Seek, Write}; -use crate::emu::Emu; +use crate::emu::{Emu, FD_MAX}; use gdbstub::target::ext::host_io::{ HostIoErrno, HostIoError, HostIoOpenFlags, HostIoOpenMode, HostIoOutput, HostIoResult, - HostIoToken, + HostIoStat, HostIoToken, }; impl target::ext::host_io::HostIo for Emu { @@ -13,13 +14,28 @@ impl target::ext::host_io::HostIo for Emu { Some(self) } + #[inline(always)] + fn enable_close(&mut self) -> Option> { + Some(self) + } + #[inline(always)] fn enable_pread(&mut self) -> Option> { Some(self) } #[inline(always)] - fn enable_close(&mut self) -> Option> { + fn enable_pwrite(&mut self) -> Option> { + Some(self) + } + + #[inline(always)] + fn enable_unlink(&mut self) -> Option> { + Some(self) + } + + #[inline(always)] + fn enable_readlink(&mut self) -> Option> { Some(self) } } @@ -28,14 +44,55 @@ impl target::ext::host_io::HostIoOpen for Emu { fn open( &mut self, filename: &[u8], - _flags: HostIoOpenFlags, - _mode: HostIoOpenMode, + flags: HostIoOpenFlags, + mode: HostIoOpenMode, ) -> HostIoResult { // Support `info proc mappings` command if filename == b"/proc/1/maps" { - Ok(1) + Ok(FD_MAX + 1) + } else { + let path = match std::str::from_utf8(filename) { + Ok(v) => v, + Err(_) => return Err(HostIoError::Errno(HostIoErrno::ENOENT)), + }; + let file; + if flags + == HostIoOpenFlags::O_WRONLY | HostIoOpenFlags::O_CREAT | HostIoOpenFlags::O_TRUNC + && mode + == HostIoOpenMode::S_IRUSR | HostIoOpenMode::S_IWUSR | HostIoOpenMode::S_IXUSR + { + file = std::fs::File::create(path)?; + } else if flags == HostIoOpenFlags::O_RDONLY { + file = std::fs::File::open(path)?; + } else { + return Err(HostIoError::Errno(HostIoErrno::EINVAL)); + } + let n = 0; + for n in 0..FD_MAX { + if self.files[n as usize].is_none() { + break; + } + } + if n == FD_MAX { + return Err(HostIoError::Errno(HostIoErrno::ENFILE)); + } + self.files[n as usize] = Some(file); + Ok(n) + } + } +} + +impl target::ext::host_io::HostIoClose for Emu { + fn close(&mut self, fd: u32) -> HostIoResult<(), Self> { + if fd == FD_MAX + 1 { + Ok(()) + } else if fd < FD_MAX { + self.files[fd as usize] + .take() + .ok_or(HostIoError::Errno(HostIoErrno::EBADF))?; + Ok(()) } else { - Err(HostIoError::Errno(HostIoErrno::EPERM)) + Err(HostIoError::Errno(HostIoErrno::EBADF)) } } } @@ -48,24 +105,115 @@ impl target::ext::host_io::HostIoPread for Emu { offset: u32, output: HostIoOutput<'a>, ) -> HostIoResult, Self> { - if fd == 1 { + if fd == FD_MAX + 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 if fd < FD_MAX { + if let Some(ref mut file) = self.files[fd as usize] { + 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])) + } else { + Err(HostIoError::Errno(HostIoErrno::EBADF)) + } } else { - Err(HostIoError::Errno(HostIoErrno::EPERM)) + Err(HostIoError::Errno(HostIoErrno::EBADF)) } } } -impl target::ext::host_io::HostIoClose for Emu { - fn close(&mut self, fd: u32) -> HostIoResult { - if fd == 1 { - Ok(0) +impl target::ext::host_io::HostIoPwrite for Emu { + fn pwrite(&mut self, fd: u32, offset: u32, data: &[u8]) -> HostIoResult { + if fd < FD_MAX { + if let Some(ref mut file) = self.files[fd as usize] { + file.seek(std::io::SeekFrom::Start(offset as u64))?; + let n = file.write(data)?; + Ok(n as u32) + } else { + Err(HostIoError::Errno(HostIoErrno::EBADF)) + } + } else { + Err(HostIoError::Errno(HostIoErrno::EBADF)) + } + } +} + +impl target::ext::host_io::HostIoFstat for Emu { + fn fstat(&mut self, fd: u32) -> HostIoResult { + if fd < FD_MAX { + if let Some(ref mut file) = self.files[fd as usize] { + let metadata = file.metadata()?; + let mtime = metadata + .modified() + .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))?; + let duration = mtime + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))?; + let secs = duration.as_secs() as u32; + Ok(HostIoStat { + st_dev: 0, + st_ino: 0, + st_mode: HostIoOpenMode::S_IRUSR + | HostIoOpenMode::S_IWUSR + | HostIoOpenMode::S_IXUSR, + st_nlink: 0, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + st_size: metadata.len(), + st_blksize: 0, + st_blocks: 0, + st_atime: 0, + st_mtime: secs, + st_ctime: 0, + }) + } else { + Err(HostIoError::Errno(HostIoErrno::EBADF)) + } } else { - Err(HostIoError::Errno(HostIoErrno::EPERM)) + Err(HostIoError::Errno(HostIoErrno::EBADF)) } } } + +impl target::ext::host_io::HostIoUnlink for Emu { + fn unlink(&mut self, filename: &[u8]) -> HostIoResult<(), Self> { + let path = match std::str::from_utf8(filename) { + Ok(v) => v, + Err(_) => return 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, 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"/")); + } + + let path = match std::str::from_utf8(filename) { + Ok(v) => v, + Err(_) => return Err(HostIoError::Errno(HostIoErrno::ENOENT)), + }; + Ok(output.write( + std::fs::read_link(path)? + .to_str() + .ok_or(HostIoError::Errno(HostIoErrno::ENOENT))? + .as_bytes(), + )) + } +} diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index fd7d3aa4..5505b6fd 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -35,7 +35,7 @@ impl GdbStubImpl { HostIo::vFileOpen(cmd) if ops.enable_open().is_some() => { let ops = ops.enable_open().unwrap(); handle_hostio_result! { - if let Ok(fd) = ops.open(cmd.filename, cmd.flags, cmd.mode) => { + if let Ok(fd) = ops.open(cmd.filename, cmd.flags, cmd.mode) => { res.write_str("F")?; res.write_num(fd)?; } @@ -45,9 +45,8 @@ impl GdbStubImpl { HostIo::vFileClose(cmd) if ops.enable_close().is_some() => { let ops = ops.enable_close().unwrap(); handle_hostio_result! { - if let Ok(ret) = ops.close(cmd.fd) => { - res.write_str("F")?; - res.write_num(ret)?; + if let Ok(()) = ops.close(cmd.fd) => { + res.write_str("F0")?; } } HandlerStatus::Handled @@ -120,9 +119,8 @@ impl GdbStubImpl { HostIo::vFileUnlink(cmd) if ops.enable_unlink().is_some() => { let ops = ops.enable_unlink().unwrap(); handle_hostio_result! { - if let Ok(ret) = ops.unlink(cmd.filename) => { - res.write_str("F")?; - res.write_num(ret)?; + if let Ok(()) = ops.unlink(cmd.filename) => { + res.write_str("F0")?; } }; HandlerStatus::Handled @@ -154,9 +152,8 @@ impl GdbStubImpl { HostIo::vFileSetfs(cmd) if ops.enable_setfs().is_some() => { let ops = ops.enable_setfs().unwrap(); handle_hostio_result! { - if let Ok(ret) = ops.setfs(cmd.fs) => { - res.write_str("F")?; - res.write_num(ret)?; + if let Ok(()) = ops.setfs(cmd.fs) => { + res.write_str("F0")?; } }; HandlerStatus::Handled diff --git a/src/protocol/commands/_vFile_pwrite.rs b/src/protocol/commands/_vFile_pwrite.rs index c474aa69..8adf3ca5 100644 --- a/src/protocol/commands/_vFile_pwrite.rs +++ b/src/protocol/commands/_vFile_pwrite.rs @@ -19,7 +19,7 @@ impl<'a> ParseCommand<'a> for vFilePwrite<'a> { let mut body = body.splitn_mut_no_panic(3, |b| *b == b','); let fd = decode_hex(body.next()?).ok()?; let offset = decode_hex_buf(body.next()?).ok()?; - let data = decode_hex_buf(body.next()?).ok()?; + let data = body.next()?; Some(vFilePwrite{fd, offset, data}) }, _ => None, diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index 44a11862..9137e50e 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -172,6 +172,22 @@ pub enum HostIoError { Fatal(E), } +/// Converts a `std::io::Error` into a `HostIoError::Errno`. +#[cfg(feature = "std")] +impl From for HostIoError { + fn from(e: std::io::Error) -> HostIoError { + use std::io::ErrorKind::*; + match e.kind() { + PermissionDenied => HostIoError::Errno(HostIoErrno::EPERM), + NotFound => HostIoError::Errno(HostIoErrno::ENOENT), + Interrupted => HostIoError::Errno(HostIoErrno::EINTR), + AlreadyExists => HostIoError::Errno(HostIoErrno::EEXIST), + InvalidInput => HostIoError::Errno(HostIoErrno::EINVAL), + _ => HostIoError::Errno(HostIoErrno::EUNKNOWN), + } + } +} + /// A specialized `Result` type for Host I/O operations. Supports reporting /// non-fatal errors back to the GDB client. /// @@ -269,9 +285,9 @@ define_ext!(HostIoOpenOps, HostIoOpen); /// Nested Target Extension - Host I/O close operation. pub trait HostIoClose: HostIo { - /// Close the open file corresponding to fd and return 0, or + /// Close the open file corresponding to fd and return Ok(()), or /// [`HostIoError::Errno`] if an error occurs. - fn close(&mut self, fd: u32) -> HostIoResult; + fn close(&mut self, fd: u32) -> HostIoResult<(), Self>; } define_ext!(HostIoCloseOps, HostIoClose); @@ -329,9 +345,9 @@ define_ext!(HostIoFstatOps, HostIoFstat); /// Nested Target Extension - Host I/O unlink operation. pub trait HostIoUnlink: HostIo { - /// Delete the file at filename on the target and return 0, or + /// Delete the file at filename on the target and return Ok(()), or /// [`HostIoError::Errno`] if an error occurs. - fn unlink(&mut self, filename: &[u8]) -> HostIoResult; + fn unlink(&mut self, filename: &[u8]) -> HostIoResult<(), Self>; } define_ext!(HostIoUnlinkOps, HostIoUnlink); @@ -345,7 +361,7 @@ pub trait HostIoReadlink: HostIo { &mut self, filename: &[u8], output: HostIoOutput<'a>, - ) -> HostIoResult; + ) -> HostIoResult, Self>; } define_ext!(HostIoReadlinkOps, HostIoReadlink); @@ -359,10 +375,10 @@ pub trait HostIoSetfs: HostIo { /// /// See [`FsKind`] for the meaning of argument. /// - /// Return 0 on success, or [`HostIoError::Errno`] if an error occurs. If - /// setfs indicates success, the selected filesystem remains selected + /// Return Ok(()) on success, or [`HostIoError::Errno`] if an error occurs. + /// If setfs indicates success, the selected filesystem remains selected /// until the next successful setfs operation. - fn setfs(&mut self, fs: FsKind) -> HostIoResult; + fn setfs(&mut self, fs: FsKind) -> HostIoResult<(), Self>; } define_ext!(HostIoSetfsOps, HostIoSetfs); From 72b9ce2a9b3c9a328d8286e6a62e72357b993a3f Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Sun, 15 Aug 2021 11:09:11 +0800 Subject: [PATCH 08/13] Supply example with a complete real filesystem access implementation --- examples/armv4t/gdb/host_io.rs | 119 ++++++++++++++++++-------------- src/gdbstub_impl/ext/host_io.rs | 2 +- src/target/ext/host_io.rs | 22 +++--- 3 files changed, 83 insertions(+), 60 deletions(-) diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index 2bb89105..dfe3d2fa 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -4,7 +4,7 @@ use std::io::{Read, Seek, Write}; use crate::emu::{Emu, FD_MAX}; use gdbstub::target::ext::host_io::{ - HostIoErrno, HostIoError, HostIoOpenFlags, HostIoOpenMode, HostIoOutput, HostIoResult, + FsKind, HostIoErrno, HostIoError, HostIoOpenFlags, HostIoOpenMode, HostIoOutput, HostIoResult, HostIoStat, HostIoToken, }; @@ -38,6 +38,11 @@ impl target::ext::host_io::HostIo for Emu { fn enable_readlink(&mut self) -> Option> { Some(self) } + + #[inline(always)] + fn enable_setfs(&mut self) -> Option> { + Some(self) + } } impl target::ext::host_io::HostIoOpen for Emu { @@ -45,48 +50,51 @@ impl target::ext::host_io::HostIoOpen for Emu { &mut self, filename: &[u8], flags: HostIoOpenFlags, - mode: HostIoOpenMode, + _mode: HostIoOpenMode, ) -> HostIoResult { - // Support `info proc mappings` command - if filename == b"/proc/1/maps" { - Ok(FD_MAX + 1) + let path = match std::str::from_utf8(filename) { + Ok(v) => v, + Err(_) => return 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 { - let path = match std::str::from_utf8(filename) { - Ok(v) => v, - Err(_) => return Err(HostIoError::Errno(HostIoErrno::ENOENT)), - }; - let file; - if flags - == HostIoOpenFlags::O_WRONLY | HostIoOpenFlags::O_CREAT | HostIoOpenFlags::O_TRUNC - && mode - == HostIoOpenMode::S_IRUSR | HostIoOpenMode::S_IWUSR | HostIoOpenMode::S_IXUSR - { - file = std::fs::File::create(path)?; - } else if flags == HostIoOpenFlags::O_RDONLY { - file = std::fs::File::open(path)?; - } else { - return Err(HostIoError::Errno(HostIoErrno::EINVAL)); - } - let n = 0; - for n in 0..FD_MAX { - if self.files[n as usize].is_none() { - break; - } - } - if n == FD_MAX { - return Err(HostIoError::Errno(HostIoErrno::ENFILE)); + 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 = 0; + for n in 0..FD_MAX { + if self.files[n as usize].is_none() { + break; } - self.files[n as usize] = Some(file); - Ok(n) } + if n == FD_MAX { + return Err(HostIoError::Errno(HostIoErrno::ENFILE)); + } + + self.files[n as usize] = Some(file); + Ok(n) } } impl target::ext::host_io::HostIoClose for Emu { fn close(&mut self, fd: u32) -> HostIoResult<(), Self> { - if fd == FD_MAX + 1 { - Ok(()) - } else if fd < FD_MAX { + if fd < FD_MAX { self.files[fd as usize] .take() .ok_or(HostIoError::Errno(HostIoErrno::EBADF))?; @@ -105,13 +113,7 @@ impl target::ext::host_io::HostIoPread for Emu { offset: u32, output: HostIoOutput<'a>, ) -> HostIoResult, Self> { - if fd == FD_MAX + 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 if fd < FD_MAX { + if fd < FD_MAX { if let Some(ref mut file) = self.files[fd as usize] { let mut buffer = vec![0; count as usize]; file.seek(std::io::SeekFrom::Start(offset as u64))?; @@ -147,19 +149,28 @@ impl target::ext::host_io::HostIoFstat for Emu { if fd < FD_MAX { if let Some(ref mut file) = self.files[fd as usize] { let metadata = file.metadata()?; + let atime = metadata + .accessed() + .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? + .as_secs() as u32; let mtime = metadata .modified() - .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))?; - let duration = mtime + .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? + .as_secs() as u32; + let ctime = metadata + .created() + .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? .duration_since(std::time::SystemTime::UNIX_EPOCH) - .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))?; - let secs = duration.as_secs() as u32; + .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? + .as_secs() as u32; Ok(HostIoStat { st_dev: 0, st_ino: 0, - st_mode: HostIoOpenMode::S_IRUSR - | HostIoOpenMode::S_IWUSR - | HostIoOpenMode::S_IXUSR, + st_mode: HostIoOpenMode::empty(), st_nlink: 0, st_uid: 0, st_gid: 0, @@ -167,9 +178,9 @@ impl target::ext::host_io::HostIoFstat for Emu { st_size: metadata.len(), st_blksize: 0, st_blocks: 0, - st_atime: 0, - st_mtime: secs, - st_ctime: 0, + st_atime: atime, + st_mtime: mtime, + st_ctime: ctime, }) } else { Err(HostIoError::Errno(HostIoErrno::EBADF)) @@ -217,3 +228,9 @@ impl target::ext::host_io::HostIoReadlink for Emu { )) } } + +impl target::ext::host_io::HostIoSetfs for Emu { + fn setfs(&mut self, _fs: FsKind) -> HostIoResult<(), Self> { + Ok(()) + } +} diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index 5505b6fd..d6735a16 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -24,7 +24,7 @@ impl GdbStubImpl { Ok($val) => $callback, Err(HostIoError::Errno(errno)) => { res.write_str("F-1,")?; - res.write_num(errno as i32)?; + res.write_num(errno as u32)?; } Err(HostIoError::Fatal(e)) => return Err(GdbStubError::TargetError(e)), } diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index 9137e50e..911d0434 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -172,7 +172,10 @@ pub enum HostIoError { Fatal(E), } -/// Converts a `std::io::Error` into a `HostIoError::Errno`. +/// When the `std` feature is enabled, `HostIoError` implements +/// `From`, mapping [`std::io::ErrorKind`] to the appropriate +/// [`HostIoErrno`] when possible, and falling back to [`HostIoErrno::EUNKNOWN`] +/// when no mapping exists. #[cfg(feature = "std")] impl From for HostIoError { fn from(e: std::io::Error) -> HostIoError { @@ -285,8 +288,7 @@ define_ext!(HostIoOpenOps, HostIoOpen); /// Nested Target Extension - Host I/O close operation. pub trait HostIoClose: HostIo { - /// Close the open file corresponding to fd and return Ok(()), or - /// [`HostIoError::Errno`] if an error occurs. + /// Close the open file corresponding to fd. fn close(&mut self, fd: u32) -> HostIoResult<(), Self>; } @@ -299,7 +301,10 @@ pub trait HostIoPread: HostIo { /// Up to count bytes will be read from the file, starting at offset /// relative to the start of the file. /// - /// The data read should be sent with [`HostIoOutput`]. + /// The data read _must_ be sent by calling [`HostIoOutput::write`], which + /// will consume the `output` object and return a [`HostIoToken`]. This + /// token ensures that the implementer of this method calls + /// [`HostIoOutput::write`]. fn pread<'a>( &mut self, fd: u32, @@ -345,8 +350,7 @@ define_ext!(HostIoFstatOps, HostIoFstat); /// Nested Target Extension - Host I/O unlink operation. pub trait HostIoUnlink: HostIo { - /// Delete the file at filename on the target and return Ok(()), or - /// [`HostIoError::Errno`] if an error occurs. + /// Delete the file at filename on the target. fn unlink(&mut self, filename: &[u8]) -> HostIoResult<(), Self>; } @@ -356,7 +360,10 @@ define_ext!(HostIoUnlinkOps, HostIoUnlink); pub trait HostIoReadlink: HostIo { /// Read value of symbolic link filename on the target. /// - /// The data read should be sent with [`HostIoOutput`]. + /// The data read _must_ be sent by calling [`HostIoOutput::write`], which + /// will consume the `output` object and return a [`HostIoToken`]. This + /// token ensures that the implementer of this method calls + /// [`HostIoOutput::write`]. fn readlink<'a>( &mut self, filename: &[u8], @@ -375,7 +382,6 @@ pub trait HostIoSetfs: HostIo { /// /// See [`FsKind`] for the meaning of argument. /// - /// Return Ok(()) on success, or [`HostIoError::Errno`] if an error occurs. /// If setfs indicates success, the selected filesystem remains selected /// until the next successful setfs operation. fn setfs(&mut self, fs: FsKind) -> HostIoResult<(), Self>; From 85a6accc82e113a86001a66b3d5ccacadd6ee0df Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Mon, 16 Aug 2021 10:20:36 +0800 Subject: [PATCH 09/13] Store files in Vec --- examples/armv4t/emu.rs | 6 +- examples/armv4t/gdb/host_io.rs | 117 ++++++++++++++------------ src/gdbstub_impl/ext/host_io.rs | 29 ++++--- src/protocol/commands/_vFile_fstat.rs | 3 +- src/protocol/commands/_vFile_setfs.rs | 4 +- src/target/ext/host_io.rs | 41 +++++---- 6 files changed, 102 insertions(+), 98 deletions(-) diff --git a/examples/armv4t/emu.rs b/examples/armv4t/emu.rs index 3982ca4d..53b96edb 100644 --- a/examples/armv4t/emu.rs +++ b/examples/armv4t/emu.rs @@ -4,7 +4,6 @@ use crate::mem_sniffer::{AccessKind, MemSniffer}; use crate::DynResult; const HLE_RETURN_ADDR: u32 = 0x12345678; -pub const FD_MAX: u32 = 256; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Event { @@ -26,12 +25,11 @@ pub struct Emu { pub(crate) watchpoints: Vec, pub(crate) breakpoints: Vec, - pub(crate) files: [Option; FD_MAX as usize], + pub(crate) files: Vec>, } impl Emu { pub fn new(program_elf: &[u8]) -> DynResult { - const FILE_INIT: Option = None; // set up emulated system let mut cpu = Cpu::new(); let mut mem = ExampleMem::new(); @@ -75,7 +73,7 @@ impl Emu { watchpoints: Vec::new(), breakpoints: Vec::new(), - files: [FILE_INIT; FD_MAX as usize], + files: Vec::new(), }) } diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index dfe3d2fa..5b3be788 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -1,12 +1,11 @@ use gdbstub::target; -use std::io::{Read, Seek, Write}; - -use crate::emu::{Emu, FD_MAX}; - use gdbstub::target::ext::host_io::{ FsKind, HostIoErrno, HostIoError, HostIoOpenFlags, HostIoOpenMode, HostIoOutput, HostIoResult, HostIoStat, HostIoToken, }; +use std::io::{Read, Seek, Write}; + +use crate::emu::Emu; impl target::ext::host_io::HostIo for Emu { #[inline(always)] @@ -29,6 +28,11 @@ impl target::ext::host_io::HostIo for Emu { Some(self) } + #[inline(always)] + fn enable_fstat(&mut self) -> Option> { + Some(self) + } + #[inline(always)] fn enable_unlink(&mut self) -> Option> { Some(self) @@ -52,10 +56,12 @@ impl target::ext::host_io::HostIoOpen for Emu { flags: HostIoOpenFlags, _mode: HostIoOpenMode, ) -> HostIoResult { - let path = match std::str::from_utf8(filename) { - Ok(v) => v, - Err(_) => return Err(HostIoError::Errno(HostIoErrno::ENOENT)), - }; + 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; @@ -77,27 +83,35 @@ impl target::ext::host_io::HostIoOpen for Emu { .create_new(flags.contains(HostIoOpenFlags::O_EXCL)) .open(path)?; - let n = 0; - for n in 0..FD_MAX { - if self.files[n as usize].is_none() { - break; + let n = match self.files.iter_mut().enumerate().find(|(_, f)| f.is_none()) { + Some((n, free_file)) => { + *free_file = Some(file); + n } - } - if n == FD_MAX { - return Err(HostIoError::Errno(HostIoErrno::ENFILE)); - } + None => { + self.files.push(Some(file)); + self.files.len() - 1 + } + }; - self.files[n as usize] = Some(file); - Ok(n) + Ok(n as u32) } } impl target::ext::host_io::HostIoClose for Emu { fn close(&mut self, fd: u32) -> HostIoResult<(), Self> { - if fd < FD_MAX { - self.files[fd as usize] - .take() - .ok_or(HostIoError::Errno(HostIoErrno::EBADF))?; + let fd: usize = fd as usize; + if fd < self.files.len() { + if fd == self.files.len() - 1 { + self.files.pop(); + while let Some(None) = self.files.last() { + self.files.pop(); + } + } else { + self.files[fd] + .take() + .ok_or(HostIoError::Errno(HostIoErrno::EBADF))?; + } Ok(()) } else { Err(HostIoError::Errno(HostIoErrno::EBADF)) @@ -113,8 +127,9 @@ impl target::ext::host_io::HostIoPread for Emu { offset: u32, output: HostIoOutput<'a>, ) -> HostIoResult, Self> { - if fd < FD_MAX { - if let Some(ref mut file) = self.files[fd as usize] { + let fd: usize = fd as usize; + if fd < self.files.len() { + if let Some(ref mut file) = self.files[fd] { let mut buffer = vec![0; count as usize]; file.seek(std::io::SeekFrom::Start(offset as u64))?; let n = file.read(&mut buffer)?; @@ -130,8 +145,9 @@ impl target::ext::host_io::HostIoPread for Emu { impl target::ext::host_io::HostIoPwrite for Emu { fn pwrite(&mut self, fd: u32, offset: u32, data: &[u8]) -> HostIoResult { - if fd < FD_MAX { - if let Some(ref mut file) = self.files[fd as usize] { + let fd: usize = fd as usize; + if fd < self.files.len() { + if let Some(ref mut file) = self.files[fd] { file.seek(std::io::SeekFrom::Start(offset as u64))?; let n = file.write(data)?; Ok(n as u32) @@ -146,27 +162,22 @@ impl target::ext::host_io::HostIoPwrite for Emu { impl target::ext::host_io::HostIoFstat for Emu { fn fstat(&mut self, fd: u32) -> HostIoResult { - if fd < FD_MAX { - if let Some(ref mut file) = self.files[fd as usize] { + let fd: usize = fd as usize; + if fd < self.files.len() { + if let Some(ref mut file) = self.files[fd] { let metadata = file.metadata()?; - let atime = metadata - .accessed() - .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? - .as_secs() as u32; - let mtime = metadata - .modified() - .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? - .as_secs() as u32; - let ctime = metadata - .created() - .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .map_err(|_| HostIoError::Errno(HostIoErrno::EACCES))? - .as_secs() as u32; + 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, @@ -193,10 +204,8 @@ impl target::ext::host_io::HostIoFstat for Emu { impl target::ext::host_io::HostIoUnlink for Emu { fn unlink(&mut self, filename: &[u8]) -> HostIoResult<(), Self> { - let path = match std::str::from_utf8(filename) { - Ok(v) => v, - Err(_) => return Err(HostIoError::Errno(HostIoErrno::ENOENT)), - }; + let path = + std::str::from_utf8(filename).map_err(|_| HostIoError::Errno(HostIoErrno::ENOENT))?; std::fs::remove_file(path)?; Ok(()) } @@ -214,12 +223,12 @@ impl target::ext::host_io::HostIoReadlink for Emu { } 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 = match std::str::from_utf8(filename) { - Ok(v) => v, - Err(_) => 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() diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index d6735a16..c9c89d30 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -1,6 +1,7 @@ use super::prelude::*; -use crate::arch::Arch; use crate::protocol::commands::ext::HostIo; + +use crate::arch::Arch; use crate::target::ext::host_io::{HostIoError, HostIoOutput, HostIoStat}; use crate::GdbStubError; @@ -99,19 +100,19 @@ impl GdbStubImpl { 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())?; + res.write_binary(&stat.st_dev.to_be_bytes())?; + res.write_binary(&stat.st_ino.to_be_bytes())?; + res.write_binary(&(stat.st_mode.bits()).to_be_bytes())?; + res.write_binary(&stat.st_nlink.to_be_bytes())?; + res.write_binary(&stat.st_uid.to_be_bytes())?; + res.write_binary(&stat.st_gid.to_be_bytes())?; + res.write_binary(&stat.st_rdev.to_be_bytes())?; + res.write_binary(&stat.st_size.to_be_bytes())?; + res.write_binary(&stat.st_blksize.to_be_bytes())?; + res.write_binary(&stat.st_blocks.to_be_bytes())?; + res.write_binary(&stat.st_atime.to_be_bytes())?; + res.write_binary(&stat.st_mtime.to_be_bytes())?; + res.write_binary(&stat.st_ctime.to_be_bytes())?; } }; HandlerStatus::Handled diff --git a/src/protocol/commands/_vFile_fstat.rs b/src/protocol/commands/_vFile_fstat.rs index a73cd5cd..9f8b02f3 100644 --- a/src/protocol/commands/_vFile_fstat.rs +++ b/src/protocol/commands/_vFile_fstat.rs @@ -14,8 +14,7 @@ impl<'a> ParseCommand<'a> for vFileFstat { match body { [b':', body @ ..] => { - let mut body = body.splitn_mut_no_panic(3, |b| *b == b','); - let fd = decode_hex(body.next()?).ok()?; + let fd = decode_hex(body).ok()?; Some(vFileFstat{fd}) }, _ => None, diff --git a/src/protocol/commands/_vFile_setfs.rs b/src/protocol/commands/_vFile_setfs.rs index 5081089b..331948c5 100644 --- a/src/protocol/commands/_vFile_setfs.rs +++ b/src/protocol/commands/_vFile_setfs.rs @@ -1,6 +1,6 @@ use super::prelude::*; + use crate::target::ext::host_io::FsKind; -use core::num::NonZeroUsize; #[derive(Debug)] pub struct vFileSetfs { @@ -16,7 +16,7 @@ impl<'a> ParseCommand<'a> for vFileSetfs { match body { [b':', body @ ..] => { - let fs = match NonZeroUsize::new(decode_hex(body).ok()?) { + let fs = match core::num::NonZeroUsize::new(decode_hex(body).ok()?) { None => FsKind::Stub, Some(pid) => FsKind::Pid(pid), }; diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index 911d0434..970e5d41 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -1,7 +1,8 @@ //! Provide Host I/O operations for the target. +use bitflags::bitflags; + use crate::arch::Arch; use crate::target::Target; -use bitflags::bitflags; bitflags! { /// Host flags for opening files. @@ -95,9 +96,9 @@ pub struct HostIoStat { /// command. #[derive(Debug)] pub enum FsKind { - /// select the filesystem as seen by the remote stub + /// Select the filesystem as seen by the remote stub. Stub, - /// select the filesystem as seen by process pid + /// Select the filesystem as seen by process pid. Pid(crate::common::Pid), } @@ -197,7 +198,7 @@ impl From for HostIoError { /// See [`HostIoError`] for more details. pub type HostIoResult = Result::Error>>; -/// Zero-sized type token that ensures HostIoOutput::write is called +/// Zero-sized type token that ensures HostIoOutput::write is called. pub struct HostIoToken<'a>(core::marker::PhantomData<&'a *mut ()>); /// An interface to send pread data back to the GDB client. @@ -269,13 +270,12 @@ define_ext!(HostIoOps, HostIo); /// Nested Target Extension - Host I/O open operation. pub trait HostIoOpen: HostIo { - /// Open a file at filename and return a file descriptor for it, or return + /// Open a file at `filename` and return a file descriptor for it, or return /// [`HostIoError::Errno`] if an error occurs. /// - /// The filename is a string, flags is an integer indicating a mask of open - /// flags (see [`HostIoOpenFlags`]), and mode is an integer indicating a - /// mask of mode bits to use if the file is created (see - /// [`HostIoOpenMode`]). + /// `flags` is the flags used when open (see [`HostIoOpenFlags`]), and + /// `mode` is the mode used if the file is created + /// (see [`HostIoOpenMode`]). fn open( &mut self, filename: &[u8], @@ -288,7 +288,7 @@ define_ext!(HostIoOpenOps, HostIoOpen); /// Nested Target Extension - Host I/O close operation. pub trait HostIoClose: HostIo { - /// Close the open file corresponding to fd. + /// Close the open file corresponding to `fd`. fn close(&mut self, fd: u32) -> HostIoResult<(), Self>; } @@ -296,9 +296,9 @@ define_ext!(HostIoCloseOps, HostIoClose); /// Nested Target Extension - Host I/O pread operation. pub trait HostIoPread: HostIo { - /// Read data from the open file corresponding to fd. + /// Read data from the open file corresponding to `fd`. /// - /// Up to count bytes will be read from the file, starting at offset + /// Up to `count` bytes will be read from the file, starting at `offset` /// relative to the start of the file. /// /// The data read _must_ be sent by calling [`HostIoOutput::write`], which @@ -318,12 +318,9 @@ define_ext!(HostIoPreadOps, HostIoPread); /// Nested Target Extension - Host I/O pwrite operation. pub trait HostIoPwrite: HostIo { - /// Write data (a binary buffer) to the open file corresponding to fd. + /// Write `data` to the open file corresponding to `fd`. /// - /// Start the write at offset from the start of the file. - /// - /// Unlike many write system calls, there is no separate count argument; the - /// length of data in the packet is used. + /// Start the write at `offset` from the start of the file. /// /// Return the number of bytes written, which may be shorter /// than the length of data, or [`HostIoError::Errno`] if an error occurred. @@ -332,14 +329,14 @@ pub trait HostIoPwrite: HostIo { fd: u32, offset: ::Usize, data: &[u8], - ) -> HostIoResult; + ) -> HostIoResult<::Usize, Self>; } define_ext!(HostIoPwriteOps, HostIoPwrite); /// Nested Target Extension - Host I/O fstat operation. pub trait HostIoFstat: HostIo { - /// Get information about the open file corresponding to fd. + /// Get information about the open file corresponding to `fd`. /// /// On success return a [`HostIoStat`] struct. /// Return [`HostIoError::Errno`] if an error occurs. @@ -350,7 +347,7 @@ define_ext!(HostIoFstatOps, HostIoFstat); /// Nested Target Extension - Host I/O unlink operation. pub trait HostIoUnlink: HostIo { - /// Delete the file at filename on the target. + /// Delete the file at `filename` on the target. fn unlink(&mut self, filename: &[u8]) -> HostIoResult<(), Self>; } @@ -358,7 +355,7 @@ define_ext!(HostIoUnlinkOps, HostIoUnlink); /// Nested Target Extension - Host I/O readlink operation. pub trait HostIoReadlink: HostIo { - /// Read value of symbolic link filename on the target. + /// Read value of symbolic link `filename` on the target. /// /// The data read _must_ be sent by calling [`HostIoOutput::write`], which /// will consume the `output` object and return a [`HostIoToken`]. This @@ -380,7 +377,7 @@ pub trait HostIoSetfs: HostIo { /// remote targets where the remote stub does not share a common filesystem /// with the inferior(s). /// - /// See [`FsKind`] for the meaning of argument. + /// See [`FsKind`] for the meaning of `fs`. /// /// If setfs indicates success, the selected filesystem remains selected /// until the next successful setfs operation. From e20d15d04a551e082f956297ca204fad51b22516 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Tue, 17 Aug 2021 10:35:14 +0800 Subject: [PATCH 10/13] Simplify code --- examples/armv4t/gdb/host_io.rs | 105 +++++++++++++-------------------- 1 file changed, 42 insertions(+), 63 deletions(-) diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index 5b3be788..b5aa95de 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -1,9 +1,10 @@ +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 std::io::{Read, Seek, Write}; use crate::emu::Emu; @@ -100,17 +101,10 @@ impl target::ext::host_io::HostIoOpen for Emu { impl target::ext::host_io::HostIoClose for Emu { fn close(&mut self, fd: u32) -> HostIoResult<(), Self> { - let fd: usize = fd as usize; - if fd < self.files.len() { - if fd == self.files.len() - 1 { + if let Some(file) = self.files.get_mut(fd as usize) { + file.take().ok_or(HostIoError::Errno(HostIoErrno::EBADF))?; + while let Some(None) = self.files.last() { self.files.pop(); - while let Some(None) = self.files.last() { - self.files.pop(); - } - } else { - self.files[fd] - .take() - .ok_or(HostIoError::Errno(HostIoErrno::EBADF))?; } Ok(()) } else { @@ -127,16 +121,11 @@ impl target::ext::host_io::HostIoPread for Emu { offset: u32, output: HostIoOutput<'a>, ) -> HostIoResult, Self> { - let fd: usize = fd as usize; - if fd < self.files.len() { - if let Some(ref mut file) = self.files[fd] { - 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])) - } else { - Err(HostIoError::Errno(HostIoErrno::EBADF)) - } + if let Some(Some(file)) = self.files.get_mut(fd as usize) { + 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])) } else { Err(HostIoError::Errno(HostIoErrno::EBADF)) } @@ -145,15 +134,10 @@ impl target::ext::host_io::HostIoPread for Emu { impl target::ext::host_io::HostIoPwrite for Emu { fn pwrite(&mut self, fd: u32, offset: u32, data: &[u8]) -> HostIoResult { - let fd: usize = fd as usize; - if fd < self.files.len() { - if let Some(ref mut file) = self.files[fd] { - file.seek(std::io::SeekFrom::Start(offset as u64))?; - let n = file.write(data)?; - Ok(n as u32) - } else { - Err(HostIoError::Errno(HostIoErrno::EBADF)) - } + if let Some(Some(file)) = self.files.get_mut(fd as usize) { + file.seek(std::io::SeekFrom::Start(offset as u64))?; + let n = file.write(data)?; + Ok(n as u32) } else { Err(HostIoError::Errno(HostIoErrno::EBADF)) } @@ -162,40 +146,35 @@ impl target::ext::host_io::HostIoPwrite for Emu { impl target::ext::host_io::HostIoFstat for Emu { fn fstat(&mut self, fd: u32) -> HostIoResult { - let fd: usize = fd as usize; - if fd < self.files.len() { - if let Some(ref mut file) = self.files[fd] { - let metadata = file.metadata()?; - 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, - }) - } else { - Err(HostIoError::Errno(HostIoErrno::EBADF)) + if let Some(Some(file)) = self.files.get(fd as usize) { + let metadata = file.metadata()?; + 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, + }) } else { Err(HostIoError::Errno(HostIoErrno::EBADF)) } From 7c039394341301ea41f6d8f53a648f0332b1883e Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Wed, 18 Aug 2021 10:43:15 +0800 Subject: [PATCH 11/13] Allow non-ASCII characters in packet --- src/gdbstub_impl/mod.rs | 4 +-- src/protocol/commands.rs | 4 ++- src/protocol/commands/_vFile_pwrite.rs | 2 +- src/protocol/common/hex.rs | 36 +++++++++++++++++++++++++- src/protocol/packet.rs | 11 -------- src/protocol/response_writer.rs | 2 +- 6 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/gdbstub_impl/mod.rs b/src/gdbstub_impl/mod.rs index fc598cd8..b27390ac 100644 --- a/src/gdbstub_impl/mod.rs +++ b/src/gdbstub_impl/mod.rs @@ -561,9 +561,7 @@ impl GdbStubImpl { ) -> Result> { match cmd { Command::Unknown(cmd) => { - // cmd must be ASCII, as the slice originated from a PacketBuf, which checks for - // ASCII as part of the initial validation. - info!("Unknown command: {}", core::str::from_utf8(cmd).unwrap()); + info!("Unknown command: {:?}", core::str::from_utf8(cmd)); Ok(HandlerStatus::Handled) } // `handle_X` methods are defined in the `ext` module diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs index 843b4cba..98538751 100644 --- a/src/protocol/commands.rs +++ b/src/protocol/commands.rs @@ -6,7 +6,9 @@ use crate::target::Target; pub(self) mod prelude { pub use super::ParseCommand; pub use crate::common::*; - pub use crate::protocol::common::hex::{decode_hex, decode_hex_buf, is_hex, HexString}; + pub use crate::protocol::common::hex::{ + decode_bin_buf, decode_hex, decode_hex_buf, is_hex, HexString, + }; pub use crate::protocol::common::lists; pub use crate::protocol::common::thread_id::{ IdKind, SpecificIdKind, SpecificThreadId, ThreadId, diff --git a/src/protocol/commands/_vFile_pwrite.rs b/src/protocol/commands/_vFile_pwrite.rs index 8adf3ca5..8e155d03 100644 --- a/src/protocol/commands/_vFile_pwrite.rs +++ b/src/protocol/commands/_vFile_pwrite.rs @@ -19,7 +19,7 @@ impl<'a> ParseCommand<'a> for vFilePwrite<'a> { let mut body = body.splitn_mut_no_panic(3, |b| *b == b','); let fd = decode_hex(body.next()?).ok()?; let offset = decode_hex_buf(body.next()?).ok()?; - let data = body.next()?; + let data = decode_bin_buf(body.next()?).ok()?; Some(vFilePwrite{fd, offset, data}) }, _ => None, diff --git a/src/protocol/common/hex.rs b/src/protocol/common/hex.rs index 2d13b232..181cf28e 100644 --- a/src/protocol/common/hex.rs +++ b/src/protocol/common/hex.rs @@ -168,7 +168,34 @@ pub fn decode_hex_buf(base_buf: &mut [u8]) -> Result<&mut [u8], DecodeHexBufErro Ok(&mut base_buf[..decoded_len + odd_adust]) } -#[allow(dead_code)] +#[derive(Debug)] +pub enum DecodeBinBufError { + UnexpectedEnd, +} + +/// Decode GDB escaped binary bytes into origin bytes _in place_. +pub fn decode_bin_buf(buf: &mut [u8]) -> Result<&mut [u8], DecodeBinBufError> { + use DecodeBinBufError::*; + let mut i = 0; + let mut j = 0; + let len = buf.len(); + while i < len { + if buf[i] == b'}' { + if i == len - 1 { + return Err(UnexpectedEnd); + } else { + buf[j] = buf[i + 1] ^ 0x20; + i += 1; + } + } else { + buf[j] = buf[i]; + } + i += 1; + j += 1; + } + Ok(&mut buf[..j]) +} + #[derive(Debug)] pub enum EncodeHexBufError { SmallBuffer, @@ -268,4 +295,11 @@ mod tests { let res = decode_hex_buf(&mut payload).unwrap(); assert_eq!(res, [0x1]); } + + #[test] + fn decode_bin_buf_escaped() { + let mut payload = b"}\x03}\x04}]}\n".to_vec(); + let res = decode_bin_buf(&mut payload).unwrap(); + assert_eq!(res, [0x23, 0x24, 0x7d, 0x2a]); + } } diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 61704249..d851ad64 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -10,7 +10,6 @@ pub enum PacketParseError { MissingChecksum, MalformedChecksum, MalformedCommand, - NotAscii, UnexpectedHeader(u8), } @@ -57,11 +56,6 @@ impl<'a> PacketBuf<'a> { .get(..2) .ok_or(PacketParseError::MalformedChecksum)?; - // validate that the body is valid ASCII - if !body.is_ascii() { - return Err(PacketParseError::NotAscii); - } - // validate the checksum let checksum = decode_hex(checksum).map_err(|_| PacketParseError::MalformedChecksum)?; let calculated = body.iter().fold(0u8, |a, x| a.wrapping_add(*x)); @@ -84,11 +78,6 @@ impl<'a> PacketBuf<'a> { /// the header/checksum trimming stage. ASCII validation is still performed. #[cfg(test)] pub fn new_with_raw_body(body: &'a mut [u8]) -> Result, PacketParseError> { - // validate the packet is valid ASCII - if !body.is_ascii() { - return Err(PacketParseError::NotAscii); - } - let len = body.len(); Ok(PacketBuf { buf: body, diff --git a/src/protocol/response_writer.rs b/src/protocol/response_writer.rs index 28cca863..64c26d28 100644 --- a/src/protocol/response_writer.rs +++ b/src/protocol/response_writer.rs @@ -54,7 +54,7 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> { #[cfg(feature = "std")] trace!( "--> ${}#{:02x?}", - core::str::from_utf8(&self.msg).unwrap(), // buffers are always ascii + String::from_utf8_lossy(&self.msg), checksum ); From 641eb0c42f286dba2dfcb152bb533d98b2fa1238 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Thu, 19 Aug 2021 11:04:04 +0800 Subject: [PATCH 12/13] Optimize away the bounds checks in decode_bin_buf --- README.md | 2 +- src/protocol/common/hex.rs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index da567901..f3858654 100644 --- a/README.md +++ b/README.md @@ -162,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. diff --git a/src/protocol/common/hex.rs b/src/protocol/common/hex.rs index 181cf28e..95b444ba 100644 --- a/src/protocol/common/hex.rs +++ b/src/protocol/common/hex.rs @@ -179,9 +179,9 @@ pub fn decode_bin_buf(buf: &mut [u8]) -> Result<&mut [u8], DecodeBinBufError> { let mut i = 0; let mut j = 0; let len = buf.len(); - while i < len { + while i < len && j < len { if buf[i] == b'}' { - if i == len - 1 { + if i + 1 >= len { return Err(UnexpectedEnd); } else { buf[j] = buf[i + 1] ^ 0x20; @@ -193,7 +193,15 @@ pub fn decode_bin_buf(buf: &mut [u8]) -> Result<&mut [u8], DecodeBinBufError> { i += 1; j += 1; } - Ok(&mut buf[..j]) + + // SAFETY: by inspection, the value of j will never exceed buf.len(). + // Unfortunately, the LLVM optimizer isn't smart enough to see this, so + // we have to manually elide the bounds check... + if cfg!(feature = "paranoid_unsafe") { + Ok(&mut buf[..j]) + } else { + unsafe { Ok(buf.get_unchecked_mut(..j)) } + } } #[derive(Debug)] From 2ba31299785f16923033b3332615986f3f60635b Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Fri, 20 Aug 2021 08:47:07 +0800 Subject: [PATCH 13/13] Fix style --- examples/armv4t/gdb/host_io.rs | 112 ++++++++++++++------------- src/gdbstub_impl/ext/host_io.rs | 3 +- src/protocol/commands/_vFile_open.rs | 4 +- src/protocol/common/hex.rs | 1 + src/protocol/packet.rs | 4 +- src/target/ext/host_io.rs | 23 +++--- 6 files changed, 76 insertions(+), 71 deletions(-) diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index b5aa95de..e4844ab7 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -101,15 +101,16 @@ impl target::ext::host_io::HostIoOpen for Emu { impl target::ext::host_io::HostIoClose for Emu { fn close(&mut self, fd: u32) -> HostIoResult<(), Self> { - if let Some(file) = self.files.get_mut(fd as usize) { - file.take().ok_or(HostIoError::Errno(HostIoErrno::EBADF))?; - while let Some(None) = self.files.last() { - self.files.pop(); - } - Ok(()) - } else { - Err(HostIoError::Errno(HostIoErrno::EBADF)) + 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(()) } } @@ -121,63 +122,66 @@ impl target::ext::host_io::HostIoPread for Emu { offset: u32, output: HostIoOutput<'a>, ) -> HostIoResult, Self> { - if let Some(Some(file)) = self.files.get_mut(fd as usize) { - 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])) - } else { - Err(HostIoError::Errno(HostIoErrno::EBADF)) - } + 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 { - if let Some(Some(file)) = self.files.get_mut(fd as usize) { - file.seek(std::io::SeekFrom::Start(offset as u64))?; - let n = file.write(data)?; - Ok(n as u32) - } else { - Err(HostIoError::Errno(HostIoErrno::EBADF)) - } + 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 { - if let Some(Some(file)) = self.files.get(fd as usize) { - let metadata = file.metadata()?; - 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, - }) - } else { - Err(HostIoError::Errno(HostIoErrno::EBADF)) + 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, + }) } } diff --git a/src/gdbstub_impl/ext/host_io.rs b/src/gdbstub_impl/ext/host_io.rs index c9c89d30..911decda 100644 --- a/src/gdbstub_impl/ext/host_io.rs +++ b/src/gdbstub_impl/ext/host_io.rs @@ -3,7 +3,6 @@ use crate::protocol::commands::ext::HostIo; use crate::arch::Arch; use crate::target::ext::host_io::{HostIoError, HostIoOutput, HostIoStat}; -use crate::GdbStubError; impl GdbStubImpl { pub(crate) fn handle_host_io( @@ -27,7 +26,7 @@ impl GdbStubImpl { res.write_str("F-1,")?; res.write_num(errno as u32)?; } - Err(HostIoError::Fatal(e)) => return Err(GdbStubError::TargetError(e)), + Err(HostIoError::Fatal(e)) => return Err(Error::TargetError(e)), } }}; } diff --git a/src/protocol/commands/_vFile_open.rs b/src/protocol/commands/_vFile_open.rs index 9c7b9a84..67db32fe 100644 --- a/src/protocol/commands/_vFile_open.rs +++ b/src/protocol/commands/_vFile_open.rs @@ -20,8 +20,8 @@ impl<'a> ParseCommand<'a> for vFileOpen<'a> { [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 = HostIoOpenMode::from_bits(decode_hex(body.next()?).ok()?).unwrap(); + let flags = HostIoOpenFlags::from_bits(decode_hex(body.next()?).ok()?)?; + let mode = HostIoOpenMode::from_bits(decode_hex(body.next()?).ok()?)?; Some(vFileOpen{filename, flags, mode}) }, _ => None, diff --git a/src/protocol/common/hex.rs b/src/protocol/common/hex.rs index 95b444ba..e30d88c3 100644 --- a/src/protocol/common/hex.rs +++ b/src/protocol/common/hex.rs @@ -200,6 +200,7 @@ pub fn decode_bin_buf(buf: &mut [u8]) -> Result<&mut [u8], DecodeBinBufError> { if cfg!(feature = "paranoid_unsafe") { Ok(&mut buf[..j]) } else { + debug_assert!(j <= len); unsafe { Ok(buf.get_unchecked_mut(..j)) } } } diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index d851ad64..84dd07b7 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -40,7 +40,7 @@ pub struct PacketBuf<'a> { impl<'a> PacketBuf<'a> { /// Validate the contents of the raw packet buffer, checking for checksum - /// consistency, structural correctness, and ASCII validation. + /// consistency and structural correctness. pub fn new(pkt_buf: &'a mut [u8]) -> Result, PacketParseError> { if pkt_buf.is_empty() { return Err(PacketParseError::EmptyBuf); @@ -75,7 +75,7 @@ impl<'a> PacketBuf<'a> { } /// (used for tests) Create a packet buffer from a raw body buffer, skipping - /// the header/checksum trimming stage. ASCII validation is still performed. + /// the header/checksum trimming stage. #[cfg(test)] pub fn new_with_raw_body(body: &'a mut [u8]) -> Result, PacketParseError> { let len = body.len(); diff --git a/src/target/ext/host_io.rs b/src/target/ext/host_io.rs index 970e5d41..36a60ee3 100644 --- a/src/target/ext/host_io.rs +++ b/src/target/ext/host_io.rs @@ -181,14 +181,15 @@ pub enum HostIoError { impl From for HostIoError { fn from(e: std::io::Error) -> HostIoError { use std::io::ErrorKind::*; - match e.kind() { - PermissionDenied => HostIoError::Errno(HostIoErrno::EPERM), - NotFound => HostIoError::Errno(HostIoErrno::ENOENT), - Interrupted => HostIoError::Errno(HostIoErrno::EINTR), - AlreadyExists => HostIoError::Errno(HostIoErrno::EEXIST), - InvalidInput => HostIoError::Errno(HostIoErrno::EINVAL), - _ => HostIoError::Errno(HostIoErrno::EUNKNOWN), - } + let errno = match e.kind() { + PermissionDenied => HostIoErrno::EPERM, + NotFound => HostIoErrno::ENOENT, + Interrupted => HostIoErrno::EINTR, + AlreadyExists => HostIoErrno::EEXIST, + InvalidInput => HostIoErrno::EINVAL, + _ => HostIoErrno::EUNKNOWN, + }; + HostIoError::Errno(errno) } } @@ -273,9 +274,9 @@ pub trait HostIoOpen: HostIo { /// Open a file at `filename` and return a file descriptor for it, or return /// [`HostIoError::Errno`] if an error occurs. /// - /// `flags` is the flags used when open (see [`HostIoOpenFlags`]), and - /// `mode` is the mode used if the file is created - /// (see [`HostIoOpenMode`]). + /// `flags` are the flags used when opening the file (see + /// [`HostIoOpenFlags`]), and `mode` is the mode used if the file is + /// created (see [`HostIoOpenMode`]). fn open( &mut self, filename: &[u8],