news 2026/7/8 1:08:43

嵌入式都能用上的gdb调试进阶--下

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式都能用上的gdb调试进阶--下


正文


大家好,我是bug菌~

很久很久以前在公众号中写了一篇《嵌入式都能用上的gdb调试进阶--上》文章,然而最近用gdb又有了一些心得和体会,于是把下篇给大家补齐了。

毕竟gdb实在是太强大了,在嵌入式中裸机、RTOS、Linux 内核 / 应用,gdb 都能适配:裸机调试(配合硬件调试器)、RTOS(比如 FreeRTOS 有 gdb 适配的插件)、Linux 应用 / 内核调试,这点比很多专用工具灵活,而且免费~

1

条件断点技巧

条件断点是 GDB 的强大功能之一,它允许断点仅在特定条件满足时触发,大大提高了调试效率。

1.1 条件断点设置

设置条件断点的基本语法:

(gdb) break <location> if <condition> (gdb) b sum.c:10 if i == 5

条件断点的特点:

  • 只有当条件表达式的值为真(非零)时,断点才会触发

  • 条件表达式可以是任何有效的 C/C++ 表达式

  • 可以使用程序中的变量、函数调用等

1.2 为现有断点添加条件

如果已经设置了断点,可以使用condition命令为其添加条件:

(gdb) condition <breakpoint number> <expression> (gdb) condition 1 i > 10

这对于调试循环中的特定情况非常有用。例如,在一个大循环中,我们可能只关心当循环变量达到某个特定值时的程序状态。

1.3 忽略断点次数

ignore命令允许设置断点在触发前忽略指定的次数:

(gdb) ignore <breakpoint number> <count> (gdb) ignore 1 5

这个功能在以下场景特别有用:

  • 调试循环时,想跳过前几次迭代

  • 断点在循环中被多次触发,但只关心特定迭代

  • 提高调试效率,避免在不关心的断点处频繁停止

1.4 断点命令列表

GDB 允许为断点定义一系列命令,当断点触发时自动执行这些命令:

(gdb) break <location> (gdb) commands <breakpoint number> commands > print variable > continue end

例如,我们可以创建一个断点,当触发时自动打印某个变量的值并继续执行:

(gdb) b select_sort (gdb) commands 1 commands > p arr[min_idx] > c end

这样,每次断点触发时,都会自动打印arr[min_idx]的值并继续执行,无需手动输入命令。

2

观察点Watchpoint的使用

观察点是调试内存相关问题的利器,它可以监控变量或表达式的值变化。

2.1 观察点类型

GDB 支持三种类型的观察点:

写观察点(Watchpoint)

(gdb) watch <expression>

当表达式被写入(值改变)时触发断点。

读观察点(Read Watchpoint)

(gdb) rwatch <expression>

当表达式被读取时触发断点。

访问观察点(Access Watchpoint)

(gdb) awatch <expression>

当表达式被读取或写入时触发断点。

2.2 观察点使用示例

假设我们有一个程序存在数组越界访问的问题:

int buffer[10]; for (int i = 0; i <= 10; i++) { buffer[i] = i; // 这里会访问buffer[10],越界 }

使用观察点调试:

在 buffer 数组的第 11 个元素(越界位置)设置观察点:

(gdb) watch buffer[10]
  1. 运行程序,当 buffer[10] 被访问时,程序会停在相应位置。

  2. 使用info watchpoints查看所有观察点的状态。

2.3 观察点的限制

使用观察点时需要注意:

  • 硬件限制:大多数系统的硬件观察点数量有限(通常是 4 个)

  • 性能影响:软件观察点会显著降低程序运行速度,因为需要单步执行并每次检查变量值

  • 数据类型限制:某些数据类型(如 double)可能无法设置硬件观察点,因为其宽度超过了硬件支持

3

多线程调试

多线程程序的调试比单线程程序复杂得多,GDB 提供了专门的多线程调试功能。

3.1 线程相关命令

查看所有线程

(gdb) info threads

显示所有线程的信息,包括线程 ID、状态、当前栈帧等。

切换线程

(gdb) thread <thread-id>

切换到指定 ID 的线程进行调试。

对所有线程执行命令

(gdb) thread apply all <command>

对所有线程执行指定的 GDB 命令,如查看所有线程的调用栈:

(gdb) thread apply all backtrace

线程特定断点

(gdb) break <location> thread <thread-id>

在特定线程中设置断点。

3.2 线程调度控制

在调试多线程程序时,控制线程调度非常重要:

(gdb) set scheduler-locking <mode>

调度锁模式包括:

  • off:无锁,所有线程自由调度(默认)

  • on:只有当前线程运行,其他线程暂停

  • step:单步执行时自动锁定调度器

例如,在调试某个线程的特定逻辑时,可以使用:

(gdb) set scheduler-locking on

这样,当使用stepnext命令时,只有当前线程会执行,避免其他线程的干扰。

4

信号处理调试

信号是 UNIX 系统中进程间通信的重要机制,GDB 提供了强大的信号处理调试功能。

4.1 查看信号处理

查看信号信息

(gdb) info signals

显示所有信号的当前处理方式。

查看特定信号

(gdb) info signal SIGINT

显示指定信号的处理方式。

4.2 捕获信号

GDB 可以捕获特定信号,当信号发生时暂停程序:

(gdb) handle <signal> <action>

常用的 action 包括:

  • stop:接收到信号时暂停程序

  • noprint:不打印信号信息

  • nostop:不暂停程序,但仍可捕获信号

  • pass/ nopass:决定是否将信号传递给程序

例如,要捕获 SIGSEGV(段错误)信号:

(gdb) handle SIGSEGV stop
4.3 生成信号

在调试过程中,可以主动向程序发送信号:

(gdb) signal <signal>

例如,向程序发送 SIGINT(中断)信号:

(gdb) signal SIGINT

5

远程调试

远程调试是 GDB 的强大功能之一,允许在本地调试运行在另一台机器或嵌入式设备上的程序。

5.1 远程调试架构

远程调试使用gdbserver作为中间代理:

  • 目标机:运行 gdbserver 和被调试程序

  • 主机:运行 GDB,通过网络连接到 gdbserver

5.2 启动 gdbserver

在目标机上启动 gdbserver:

gdbserver <host:port> <program> [arguments]

例如,监听本地端口 1234:

gdbserver :1234 ./my_program

或者通过串口连接:

gdbserver /dev/ttyS0 ./my_program
5.3 连接到远程目标

在主机上使用 GDB 连接到远程目标:

(gdb) target remote <host:port>

例如:

(gdb) target remote 192.168.1.100:1234

连接成功后,就可以使用标准的 GDB 命令进行调试,就像在本地一样。

5.4 交叉调试

在嵌入式开发中,经常需要进行交叉调试(主机和目标机架构不同):

编译目标程序:

arm-linux-gnueabihf-gcc -g -o my_program my_program.c

在目标机上启动 gdbserver:

gdbserver :1234 ./my_program

在主机上使用对应的交叉调试器:

arm-linux-gnueabihf-gdb (gdb) target remote 192.168.1.100:1234

6

内存调试技巧

内存问题是程序中最难调试的问题之一,GDB 提供了一些内存调试功能,但通常需要结合其他工具(如 Valgrind)使用。

6.1 内存查看技巧

查看内存内容

(gdb) x/20xb buffer

以字节形式查看数组或缓冲区的内容。

查看动态分配的内存

(gdb) p *(int *)0x600850

通过地址查看动态分配的内存内容。

6.2 内存泄漏检测

虽然 GDB 本身不直接支持内存泄漏检测,但可以通过以下方式辅助调试:

  1. 使用内存分配钩子函数

  2. 跟踪 malloc/free 调用

  3. 分析内存分配模式

更有效的方法是使用专门的内存调试工具如 Valgrind:

valgrind --tool=memcheck --leak-check=full ./program
6.3 缓冲区溢出调试

调试缓冲区溢出的步骤:

定位内存越界位置:

(gdb) watch buffer[10]

分析调用栈:

(gdb) bt

查看内存状态:

(gdb) x/20xw $esp

通过这些技巧,可以定位导致缓冲区溢出的代码位置。

最后

好了,今天就跟大家分享这么多了,如果你觉得有所收获,一定记得点个~

唯一、永久、免费分享嵌入式技术知识平台~

推荐专辑 点击蓝色字体即可跳转

MCU进阶专辑

嵌入式C语言进阶专辑

“bug说”专辑

专辑|Linux应用程序编程大全

专辑|学点网络知识

专辑|手撕C语言

专辑|手撕C++语言

专辑|经验分享

专辑|电能控制技术

专辑 | 从单片机到Linux

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/7 1:50:22

Gradle8.0中Transform的替代方案

一、路由插件设计思路 Transform在Gradle8.0中被移除了&#xff0c;从官方文档《Android Gradle 插件 API 更新》中可知并没有提供直接的替代Api&#xff0c;而是区分不同场景提供了几种对应的解决方案。在上篇文章《基于Gradle8.0的插件开发》中&#xff0c;基于Gradle8.0最新…

作者头像 李华
网站建设 2026/7/6 14:15:17

47、运维脚本与消息推送:从菜单到弹窗的全面指南

运维脚本与消息推送:从菜单到弹窗的全面指南 1. 运维菜单脚本的创建与使用 1.1 脚本初始化 在编写运维菜单脚本时,首先要定义文件和变量。例如,定义 BINDIR 目录,它是从菜单调用的所有 shell 脚本和程序的位置;定义密码服务器的主机名,可使用单一服务器保存主密码列…

作者头像 李华
网站建设 2026/7/7 9:54:45

50、脚本与函数的综合应用指南

脚本与函数的综合应用指南 1. 脚本介绍 1.1 文件处理脚本 mk_unique_filename.ksh :用于创建唯一的文件名。这在处理大量文件时,能有效避免文件名冲突,确保文件管理的有序性。 operations_menu.ksh :为运维人员提供一个脚本菜单,方便他们快速执行各种操作,提高工作…

作者头像 李华
网站建设 2026/7/3 7:55:21

XDM浏览器插件终极指南:5分钟快速上手高速下载神器

想要告别龟速下载和繁琐的视频下载流程吗&#xff1f;XDM&#xff08;Xtreme Download Manager&#xff09;通过强大的浏览器插件集成&#xff0c;将彻底改变你的下载体验。本文将带你从零开始&#xff0c;快速掌握XDM插件的安装、配置和高效使用方法。 【免费下载链接】xdm Po…

作者头像 李华
网站建设 2026/7/6 6:18:54

如何配置一个!P地址和子网掩码?

配置IP地址和子网掩码的基础知识IP地址是网络中设备的唯一标识符&#xff0c;用于设备之间的通信。子网掩码用于划分IP地址的网络部分和主机部分。配置IP地址和子网掩码是网络管理的基本任务之一。IPv4地址由32位二进制数组成&#xff0c;通常表示为四个十进制数&#xff0c;每…

作者头像 李华
网站建设 2026/7/7 18:24:07

EmotiVoice项目依赖项精简计划:降低部署复杂度

EmotiVoice项目依赖项精简计划&#xff1a;降低部署复杂度 在虚拟主播直播带货、AI有声书自动生成、游戏NPC情绪化对话层出不穷的今天&#xff0c;一个共通的技术瓶颈逐渐浮现——如何让高性能语音合成系统真正“跑得起来”&#xff1f;尤其是像EmotiVoice这样具备零样本克隆与…

作者头像 李华