前言
- 个人邮箱:zhangyixu02@gmail.com
- 本人使用的是 Ubuntu 环境,采用 GDB 方式进行调试。
- 对于新手,我个人还是建议参考ESP32S3学习笔记(0)—— Vscode IDF环境搭建及OpenOCD调试介绍进行图形化的方式调试。
- 如果是希望在 Windows 环境下进行 GDB 调试,可以参考 Windows 环境下,使用 ESP32-S3 USB 接口进行 JTAG 调试的流程。
GDB 介绍
两种调试方式
- ESP32 提供了两种调试方式,一种是利用串口进行日志打印。这种方式是常用的,我们可以根据日志信息知道程序的运行信息。
- 但是在一些特殊场景,例如我需要让程序在某个时刻停下来进行调试,日志打印的方式就并不那么好用了。我们此时就可以使用 JTAG 调试的方式进行。

GDB 和 Openocd 介绍
- 在电脑端,我们需要先运行 Openocd 充当调试代理用于与目标硬件进行直接通讯,他提供一个 GDB 服务器接口(通常在TCP端口 :3333 上),GDB 可以通过该接口与 OpenOCD 通信。 GDB 会向 OpenOCD 发送调试命令,例如设置断点、查看寄存器、单步执行等。当 OpenOCD 接收到来自 GDB 的命令后,负责将这些命令转换成特定的硬件指令,并执行到目标设备上。
- GDB 是一个高层的调试器,用户通过它来编写和管理调试会话。GDB本身不直接与硬件通信,它通过GDB服务器(如OpenOCD提供的)与设备进行交互。
- 如下为 电脑端 <—> 调试器 <—> ESP32C3 的方式进行调试。调试器为 FT2232/FT232 芯片。

- 这种外置调试器的方法相对麻烦,还需要自行准备调试芯片,后面乐鑫将调试器集成到了芯片内部。

- 我们可以通过乐鑫官方选型网站得知哪些芯片内部集成了 JTAG 调试接口。

- 如果当前使用的芯片内部没有 JTAG 调试接口,我们可以购买 ESP-Prog 进行调试。
环境准备
打开 Openocd
- 通过上面的内容我们知道,要进行 GDB 调试 ESP32-S3 的话,需要先打开 Openocd 提供一个接口。我们可以输入如下命令进行开启 Openocd。
openocd -f board/esp32s3-builtin.cfg
- 这个 cfg 文件在如下目录中,我们可以在该目录中选择合适的文件打开 Openocd。
注:随着版本更新,你可能并不是 v0.12.0-esp32-20230921。
~/.espressif/tools/openocd-esp32/v0.12.0-esp32-20230921/openocd-esp32/share/openocd/scripts/board
- 这个时候肯定就会有人要说了,我怎么知道应该选择哪个 cfg 文件呢?
- 如果是内部集成了 JTAG 接口,一般选择 builtin 名称的 cfg 文件。
- 如果是使用的 ESP-Prog 调试器,那么就选择 bridge 名称的 cfg 文件。
- 如果你发现上述做法都不对,那就找到对应的芯片前缀名,然后一个一个的试吧。(哭笑)
- 在 Ubuntu 环境中,你打开 Openocd 发现如下报错,那么说明当前用户没有足够的权限访问 USB 设备。
Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
Error: esp_usb_jtag: could not find or open device!
- 此时你需要创建一个
udev
规则文件添加规则。
sudo vim /etc/udev/rules.d/99-openocd.rules
- 添加如下内容。
SUBSYSTEM=="usb", ATTR{idVendor}=="303a", ATTR{idProduct}=="1001", MODE="0666"
- 重新加载
udev
规则。
sudo udevadm control --reload-rules
sudo udevadm trigger
进入 GDB 调试
- 我们需要在项目根目录中创建 gdbinit 文件,并且在该文件中加入如下内容。
tui enable 命令能够在终端中增加一个 UI 界面方便我们知道当前调试的位置,如果觉得这个 UI 界面看的不舒服,可以将这一行给删除
target remote :3333
set remote hardware-watchpoint-limit 2
mon reset halt
flushregs
thb app_main
c
tui enable
- 此时我们需要再打开一个终端,输入如下命令即可进入 GDB 调试界面。
注:当前 elf 文件应该是你烧录到芯片时,生成的 elf 文件! 调试过程中,工程代码建议不要修改。
xtensa-esp32s3-elf-gdb -x gdbinit build/gatt_client_demo.elf
- 不同的芯片/架构使用的 GDB 调试器不同,具体参考如下:
架构/芯片 | 命令 |
---|
Xtensa ESP32 | xtensa-esp32-elf-gdb |
Xtensa ESP32-S2 | xtensa-esp32s2-elf-gdb |
Xtensa ESP32-S3 | xtensa-esp32s3-elf-gdb |
RISC-V | riscv32-esp-elf-gdb |
GDB 命令
控制命令
运行命令
命令 | 作用 |
---|
continue/c | 运行程序,直到遇到断点才停止 |
next/n | 单步执行, 跳过函数调用 |
next/n count | 运行多步, 跳过函数调用(count 要跳过运行的步骤次数) |
step/s | 单步调试,进入函数调用 |
step/s count | 多步调试,进入函数调用(count 要跳过运行的步骤次数) |
finish | 继续执行,直到当前函数返回 |
until num | 运行到指定行号(num 行号) |
jump/j num | 直接跳转到指定行数代码,相当于 C 语言的 goto 语句 |
monitor reset halt | 复位开发板 |
set 命令
命令 | 作用 |
---|
set 变量名=num | 将指定变量设置为指定值(num 数值) |
set $变量名=num | 设置一个 GDB 的内部变量,此方法可以用于进行特定的调试计算。具体参考ESP32 JTAG Debug 14: GDB Set 命令的第 6 min |
set print address off/on | 打印数据时,关闭/开启 打印对应数据地址 |
set style address foreground | 设置内存地址的前景色(字体颜色) |
set style address background | 设置内存地址的背景色 |
信息查看命令
命令 | 作用 |
---|
list/l | 列出当前位置往下10列源代码 |
backtrace/bt | 查看函数调用信息 |
where | 查看当前程序运行到了哪里 |
info locals | 查看当前作用域的局部变量信息 |
info registers/reg | 显示所有寄存器的值 |
info registers/reg | 显示指定寄存器的值 |
print 命令
命令 | 作用 |
---|
print 变量名/数组/字符串/结构体 | 查看指定变量的值 |
print /x 变量名 | 以 16 进制形式打印变量值 |
print /d 变量名 | 以 10 进制形式打印变量值 |
print /u 变量名 | 以无符号 10 进制形式打印变量值 |
print /o 变量名 | 以 8 进制形式打印变量值 |
print /t 变量名 | 以 2 进制形式打印变量值 |
print /a 变量名 | 以地址格式打印变量值 |
print /c 变量名 | 以字符形式打印变量值 |
print /f 变量名 | 以浮点数形式打印变量值 |
print /s 变量名 | 以字符串形式打印变量值 |
print 函数名 :: 变量名 | 打印指定函数中,指定变量的数据 |
print ‘指定文件路径’ :: 变量 | 打印不同文件中变量的信息 |
print pretty on | 启动 “漂亮打印”(pretty-printing)功能,这将允许 GDB 以更可读的格式输出复杂数据结构(如结构体、类、数组等),使得调试时的输出更加清晰易懂 |
display 命令
命令 | 作用 |
---|
display 变量名 | 持续监视某个变量 |
display /x 变量名 | 以 16 进制形式持续监视某个变量 |
info display | 查看 display 列表 |
disable display num | 失能 display 列表中指定监视 |
enable display num | 使能 display 列表中指定监视 |
undisplay num | 删除 display 列表中指定监视 |
delete display num | 删除 display 列表中指定监视 |
地址信息打印命令
格式 :
x/<count><format><size> <address>
<count>
:要显示的内存单元的数量(可选,默认值为 1)。<format>
:指定输出格式,可以是以下之一:
x
:十六进制(hexadecimal)d
:十进制(decimal)u
:无符号十进制(unsigned decimal)o
:八进制(octal)t
:二进制(binary)c
:字符(character)f
:浮点数(float)s
:字符串(string)i
:指令(instruction)
<size>
:指定每个单元的大小,可以是以下之一:
b
:字节(byte)h
:半字(halfword,2 bytes)w
:字(word,通常为 4 bytes)g
:巨字(giant word,通常为 8 bytes)
<address>
:要查看的内存地址,可以是变量名、指针或具体的内存地址。
断点命令
添加断点
命令 | 作用 |
---|
break/b n | gdb 运行到的当前文件中的某一行设置断点(n : 当前文件行号) |
break/b filename: n | 向指定文件的指定行设置断点(filename : 指定) |
break/b func | 在指定函数开头设置断点(func : 函数名) |
tbreak n/func | 设置临时断点,在设置之后只起作用一次(n : 当前文件行号 func : 函数名) |
断点控制
命令 | 作用 |
---|
disable n | 失能指定断点号断点 (n : 断点号) |
enable n | 使能指定断点号断点 (n : 断点号) |
delete/d | 删除所有断点 |
delete/d n | 删除指定断点号断点 (n : 断点号) |
clear n | 清除行 n 上面的所有断点 |
查看断点信息
命令 | 作用 |
---|
info break/b | 查看所有断点信息 |
info break/b n | 查看指定断点号断点信息 (n : 断点号) |
观察断点
命令 | 作用 |
---|
watch 变量名 | 给指定变量名设置一个观察断点,所有与该变量名发生变化的地方进行打断,在运行到发生修改地方时,会停止并且打印该变量的原来值和修改之后的值。在相同的地方,如果只有第一次该变量发生变化,那么该地方只有第一次会被打断 |
info watch | 查看观察断点相关信息 |
watch 表达式 | 给一个表达式观察断点,例如表达式 i+j,如果变量 i 不发生变化,j 发生变化,依旧会在 j 发生变化的地方进行打断 |
其他命令
TUI 使用
命令 | 作用 |
---|
上下左右箭头 | 向上/下/左/右向滚动代码 |
PgUP/PgDn | 向上/下翻页 |
update | 回到当前运行的位置 |
list/l num | 查看指定行号信息(num 行号) |
layout asm | 查看汇编代码 |
layout regs | 显示寄存器相关信息 |
layout src | 回到源代码 |
参考
- Windows 环境下,使用 ESP32-S3 USB 接口进行 JTAG 调试的流程
- B站:ESP32 JTAG Debug 01: JTAG接口简介
- 利用 Guru Meditation 错误打印定位问题
- GDB常用命令大全 GDB 命令详细解释
- gdb调试常见命令详细总结(附示例操作)