Skip to content

Commit b6c02bd

Browse files
authored
Add support for x packet (#186)
- Add `gdbstub` support for `x` packet that responds with `b` prefixed packed to indicate a binary format - This is by default disabled, implement the `use_x_lowcase_packet` method of `Target` to enable it - When this is enabled, `gdbstub` responds with `binary-upload+` to `qSupported` packet, to indicate support of this command. Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent c312107 commit b6c02bd

6 files changed

Lines changed: 159 additions & 0 deletions

File tree

src/protocol/commands.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ macro_rules! commands {
9494
fn support_reverse_step(&mut self) -> Option<()>;
9595
fn support_reverse_cont(&mut self) -> Option<()>;
9696
fn support_no_ack_mode(&mut self) -> Option<()>;
97+
fn support_x_lowcase_packet(&mut self) -> Option<()>;
9798
fn support_x_upcase_packet(&mut self) -> Option<()>;
9899
fn support_thread_extra_info(&mut self) -> Option<()>;
99100
}
@@ -155,6 +156,14 @@ macro_rules! commands {
155156
}
156157
}
157158

159+
fn support_x_lowcase_packet(&mut self) -> Option<()> {
160+
if self.use_x_lowcase_packet() {
161+
Some(())
162+
} else {
163+
None
164+
}
165+
}
166+
158167
fn support_x_upcase_packet(&mut self) -> Option<()> {
159168
if self.use_x_upcase_packet() {
160169
Some(())
@@ -255,6 +264,10 @@ commands! {
255264
"vCont" => _vCont::vCont<'a>,
256265
}
257266

267+
x_lowcase_packet use 'a {
268+
"x" => _x_lowcase::x<'a>,
269+
}
270+
258271
x_upcase_packet use 'a {
259272
"X" => _x_upcase::X<'a>,
260273
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use super::prelude::*;
2+
3+
#[derive(Debug)]
4+
pub struct x<'a> {
5+
pub addr: &'a [u8],
6+
pub len: usize,
7+
8+
/// Reuse PacketBuf underlying buffer to read the binary data into it
9+
pub buf: &'a mut [u8],
10+
}
11+
12+
impl<'a> ParseCommand<'a> for x<'a> {
13+
#[inline(always)]
14+
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
15+
// the total packet buffer currently looks like:
16+
//
17+
// +------+--------------------+-------------------+-------+-----------------+
18+
// | "$x" | addr (hex-encoded) | len (hex-encoded) | "#XX" | empty space ... |
19+
// +------+--------------------+-------------------+-------+-----------------+
20+
//
21+
// Unfortunately, while `len` can be hex-decoded right here and now into a
22+
// `usize`, `addr` corresponds to a Target::Arch::Usize, which requires holding
23+
// on to a valid &[u8] reference into the buffer.
24+
//
25+
// While it's not _perfectly_ efficient, simply leaving the decoded addr in
26+
// place and wasting a couple bytes is probably the easiest way to tackle this
27+
// problem:
28+
//
29+
// +------+------------------+------------------------------------------------+
30+
// | "$x" | addr (raw bytes) | usable buffer ... |
31+
// +------+------------------+------------------------------------------------+
32+
33+
let (buf, body_range) = buf.into_raw_buf();
34+
let body = buf.get_mut(body_range.start..body_range.end)?;
35+
36+
let mut body = body.split_mut(|b| *b == b',');
37+
38+
let addr = decode_hex_buf(body.next()?).ok()?;
39+
let addr_len = addr.len();
40+
let len = decode_hex(body.next()?).ok()?;
41+
42+
// ensures that `split_at_mut` doesn't panic
43+
if buf.len() < body_range.start + addr_len {
44+
return None;
45+
}
46+
47+
let (addr, buf) = buf.split_at_mut(body_range.start + addr_len);
48+
let addr = addr.get(b"$x".len()..)?;
49+
50+
Some(x { addr, len, buf })
51+
}
52+
}

src/stub/core_impl.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ mod target_xml;
4646
mod thread_extra_info;
4747
mod tracepoints;
4848
mod wasm;
49+
mod x_lowcase_packet;
4950
mod x_upcase_packet;
5051

5152
pub(crate) use resume::FinishExecStatus;
@@ -204,6 +205,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
204205
Command::TargetXml(cmd) => self.handle_target_xml(res, target, cmd),
205206
Command::Resume(cmd) => self.handle_stop_resume(res, target, cmd),
206207
Command::NoAckMode(cmd) => self.handle_no_ack_mode(res, target, cmd),
208+
Command::XLowcasePacket(cmd) => self.handle_x_lowcase_packet(res, target, cmd),
207209
Command::XUpcasePacket(cmd) => self.handle_x_upcase_packet(res, target, cmd),
208210
Command::SingleRegisterAccess(cmd) => {
209211
self.handle_single_register_access(res, target, cmd)

src/stub/core_impl/base.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
131131
res.write_str(";vforkdone-events+")?;
132132
}
133133

134+
if target.use_x_lowcase_packet() {
135+
res.write_str(";binary-upload+")?;
136+
}
137+
134138
if let Some(resume_ops) = target.base_ops().resume_ops() {
135139
let (reverse_cont, reverse_step) = match resume_ops {
136140
ResumeOps::MultiThread(ops) => (
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use super::prelude::*;
2+
use crate::arch::Arch;
3+
use crate::protocol::commands::ext::XLowcasePacket;
4+
use crate::target::ext::base::BaseOps;
5+
6+
impl<T: Target, C: Connection> GdbStubImpl<T, C> {
7+
pub(crate) fn handle_x_lowcase_packet(
8+
&mut self,
9+
res: &mut ResponseWriter<'_, C>,
10+
target: &mut T,
11+
command: XLowcasePacket<'_>,
12+
) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
13+
if !target.use_x_lowcase_packet() {
14+
return Ok(HandlerStatus::Handled);
15+
}
16+
17+
crate::__dead_code_marker!("x_lowcase_packet", "impl");
18+
19+
let handler_status = match command {
20+
XLowcasePacket::x(cmd) => {
21+
let buf = cmd.buf;
22+
let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
23+
.ok_or(Error::TargetMismatch)?;
24+
25+
let mut i = 0;
26+
let mut n = cmd.len;
27+
while n != 0 {
28+
let chunk_size = n.min(buf.len());
29+
30+
use num_traits::NumCast;
31+
32+
let addr = addr + NumCast::from(i).ok_or(Error::TargetMismatch)?;
33+
let data = &mut buf[..chunk_size];
34+
let data_len = match target.base_ops() {
35+
BaseOps::SingleThread(ops) => ops.read_addrs(addr, data),
36+
BaseOps::MultiThread(ops) => {
37+
ops.read_addrs(addr, data, self.current_mem_tid)
38+
}
39+
}
40+
.handle_error()?;
41+
42+
// TODO: add more specific error variant?
43+
let data = data.get(..data_len).ok_or(Error::PacketBufferOverflow)?;
44+
45+
// Start data with 'b' to indicate binary data
46+
if i == 0 {
47+
res.write_str("b")?;
48+
}
49+
50+
n -= chunk_size;
51+
i += chunk_size;
52+
53+
res.write_binary(data)?;
54+
}
55+
56+
HandlerStatus::Handled
57+
}
58+
};
59+
60+
Ok(handler_status)
61+
}
62+
}

src/target/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,31 @@ pub trait Target {
530530
true
531531
}
532532

533+
/// Enable/disable using the more efficient `x` packet to read to target
534+
/// memory (as opposed to the basic `m` packet).
535+
///
536+
/// **By default, this method returns `false`.**
537+
///
538+
/// This packet is disabled by default in order to maximize out-of-the-box
539+
/// compatibility between `gdbstub` and all released GDB and LLDB versions.
540+
/// For more context, see the discussion at
541+
/// [daniel5151/gdbstub#163](https://github.com/daniel5151/gdbstub/issues/163#issuecomment-4049691552).
542+
///
543+
/// `gdbstub` implements the `x` packet according to the GDB RSP spec, and
544+
/// as such, enabling this packet will break compatibility with older LLDB
545+
/// version.
546+
///
547+
/// _Author's note:_ If you are _certain_ that your target will only even be
548+
/// debugged used alongside a sufficiently recent GDB / LLDB version (i.e:
549+
/// those released sometime after ~early 2026), you can set this to `true`
550+
/// for improved performance. That said, unless you're planning to fetch
551+
/// a _lot_ of memory data from your `Target`, you may as well leave
552+
/// this optimization disabled, and guarantee client compatibility.
553+
#[inline(always)]
554+
fn use_x_lowcase_packet(&self) -> bool {
555+
false
556+
}
557+
533558
/// Whether `gdbstub` should provide a "stub" `resume` implementation on
534559
/// targets without support for resumption.
535560
///
@@ -802,6 +827,7 @@ macro_rules! impl_dyn_target {
802827
__delegate!(fn guard_rail_implicit_sw_breakpoints(&self) -> bool);
803828

804829
__delegate!(fn use_no_ack_mode(&self) -> bool);
830+
__delegate!(fn use_x_lowcase_packet(&self) -> bool);
805831
__delegate!(fn use_x_upcase_packet(&self) -> bool);
806832
__delegate!(fn use_resume_stub(&self) -> bool);
807833
__delegate!(fn use_rle(&self) -> bool);

0 commit comments

Comments
 (0)