From ba018263d82137734c095df950ab6136530d0fc1 Mon Sep 17 00:00:00 2001 From: Bin Meng Date: Thu, 27 Apr 2023 23:49:21 +0800 Subject: [PATCH 1/6] articles: Add an introduction to gdb & QEMU gdbstub debug Add an article to describe a debug process of gdb & QEMU gdbstub. Signed-off-by: Bin Meng --- .../20230428-gdb-and-qemu-gdbstub-debug.md | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 articles/20230428-gdb-and-qemu-gdbstub-debug.md diff --git a/articles/20230428-gdb-and-qemu-gdbstub-debug.md b/articles/20230428-gdb-and-qemu-gdbstub-debug.md new file mode 100644 index 0000000..4bed41e --- /dev/null +++ b/articles/20230428-gdb-and-qemu-gdbstub-debug.md @@ -0,0 +1,320 @@ +> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1 - [spaces]
+> Author: Bin Meng
+> Date: 2023/04/28
+> Revisor: Falcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Sponsor: PLCT Lab, ISCAS + +# gdb 和 QEMU gdbstub 调试技巧 + +## 前言 + +我们在调试与 QEMU 自带的 gdbstub 相关的问题时,单独调试 QEMU 的 gdbstub 可能看不到问题的全貌。俗话说“一个巴掌拍不响”,QEMU 的 gdbstub 是要跟 gdb 客户端配合使用的,如果出现 bug 也有可能是 gdb 客户端的问题。本文用一个实际例子说明 gdb 和 QEMU gdbstub 的调试技巧。 + +## QEMU gdbstub + +调试与 QEMU 自带的 gdbstub 相关的问题时,需要熟练掌握 gdb 的远程串行调试协议。协议内容详见 [GDB Remote Serial Protocol][001] + +QEMU 里与 gdbstub 相关的代码分布在如下目录: + +- gdbstub/ +- gdb-xml/ +- target/*/gdbstub.c + +其中 gdbstub/ 目录里的 gdbstub.c 包含处理 gdb 远程串行调试协议的与体系架构无关的核心逻辑。与体系架构相关的处理逻辑都放到了各个体系架构支持的子目录里(target/*/gdbstub.c)。 + +gdbstub/trace-events 包含了 QEMU 自带的 trace 功能记录下来的 gdbstub 相关的事件,可供调试需要,通过 QEMU 命令行 `-d trace:gdbstub*` 打开,如下面的命令行: + +```shell +$ qemu-system-riscv64 -nographic -M sifive_u,msel=11 -smp 5 -m 8G -bios u-boot-spl.bin -drive file=sdcard.img,if=sd -s -S -D gdbstub.txt -d trace:gdbstub* +``` + +按照给定的命令行参数启动模拟的 sifive_u 机器,将所有与 gdbstub 相关的 trace 事件日志都写入当前目录下的 gdbstub.txt 里,查看 gdbstub.txt 可以得到 QEMU 记录的其与 gdb 客户端非常详细的交互信息。下面是日志里的一段信息,可以看到 QEMU 收到了客户端的读取 target.xml 的请求并返回了文件内容。 + +``` +gdbstub_io_command Received: qXfer:features:read:target.xml:0,ffb +gdbstub_io_binaryreply 0x0000: 6c 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 l< +gdbstub_io_binaryreply 0x0040: 74 61 72 67 65 74 3e 3c 61 72 63 68 69 74 65 63 target>riscv:rv64< +gdbstub_io_binaryreply 0x0060: 2f 61 72 63 68 69 74 65 63 74 75 72 65 3e 3c 78 /architecture> +gdbstub_io_got_ack Got ACK +``` + +gdb-xml/ 目录下包含 QEMU 支持的所有体系架构的静态的 XML 文件,这里主要包括 CPU 通用寄存器、浮点寄存器等。如 gdb-xml/riscv-64bit-cpu.xml 文件描述了 gdb 访问 64 位 RISC-V 处理器的所有通用寄存器的信息。对于 CSR 这种与某个特定处理器实现相关的寄存器描述文件,通过 target/*/gdbstub.c 在代码里动态生成,如 RISC-V 的代码 target/riscv/gdbstub.c::riscv_gen_dynamic_csr_xml() 通过遍历检查 CSR 表里的每个 CSR 是否存在来决定是否向 gdb 客户端报告这个寄存器。 + +## gdb 调试 + +上一节简单介绍了 QEMU gdbstub 是什么,在 QEMU 里实现的代码在何处,以及 gdb 客户端连上 QEMU gdbstub 后 QEMU 测收到的 target.xml 的读取请求。 + +如果 gdb 客户端连不上 QEMU gdbstub,这种情况应该怎么调试呢?下面以一个实际例子为例进行说明。 + +### 复现环境 + +实验用到的软件版本和主机系统如下: + +- QEMU (v7.2.0) +- gdb-multiarch (v9.2) +- Host: Ubuntu 20.04 LTS + +QEMU 命令行: + +```shell +$ qemu-system-riscv64 -nographic -M virt -s -S +``` + +### 问题描述 + +gdb 连上 QEMU 调试: + +```shell +$ gdb-multiarch +>>> set architecture riscv:rv64 +>>> target extended-remote :1234 +Remote debugging using :1234 +warning: Architecture rejected target-supplied description +warning: No executable has been specified and target does not support +determining executable automatically. Try using the "file" command. +``` + +注意:`gdb-multiarch` 开始调试前,需要正确设置被调试代码的体系架构,否则在没有指定被调试文件的情况下,gdb 并不会根据被调试文件来 “猜测” 被调试代码的指令集,这样会导致出现一个错误信息 “Truncated register 37 in remote 'g' packet” 从而连不上 QEMU 的 gdbstub。这里我们设置了 `gdb-multiarch` 被调试的目标体系架构为 riscv:rv64。 + +这里出现了两条 warning。第二条 warning 是正常的,因为我在启动 gdb 的时候没有给它被调试的文件,所以这条可以忽略。 + +### 初步分析 + +对于这个问题笔者的第一反应这应该是一个 QEMU regression。笔者工作的主机环境是 Ubuntu 20.04,平时多次用 gdb-multiarch 调试跑在 QEMU 上的软件,在 7.2 版本之前并没有发现这个 warning,那么一定是 QEMU 之后的某个修改引入了这个 bug。查阅 QEMU 历史,以下两个 commit 跟 gdbstub 相关: + +- [target/riscv: remove fflags, frm, and fcsr from riscv-*-fpu.xml][002] +- [target/riscv: remove fixed numbering from GDB xml feature files][003] + +简单测试发现只要 revert [commit][002],这个问题便不再复现。但是仔细阅读这个 commit 的 commit message 和修改,发现改动是合理的,那么问题来了,有没有可能是原作者使用的 gdb 版本跟笔者的不一样,而新版本 gdb 的行为发生了改变? + +### 编译新版本 gdb + +好,我们来编译一下最新的 gdb v12.1 试试看: + +```shell +$ wget https://ftp.gnu.org/gnu/gdb/gdb-12.1.tar.xz +$ tar xf gdb-12.1.tar.xz +$ cd gdb-12.1 +$ mkdir build +$ cd build +$ ../configure --target=riscv64-linux --with-python=/usr/bin/python3 +$ make -j$(nproc) +``` + +注意:如果不 `make install` 而直接从 build 目录里执行 gdb 程序,gdb python 模块不会正确的加载。gdb 的 python 模块在 /gdb/data-directory,需要显式的给 gdb 传入这个目录: + +```shell +$ ./gdb --data-directory=./data-directory +``` + +果然,新版本的 gdb 不会出现这个问题。那么 QEMU v7.2.0 的这个 [commit][002] 改动其实是要配合新版本的 gdb 来使用的,严格意义上来讲这里是存在一个兼容性的问题,具体是谁的兼容性问题这里我们暂且不表。先看看 gdb 的行为发生了什么改变,这里我们需要一些调试 gdb 程序本身的技巧。 + +### 深入分析 + +由 warning 信息我们自然的想到这个问题可能是 gdb 的 target description 有关,在本文开始的 QEMU gdbstub 章节中笔者提到了 QEMU 会发送 target description(cpu、fpu、csr 等寄存器描述)给 gdb 客户端,那么我们在 gdb 这边可以查看一下,gdb 到底用了 QEMU 的 target description 没有。这里要用到 gdb 的命令 maintenance 中的 `print c-tdesc` 子命令,以 C 代码的形式打印出 gdb 当前所用的 target description。 + +用 Ubuntu 20.04 自带的 gdb-multiarch v9.2: + +``` +>>> maintenance print c-tdesc +The current target description did not come from an XML file. +``` + +可以看到 9.2 版本根本没有用到 QEMU 发送过来的 target description XML 文件?! + +换刚编好的 gdb v12.1 试试: + +``` +>>> maintenance print c-tdesc +/* THIS FILE IS GENERATED. -*- buffer-read-only: t -*- vi:set ro: + Original: */ + +#include "defs.h" +#include "osabi.h" +#include "target-descriptions.h" + +struct target_desc *tdesc_; +static void +initialize_tdesc_ (void) +{ +... +} +``` + +可以看到 12.1 版本的 gdb 用到了 target description,且内容跟 QEMU 发送过来的 XML 文件内容完全能对上。 + +注意:从 gdb v10.0 版本开始 gdb 引入了一条新命令 `maintenance print xml-tdesc`,直接打印出 XML 文件,更加直观和人性化。 + +``` +>>> maintenance print xml-tdesc + + + + riscv:rv64 + + + +... +``` + +至此,我们可以非常确信这个问题跟 gdb 不同版本处理 RISC-V 的 target description 有关了。在 gdb 源码里搜索 warning 的文本 “Architecture rejected target-supplied description”,看到./gdb/target-descriptions.c::target_find_description() 函数打印出的 warning: + +```c + if (tdesc_info->tdesc != nullptr) + { + struct gdbarch_info info; + + info.target_desc = tdesc_info->tdesc; + if (!gdbarch_update_p (info)) + warning (_("Architecture rejected target-supplied description")); + else +``` + +这个 warning 在检查调用 gdbarch_update_p() 函数的返回值后打印,进一步查看 gdbarch_update_p() 函数发现它里面有很多调试信息打印: + +```c +int +gdbarch_update_p (struct gdbarch_info info) +{ + struct gdbarch *new_gdbarch; + +... + + /* If there no architecture by that name, reject the request. */ + if (new_gdbarch == NULL) + { + if (gdbarch_debug) + gdb_printf (gdb_stdlog, "gdbarch_update_p: " + "Architecture not found\n"); + return 0; + } + + /* If it is the same old architecture, accept the request (but don't + swap anything). */ + if (new_gdbarch == target_gdbarch ()) + { + if (gdbarch_debug) + gdb_printf (gdb_stdlog, "gdbarch_update_p: " + "Architecture %s (%s) unchanged\n", + host_address_to_string (new_gdbarch), + gdbarch_bfd_arch_info (new_gdbarch)->printable_name); + return 1; + } + + /* It's a new architecture, swap it in. */ + if (gdbarch_debug) + gdb_printf (gdb_stdlog, "gdbarch_update_p: " + "New architecture %s (%s) selected\n", + host_address_to_string (new_gdbarch), + gdbarch_bfd_arch_info (new_gdbarch)->printable_name); + set_target_gdbarch (new_gdbarch); + + return 1; +} +``` + +调试信息的打印都受一个名叫 `gdbarch_debug` 的全局变量控制,该变量定义在这里且默认值为 0: + +```c +#ifndef GDBARCH_DEBUG +#define GDBARCH_DEBUG 0 +#endif +unsigned int gdbarch_debug = GDBARCH_DEBUG; +``` + +这不很简单嘛,修改源码将此默认值改成 1,然后再次测试即可看到调试信息输出了。 + +### gdb 进阶调试技巧 + +gdb 本身作为一个调试器,对于自己输出的调试信息还需要修改源码来控制这么 “老土” 么?答案当然是否定的,详见 [gdb 文档][004]。 + +这里我们需要关心这个 gdbarch。不多说,直接修改看看效果: + +``` +>>> show debug arch +Architecture debugging is 0. +>>> set debug arch 1 +>>> show debug arch +Architecture debugging is 1. +>>> target remote :1234 +Remote debugging using :1234 +gdbarch_find_by_info: info.bfd_arch_info riscv:rv64 +gdbarch_find_by_info: info.byte_order 1 (little) +gdbarch_find_by_info: info.osabi 5 (GNU/Linux) +gdbarch_find_by_info: info.abfd 0x0 +gdbarch_find_by_info: info.tdep_info 0x0 +gdbarch_find_by_info: Target rejected architecture +gdbarch_update_p: Architecture not found +warning: Architecture rejected target-supplied description +``` + +对比一下 12.1 版本的输出: + +``` +>>> target remote :1234 +Remote debugging using :1234 +gdbarch_find_by_info: info.bfd_arch_info riscv:rv64 +gdbarch_find_by_info: info.byte_order 1 (little) +gdbarch_find_by_info: info.osabi 5 (GNU/Linux) +gdbarch_find_by_info: info.abfd 0x0 +gdbarch_find_by_info: info.tdep_info 0x0 +gdbarch_find_by_info: New architecture 0x55c97f9defc0 (riscv:rv64) selected +``` + +这里问题非常清楚了,v9.2 版本的 gdb 的./gdb/arch-utils.c::gdbarch_find_by_info() 返回值检查报错: + +```c + if (new_gdbarch == NULL) + { + if (gdbarch_debug) + fprintf_unfiltered (gdb_stdlog, "gdbarch_update_p: " + "Architecture not found\n"); + return 0; + } +``` + +“Target rejected architecture” 的报错信息来自 new_gdbarch 指针为空,由函数 gdbarch_find_by_info() 抛出: + +```c + /* Ask the tdep code for an architecture that matches "info". */ + new_gdbarch = rego->init (info, rego->arches); + + /* Did the tdep code like it? No. Reject the change and revert to + the old architecture. */ + if (new_gdbarch == NULL) + { + if (gdbarch_debug) + fprintf_unfiltered (gdb_stdlog, "gdbarch_find_by_info: " + "Target rejected architecture\n"); + return NULL; + } +``` + +至此,这个问题的根因找到了。 + +## 总结 + +本文介绍了一个与 QEMU gdbstub 相关的问题调试实例,其中牵涉了一些 QEMU gdbstub 本身的调试思路和 gdb 客户端的高级调试技巧,希望能够帮助读者在遇到类似问题时能够有所启发。 + +## 参考资料 + +[001]: https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html +[002]: https://gitlab.com/qemu-project/qemu/-/commit/94452ac4cf263e8996613db8d981e4ea85bd019a +[003]: https://gitlab.com/qemu-project/qemu/-/commit/4c0f0b6619126637e802f07c9fe8e9fffbc1c4bb +[004]: https://sourceware.org/gdb/onlinedocs/gdb/Debugging-Output.html -- Gitee From 13fcc40584b53eb21767d459c1bac12106c5d7d5 Mon Sep 17 00:00:00 2001 From: falcon Date: Thu, 27 Apr 2023 16:23:07 +0000 Subject: [PATCH 2/6] Update articles/20230428-gdb-and-qemu-gdbstub-debug.md --- articles/20230428-gdb-and-qemu-gdbstub-debug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230428-gdb-and-qemu-gdbstub-debug.md b/articles/20230428-gdb-and-qemu-gdbstub-debug.md index 4bed41e..b224cc9 100644 --- a/articles/20230428-gdb-and-qemu-gdbstub-debug.md +++ b/articles/20230428-gdb-and-qemu-gdbstub-debug.md @@ -58,7 +58,7 @@ gdb-xml/ 目录下包含 QEMU 支持的所有体系架构的静态的 XML 文件 ## gdb 调试 -上一节简单介绍了 QEMU gdbstub 是什么,在 QEMU 里实现的代码在何处,以及 gdb 客户端连上 QEMU gdbstub 后 QEMU 测收到的 target.xml 的读取请求。 +上一节简单介绍了 QEMU gdbstub 是什么,在 QEMU 里实现的代码在何处,以及 gdb 客户端连上 QEMU gdbstub 后 QEMU 侧收到的 target.xml 的读取请求。 如果 gdb 客户端连不上 QEMU gdbstub,这种情况应该怎么调试呢?下面以一个实际例子为例进行说明。 -- Gitee From 6d8fc7835dc16a0a335dac7795af4fec66d4d0aa Mon Sep 17 00:00:00 2001 From: falcon Date: Thu, 27 Apr 2023 16:23:29 +0000 Subject: [PATCH 3/6] Update articles/20230428-gdb-and-qemu-gdbstub-debug.md --- articles/20230428-gdb-and-qemu-gdbstub-debug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230428-gdb-and-qemu-gdbstub-debug.md b/articles/20230428-gdb-and-qemu-gdbstub-debug.md index b224cc9..c807d95 100644 --- a/articles/20230428-gdb-and-qemu-gdbstub-debug.md +++ b/articles/20230428-gdb-and-qemu-gdbstub-debug.md @@ -127,7 +127,7 @@ $ ./gdb --data-directory=./data-directory ### 深入分析 -由 warning 信息我们自然的想到这个问题可能是 gdb 的 target description 有关,在本文开始的 QEMU gdbstub 章节中笔者提到了 QEMU 会发送 target description(cpu、fpu、csr 等寄存器描述)给 gdb 客户端,那么我们在 gdb 这边可以查看一下,gdb 到底用了 QEMU 的 target description 没有。这里要用到 gdb 的命令 maintenance 中的 `print c-tdesc` 子命令,以 C 代码的形式打印出 gdb 当前所用的 target description。 +由 warning 信息我们自然地想到这个问题可能跟 gdb 的 target description 有关,在本文开始的 QEMU gdbstub 章节中笔者提到了 QEMU 会发送 target description(cpu、fpu、csr 等寄存器描述)给 gdb 客户端,那么我们在 gdb 这边可以查看一下,gdb 到底用了 QEMU 的 target description 没有。这里要用到 gdb 的命令 maintenance 中的 `print c-tdesc` 子命令,以 C 代码的形式打印出 gdb 当前所用的 target description。 用 Ubuntu 20.04 自带的 gdb-multiarch v9.2: -- Gitee From b849f58c32695a914901c83ac9eedb3095f0e8dc Mon Sep 17 00:00:00 2001 From: falcon Date: Thu, 27 Apr 2023 16:23:49 +0000 Subject: [PATCH 4/6] Update articles/20230428-gdb-and-qemu-gdbstub-debug.md --- articles/20230428-gdb-and-qemu-gdbstub-debug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230428-gdb-and-qemu-gdbstub-debug.md b/articles/20230428-gdb-and-qemu-gdbstub-debug.md index c807d95..25d9f4d 100644 --- a/articles/20230428-gdb-and-qemu-gdbstub-debug.md +++ b/articles/20230428-gdb-and-qemu-gdbstub-debug.md @@ -173,7 +173,7 @@ initialize_tdesc_ (void) ... ``` -至此,我们可以非常确信这个问题跟 gdb 不同版本处理 RISC-V 的 target description 有关了。在 gdb 源码里搜索 warning 的文本 “Architecture rejected target-supplied description”,看到./gdb/target-descriptions.c::target_find_description() 函数打印出的 warning: +至此,我们可以非常确信这个问题跟 gdb 不同版本处理 RISC-V 的 target description 有关了。在 gdb 源码里搜索 warning 的文本 “Architecture rejected target-supplied description”,看到 `./gdb/target-descriptions.c::target_find_description()` 函数打印出的 warning: ```c if (tdesc_info->tdesc != nullptr) -- Gitee From 2473263bb86e40aff98a46414f5d445baef8843d Mon Sep 17 00:00:00 2001 From: falcon Date: Thu, 27 Apr 2023 16:24:13 +0000 Subject: [PATCH 5/6] Update articles/20230428-gdb-and-qemu-gdbstub-debug.md --- articles/20230428-gdb-and-qemu-gdbstub-debug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230428-gdb-and-qemu-gdbstub-debug.md b/articles/20230428-gdb-and-qemu-gdbstub-debug.md index 25d9f4d..9c3efe5 100644 --- a/articles/20230428-gdb-and-qemu-gdbstub-debug.md +++ b/articles/20230428-gdb-and-qemu-gdbstub-debug.md @@ -277,7 +277,7 @@ gdbarch_find_by_info: info.tdep_info 0x0 gdbarch_find_by_info: New architecture 0x55c97f9defc0 (riscv:rv64) selected ``` -这里问题非常清楚了,v9.2 版本的 gdb 的./gdb/arch-utils.c::gdbarch_find_by_info() 返回值检查报错: +这里问题非常清楚了,v9.2 版本的 gdb 的 `./gdb/arch-utils.c::gdbarch_find_by_info()` 返回值检查报错: ```c if (new_gdbarch == NULL) -- Gitee From d08c8b1aada94fee6112316c0a86a7e9e748fff7 Mon Sep 17 00:00:00 2001 From: falcon Date: Thu, 27 Apr 2023 16:24:29 +0000 Subject: [PATCH 6/6] Update articles/20230428-gdb-and-qemu-gdbstub-debug.md --- articles/20230428-gdb-and-qemu-gdbstub-debug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230428-gdb-and-qemu-gdbstub-debug.md b/articles/20230428-gdb-and-qemu-gdbstub-debug.md index 9c3efe5..470a5e3 100644 --- a/articles/20230428-gdb-and-qemu-gdbstub-debug.md +++ b/articles/20230428-gdb-and-qemu-gdbstub-debug.md @@ -310,7 +310,7 @@ gdbarch_find_by_info: New architecture 0x55c97f9defc0 (riscv:rv64) selected ## 总结 -本文介绍了一个与 QEMU gdbstub 相关的问题调试实例,其中牵涉了一些 QEMU gdbstub 本身的调试思路和 gdb 客户端的高级调试技巧,希望能够帮助读者在遇到类似问题时能够有所启发。 +本文介绍了一个与 QEMU gdbstub 相关的问题调试实例,其中牵涉了一些 QEMU gdbstub 本身的调试思路和 gdb 客户端的高级调试技巧,希望在读者遇到类似问题时能够有所启发。 ## 参考资料 -- Gitee