Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for qXfer:libraries{,-svr4}:read #142

Merged
merged 8 commits into from Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -88,6 +88,7 @@ Of course, most use-cases will want to support additional debugging features as
- Can be used to automatically read the remote executable on attach (using `ExecFile`)
- Read auxiliary vector (`info auxv`)
- Extra thread info (`info threads`)
- Extra library information (`info sharedlibraries`)

_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
28 changes: 28 additions & 0 deletions examples/armv4t/gdb/libraries.rs
@@ -0,0 +1,28 @@
use super::copy_range_to_buf;
use crate::emu::Emu;
use gdbstub::target;
use gdbstub::target::TargetResult;

impl target::ext::libraries::LibrariesSvr4 for Emu {
fn get_libraries_svr4(
&self,
offset: u64,
length: usize,
buf: &mut [u8],
) -> TargetResult<usize, Self> {
// `l_ld` is the address of the `PT_DYNAMIC` ELF segment, so fake an
// address here.
//
// The `main-lm`, `lm`, and `lmid` seem to refer to in-memory structures
// which gdb may read, but gdb also seems to work well enough if they're
// null-ish or otherwise pointing to non-present things.
let xml = r#"
<library-list-svr4 version="1.0" main-lm="0x4">
<library name="/test.elf" lm="0x8" l_addr="0" l_ld="0" lmid="0x14"/>
</library-list-svr4>
"#
.trim()
.as_bytes();
Ok(copy_range_to_buf(xml, offset, length, buf))
}
}
8 changes: 8 additions & 0 deletions examples/armv4t/gdb/mod.rs
Expand Up @@ -20,6 +20,7 @@ mod catch_syscalls;
mod exec_file;
mod extended_mode;
mod host_io;
mod libraries;
mod lldb_register_info_override;
mod memory_map;
mod monitor_cmd;
Expand Down Expand Up @@ -153,6 +154,13 @@ impl Target for Emu {
fn support_auxv(&mut self) -> Option<target::ext::auxv::AuxvOps<'_, Self>> {
Some(self)
}

#[inline(always)]
fn support_libraries_svr4(
&mut self,
) -> Option<target::ext::libraries::LibrariesSvr4Ops<'_, Self>> {
Some(self)
}
}

impl SingleThreadBase for Emu {
Expand Down
4 changes: 4 additions & 0 deletions src/protocol/commands.rs
Expand Up @@ -330,4 +330,8 @@ commands! {
lldb_register_info {
"qRegisterInfo" => _qRegisterInfo::qRegisterInfo,
}

libraries_svr4 use 'a {
"qXfer:libraries-svr4:read" => _qXfer_libraries_svr4_read::qXferLibrariesSvr4Read<'a>,
}
}
18 changes: 18 additions & 0 deletions src/protocol/commands/_qXfer_libraries_svr4_read.rs
@@ -0,0 +1,18 @@
use crate::protocol::common::qxfer::ParseAnnex;
use crate::protocol::common::qxfer::QXferReadBase;

pub type qXferLibrariesSvr4Read<'a> = QXferReadBase<'a, LibrariesSvr4Annex>;

#[derive(Debug)]
pub struct LibrariesSvr4Annex;

impl<'a> ParseAnnex<'a> for LibrariesSvr4Annex {
#[inline(always)]
fn from_buf(buf: &[u8]) -> Option<Self> {
if buf != b"" {
daniel5151 marked this conversation as resolved.
Show resolved Hide resolved
return None;
}

Some(LibrariesSvr4Annex)
}
}
2 changes: 2 additions & 0 deletions src/stub/core_impl.rs
Expand Up @@ -31,6 +31,7 @@ mod catch_syscalls;
mod exec_file;
mod extended_mode;
mod host_io;
mod libraries;
mod lldb_register_info;
mod memory_map;
mod monitor_cmd;
Expand Down Expand Up @@ -216,6 +217,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
Command::Auxv(cmd) => self.handle_auxv(res, target, cmd),
Command::ThreadExtraInfo(cmd) => self.handle_thread_extra_info(res, target, cmd),
Command::LldbRegisterInfo(cmd) => self.handle_lldb_register_info(res, target, cmd),
Command::LibrariesSvr4(cmd) => self.handle_libraries_svr4(res, target, cmd),
// in the worst case, the command could not be parsed...
Command::Unknown(cmd) => {
// HACK: if the user accidentally sends a resume command to a
Expand Down
4 changes: 4 additions & 0 deletions src/stub/core_impl/base.rs
Expand Up @@ -195,6 +195,10 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
res.write_str(";qXfer:auxv:read+")?;
}

if target.support_libraries_svr4().is_some() {
res.write_str(";qXfer:libraries-svr4:read+")?;
}

HandlerStatus::Handled
}

Expand Down
36 changes: 36 additions & 0 deletions src/stub/core_impl/libraries.rs
@@ -0,0 +1,36 @@
use super::prelude::*;
use crate::protocol::commands::ext::LibrariesSvr4;

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

crate::__dead_code_marker!("libraries-svr4", "impl");

let handler_status = match command {
LibrariesSvr4::qXferLibrariesSvr4Read(cmd) => {
let ret = ops
.get_libraries_svr4(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)
}
}
29 changes: 29 additions & 0 deletions src/target/ext/libraries.rs
@@ -0,0 +1,29 @@
//! Report information about the loaded shared libraries for targets where there
//! are possibly multiple files to be debugged mapped into the same address
//! space.

use crate::target::Target;
use crate::target::TargetResult;

/// Target Extension - List an SVR4 (System-V/Unix) target's libraries.
pub trait LibrariesSvr4: Target {
/// Get library list XML for this target.
///
/// See the [GDB Documentation] for a description of the format.
///
/// [GDB Documentation]: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Library-List-Format-for-SVR4-Targets.html
///
/// 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_libraries_svr4(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's not as part of this PR... but having another API force the user return "raw XML" (vs. having gdbstub generate it on-the-fly) seems a little unfortunate from a usability POV.

e.g: In this case, I wonder if we could instead have an API that looks something like this instead:

fn get_libraries_srv4(
    &self, 
    main_lm: Target::Arch::Usize,
    report_lib: &mut dyn FnMut(
        /*name:*/ &str, 
        /*lm:*/ Target::Arch::Usize,
        /*l_addr:*/ Target::Arch::Usize,
        /*l_ld:*/ Target::Arch::Usize, 
        /*lmid:*/ Option<Target::Arch::Usize>
    )
) -> TargetResult<usize, Self>;

Though, as I typed this out, I remembered that I basically had the same through process while reviewing #54, and after reviewing that thread... I think the implementation in gdbstub would get a bit too hairy (especially when considering how we'd need to handle non-zero offset requests...)

Maybe a middle ground could be to keep the "raw XML" API, but also include some helper XML builders that would make it easier for end-users to generate the properly formatted XML they need to return?

e.g:

struct LibrariesSvr4XmlBuilder<'a, T> { ... }

impl<'a, T> LibrariesSvr4XmlBuilder<'a, T> where T: Target {
    pub fn new_fixed(buf: &'a mut [u8]) -> Self { ... }
    // using a growable vec
    #[cfg(feature = "alloc")]
    pub fn new_dynamic(&mut Vec<u8>) -> Self { ... }
    // returns error if buffer is full
    pub fn add_lib(&mut self, name: &str, lm: T::Arch::Usize, l_addr: T::Arch::Usize, ...) -> TargetResult<(), T> { ... }
}

Under the hood, this could re-use the ManagedVec type https://github.com/daniel5151/gdbstub/blob/master/src/util/managed_vec.rs

Hmmm...


...but in any case, I don't think this is something that needs to happen as part of this PR.

I think adding this (and other) XML builders would be a workstream in its own right, and some more thinking is required on what I'd want maintain a cohesive-ish API across all the different kinds of XML that'd be getting generated.

TL;DR: feel free to ignore this comment - I basically just wrote it for future me 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is a really good idea yeah, and I was wondering how bad it would be to prototype this. I like your idea of a LibrariesSvr4XmlBuilder as a sort of maximally general way of doing this so if raw XML were available it could be given but otherwise it's just as convenient to use the builder. The builder could also have a helper function to take all the raw parameters to get_libraries_svr4 plus a closure and do it all on-the-fly.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am now tracking this and other instances of generating XML via #143

&self,
offset: u64,
length: usize,
buf: &mut [u8],
) -> TargetResult<usize, Self>;
}

define_ext!(LibrariesSvr4Ops, LibrariesSvr4);
1 change: 1 addition & 0 deletions src/target/ext/mod.rs
Expand Up @@ -265,6 +265,7 @@ pub mod catch_syscalls;
pub mod exec_file;
pub mod extended_mode;
pub mod host_io;
pub mod libraries;
pub mod lldb_register_info_override;
pub mod memory_map;
pub mod monitor_cmd;
Expand Down
7 changes: 7 additions & 0 deletions src/target/ext/section_offsets.rs
Expand Up @@ -8,6 +8,13 @@
//! limited to reporting the offsets for code, data and bss, and is
//! generally considered a legacy feature.
//!
//! For System-V architectures GDB may use the `qXfer:libraries-svr4:read`
//! command to try to learn about loaded libraries and this can be implemented
//! with the [`LibrariesSvr4`
//! trait](crate::target::ext::libraries::LibrariesSvr4). Note that not all
//! targets may query this and it may not be applicable in all situations
//! either.
//!
//! For targets where library offsets are maintained externally (e.g. Windows)
//! you should consider implementing the more flexible `qXfer:library:read`.
//! See issue [#20](https://github.com/daniel5151/gdbstub/issues/20) for more
Expand Down
7 changes: 7 additions & 0 deletions src/target/mod.rs
Expand Up @@ -676,6 +676,13 @@ pub trait Target {
fn support_auxv(&mut self) -> Option<ext::auxv::AuxvOps<'_, Self>> {
None
}

/// Support for reading a list of libraries for SVR4 (System-V/Unix)
/// platforms.
#[inline(always)]
fn support_libraries_svr4(&mut self) -> Option<ext::libraries::LibrariesSvr4Ops<'_, Self>> {
None
}
}

macro_rules! __delegate {
Expand Down