【gdb工具】 使用详细介绍

【gdb工具】 使用详细介绍

标签: gdb, linux, 问题调试

前言

gdb(GNU Debugger)是 Linux 平台上最强大的程序调试工具,没有之一。无论你是做应用层开发还是内核开发,掌握 gdb 都是必备技能。尤其在嵌入式 Linux 开发中,设备资源有限,很多时候没有图形界面,gdb 命令行调试就是你唯一的武器。

本文将从基本概念到高级技巧,系统性地介绍 gdb 的使用方法。

一、gdb 基本介绍

gdb 是 GNU 项目开发的调试器,支持 C、C++、Go、Rust、Fortran 等多种语言。它的核心能力可以概括为四大功能:

功能 说明
启动控制 设置参数、环境变量、控制程序启动方式
断点管理 在指定位置暂停程序执行
查看状态 查看变量值、内存内容、寄存器、调用栈
修改状态 修改变量值、修改内存、修改寄存器

前置条件

使用 gdb 调试程序时,编译时需要加上 -g 选项来生成调试信息:

bash 复制代码
# 编译时加入 -g 生成调试信息
gcc -g -O0 test.c -o test

# -O0 禁用优化,避免调试时代码行号与实际不匹配
# -g 生成 DWARF 调试信息

二、基本命令

2.1 启动与退出

bash 复制代码
# 启动 gdb 并加载可执行文件
gdb ./test

# 启动时传入程序参数
gdb --args ./test arg1 arg2

# 启动后设置参数
(gdb) set args arg1 arg2

# 运行程序
(gdb) run
(gdb) r    # 简写

# 退出 gdb
(gdb) quit
(gdb) q    # 简写

2.2 断点管理

bash 复制代码
# 在函数入口设置断点
(gdb) break main
(gdb) b my_function

# 在指定文件的指定行设置断点
(gdb) break test.c:42

# 设置条件断点(只在条件满足时触发)
(gdb) break test.c:42 if count > 100

# 查看所有断点
(gdb) info breakpoints
(gdb) i b

# 删除断点
(gdb) delete 1        # 删除编号为 1 的断点
(gdb) delete          # 删除所有断点

# 禁用/启用断点
(gdb) disable 1
(gdb) enable 1

2.3 执行控制

bash 复制代码
# 继续执行到下一个断点
(gdb) continue
(gdb) c

# 单步执行(不进入函数)
(gdb) next
(gdb) n

# 单步执行(进入函数)
(gdb) step
(gdb) s

# 执行到当前函数返回
(gdb) finish

# 执行到指定行
(gdb) until 50

# 执行指定次数的 next
(gdb) next 10

2.4 查看数据

bash 复制代码
# 打印变量值
(gdb) print my_var
(gdb) p my_var

# 以十六进制格式打印
(gdb) p/x my_var

# 打印数组(显示前 10 个元素)
(gdb) p *array@10

# 打印结构体
(gdb) p my_struct

# 打印指针指向的内容
(gdb) p *my_ptr

# 格式化打印
(gdb) p/d my_var    # 十进制
(gdb) p/x my_var    # 十六进制
(gdb) p/t my_var    # 二进制
(gdb) p/c my_var    # 字符
(gdb) p/f my_var    # 浮点数

# 查看内存内容
(gdb) x/16xb my_ptr    # 以十六进制显示 16 字节
(gdb) x/10dw my_array  # 以十进制显示 10 个 4 字节整数
(gdb) x/s my_string    # 以字符串形式显示

# 持续显示某个变量的值(每次停下来都自动显示)
(gdb) display my_var
(gdb) info display
(gdb) undisplay 1

# 查看所有局部变量
(gdb) info locals

# 查看所有参数
(gdb) info args

2.5 调用栈(Backtrace)

bash 复制代码
# 查看调用栈
(gdb) backtrace
(gdb) bt

# 查看详细的调用栈(含局部变量)
(gdb) bt full

# 切换到指定的栈帧
(gdb) frame 2
(gdb) f 2

# 查看当前栈帧的详细信息
(gdb) info frame

典型输出:

复制代码
(gdb) bt
#0  my_function (ptr=0x0) at test.c:15
#1  0x00005555555551a0 in process_data (data=0x7fffffffe010) at test.c:32
#2  0x0000555555555210 in main (argc=1, argv=0x7fffffffe128) at test.c:48

2.6 查看与修改寄存器

bash 复制代码
# 查看所有寄存器
(gdb) info registers

# 查看指定寄存器
(gdb) p $rax
(gdb) p $pc

# 修改寄存器值
(gdb) set $rax = 0

# 查看反汇编
(gdb) disassemble
(gdb) disas my_function

三、远程调试(gdbserver)

在嵌入式开发中,目标设备上通常不会安装完整的 gdb,而是使用轻量级的 gdbserver

3.1 部署 gdbserver

bash 复制代码
# 在目标设备上启动 gdbserver
# 方式一:直接启动程序
target# gdbserver :1234 ./my_app arg1 arg2
# 输出: Listening on port 1234

# 方式二:attach 到已运行的进程
target# gdbserver :1234 --attach <pid>

3.2 本地 gdb 连接

bash 复制代码
# 在开发机上启动 gdb
host$ arm-linux-gnueabihf-gdb ./my_app

# 连接到目标设备
(gdb) target remote 192.168.1.100:1234

# 如果使用串口连接
(gdb) target remote /dev/ttyUSB0

# 连接成功后即可正常使用断点、单步等命令
(gdb) break main
(gdb) continue

3.3 远程调试的符号解析

bash 复制代码
# 如果可执行文件在不同路径,需要设置 sysroot
(gdb) set sysroot /path/to/target/rootfs

# 设置共享库搜索路径
(gdb) set solib-search-path /path/to/libs

# 查看已加载的共享库
(gdb) info sharedlibrary

四、Attach 模式

对于正在运行的进程,可以使用 attach 模式进行调试:

bash 复制代码
# 方式一:直接 attach
gdb -p <pid>

# 方式二:先启动 gdb,再 attach
(gdb) attach <pid>

# attach 后进程会暂停,可以查看状态、设置断点等
(gdb) bt              # 查看当前调用栈
(gdb) info threads    # 查看所有线程

# 继续运行
(gdb) continue

# 分离(不杀死进程)
(gdb) detach

查看进程的所有线程:

复制代码
(gdb) info threads
  Id   Target Id         Frame
* 1    Thread 0x7f8a0010 (LWP 1234) "my_app" my_function () at test.c:42
  2    Thread 0x7f89f010 (LWP 1235) "my_app" 0x00007f8a1234 in pthread_cond_wait ()
  3    Thread 0x7f89e010 (LWP 1236) "my_app" 0x00007f8a1234 in poll ()

# 切换到指定线程
(gdb) thread 2

# 查看所有线程的调用栈
(gdb) thread apply all bt

五、Core Dump 分析

Core dump 是进程崩溃时的内存快照,是分析 Crash 问题最重要的资料。

5.1 开启 Core Dump

bash 复制代码
# 查看 core dump 大小限制
ulimit -c

# 开启 core dump(不限制大小)
ulimit -c unlimited

# 设置 core dump 文件名格式
# %e=程序名, %p=进程号, %t=时间戳
echo "core-%e-%p-%t" > /proc/sys/kernel/core_pattern

# 或者通过 sysctl 永久设置
sysctl -w kernel.core_pattern="/tmp/core-%e-%p"

5.2 分析 Core Dump

bash 复制代码
# 加载 core dump 文件
gdb ./my_app core.1234

# gdb 会自动停在崩溃的位置
# 常见操作:
(gdb) bt               # 查看崩溃时的调用栈
(gdb) info registers    # 查看寄存器状态
(gdb) frame 0           # 切换到崩溃的栈帧
(gdb) print *ptr        # 查看变量值
(gdb) x/16xb addr       # 查看内存内容

典型崩溃分析示例:

复制代码
$ gdb ./my_app core.1234
...
Core was generated by `./my_app'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000555555555160 in my_function (ptr=0x0) at test.c:15
15          int value = *ptr;

(gdb) bt
#0  0x0000555555555160 in my_function (ptr=0x0) at test.c:15
#1  0x00005555555551a0 in process_data () at test.c:32
#2  0x0000555555555210 in main () at test.c:48

(gdb) print ptr
$1 = (int *) 0x0
# 结论:ptr 是空指针,解引用导致段错误

5.3 地址转源码行

当 core dump 中没有完整的调试信息时,可以使用 addr2line

bash 复制代码
# 将地址转换为文件名和行号
addr2line -e ./my_app -f 0x555555555160
# 输出:
# my_function
# test.c:15

# 结合 dmesg 使用
# dmesg 中的崩溃信息通常包含指令指针地址
dmesg | grep "segfault"
# [12345.678] my_app[1234]: segfault at 0 ip 0000555555555160 sp 00007fffffffe010 error 6 in my_app[555555555000+1000]

addr2line -e ./my_app 0x160
# 输出: test.c:15

六、高级技巧

6.1 Watchpoint(数据断点)

Watchpoint 可以在某个变量的值发生变化时自动暂停:

bash 复制代码
# 当变量被写入时暂停
(gdb) watch my_var

# 当变量被读取时暂停
(gdb) rwatch my_var

# 当变量被读取或写入时暂停
(gdb) awatch my_var

# watchpoint 也可以设置条件
(gdb) watch my_var if my_var > 100

# 查看所有 watchpoint
(gdb) info watchpoints

6.2 Catchpoint

bash 复制代码
# 捕获系统调用
(gdb) catch syscall open
(gdb) catch syscall write

# 捕获信号
(gdb) catch signal SIGSEGV
(gdb) catch signal SIGABRT

# 捕获 C++ 异常
(gdb) catch throw
(gdb) catch catch

6.3 反汇编与寄存器深入

bash 复制代码
# 反汇编当前函数
(gdb) disassemble

# 反汇编指定函数并显示源码
(gdb) disassemble /m my_function

# 反汇编并显示机器码
(gdb) disassemble /r my_function

# 设置反汇编风格
(gdb) set disassembly-flavor intel    # Intel 语法
(gdb) set disassembly-flavor att      # AT&T 语法(默认)

# 查看浮点寄存器
(gdb) info all-registers

6.4 多线程调试

bash 复制代码
# 查看所有线程
(gdb) info threads

# 切换线程
(gdb) thread <thread_id>

# 打印所有线程的调用栈
(gdb) thread apply all bt

# 只打印所有线程的简要调用栈
(gdb) thread apply all bt brief

# 锁定线程调度(调试时不自动切换线程)
(gdb) set scheduler-locking on

# 恢复自动调度
(gdb) set scheduler-locking off

6.5 实用技巧

bash 复制代码
# 记录日志(所有输出保存到文件)
(gdb) set logging on
(gdb) set logging file gdb_log.txt

# 查看函数的源码
(gdb) list
(gdb) list my_function
(gdb) list test.c:30

# 查看类型的定义
(gdb) ptype my_struct

# 搜索内存中的值
(gdb) find /b 0x7fffffffe000, 0x7ffffffff000, 0x41, 0x42, 0x43
# 在指定范围内搜索字节序列 0x41 0x42 0x43

# 调用被调试程序中的函数
(gdb) call printf("hello %d\n", 42)
(gdb) call malloc(100)

# 保存断点设置到文件
(gdb) save breakpoints breakpoints.txt

# 从文件加载断点
(gdb) source breakpoints.txt

七、实战案例:调试段错误

场景描述

一个嵌入式应用程序在运行过程中偶尔出现段错误,需要定位原因。

步骤一:开启 Core Dump 并复现问题

bash 复制代码
# 目标设备上
ulimit -c unlimited
echo "/tmp/core-%e-%p" > /proc/sys/kernel/core_pattern

# 运行程序直到崩溃
./my_app
# Segmentation fault (core dumped)

# 确认 core 文件已生成
ls -lh /tmp/core-my_app-*
# -rw------- 1 root root 8.4M ... /tmp/core-my_app-1234

步骤二:分析 Core Dump

bash 复制代码
# 开发机上
$ arm-linux-gnueabihf-gdb ./my_app /tmp/core-my_app-1234

GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
...
Core was generated by `./my_app'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  free_linked_list (head=0x10001234) at linked_list.c:42
42          free(current->data);

(gdb) bt
#0  free_linked_list (head=0x10001234) at linked_list.c:42
#1  0x00000000004012a0 in cleanup_handler () at main.c:156
#2  0x0000000000401310 in signal_handler (sig=15) at main.c:172
#3  <signal handler called>
#4  0x00007f8a1234 in __GI___poll () at ../sysdeps/unix/syscall-template.S:81

(gdb) frame 0
(gdb) print *current
$1 = {data = 0x0, next = 0x10001300}
# current->data 是 NULL,free(NULL) 不会出错...
# 但是看代码逻辑,实际是 current 本身可能已被释放

步骤三:定位根因

c 复制代码
// linked_list.c 中的问题代码
void free_linked_list(node_t *head) {
    node_t *current = head;
    while (current != NULL) {
        node_t *next = current->next;
        free(current->data);  // 第 42 行:崩溃在这里
        free(current);
        current = next;
    }
}

通过进一步分析发现,信号处理函数在主线程执行 free_linked_list 时,另一个线程也在操作同一个链表,导致了 Use-After-Free

步骤四:修复

c 复制代码
// 修复:添加互斥锁保护
static pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;

void free_linked_list(node_t *head) {
    pthread_mutex_lock(&list_mutex);
    node_t *current = head;
    while (current != NULL) {
        node_t *next = current->next;
        free(current->data);
        free(current);
        current = next;
    }
    pthread_mutex_unlock(&list_mutex);
}

// 信号处理函数中应避免复杂操作
void signal_handler(int sig) {
    // 只设置标志位,不在信号处理函数中做复杂的资源释放
    g_shutdown_flag = 1;
}

总结

gdb 是嵌入式 Linux 开发中最核心的调试工具。本文覆盖了从基本使用到高级技巧的完整知识体系。核心要点:

  1. 编译时加 -g,确保有调试信息。
  2. 远程调试 使用 gdbserver,是嵌入式开发的常用方式。
  3. Core Dump 分析 是事后排查 Crash 问题的最有效手段。
  4. Watchpoint 对于定位"变量值被意外修改"的问题非常有用。
  5. thread apply all bt 是分析多线程死锁问题的利器。

工具的价值在于实战中的应用。建议读者在日常开发中主动使用 gdb 进行调试,逐步积累经验。


下一篇预告【工具】ASan 快速定位内存越界、内存泄漏

相关推荐
如果超人不会飞2 分钟前
TinyRobot SuggestionPopover智能建议弹出框组件
前端·vue.js
LiuJun2Son17 分钟前
Angular 快速入门:从零搭建你的第一个应用
前端·javascript·angular.js
小徐_233325 分钟前
Wot UI 2.1.0 发布:ConfigProvider 全局配置能力升级
前端·uni-app
方白羽26 分钟前
Vibe Coding 四个核心阶段
android·前端·app
奶油话梅糖27 分钟前
浏览器解析 HTML 头部的底层逻辑:从字节流到资源调度
前端·html
YHL27 分钟前
🚀从零理解树与二叉树 —— 概念、实现与遍历
前端·javascript·数据结构
小时前端28 分钟前
微前端技术选型深度分析:从概念到实践
前端
wyhwust43 分钟前
基于Apifox的接口管理工具
前端
柒和远方1 小时前
后端认证、鉴权、高并发:从 Session 到 JWT 再到 Redis
前端·后端·面试
piglet121381 小时前
把搜索调到 Claude.ai 的水准
前端·人工智能