diff --git a/README.md b/README.md index f3858654..9fcec129 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Of course, most use-cases will want to support additional debugging features as - Extend the GDB protocol with custom debug commands using GDB's `monitor` command - Get target memory map - Perform Host I/O operations +- Get target exec file _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/exec_file.rs b/examples/armv4t/gdb/exec_file.rs new file mode 100644 index 00000000..c130a9dc --- /dev/null +++ b/examples/armv4t/gdb/exec_file.rs @@ -0,0 +1,22 @@ +use gdbstub::common::Pid; +use gdbstub::target; +use gdbstub::target::TargetResult; + +use crate::emu::Emu; + +impl target::ext::exec_file::ExecFile for Emu { + fn get_exec_file( + &self, + _pid: Option, + offset: u64, + length: usize, + buf: &mut [u8], + ) -> TargetResult { + let filename = b"/test.elf"; + let len = filename.len(); + let data = &filename[len.min(offset as usize)..len.min(offset as usize + length)]; + let buf = &mut buf[..data.len()]; + buf.copy_from_slice(data); + Ok(data.len()) + } +} diff --git a/examples/armv4t/gdb/host_io.rs b/examples/armv4t/gdb/host_io.rs index e4844ab7..a3a6e3f1 100644 --- a/examples/armv4t/gdb/host_io.rs +++ b/examples/armv4t/gdb/host_io.rs @@ -7,6 +7,9 @@ use gdbstub::target::ext::host_io::{ }; use crate::emu::Emu; +use crate::TEST_PROGRAM_ELF; + +const FD_RESERVED: u32 = 1; impl target::ext::host_io::HostIo for Emu { #[inline(always)] @@ -61,6 +64,14 @@ impl target::ext::host_io::HostIoOpen for Emu { return Err(HostIoError::Errno(HostIoErrno::ENOENT)); } + // In this example, the test binary is compiled into the binary itself as the + // `TEST_PROGRAM_ELF` array using `include_bytes!`. As such, we must "spoof" the + // existence of a real file, which will actually be backed by the in-binary + // `TEST_PROGRAM_ELF` array. + if filename == b"/test.elf" { + return Ok(0); + } + let path = std::str::from_utf8(filename).map_err(|_| HostIoError::Errno(HostIoErrno::ENOENT))?; @@ -95,13 +106,17 @@ impl target::ext::host_io::HostIoOpen for Emu { } }; - Ok(n as u32) + Ok(n as u32 + FD_RESERVED) } } impl target::ext::host_io::HostIoClose for Emu { fn close(&mut self, fd: u32) -> HostIoResult<(), Self> { - let file = match self.files.get_mut(fd as usize) { + if fd < FD_RESERVED { + return Ok(()); + } + + let file = match self.files.get_mut((fd - FD_RESERVED) as usize) { Some(file) => file, _ => return Err(HostIoError::Errno(HostIoErrno::EBADF)), }; @@ -122,7 +137,18 @@ impl target::ext::host_io::HostIoPread for Emu { offset: u32, output: HostIoOutput<'a>, ) -> HostIoResult, Self> { - let file = match self.files.get_mut(fd as usize) { + if fd < FD_RESERVED { + if fd == 0 { + let len = TEST_PROGRAM_ELF.len(); + return Ok(output.write( + &TEST_PROGRAM_ELF[len.min(offset as usize)..len.min((offset + count) as usize)], + )); + } else { + return Err(HostIoError::Errno(HostIoErrno::EBADF)); + } + } + + let file = match self.files.get_mut((fd - FD_RESERVED) as usize) { Some(Some(file)) => file, _ => return Err(HostIoError::Errno(HostIoErrno::EBADF)), }; @@ -136,7 +162,11 @@ 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 file = match self.files.get_mut(fd as usize) { + if fd < FD_RESERVED { + return Err(HostIoError::Errno(HostIoErrno::EACCES)); + } + + let file = match self.files.get_mut((fd - FD_RESERVED) as usize) { Some(Some(file)) => file, _ => return Err(HostIoError::Errno(HostIoErrno::EBADF)), }; @@ -149,7 +179,28 @@ impl target::ext::host_io::HostIoPwrite for Emu { impl target::ext::host_io::HostIoFstat for Emu { fn fstat(&mut self, fd: u32) -> HostIoResult { - let metadata = match self.files.get(fd as usize) { + if fd < FD_RESERVED { + if fd == 0 { + return 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: TEST_PROGRAM_ELF.len() as u64, + st_blksize: 0, + st_blocks: 0, + st_atime: 0, + st_mtime: 0, + st_ctime: 0, + }); + } else { + return Err(HostIoError::Errno(HostIoErrno::EBADF)); + } + } + let metadata = match self.files.get((fd - FD_RESERVED) as usize) { Some(Some(file)) => file.metadata()?, _ => return Err(HostIoError::Errno(HostIoErrno::EBADF)), }; diff --git a/examples/armv4t/gdb/mod.rs b/examples/armv4t/gdb/mod.rs index 6b8896a9..89929cc1 100644 --- a/examples/armv4t/gdb/mod.rs +++ b/examples/armv4t/gdb/mod.rs @@ -16,6 +16,7 @@ use crate::emu::{Emu, Event}; mod breakpoints; mod catch_syscalls; +mod exec_file; mod extended_mode; mod host_io; mod memory_map; @@ -100,6 +101,11 @@ impl Target for Emu { fn host_io(&mut self) -> Option> { Some(self) } + + #[inline(always)] + fn exec_file(&mut self) -> Option> { + Some(self) + } } impl Emu { diff --git a/examples/armv4t/main.rs b/examples/armv4t/main.rs index 1ada5f41..e2e1f242 100644 --- a/examples/armv4t/main.rs +++ b/examples/armv4t/main.rs @@ -9,7 +9,7 @@ use gdbstub::{target::Target, ConnectionExt, DisconnectReason, GdbStub}; pub type DynResult = Result>; -static TEST_PROGRAM_ELF: &[u8] = include_bytes!("test_bin/test.elf"); +pub static TEST_PROGRAM_ELF: &[u8] = include_bytes!("test_bin/test.elf"); mod emu; mod gdb; diff --git a/examples/armv4t/test_bin/.gdbinit b/examples/armv4t/test_bin/.gdbinit index 6f9d0b88..2d2e812d 100644 --- a/examples/armv4t/test_bin/.gdbinit +++ b/examples/armv4t/test_bin/.gdbinit @@ -1,2 +1 @@ -file test.elf target extended-remote :9001 diff --git a/src/gdbstub_impl/ext/base.rs b/src/gdbstub_impl/ext/base.rs index 58513428..cfbfe678 100644 --- a/src/gdbstub_impl/ext/base.rs +++ b/src/gdbstub_impl/ext/base.rs @@ -115,6 +115,10 @@ impl GdbStubImpl { res.write_str(";qXfer:memory-map:read+")?; } + if target.exec_file().is_some() { + res.write_str(";qXfer:exec-file:read+")?; + } + HandlerStatus::Handled } Base::QStartNoAckMode(_) => { diff --git a/src/gdbstub_impl/ext/exec_file.rs b/src/gdbstub_impl/ext/exec_file.rs new file mode 100644 index 00000000..c7985c6b --- /dev/null +++ b/src/gdbstub_impl/ext/exec_file.rs @@ -0,0 +1,36 @@ +use super::prelude::*; +use crate::protocol::commands::ext::ExecFile; + +impl GdbStubImpl { + pub(crate) fn handle_exec_file( + &mut self, + res: &mut ResponseWriter, + target: &mut T, + command: ExecFile, + ) -> Result> { + let ops = match target.exec_file() { + Some(ops) => ops, + None => return Ok(HandlerStatus::Handled), + }; + + crate::__dead_code_marker!("exec_file", "impl"); + + let handler_status = match command { + ExecFile::qXferExecFileRead(cmd) => { + let ret = ops + .get_exec_file(cmd.pid, cmd.offset, cmd.length, cmd.buf) + .handle_error()?; + if ret == 0 { + res.write_str("l")?; + } else { + res.write_str("m")?; + // TODO: add more specific error variant? + res.write_binary(cmd.buf.get(..ret).ok_or(Error::PacketBufferOverflow)?)?; + } + HandlerStatus::Handled + } + }; + + Ok(handler_status) + } +} diff --git a/src/gdbstub_impl/ext/mod.rs b/src/gdbstub_impl/ext/mod.rs index 2aae2fdf..88684f85 100644 --- a/src/gdbstub_impl/ext/mod.rs +++ b/src/gdbstub_impl/ext/mod.rs @@ -14,6 +14,7 @@ mod prelude { mod base; mod breakpoints; mod catch_syscalls; +mod exec_file; mod extended_mode; mod host_io; mod memory_map; diff --git a/src/gdbstub_impl/mod.rs b/src/gdbstub_impl/mod.rs index b27390ac..8b7cc6f1 100644 --- a/src/gdbstub_impl/mod.rs +++ b/src/gdbstub_impl/mod.rs @@ -578,6 +578,7 @@ impl GdbStubImpl { 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), + Command::ExecFile(cmd) => self.handle_exec_file(res, target, cmd), } } } diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs index 98538751..b171ea6e 100644 --- a/src/protocol/commands.rs +++ b/src/protocol/commands.rs @@ -225,6 +225,10 @@ commands! { "qXfer:memory-map:read" => _qXfer_memory_map::qXferMemoryMapRead, } + exec_file use 'a { + "qXfer:exec-file:read" => _qXfer_exec_file::qXferExecFileRead<'a>, + } + host_io use 'a { "vFile:open" => _vFile_open::vFileOpen<'a>, "vFile:close" => _vFile_close::vFileClose, diff --git a/src/protocol/commands/_qXfer_exec_file.rs b/src/protocol/commands/_qXfer_exec_file.rs new file mode 100644 index 00000000..a74b83d5 --- /dev/null +++ b/src/protocol/commands/_qXfer_exec_file.rs @@ -0,0 +1,37 @@ +use super::prelude::*; + +use crate::common::Pid; + +#[derive(Debug)] +pub struct qXferExecFileRead<'a> { + pub pid: Option, + pub offset: u64, + pub length: usize, + + pub buf: &'a mut [u8], +} + +impl<'a> ParseCommand<'a> for qXferExecFileRead<'a> { + fn from_packet(buf: PacketBuf<'a>) -> Option { + let (buf, body_range) = buf.into_raw_buf(); + let body = buf.get_mut(body_range.start..body_range.end)?; + + if body.is_empty() { + return None; + } + + let mut body = body.split(|b| *b == b':').skip(1); + let pid = match body.next()? { + [] => None, + buf => Some(Pid::new(decode_hex(buf).ok()?)?) + }; + + let mut body = body.next()?.split(|b| *b == b','); + let offset = decode_hex(body.next()?).ok()?; + let length = decode_hex(body.next()?).ok()?; + + drop(body); + + Some(qXferExecFileRead {pid, offset, length, buf}) + } +} diff --git a/src/protocol/commands/_vFile_setfs.rs b/src/protocol/commands/_vFile_setfs.rs index 331948c5..ff0aab1c 100644 --- a/src/protocol/commands/_vFile_setfs.rs +++ b/src/protocol/commands/_vFile_setfs.rs @@ -16,7 +16,7 @@ impl<'a> ParseCommand<'a> for vFileSetfs { match body { [b':', body @ ..] => { - let fs = match core::num::NonZeroUsize::new(decode_hex(body).ok()?) { + let fs = match crate::common::Pid::new(decode_hex(body).ok()?) { None => FsKind::Stub, Some(pid) => FsKind::Pid(pid), }; diff --git a/src/target/ext/exec_file.rs b/src/target/ext/exec_file.rs new file mode 100644 index 00000000..0db98742 --- /dev/null +++ b/src/target/ext/exec_file.rs @@ -0,0 +1,32 @@ +//! Provide exec-file path for the target. +use crate::target::{Target, TargetResult}; + +use crate::common::Pid; + +/// Target Extension - Provide current exec-file. +/// +/// NOTE: this extension is primarily intended to be used alongside the [`Host +/// I/O Extensions`](crate::target::ext::host_io), which enables the GDB client +/// to read the executable file directly from the target +pub trait ExecFile: Target { + /// Get full absolute path of the file that was executed to create + /// process `pid` running on the remote system. + /// + /// If `pid` is `None`, return the filename corresponding to the + /// currently executing process. + /// + /// Return the number of bytes written into `buf` (which may be less than + /// `length`). + /// + /// If `offset` is greater than the length of the underlying data, return + /// `Ok(0)`. + fn get_exec_file( + &self, + pid: Option, + offset: u64, + length: usize, + buf: &mut [u8], + ) -> TargetResult; +} + +define_ext!(ExecFileOps, ExecFile); diff --git a/src/target/ext/mod.rs b/src/target/ext/mod.rs index 99f02809..5aae7807 100644 --- a/src/target/ext/mod.rs +++ b/src/target/ext/mod.rs @@ -259,6 +259,7 @@ macro_rules! define_ext { pub mod base; pub mod breakpoints; pub mod catch_syscalls; +pub mod exec_file; pub mod extended_mode; pub mod host_io; pub mod memory_map; diff --git a/src/target/mod.rs b/src/target/mod.rs index 0f262bee..e5b02f61 100644 --- a/src/target/mod.rs +++ b/src/target/mod.rs @@ -364,6 +364,11 @@ pub trait Target { fn host_io(&mut self) -> Option> { None } + + /// Provide exec-file + fn exec_file(&mut self) -> Option> { + None + } } macro_rules! impl_dyn_target { @@ -395,6 +400,11 @@ macro_rules! impl_dyn_target { (**self).monitor_cmd() } + #[inline(always)] + fn exec_file(&mut self) -> Option> { + (**self).exec_file() + } + #[inline(always)] fn extended_mode(&mut self) -> Option> { (**self).extended_mode()