#gdb #emulation

无需std gdbstub

Rust中对GDB远程串行协议的实现

23个版本

新版本 0.7.2 2024年8月17日
0.7.1 2024年1月6日
0.7.0 2023年11月24日
0.6.6 2023年4月13日
0.1.1 2020年3月29日

#52 in 嵌入式开发

Download history 12417/week @ 2024-05-03 11289/week @ 2024-05-10 9117/week @ 2024-05-17 8960/week @ 2024-05-24 10876/week @ 2024-05-31 12355/week @ 2024-06-07 10797/week @ 2024-06-14 9112/week @ 2024-06-21 10716/week @ 2024-06-28 10686/week @ 2024-07-05 10560/week @ 2024-07-12 11276/week @ 2024-07-19 12095/week @ 2024-07-26 11859/week @ 2024-08-02 11158/week @ 2024-08-09 9099/week @ 2024-08-16

46,746 每月下载量
17 个crate中使用了(直接使用13个)

MIT/Apache

430KB
6.5K SLoC

gdbstub

一个舒适、功能丰富且易于集成的Rust中的GDB远程串行协议实现,提供无妥协#![no_std]支持。

gdbstub可以轻松地将强大的客户机调试支持集成到您的仿真器/虚拟机管理程序/调试器/嵌入式项目中。通过仅实现gdbstub::Target特质的一小部分基本方法,您可以在极短的时间内启动并运行丰富的GDB调试会话!

gdbstub的API广泛使用了称为可内联的动态扩展特质 (IDETs) 的技术,以暴露对启用GDB协议功能的精细粒度、零成本控制,而无需依赖于编译时功能标志。除了使切换启用协议功能变得轻而易举外,IDETs还确保任何未实现的功能都将被保证在发布构建中删除!

如果您想查看一个示例代码片段,以了解功能丰富的gdbstub集成可能是什么样子,请查看examples/armv4t/gdb/mod.rs

为什么使用gdbstub

  • 出色的易用性
    • 不是简单地暴露底层的GDB协议“瑕瑜互见”,gdbstub试图尽可能多地抽象原始GDB协议细节,从用户那里分离出来。
      • 不再需要深入GDB代码库深处的晦涩XML文件,只需从CPU/架构寄存器读取/写入,gdbstub就自带了一组社区维护的、针对大多数流行平台的内置架构定义
      • 将GDB无数的可选协议扩展组织成一致、可理解和类型安全的特质层次结构。
      • 自动处理客户端/服务器协议功能协商,无需微观管理特定的qSupported数据包响应。
    • gdbstub充分利用Rust强大的类型系统和泛型,在编译时强制执行协议不变性,最小化最终用户需要担心复杂协议细节的数量。
    • 使用一种名为可内联动态扩展特质 (IDETs) 的创新技术,gdbstub允许对活动协议扩展进行细粒度控制,而不依赖于笨拙的cargo功能或使用unsafe代码!
  • 易于集成
    • gdbstub的API设计为“即插即用”解决方案,当您想在项目中添加调试支持时,并无需进行任何大规模重构工作即可集成到现有项目中。
  • #![no_std]准备就绪 & 尺寸优化
    • gdbstub是一个**首选**无标准库的库,其中所有协议功能都必须与no_std兼容。
    • gdbstub不要求任何动态内存分配,并可以配置为使用固定大小的预分配缓冲区。这使得gdbstub可以在甚至资源最受限的无alloc平台上使用。
    • gdbstub在大多数最小配置中完全**无panic**,结果生成更小、更健壮的代码。
    • gdbstub与传输层无关,并使用一个基本的Connection接口与GDB服务器通信。只要目标有一些执行有序、串行、字节级I/O的方法(例如:通过UART的putchar/getchar),就可以在目标上运行gdbstub
    • "你不为不用之物付费":与解析/处理协议扩展相关的所有代码,如果未实现,则保证从优化后的二进制文件中删除死代码。有关更多详细信息,请参阅下面的零开销协议扩展部分。
    • gdbstub的最小配置具有极低的二进制大小和RAM开销,使其可以在资源最受限的微控制器上使用。
      • 在发布模式下编译,使用min-sized-rust中概述的所有技巧,一个基线gdbstub实现可以小到**小于10kb的.text + .rodata**! *
      • 确切数字因目标平台、编译器版本和 gdbstub 修订而异。在混合语言项目中,可能需要跨语言LTO(#101)。数据使用包含的 example_no_std 项目编译在 x86_64 上收集。

我能否在生产环境中使用 gdbstub

是的,只要你不介意在 1.0.0 发布之前API有所变动。

由于 gdbstub 在编译时通过Rust的类型系统强制执行GDB协议不变性,因此实现新的GDB协议功能通常需要做出一些破坏性的API更改。虽然这些更改通常很小,但它们确实是semver-breaking的,并且在版本之间移动时可能需要代码更改。任何特别复杂的变化通常都会在专门的 迁移指南 文档中记录。

尽管如此,gdbstub 自从最初的 0.1 发布以来,已经集成到许多实际项目中,实证研究表明,它似乎正在很好地完成其工作!迄今为止,大多数报告的问题都是由不正确实现的 Target 和/或 Arch 实现引起的,而核心 gdbstub 库本身已被证明相对无错误。

有关 gdbstub 在版本 1.0.0 稳定API之前还需要实现哪些功能的更多信息,请参阅 未来计划 + 路线图

调试功能

尽管GDB远程串行协议非常复杂,支持诸如远程文件I/O、启动新进程、"回滚"程序执行等高级功能,但幸运的是,大多数这些功能都是完全可选的。仅通过实现一些基本方法,就可以开始一个基本的调试会话。

  • 基本GDB协议
    • 读取/写入内存
    • 读取/写入寄存器
    • 枚举线程

没错!这就足够了,可以让 gdb 连接上了!

当然,大多数用例还希望支持额外的调试功能。目前,gdbstub 实现了以下GDB协议扩展:

  • 自动目标架构 + 功能配置
  • 恢复
    • 继续
    • 单步执行
    • 范围步进
    • 反向 步进/继续
  • 断点
    • 软件断点
    • 硬件断点
    • 读取/写入/访问观察点(即:值断点)
  • 扩展模式
    • 启动新进程
    • 附加到现有进程
    • 终止现有进程
    • 向启动的进程传递环境变量 + 参数
    • 更改工作目录
    • 启用/禁用ASLR
  • 读取内存映射(info mem
  • 读取段/段重定位偏移量
  • 处理自定义 monitor 命令
    • 使用GDB的 monitor 命令扩展GDB协议以自定义调试命令!
  • 主机I/O
    • 访问远程目标文件的系统以读取/写入文件
    • 可用于在附加时自动读取远程可执行文件(使用 ExecFile
  • 读取辅助向量(info auxv
  • 额外线程信息(info threads
  • 额外库信息(info sharedlibraries

注意: GDB 功能由 gdbstub 的贡献者根据需要实现。如果您希望 gdbstub 实现缺失的 GDB 功能,请提交问题并/或打开一个 PR!

有关 GDB 远程功能的完整列表,请查看 GDB 远程配置文档,其中包含 GDB 命令及其对应的远程串行协议数据包的表格。

零开销协议扩展

通过使用称为 可内联动态扩展特性(IDETs) 的技术,gdbstub 能够利用 Rust 编译器的强大优化过程,确保在发布版本中任何未使用的功能都会被死代码消除,而无需依赖于编译时功能标志!

例如,如果您的目标没有实现自定义 GDB monitor 命令处理程序,生成的二进制文件将不包括与解析/处理底层 qRcmd 数据包相关的任何代码!

如果您对 IDETs 的工作原理感兴趣,我在文档中包含了一个简要的说明 这里

功能标志

默认情况下,启用了 stdalloc 功能。

#![no_std] 环境中使用 gdbstub 时,请确保设置 default-features = false

  • alloc
    • Box<dyn Connection> 实现 Connection
    • 通过 log::trace! 记录外出数据包(使用堆分配的输出缓冲区)。
    • 为某些协议功能提供内置实现
      • GdbStub 中使用堆分配的数据包缓冲区(如果没有通过 GdbStubBuilder::with_packet_buffer 提供)。
      • (监视命令)在 ConsoleOutput 中使用堆分配的输出缓冲区。
  • std(隐含 alloc
    • TcpStreamUnixStream 实现 Connection
    • std::error::Error 实现 gdbstub::Error
    • 为简化从 Target 方法中处理 std::io::Error,添加 TargetError::Io 变体。
  • paranoid_unsafe

示例

现实世界示例

虽然这些项目可能使用 gdbstub 的旧版本,但它们仍然可以作为典型的 gdbstub 集成示例。

如果您在项目中使用了 gdbstub,请考虑提交一个PR并将其添加到这个列表中!

树内“玩具”示例

这些示例是作为CI的一部分构建的,并保证与gdbstub的最新版本API保持更新。

  • armv4t - ./examples/armv4t/
    • 一个基于ARMv4T的简单系统仿真器,具有gdbstub支持。
    • 实现了(几乎)所有可用的target::ext功能。这使得它在首次实现新的协议扩展时成为一个极好的资源!
  • armv4t_multicore - ./examples/armv4t_multicore/
    • armv4t示例的双核变体。
    • 实现了gdbstub多线程扩展API的核心,但除此之外没有太多。
  • example_no_std - ./example_no_std
    • 这是一个极简示例,展示了如何在#![no_std]项目中使用gdbstub
    • armv4t/armv4t_multicore 示例不同,本项目不包括可工作的模拟器,仅简单地模拟所有 gdbstub 函数。
    • 充当 gdbstub 的测试平台,以跟踪其大约的二进制占用空间(通过 check_size.sh 脚本),以及验证某些死代码消除优化。

unsafegdbstub 中的使用

gdbstub 将对 unsafe 的使用限制在最低限度,所有使用 unsafe 的地方都需要有相应的 // SAFETY 注释作为理由。

对于那些对信任第三方 unsafe 代码感到担忧的人,gdbstub 提供了一个可选的 paranoid_unsafe 功能,该功能在 gdbstub crate 的整个范围内启用 #![forbid(unsafe_code)],将所有 unsafe 代码实例替换为等效的(尽管性能较低)替代方案。

以下列表详尽地记录了 gdbstub 中所有 unsafe 的使用情况

  • 使用 default 功能

    • 不发出可证明无法到达的恐慌
      • src/protocol/packet.rs:在 PacketBuf 中使用存储的子 Range<usize> 到缓冲区的索引的方法
      • src/protocol/common/hex.rsdecode_hex_buf
  • 当启用 std 功能时

    • src/connection/impls/unixstream.rs:一个实现 UnixStream::peek 的方法,它使用 libc::recv。一旦 rust-lang/rust#76923 在 stdlib 中稳定了这个功能,它将被删除。

编写无恐慌代码

理想情况下,Rust 编译器应该有一些方法可以启用严格的“无恐慌”模式。不幸的是,在撰写本文时(2022/04/24),还没有这样的模式。因此,避免 Rust 编译器和 stdlib 的隐式恐慌的唯一方法是编写代码时要非常小心,并 手动检查 那些恐慌路径是否被优化掉了!

当我说“手动检查”时,我的意思是 检查生成的汇编输出

为什么要付出这么大的努力?

  • 恐慌基础设施可能非常 昂贵,当你在针对嵌入式系统优化时,panic 基础设施会将数百个额外的字节带到最终的二进制文件中。
  • gdbstub 可以用于实现低级调试器,如果调试器本身出现恐慌,那么...你不太可能轻易地调试它!

因此,gdbstub 承诺在以下条件下将零额外的恐慌引入现有项目

  1. 二进制文件以发布模式编译
    • *受使用的特定 rustc 版本的影响(代码生成和优化在不同版本之间有所不同)
    • *不同的硬件架构可能受到不同的编译器优化的影响
      • 例如:目前只有 x86 被积极测试以确保无panic
  2. gdbstubparanoid_unsafe cargo 特性已被 禁用
    • LLVM 无法在不包含一些 unsafe 代码的情况下省略某些 panic 检查
    • 有关 gdbstub 中的 unsafe 的更多信息,请参阅unsafe 部分
  3. 正在使用的 Arch 实现不包括 panic 代码
    • 注意:gdbstub_arch 下的架构实现并不保证一定是无panic的!
    • 如果您在 gdbstub_arch 中发现了一个 panic 架构,请考虑提交一个 PR 来修复它

如果您在无 panic 项目的环境中使用 gdbstub,并确定 gdbstub 是引入 panic 代码路径的罪魁祸首,请提交一个问题!

未来计划 + 路线图至 1.0.0

虽然 GDB 协议的绝大多数功能(例如:远程文件系统支持、tracepoint 数据包、大多数查询数据包等)不应需要破坏性 API 变更,但以下功能很可能需要至少一些破坏性 API 变更,因此应该在 1.0.0 之前实现。

请注意,这并不是一个详尽的列表,并且可能会发生变化。

  • 通过 Arch 特性允许对目标特性进行细粒度控制(#12
  • 实现 GDB 的各种高级操作模式
    • 单/多线程调试
    • 多进程调试(#124
      • 需要添加一个新的 target::ext::base::multiprocess API。
      • 注意: gdbstub 已经在“幕后”实现了多进程扩展,并硬编码了一个假的 PID,所以这主要是一个“投入工作”的问题。
    • 扩展模式target extended-remote
    • 非停止模式
  • 有一个 gdbstub 在“裸机”环境中运行的示例(#![no_std]

此外,虽然这些特性不是 严格 的阻止因素,但探索这些特性也是好的

  • 是否应该 gdbstub 承诺一个 MSRV?
  • gdbstub_arch 中移除 RawRegId 的遗留实例(#29
  • 暴露 async/await 接口(特别是关于处理 GDB 客户端中断)(#36
  • 如何/是否支持 LLDB 扩展#99
  • 通过单个目标支持多架构调试
    • 例如:在 macOS 上调试 x86 和 ARM 进程
  • 正确处理“nack”数据包(用于不稳定的连接)(#137

许可证

gdbstub 是免费的、开源的!本仓库中所有代码都采用了双重许可,可选择以下其中之一:

您可以根据自己的选择。这意味着您可以选择您喜欢的许可证!这种双重许可的方法是 Rust 生态系统中的事实标准,并且存在非常好的理由来包含两者。

除非您明确表示,否则,根据 Apache-2.0 许可证的定义,您有意提交的任何贡献,均应如上所述进行双重许可,不附加任何额外的条款或条件。

依赖项

~290–420KB