Skip to content

Commit

Permalink
Support read exec-file (#69)
Browse files Browse the repository at this point in the history
* Support read exec-file

* Some small fixes

* Try to pass buf as an argument of get_exec_file

* Set type of offset and length to usize

* Check return size

* Apply suggestions from code review

Co-authored-by: Daniel Prilik <[email protected]>

* Lint

* Change type of offset to u64

Co-authored-by: Daniel Prilik <[email protected]>
  • Loading branch information
bet4it and daniel5151 authored Sep 15, 2021
1 parent 9227dfd commit c2ed3e4
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!

Expand Down
22 changes: 22 additions & 0 deletions examples/armv4t/gdb/exec_file.rs
Original file line number Diff line number Diff line change
@@ -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<Pid>,
offset: u64,
length: usize,
buf: &mut [u8],
) -> TargetResult<usize, Self> {
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())
}
}
61 changes: 56 additions & 5 deletions examples/armv4t/gdb/host_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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))?;

Expand Down Expand Up @@ -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)),
};
Expand All @@ -122,7 +137,18 @@ impl target::ext::host_io::HostIoPread for Emu {
offset: u32,
output: HostIoOutput<'a>,
) -> HostIoResult<HostIoToken<'a>, 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)),
};
Expand All @@ -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<u32, Self> {
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)),
};
Expand All @@ -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<HostIoStat, Self> {
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)),
};
Expand Down
6 changes: 6 additions & 0 deletions examples/armv4t/gdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -100,6 +101,11 @@ impl Target for Emu {
fn host_io(&mut self) -> Option<target::ext::host_io::HostIoOps<Self>> {
Some(self)
}

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

impl Emu {
Expand Down
2 changes: 1 addition & 1 deletion examples/armv4t/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use gdbstub::{target::Target, ConnectionExt, DisconnectReason, GdbStub};

pub type DynResult<T> = Result<T, Box<dyn std::error::Error>>;

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;
Expand Down
1 change: 0 additions & 1 deletion examples/armv4t/test_bin/.gdbinit
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
file test.elf
target extended-remote :9001
4 changes: 4 additions & 0 deletions src/gdbstub_impl/ext/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
res.write_str(";qXfer:memory-map:read+")?;
}

if target.exec_file().is_some() {
res.write_str(";qXfer:exec-file:read+")?;
}

HandlerStatus::Handled
}
Base::QStartNoAckMode(_) => {
Expand Down
36 changes: 36 additions & 0 deletions src/gdbstub_impl/ext/exec_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use super::prelude::*;
use crate::protocol::commands::ext::ExecFile;

impl<T: Target, C: Connection> GdbStubImpl<T, C> {
pub(crate) fn handle_exec_file(
&mut self,
res: &mut ResponseWriter<C>,
target: &mut T,
command: ExecFile,
) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
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)
}
}
1 change: 1 addition & 0 deletions src/gdbstub_impl/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/gdbstub_impl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
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),
}
}
}
4 changes: 4 additions & 0 deletions src/protocol/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
37 changes: 37 additions & 0 deletions src/protocol/commands/_qXfer_exec_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use super::prelude::*;

use crate::common::Pid;

#[derive(Debug)]
pub struct qXferExecFileRead<'a> {
pub pid: Option<Pid>,
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<Self> {
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})
}
}
2 changes: 1 addition & 1 deletion src/protocol/commands/_vFile_setfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
Expand Down
32 changes: 32 additions & 0 deletions src/target/ext/exec_file.rs
Original file line number Diff line number Diff line change
@@ -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<Pid>,
offset: u64,
length: usize,
buf: &mut [u8],
) -> TargetResult<usize, Self>;
}

define_ext!(ExecFileOps, ExecFile);
1 change: 1 addition & 0 deletions src/target/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/target/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,11 @@ pub trait Target {
fn host_io(&mut self) -> Option<ext::host_io::HostIoOps<Self>> {
None
}

/// Provide exec-file
fn exec_file(&mut self) -> Option<ext::exec_file::ExecFileOps<Self>> {
None
}
}

macro_rules! impl_dyn_target {
Expand Down Expand Up @@ -395,6 +400,11 @@ macro_rules! impl_dyn_target {
(**self).monitor_cmd()
}

#[inline(always)]
fn exec_file(&mut self) -> Option<ext::exec_file::ExecFileOps<Self>> {
(**self).exec_file()
}

#[inline(always)]
fn extended_mode(&mut self) -> Option<ext::extended_mode::ExtendedModeOps<Self>> {
(**self).extended_mode()
Expand Down

0 comments on commit c2ed3e4

Please sign in to comment.