跳转至

GDB调试器基础使用:命令行调试完全指南

学习目标

完成本教程后,你将能够:

  • 理解GDB调试器的工作原理和基本概念
  • 掌握GDB的基本命令和操作流程
  • 使用断点、单步执行等调试功能
  • 查看和修改变量、内存和寄存器
  • 分析程序调用栈和线程状态
  • 进行远程调试和自动化调试

前置要求

在开始本教程之前,你需要:

知识要求: - 了解C/C++编程基础 - 熟悉编译和链接的基本概念 - 了解程序执行流程 - 掌握基本的命令行操作

技能要求: - 能够使用GCC编译器 - 会使用基本的Shell命令 - 了解调试的基本概念 - 能够阅读汇编代码(可选)

准备工作

软件准备

  • 操作系统: Linux、macOS 或 Windows (WSL/MinGW)
  • GDB: GNU Debugger 8.0+
  • 编译器: GCC 或 ARM GCC工具链
  • 文本编辑器: 任何文本编辑器

安装GDB

Linux系统:

# Ubuntu/Debian
sudo apt install gdb

# CentOS/RHEL
sudo yum install gdb

# Arch Linux
sudo pacman -S gdb

macOS系统:

# 使用Homebrew
brew install gdb

# 配置代码签名(macOS需要)
# 参考:https://sourceware.org/gdb/wiki/PermissionsDarwin

Windows系统:

# 使用MinGW-w64
# 下载并安装MinGW-w64,包含gdb

# 或使用WSL
wsl --install
sudo apt install gdb

验证安装

gdb --version

预期输出:

GNU gdb (GDB) 10.2
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later

系统要求

  • 内存: 至少2GB RAM
  • 磁盘: 100MB可用空间
  • 权限: 普通用户权限(某些功能需要root)

步骤1: 理解GDB调试器

1.1 什么是GDB?

GDB (GNU Debugger) 是GNU项目开发的强大的命令行调试器,支持多种编程语言和处理器架构。

GDB的主要特点: - 开源免费,功能强大 - 支持多种语言(C、C++、Fortran等) - 支持多种架构(x86、ARM、MIPS等) - 可以调试本地和远程程序 - 支持脚本自动化 - 可扩展性强

GDB的核心功能: - 启动程序并指定运行参数 - 在指定条件下停止程序 - 检查程序停止时的状态 - 修改程序的变量和内存 - 分析程序崩溃原因

1.2 GDB工作原理

调试架构:

GDB调试器
ptrace系统调用
被调试程序

关键概念: 1. 符号表: 包含变量名、函数名和地址的映射 2. 断点: 程序执行到指定位置时暂停 3. 单步执行: 逐行或逐指令执行程序 4. 调用栈: 函数调用的层次关系 5. 观察点: 监视变量或内存的变化

1.3 调试信息

要使用GDB调试,程序必须包含调试信息。

编译时添加调试信息:

# 添加调试信息(-g选项)
gcc -g program.c -o program

# 添加详细调试信息(-g3)
gcc -g3 program.c -o program

# 禁用优化(便于调试)
gcc -g -O0 program.c -o program

调试信息级别: - -g0: 无调试信息 - -g1: 最小调试信息(行号) - -g2: 默认调试信息(推荐) - -g3: 最详细调试信息(包括宏定义)

优化级别对调试的影响: - -O0: 无优化,最适合调试 - -O1: 基本优化,部分变量可能被优化 - -O2: 标准优化,调试困难 - -O3: 激进优化,不推荐调试

1.4 GDB vs 图形化调试器

特性 GDB 图形化调试器
界面 命令行 图形界面
学习曲线 陡峭 平缓
功能 强大完整 功能有限
远程调试 原生支持 需要配置
自动化 脚本支持 较困难
资源占用
适用场景 服务器、嵌入式 桌面开发

选择建议: - 使用GDB: 远程调试、嵌入式开发、服务器调试、自动化测试 - 使用图形化: 桌面应用开发、初学者、快速调试

步骤2: GDB基本操作

2.1 创建测试程序

创建 test.c:

#include <stdio.h>
#include <stdlib.h>

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    int result = 0;
    for (int i = 0; i < b; i++) {
        result = add(result, a);
    }
    return result;
}

int main(int argc, char *argv[]) {
    int x = 5;
    int y = 3;

    printf("x = %d, y = %d\n", x, y);

    int sum = add(x, y);
    printf("x + y = %d\n", sum);

    int product = multiply(x, y);
    printf("x * y = %d\n", product);

    return 0;
}

编译程序:

gcc -g -O0 test.c -o test

2.2 启动GDB

方法1: 直接启动

gdb ./test

方法2: 附加到运行中的进程

# 先运行程序
./test &
# 获取进程ID
ps aux | grep test
# 附加到进程
gdb -p <PID>

方法3: 调试core文件

# 启用core dump
ulimit -c unlimited
# 运行程序(如果崩溃会生成core文件)
./test
# 调试core文件
gdb ./test core

2.3 GDB界面

启动GDB后,你会看到:

GNU gdb (GDB) 10.2
Copyright (C) 2021 Free Software Foundation, Inc.
...
Reading symbols from ./test...
(gdb) 

GDB提示符: (gdb) 表示等待输入命令

2.4 基本命令

运行程序

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

# 带参数运行
(gdb) run arg1 arg2

# 带输入重定向
(gdb) run < input.txt

# 带输出重定向
(gdb) run > output.txt

退出GDB

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

# 强制退出(不确认)
(gdb) quit -y

获取帮助

# 查看所有命令
(gdb) help

# 查看特定命令的帮助
(gdb) help run
(gdb) help break

# 查看命令类别
(gdb) help breakpoints
(gdb) help data

步骤3: 断点管理

3.1 设置断点

在函数处设置断点:

# 在main函数设置断点
(gdb) break main
# 或简写
(gdb) b main

# 在add函数设置断点
(gdb) break add

在行号处设置断点:

# 在当前文件的第10行设置断点
(gdb) break 10
(gdb) b 10

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

在地址处设置断点:

# 在内存地址0x400500设置断点
(gdb) break *0x400500

3.2 条件断点

设置条件断点:

# 当i等于5时才触发断点
(gdb) break 12 if i == 5

# 当指针不为空时触发
(gdb) break function if ptr != 0

# 复杂条件
(gdb) break 15 if x > 10 && y < 20

修改断点条件:

# 为断点1添加条件
(gdb) condition 1 i == 10

# 删除断点条件
(gdb) condition 1

3.3 临时断点

# 设置临时断点(触发一次后自动删除)
(gdb) tbreak main
(gdb) tb main

# 在第15行设置临时断点
(gdb) tbreak 15

3.4 查看断点

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

# 输出示例:
# Num     Type           Disp Enb Address            What
# 1       breakpoint     keep y   0x0000000000401142 in main at test.c:16
# 2       breakpoint     keep y   0x0000000000401126 in add at test.c:4

断点信息说明: - Num: 断点编号 - Type: 断点类型 - Disp: 触发后的处理(keep保留/del删除) - Enb: 是否启用(y启用/n禁用) - Address: 内存地址 - What: 断点位置

3.5 管理断点

禁用断点:

# 禁用断点1
(gdb) disable 1

# 禁用所有断点
(gdb) disable

启用断点:

# 启用断点1
(gdb) enable 1

# 启用所有断点
(gdb) enable

删除断点:

# 删除断点1
(gdb) delete 1
(gdb) d 1

# 删除所有断点
(gdb) delete

忽略断点:

# 忽略断点1的前10次触发
(gdb) ignore 1 10

3.6 观察点(Watchpoint)

观察点用于监视变量或内存的变化。

设置观察点:

# 监视变量x的变化
(gdb) watch x

# 监视表达式
(gdb) watch x + y

# 监视内存地址
(gdb) watch *(int*)0x12345678

读观察点:

# 当变量被读取时触发
(gdb) rwatch x

访问观察点:

# 当变量被读取或写入时触发
(gdb) awatch x

查看观察点:

(gdb) info watchpoints

步骤4: 程序执行控制

4.1 单步执行

逐语句执行(Step Into):

# 执行一行代码,进入函数内部
(gdb) step
# 或简写
(gdb) s

# 执行5行
(gdb) step 5

逐过程执行(Step Over):

# 执行一行代码,不进入函数内部
(gdb) next
# 或简写
(gdb) n

# 执行5行
(gdb) next 5

单步执行汇编指令:

# 执行一条汇编指令(进入函数)
(gdb) stepi
(gdb) si

# 执行一条汇编指令(不进入函数)
(gdb) nexti
(gdb) ni

4.2 继续执行

继续执行到下一个断点:

# 继续执行
(gdb) continue
# 或简写
(gdb) c

# 继续执行并忽略当前断点N次
(gdb) continue 5

执行到函数返回:

# 执行到当前函数返回
(gdb) finish
# 或简写
(gdb) fin

执行到指定位置:

# 执行到第20行
(gdb) until 20
(gdb) u 20

# 执行到指定函数
(gdb) until add

4.3 跳转执行

跳转到指定行:

# 跳转到第15行(不执行中间代码)
(gdb) jump 15
(gdb) j 15

# 跳转到指定函数
(gdb) jump add

⚠️ 注意: 跳转可能导致程序状态不一致,谨慎使用。

4.4 信号处理

查看信号处理:

# 查看所有信号的处理方式
(gdb) info signals
(gdb) info handle

设置信号处理:

# 捕获SIGINT信号(Ctrl+C)
(gdb) handle SIGINT stop print

# 忽略SIGPIPE信号
(gdb) handle SIGPIPE nostop noprint

# 传递信号给程序
(gdb) handle SIGUSR1 pass

发送信号:

# 向程序发送SIGUSR1信号
(gdb) signal SIGUSR1

步骤5: 查看和修改数据

5.1 查看变量

打印变量值:

# 打印变量x的值
(gdb) print x
# 或简写
(gdb) p x

# 打印表达式
(gdb) print x + y
(gdb) p x * 2

# 打印函数返回值
(gdb) print add(3, 5)

不同格式打印:

# 十六进制
(gdb) print/x x
(gdb) p/x x

# 二进制
(gdb) print/t x

# 八进制
(gdb) print/o x

# 十进制
(gdb) print/d x

# 字符
(gdb) print/c x

# 浮点数
(gdb) print/f x

# 地址
(gdb) print/a x

打印数组:

# 打印数组
(gdb) print array

# 打印数组的前10个元素
(gdb) print array[0]@10

# 打印指针指向的10个元素
(gdb) print *ptr@10

打印结构体:

# 打印结构体
(gdb) print my_struct

# 打印结构体成员
(gdb) print my_struct.member

# 打印指针指向的结构体
(gdb) print *ptr_struct

5.2 查看内存

查看内存内容:

# 查看地址0x12345678的内存
(gdb) x/10x 0x12345678

# 格式:x/[数量][格式][大小] 地址

格式说明: - 数量: 显示多少个单元 - 格式: x(十六进制) d(十进制) u(无符号) o(八进制) t(二进制) a(地址) c(字符) s(字符串) - 大小: b(字节) h(半字) w(字) g(双字)

示例:

# 查看10个字节(十六进制)
(gdb) x/10xb 0x12345678

# 查看5个字(十六进制)
(gdb) x/5xw 0x12345678

# 查看字符串
(gdb) x/s 0x12345678

# 查看变量的内存
(gdb) x/10x &variable

5.3 修改变量

修改变量值:

# 修改变量x的值
(gdb) set variable x = 10
(gdb) set var x = 10

# 修改表达式
(gdb) set var x = x + 5

# 修改数组元素
(gdb) set var array[5] = 100

# 修改结构体成员
(gdb) set var my_struct.member = 20

修改内存:

# 修改内存地址的值
(gdb) set {int}0x12345678 = 100

# 修改字节
(gdb) set {char}0x12345678 = 0xFF

# 修改指针指向的值
(gdb) set *ptr = 50

5.4 查看寄存器

查看所有寄存器:

# 查看通用寄存器
(gdb) info registers
(gdb) i r

# 查看所有寄存器(包括浮点和向量)
(gdb) info all-registers

查看特定寄存器:

# 查看rax寄存器
(gdb) info registers rax
(gdb) i r rax

# 打印寄存器值
(gdb) print $rax
(gdb) p $rax

# x86-64常用寄存器
(gdb) p $rip    # 指令指针
(gdb) p $rsp    # 栈指针
(gdb) p $rbp    # 基址指针

修改寄存器:

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

# ARM寄存器
(gdb) p $r0
(gdb) p $pc
(gdb) p $sp

5.5 自动显示

设置自动显示:

# 每次停止时自动显示变量x
(gdb) display x

# 自动显示表达式
(gdb) display x + y

# 自动显示内存
(gdb) display/10x &array

查看自动显示列表:

(gdb) info display

删除自动显示:

# 删除显示1
(gdb) undisplay 1

# 删除所有自动显示
(gdb) undisplay

步骤6: 调用栈分析

6.1 查看调用栈

显示调用栈:

# 显示完整调用栈
(gdb) backtrace
# 或简写
(gdb) bt

# 显示前5层调用栈
(gdb) backtrace 5
(gdb) bt 5

# 显示完整信息(包括参数)
(gdb) backtrace full
(gdb) bt full

调用栈输出示例:

#0  add (a=5, b=3) at test.c:4
#1  0x0000000000401165 in multiply (a=5, b=3) at test.c:10
#2  0x00000000004011a5 in main (argc=1, argv=0x7fffffffe0b8) at test.c:23

栈帧信息说明: - #0: 栈帧编号(0是当前帧) - add: 函数名 - (a=5, b=3): 函数参数 - at test.c:4: 源文件和行号 - 0x0000000000401165: 返回地址

6.2 切换栈帧

选择栈帧:

# 切换到栈帧1
(gdb) frame 1
(gdb) f 1

# 切换到上一层栈帧
(gdb) up

# 切换到下一层栈帧
(gdb) down

# 向上移动2层
(gdb) up 2

查看当前栈帧:

# 显示当前栈帧信息
(gdb) frame
(gdb) f

# 显示当前栈帧的详细信息
(gdb) info frame
(gdb) i f

6.3 查看局部变量

查看当前函数的局部变量:

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

# 查看函数参数
(gdb) info args
(gdb) i args

查看指定栈帧的变量:

# 切换到栈帧1并查看局部变量
(gdb) frame 1
(gdb) info locals

6.4 查看源代码

显示当前位置的源代码:

# 显示当前行附近的代码
(gdb) list
(gdb) l

# 显示第10行附近的代码
(gdb) list 10

# 显示函数的代码
(gdb) list add

# 显示指定文件的代码
(gdb) list test.c:10

设置显示行数:

# 设置每次显示10行
(gdb) set listsize 10

# 查看当前设置
(gdb) show listsize

反汇编:

# 反汇编当前函数
(gdb) disassemble
(gdb) disas

# 反汇编指定函数
(gdb) disassemble add

# 反汇编地址范围
(gdb) disassemble 0x400500,0x400550

# 显示源代码和汇编混合
(gdb) disassemble /m

步骤7: 多线程调试

7.1 查看线程

查看所有线程:

# 显示所有线程
(gdb) info threads
(gdb) i threads

输出示例:

  Id   Target Id         Frame 
* 1    Thread 0x7ffff7fc0740 (LWP 12345) main () at test.c:20
  2    Thread 0x7ffff6fbf700 (LWP 12346) thread_func () at test.c:10
  3    Thread 0x7ffff5fbe700 (LWP 12347) thread_func () at test.c:10

线程信息说明: - *: 当前线程 - Id: GDB内部线程ID - Target Id: 系统线程ID - Frame: 当前执行位置

7.2 切换线程

切换到指定线程:

# 切换到线程2
(gdb) thread 2
(gdb) t 2

# 查看当前线程
(gdb) thread

对所有线程执行命令:

# 对所有线程执行backtrace
(gdb) thread apply all backtrace
(gdb) thread apply all bt

# 对线程1和2执行命令
(gdb) thread apply 1 2 print x

7.3 线程断点

在特定线程设置断点:

# 在线程2的main函数设置断点
(gdb) break main thread 2

# 在所有线程的add函数设置断点
(gdb) break add

线程特定的条件断点:

# 只在线程2且x==5时触发
(gdb) break 10 thread 2 if x == 5

7.4 调度器锁定

锁定调度器:

# 锁定调度器(只运行当前线程)
(gdb) set scheduler-locking on

# 解锁调度器(所有线程运行)
(gdb) set scheduler-locking off

# 单步时锁定,其他时候解锁
(gdb) set scheduler-locking step

# 查看当前设置
(gdb) show scheduler-locking

应用场景: - 调试多线程竞态条件 - 隔离单个线程的行为 - 避免其他线程干扰调试

步骤8: 远程调试

8.1 远程调试架构

开发机(GDB客户端)
    ↓ 网络连接
目标机(gdbserver)
被调试程序

8.2 使用gdbserver

在目标机上启动gdbserver:

# 启动程序并等待连接
gdbserver :1234 ./program

# 附加到运行中的进程
gdbserver :1234 --attach <PID>

# 指定监听地址
gdbserver 192.168.1.100:1234 ./program

在开发机上连接:

# 启动GDB
gdb ./program

# 连接到远程目标
(gdb) target remote 192.168.1.100:1234

# 或使用extended-remote(支持运行新程序)
(gdb) target extended-remote 192.168.1.100:1234

8.3 嵌入式远程调试

使用OpenOCD进行ARM调试:

启动OpenOCD:

# 启动OpenOCD(在目标机或调试器主机)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

连接GDB:

# 启动ARM GDB
arm-none-eabi-gdb firmware.elf

# 连接到OpenOCD
(gdb) target extended-remote localhost:3333

# 加载程序
(gdb) load

# 复位并停止
(gdb) monitor reset halt

# 开始调试
(gdb) continue

8.4 远程调试技巧

文件传输:

# 设置sysroot(远程文件系统根目录)
(gdb) set sysroot /path/to/sysroot

# 设置远程文件路径
(gdb) set remote exec-file /path/on/target/program

符号文件:

# 加载符号文件
(gdb) symbol-file /path/to/program.elf

# 添加额外的符号文件
(gdb) add-symbol-file /path/to/library.so 0x12345678

远程命令:

# 发送命令到远程调试器
(gdb) monitor reset
(gdb) monitor halt

# OpenOCD特定命令
(gdb) monitor flash write_image erase firmware.bin 0x08000000

步骤9: GDB脚本和自动化

9.1 命令文件

创建GDB命令文件 debug.gdb:

# 连接到远程目标
target extended-remote localhost:3333

# 加载程序
load

# 复位
monitor reset halt

# 设置断点
break main
break error_handler

# 设置自动显示
display x
display y

# 运行
continue

使用命令文件:

# 启动时执行命令文件
gdb -x debug.gdb program

# 在GDB中执行命令文件
(gdb) source debug.gdb

9.2 初始化文件

用户初始化文件 ~/.gdbinit:

# 设置历史记录
set history save on
set history size 10000
set history filename ~/.gdb_history

# 美化输出
set print pretty on
set print array on
set print array-indexes on

# 禁用分页
set pagination off

# 设置反汇编风格
set disassembly-flavor intel

# 自定义命令
define hook-stop
    info registers
    x/10i $pc
end

9.3 Python脚本

GDB支持Python脚本扩展。

简单Python命令:

# 执行Python代码
(gdb) python print("Hello from Python")

# 执行Python脚本
(gdb) source script.py

Python脚本示例 script.py:

import gdb

class HelloCommand(gdb.Command):
    """打印Hello World"""

    def __init__(self):
        super(HelloCommand, self).__init__("hello", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        print("Hello, World!")

HelloCommand()

class PrintVarCommand(gdb.Command):
    """打印变量的详细信息"""

    def __init__(self):
        super(PrintVarCommand, self).__init__("pvar", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        try:
            val = gdb.parse_and_eval(arg)
            print(f"Variable: {arg}")
            print(f"Type: {val.type}")
            print(f"Value: {val}")
            print(f"Address: {val.address}")
        except Exception as e:
            print(f"Error: {e}")

PrintVarCommand()

使用自定义命令:

(gdb) source script.py
(gdb) hello
Hello, World!

(gdb) pvar x
Variable: x
Type: int
Value: 5
Address: 0x7fffffffe0a4

9.4 条件断点脚本

使用命令列表:

# 设置断点并添加命令
(gdb) break main
(gdb) commands 1
> silent
> printf "x = %d, y = %d\n", x, y
> continue
> end

说明: - silent: 不显示断点触发信息 - printf: 打印信息 - continue: 自动继续执行 - end: 结束命令列表

9.5 自动化测试

测试脚本示例 test.gdb:

# 设置断点
break test_function

# 运行程序
run

# 检查结果
if result == expected
    printf "Test PASSED\n"
else
    printf "Test FAILED: expected %d, got %d\n", expected, result
end

# 退出
quit

批处理模式运行:

# 批处理模式(不进入交互)
gdb -batch -x test.gdb program

# 静默模式
gdb -batch -silent -x test.gdb program

步骤10: 高级调试技巧

10.1 调试优化代码

问题: 优化后的代码难以调试,变量可能被优化掉。

解决方法:

  1. 使用volatile关键字:

    volatile int debug_var = 0;
    

  2. 部分禁用优化:

    #pragma GCC push_options
    #pragma GCC optimize ("O0")
    
    void debug_function() {
        // 这个函数不会被优化
    }
    
    #pragma GCC pop_options
    

  3. 编译时保留调试信息:

    gcc -g -O2 -fno-omit-frame-pointer program.c -o program
    

10.2 调试宏

查看宏定义:

# 查看宏定义(需要-g3编译)
(gdb) info macro MACRO_NAME

# 展开宏
(gdb) macro expand MACRO_NAME

编译时包含宏信息:

gcc -g3 program.c -o program

10.3 调试内联函数

问题: 内联函数没有独立的栈帧。

解决方法:

  1. 禁用内联:

    gcc -g -O0 -fno-inline program.c -o program
    

  2. 或在代码中禁用:

    __attribute__((noinline))
    void my_function() {
        // 这个函数不会被内联
    }
    

10.4 反向调试

GDB支持反向执行(需要记录执行历史)。

启用记录:

# 开始记录
(gdb) record
(gdb) rec

# 或使用完整记录
(gdb) record full

反向执行命令:

# 反向继续
(gdb) reverse-continue
(gdb) rc

# 反向单步
(gdb) reverse-step
(gdb) rs

# 反向next
(gdb) reverse-next
(gdb) rn

# 反向执行到函数开始
(gdb) reverse-finish

应用场景: - 找出变量何时被修改 - 追踪bug的根源 - 理解程序执行流程

10.5 checkpoint(检查点)

创建检查点:

# 创建当前状态的检查点
(gdb) checkpoint

# 输出:checkpoint 1: fork returned pid 12345

恢复检查点:

# 恢复到检查点1
(gdb) restart 1

# 查看所有检查点
(gdb) info checkpoints

删除检查点:

# 删除检查点1
(gdb) delete checkpoint 1

10.6 调试core dump

生成core文件:

# 启用core dump
ulimit -c unlimited

# 设置core文件格式
echo "core.%e.%p" > /proc/sys/kernel/core_pattern

调试core文件:

# 加载core文件
gdb program core

# 或
gdb program
(gdb) core-file core

# 查看崩溃时的状态
(gdb) bt
(gdb) info registers
(gdb) info locals

分析崩溃原因:

# 查看崩溃位置
(gdb) where

# 查看信号信息
(gdb) info signal

# 查看内存映射
(gdb) info proc mappings

故障排除

问题1: 找不到调试符号

现象:

Reading symbols from ./program...
(No debugging symbols found in ./program)

原因: - 编译时没有添加-g选项 - 程序被strip删除了符号

解决方法:

# 重新编译,添加调试信息
gcc -g program.c -o program

# 检查是否包含调试信息
file program
# 应该显示:not stripped

# 如果已经strip,需要重新编译

问题2: 源代码找不到

现象:

(gdb) list
1       in program.c

原因: - 源文件路径改变 - 编译时使用了相对路径

解决方法:

# 设置源代码搜索路径
(gdb) directory /path/to/source
(gdb) dir /path/to/source

# 查看当前搜索路径
(gdb) show directories

# 替换源代码路径
(gdb) set substitute-path /old/path /new/path

问题3: 断点无法设置

现象:

(gdb) break main
No symbol "main" in current context.

原因: - 程序未加载 - 符号表损坏 - 函数名错误

解决方法:

# 确保程序已加载
(gdb) file program

# 查看所有函数
(gdb) info functions

# 使用地址设置断点
(gdb) break *0x400500

# 延迟断点(动态库)
(gdb) set breakpoint pending on
(gdb) break library_function

问题4: 变量显示为optimized out

现象:

(gdb) print x
$1 = <optimized out>

原因: - 编译器优化导致变量被优化

解决方法:

# 重新编译,禁用优化
gcc -g -O0 program.c -o program

# 或使用volatile
volatile int x = 0;

# 查看汇编代码确认变量位置
(gdb) disassemble

问题5: 远程调试连接失败

现象:

(gdb) target remote 192.168.1.100:1234
192.168.1.100:1234: Connection refused.

原因: - gdbserver未启动 - 防火墙阻止 - 网络不通

解决方法:

# 检查gdbserver是否运行
ps aux | grep gdbserver

# 检查端口是否监听
netstat -an | grep 1234

# 检查防火墙
sudo iptables -L

# 测试网络连接
ping 192.168.1.100
telnet 192.168.1.100 1234

问题6: 多线程调试混乱

现象: - 线程切换频繁 - 难以跟踪特定线程

解决方法:

# 锁定调度器
(gdb) set scheduler-locking on

# 只在特定线程设置断点
(gdb) break function thread 2

# 对所有线程执行命令
(gdb) thread apply all bt

问题7: GDB响应缓慢

原因: - 符号表过大 - 自动显示过多 - 记录模式开启

解决方法:

# 禁用自动显示
(gdb) undisplay

# 关闭记录模式
(gdb) record stop

# 禁用分页
(gdb) set pagination off

# 限制backtrace深度
(gdb) set backtrace limit 10

实用技巧

技巧1: 快速命令

命令缩写:

r     # run
c     # continue
s     # step
n     # next
b     # break
p     # print
bt    # backtrace
i     # info

重复上一条命令:

# 按Enter键重复上一条命令
(gdb) step
(gdb) <Enter>  # 再次执行step

技巧2: 命令历史

使用历史记录:

# 上一条命令
Ctrl+P  
# 下一条命令
Ctrl+N  
# 搜索历史
Ctrl+R

# 查看历史
(gdb) show commands

保存历史:

# 在~/.gdbinit中添加
set history save on
set history size 10000
set history filename ~/.gdb_history

技巧3: 条件表达式

复杂条件:

# 字符串比较
(gdb) break function if strcmp(str, "test") == 0

# 指针检查
(gdb) break function if ptr != 0 && *ptr == 100

# 范围检查
(gdb) break function if x >= 10 && x <= 20

技巧4: 便捷函数

调用函数:

# 调用printf
(gdb) call printf("x = %d\n", x)

# 调用自定义函数
(gdb) call my_debug_function()

# 修改全局状态
(gdb) call reset_state()

技巧5: 日志记录

记录GDB会话:

# 开始记录
(gdb) set logging on
(gdb) set logging file debug.log

# 记录所有输出
(gdb) set logging overwrite on

# 停止记录
(gdb) set logging off

技巧6: TUI模式

启用文本用户界面:

# 启动时开启TUI
gdb -tui program

# 在GDB中切换TUI
(gdb) tui enable
Ctrl+X Ctrl+A

# TUI布局
(gdb) layout src     # 源代码
(gdb) layout asm     # 汇编
(gdb) layout split   # 源代码+汇编
(gdb) layout regs    # 寄存器

# 刷新屏幕
Ctrl+L

技巧7: 便捷宏

在~/.gdbinit中定义宏:

# 打印数组
define parray
    set $i = 0
    while $i < $arg1
        print $arg0[$i]
        set $i = $i + 1
    end
end

# 使用:parray array 10

# 打印结构体数组
define pstruct_array
    set $i = 0
    while $i < $arg1
        print $arg0[$i]
        set $i = $i + 1
    end
end

# 十六进制dump
define hexdump
    set $addr = $arg0
    set $len = $arg1
    set $i = 0
    while $i < $len
        if $i % 16 == 0
            printf "\n0x%08x: ", $addr + $i
        end
        printf "%02x ", *(unsigned char*)($addr + $i)
        set $i = $i + 1
    end
    printf "\n"
end

技巧8: 调试技巧总结

调试流程: 1. 编译时添加-g选项 2. 设置断点在关键位置 3. 运行程序到断点 4. 检查变量和状态 5. 单步执行分析问题 6. 修改代码重新测试

常用调试模式: - 断点调试: 在关键位置停止 - 单步调试: 逐行执行分析 - 观察点调试: 监视变量变化 - 条件调试: 特定条件触发 - 反向调试: 回溯执行历史

总结

通过本教程,你学习了:

  • ✅ GDB调试器的基本概念和工作原理
  • ✅ 编译程序时添加调试信息的方法
  • ✅ GDB的启动和基本操作命令
  • ✅ 断点、观察点的设置和管理
  • ✅ 程序执行控制(单步、继续、跳转)
  • ✅ 变量、内存和寄存器的查看和修改
  • ✅ 调用栈分析和栈帧切换
  • ✅ 多线程程序的调试技巧
  • ✅ 远程调试和嵌入式调试
  • ✅ GDB脚本和自动化调试
  • ✅ 高级调试技巧和故障排除

关键要点: 1. 编译时必须添加-g选项才能使用GDB调试 2. 断点是最常用的调试工具,合理设置断点很重要 3. 单步执行可以详细分析程序执行流程 4. 观察点用于监视变量变化,找出修改位置 5. 远程调试适用于嵌入式和服务器调试 6. GDB脚本可以实现自动化调试和测试 7. 熟练使用命令缩写可以提高调试效率

下一步学习

建议继续学习以下内容:

初级进阶

中级进阶

高级进阶

实践项目建议

项目1: 简单程序调试

难度: ⭐ 目标: 使用GDB调试简单的C程序 任务: - 设置断点并运行程序 - 单步执行观察变量变化 - 修改变量值测试不同路径 - 分析调用栈

学习要点: - 基本GDB命令 - 断点使用 - 变量查看 - 程序流程控制

项目2: 多线程程序调试

难度: ⭐⭐ 目标: 调试多线程程序的竞态条件 任务: - 查看所有线程状态 - 切换线程进行调试 - 使用调度器锁定 - 找出竞态条件

学习要点: - 多线程调试 - 线程切换 - 调度器控制 - 竞态条件分析

项目3: 远程嵌入式调试

难度: ⭐⭐⭐ 目标: 使用GDB调试ARM嵌入式程序 任务: - 配置OpenOCD和GDB - 远程连接到目标板 - 设置断点调试固件 - 查看寄存器和内存

学习要点: - 远程调试配置 - OpenOCD使用 - 嵌入式调试技巧 - 硬件寄存器操作

项目4: 自动化调试脚本

难度: ⭐⭐⭐⭐ 目标: 编写GDB脚本实现自动化测试 任务: - 编写GDB命令脚本 - 使用Python扩展GDB - 实现自动化测试 - 生成测试报告

学习要点: - GDB脚本编写 - Python扩展 - 自动化测试 - 测试报告生成

常见问题FAQ

Q1: GDB和LLDB有什么区别?

A: - GDB: GNU项目,历史悠久,功能完整,支持广泛 - LLDB: LLVM项目,现代化设计,macOS默认调试器 - 建议: Linux用GDB,macOS可以用LLDB,命令类似

Q2: 如何调试没有源代码的程序?

A: - 使用反汇编:disassemble - 查看符号表:info functions - 设置地址断点:break *0x400500 - 分析调用栈:backtrace - 使用反编译工具辅助

Q3: GDB可以调试Python程序吗?

A: 可以,但需要特殊配置: - 安装python-dbg包 - 使用gdb的Python扩展 - 或使用pdb(Python调试器)

Q4: 如何在GDB中查看C++对象?

A:

# 打印对象
(gdb) print object

# 打印虚函数表
(gdb) info vtbl object

# 调用成员函数
(gdb) call object.method()

# 查看类型信息
(gdb) ptype ClassName

Q5: GDB支持哪些架构?

A: GDB支持众多架构: - x86/x86-64 - ARM/ARM64 - MIPS - PowerPC - RISC-V - AVR - 等等

Q6: 如何调试动态库?

A:

# 设置延迟断点
(gdb) set breakpoint pending on
(gdb) break library_function

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

# 加载符号
(gdb) add-symbol-file library.so 0x12345678

Q7: GDB可以调试内核吗?

A: 可以,使用KGDB: - 配置内核支持KGDB - 通过串口或网络连接 - 使用特殊的GDB命令 - 需要两台机器(调试机和目标机)

Q8: 如何提高GDB调试效率?

A: - 熟练使用命令缩写 - 配置.gdbinit文件 - 使用TUI模式 - 编写自定义脚本 - 使用条件断点减少停止次数 - 善用命令历史

参考资料

官方文档

  1. GDB官方文档 - 完整的GDB手册
  2. GDB用户手册 - 在线版本
  3. GDB Wiki - 社区维护的Wiki

教程和文章

  1. GDB Tutorial - 交互式教程
  2. Debugging with GDB - GNU官方教程
  3. GDB Cheat Sheet - 快速参考卡片

视频教程

  1. GDB Tutorial for Beginners - YouTube教程
  2. Advanced GDB Usage - 高级技巧

书籍推荐

  1. "The Art of Debugging with GDB, DDD, and Eclipse" - Norman Matloff
  2. "Debugging with GDB" - Richard Stallman
  3. "Advanced Linux Programming" - Mark Mitchell

在线资源

  1. Stack Overflow - GDB标签
  2. GDB邮件列表
  3. GDB源代码

工具和插件

  1. GDB Dashboard: 增强的GDB界面
  2. GEF: GDB增强框架
  3. PEDA: Python Exploit Development Assistance
  4. pwndbg: CTF和漏洞分析工具

附录

附录A: GDB命令速查表

程序控制

命令 简写 说明
run r 运行程序
continue c 继续执行
step s 单步进入
next n 单步跳过
finish fin 执行到返回
until u 执行到指定位置
jump j 跳转到指定位置
quit q 退出GDB

断点管理

命令 简写 说明
break b 设置断点
tbreak tb 临时断点
watch - 设置观察点
delete d 删除断点
disable - 禁用断点
enable - 启用断点
info breakpoints i b 查看断点

数据查看

命令 简写 说明
print p 打印变量
display - 自动显示
x - 查看内存
info registers i r 查看寄存器
info locals i locals 查看局部变量
info args i args 查看参数

栈帧操作

命令 简写 说明
backtrace bt 查看调用栈
frame f 选择栈帧
up - 上一层栈帧
down - 下一层栈帧
info frame i f 查看栈帧信息

源代码

命令 简写 说明
list l 显示源代码
disassemble disas 反汇编
info functions i functions 查看函数
info variables i variables 查看变量

附录B: 常用格式说明

打印格式

  • /x - 十六进制
  • /d - 十进制
  • /u - 无符号十进制
  • /o - 八进制
  • /t - 二进制
  • /a - 地址
  • /c - 字符
  • /f - 浮点数
  • /s - 字符串

内存查看格式

  • b - 字节(1字节)
  • h - 半字(2字节)
  • w - 字(4字节)
  • g - 双字(8字节)

附录C: .gdbinit配置示例

# 历史记录
set history save on
set history size 10000
set history filename ~/.gdb_history

# 输出美化
set print pretty on
set print array on
set print array-indexes on
set print elements 200

# 禁用分页
set pagination off

# 反汇编风格(Intel语法)
set disassembly-flavor intel

# 自动加载.gdbinit
set auto-load safe-path /

# 启动时显示信息
set verbose off

# 确认操作
set confirm off

# 线程调试
set non-stop on
set target-async on

# Python支持
python
import sys
sys.path.insert(0, '/path/to/gdb/scripts')
end

# 自定义命令
define hook-stop
    info registers
    x/5i $pc
end

反馈与支持: - 如果你在学习过程中遇到问题,欢迎在评论区留言 - 发现文档错误或有改进建议,请提交Issue - 想要分享你的GDB使用经验,欢迎投稿

版本历史: - v1.0 (2024-01-15): 初始版本发布

许可证: 本文档采用 CC BY-SA 4.0 许可协议