第一章:GDB入门——从零开始认识这个调试利器

1.1 GDB是什么?

GDB,全称GNU Debugger,是Linux平台上最经典的调试器。说白了,它就是一个能让你「看穿」程序运行过程的工具。

我刚开始接触GDB时,觉得它就是个命令行工具,黑乎乎的界面,远不如IDE的图形化调试来得直观。但用久了你会发现——GDB才是真正能解决问题的硬核工具。IDE做不到的,它能做;IDE看不到的,它能看。

GDB能让你做到三件事:

  • 控制程序执行——想停就停,想跑就跑
  • 观察程序状态——变量、内存、寄存器,一览无余
  • 修改程序行为——运行时改值、改流程,现场修复

一句话总结:GDB就是程序员的「显微镜」和「手术刀」。

1.2 GDB能做什么?

我遇到过不少同事,觉得调试就是「加printf打印日志」。嗯,这确实是最原始的方法。但你想过没有——

  • 生产环境不能随便加日志怎么办?
  • 段错误(Segmentation Fault)发生时,你根本不知道在哪崩溃?
  • 多线程死锁了,日志里看不出任何线索?

这些场景,GDB都能搞定。具体来说:

功能 说明 我常用的场景
断点调试 在指定行、函数、条件处暂停 排查逻辑错误,逐行看变量变化
查看调用栈 程序崩溃时,看是谁调了谁 段错误后,bt命令直接定位
内存检查 查看指针指向的内容、堆栈数据 排查野指针、内存泄漏
修改变量 运行时改值,绕过错误分支 测试异常路径,不用重新编译
反汇编 看CPU到底在执行什么指令 优化热点代码时,看汇编级效率

我的经验:GDB最强大的地方不是「调试」,而是「诊断」。尤其是线上问题,用GDB attach到正在运行的进程上,现场分析,这才是它的杀手锏。

1.3 GDB的安装与启动

安装GDB非常简单。不同系统略有差异:

# Ubuntu/Debian
sudo apt-get install gdb

# CentOS/RHEL
sudo yum install gdb

# macOS (通过Homebrew)
brew install gdb

# 验证安装
gdb --version

安装完成后,启动GDB有两种方式:

  1. 直接调试程序:gdb ./myprogram
  2. 分析core dump:gdb ./myprogram core.12345
  3. 附加到运行中的进程:gdb -p 12345

注意:用GDB调试程序,编译时一定要加 -g 选项。否则你看到的只有汇编代码,没有源码行号。我曾经帮一个同事排查问题,他编译时没加 -g,我折腾了半天才发现——嗯,这个坑踩过一次就不会忘了。

1.4 第一个GDB调试会话

光说不练假把式。我们来写一个简单的C程序,亲手走一遍GDB调试流程。

先准备一个示例程序 hello.c:

#include <stdio.h>

void say_hello(const char *name) {
    printf("Hello, %s!\n", name);
}

int main() {
    char *names[] = {"Alice", "Bob", "Charlie"};
    int i;
    
    for (i = 0; i <= 3; i++) {
        say_hello(names[i]);
    }
    
    return 0;
}

注意看,这个程序有个明显的bug——循环条件是 i <= 3,但数组只有3个元素(下标0、1、2)。当i=3时,访问 names[3] 会越界。

编译并启动GDB:

gcc -g -o hello hello.c
gdb ./hello

进入GDB后,你会看到类似这样的提示符:(gdb)。接下来我们一步步操作:

(gdb) list          # 查看源码
1       #include <stdio.h>
2       
3       void say_hello(const char *name) {
4           printf("Hello, %s!\n", name);
5       }
6       
7       int main() {
8           char *names[] = {"Alice", "Bob", "Charlie"};
9           int i;
10          
11          for (i = 0; i <= 3; i++) {
12              say_hello(names[i]);
13          }
14          
15          return 0;
16      }

(gdb) break 12      # 在第12行设置断点
Breakpoint 1 at 0x40054f: file hello.c, line 12.

(gdb) run           # 运行程序
Starting program: /home/user/hello 

Breakpoint 1, main () at hello.c:12
12              say_hello(names[i]);

(gdb) print i       # 查看当前i的值
$1 = 0

(gdb) print names[i]  # 查看names[i]的值
$2 = 0x4006e8 "Alice"

(gdb) continue      # 继续运行
Hello, Alice!

Breakpoint 1, main () at hello.c:12
12              say_hello(names[i]);

(gdb) print i
$3 = 1

(gdb) continue      # 继续,重复几次...
Hello, Bob!

Breakpoint 1, main () at hello.c:12
12              say_hello(names[i]);

(gdb) print i
$4 = 2

(gdb) continue
Hello, Charlie!

Breakpoint 1, main () at hello.c:12
12              say_hello(names[i]);

(gdb) print i
$5 = 3

(gdb) print names[3]  # 看!越界了
$6 = 0x0

(gdb) step          # 单步进入函数
say_hello (name=0x0) at hello.c:4
4           printf("Hello, %s!\n", name);

(gdb) print name
$7 = 0x0            # 传入的是NULL指针!

(gdb) quit          # 退出GDB

看到了吗?当i=3时,names[3] 是NULL。程序虽然没立刻崩溃,但传入NULL给printf,在某些系统上会直接段错误。这就是GDB帮我们抓到的bug。

我的习惯:每次写循环,我都会先用GDB跑一遍边界条件。尤其是 i <= n 这种写法,十有八九会出问题。改成 i < n 就安全了。

1.5 本章知识体系

下面这张图,帮你理清GDB入门的核心脉络:

GDB入门知识体系 GDB调试器 GDB是什么 GDB能做什么 安装与启动 第一个GDB调试会话 控制执行 观察状态 修改行为 断点调试 调用栈 内存检查 反汇编 apt/yum brew gdb 程序 gdb -p list/break run/continue print/step 核心命令:list, break, run, continue, print, step, quit

1.6 本章小结

这一章我们走完了GDB的入门流程。你学会了:

  • GDB是什么——一个能让你「看穿」程序的调试器
  • GDB能做什么——断点、调用栈、内存检查、反汇编
  • 怎么安装和启动——apt/yum/brew,三种方式
  • 第一个调试会话——亲手抓到了一个数组越界的bug

说实话,GDB的学习曲线确实有点陡。但别怕,多用几次就习惯了。我刚开始用GDB时,连 quit 命令都记不住,每次都是Ctrl+D退出。现在?闭着眼睛都能敲出常用的调试流程。

下一章,我们会深入GDB的断点机制——条件断点、硬件断点、数据断点,这些才是真正提升调试效率的利器。

可可精品资料库