From dac328a4ed0116442fa2e15fa4edcdb75f7a8390 Mon Sep 17 00:00:00 2001 From: wj Date: Thu, 2 Jun 2022 16:21:33 +0200 Subject: [PATCH] add qRegisterInfo packet support --- CHANGELOG.md | 4 + examples/armv4t/gdb/mod.rs | 9 ++ examples/armv4t/gdb/register_info.rs | 73 ++++++++++++++++ src/protocol/commands.rs | 4 + src/protocol/commands/_qRegisterInfo.rs | 17 ++++ src/protocol/response_writer.rs | 33 +++++++- src/stub/core_impl.rs | 2 + src/stub/core_impl/register_info.rs | 105 ++++++++++++++++++++++++ src/target/ext/mod.rs | 1 + src/target/ext/register_info.rs | 49 +++++++++++ src/target/mod.rs | 13 +++ 11 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 examples/armv4t/gdb/register_info.rs create mode 100644 src/protocol/commands/_qRegisterInfo.rs create mode 100644 src/stub/core_impl/register_info.rs create mode 100644 src/target/ext/register_info.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1e028f..816faecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/examples/armv4t/gdb/mod.rs b/examples/armv4t/gdb/mod.rs index 155fff3e..02814162 100644 --- a/examples/armv4t/gdb/mod.rs +++ b/examples/armv4t/gdb/mod.rs @@ -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; @@ -141,6 +142,14 @@ impl Target for Emu { fn support_auxv(&mut self) -> Option> { Some(self) } + + // LLDB only + #[inline(always)] + fn support_register_info( + &mut self, + ) -> Option> { + Some(self) + } } impl SingleThreadBase for Emu { diff --git a/examples/armv4t/gdb/register_info.rs b/examples/armv4t/gdb/register_info.rs new file mode 100644 index 00000000..c4d96201 --- /dev/null +++ b/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>) + '_>, + ) -> 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(()) + } +} diff --git a/src/protocol/commands.rs b/src/protocol/commands.rs index e96a1a59..88504a78 100644 --- a/src/protocol/commands.rs +++ b/src/protocol/commands.rs @@ -288,4 +288,8 @@ commands! { catch_syscalls use 'a { "QCatchSyscalls" => _QCatchSyscalls::QCatchSyscalls<'a>, } + + register_info { + "qRegisterInfo" => _qRegisterInfo::qRegisterInfo, + } } diff --git a/src/protocol/commands/_qRegisterInfo.rs b/src/protocol/commands/_qRegisterInfo.rs new file mode 100644 index 00000000..b671620d --- /dev/null +++ b/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 { + let body = buf.into_body(); + + let reg_id = decode_hex(body).ok()?; + + Some(qRegisterInfo { reg_id }) + } +} diff --git a/src/protocol/response_writer.rs b/src/protocol/response_writer.rs index 2117181d..2d70c870 100644 --- a/src/protocol/response_writer.rs +++ b/src/protocol/response_writer.rs @@ -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; @@ -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> { + pub fn write_str(&mut self, s: &'_ str) -> Result<(), Error> { for b in s.as_bytes().iter() { self.write(*b)?; } @@ -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( + &mut self, + mut digit: D, + ) -> Result<(), 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> { match tid { diff --git a/src/stub/core_impl.rs b/src/stub/core_impl.rs index 82f3657b..9f14d9d4 100644 --- a/src/stub/core_impl.rs +++ b/src/stub/core_impl.rs @@ -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; @@ -207,6 +208,7 @@ impl GdbStubImpl { 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 diff --git a/src/stub/core_impl/register_info.rs b/src/stub/core_impl/register_info.rs new file mode 100644 index 00000000..a9e68e04 --- /dev/null +++ b/src/stub/core_impl/register_info.rs @@ -0,0 +1,105 @@ +use super::prelude::*; +use crate::protocol::commands::ext::RegisterInfo; + +impl GdbStubImpl { + pub(crate) fn handle_register_info( + &mut self, + res: &mut ResponseWriter<'_, C>, + target: &mut T, + command: RegisterInfo, + ) -> Result> { + 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) + } +} diff --git a/src/target/ext/mod.rs b/src/target/ext/mod.rs index 81989360..1b9db77a 100644 --- a/src/target/ext/mod.rs +++ b/src/target/ext/mod.rs @@ -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; diff --git a/src/target/ext/register_info.rs b/src/target/ext/register_info.rs new file mode 100644 index 00000000..b53de91f --- /dev/null +++ b/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, + /// The DWARF register number for this register that is used for this register in the debug information + pub dwarf: Option, + /// 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 is greater than the number of registers to end the request. + fn get_register_info( + &mut self, + reg_id: u8, + write_info: Box>) + '_>, + ) -> Result<(), Self::Error>; +} + +define_ext!(RegisterInfoOps, RegisterInfo); diff --git a/src/target/mod.rs b/src/target/mod.rs index eaed4270..42f31606 100644 --- a/src/target/mod.rs +++ b/src/target/mod.rs @@ -665,6 +665,13 @@ pub trait Target { fn support_auxv(&mut self) -> Option> { 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> { + None + } } macro_rules! impl_dyn_target { @@ -755,6 +762,12 @@ macro_rules! impl_dyn_target { fn support_auxv(&mut self) -> Option> { (**self).support_auxv() } + + fn support_register_info( + &mut self, + ) -> Option> { + (**self).support_register_info() + } } }; }