51、使用 GDB 进行调试

使用 GDB 进行调试

1. 技术要求

为了能够顺利跟随示例进行操作,你需要确保具备以下条件:
- 基于 Linux 的主机系统,至少有 60GB 的可用磁盘空间。
- Buildroot 2020.02.9 LTS 版本。
- Yocto 3.1 (Dunfell) LTS 版本。
- 适用于 Linux 的 Etcher。
- MicroSD 卡读卡器和卡。
- USB 转 TTL 3.3V 串口线。
- 树莓派 4。
- 5V 3A USB - C 电源。
- 用于网络连接的以太网线和端口。
- BeagleBone Black。
- 5V 1A 直流电源。

如果你还未安装 Buildroot 2020.02.9 LTS 版本,可参考 Buildroot 用户手册的系统要求部分(https://buildroot.org/downloads/manual/manual.html),并按照相关说明在 Linux 主机上进行安装。同样,若未安装 Yocto 3.1 (Dunfell) LTS 版本,可参考 Yocto 项目快速构建指南的兼容 Linux 发行版和构建主机软件包部分(https://www.yoctoproject.org/docs/current/brief-yoctoprojectqs/brief-yoctoprojectqs.html),然后在 Linux 主机上安装。

本章的所有代码可在 GitHub 仓库的 Chapter19 文件夹中找到:https://github.com/PacktPublishing/Mastering-Embedded-Linux-Programming-Third-Edition。

2. GNU 调试器(GDB)

GDB 是一种用于编译型语言的源代码级调试器,主要用于 C 和 C++,不过也支持多种其他语言,如 Go 和 Objective - C。你可以阅读所使用的 GDB 版本的相关说明,以了解其对各种语言的支持现状。

GDB 的项目网站是 https://www.gnu.org/software/gdb/,该网站包含许多有用的信息,包括 GDB 用户手册《Debugging with GDB》。

GDB 自带命令行用户界面,有些人可能会觉得不太友好,但实际上,稍加练习就很容易上手。如果你不喜欢命令行界面,有很多 GDB 的前端用户界面可供选择,后续会介绍其中三种。

3. 调试前的准备

在调试代码之前,需要使用调试符号来编译要调试的代码。GCC 为此提供了两个选项:-g 和 -ggdb。后者添加了特定于 GDB 的调试信息,而前者则以适合目标操作系统的格式生成信息,因此是更具可移植性的选项。在我们的案例中,目标操作系统通常是 Linux,使用 -g 或 -ggdb 差别不大。这两个选项都允许指定调试信息的级别,从 0 到 3:
| 级别 | 说明 |
| ---- | ---- |
| 0 | 不生成任何调试信息,相当于省略 -g 或 -ggdb 开关。 |
| 1 | 生成最少的信息,但包含函数名和外部变量,足以生成回溯信息。 |
| 2 | 默认级别,包含局部变量和行号信息,可进行源代码级调试和单步执行代码。 |
| 3 | 包含额外信息,例如 GDB 能正确处理宏展开。 |

在大多数情况下,-g 就足够了。如果在单步执行代码时遇到问题,尤其是代码中包含宏,可使用 -g3 或 -ggdb3。

另一个需要考虑的问题是代码优化级别。编译器优化往往会破坏源代码行与机器代码之间的关系,导致单步执行源代码时出现不可预测的情况。如果遇到此类问题,可能需要在不进行优化的情况下编译代码,即省略 -O 编译开关,或使用 -Og,它允许进行不影响调试的优化。

相关的问题是栈帧指针,GDB 需要它来生成函数调用的回溯信息。在某些架构上,GCC 在较高的优化级别(-O2 及以上)下不会生成栈帧指针。如果你必须使用 -O2 进行编译,但仍希望生成回溯信息,可以使用 -fno - omit - frame - pointer 来覆盖默认行为。同时,要留意那些通过添加 -fomit - frame - pointer 手动优化以省略帧指针的代码,可能需要暂时移除这些部分。

4. 调试应用程序

使用 GDB 调试应用程序有两种方式:
- 如果你正在开发在桌面和服务器上运行的代码,或者在编译和运行代码的同一台机器上进行开发,自然可以本地运行 GDB。
- 大多数嵌入式开发使用交叉工具链,因此你希望调试在设备上运行的代码,但从拥有源代码和工具的交叉开发环境进行控制。这里将重点介绍后一种情况。

5. 使用 gdbserver 进行远程调试

远程调试的关键组件是调试代理 gdbserver,它运行在目标设备上,控制被调试程序的执行。gdbserver 通过网络连接或串口接口与运行在主机上的 GDB 副本进行通信。

通过 gdbserver 进行调试与本地调试几乎相同,但由于涉及两台计算机,调试时需要确保它们处于正确的状态。以下是一些需要注意的事项:
- 调试会话开始时,需要使用 gdbserver 在目标设备上加载要调试的程序,然后在主机上单独加载交叉工具链中的 GDB。
- GDB 和 gdbserver 需要相互连接后才能开始调试会话。
- 运行在主机上的 GDB 需要知道在哪里查找调试符号和源代码,特别是对于共享库。
- GDB 的 run 命令可能无法按预期工作。
- 调试会话结束时,gdbserver 会终止,如果需要再次进行调试会话,需要重新启动它。
- 你需要在主机上拥有要调试的二进制文件的调试符号和源代码,但目标设备上不需要。通常,目标设备上没有足够的存储空间来存储这些信息,因此在部署到目标设备之前需要剥离它们。
- GDB/gdbserver 组合并不支持本地运行 GDB 的所有功能,例如,gdbserver 无法跟踪 fork 后的子进程,而本地 GDB 可以。
- 如果 GDB 和 gdbserver 来自不同版本的 GDB,或者虽然版本相同但配置不同,可能会出现奇怪的问题。理想情况下,应该使用你喜欢的构建工具从相同的源代码构建它们。

调试符号会显著增加可执行文件的大小,有时会增加 10 倍。可以使用交叉工具链中 binutils 包的 strip 工具在不重新编译所有代码的情况下移除调试符号。可以使用以下开关控制 strip 级别:
- –strip - all:移除所有符号(默认)。
- –strip - unneeded:移除重定位处理不需要的符号。
- –strip - debug:仅移除调试符号。

需要注意的是,对于应用程序和共享库,使用 –strip - all(默认)即可,但对于内核模块,使用 –strip - all 会导致模块无法加载,应使用 –strip - unneeded。

6. 为远程调试设置 Yocto 项目

使用 Yocto 项目进行远程调试应用程序时,需要完成两件事:
- 将 gdbserver 添加到目标镜像中。可以通过在 conf/local.conf 中添加以下内容来显式添加该包:

IMAGE_INSTALL_append = " gdbserver"

如果没有串口控制台,还需要添加 SSH 守护进程,以便在目标设备上启动 gdbserver:

EXTRA_IMAGE_FEATURES ?= "ssh - server - openssh"

或者,你可以将 tools - debug 添加到 EXTRA_IMAGE_FEATURES 中,这将向目标镜像中添加 gdbserver、本地 gdb 和 strace:

EXTRA_IMAGE_FEATURES ?= "tools - debug ssh - server - openssh"
  • 创建一个包含 GDB 并具有要调试的可执行文件的调试符号的 SDK。只需按照相关说明构建 SDK 即可:
$ bitbake -c populate_sdk <image>

SDK 包含 GDB 副本、目标设备的 sysroot(包含目标镜像中所有程序和库的调试符号)以及可执行文件的源代码。例如,为树莓派 4 构建的 SDK 安装在 /opt/poky/3.1.5/,目标设备的 sysroot 位于 /opt/poky/3.1.5/sysroots/aarch64 - poky - linux/。程序位于 /bin/、/sbin/、/usr/bin/ 和 /usr/sbin/ 中,库位于 /lib/ 和 /usr/lib/ 中。每个目录下都有一个名为 .debug/ 的子目录,包含每个程序和库的符号。GDB 在搜索符号信息时会自动查找 .debug/ 目录。可执行文件的源代码存储在 /usr/src/debug/ 中。

7. 为远程调试设置 Buildroot

Buildroot 不区分构建环境和应用程序开发环境,没有 SDK。假设你使用 Buildroot 内部工具链,需要启用以下选项来为主机构建交叉 GDB 并为目标设备构建 gdbserver:
- 在 Toolchain | Build cross gdb for the host 中启用 BR2_PACKAGE_HOST_GDB。
- 在 Target packages | Debugging, profiling and benchmark | gdb 中启用 BR2_PACKAGE_GDB。
- 在 Target packages | Debugging, profiling and benchmark | gdbserver 中启用 BR2_PACKAGE_GDB_SERVER。

此外,还需要启用 BR2_ENABLE_DEBUG 来构建带有调试符号的可执行文件,这将在 output/host/usr/ /sysroot 中创建带有调试符号的库。

8. 开始调试

现在你已经在目标设备上安装了 gdbserver,并在主机上安装了交叉 GDB,可以开始调试会话了。

8.1 连接 GDB 和 gdbserver

GDB 和 gdbserver 之间的连接可以通过网络或串口接口实现。

网络连接
在目标设备上,启动 gdbserver 并指定要监听的 TCP 端口号,可选地指定允许连接的 IP 地址。在大多数情况下,你可能不关心哪个 IP 地址连接,因此只需提供端口号即可。例如,让 gdbserver 在端口 10000 上等待任何主机的连接:

# gdbserver :10000 ./hello - world
Process hello - world created; pid = 103
Listening on port 10000

在主机上,启动交叉工具链中的 GDB 副本,并指向未剥离符号的程序副本,以便 GDB 加载符号表:

$ aarch64 - poky - linux - gdb hello - world

在 GDB 中,使用 target remote 命令连接到 gdbserver,指定目标设备的 IP 地址或主机名以及它正在等待的端口:

(gdb) target remote 192.168.1.101:10000

当 gdbserver 看到来自主机的连接时,会输出以下信息:

Remote debugging from host 192.168.1.1

串口连接
在目标设备上,告诉 gdbserver 使用哪个串口:

# gdbserver /dev/ttyO0 ./hello - world

你可能需要事先使用 stty(1) 或类似程序配置端口波特率,例如:

# stty -F /dev/ttyO0 115200

在主机上,使用 target remote 命令加上串口设备连接到 gdbserver。大多数情况下,需要先使用 GDB 命令 set serial baud 设置主机串口的波特率:

(gdb) set serial baud 115200
(gdb) target remote /dev/ttyUSB0
8.2 设置 sysroot

GDB 需要知道在哪里查找要调试的程序和共享库的调试信息和源代码。本地调试时,路径是已知的且内置于 GDB 中,但使用交叉工具链时,GDB 无法猜测目标文件系统的根目录,因此需要提供这些信息。

如果你使用 Yocto 项目 SDK 构建应用程序,可以在 GDB 中设置 sysroot 如下:

(gdb) set sysroot /opt/poky/3.1.5/sysroots/aarch64 - poky - linux

如果你使用 Buildroot,sysroot 位于 output/host/usr/ /sysroot,并且在 output/staging 中有一个符号链接指向它。因此,对于 Buildroot,可在 GDB 中设置 sysroot 如下:

(gdb) set sysroot /home/chris/buildroot/output/staging

GDB 还需要找到要调试的文件的源代码。可以使用 show directories 命令查看 GDB 的源代码搜索路径:

(gdb) show directories
Source directories searched: $cdir:$cwd

默认情况下,$cwd 是主机上运行的 GDB 实例的当前工作目录,$cdir 是源代码编译的目录,该信息编码在目标文件中,带有标签 DW_AT_comp_dir。可以使用 objdump –dwarf 查看这些标签,例如:

$ aarch64 - poky - linux - objdump --dwarf ./helloworld | grep DW_AT_comp_dir

在大多数情况下,默认的 $cdir 和 $cwd 就足够了,但如果在编译和调试之间移动了目录,可能会出现问题。例如,使用 Yocto 项目 SDK 编译的程序,查看 DW_AT_comp_dir 标签时可能会发现多个对 /usr/src/debug/glibc/2.31 - r0/git 目录的引用,实际上它位于 SDK 的 sysroot 中,完整路径是 /opt/poky/3.1.5/sysroots/aarch64 - poky - linux/usr/src/debug/glibc/2.31 - r0/git。GDB 可以使用 substitute - path 命令处理整个目录树的移动问题。因此,在使用 Yocto 项目 SDK 调试时,需要使用以下命令:

(gdb) set sysroot /opt/poky/3.1.5/sysroots/aarch64 - poky - linux
(gdb) set substitute path /usr/src/debug/opt/poky/3.1.5/sysroots/aarch64 - poky - linux/usr/src/debug

如果你有存储在 sysroot 之外的额外共享库,可以使用 set solib - search - path 命令,该命令可以包含一个用冒号分隔的目录列表,用于搜索共享库。GDB 只有在 sysroot 中找不到二进制文件时才会搜索 solib - search - path。

还可以使用 directory 命令告诉 GDB 在哪里查找源代码,例如:

(gdb) directory /home/chris/MELP/src/lib_mylib
Source directories searched: /home/chris/MELP/src/lib_mylib:$cdir:$cwd

通过这种方式添加的路径具有更高的优先级,因为它们会在搜索 sysroot 或 solib - search - path 之前被搜索。

8.3 GDB 命令文件

每次运行 GDB 时,有些操作是必须要做的,例如设置 sysroot。将这些命令放在一个命令文件中,每次启动 GDB 时运行它们会很方便。GDB 会依次从 $HOME/.gdbinit、当前目录下的 .gdbinit 以及命令行中使用 -x 参数指定的文件中读取命令。但由于安全原因,最新版本的 GDB 会拒绝加载当前目录下的 .gdbinit。可以在 $HOME/.gdbinit 中添加以下内容来覆盖此行为:

set auto - load safe - path /

或者,如果你不想全局启用自动加载,可以指定特定的目录:

add - auto - load safe - path /home/chris/myprog

个人建议使用 -x 参数指向命令文件,这样可以明确文件的位置,避免遗忘。

Buildroot 会在 output/staging/usr/share/buildroot/gdbinit 中创建一个包含正确 sysroot 命令的 GDB 命令文件,例如:

set sysroot /home/chris/buildroot/output/host/usr/aarch64 - buildroot - linux - gnu/sysroot
8.4 GDB 常用命令概述

GDB 有许多命令,在线手册和相关资源中有详细描述。为了帮助你尽快上手,以下是一些最常用命令的列表:
| 分类 | 命令 | 说明 |
| ---- | ---- | ---- |
| 断点管理 | break | 设置断点 |
| | delete | 删除断点 |
| | disable | 禁用断点 |
| | enable | 启用断点 |
| 程序执行控制 | run | 开始执行程序 |
| | next | 单步执行下一行代码(不进入函数) |
| | step | 单步执行下一行代码(进入函数) |
| | continue | 继续执行程序直到下一个断点 |
| 获取调试信息 | info breakpoints | 显示断点信息 |
| | info registers | 显示寄存器信息 |
| | backtrace | 显示函数调用栈回溯信息 |

在开始单步执行程序之前,需要设置一个初始断点。

8.5 运行到断点

gdbserver 将程序加载到内存中,并在第一条指令处设置断点,然后等待 GDB 的连接。连接建立后,进入调试会话。但如果你立即尝试单步执行,会收到以下消息:

Cannot find bounds of current function

这是因为程序在汇编代码中暂停,汇编代码为 C/C++ 程序创建运行时环境。C/C++ 代码的第一行是 main() 函数。如果你想在 main() 函数处停止,可以在那里设置断点,然后使用 continue 命令(缩写为 c)让 gdbserver 从程序开始处的断点继续执行,直到 main() 函数处停止:

(gdb) break main
Breakpoint 1, main (argc = 1, argv = 0xbefffe24) at helloworld.c:8 
printf("Hello, world!\n");
(gdb) c

此时,你可能会看到以下信息:

Reading /lib/ld - linux.so.3 from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.

在较旧版本的 GDB 中,可能会看到:

warning: Could not load shared library symbols for 2 libraries, e.g. /lib/libc.so.6.

这两种情况都表明你忘记设置 sysroot 了,需要回顾前面关于 sysroot 的部分。

这与本地启动程序不同,本地启动只需输入 run 命令。在远程调试会话中尝试输入 run 命令时,你可能会看到消息提示远程目标不支持 run 命令,或者在较旧版本的 GDB 中,程序会无响应。

9. 使用 Python 扩展 GDB

可以将完整的 Python 解释器嵌入到 GDB 中以扩展其功能。在构建 GDB 之前,使用 –with - python 选项进行配置即可实现。GDB 有一个 API,它将其内部状态的大部分作为 Python 对象暴露出来。通过这个 API,我们可以用 Python 脚本定义自己的自定义 GDB 命令,这些额外的命令可能包括有用的调试辅助工具,如跟踪点和漂亮的打印机,这些在 GDB 中并非内置功能。

9.1 构建支持 Python 的 GDB

在已经完成远程调试的 Buildroot 设置基础上,要启用 GDB 对 Python 的支持,还需要一些额外的步骤。目前,Buildroot 仅支持在 GDB 中嵌入 Python 2.7,虽然不太理想,但总比完全不支持 Python 要好。由于 Buildroot 生成的工具链缺少一些必要的线程支持,因此不能使用它来构建支持 Python 的 GDB。

要为主机构建支持 Python 的交叉 GDB,请按照以下步骤操作:
1. 导航到安装 Buildroot 的目录:

$ cd buildroot
  1. 复制要构建镜像的开发板的配置文件:
$ cd configs
$ cp raspberrypi4_64_defconfig rpi4_64_gdb_defconfig
$ cd ..
  1. 清理输出目录中的先前构建工件:
$ make clean
  1. 激活配置文件:
$ make rpi4_64_gdb_defconfig
  1. 开始自定义镜像:
$ make menuconfig
  1. 导航到 Toolchain | Toolchain type | External toolchain,选择使用外部工具链:
在 menuconfig 中选择 External toolchain
  1. 退出 External toolchain 菜单,打开 Toolchain 子菜单,选择一个已知可用的工具链,如 Linaro AArch64 2018.05 作为外部工具链:
在 Toolchain 子菜单中选择 Linaro AArch64 2018.05
  1. 在 Toolchain 页面中选择 Build cross gdb for the host,并启用 TUI 支持和 Python 支持:
在 Toolchain 页面勾选 Build cross gdb for the host,同时启用 TUI 和 Python 支持
  1. 深入到 Toolchain 页面的 GDB debugger Version 子菜单,选择 Buildroot 中可用的最新版本的 GDB:
在 GDB debugger Version 子菜单中选择最新版本的 GDB
  1. 退出 Toolchain 页面,深入到 Build options,选择 build packages with debugging symbols:
在 Build options 中选择 build packages with debugging symbols
  1. 退出 Build options 页面,深入到 System Configuration,选择 Enable root login with password,打开 Root password 并在文本字段中输入非空密码:
在 System Configuration 中选择 Enable root login with password,设置密码
  1. 退出 System Configuration 页面,深入到 Target packages | Debugging, profiling and benchmark,选择 gdb 包以将 gdbserver 添加到目标镜像中:
在 Target packages 的 Debugging 部分选择 gdb 包
  1. 退出 Debugging, profiling and benchmark 菜单,深入到 Target packages | Networking applications,选择 dropbear 包以启用对目标设备的 scp 和 ssh 访问。注意,dropbear 在没有密码的情况下不允许 root 用户进行 scp 和 ssh 访问:
在 Networking applications 中选择 dropbear 包
  1. 在 Target packages | Miscellaneous 中添加 haveged 熵守护进程,以便在启动时更快地启用 SSH:
在 Miscellaneous 中选择 haveged
  1. 向镜像中添加另一个包,以便有东西可以调试。选择 bsdiff 二进制补丁/差异工具,它用 C 语言编写,可在 Target packages | Development tools 中找到:
在 Development tools 中选择 bsdiff
  1. 保存更改并退出 Buildroot 的 menuconfig:
在 menuconfig 中保存并退出
  1. 保存配置文件的更改:
$ make savedefconfig
  1. 为目标设备构建镜像:
$ make

如果想跳过上述 menuconfig 步骤,可以在本章的代码存档中找到适用于树莓派 4 的现成 rpi4_64_gdb_defconfig 文件。将该文件从 MELP/Chapter19/buildroot/configs/ 复制到你的 buildroot/configs 目录,然后运行 make 即可。

构建完成后,在 output/images/ 中应该会有一个可引导的 sdcard.img 文件,你可以使用 Etcher 将其写入 MicroSD 卡。将该 MicroSD 卡插入目标设备并启动它。使用以太网线将目标设备连接到本地网络,使用 arp - scan 查找其 IP 地址。以 root 用户身份通过 SSH 登录到设备,并输入在配置镜像时设置的密码。例如,在 rpi4_64_gdb_defconfig 镜像中设置的 root 密码为 temppwd。

9.2 使用 GDB 远程调试 bsdiff

现在可以使用 GDB 远程调试 bsdiff 了,步骤如下:
1. 在目标设备上导航到 /usr/bin 目录:

# cd /usr/bin
  1. 像之前调试 hello - world 一样,使用 gdbserver 启动 bsdiff:
# gdbserver :10000 ./bsdiff pcregrep pcretest out
Process ./bsdiff created; pid = 169
Listening on port 10000
  1. 在主机上,导航到 output/build/bsdiff - 4.3 目录,启动交叉工具链中的 GDB 副本,并指向未剥离符号的 bsdiff 程序副本,以便 GDB 加载符号表:
$ cd output/build/bsdiff - 4.3
$ ~/buildroot/output/host/bin/aarch64 - linux - gdb bsdiff
  1. 在 GDB 中设置 sysroot:
(gdb) set sysroot ~/buildroot/output/staging
  1. 使用 target remote 命令连接到 gdbserver,指定目标设备的 IP 地址或主机名以及它正在等待的端口:
(gdb) target remote 192.168.1.101:10000

通过以上步骤,你就可以使用 GDB 对 bsdiff 进行远程调试了。在实际调试过程中,你可以结合前面介绍的 GDB 常用命令,如设置断点、单步执行、查看变量值等,来定位和解决代码中的问题。同时,利用 Python 扩展 GDB 的功能,可以根据具体需求编写自定义的调试命令,提高调试效率。希望这些内容能帮助你更好地使用 GDB 进行调试工作。

使用 GDB 进行调试

10. 调试流程总结

为了更清晰地展示使用 GDB 进行调试的整个流程,下面给出一个 mermaid 格式的流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B{选择调试方式}:::decision
    B -->|本地调试| C(本地运行 GDB):::process
    B -->|远程调试| D(设置目标环境):::process
    D --> E(添加 gdbserver 到目标镜像):::process
    E --> F(创建包含 GDB 和调试符号的 SDK):::process
    F --> G(连接 GDB 和 gdbserver):::process
    G --> H(设置 sysroot):::process
    H --> I(设置初始断点):::process
    I --> J(运行到断点):::process
    J --> K(单步执行或继续执行):::process
    C --> K
    K --> L([结束]):::startend

这个流程图展示了使用 GDB 进行调试的主要步骤,无论是本地调试还是远程调试,都有清晰的流程指引。

11. 调试过程中的常见问题及解决方法

在使用 GDB 进行调试的过程中,可能会遇到一些常见问题,下面以表格的形式列出这些问题及相应的解决方法:
| 问题描述 | 可能原因 | 解决方法 |
| ---- | ---- | ---- |
| 单步执行时提示 “Cannot find bounds of current function” | 程序在汇编代码中暂停,汇编代码为 C/C++ 程序创建运行时环境 | 设置初始断点在 main() 函数处,使用 continue 命令继续执行到 main() 函数 |
| 提示 “Could not load shared library symbols” | 忘记设置 sysroot | 在 GDB 中使用 set sysroot 命令设置正确的 sysroot |
| GDB 的 run 命令无响应或提示不支持 | 远程调试时 run 命令可能无法按预期工作 | 使用 continue 命令代替 run 命令 |
| 栈帧指针问题导致无法生成回溯信息 | 在某些架构上,GCC 在较高的优化级别(-O2 及以上)下不会生成栈帧指针 | 使用 -fno - omit - frame - pointer 覆盖默认行为,或暂时移除手动优化以省略帧指针的代码 |

12. 高级调试技巧

除了前面介绍的基本调试方法,还有一些高级调试技巧可以帮助你更高效地进行调试。

12.1 条件断点

在某些情况下,你可能只希望在满足特定条件时才触发断点。可以使用条件断点来实现这一需求。例如,在 GDB 中设置一个条件断点,当变量 x 的值等于 10 时触发:

(gdb) break my_function if x == 10
12.2 观察点

观察点用于监视变量的变化。当变量的值发生改变时,程序会暂停执行。例如,监视变量 y 的变化:

(gdb) watch y
12.3 信号处理

在调试过程中,程序可能会收到各种信号,如 SIGSEGV(段错误)。可以使用 GDB 来处理这些信号,例如,在收到 SIGSEGV 信号时暂停程序:

(gdb) handle SIGSEGV stop
13. 总结

使用 GDB 进行调试是一项强大的技能,无论是在本地开发环境还是远程嵌入式开发环境中,都能帮助你快速定位和解决代码中的问题。通过本文的介绍,你了解了调试前的准备工作,包括编译代码时添加调试符号和选择合适的优化级别;掌握了使用 GDB 调试应用程序的两种方式,特别是远程调试的详细步骤,包括设置 Yocto 项目和 Buildroot 环境、连接 GDB 和 gdbserver、设置 sysroot 等;还学习了一些常用的 GDB 命令和高级调试技巧。

在实际应用中,你可以根据具体的调试需求选择合适的方法和技巧。同时,要注意调试过程中的一些常见问题,并根据本文提供的解决方法进行处理。希望这些内容能帮助你更好地使用 GDB 进行调试工作,提高开发效率和代码质量。

最后,再次强调调试符号的重要性,它是进行源代码级调试的基础。在编译代码时,合理选择调试符号的级别,可以在不影响程序性能的前提下,为调试提供足够的信息。同时,对于不同类型的文件(如应用程序、共享库和内核模块),要选择合适的 strip 级别来移除调试符号,以节省存储空间。

通过不断实践和积累经验,你将能够熟练掌握 GDB 的使用,成为一名高效的调试高手。

### 配置 VSCode 使用 C51 头文件 为了能够在 VSCode 中顺利使用 C51 编程并获得良好的开发体验,包括代码提示等功能,需要进行一系列配置。这不仅提高了编码效率也减少了错误的发生。 #### 安装必要的扩展 首先,在 VSCode 中安装 `C/C++` 扩展插件,这是由 Microsoft 提供的支持 C 和 C++ 的官方工具集[^2]。此扩展提供了 IntelliSense 功能,即智能感知特性,可以实现自动补全、参数信息显示以及定义跳转等重要功能。 #### 设置工作区环境 创建一个新的项目文件夹作为工作空间,并在此基础上建立 `.vscode` 文件夹用来放置配置文件。主要涉及两个重要的 JSON 文件: - **c_cpp_properties.json**: 此文件用于指定编译器路径及预处理器宏定义等内容。 ```json { "configurations": [ { "name": "Win32", "includePath": [ "${workspaceFolder}/**", "path/to/c51/include" ], "defines": [], "compilerPath": "gcc.exe", // 如果适用的话; 对于 Keil 可能不需要这项 "intelliSenseMode": "gcc-x64", "browse": { "path": ["${workspaceFolder}", "path/to/c51/include"], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" } } ], "version": 4 } ``` - **tasks.json**: 这里定义了构建任务,比如调用外部命令来进行编译链接等工作流自动化处理。 ```json { "version": "2.0.0", "tasks": [ { "label": "build_c51_project", "type": "shell", "command": "keil_path\\UV4.exe", "args": [ "-b", "${workspaceFolder}\\project.uvprojx" ] } ] } ``` 对于 C51 特定的头文件位置 `"path/to/c51/include"` 应替换为实际存放这些头文件的位置;同样地,“keil_path”也需要指向本地已安装好的 Keil 工具链所在目录。 #### 调试配置 最后一步是设置调试选项以便可以在 IDE 内部启动仿真器或连接硬件设备运行程序。编辑 launch.json 添加如下内容: ```json { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/Debug/project.axf", "miDebuggerPath": "/usr/bin/gdb-multiarch", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "logging": { "trace": true, "traceResponse": true, "engineLogging": true }, "preLaunchTask": "build_c51_project", "internalConsoleOptions": "openOnSessionStart" } ] } ``` 需要注意的是上述例子假设使用 GDB 作为调试器,而对于特定型号单片机可能还需要额外安装相应的 JTAG 或 SWD 接口驱动程序来配合在线调试过程[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值