Skip to content

Commit

Permalink
add qRegisterInfo packet support
Browse files Browse the repository at this point in the history
  • Loading branch information
jawilk committed Jun 2, 2022
1 parent 1fde6f7 commit dac328a
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file.

This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# 0.6.2
- add LLDB-specific `qRegisterInfo` packet
- add `write_dec` method in the `ResponseWriter` as a convenient method to write base 10 numbers as ascii chars

# 0.6.1

- add LLDB-specific HostIoOpenFlags [\#100](https://github.com/daniel5151/gdbstub/pull/100) ([mrk](https://github.com/mrk-its))
Expand Down
9 changes: 9 additions & 0 deletions examples/armv4t/gdb/mod.rs
Expand Up @@ -19,6 +19,7 @@ mod extended_mode;
mod host_io;
mod memory_map;
mod monitor_cmd;
mod register_info;
mod section_offsets;
mod target_description_xml_override;

Expand Down Expand Up @@ -141,6 +142,14 @@ impl Target for Emu {
fn support_auxv(&mut self) -> Option<target::ext::auxv::AuxvOps<'_, Self>> {
Some(self)
}

// LLDB only
#[inline(always)]
fn support_register_info(
&mut self,
) -> Option<target::ext::register_info::RegisterInfoOps<'_, Self>> {
Some(self)
}
}

impl SingleThreadBase for Emu {
Expand Down
73 changes: 73 additions & 0 deletions examples/armv4t/gdb/register_info.rs
@@ -0,0 +1,73 @@
use gdbstub::target;
use gdbstub::target::ext::register_info::Register;

use crate::gdb::Emu;

// This implementation is for illustrative purposes only. If the .xml target description is used, the qRegisterInfo Packet is not necessary. (Note: This is an LLDB specific packet)

// We have r0-pc from 0-16 but cpsr is at offset 25*4 in the 'g'/'G' packet, so we add 8 padding
// registers here. Please see gdbstub/examples/armv4t/gdb/target_description_xml_override.rs for
// more info.
const MAX_REG_NUM: u8 = 17 + 8;
const SP: u8 = 13;
const PC: u8 = 15;
const CPSR: u8 = 25;
const REG_NAMES: &[&str] = &[
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr",
"pc", "padding", "padding", "padding", "padding", "padding", "padding", "padding", "padding",
"padding", "cpsr",
];

impl target::ext::register_info::RegisterInfo for Emu {
fn get_register_info(
&mut self,
reg_id: u8,
write_info: Box<dyn FnOnce(Option<Register<'_>>) + '_>,
) -> Result<(), Self::Error> {
match reg_id {
0..=MAX_REG_NUM => {
let name = REG_NAMES[reg_id as usize];
let encoding = if reg_id < 16 || reg_id == CPSR {
String::from("uint")
} else {
String::from("vector")
};
let format = if reg_id < 16 || reg_id == CPSR {
String::from("hex")
} else {
String::from("vector-uint8")
};
let set = if reg_id < 16 || reg_id == CPSR {
String::from("General Purpose Registers")
} else {
String::from("Floating Point Registers")
};
let generic = if reg_id == SP {
Some(name)
} else if reg_id == PC {
Some(name)
} else {
None
};
let reg = Register {
name: &name,
alt_name: &name,
bitsize: 32,
offset: (reg_id as u16) * 4,
encoding: &encoding,
format: &format,
set: &set,
gcc: None,
dwarf: Some(reg_id as u16),
generic,
container_regs: None,
invalidate_regs: None,
};
write_info(Some(reg));
}
// Will end the 'qRegisterInfo' query
_ => write_info(None),
}
Ok(())
}
}
4 changes: 4 additions & 0 deletions src/protocol/commands.rs
Expand Up @@ -288,4 +288,8 @@ commands! {
catch_syscalls use 'a {
"QCatchSyscalls" => _QCatchSyscalls::QCatchSyscalls<'a>,
}

register_info {
"qRegisterInfo" => _qRegisterInfo::qRegisterInfo,
}
}
17 changes: 17 additions & 0 deletions src/protocol/commands/_qRegisterInfo.rs
@@ -0,0 +1,17 @@
use super::prelude::*;

#[derive(Debug)]
pub struct qRegisterInfo {
pub reg_id: u8,
}

impl<'a> ParseCommand<'a> for qRegisterInfo {
#[inline(always)]
fn from_packet(buf: PacketBuf<'a>) -> Option<Self> {
let body = buf.into_body();

let reg_id = decode_hex(body).ok()?;

Some(qRegisterInfo { reg_id })
}
}
33 changes: 31 additions & 2 deletions src/protocol/response_writer.rs
Expand Up @@ -3,7 +3,8 @@ use alloc::string::String;
#[cfg(feature = "trace-pkt")]
use alloc::vec::Vec;

use num_traits::PrimInt;
use num_traits::identities::one;
use num_traits::{CheckedRem, PrimInt};

use crate::conn::Connection;
use crate::internal::BeBytes;
Expand Down Expand Up @@ -156,7 +157,7 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
}

/// Write an entire string over the connection.
pub fn write_str(&mut self, s: &'static str) -> Result<(), Error<C::Error>> {
pub fn write_str(&mut self, s: &'_ str) -> Result<(), Error<C::Error>> {
for b in s.as_bytes().iter() {
self.write(*b)?;
}
Expand Down Expand Up @@ -228,6 +229,34 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
Ok(())
}

/// Write a number as a decimal string, converting every digit to an ascii char.
#[allow(clippy::just_underscores_and_digits)]
pub fn write_dec<D: PrimInt + CheckedRem>(
&mut self,
mut digit: D,
) -> Result<(), Error<C::Error>> {
if digit.is_zero() {
return self.write(b'0');
}

let _1: D = one();
let _10 = (_1 << 3) + (_1 << 1);
let mut d = digit;
let mut pow_10 = _1;
// Get the number of digits in digit
while d >= _10 {
d = d / _10;
pow_10 = pow_10 * _10;
}
// Write every digit from left to right as an ascii char
while !pow_10.is_zero() {
self.write(b'0' + (digit / pow_10).to_u8().unwrap())?;
digit = digit % pow_10;
pow_10 = pow_10 / _10;
}
Ok(())
}

#[inline]
fn write_specific_id_kind(&mut self, tid: SpecificIdKind) -> Result<(), Error<C::Error>> {
match tid {
Expand Down
2 changes: 2 additions & 0 deletions src/stub/core_impl.rs
Expand Up @@ -30,6 +30,7 @@ mod extended_mode;
mod host_io;
mod memory_map;
mod monitor_cmd;
mod register_info;
mod resume;
mod reverse_exec;
mod section_offsets;
Expand Down Expand Up @@ -207,6 +208,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
Command::HostIo(cmd) => self.handle_host_io(res, target, cmd),
Command::ExecFile(cmd) => self.handle_exec_file(res, target, cmd),
Command::Auxv(cmd) => self.handle_auxv(res, target, cmd),
Command::RegisterInfo(cmd) => self.handle_register_info(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
105 changes: 105 additions & 0 deletions src/stub/core_impl/register_info.rs
@@ -0,0 +1,105 @@
use super::prelude::*;
use crate::protocol::commands::ext::RegisterInfo;

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

crate::__dead_code_marker!("register_info", "impl");

macro_rules! write_register {
($register:expr, $name:expr) => {
res.write_str($name)?;
res.write_num($register[0])?;
for reg in $register.iter().skip(1) {
res.write_str(",")?;
res.write_num(*reg)?;
}
};
}

let handler_status = match command {
RegisterInfo::qRegisterInfo(cmd) => {
use crate::target::ext::register_info::Register;

let mut err = Ok(());
ops.get_register_info(
cmd.reg_id,
Box::new(|reg_info| {
let res = match reg_info {
Some(Register {
name,
alt_name,
bitsize,
offset,
encoding,
format,
set,
gcc,
dwarf,
generic,
container_regs,
invalidate_regs,
}) => {
// TODO: replace this with a try block (once stabilized)
(|| {
res.write_str("name:")?;
res.write_str(name)?;
res.write_str(";alt-name:")?;
res.write_str(alt_name)?;
res.write_str(";bitsize:")?;
res.write_dec(bitsize)?;
res.write_str(";offset:")?;
res.write_dec(offset)?;
res.write_str(";encoding:")?;
res.write_str(encoding)?;
res.write_str(";format:")?;
res.write_str(format)?;
res.write_str(";set:")?;
res.write_str(set)?;
if let Some(gcc) = gcc {
res.write_str(";gcc:")?;
res.write_dec(gcc)?;
}
if let Some(dwarf) = dwarf {
res.write_str(";dwarf:")?;
res.write_dec(dwarf)?;
}
if let Some(generic) = generic {
res.write_str(";generic:")?;
res.write_str(generic)?;
}
if let Some(c_regs) = container_regs {
write_register!(c_regs, ";container-regs:");
}
if let Some(i_regs) = invalidate_regs {
write_register!(i_regs, ";invalidate-regs:");
}
res.write_str(";")
})()
}
// This will end the 'qRegisterInfo' query
None => res.write_str("E45"),
};
if let Err(e) = res {
err = Err(e);
}
}),
)
.map_err(Error::TargetError)?;
err?;
HandlerStatus::Handled
}
};

Ok(handler_status)
}
}
1 change: 1 addition & 0 deletions src/target/ext/mod.rs
Expand Up @@ -267,5 +267,6 @@ pub mod extended_mode;
pub mod host_io;
pub mod memory_map;
pub mod monitor_cmd;
pub mod register_info;
pub mod section_offsets;
pub mod target_description_xml_override;
49 changes: 49 additions & 0 deletions src/target/ext/register_info.rs
@@ -0,0 +1,49 @@
//! Provide register information for the target. See the lldb-gdb-remote [documentation](https://github.com/llvm/llvm-project/blob/main/lldb/docs/lldb-gdb-remote.txt) for more information.
//!
//! Some targets don't have register context in the compiled version of the debugger. Help the debugger by dynamically supplying the register info from the target if the packet 'qXfer:features:read' isn't used to provide a target description. The debugger will request the register info in a sequential manner till an 'E45' packet is received.
//!
//! _Note:_ LLDB specific! This packet is meant as an alternative to the 'qXfer:features:read:target.xml' packet. LLDB will fall back to the .xml file if qRegisterInfo isn't supported on the target. See issue [#99](https://github.com/daniel5151/gdbstub/issues/99) for more info on LLDB compatibility.
//!
use crate::target::Target;

/// Describes the register info for a single register of the target
pub struct Register<'a> {
/// The primary register name
pub name: &'a str,
/// An alternate name for the register
pub alt_name: &'a str,
/// Size in bits of a register (base 10)
pub bitsize: u16,
/// The offset within the 'g' and 'G' packet of the register data for this register (base 10)
pub offset: u16,
/// The encoding type of the register
pub encoding: &'a str,
/// The preferred format for display of this register
pub format: &'a str,
/// The register set name this register belongs to
pub set: &'a str,
/// The GCC compiler registers number for this register
pub gcc: Option<u16>,
/// The DWARF register number for this register that is used for this register in the debug information
pub dwarf: Option<u16>,
/// Specify as a generic register
pub generic: Option<&'a str>,
/// Other concrete register values this register is contained in (expecting base 10 here but will be written with write_num() as hex)
pub container_regs: Option<&'a [u16]>,
/// Specifies which register values should be invalidated when this register is modified (expecting base 10 here but will be written with write_num() as hex)
pub invalidate_regs: Option<&'a [u16]>,
}

/// Target Extension - Get register information from the target.
///
/// Corresponds to the `qRegisterInfo` command.
pub trait RegisterInfo: Target {
/// Return the target's register info for a single register or Ok(None) if <reg_id> is greater than the number of registers to end the request.
fn get_register_info(
&mut self,
reg_id: u8,
write_info: Box<dyn FnOnce(Option<Register<'_>>) + '_>,
) -> Result<(), Self::Error>;
}

define_ext!(RegisterInfoOps, RegisterInfo);
13 changes: 13 additions & 0 deletions src/target/mod.rs
Expand Up @@ -665,6 +665,13 @@ pub trait Target {
fn support_auxv(&mut self) -> Option<ext::auxv::AuxvOps<'_, Self>> {
None
}

/// Support for querying register info in LLDB.
/// Let the debugger request register info one by one. Should be used if 'use_target_description_xml()' is disabled.
#[inline(always)]
fn support_register_info(&mut self) -> Option<ext::register_info::RegisterInfoOps<'_, Self>> {
None
}
}

macro_rules! impl_dyn_target {
Expand Down Expand Up @@ -755,6 +762,12 @@ macro_rules! impl_dyn_target {
fn support_auxv(&mut self) -> Option<ext::auxv::AuxvOps<'_, Self>> {
(**self).support_auxv()
}

fn support_register_info(
&mut self,
) -> Option<ext::register_info::RegisterInfoOps<'_, Self>> {
(**self).support_register_info()
}
}
};
}
Expand Down

0 comments on commit dac328a

Please sign in to comment.