调试
应用调试
本节提供使用 QEMU 调试应用的快速实践参考。大部分内容已在 QEMU 和 GNU_Debugger 参考手册中介绍。
本快速参考介绍一些快捷方法、专用环境变量和参数,帮助你快速搭建调试环境。
调试 QEMU 中运行的应用,最简单的方法是使用 GNU 调试器,并通过 QEMU 在开发系统上启动本地 GDB 服务器。
调试需要 ELF 二进制镜像。构建系统在构建目录中生成该镜像。内核二进制文件默认名为 zephyr.elf,可通过 CONFIG_KERNEL_BIN_NAME 更改。
GDB 服务器
这里使用标准 TCP 端口 1234 启动 GDB 服务器实例。可根据开发环境更改端口号。有多种实现方式,每种方式都会启动一个处理器暂停执行的 QEMU 实例,并让 GDB 服务器实例监听连接。
直接运行 QEMU
可以让 QEMU 在执行任何代码之前监听“gdb 连接”,以便调试。
qemu -s -S <image>
这会使 QEMU 监听端口 1234,等待 GDB 连接。
上述选项含义如下:
-S:启动时不运行 CPU;必须在监视器中输入 'c' 才会开始。-s:-gdb tcp::1234的简写,在 TCP 端口 1234 上启动 GDB 服务器。
通过 ninja 运行 QEMU
在应用的构建目录中运行:
ninja debugserver
QEMU 将控制台输出写入通过 CMake 的 ${QEMU_PIPE} 指定的路径,通常为构建目录中的 qemu-fifo。运行期间可用 tail -f qemu-fifo 监视该文件。
通过 west 运行 QEMU
在项目根目录运行:
west build -t debugserver_qemu
QEMU 将控制台输出写入调用 west 的终端。
配置 gdbserver 监听设备
Kconfig 选项 CONFIG_QEMU_GDBSERVER_LISTEN_DEV 控制监听设备,可以是 TCP 端口号或字符设备路径。GDB 9.0 及更新版本还支持 Unix 域套接字。
若未设置该选项,QEMU 调用将不带 -s 或 -gdb 参数。此时可通过 shell 环境变量 QEMU_EXTRA_FLAGS 传入自定义监听设备配置。
GDB 客户端
运行 gdb 并输入以下命令连接服务器:
$ path/to/gdb path/to/zephyr.elf
(gdb) target remote localhost:1234
(gdb) dir ZEPHYR_BASE
备注
替换为系统上正确的 ZEPHYR_BASE。
可使用本地 GDB 配置文件 .gdbinit 在每次运行时初始化 GDB 实例。.gdbinit 通常放在主目录,但也可配置 GDB 从其他位置加载,包括调用 gdb 的目录。以下示例文件与上述配置相同:
target remote localhost:1234
dir ZEPHYR_BASE
其他界面
GDB 提供在终端中运行的 curses 界面。调用 gdb 时传入 --tui 选项,或在 gdb 中执行 tui enable 命令。
备注
开发系统上的 GDB 版本可能不支持 --tui 选项。请确保使用与构建该二进制文件所用工具链对应的 SDK 中的 GDB。
最后,以下命令使用 GDB 的图形前端 DDD 连接 GDB 服务器。该命令从 ELF 二进制文件(此处为 zephyr.elf)加载符号表。
ddd --gdb --debugger "gdb zephyr.elf"
这两条命令均执行 gdb。具体命令名称可能因工具链和交叉开发工具而异。
开发系统可能默认未安装 ddd。请按系统说明安装,例如在 Ubuntu 上使用 sudo apt-get install ddd。
Debugging
按上述方式配置后,连接 GDB 客户端时,应用会停在系统启动阶段。你可以设置断点、单步执行代码等,操作方式与在 gdb 中直接运行应用相同。
备注
与直接在 GDB 中运行本机应用不同,gdb 不会在应用运行时打印系统控制台输出。如果连接客户端后直接执行 continue,应用会运行,但看起来没有任何变化。请按上述方法查看控制台输出。
使用 Eclipse 调试
概述
CMake 支持生成项目描述文件,可导入 Eclipse 集成开发环境(IDE)进行图形化调试。
GNU MCU Eclipse plug-ins 提供在 Eclipse 中通过 pyOCD、Segger J-Link 和 OpenOCD 调试工具调试 ARM 项目的机制。
以下教程演示在 Windows 上使用 Eclipse 和 pyOCD 调试 Zephyr 应用,假定已安装 GCC ARM Embedded 工具链和 pyOCD。
搭建 Eclipse 开发环境
在 Eclipse 中打开
Window->Eclipse Marketplace...菜单,搜索GNU MCU Eclipse,在匹配结果中单击Install,安装 GNU MCU Eclipse plug-ins。打开
Window->Preferences菜单,进入MCU,设置Global pyOCD Path,以配置 pyOCD GDB 服务器路径。
生成并导入 Eclipse 项目
按 GNU Arm Embedded 所述配置 GNU Arm Embedded 工具链。
进入 Zephyr 目录树之外的文件夹构建应用。
# On Windows cd %userprofile%
备注
如果构建目录像 Zephyr 通常的做法那样位于源代码目录下,CMake 会警告:
“构建目录是源代码目录的子目录。
Eclipse 对此支持不佳。强烈建议使用与源代码目录同级的构建目录。”
使用 CMake 配置应用,再用 ninja 构建。注意,
-G"Eclipse CDT4 - Ninja"参数指定了不同的 CMake 生成器。除通常的 ninja 构建文件外,还会生成 Eclipse 项目描述文件.project。Using west:
west build -b frdm_k64f samples/synchronization -- -G"Eclipse CDT4 - Ninja"
Using CMake and ninja:
cmake -Bbuild -GNinja -DBOARD=frdm_k64f -G"Eclipse CDT4 - Ninja" samples/synchronization ninja -Cbuild
在 Eclipse 中打开
File->Import...菜单,选择Existing Projects into Workspace,导入生成的项目。在Select root directory:中选择应用构建目录。在找到的项目列表中勾选项目,然后单击Finish。
创建调试器配置
打开
Run->Debug Configurations...菜单。选择
GDB PyOCD Debugging,单击New,配置以下选项:在 Main 选项卡中:
Project:
my_zephyr_app@buildC/C++ Application:
zephyr/zephyr.elf
在 Debugger 选项卡中:
pyOCD 设置
Executable path:
$pyocd_path\$pyocd_executable取消勾选“Allocate console for semihosting”
开发板设置
Bus speed:8000000 Hz
取消勾选“Enable semihosting”
GDB 客户端设置
Executable path 示例(使用你的
GNUARMEMB_TOOLCHAIN_PATH):C:\gcc-arm-none-eabi-6_2017-q2-update\bin\arm-none-eabi-gdb.exe
在 SVD Path 选项卡中:
File path:
<workspace top>\modules\hal\nxp\mcux\devices\MK64F12\MK64F12.xml
备注
此项可选,用于向调试器提供 SoC 的内存映射寄存器地址和位域。
单击
Debug开始调试。
RTOS 感知
pyOCD v0.11.0 及更新版本支持 Zephyr RTOS 感知,可配合 Eclipse 中的 GDB PyOCD Debugging 使用,但必须在应用中启用 CONFIG_DEBUG_THREAD_INFO=y。
调试 I2C 通信
可以记录应用执行的全部或部分 I2C 事务。通过 Kconfig 选项 CONFIG_I2C_DUMP_MESSAGES 启用此功能;由于它使用 LOG_DBG 打印内容,还必须启用 CONFIG_I2C_LOG_LEVEL_DBG。
转储输出示例如下:
D: I2C msg: io_i2c_ctrl7_port0, addr=50
D: W len=01: 00
D: R Sr P len=08:
D: contents:
D: 43 42 41 00 00 00 00 00 |CBA.....
第一行给出事务的 I2C 控制器和目标地址。上例中,I2C 控制器名为 io_i2c_ctrl7_port0,目标设备地址为 0x50。
备注
地址、长度和内容均以十六进制表示,但不带 0x 前缀。
后续各行包含发送和接收的消息。写消息始终显示内容,读消息是否显示内容则由传给 i2c_dump_msgs_rw 的参数控制。用户可以调用此函数,i2c_transfer API 也会在内部调用它,并启用读内容转储。消息长度前会用以下缩写打印消息头:
W:写消息
R:读消息
Sr:重复起始位
P:停止位
上例显示一条写消息,字节 0x00 表示要从 I2C 目标读取的寄存器地址。随后日志显示接收消息的长度,以及从目标读出的字节 43 42 41 00 00 00 00 00。内容转储同时包含十六进制和 ASCII 表示。
过滤 I2C 通信转储
默认记录所有 I2C 控制器与目标之间的全部通信。无关设备的日志可能淹没有用信息,使目标设备的通信难以调试。
启用 Kconfig 选项 CONFIG_I2C_DUMP_MESSAGES_ALLOWLIST,可创建要记录的 I2C 目标允许列表。通过设备树配置设备允许列表,例如:
/ {
i2c {
display0: some-display@a {
...
};
sensor3: some-sensor@b {
...
};
};
i2c-dump-allowlist {
compatible = "zephyr,i2c-dump-allowlist";
devices = <&display0>, <&sensor3>;
};
};
过滤器节点通过值为 zephyr,i2c-dump-allowlist 的 compatible 字符串识别。devices 属性通过指向 I2C 总线设备的 phandle 选择设备。
上例中,与 display0 和 sensor3 设备的通信将显示在日志中。