4.6.3. 使用 GDB 调试应用

4.6.3.1. GDB 概述

GDB(GNU Debugger)是一个强大开源调试工具,用于调试 C、C++、Fortran 等程序。它允许开发者程序运行暂停,查看变量值、检查程序控制流、修复错误等。GDB 是开源软件,广泛应用于 Linux 和其他类 Unix 操作系统,且与 GCC 编译器紧密集成。

GDB 主要用于调试用户空间程序。它最初由 Richard Stallman 和 The GNU Project 开发的,旨在开源软件提供强大免费调试工具。下面是 GDB 的发展历史:

  • 1986年:GDB 的第一个版本发布。它由 Stallman 在工作早期阶段开发的,主要为了调试 GNU 项目软件,尤其是 GCC(GNU 编译器)编译程序。

  • 1990年代:随着开源软件和 GNU 项目发展,GDB 被广泛应用各种开发项目中。它支持多种编程语言(如 C、C++、Fortran 等),并逐步支持操作系统平台。

  • 2000年代:GDB 增加多功能,如多线程调试、远程调试、与硬件相关调试(例如使用 JTAG 接口)等。

  • 2010年代至今:GDB 继续得到活跃维护,增加现代硬件架构支持(如 ARM、RISC-V 等),并进一步扩展远程调试嵌入式系统调试功能。GDB 的功能不断扩展,逐步成为世界强大调试工具之一。

4.6.3.2. GDB 的主要功能

GDB 的主要功能如下几个方面:

  • 程序执行控制

    • 可以启动程序控制执行(单步执行、跳过、暂停等)。

    • 支持程序不同位置(例如:行号函数)设置断点。

  • 变量检查

    • 可以程序运行检查和修改变量值,帮助开发者追踪程序状态。

  • 堆栈跟踪

    • 程序崩溃发生异常时,GDB 可以显示程序调用堆栈(backtrace),帮助开发者定位问题源。

  • 动态调试

    • 支持动态加载函数,可以程序运行进行调试。

  • 源代码调试

    • 可以通过源代码(如 C 或 C++ 的源代码)进行调试,显示变量、函数调用执行位置。

  • 平台支持

    • 支持多种架构(如 x86、ARM 等),以及跨平台调试。

4.6.3.3. 常用的 GDB 调试命令

常用的 GDB 调试命令汇总如下:

命令 说明
l (list) 显示当前代码上下文,每次显示 10 行代码。可以指定行号函数名。
r (run) 运行程序。没有断点直接运行,有断点第一个断点开始运行。
b (breakpoint) <行号> 指定行号设置断点。
b <源文件>:<函数名> 指定源文件函数设置断点。
b <源文件>:<行号> 指定源文件一行设置断点。
info b 查看当前断点信息,包括命中次数等。
d (delete) <断点编号> 删除指定断点,不能使用行号删除断点。若当前没有跳出过gdb,则断点编号持续累加。
d breakpoints 删除所有断点。
disable b (breakpoints) 禁用所有断点。
enable b (breakpoints) 启用所有断点。
disable b (breakpoint) <编号> 禁用指定编号断点。
enable b (breakpoint) <编号> 启用指定编号断点。
enable breakpoint 启用指定断点,使生效。
n (next) 过程执行,跳过函数内部。
s (step) 语句执行,进入函数内部执行。
bt (backtrace) 显示当前调用堆栈,查看函数调用过程。
set var 修改变量值。
p (print) <变量名> 打印指定变量值。
display <变量名> 跟踪显示某个变量值,每次停下来时显示变量当前值。
undisplay <变量名> 取消先前设置变量跟踪。
until <行号> 跳转指定行号,执行指定区间代码停下来。
finish 执行当前函数返回,然后函数调用处。
c (continue) 当前断点继续执行,直到一个断点停止。

注: 括号里面指令全称。

GDB 详细说明可以参考官方文档 GDB: The GNU Project Debugger

4.6.3.4. GDB 调试具体方法介绍

编写测试程序

在 PC 端编写应用程序,交叉编译时加 -g 选项编译调试二进制文件:

示例 demo.c:

#include <stdio.h>

int main()
{
    int a = 0;
    char i = 0;
    for(i =0; i <10; i++)
    {
        a = i + 1;
        int b = 10/a;
        printf("b = %d\n",b);
    }
    return 0;
}

编译

注意,编译时候需要指定编译工具链为 X5 BSP 所使用opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc,为了方便调用,建议.bashrc创建别名:

alias arm_gcc='/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc'
alias arm_gdb='/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gdb'

具体编译过程如下:

$ arm_gcc -g demo.c -o gdb_demo
$ ls
demo.c  gdb_demo
$ file gdb_demo
gdb_demo: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1,
for GNU/Linux 3.7.0, with debug_info, not stripped

最终生成需要gdb_demo

GDB 调试过程

gdb_demodemo.c 传到单板上(如 /userdata 目录),然后可以进行 gdb 调试:

root@buildroot:/userdata# chmod +x gdb_demo
root@buildroot:/userdata# gdb gdb_demo
GNU gdb (GDB) 13.2
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "aarch64-buildroot-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from gdb_demo...
# 从 line 1 开始显示 demo.c 的 10 行代码
(gdb) l 1
warning: Source file is more recent than executable.
1       #include <stdio.h>
2
3       int main()
4       {
5           int a = 0;
6           char i = 0;
7           for(i =0; i <10; i++)
8           {
9               a = i + 1;
10              int b = 10/a;
# 运行代码直到遇到断点
(gdb) r
Starting program: /userdata/gdb_demo
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/libthread_db.so.1".
b = 10
b = 5
b = 3
b = 2
b = 2
b = 1
b = 1
b = 1
b = 1
b = 1
[Inferior 1 (process 2581) exited normally]
# 在 demo.c 的第 10 行设置断点
(gdb) b 10
Breakpoint 1 at 0x4005e8: file demo.c, line 10.
# 查看当前设置的断点
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004005e8 in main at demo.c:10
(gdb) r
Starting program: /userdata/gdb_demo
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/libthread_db.so.1".

Breakpoint 1, main () at demo.c:10
10              int b = 10/a;
# 监控变量 'b' 的变化
(gdb) display b
1: b = 65535
# 继续运行
(gdb) c
Continuing.
b = 10

Breakpoint 1, main () at demo.c:10
10              int b = 10/a;
1: b = 10
(gdb) c
Continuing.
b = 5

Breakpoint 1, main () at demo.c:10
10              int b = 10/a;
1: b = 5
(gdb) c
Continuing.
b = 3

Breakpoint 1, main () at demo.c:10
10              int b = 10/a;
1: b = 3
# 退出 gdb 调试
(gdb) q

gdb 分析 Core Dump 文件

通过 gdb,我们可以有效分析程序崩溃状态,快速定位问题进行修复。

编写一个测例模拟 crash:

#include <stdio.h>

int main() {

    printf("Program will now crash due to null pointer dereferencing.\n");

    // 创建一个空指针
    int *ptr = NULL;

    // 尝试解引用空指针
    *ptr = 10;  // 空指针解引用会导致崩溃

    return 0;
}

编译:

arm_gcc -g crash_example.c -o crash_example

其中 arm_gcc.bashrc创建编译工具别名:

# ~/.bashrc
alias arm_gcc='/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc'

编译产物传入单板中,进行测试:

1) 执行文件,触发 crash

root@buildroot:/userdata# ./crash_example
Program will now crash due to null pointer dereferencing.
Segmentation fault (core dumped)

2)取出生成的 Core dump 文件

X5设备生成的 Core dump 文件存放/userdata/log/coredump 目录中,将复制到 /userdata 目录进行分析:

root@buildroot:/userdata/log/coredump# ls
core-crash_example-2685-36246
root@buildroot:/userdata/log/coredump# cp core-crash_example-2685-36246 ../../

3)运行 gdb 分析 Core dump 文件

# 开始 gdb 分析
root@buildroot:/userdata# gdb crash_example core-crash_example-2685-36246
GNU gdb (GDB) 13.2
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "aarch64-buildroot-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from crash_example...
[New LWP 2685]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/libthread_db.so.1".
Core was generated by `./crash_example'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00000000004005e4 in main () at crash_example.c:11
11          *ptr = 10;  // 空指针解引用会导致崩溃
# 显示报错行附件的代码
(gdb) l 10
5           printf("Program will now crash due to null pointer dereferencing.\n");
6
7           // 创建一个空指针
8           int *ptr = NULL;
9
10          // 尝试解引用空指针
11          *ptr = 10;  // 空指针解引用会导致崩溃
12
13          return 0;
14      }
# 显示当前调用堆栈
(gdb) bt
#0  0x00000000004005e4 in main () at crash_example.c:11
# 显示当前所有变量值
(gdb) info locals
ptr = 0x0

4.6.3.5. 常见问题

GDB 调试过程一般常见以下问题:

问题 描述 可能原因 解决方案
无法启动 GDB 或连接目标程序 启动 GDB 时,出现启动失败无法连接正在调试进程 GDB 与目标程序架构匹配,缺少足够权限,目标程序没有符号信息 确保 GDB 与目标架构匹配,使用 sudo 权限运行 GDB,确保编译使用 -g 生成调试符号
调试符号缺失 GDB 提示不到符号,无法查看变量值或栈信息 编译时未包含调试符号,优化级别过高,导致无法映射源代码机器代码 确保编译使用 -g 选项生成调试符号,使用优化级别(如 -O0)编译
GDB 无法加载目标程序符号 调试器无法加载目标程序符号,导致无法查看变量或栈信息 GDB 找不到正确符号文件,或者符号文件程序版本匹配 手动加载符号文件,使用 set solib-search-path 指定共享符号文件路径
调试多线程程序无法正确切换线程 多线程程序中,GDB 无法正确识别切换线程 GDB 与多线程程序同步问题,未启用多线程调试支持,目标程序正确链接线程 使用 info threads 查看线程信息,使用 thread <id> 切换线程,确保编译启用多线程支持
断点生效 设置断点生效,或者程序执行断点 断点设置位置无效,代码优化导致目标行未执行,动态符号信息丢失 使用 info breakpoints 查看断点状态,重新编译使用优化级别,确保加载共享符号
调试内核裸机程序遇到问题 使用 GDB 调试内核裸机程序时,出现连接问题调试器无法加载符号 内核裸机程序缺少调试符号,裸机程序中 GDB 与目标程序连接稳定 编译启用调试符号,使用适当远程调试方法(如串口、JTAG)连接目标
GDB 与目标程序版本匹配 调试过程中,GDB 提示符号文件匹配无法找到符号 GDB 使用符号文件目标程序版本一致 确保 GDB 使用正确符号文件,与目标程序版本匹配
远程调试网络连接问题 使用 GDB 进行远程调试时,连接稳定无法建立连接 网络配置错误、防火墙阻止调试端口,目标系统调试代理正确启动 检查网络配置,确保防火墙阻止调试端口,确保 GDBserver 正确启动监听正确端口
内存泄漏数据一致 调试过程出现内存泄漏数据一致错误 代码存在初始化变量、越界访问,编译优化调试符号丢失导致变量值无法追踪 使用 valgrind 检查内存泄漏,确保编译使用调试选项,避免优化级别

4.6.3.6. 参考文献

GDB: The GNU Project Debugger

gdb Debugging Full Example (Tutorial): ncurses