Linux 下使用 GDB 调试 C++ 的全面总结

这是一份面向实际开发的 GDB 中文速查与进阶指南,覆盖从编译参数、断点、单步、变量查看,到线程、core dump、动态库、远程调试、反向调试与脚本化等常用和进阶能力。

你会得到什么

  • 一套完整的 GDB 命令地图
  • 常见调试工作流的直接示例
  • C++ 场景下的重点:STL、引用、虚函数、线程、异常
  • 排查"断点不生效、变量看不对、源码对不上"这类常见问题的方法

说明:GDB 功能很多,不可能在一页里穷尽每个冷门子命令。本文目标是"尽量全面并且可直接使用",覆盖绝大多数 Linux 下 C++ 调试场景。

核心原则:先带调试信息编译,再用最少命令复现问题,然后优先看调用栈、当前线程、关键变量和值的变化。

1. 调试前准备

1.1 编译选项

GDB 是否好用,首先取决于你是否正确编译。最基本的是带调试信息,必要时降低优化级别。

复制代码
g++ -g -O0 -std=c++20 main.cpp -o app

# 如果要保留一定优化但仍可调试
g++ -g -Og -fno-omit-frame-pointer main.cpp -o app

-g

生成调试符号,没有它很多源码级调试能力会失效。

-O0 / -Og

避免变量被优化掉、代码重排严重、单步行为和源码不一致。

-fno-omit-frame-pointer

更利于调用栈回溯,特别是线上问题和性能分析场景。

如果二进制使用了高优化,例如 -O2-O3,你仍然可以调试,但要准备接受"变量被优化掉""单步跳来跳去""当前行和执行位置不完全一致"等现象。

1.2 Core Dump 开关

复制代码
ulimit -c unlimited
cat /proc/sys/kernel/core_pattern

如果程序崩溃后要分析 core 文件,先确认系统允许生成 core。

1.3 本文配套示例程序

下面这份 gdb_all.cpp 是贯穿全文所有章节的唯一代码,编译一次即可练习本文每个知识点。后续每个子章节的"▸ 示例"都直接引用这份代码中的函数。

复制代码
g++ -std=c++20 -g -O0 -pthread -rdynamic gdb_all.cpp -o gdb_all

#include <algorithm>
#include <cstring>
#include <iostream>
#include <map>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>

// ===== 全局变量(用于 2.3、5.2 演示) =====
int globalCounter = 0;

// ===== 基础函数(用于 3.x 演示) =====
int add(int a, int b) { return a + b; }
int multiply(int x, int y) { return x * y; }

int compute(int n)
{
    int sum = add(n, 10);
    int product = multiply(sum, 2);
    return product;
}

int loopSum(int upperBound)
{
    int sum = 0;
    for (int i = 1; i <= upperBound; ++i)
        sum += i;
    return sum;
}

// ===== 数据结构(用于 4.x、5.x 演示) =====
struct Point { int x; int y; };

struct Student
{
    int id;
    std::string name;
    double gpa;
};

class Calculator
{
public:
    explicit Calculator(std::string tag) : tag_(std::move(tag)) {}
    int process(int input) const { return helper(input) + 3; }
    const std::string &getTag() const { return tag_; }
private:
    int helper(int input) const { return input * 2; }
    std::string tag_;
};

// ===== 观察点演示(用于 4.6) =====
void mutateValue(int &tracked)
{
    for (int i = 0; i < 4; ++i)
        tracked += i + 1;   // 每轮 +1, +2, +3, +4
}

// ===== 断点命令演示(用于 4.5) =====
void accumulate(int &val)
{
    for (int i = 0; i < 6; ++i)
        val += (i + 1) * 2;
}

// ===== 指针与内存(用于 5.5) =====
void deeper(Point *p)
{
    p->x += 10;
    p->y += 20;
}

void middle(Point &p, const std::string &tag)
{
    deeper(&p);
    std::cout << tag << ": " << p.x << "," << p.y << '\n';
}

// ===== 多态与虚函数(用于 10.3、10.4) =====
class Shape
{
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape
{
public:
    explicit Circle(double r) : radius_(r) {}
    double area() const override { return 3.14159 * radius_ * radius_; }
private:
    double radius_;
};

class Rectangle : public Shape
{
public:
    Rectangle(double w, double h) : w_(w), h_(h) {}
    double area() const override { return w_ * h_; }
private:
    double w_, h_;
};

// ===== 异常(用于 4.7、10.5) =====
int riskyDivide(int a, int b)
{
    if (b == 0) throw std::runtime_error("divide by zero");
    return a / b;
}

// ===== 线程(用于 7.x) =====
std::mutex mtx;
void threadWorker(int id, int &counter)
{
    for (int r = 0; r < 3; ++r)
    {
        std::lock_guard<std::mutex> lk(mtx);
        counter += id + r;
    }
}

// ===== 崩溃(用于 8.x) =====
struct Node { int value; Node *next; };

void processNode(Node *node)
{
    std::cout << "value=" << node->value << '\n';
}

void traverse(Node *head)
{
    while (head) { processNode(head); head = head->next; }
    processNode(head);  // BUG: head 已是 nullptr
}

// ===== 场景入口 =====
void runStep()       { std::cout << "compute(5)=" << compute(5) << '\n'; }
void runLoop()       { std::cout << "loopSum(10)=" << loopSum(10) << '\n'; }
void runBreakpoint() { int v=0; accumulate(v); std::cout << "acc=" << v << '\n'; }
void runWatch()      { int t=10; mutateValue(t); std::cout << "watch=" << t << '\n'; }
void runInspect()
{
    Point pt{3,7};
    int arr[] = {10,20,30,40,50};
    std::string msg = "hello gdb";
    std::vector<int> nums = {100,200,300};
    middle(pt, msg);
}
void runStl()
{
    std::vector<Student> stu = {{1,"alice",3.8},{2,"bob",3.5},{3,"carol",3.9}};
    std::map<std::string,int> wc = {{"hello",3},{"world",5},{"gdb",7}};
    Calculator calc("demo");
    std::cout << stu.size() << " " << wc.size() << " " << calc.process(4) << '\n';
}
void runPoly()
{
    std::unique_ptr<Shape> s1 = std::make_unique<Circle>(5.0);
    std::unique_ptr<Shape> s2 = std::make_unique<Rectangle>(3.0, 4.0);
    std::cout << "circle=" << s1->area() << " rect=" << s2->area() << '\n';
}
void runException()
{
    try { riskyDivide(10, 0); }
    catch (const std::exception &e) { std::cout << "caught: " << e.what() << '\n'; }
}
void runThread()
{
    int counter = 0;
    std::thread t1(threadWorker, 1, std::ref(counter));
    std::thread t2(threadWorker, 2, std::ref(counter));
    t1.join(); t2.join();
    std::cout << "threads counter=" << counter << '\n';
}
void runCrash()
{
    Node c{30,nullptr}, b{20,&c}, a{10,&b};
    traverse(&a);
}

int main(int argc, char **argv)
{
    if (argc != 2) {
        std::cout << "Usage: " << argv[0]
            << " <step|loop|breakpoint|watch|inspect|stl|poly|exception|thread|crash>\n";
        return 1;
    }
    std::string s = argv[1];
    if (s == "step")       runStep();
    else if (s == "loop")       runLoop();
    else if (s == "breakpoint") runBreakpoint();
    else if (s == "watch")      runWatch();
    else if (s == "inspect")    runInspect();
    else if (s == "stl")        runStl();
    else if (s == "poly")       runPoly();
    else if (s == "exception")  runException();
    else if (s == "thread")     runThread();
    else if (s == "crash")      runCrash();
    else std::cout << "unknown: " << s << '\n';
    return 0;
}

编译这一份程序后,后续每个章节的示例都直接用 gdb --args ./gdb_all <场景名> 来练习,无需再编译其他文件。

2. 启动 GDB 的方式

方式 命令 用途
调试可执行文件 gdb ./app 最常见方式
带参数启动 gdb --args ./app arg1 arg2 程序启动参数较多时更方便
附加到运行中的进程 gdb -p PID 分析卡死、死循环、现场问题
分析 core gdb ./app core.xxx 崩溃后离线分析
命令行直接执行 gdb -ex "set pagination off" -ex "run" ./app 自动化或批处理

2.1 进入后常见初始化

复制代码
set pagination off
set print pretty on
set breakpoint pending on
set disassemble-next-line on
set confirm off

▸ 示例

复制代码
$ gdb ./gdb_all
(gdb) set pagination off           # 翻页关掉,不会停下来问你按回车
(gdb) set print pretty on          # 结构体和STL容器分行缩进显示
(gdb) set breakpoint pending on    # 对尚未加载的库也能设断点
(gdb) break main
(gdb) run step
  # 现在 STL 打印更易读,不需要每次手动设置
  # 建议把这几行写进 ~/.gdbinit 自动执行

2.2 命令帮助

复制代码
help
help break
apropos thread
show args

记不住命令时,先用 helpapropos 检索,效率很高。

▸ 示例

复制代码
$ gdb ./gdb_all
(gdb) help breakpoints             # 看断点相关所有命令的摘要
(gdb) help watch                   # 看 watch 命令详细用法
(gdb) apropos thread               # 搜索所有包含 "thread" 的命令
  # info threads -- Display currently known threads
  # thread -- Use this command to switch between threads
  # thread apply -- Apply a command to a list of threads
(gdb) apropos reverse              # 搜索反向调试相关命令

2.3 程序运行上下文与启动选项

复制代码
file ./app
set args --mode=test input.txt
show args
path /custom/bin
show paths
set environment LOG_LEVEL=debug
show environment LOG_LEVEL
cd /tmp/testcase
pwd
tty /dev/pts/3
info terminal

这组命令对应网页里强调的"运行上下文"能力:装入目标文件、设置运行参数、运行路径、环境变量、工作目录,以及将程序输入输出绑定到指定终端。

▸ 示例(file / set args / set environment)

复制代码
$ gdb
(gdb) file ./gdb_all               # 会话里装入可执行文件
(gdb) set args step                # 设置参数
(gdb) show args                    # "step"
(gdb) set environment MY_VAR=test
(gdb) show environment MY_VAR      # MY_VAR=test
(gdb) pwd                          # 查看当前工作目录
(gdb) run                          # 用上面的参数+环境变量运行
  # compute(5)=30
(gdb) set args watch               # 改参数重新跑另一个场景
(gdb) run
  # watch=20

3. 运行与单步控制

命令 缩写 作用
run r 启动程序
start main 的第一行前停下
starti 从第一条机器指令开始
continue c 继续运行到下一个断点
next n 源码级单步,不进入函数
step s 源码级单步,会进入函数
nexti ni 指令级单步,不进入调用
stepi si 指令级单步,会进入调用
finish 运行到当前函数返回,并打印返回值
until u 运行到当前循环后面或指定位置
jump line 强行修改下一条执行位置,慎用
return [expr] 强制当前函数立即返回,可指定返回值

3.1 运行状态与中止控制

复制代码
info program
kill
Ctrl + C

info program 可查看程序是否在运行、为什么停住;Ctrl + C 常用于把正在跑的程序中断到当前点;kill 则是终止当前 inferior。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) break compute
(gdb) run
  # Breakpoint 1, compute (n=5) at gdb_all.cpp:XX
(gdb) info program
  # Using the running image of child process XXXX.
  # Program stopped at 0x...
  # It stopped at breakpoint 1.
(gdb) kill                         # 终止当前运行
  # Kill the program being debugged? (y or n) y
(gdb) info program
  # The program being debugged is not being run.
(gdb) run                          # 重新启动
  # 又停在 compute 断点

3.2 强制调用和强制返回

复制代码
call obj.validate()
call printf("value=%d\n", value)
print expensiveCheck()
return false

callprint 都可以执行表达式或函数调用;return 适合跳出当前函数验证边界分支,但只建议在你明确知道副作用时使用。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) break compute
(gdb) run
  # Breakpoint 1, compute (n=5)
(gdb) call add(100, 200)           # 在断点处直接调用函数
  # $1 = 300
(gdb) call printf("n=%d\n", n)     # 调用 printf 打印
  # n=5
(gdb) step                         # 进入 add()
(gdb) return 999                   # 强制让 add 返回 999(跳过真正执行)
  # Make add return now? (y or n) y
(gdb) print sum                    # sum 现在是 999 而不是 15
  # $2 = 999

调试时建议先用 start,再设置关键断点,然后用 nsfinish 组合推进,大多数问题不需要一开始就指令级调试。

3.3 next / step / finish / until / continue 综合演练

▸ 示例 (使用 gdb_all 的 step 场景:main → runStep → compute → add / multiply

复制代码
$ gdb --args ./gdb_all step
(gdb) break runStep
(gdb) run
  # Breakpoint 1, runStep () at gdb_all.cpp:XX

(gdb) step                         # 进入 compute(5)
  # compute (n=5) at gdb_all.cpp:XX
(gdb) step                         # 进入 add(5, 10)
  # add (a=5, b=10) at gdb_all.cpp:XX
(gdb) bt                           # main → runStep → compute → add
  # #0 add (a=5, b=10)
  # #1 compute (n=5)
  # #2 runStep ()
  # #3 main (argc=2, argv=...)
(gdb) finish                       # 从 add() 返回
  # Value returned is $1 = 15
(gdb) next                         # 不进入 multiply,直接执行
(gdb) print product                # 30
(gdb) finish                       # 从 compute() 返回
  # Value returned is $2 = 30

# ---- until 跳出循环 ----
(gdb) run                          # 重新运行
(gdb) break loopSum
(gdb) continue
  # ⚠ 注意:loopSum 不在 step 场景里,改用 loop 场景:
(gdb) set args loop
(gdb) run
  # Breakpoint 2, loopSum (upperBound=10)
(gdb) next                         # 进入循环
(gdb) until                        # 跳出循环,直接到 return sum;
(gdb) print sum                    # 55
(gdb) continue                     # 程序结束

练习重点:next 跳过函数、step 进入函数、finish 返回并打印返回值、until 跳出循环。

4. 断点、观察点、捕获点

4.1 普通断点

复制代码
break main
break file.cpp:120
break ClassName::method
break namespace::ClassName::method
break +5
break -3
break *0x40123a
break
info breakpoints
delete 3
disable 2
enable 2

网页里额外强调了几种经常被遗漏的断点形式:break +offset / break -offset 用于按当前行偏移下断点,break *address 用于按指令地址下断点,裸 break 则表示在下一条指令处停下。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) break compute                # 按函数名
(gdb) break add                    # 再加一个
(gdb) info breakpoints             # 列出所有断点
  # Num  Type       Disp  Enb  Address    What
  # 1    breakpoint keep  y    0x...      in compute(int)
  # 2    breakpoint keep  y    0x...      in add(int,int)
(gdb) disable 2                    # 暂时禁用 add 的断点
(gdb) run
  # 只停在 compute,不会停在 add
(gdb) enable 2
(gdb) delete 1                     # 删除 compute 断点

4.2 条件断点

复制代码
break file.cpp:120 if count > 10
condition 2 ptr == nullptr

条件断点非常适合排查循环中第 N 次出错、只有某个对象状态异常时才停的问题。

▸ 示例

复制代码
$ gdb --args ./gdb_all loop
(gdb) break loopSum
(gdb) run
(gdb) break +3 if i == 8           # 循环体,仅当 i==8 停
(gdb) continue
(gdb) print i                      # 8
(gdb) print sum                    # 28(1+...+7)
(gdb) condition 2 i == 10          # 改条件
(gdb) continue
(gdb) print i                      # 10
(gdb) print sum                    # 45(1+...+9)

4.3 临时断点与正则断点

复制代码
tbreak main
rbreak ^Foo::

tbreak 触发一次后自动删除;rbreak 可以按正则批量下断点。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) tbreak compute               # 临时断点
(gdb) run
  # Temporary breakpoint, compute (n=5)
(gdb) continue                     # 不会再停了(已自动删除)
  # 程序正常结束

(gdb) rbreak ^run                  # 正则:所有 run 开头的函数
  # Breakpoint at runStep, runLoop, runBreakpoint ...
(gdb) info breakpoints             # 一次设了很多断点

4.4 停止点维护与忽略次数

复制代码
clear
clear file.cpp:120
clear ClassName::method
delete 3
disable 2 4
enable 2
enable once 5
enable delete 6
ignore 2 100

ignore 非常适合循环内断点,只想让程序在第 101 次命中时再停;enable onceenable delete 则能减少手工维护断点状态。

▸ 示例

复制代码
$ gdb --args ./gdb_all breakpoint
(gdb) break accumulate
(gdb) run
(gdb) break +2                     # 循环体断点
(gdb) ignore 2 4                   # 前 4 次跳过
(gdb) continue
(gdb) print i                      # 4(跳过了 0,1,2,3)
(gdb) enable once 2                # 只再停一次
(gdb) continue                     # 停
(gdb) continue                     # 不再停了

4.5 断点命令

复制代码
break file.cpp:88
commands
silent
print value
bt
continue
end

可以给断点绑定动作,让它像轻量日志一样自动打印信息而不中断流程。

▸ 示例

复制代码
$ gdb --args ./gdb_all breakpoint
(gdb) break accumulate
(gdb) run
(gdb) break +2
(gdb) commands 2
  silent
  printf "i=%d val=%d\n", i, val
  continue
  end
(gdb) continue
  # i=0 val=2
  # i=1 val=6
  # i=2 val=12
  # i=3 val=20
  # i=4 val=30
  # i=5 val=42
  # acc=42(GDB 自动打印不停顿)

4.6 观察点 Watchpoint

复制代码
watch x
rwatch x
awatch x
命令 含义
watch expr 表达式被写入时停下
rwatch expr 表达式被读取时停下
awatch expr 表达式被读或写时停下

观察点依赖硬件支持,数量有限,而且被观察的对象必须在当前上下文可解析。对于定位"谁改坏了这个值",它非常有效。

▸ 示例

复制代码
$ gdb --args ./gdb_all watch
(gdb) break mutateValue
(gdb) run
(gdb) watch tracked
(gdb) continue
  # Hardware watchpoint: Old=10, New=11
(gdb) bt                           # 确认是 mutateValue 改的
(gdb) continue                     # Old=11, New=13
(gdb) continue                     # Old=13, New=16
(gdb) continue                     # Old=16, New=20
(gdb) continue                     # watch=20,程序结束

4.7 捕获点 Catchpoint

复制代码
catch throw
catch catch
catch syscall open
catch fork
catch vfork
catch exec

常用于异常抛出、系统调用、进程分叉等场景的拦截。

▸ 示例

复制代码
$ gdb --args ./gdb_all exception
(gdb) catch throw
(gdb) run
  # Catchpoint (exception thrown)
(gdb) bt
  # #0 __cxa_throw
  # #1 riskyDivide (a=10, b=0)
(gdb) frame 1
(gdb) print b                      # 0
(gdb) continue                     # caught: divide by zero

4.8 C++ 重载函数断点

复制代码
break Foo::bar(int)
break Foo::bar(std::string const&)
break namespace::Foo::bar(double)

网页里专门提到了 C++ 重载函数的断点歧义。遇到同名重载时,直接写出参数类型,或者让 GDB 弹出断点菜单供你选择。

▸ 示例

复制代码
$ gdb --args ./gdb_all stl
(gdb) break Calculator::process    # 类方法断点
(gdb) run
  # Breakpoint, Calculator::process (this=0x..., input=4)
(gdb) print *this                  # {tag_ = "demo"}
(gdb) step                         # 进入 helper()
(gdb) bt
  # #0 Calculator::helper (this=..., input=4)
  # #1 Calculator::process (this=..., input=4)

5. 查看变量、内存、类型与调用栈

Examine memory: x/FMT ADDRESS.

ADDRESS is an expression for the memory address to examine.

FMT is a repeat count followed by a format letter and a size letter.

Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),

t(binary), f(float), a(address), i(instruction), c(char) and s(string).

Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).

The specified number of objects of the specified size are printed

according to the format.

Defaults for format and size letters are those previously used.

Default count is 1. Default address is following last thing printed

with this command or "print".

(gdb) x/32xb p_stSessionCtx->stProxyDB.list_addr

0x8bb5dc8: 0x36 0x30 0x30 0x36 0x00 0x00 0x00 0x00

0x8bb5dd0: 0x00 0x00 0x30 0x30 0x30 0x36 0x30 0x30

0x8bb5dd8: 0x37 0x00 0x00 0x00 0x00 0x00 0x00 0x30

0x8bb5de0: 0x30 0x30 0x00 0x00 0x29 0x00 0x00 0x00

(gdb) p/x p_stSessionCtx->stProxyDB.list_addr

$5 = 0x8bb5dc8

(gdb) x/32cb p_stSessionCtx->stProxyDB.list_addr

0x8bb5dc8:54 '6' 48 '0' 48 '0' 54 '6' 0 '\0' 0 '\0' 0 '\0' 0'\0'

0x8bb5dd0:0 '\0' 0 '\0' 48 '0' 48 '0' 48 '0' 54 '6' 48 '0' 48'0'

0x8bb5dd8:55 '7' 0 '\0' 0 '\0' 0 '\0' 0 '\0' 0 '\0' 0 '\0' 48'0'

0x8bb5de0:48 '0' 48 '0' 0 '\0' 0 '\0' 41 ')' 0 '\0' 0 '\0' 0'\0'

5.1 查看变量和值

复制代码
print x
p x
p *ptr
p obj.member
p this
display x
undisplay 1

display 会在每次停下时自动显示表达式,适合盯关键变量。

▸ 示例

复制代码
$ gdb --args ./gdb_all inspect
(gdb) break deeper
(gdb) run
  # Breakpoint, deeper (p=0x...) at gdb_all.cpp:XX
(gdb) print *p                     # {x = 3, y = 7}
(gdb) print p->x                   # 3
(gdb) display p->x                 # 每次停下自动显示
(gdb) display p->y
(gdb) next                         # p->x += 10
  # 1: p->x = 13
  # 2: p->y = 7
(gdb) next                         # p->y += 20
  # 1: p->x = 13
  # 2: p->y = 27
(gdb) undisplay 1                  # 取消跟踪 p->x

5.2 表达式格式、全局变量与数组视图

复制代码
p/x value
p/d value
p/t flags
p 'file.cpp'::globalCounter
p functionName::localStatic
p *array@len
p {int}0x7fffffffd9ac

网页里补充得比较完整的几项这里一并加上:p/xp/t 这类格式化输出;用 'file.cpp'::var 指定同名全局变量;用 @ 把一段连续内存当数组显示;用 {type}addr 把某个地址按指定类型解释。

▸ 示例

复制代码
$ gdb --args ./gdb_all inspect
(gdb) break middle
(gdb) run
  # 停在 middle()
(gdb) up                           # 切到 runInspect()
(gdb) print *arr@5                 # C 数组当数组看:{10,20,30,40,50}
(gdb) print arr[2]                 # 30
(gdb) p/x arr[0]                   # 0xa(十六进制)
(gdb) p/t arr[0]                   # 1010(二进制)
(gdb) print globalCounter          # 全局变量:0
(gdb) print nums                   # vector: {100,200,300}

5.3 查看局部变量和参数

复制代码
info locals
info args
ptype obj
whatis obj

▸ 示例

复制代码
$ gdb --args ./gdb_all inspect
(gdb) break deeper
(gdb) run
(gdb) info locals                  # deeper 没有局部变量,只有参数 p
(gdb) info args                    # p = 0x...
(gdb) up                           # 切到 middle()
(gdb) info args                    # p = @...: {x=3,y=7}, tag = "hello gdb"
(gdb) info locals                  # (无)
(gdb) up                           # 切到 runInspect()
(gdb) info locals                  # pt, arr, msg, nums
(gdb) ptype Point                  # type = struct Point { int x; int y; }
(gdb) whatis msg                   # type = std::string

5.4 查看调用栈

复制代码
backtrace
bt
bt full
bt 5
bt -3
frame 3
up
down
info frame

bt full 很重要,特别适合崩溃分析,因为它会同时打印每层栈帧里的局部变量。

复制代码
select-frame 2
up-silently
down-silently

如果你只想切栈帧,不想每次都打印冗长信息,可以使用这些 silent 变体。

▸ 示例

复制代码
$ gdb --args ./gdb_all inspect
(gdb) break deeper
(gdb) run
(gdb) bt
  # #0 deeper (p=0x...) at gdb_all.cpp:XX
  # #1 middle (p=..., tag="hello gdb") at gdb_all.cpp:XX
  # #2 runInspect () at gdb_all.cpp:XX
  # #3 main (argc=2, ...) at gdb_all.cpp:XX
(gdb) bt full
  # #0 deeper: p = 0x...
  # #1 middle: p = @...: {x=3,y=7}, tag = "hello gdb"
  # #2 runInspect: pt={x=3,y=7}, arr={10,20,...}, msg="hello gdb", nums={100,200,300}
(gdb) frame 2                      # 切到 runInspect
(gdb) info locals
(gdb) up                           # 再上一层:main
(gdb) down                         # 回到 runInspect

5.5 查看内存

复制代码
x/16xw ptr
x/32bx buffer
x/10i $pc
x/s strPtr
格式 含义
x/NFU address N 是数量,F 是显示格式,U 是单元大小
x/16xw ptr 以十六进制查看 16 个 word
x/s ptr 按 C 字符串查看
x/10i $pc 从当前指令地址开始反汇编 10 条

▸ 示例

复制代码
$ gdb --args ./gdb_all inspect
(gdb) break deeper
(gdb) run
(gdb) up 2                         # 切到 runInspect()
(gdb) print &arr                   # 获取 arr 地址
  # (int (*)[5]) 0x7fffffffXXXX
(gdb) x/5dw &arr                   # 以十进制查看 5 个 int
  # 10  20  30  40  50
(gdb) x/5xw &arr                   # 以十六进制
  # 0xa  0x14  0x1e  0x28  0x32
(gdb) x/s msg.c_str()              # 查看 string 底层内容
  # "hello gdb"
(gdb) x/10i $pc                    # 当前位置反汇编 10 条指令

5.6 自动显示、值历史与便利变量

复制代码
display/i $pc
info display
disable display 1
enable display 1
show values
print $1
set $index = 0
show convenience

网页里有一块经常被现代教程忽略:GDB 会记录每次 print 的结果到 $1$2 这样的值历史里;同时还支持自定义便利变量,例如 set $index = 0,适合做重复检查和临时脚本。

▸ 示例

复制代码
$ gdb --args ./gdb_all inspect
(gdb) break deeper
(gdb) run
(gdb) print *p                     # $1 = {x=3, y=7}
(gdb) print p->x                   # $2 = 3
(gdb) print $1                     # 引用之前的值历史:{x=3, y=7}
(gdb) set $saved = p->y            # 自定义便利变量
(gdb) next                         # p->x += 10
(gdb) print $saved                 # 仍然是 7(修改前的值)
(gdb) print p->y                   # 还是 7(还没执行 y+=20)
(gdb) show values                  # 列出所有值历史

5.7 修改变量

复制代码
set var x = 42
set var ptr = nullptr
set variable obj.member = 100

可以临时修改程序状态验证猜想,但不要把调试中的临时改动误当成根因修复。

网页里特别提醒了一点:修改程序变量时尽量优先使用 set var,因为裸 set 可能和 GDB 自己的设置项重名,导致语义混淆。

5.8 寄存器

复制代码
info registers
info all-registers
p $pc
p $sp
p $fp
set $pc = 0x401080

网页补充了寄存器视角的排查手段。源码栈损坏、汇编级跳转、ABI 相关问题时,寄存器和 $pc$sp 这些特殊寄存器非常关键。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) break add
(gdb) run
(gdb) info registers               # 列出通用寄存器
  # rax  rbx  rcx  rdx  rsi  rdi  ...
(gdb) print $rdi                   # x86-64 第一个参数:a=5
(gdb) print $rsi                   # 第二个参数:b=10
(gdb) print $pc                    # 当前指令地址
(gdb) print $sp                    # 栈顶地址

▸ 示例(5.7 修改变量)

复制代码
$ gdb --args ./gdb_all inspect
(gdb) break deeper
(gdb) run
(gdb) print p->x                   # 3
(gdb) set var p->x = 999           # 临时改变量
(gdb) print p->x                   # 999
(gdb) continue
  # 程序输出 hello gdb: 1009,27(x 被改成了 999+10=1009)

6. 源码、反汇编与 TUI

6.1 查看源码

复制代码
list
list 120
list file.cpp:120
list ClassName::method

list -
list 100,130
list ,130
list +
set listsize 20
show listsize
info line file.cpp:120

网页里对 list 的补充比较细:可以按区间列源码、向前或向后滚动源码,并调整每次显示的行数。info line 则能把源码行映射到内存地址。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) break add
(gdb) run
(gdb) list                         # 显示 add() 附近 10 行
(gdb) list -                       # 向前翻页
(gdb) list +                       # 向后翻页
(gdb) list compute                 # 列出 compute 函数
(gdb) set listsize 20              # 每次显示 20 行
(gdb) info line add                # add() 第一行的内存地址

6.2 搜索源码与指定源码路径

复制代码
search criticalFlag
forward-search timeout
reverse-search mutex
directory /path/to/src
show directories

如果调试信息里没有完整源码路径,或者你在调试动态库、旧构建产物,directory / dir 往往能直接解决"找不到源码"的问题。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) break compute
(gdb) run
(gdb) search multiply              # 在当前源码文件中向下搜索
(gdb) reverse-search add            # 向上搜索
(gdb) show directories              # 当前源码搜索路径
(gdb) directory /tmp/extra_src      # 添加额外源码路径

6.3 查看汇编

复制代码
disassemble
disassemble /m
disassemble /s ClassName::method

/m 会混合显示源码和汇编,适合分析优化后的行为或 ABI 问题。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) break add
(gdb) run
(gdb) disassemble                  # 当前函数的汇编
(gdb) disassemble /m                # 源码+汇编混合显示
(gdb) disassemble /s multiply       # multiply 函数的源码+汇编

6.4 TUI 文本界面

复制代码
gdb -tui ./app
layout src
layout asm
layout split
focus cmd
focus next
Ctrl + x, a

适用场景

命令行环境下需要同时看源码和调试命令,不想来回 list

注意

某些终端对 TUI 支持一般;如果显示错乱,先换终端再判断是否是 GDB 问题。

▸ 示例

复制代码
$ gdb -tui --args ./gdb_all step
(gdb) break compute
(gdb) run
  # TUI 上半屏自动显示源码,高亮当前行
(gdb) layout asm                   # 切换到汇编视图
(gdb) layout split                 # 上源码 + 下汇编
(gdb) focus cmd                    # 焦点切回命令窗口
(gdb) next
  # 源码窗口中高亮行自动跟踪
Ctrl+x, a                          # 退出 TUI 回到普通命令行

7. 线程、进程、fork 与信号

7.1 查看线程

复制代码
info threads
thread 3
thread apply all bt
thread apply all bt full

多线程程序卡死时,thread apply all bt 往往是第一条应该执行的命令。

▸ 示例

复制代码
$ gdb --args ./gdb_all thread
(gdb) break threadWorker
(gdb) run
(gdb) info threads
  #   Id   Target Id               Frame
  # * 2    Thread 0x... "gdb_all"   threadWorker(id=0,...)
  #   3    Thread 0x... "gdb_all"   threadWorker(id=1,...)
  #   4    Thread 0x... "gdb_all"   threadWorker(id=2,...)
  #   1    Thread 0x... "gdb_all"   ... pthread_cond_wait ...
(gdb) thread 3                     # 切到线程 3
(gdb) print id                     # 1
(gdb) thread apply all bt          # 一次打印全部线程调用栈

7.2 线程专属断点

复制代码
break file.cpp:120 thread 4
break Worker::run thread 2 if state == Failed

这是网页里强调但原文档没单独写出的点:多线程下可以把断点限制到某个 GDB 线程号,避免所有线程都命中同一个断点。

▸ 示例

复制代码
(gdb) break threadWorker thread 2   # 只在 GDB 线程 2 停
(gdb) continue
  # 只有线程 2 命中断点
(gdb) print id                     # 0
(gdb) print counter                # 观察共享 counter 的值

7.3 调试 fork / 子进程

复制代码
set follow-fork-mode parent
set follow-fork-mode child
set detach-on-fork off
info inferiors
inferior 2
设置 含义
follow-fork-mode parent fork 后继续跟踪父进程
follow-fork-mode child fork 后切到子进程
detach-on-fork off 父子进程都保留在 GDB 中,可切换调试

▸ 示例(fork 概念演示)

复制代码
# fork 调试需要程序调用 fork(),gdb_all 未包含 fork,此处为概念演示
$ gdb ./my_fork_app
(gdb) set follow-fork-mode child    # fork 后跟踪子进程
(gdb) set detach-on-fork off        # 保留父子进程
(gdb) break main
(gdb) run
  # fork 发生后,自动切到子进程
(gdb) info inferiors                # 列出所有进程
  # Num  Description       Executable
  # * 2  process 12346     ./my_fork_app
  #   1  process 12345     ./my_fork_app
(gdb) inferior 1                    # 切回父进程

7.4 信号处理

复制代码
info signals
handle SIGPIPE nostop noprint pass
handle SIGSEGV stop print pass
signal SIGUSR1

GDB 可以控制收到信号时是否停下、是否打印、是否把信号继续传给程序。

▸ 示例

复制代码
$ gdb --args ./gdb_all crash
(gdb) handle SIGSEGV stop print      # 收到 SIGSEGV 时停下并打印
(gdb) run
  # Program received signal SIGSEGV, Segmentation fault.
  # 0x... in processNode (node=0x0) at gdb_all.cpp:XX
(gdb) info signals SIGSEGV          # 查看 SIGSEGV 的处理策略
  # Signal  Stop  Print  Pass to program
  # SIGSEGV Yes   Yes    Yes
(gdb) bt                            # 看崩溃调用栈

8. Core Dump 分析

**步骤 1:**确认二进制和 core 文件匹配,版本、构建产物、符号必须对应。

步骤 2: 使用 gdb ./app core.xxx 打开。

步骤 3: 优先执行 btbt fullinfo threadsthread apply all bt

**步骤 4:**切到崩溃线程和相关栈帧,检查局部变量、参数、对象状态。

复制代码
gdb ./app core.12345
bt
bt full
info threads
thread 5
frame 2
info locals
p *this

如果出现"源码对不上""变量看起来很怪""没有符号",最常见原因是 core 和可执行文件不匹配,或者缺少调试符号包。

▸ 示例(S8 Core Dump 基本流程)

复制代码
$ gdb ./app core.12345
(gdb) bt full
  # 看崩溃调用栈和局部变量
(gdb) info threads                  # 多线程时看所有线程
(gdb) thread apply all bt           # 全部线程调用栈
(gdb) thread 3                      # 切到可疑线程
(gdb) frame 2                       # 切到相关栈帧
(gdb) info locals                   # 查看局部变量
(gdb) print *this                   # 检查对象状态

8.1 实战演练 ▸ 用 gdb_all 模拟崩溃与 core 分析

复制代码
# 1. 启用 core dump
$ ulimit -c unlimited

# 2. 运行 crash 场景(会触发 SIGSEGV)
$ ./gdb_all crash
  # Segmentation fault (core dumped)

# 3. 用 GDB 分析 core
$ gdb ./gdb_all core
(gdb) bt
  # #0 processNode (node=0x0) at gdb_all.cpp:XX
  # #1 traverse (head=0x...) at gdb_all.cpp:XX
  # #2 runCrash () at gdb_all.cpp:XX
  # #3 main (argc=2, ...) at gdb_all.cpp:XX
(gdb) frame 0
(gdb) print node                    # 0x0 ------ 空指针!
(gdb) frame 1
(gdb) print *head                   # {value=1, next=0x...}
(gdb) info locals                   # 循环变量等
  # 结论:traverse() 循环结束后多调了一次 processNode(nullptr)

练习重点:崩溃后第一条命令 bt full(调用栈+局部变量)、frame N 逐层检查、print 确认空指针或越界。离线 core 分析流程与在线完全一致。

9. 动态库与远程调试

9.1 动态库

复制代码
info sharedlibrary
sharedlibrary
set solib-search-path /path/to/libs
set sysroot /path/to/sysroot

动态库调试时,符号文件、so 路径、sysroot 经常是关键问题。

复制代码
add-symbol-file libplugin.so 0x7ffff7dd4000

网页延伸场景里还提到了手工加载动态库符号。遇到延迟加载、地址已知但符号未自动装入时,add-symbol-file 很有用。

▸ 示例

复制代码
$ gdb --args ./gdb_all step
(gdb) info sharedlibrary            # 列出已加载的所有 .so
  # From        To          Syms Read  Shared Object Library
  # 0x7ffff...  0x7ffff...  Yes         /lib64/libstdc++.so.6
  # 0x7ffff...  0x7ffff...  Yes         /lib64/libpthread.so.0
  # ...
(gdb) set solib-search-path /opt/debug_libs  # 添加 so 搜索路径

9.2 远程调试 gdbserver

复制代码
# 目标机
gdbserver :1234 ./app arg1 arg2

# 本机
gdb ./app
target remote 192.168.1.10:1234

如果目标机环境和本机不同,通常还需要配置源码路径映射、sysroot 和动态库搜索路径。

▸ 示例(概念演示)

复制代码
# 目标机上启动 gdbserver
$ gdbserver :1234 ./gdb_all step
  # Process ./gdb_all created; pid = 54321
  # Listening on port 1234

# 本机连接
$ gdb ./gdb_all
(gdb) target remote 192.168.1.10:1234
  # Remote debugging using 192.168.1.10:1234
(gdb) break compute
(gdb) continue
(gdb) bt                            # 远端的调用栈
(gdb) print result                   # 远端变量

9.3 附加到远端已运行进程

复制代码
# 目标机
gdbserver --attach :1234 PID

# 本机
gdb ./app
target remote 192.168.1.10:1234

▸ 示例(概念演示)

复制代码
# 目标机上附加到已运行的进程
$ gdbserver --attach :1234 $(pidof gdb_all)
  # Attached; pid = 54321
  # Listening on port 1234

# 本机
$ gdb ./gdb_all
(gdb) target remote 192.168.1.10:1234
(gdb) bt                            # 看进程当前在干什么
(gdb) info threads                  # 多线程时查看所有线程

10. C++ 调试重点

10.1 STL 容器与 pretty printer

现代 GDB 对 STL 支持已经比过去好很多,但仍建议启用 pretty printer,这样查看 std::stringstd::vectorstd::map 会更直观。

复制代码
set print pretty on
set print object on
set print static-members on
set print vtbl on

▸ 示例

复制代码
$ gdb --args ./gdb_all stl
(gdb) set print pretty on
(gdb) break runStl
(gdb) run
(gdb) next 3                        # 让容器初始化完成
(gdb) print words                   # vector<string> pretty print
  # std::vector of length 3 = {"alpha", "beta", "gamma"}
(gdb) print scores                  # map<string,int>
  # std::map with 2 elements = {["Alice"] = 95, ["Bob"] = 87}

▸ 示例

复制代码
$ gdb --args ./gdb_all stl
(gdb) set print pretty on
(gdb) break runStl
(gdb) run
(gdb) next 3                        # 让容器初始化完成
(gdb) print words                   # vector pretty print
  # std::vector of length 3 = {"alpha", "beta", "gamma"}
(gdb) print scores                  # map
  # std::map with 2 elements = {["Alice"] = 95, ["Bob"] = 87}

10.2 老版本 GDB 查看 STL 容器

网页里专门有一篇讲老版本 GDB 查看 STL 容器的办法。现代 GDB 往往已经能较好打印 vectormap,但在老环境或内网机器上,依然可能只看到底层实现细节。

复制代码
source stl-views-1.0.3.gdb
pvector myVec
pmap myMap
pset mySet
pstring myString

cat stl-views-1.0.3.gdb >> ~/.gdbinit

这种做法会向 GDB 注入一批自定义命令,例如 pvectorpmappstring。如果你的工具链较旧,这是非常实用的补强方案。

▸ 示例(老版本 GDB)

复制代码
# 在老版本 GDB 中,先加载 STL 辅助脚本
(gdb) source stl-views-1.0.3.gdb
(gdb) break runStl
(gdb) run
(gdb) pvector words                 # 用自定义命令查看 vector
(gdb) pstring msg                   # 查看 string 内容

10.3 对象、指针、引用

复制代码
p obj
p *ptr
p &obj
p ref
p this
p *this

调成员函数时,先看 this*this;调空指针问题时,先看指针地址,再看调用栈是谁传进来的。

▸ 示例

复制代码
$ gdb --args ./gdb_all inspect
(gdb) break Calculator::process
(gdb) run
(gdb) print this                    # Calculator 对象地址
(gdb) print *this                   # {tag_ = "calc"}
(gdb) print tag_                    # "calc"
(gdb) up
(gdb) print pt                      # Point{x=3, y=7}
(gdb) print &pt                     # 地址

10.4 虚函数与动态类型

复制代码
set print object on
ptype obj
info vtbl obj

排查多态行为异常时,动态类型和虚表信息很重要。

▸ 示例

复制代码
$ gdb --args ./gdb_all poly
(gdb) set print object on
(gdb) break runPoly
(gdb) run
(gdb) next 4                        # 让 shapes 初始化完成
(gdb) print *shapes[0]              # 动态类型 Circle,radius=5
(gdb) print *shapes[1]              # 动态类型 Rectangle,w=3, h=4
(gdb) ptype *shapes[0]              # type = class Circle : public Shape {...}
(gdb) info vtbl *shapes[0]          # 虚表内容

10.5 异常

复制代码
catch throw
catch catch
bt

程序最终崩在别处时,不要只看崩溃点;异常一旦被错误地吞掉或转化,源头可能更早。

▸ 示例

复制代码
$ gdb --args ./gdb_all exception
(gdb) catch throw                   # 任何 throw 都停下
(gdb) run
  # Catchpoint hit: throw of std::runtime_error
(gdb) bt
  # #0 __cxa_throw
  # #1 riskyDivide (a=10, b=0) at gdb_all.cpp:XX
  # #2 runException () at gdb_all.cpp:XX
(gdb) frame 1
(gdb) print a                       # 10
(gdb) print b                       # 0 ------ 触发 throw 的原因

11. 进阶能力

11.1 反向调试

复制代码
record full
reverse-continue
reverse-next
reverse-step

反向调试适合"值已经坏了,但不知道是谁先改坏的"这类问题。不过它开销较大,不适合所有程序。

▸ 示例

复制代码
$ gdb --args ./gdb_all watch
(gdb) break mutateValue
(gdb) run
(gdb) record full                   # 开始记录
(gdb) continue                      # 让程序跑完 mutateValue
(gdb) reverse-next                  # 倒退一步
(gdb) print tracked                 # 看到是哪一步改的
(gdb) reverse-continue              # 继续倒退到上一个断点
(gdb) record stop                   # 停止记录

▸ 示例

复制代码
$ gdb --args ./gdb_all watch
(gdb) break mutateValue
(gdb) run
(gdb) record full                   # 开始记录
(gdb) continue                      # 让程序跑完 mutateValue
(gdb) reverse-next                  # 倒退一步
(gdb) print tracked                 # 看到是哪一步改的
(gdb) reverse-continue              # 继续倒退到上一个断点
(gdb) record stop                   # 停止记录

11.2 批处理与脚本

复制代码
gdb -batch -ex "bt" ./app core.12345
source my_gdbinit.txt
define hook-run
    echo starting...\n
end

适合自动化分析、批量收集崩溃信息。

复制代码
shell ls -l
make -j8

网页里还补充了两个很实用但常被忽略的命令:shell 允许你不退出 GDB 直接执行 shell 命令,make 可以在调试会话中直接重新构建。

▸ 示例

复制代码
# 批量分析 core dump
$ gdb -batch -ex "bt full" -ex "info threads" ./gdb_all core
  # 直接输出调用栈,不进入交互模式

# 用脚本文件
$ cat my_commands.gdb
  break runCrash
  run
  bt
  quit
$ gdb -x my_commands.gdb --args ./gdb_all crash

# GDB 内执行 shell 命令
(gdb) shell ls -la gdb_all*
(gdb) make -j8                      # 不退出 GDB 直接重编

▸ 示例

复制代码
# 批量分析 core dump
$ gdb -batch -ex "bt full" -ex "info threads" ./gdb_all core
  # 直接输出调用栈,不进入交互模式

# 用脚本文件
$ cat my_commands.gdb
  break runCrash
  run
  bt
  quit
$ gdb -x my_commands.gdb --args ./gdb_all crash

# GDB 内执行 shell 命令
(gdb) shell ls -la gdb_all*
(gdb) make -j8                      # 不退出 GDB 直接重编

11.3 Python 扩展

复制代码
python print("hello from gdb")
python import gdb

当普通命令不够时,可以用 Python 扩展 GDB,实现批量对象解析、定制打印和自动诊断。

▸ 示例

复制代码
(gdb) python
>import gdb
>val = gdb.parse_and_eval("globalCounter")
>print(f"globalCounter = {val}")
>end
  # globalCounter = 0

# 用 Python 遍历所有局部变量
(gdb) python
>frame = gdb.selected_frame()
>block = frame.block()
>for sym in block:
>    if sym.is_variable:
>        print(f"{sym.name} = {sym.value(frame)}")
>end

▸ 示例

复制代码
(gdb) python
>import gdb
>val = gdb.parse_and_eval("globalCounter")
>print(f"globalCounter = {val}")
>end
  # globalCounter = 0

# 用 Python 遍历所有局部变量
(gdb) python
>frame = gdb.selected_frame()
>block = frame.block()
>for sym in block:
>    if sym.is_variable:
>        print(f"{sym.name} = {sym.value(frame)}")
>end

11.4 常用显示设置

复制代码
set print elements 0
set print repeats 0
set print null-stop on
set print demangle on
set language c++

▸ 示例

复制代码
$ gdb --args ./gdb_all stl
(gdb) set print elements 0          # 不截断数组/容器显示
(gdb) set print pretty on           # 结构体多行缩进显示
(gdb) set print demangle on         # C++ 符号解析为可读名
(gdb) break runStl
(gdb) run
(gdb) print words                   # 完整显示,不截断

▸ 示例

复制代码
$ gdb --args ./gdb_all stl
(gdb) set print elements 0          # 不截断数组/容器显示
(gdb) set print pretty on           # 结构体多行缩进显示
(gdb) set print demangle on         # C++ 符号解析为可读名
(gdb) break runStl
(gdb) run
(gdb) print words                   # 完整显示,不截断

12. 常见调试流程模板

12.1 程序一运行就崩

  1. gdb ./app 启动。
  2. 执行 run
  3. 崩溃后先看 btbt full
  4. 切换到可疑栈帧,用 frame Ninfo localsp 看关键变量。

▸ 示例

复制代码
$ gdb --args ./gdb_all crash
(gdb) run
  # SIGSEGV in processNode
(gdb) bt full
(gdb) frame 0
(gdb) print node                    # 0x0
(gdb) frame 1
(gdb) info locals

12.2 死循环或卡住不动

  1. 使用 gdb -p PID 附加。
  2. 执行 info threads
  3. 执行 thread apply all bt
  4. 看线程是在锁等待、条件变量、系统调用还是忙等。

▸ 示例

复制代码
# 假设 gdb_all thread 场景卡住
$ gdb -p $(pidof gdb_all)
(gdb) info threads
(gdb) thread apply all bt
  # 看哪个线程在 __lll_lock_wait / pthread_cond_wait

12.3 某个值被改坏了

  1. 先让程序运行到该变量合法的阶段。
  2. 对该变量设 watch
  3. 继续执行直到触发。
  4. 查看是谁、在哪个栈帧、在什么条件下写坏它。

▸ 示例

复制代码
$ gdb --args ./gdb_all watch
(gdb) break mutateValue
(gdb) run
(gdb) watch tracked                 # 值变化时停下
(gdb) continue
  # Watchpoint hit: old=0, new=1
(gdb) bt                            # 看是谁改的

12.4 只有线上 core,没有复现条件

  1. 确认 core 和二进制、so 版本对应。
  2. 看崩溃线程和全部线程调用栈。
  3. 优先验证空指针、越界、悬垂指针、数据竞争、对象生命周期问题。
  4. 必要时结合日志时间线还原上下文。

▸ 示例

复制代码
# 拿到线上 core 后
$ gdb ./gdb_all core
(gdb) bt full
(gdb) thread apply all bt
(gdb) frame 0
(gdb) print node                    # 确认空指针
(gdb) info sharedlibrary            # 确认 so 版本匹配

13. 常见问题与排查

断点打不上

检查是否带了 -g,符号是否存在,源码和二进制是否匹配,动态库是否尚未加载,可尝试 set breakpoint pending on

变量显示 optimized out

说明编译优化把变量消掉了。改用 -O0-Og 重新构建。

源码行号对不上

通常是优化、二进制不匹配、增量构建污染、旧 core 搭配新程序导致。

STL 内容不好看

启用 pretty printer,并使用 set print pretty on

多线程只看到了当前线程

thread apply all bt,不要只盯着一个线程看问题。

attach 权限失败

检查权限、ptrace_scope、容器隔离和目标进程用户。

建议的最小排查顺序

  • 先确认符号和二进制版本是否匹配。
  • 先看调用栈,再看变量,不要一开始就盲目单步。
  • 先缩小问题范围,再考虑用 watchpoint、catchpoint、反向调试。
  • 多线程问题优先看所有线程栈,不要只看当前线程。

14. 高频命令速查表

类别 高频命令
启动 gdb ./appgdb --args ./app a bgdb -p PIDfile ./app
运行控制 runstartcontinuenextstepfinishreturn
断点 breaktbreakconditionignoreinfo breakdelete
观察点 watchrwatchawatch
变量查看 pdisplayinfo localsinfo argsptypewhatis
调用栈 btbt fullframeupdown
内存与汇编 x/16xwx/sdisassemble /mx/10i $pcinfo registers
线程 info threadsthread Nbreak ... thread Nthread apply all bt
异常与信号 catch throwhandle SIGPIPE nostop noprint pass
core 分析 gdb ./app core.xxxbt fullinfo threads
源码定位 listsearchreverse-searchdirectoryinfo line
STL 容器 set print pretty onp myVecsource stl-views-1.0.3.gdbpvectorpmap

如果你只记三条:bt fullthread apply all btwatch。这三类命令分别解决崩溃定位、多线程定位、值被谁改坏这三个高频问题。

15. 各节示例代码汇总

全文所有示例均使用同一个统一程序 gdb_all.cpp(嵌入在 1.3 节),通过命令行参数选择不同场景。

场景参数 练习的核心技能 涉及章节
./gdb_all step next step finish until list disassemble S3, S6
./gdb_all loop until 跳出循环、条件断点 S3
./gdb_all breakpoint commands 自动打印、ignore S4
./gdb_all watch watch 观察点、反向调试 S4, S11, S12
./gdb_all inspect print display bt full x/ info locals set var S5, S10
./gdb_all stl STL pretty print、set print pretty on S10, S11
./gdb_all poly ptype 动态类型、info vtbl 虚表 S10
./gdb_all exception catch throw 异常捕获 S4, S10
./gdb_all thread info threads thread apply all bt 线程断点 S7
./gdb_all crash SIGSEGV、core dump 分析、bt full S7, S8, S12
复制代码
# 统一编译命令
g++ -std=c++20 -g -O0 -pthread -rdynamic gdb_all.cpp -o gdb_all

所有场景集中在一个 gdb_all.cpp 中(源码见 1.3 节),编译一次即可练习全文所有 GDB 技能。

示例里故意包含了崩溃、异常等错误场景,只用于学习 GDB。不要把它当作业务代码模板。

相关推荐
笨笨饿2 小时前
66_C语言与微控制器底层开发
linux·c语言·网络·数据结构·算法·机器人·个人开发
aramae2 小时前
Linux多线程编程(二):互斥锁、线程安全与死锁剖析
linux·运维·服务器·网络·安全·centos
南境十里·墨染春水2 小时前
linux学习进展 线程
java·linux·学习
HABuo2 小时前
【linux网络基础(二)】理解端口号&UDP、TCP协议&网络字节序
linux·服务器·c语言·网络·c++·ubuntu·centos
爱学习的小囧2 小时前
ESXi 存储路径丢失(PDL/APD)完整处置教程:分清类型再操作,一步不踩坑
linux·运维·服务器·网络·esxi·vmware
不做超级小白2 小时前
Termux 完整安装与配置指南(2026.4.24最新版,从零到可用)
linux·手机
Lumos_7772 小时前
Linux -- 信号
linux·运维·服务器
Lumos_7772 小时前
Linux -- 管道
linux·运维·服务器
宇宙realman_9993 小时前
DSP28335-FlashAPI使用
linux·前端·python