前言
个人邮箱: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 发生变化的地方进行打断
其他命令
命令 作用 shell clear 调用 shell 清屏命令 quit/q 退出 gdb 调试 define my_command 自定义命令,能够进行自定义调试,具体用法参考ESP32 JTAG Debug 15: GDB Define 命令
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调试常见命令详细总结(附示例操作)