一、概述

本指南旨在为开发者提供一份全面而实用的 GNU Debugger (GDB) 操作手册。无论您是初学者还是希望深化嵌入式调试技能的开发者,都能从中获益。

1、GDB 是什么

GDB (GNU Debugger) 是 GNU 项目提供的标准化调试工具。它功能强大,能与 GCC 编译器紧密协作,帮助开发者深入理解和修复程序中的问题。

2、GDB 的核心能力

GDB 主要提供以下四项核心能力,赋予您完全控制程序执行的能力:

  1. 控制执行:按照您的设定启动并运行程序,包括传递命令行参数和设置环境变量。
  2. 设置断点:在代码的任意位置(如特定行号、函数入口)设置断点,或创建在满足特定条件时才触发的条件断点。
  3. 检查状态:当程序暂停时,您可以检查变量的当前值、查看内存内容、回溯函数调用栈以及检查寄存器状态。
  4. 动态修改:在运行时动态改变变量的值,或修改内存内容,以测试不同的代码路径和修复假设。

二、准备工作

在开始调试前,必须确保您的程序包含了 GDB 所需的调试信息。

1、编译时加入调试信息

使用 GCC 编译代码时,必须添加 -g 选项。为了获得最详尽的调试信息(包括宏定义),推荐使用 -g3:

# 示例编译命令
gcc -g3 -o my_program my_program.c

 

在 openvela 的构建系统中,您可以通过 Kconfig 开启调试选项,系统会自动为编译器添加合适的标志。

2、安装 GDB

对于跨平台嵌入式开发(例如,在 x86 主机上调试 ARM 目标板),您需要一个支持多架构的 GDB 版本。

# 在 Ubuntu/Debian 系统上安装 gdb-multiarch
sudo apt install gdb-multiarch

 

对于特定的 ARM 目标,您也可以使用对应的工具链中的 GDB,例如 arm-none-eabi-gdb。

三、GDB 核心命令参考

本章节将命令按功能分组,帮助您快速掌握 GDB 的核心操作。

1、启动与退出

命令 描述
gdb ./nuttx 加载 nuttx 可执行文件并进入 GDB 交互界面。
attach <PID> 附加到已在后台运行的进程。
您需要先用 ps -ef | grep nuttx 找到进程 ID (PID)。
target remote <IP>:<Port> 在 GDB 内执行,用于连接到远程目标机上运行的 GDB Server。
例如,target remote :1234 连接到本地的 1234 端口。
gdb -q ./nuttx 启动 GDB 但不显示冗长的版本和版权信息。
quit q,退出 GDB 会话。
kill 终止被调试的程序。

2、控制程序执行

命令 描述
run r,启动或重新启动程序。
程序将一直运行,直到遇到断点、观察点、异常或手动中断。
continue c,从当前暂停位置继续执行,直到下一个断点或程序结束。
next n,单步执行 (Step Over)
执行当前行代码。如果当前行是函数调用,它会执行整个函数,然后停在下一行。
step s,单步进入 (Step In)
如果当前行是函数调用,它会进入该函数内部并停在函数的第一行。
stepi si,单条指令执行
用于汇编级别的单步调试。
finish fin,完成当前函数
执行当前函数剩余部分,然后停在函数返回后的下一条语句。
(回车) 重复执行上一条命令(对于 n、s 等命令非常有用)。

3、管理断点与观察点

断点使程序在特定位置暂停,观察点则在变量或内存地址被访问时暂停。

命令 描述
break <location> b,在指定位置设置断点。
location 可以是行号 (b main.c:25)、函数名 (b my_function) 或地址 (b *0xdeadbeef)。
break ... if <condition> 设置条件断点
仅当 condition 为真时,断点才会被触发。
例如:b 15 if i == 10。
info breakpoints info b,显示所有断点及其状态(编号、是否启用、命中次数等)。
delete <num> del,删除指定编号的断点。若不带编号,则删除所有断点。
disable <num> dis,禁用指定编号的断点,但不会删除它。
enable <num> en,重新启用一个被禁用的断点。
watch <expr> 设置观察点 (Write Watchpoint)
当表达式 expr(通常是一个变量名或内存地址)的值被写入时,程序暂停。
rwatch <expr> 设置读观察点 (Read Watchpoint)
当 expr 被读取时,程序暂停。
awatch <expr> 设置访问观察点 (Access Watchpoint)
当 expr 被读取或写入时,程序暂停。

4、检查程序状态

当程序暂停时,这些命令帮助您探查问题所在。

命令 描述
print <expr> p,打印变量或表达式的值。 例如 p my_var 或 p *my_ptr。
x/<nfu> <addr> 检查内存 (Examine)
n 是数量
f 是格式 (x=十六进制, d=十进制, c=字符, s=字符串),
u 是单位 (b=字节, h=半字, w=字, g=双字)
例如,x/16xw 0x1000 表示从地址 0x1000 开始显示 16 个字的十六进制内容。
backtrace bt,显示当前的函数调用栈,帮助您追溯代码是如何执行到当前位置的。
frame <num> f,切换到指定编号的栈帧。 结合 bt 使用,可以查看调用栈中任意层级的局部变量和参数。
info locals 显示当前栈帧中的所有局部变量
info args 显示当前栈帧中的所有函数参数
info registers 显示所有 CPU 寄存器的当前值。
info threads 在多线程程序(如 openvela)中,显示所有线程及其 ID。
thread <id> 切换到指定 ID 的线程上下文。
ptype <expr> 显示变量或类型的数据结构定义。 例如
ptype struct my_struct。
ptype /o 命令可以打印出指定类型的完整内存布局,清晰地展示每个成员的偏移量(offset)和大小(size)。
set var <name>=<value> 在运行时修改变量的值。 例如 set var i = 10。不引起歧义时,可使用 set i = 10。

5、与源码和汇编交互

命令 描述
list l,显示当前位置附近的源代码。
layout src 进入文本用户界面 (TUI) 模式,分屏显示源代码
layout asm 分屏显示汇编代码
layout split 分屏同时显示源代码和汇编代码
在此模式下,可使用 focus 命令或快捷键 Ctrl + X + O 在不同窗口间切换焦点。
Ctrl + X, A 退出 TUI 模式,返回标准 GDB 命令行界面。
disassemble <func> disas,反汇编指定的函数。
/m 参数表示混合显示源代码与反汇编代码。

6、GDB 环境与 Shell 交互

命令 描述
help <command> 显示指定 GDB 命令的帮助信息。
pipe <cmd> | <shell_cmd> 将 GDB 命令 cmd 的输出通过管道传递给 Shell 命令。
例如 pipe bt | less。
shell <shell_cmd> 在 GDB 内部执行一个 Shell 命令。
例如 shell ls -l。

四、openvela 典型调试场景

本节将理论应用于实践,展示如何使用 GDB 解决在 openvela(尤其是 sim 和硬件)开发中遇到的具体问题。

场景一:分析程序崩溃(Hard Fault, Crash)

问题描述

程序在 sim 或开发板上运行时突然崩溃,例如出现 ASan 错误、段错误或硬件异常(Data Abort, Prefetch Abort)。

调试策略

  1. 复现问题:使用 gdb ./nuttx 启动调试会话,然后输入 r 运行程序直至其崩溃。
  2. 定位崩溃点:程序崩溃后 GDB 会自动暂停。使用 bt 命令查看调用栈,最顶层的栈帧通常就是导致崩溃的直接原因。
  3. 分析上下文:使用 frame <num> 切换到可疑的栈帧,然后用 p <var> 和 info locals 检查当时的变量值,分析崩溃原因。
  4. 硬件异常分析:对于硬件异常,info registers 查看 PC (Program Counter), LR (Link Register) 等寄存器的值至关重要。使用 disassemble /m <PC_value> 可以查看崩溃时正在执行的汇编指令及其对应的源代码行。

场景二:程序卡死或死锁

问题描述

程序运行后终端无响应,CPU 占用率高,疑似进入死循环或死锁。

调试策略

  1. 中断程序

    • 若程序在前台运行,直接在 GDB 中按 Ctrl + C。
    • 若程序在后台运行,先用 ps 找到 PID,然后执行 sudo gdb attach <PID>。附加成功后,程序会自动暂停。
    • 对于 sim 环境,也可在新终端执行 pkill -SIGSTOP nuttx 来暂停进程。
  2. 检查所有线程:输入 info threads 查看所有线程的状态,是否有线程处于异常状态或都在等待某个资源。

  3. 分析每个线程:使用 thread <id> 逐一切换到每个线程,然后用 bt 查看其调用栈,判断它们正在执行什么任务。这通常能快速定位到死循环或死锁的位置。

场景三:追踪变量的意外修改

问题描述

一个全局变量的值在某个时刻被错误地修改,但代码中有多处可能修改它的地方。

调试策略

  1. 设置观察点:启动 GDB 后,使用 watch my_global_variable 或 watch *<address_of_variable> 为该变量设置一个写观察点。
  2. 运行并等待:输入 run 或者 continue 启动或继续运行程序。当该变量的值被修改时,程序会立即暂停。
  3. 定位修改者:GDB 会报告变量的新旧值,并停在修改该变量的代码行。使用 bt 查看调用栈,即可找到修改变量的代码。

场景四:分析被编译器优化的变量

问题描述

在开启优化(如 -O2, -O3)后,尝试用 print 查看某个局部变量时,GDB 提示 <optimized out>。

原因分析

编译器为了效率,可能已将该变量优化掉,或将其值存放在了 CPU 寄存器中,而不是内存(栈)中。

调试策略

  1. 查看变量地址信息:使用 info address your_var。
  2. 解读输出:GDB 会告诉您该变量当前位于何处。

    • 如果它显示 "in register r5",意味着变量的值就在 r5 寄存器中,您可以用 p $r5 来查看。
    • 如果它显示在某个栈地址,您仍然可以尝试用 x/w <address> 来查看内存。
  3. 终极手段:如果必须精确调试,请临时使用 -O0 关闭优化重新编译。

场景五:远程调试 Coredump 文件

问题描述

服务器上编译的程序在目标设备上崩溃并生成 coredump,您需要在本地分析,但源码路径不匹配。

调试策略

  1. 加载 Coredump:gdb ./nuttx /path/to/coredump
  2. 查看原始路径:使用 info source 查看编译时记录的源码路径。
  3. 映射路径:使用 set substitute-path <original_path> <local_path> 命令进行路径替换。

    # 示例:将服务器路径映射到本地路径
    set substitute-path /home/build-server/project/ /home/user/my_project/

     

  4. 开始分析:路径映射后,即可正常使用 bt、list 等命令查看源码。

五、GDB 工作原理

1、本地调试

在本地调试中,GDB 和被调试的程序运行在同一台计算机上。GDB 通过操作系统提供的 ptrace (在 Linux 上) 等系统调用来控制和检查目标进程。

2、远程调试

远程调试是嵌入式开发中最常见的模式。它采用客户端/服务器 (Client/Server) 架构:

  • GDB Client:运行在您的开发主机上(例如,您的 PC)。
  • GDB Server:一个轻量级程序,运行在目标设备上(例如,ARM 开发板)。
  • 通信协议:两者之间通过 GDB Remote Serial Protocol (RSP) 进行通信,通信介质可以是串口或网络 (TCP/IP)。

当您在 GDB Client 中输入 continue 时,Client 会将对应的 RSP 命令发送给 Server,Server 接收后控制目标程序继续执行。当程序遇到断点时,Server 会暂停程序,并将状态信息通过 RSP 回传给 Client 显示。

六、高级主题与相关实践

1、使用 GDB 脚本 (Command File)

当您需要重复执行一系列 GDB 命令时,可以将其写入一个文本文件(例如 my_setup.gdb),然后使用 source 命令加载:

source [-s] [-v] filename

 

  • -s 表示在系统的 PATH 环境变量中搜索指定文件。
  • -v 表示开启详细模式,显示每条指令的执行过程。

更多信息请参考:Debugging with GDB - Command Files (gnu.org)

2、相关调试实践

七、故障排查 (Troubleshooting)

GDB 启动时报错 ModuleNotFoundError: No module named 'encodings'

问题原因

这通常是由于 GDB 依赖的 Python 版本与您系统中默认或环境变量中配置的 Python 版本不兼容导致的。GDB 内部使用 Python 脚本来增强功能(如 pretty-printing),如果找不到正确的 Python 环境,就会启动失败。

解决方案

  1. 确认 GDB 需要的 Python 版本:错误信息通常会暗示所需的版本(例如 python3.8)。

  2. 安装对应版本:确保您的系统中安装了该版本的 Python,可参考安装多个 Python 版本

  3. 配置 PYTHONHOME:如果安装后问题依旧,尝试设置 PYTHONHOME 环境变量,强制 GDB 使用正确的 Python 解释器。

八、参考资料

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐