Skip to content

Commit 769fe6a

Browse files
committed
create Arch + Register traits
Ah ha! No more leaking GDB internals to users*! *Assuming the user is using an architecture listed already implemented under the `arch` module, they don't have to dig through the GDB source for some obscure XML files. The unfortunate side-effect of this change is the really digusting type syntax which `Target` has to use. `<Self::Arch as Arch>::Usize` is really gnarly :sad: If rust-lang/rust#38078 ever gets fixed, the type could be re-written as `Self::Arch::Usize`, which is a _lot_ cleaner.
1 parent b3a625f commit 769fe6a

File tree

9 files changed

+249
-129
lines changed

9 files changed

+249
-129
lines changed

examples/armv4t/gdb.rs

+19-44
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
use armv4t_emu::{reg, Memory};
2-
use gdbstub::{HwBreakOp, Target, TargetState, WatchKind};
2+
use gdbstub::{arch, HwBreakOp, Target, TargetState, WatchKind};
33

44
use crate::emu::{Emu, Event};
55

66
impl Target for Emu {
7-
type Usize = u32;
7+
type Arch = arch::arm::Armv4t;
88
type Error = &'static str;
99

10-
fn target_description_xml() -> Option<&'static str> {
11-
Some(r#"<target version="1.0"><architecture>armv4t</architecture></target>"#)
12-
}
13-
1410
fn step(&mut self) -> Result<TargetState<u32>, Self::Error> {
1511
let event = match self.step() {
1612
Some(event) => event,
@@ -32,54 +28,33 @@ impl Target for Emu {
3228
}
3329

3430
// order specified in binutils-gdb/blob/master/gdb/features/arm/arm-core.xml
35-
fn read_registers(&mut self, mut push_reg: impl FnMut(&[u8])) -> Result<(), &'static str> {
31+
fn read_registers(
32+
&mut self,
33+
regs: &mut arch::arm::reg::ArmCoreRegs,
34+
) -> Result<(), &'static str> {
3635
let mode = self.cpu.mode();
37-
for i in 0..13 {
38-
push_reg(&self.cpu.reg_get(mode, i).to_le_bytes());
39-
}
40-
push_reg(&self.cpu.reg_get(mode, reg::SP).to_le_bytes()); // 13
41-
push_reg(&self.cpu.reg_get(mode, reg::LR).to_le_bytes()); // 14
42-
push_reg(&self.cpu.reg_get(mode, reg::PC).to_le_bytes()); // 15
4336

44-
// Floating point registers, unused
45-
for _ in 0..25 {
46-
push_reg(&[0, 0, 0, 0]);
37+
for i in 0..13 {
38+
regs.r[i] = self.cpu.reg_get(mode, i as u8);
4739
}
48-
49-
push_reg(&self.cpu.reg_get(mode, reg::CPSR).to_le_bytes());
40+
regs.sp = self.cpu.reg_get(mode, reg::SP);
41+
regs.lr = self.cpu.reg_get(mode, reg::LR);
42+
regs.pc = self.cpu.reg_get(mode, reg::PC);
43+
regs.cpsr = self.cpu.reg_get(mode, reg::CPSR);
5044

5145
Ok(())
5246
}
5347

54-
fn write_registers(
55-
&mut self,
56-
mut pop_reg: impl FnMut() -> Option<u8>,
57-
) -> Result<(), &'static str> {
58-
const ERR: &str = "malformed write register packet";
59-
60-
let mut next = {
61-
move || -> Option<u32> {
62-
Some(
63-
(pop_reg()? as u32)
64-
| (pop_reg()? as u32) << 8
65-
| (pop_reg()? as u32) << 16
66-
| (pop_reg()? as u32) << 24,
67-
)
68-
}
69-
};
48+
fn write_registers(&mut self, regs: &arch::arm::reg::ArmCoreRegs) -> Result<(), &'static str> {
7049
let mode = self.cpu.mode();
50+
7151
for i in 0..13 {
72-
self.cpu.reg_set(mode, i, next().ok_or(ERR)?);
73-
}
74-
self.cpu.reg_set(mode, reg::SP, next().ok_or(ERR)?);
75-
self.cpu.reg_set(mode, reg::LR, next().ok_or(ERR)?);
76-
self.cpu.reg_set(mode, reg::PC, next().ok_or(ERR)?);
77-
// Floating point registers, unused
78-
for _ in 0..25 {
79-
next().ok_or(ERR)?;
52+
self.cpu.reg_set(mode, i, regs.r[i as usize]);
8053
}
81-
82-
self.cpu.reg_set(mode, reg::CPSR, next().ok_or(ERR)?);
54+
self.cpu.reg_set(mode, reg::SP, regs.sp);
55+
self.cpu.reg_set(mode, reg::LR, regs.lr);
56+
self.cpu.reg_set(mode, reg::PC, regs.pc);
57+
self.cpu.reg_set(mode, reg::CPSR, regs.cpsr);
8358

8459
Ok(())
8560
}

src/arch/arm/mod.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//! Implementations for various ARM architectures.
2+
3+
use crate::Arch;
4+
5+
pub mod reg;
6+
7+
/// Implements `Arch` for ARMv4T
8+
#[derive(Eq, PartialEq)]
9+
pub struct Armv4t;
10+
11+
impl Arch for Armv4t {
12+
type Usize = u32;
13+
type Registers = reg::ArmCoreRegs;
14+
15+
fn read_pc(regs: &reg::ArmCoreRegs) -> u32 {
16+
regs.pc
17+
}
18+
19+
fn target_description_xml() -> Option<&'static str> {
20+
Some(r#"<target version="1.0"><architecture>armv4t</architecture></target>"#)
21+
}
22+
}

src/arch/arm/reg/arm_core.rs

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use crate::Registers;
2+
3+
/// 32-bit ARM core registers.
4+
///
5+
/// Source: https://github.com/bminor/binutils-gdb/blob/master/gdb/features/arm/arm-core.xml
6+
#[derive(Default)]
7+
pub struct ArmCoreRegs {
8+
/// General purpose registers (R0-R12)
9+
pub r: [u32; 13],
10+
/// Stack Pointer (R13)
11+
pub sp: u32,
12+
/// Link Register (R14)
13+
pub lr: u32,
14+
/// Program Counter (R15)
15+
pub pc: u32,
16+
/// Current Program Status Register (cpsr)
17+
pub cpsr: u32,
18+
}
19+
20+
impl Registers for ArmCoreRegs {
21+
fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
22+
macro_rules! write_bytes {
23+
($bytes:expr) => {
24+
for b in $bytes {
25+
write_byte(Some(*b))
26+
}
27+
};
28+
}
29+
30+
for reg in self.r.iter() {
31+
write_bytes!(&reg.to_le_bytes());
32+
}
33+
write_bytes!(&self.sp.to_le_bytes());
34+
write_bytes!(&self.lr.to_le_bytes());
35+
write_bytes!(&self.pc.to_le_bytes());
36+
37+
// Floating point registers (unused)
38+
for _ in 0..25 {
39+
(0..4).for_each(|_| write_byte(None))
40+
}
41+
42+
write_bytes!(&self.cpsr.to_le_bytes());
43+
}
44+
45+
fn gdb_deserialize(&mut self, mut bytes: impl Iterator<Item = u8>) -> Result<(), ()> {
46+
let mut next_u32 = move || -> Option<u32> {
47+
let val = (bytes.next()? as u32)
48+
| (bytes.next()? as u32) << 8
49+
| (bytes.next()? as u32) << 16
50+
| (bytes.next()? as u32) << 24;
51+
Some(val)
52+
};
53+
54+
for reg in self.r.iter_mut() {
55+
*reg = next_u32().ok_or(())?
56+
}
57+
self.sp = next_u32().ok_or(())?;
58+
self.lr = next_u32().ok_or(())?;
59+
self.pc = next_u32().ok_or(())?;
60+
61+
// Floating point registers (unused)
62+
for _ in 0..25 {
63+
next_u32().ok_or(())?;
64+
}
65+
66+
self.cpsr = next_u32().ok_or(())?;
67+
68+
Ok(())
69+
}
70+
}

src/arch/arm/reg/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//! `GdbRegister` structs for various ARM architectures.
2+
3+
mod arm_core;
4+
5+
pub use arm_core::ArmCoreRegs;

src/arch/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Built-in implementations of the [`Arch`](arch/trait.Arch.html) and
2+
//! [`Registers`](arch/trait.Registers.html) traits for various
3+
//! architectures.
4+
//!
5+
//! **Note:** Please consider upstreaming any missing `Arch` + `Registers`
6+
//! implementations you happen to implement yourself!
7+
8+
pub mod arm;

src/arch_traits.rs

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use core::fmt::{self, Debug};
2+
3+
use num_traits::{Num, PrimInt, Unsigned};
4+
5+
/// A struct which corresponds to a particular architecture's registers.
6+
pub trait Registers: Default {
7+
/// Serialize `self` into a GDB register bytestream.
8+
///
9+
/// The registers must be serialized in the order specified by the
10+
/// architecture's `<target>.xml`. Missing registers are serialized by
11+
/// passing `None` to write_byte (which gets translated to an "xx" string
12+
/// within the GdbStub).
13+
///
14+
/// e.g: for ARM:
15+
/// github.com/bminor/binutils-gdb/blob/master/gdb/features/arm/arm-core.xml
16+
fn gdb_serialize(&self, write_byte: impl FnMut(Option<u8>));
17+
18+
/// Deserialize a GDB register bytestream into `self`.
19+
///
20+
/// The bytes will be provided in the order specified by the architecture's
21+
/// `<target>.xml`.
22+
///
23+
/// e.g: for ARM:
24+
/// github.com/bminor/binutils-gdb/blob/master/gdb/features/arm/arm-core.xml
25+
fn gdb_deserialize(&mut self, bytes: impl Iterator<Item = u8>) -> Result<(), ()>;
26+
}
27+
28+
/// Encodes architecture-specific information, such as pointer size, register
29+
/// layout, etc...
30+
pub trait Arch: Eq + PartialEq {
31+
/// The architecture's pointer size (e.g: `u32` on a 32-bit system).
32+
type Usize: Num + PrimInt + Unsigned + Debug + fmt::LowerHex;
33+
34+
/// The architecture's register file
35+
type Registers: Registers;
36+
37+
/// Read the target's current PC.
38+
fn read_pc(registers: &Self::Registers) -> Self::Usize;
39+
40+
/// (optional) Return the platform's `features.xml` file.
41+
///
42+
/// Implementing this method enables `gdb` to automatically detect the
43+
/// target's architecture, saving the hassle of having to run `set
44+
/// architecture <arch>` when starting a debugging session.
45+
///
46+
/// These descriptions can be quite succinct. For example, the target
47+
/// description for an `armv4t` platform can be as simple as:
48+
///
49+
/// ```
50+
/// r#"<target version="1.0"><architecture>armv4t</architecture></target>"#
51+
/// # ;
52+
/// ```
53+
///
54+
/// See the [GDB docs](https://sourceware.org/gdb/current/onlinedocs/gdb/Target-Description-Format.html)
55+
/// for details on the target description XML format.
56+
fn target_description_xml() -> Option<&'static str> {
57+
None
58+
}
59+
}

src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -242,14 +242,19 @@
242242
//! open a PR to add it to this list!
243243
244244
#![cfg_attr(not(feature = "std"), no_std)]
245+
// #![deny(missing_docs)]
245246

247+
pub mod arch;
248+
249+
mod arch_traits;
246250
mod connection;
247251
mod error;
248252
mod protocol;
249253
mod stub;
250254
mod target;
251255
mod util;
252256

257+
pub use arch_traits::{Arch, Registers};
253258
pub use connection::Connection;
254259
pub use error::Error;
255260
pub use stub::GdbStub;

src/stub.rs

+25-20
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use num_traits::ops::saturating::Saturating;
55
use crate::util::slicevec::SliceVec;
66
use crate::{
77
protocol::{Command, Packet, ResponseWriter},
8-
Connection, Error, HwBreakOp, Target, TargetState, WatchKind,
8+
Arch, Connection, Error, HwBreakOp, Registers, Target, TargetState, WatchKind,
99
};
1010

1111
enum ExecState {
@@ -24,7 +24,7 @@ pub struct GdbStub<'a, T: Target, C: Connection> {
2424
conn: C,
2525
exec_state: ExecState,
2626
// TODO?: use BTreeSet if std is enabled for infinite swbreaks?
27-
swbreak: ArrayVec<[T::Usize; MAX_SWBREAK]>,
27+
swbreak: ArrayVec<[<T::Arch as Arch>::Usize; MAX_SWBREAK]>,
2828
packet_buffer: Option<&'a mut [u8]>,
2929
}
3030

@@ -67,14 +67,14 @@ impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> {
6767
// res.write_str("ConditionalBreakpoints+;")?;
6868

6969
// probe support for target description xml
70-
if T::target_description_xml().is_some() {
70+
if T::Arch::target_description_xml().is_some() {
7171
res.write_str("qXfer:features:read+;")?;
7272
}
7373
}
7474
Command::vContQuestionMark(_) => res.write_str("vCont;c;s;t")?,
7575
Command::qXferFeaturesRead(cmd) => {
7676
assert_eq!(cmd.annex, "target.xml");
77-
match T::target_description_xml() {
77+
match T::Arch::target_description_xml() {
7878
Some(xml) => {
7979
let xml = xml.trim();
8080
if cmd.offset >= xml.len() {
@@ -102,28 +102,33 @@ impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> {
102102
Command::QuestionMark(_) => res.write_str("S05")?,
103103
Command::qAttached(_) => res.write_str("1")?,
104104
Command::g(_) => {
105-
let mut err = Ok(());
105+
let mut regs: <T::Arch as Arch>::Registers = Default::default();
106106
target
107-
.read_registers(|reg| {
108-
if let Err(e) = res.write_hex_buf(reg) {
109-
err = Err(e)
110-
}
111-
})
107+
.read_registers(&mut regs)
112108
.map_err(Error::TargetError)?;
109+
let mut err = Ok(());
110+
regs.gdb_serialize(|val| {
111+
let res = match val {
112+
Some(b) => res.write_hex(b),
113+
None => res.write_str("xx"),
114+
};
115+
if let Err(e) = res {
116+
err = Err(e);
117+
}
118+
});
113119
err?;
114120
}
115-
Command::G(mut cmd) => {
116-
// TODO: use the length of the slice returned by `target.read_registers` to
117-
// validate that the server sent the correct amount of data
118-
target
119-
.write_registers(|| cmd.vals.next())
120-
.map_err(Error::TargetError)?;
121+
Command::G(cmd) => {
122+
let mut regs: <T::Arch as Arch>::Registers = Default::default();
123+
regs.gdb_deserialize(cmd.vals)
124+
.map_err(|_| Error::PacketParse)?; // FIXME: more granular error?
125+
target.write_registers(&regs).map_err(Error::TargetError)?;
121126
res.write_str("OK")?;
122127
}
123128
Command::m(cmd) => {
124129
let mut err = Ok(());
125130
// XXX: get rid of these unwraps ahhh
126-
let start: T::Usize = num_traits::NumCast::from(cmd.addr).unwrap();
131+
let start: <T::Arch as Arch>::Usize = num_traits::NumCast::from(cmd.addr).unwrap();
127132
// XXX: on overflow, this _should_ wrap around to low addresses (maybe?)
128133
let end = start.saturating_add(num_traits::NumCast::from(cmd.len).unwrap());
129134

@@ -156,7 +161,7 @@ impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> {
156161
}
157162
Command::Z(cmd) => {
158163
// XXX: get rid of this unwrap ahhh
159-
let addr: T::Usize = num_traits::NumCast::from(cmd.addr).unwrap();
164+
let addr: <T::Arch as Arch>::Usize = num_traits::NumCast::from(cmd.addr).unwrap();
160165

161166
use HwBreakOp::*;
162167
let supported = match cmd.type_ {
@@ -177,7 +182,7 @@ impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> {
177182
}
178183
Command::z(cmd) => {
179184
// XXX: get rid of this unwrap ahhh
180-
let addr: T::Usize = num_traits::NumCast::from(cmd.addr).unwrap();
185+
let addr: <T::Arch as Arch>::Usize = num_traits::NumCast::from(cmd.addr).unwrap();
181186

182187
use HwBreakOp::*;
183188
let supported = match cmd.type_ {
@@ -293,7 +298,7 @@ impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> {
293298
///
294299
/// Returns once the GDB client cleanly closes the debugging session (via
295300
/// the GDB `quit` command).
296-
pub fn run(&mut self, target: &mut T) -> Result<TargetState<T::Usize>, Error<T, C>> {
301+
pub fn run(&mut self, target: &mut T) -> Result<TargetState<T::Arch>, Error<T, C>> {
297302
let packet_buffer = self.packet_buffer.take().unwrap();
298303

299304
loop {

0 commit comments

Comments
 (0)