【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 快速定位内存越界、内存泄漏

相关推荐
guhy fighting1 小时前
使用vue-virtual-scroller导致打包报错
前端·javascript·vue.js·webpack
UXbot1 小时前
如何用 AI 生成产品原型:从需求描述到可交互界面的完整 5 步流程
前端·人工智能·ui·交互·ai编程
hbstream1 小时前
Hermes Agent 一周暴涨五万 Star,但我劝你别急着追
前端·人工智能
光影少年2 小时前
前端开发桌面端都有哪些框架?
前端·react.js·electron
Cecilialana2 小时前
同域名、同项目、仅 hash 变化,window.location.href 不跳转
前端·javascript
Hello--_--World2 小时前
DOM事件流与事件委托、判断数据类型、深浅拷贝、对象遍历方式
前端·javascript
落魄江湖行2 小时前
进阶篇二 Nuxt4 渲染模式:SSR/SSG/CSR 怎么选
前端·vue.js·typescript·nuxt4
M宝可梦2 小时前
ReAct 与 LLM Agentic 范式:从推理到行动的完整技术科普
前端·react.js·前端框架
x-cmd2 小时前
[260416] 谷歌 Chrome 推出 Skills 功能!帮你保存、复用提示词
前端·chrome·ai·自动化·agent·x-cmd·skill