在 QEMU 中用 GDB 调试 Zephyr 固件

QEMU 可以在执行第一段固件代码之前暂停模拟处理器,并提供一个 GDB 服务端。另一终端中的 GDB 加载同一次构建得到的 ELF 符号文件,连入该服务端,就能设置断点、逐步执行并查看源码状态。这是 Zephyr 官方调试指南中最直接的应用调试路线,无需实体开发板或 JTAG 探针。

本文依据 Zephyr 4.4.0 的 Debugging 中 Application Debugging 部分翻译整理,并补充同版 Hello World 与 SDK 文档中的前提。文档页的 Eclipse、旧 ARM Embedded 2017 工具链、pyOCD 板卡调试以及 I2C 日志部分已阅读,但属于不同任务,不纳入本教程。本文版本范围固定为 Zephyr 4.4.0、SDK 1.0.1 与同版示例。

QEMU 调试流程:终端 A 启动暂停的固件与控制台,终端 B 用 GDB 加载同版 ELF 并连接本地端口
未完纪根据官方文档绘制;示意图不代表已经运行或成功连接。

先确认构建产物与工具链

调试需要包含符号信息的 ELF 文件,不能只提供裸二进制镜像。Zephyr 构建系统默认在构建目录的 zephyr/ 子目录生成 zephyr.elf;默认构建目录为 build,因此本例路径是 build/zephyr/zephyr.elf。如果工程通过 CONFIG_KERNEL_BIN_NAME 修改了内核文件名,GDB 命令也必须随之改变。

准备好 Zephyr 4.4.0 的工作区、依赖和已安装的 SDK 1.0.1,再从同版 Zephyr 源码根目录构建官方 Hello World 示例。SDK 1.0.1 是这版 SDK 安装说明给出的版本;并不是任意最新 SDK 与任意旧工程可以随意混用的保证。

west build -b qemu_x86 samples/hello_world

这条命令来自同版 Hello World 文档。教程假定默认 build 目录是为本例准备的,且没有混用其他板卡或旧构建缓存。官方示例还给出了 west build -t run 的直接运行方式;本次任务要在启动时暂停,因此下一步使用调试服务端目标。

GDB 也要与构建架构匹配。例如本例目标为 qemu_x86,不能拿仅面向 ARM 的交叉 GDB 来调试。以下命令中的 /path/to/sdk-gdb 和源码路径是明确的路径占位,需要替换为你实际安装和构建使用的工具链路径;不要将占位文本原样执行。

终端 A:启动 QEMU 调试服务端

从项目根目录运行:

west build -t debugserver_qemu

这个目标通过 Zephyr 已生成的构建配置启动 QEMU,并使处理器在启动阶段处于暂停状态,等待调试器连接。官方说明以 TCP 1234 为常用端口;实际监听设备由工程配置决定。保持终端 A 打开,之后固件的系统控制台输出也会出现在这个终端中。

原文同时介绍了两条替代路线。第一条是在构建目录执行 ninja debugserver。这种方式将控制台输出写到 CMake 中 QEMU_PIPE 指定的路径,通常是构建目录内的 qemu-fifo;需要在相应位置用 tail -f qemu-fifo 观察。第二条是直接给 QEMU 指定 -s 和 -S:大写 -S 表示启动时不运行 CPU,小写 -s 是 -gdb tcp::1234 的缩写。

编者说明:原文的裸 qemu -s -S <image> 是解释参数的简写,不能代替真实目标需要的架构程序、机器型号、设备参数与镜像装载方式。本文采用 west 目标,避免读者将简写误认为适合所有 Zephyr 镜像的完整启动命令。

监听地址是调试配置的一部分

CONFIG_QEMU_GDBSERVER_LISTEN_DEV 控制 GDB 服务端的监听设备。官方说明支持 TCP 端口或字符设备路径,GDB 9.0 及以后的版本也支持 Unix 域套接字。如果该 Kconfig 选项未设置,QEMU 调用中不会自动带上 -s 或 -gdb,此时可以通过 QEMU_EXTRA_FLAGS 环境变量提供自己的监听设置。

静态审查提醒:GDB 连接具有读写被调试目标内存及控制执行的能力。本地客户端写 localhost:1234,并不自动证明服务端只绑定本地回环地址;尤其原文解释的 tcp::1234 没有明确写出主机地址。启动前应按这版 QEMU 与 Zephyr 配置语法核对实际监听参数,只允许本机使用,并避免将调试端口暴露到共享网络。这里没有提供需要开放防火墙、远程转发或连接生产目标的步骤。

终端 B:让 GDB 连接到暂停的固件

加载刚才构建的 ELF,然后连接服务端:

/path/to/sdk-gdb build/zephyr/zephyr.elf

(gdb) target remote localhost:1234
(gdb) dir /absolute/path/to/zephyr

原文将最后的参数写为 ZEPHYR_BASE,意思是替换成当前系统实际的 Zephyr 源码目录;它不是要求在 GDB 内原样输入一个自动解析的 shell 变量。GDB 的 dir 命令为查找源码添加目录。ELF、源码和 QEMU 加载的固件必须对应同一次构建,否则源码行、符号或断点位置可能对不上。

连接成功时,应用应仍停在系统启动阶段。下面是编者补充的最小交互顺序,用于说明连接之后通常怎样进入应用入口;它不是本次实测记录:

(gdb) break main
(gdb) continue
(gdb) list
(gdb) next
(gdb) info locals

break main 请求在应用入口暂停,continue 让启动过程继续;到达断点后可以查看源码、单步和局部变量。是否有可见局部变量取决于所在函数、符号信息和优化设置,不能把空的局部变量列表直接判断为调试失败。

控制台没有出现在 GDB 中,并不表示程序没运行

这是原文特别强调的区别:远程调试 QEMU 中的 Zephyr,与 GDB 直接启动本机程序并不一样。GDB 不负责展示 Zephyr 的系统控制台输出。若你连接后只输入 continue,GDB 窗口可能没有新的应用输出,但固件已经继续执行。

采用本文 west 路线时,应回到终端 A 查看输出;采用 ninja 路线时,应查看其 QEMU_PIPE。同版 Hello World 文档展示的示例输出为:

Hello World! x86

这行是官方示例的预期内容,不是本文作者运行得到的结果。若断点命中而没有看到它,先确认停留位置、是否已经继续到打印语句,以及查看的是否为正确输出通道。

重复连接与其他界面

原文建议把连接命令放进本地 .gdbinit,以便每次启动 GDB 时重复初始化:

target remote localhost:1234
dir /absolute/path/to/zephyr

主目录是常见存放位置,也可以按 GDB 规则从其他位置加载。编者补充:.gdbinit 是可执行调试器命令的配置文件,只加载你检查过、可信的文件;不要为了自动加载陌生项目而全局放宽 GDB 的安全路径限制。本文没有创建或更改用户主目录中的配置。

如果希望在终端中同时查看源码,可以在调用 GDB 时加 --tui,或在 GDB 中执行 tui enable。某些 GDB 构建不包含 TUI 支持,要使用与本次构建工具链对应的 SDK GDB,而不是盲目换成系统中另一个 gdb。

原文还介绍了图形前端 DDD,它最终仍调用 GDB,并从 ELF 读取符号表,形式为:

ddd --gdb --debugger "gdb zephyr.elf"

这里的 gdb 和 zephyr.elf 同样要换成正确的工具与文件路径。DDD 未必预装,原文列出的 Ubuntu 安装命令 sudo apt-get install ddd 会修改系统软件包,不是完成本教程的必要步骤;没有安装 DDD 也能使用前面的命令行流程。

来源、许可与验证范围

原文维护方:Zephyr Project。文档页署名为 © 2015–2026 Zephyr Project members and individual contributors。来源为 Debugging 4.4.0、Hello World 4.4.0 与 Zephyr SDK 文档。本文经授权翻译整理,保留归属。项目许可说明列有第三方组件例外,不能以软件主许可证一概替代所有文档与组件的许可。

本文只做了全文阅读、版本交叉核对与命令静态审查,未安装 SDK、构建固件、启动 QEMU、开放端口、连接 GDB 或执行示例。图为新绘示意图;断点交互顺序与安全边界补充均已标为编者内容。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容