菜鸟科技网

如何高效掌握GDB命令信息?核心命令、参数解析及调试场景应用指南

GDB 命令速查表

GDB 的命令可以分为几个大类:启动与程序控制断点管理查看数据执行控制线程与进程、以及辅助命令

如何高效掌握GDB命令信息?核心命令、参数解析及调试场景应用指南-图1
(图片来源网络,侵删)

启动与程序控制

这些命令用于加载程序、设置参数并控制其执行流程。

命令 缩写 描述 示例
file (无) 加载一个可执行文件到 GDB 中。 file my_program
run r 启动被调试的程序,可以附带参数。 run arg1 arg2
args (无) 设置 run 命令的参数,不执行程序。 args --help -v
kill k 终止当前正在调试的进程。 kill
quit q 退出 GDB。 quit
start (无) 开始执行程序,但在 main 函数的第一行代码前停住。 start

断点管理

断点是调试的核心,用于在特定位置暂停程序执行。

命令 缩写 描述 示例
break b 设置断点。
   b <行号> 在指定行号设置断点。 b 42
   b <函数名> 在函数入口处设置断点。 b main
   b <文件名>:<行号> 在指定文件的行号设置断点。 b utils.cpp:100
   b *<地址> 在内存的特定地址设置断点。 b *0x0804856b
tbreak tb 设置临时断点,程序第一次停住后会自动删除。 tbreak loop
info breakpoints info b 查看当前所有断点的信息。 info b
delete d 删除一个或所有断点。
   delete <断点号> 删除指定编号的断点。 delete 1
   delete 删除所有断点。 delete
disable (无) 禁用一个断点,但保留它。 disable 1
enable (无) 启用一个被禁用的断点。 enable 1
condition <断点号> <表达式> (无) 设置断点条件,只有当表达式为真时,断点才触发。 condition 2 i == 10 (断点2在i等于10时触发)
watch (无) 设置观察点,当变量的值发生变化时暂停程序。 watch my_var
rwatch (无) 设置观察点,当变量被读取时暂停。 rwatch my_var
awatch (无) 设置观察点,当变量被读取或写入时暂停。 awatch my_var

查看数据

程序暂停后,使用这些命令来检查变量、内存和程序状态。

命令 缩写 描述 示例
print p 打印一个变量或表达式的值。 p i
display (无) 添加一个自动显示项,每次程序停止时,GDB 会自动打印该变量的值。 display my_array
info display (无) 查看所有自动显示项。 info display
undisplay <编号> (无) 删除一个自动显示项。 undisplay 1
info locals (无) 打印当前栈帧中所有局部变量的值。 info locals
info args (无) 打印当前函数的参数值。 info args
x (无) 检查内存内容。x/<n><f><u> 是其格式。
   x/20xw &my_var my_var 的地址开始,以16进制(x)、4字节(w)为单位,打印20个(20)单位。 x/20xw &my_var
ptype <变量名> (无) 打印一个变量的类型定义。 ptype my_struct
backtrace bt 打印函数调用栈(堆栈跟踪)。 bt
frame <n> f <n> 切换到栈帧编号为 n 的帧。 frame 2
up (无) 向上移动一帧(调用者方向)。 up
down (无) 向下移动一帧(被调用者方向)。 down

执行控制

控制程序如何一步步地执行。

如何高效掌握GDB命令信息?核心命令、参数解析及调试场景应用指南-图2
(图片来源网络,侵删)
命令 缩写 描述 示例
continue c 从当前点继续执行程序,直到遇到下一个断点或程序结束。 c
next n 执行下一行源代码,如果该行有函数调用,则不会进入函数内部。 n
step s 执行下一行源代码,如果该行有函数调用,则会进入函数内部。 s
finish (无) 继续执行,直到当前函数返回。 finish
until u 在一个循环中,执行到循环体外。 u
return (无) 强制从当前函数返回,可以指定一个返回值。 return 0
jump <行号或地址> j 让程序跳转到指定的行或地址继续执行。 jump 50

线程与进程

调试多线程或多进程程序时使用。

命令 缩写 描述 示例
info threads (无) 显示所有线程的列表。 info threads
thread <线程ID> (无) 切换到指定的线程进行调试。 thread 2
break ... thread <线程ID> (无) 只在指定线程的特定位置设置断点。 b main thread 3
info inferiors (无) 显示所有被调试的进程。 info inferiors
inferior <进程ID> (无) 切换到指定的进程进行调试。 inferior 2

辅助命令

这些命令帮助您更好地使用 GDB。

命令 缩写 描述 示例
help h 显示命令的帮助信息。 help print
list l 列出源代码,默认显示当前行及之后的10行。 l
   list <行号> 从指定行号开始列出代码。 l 100
   list <函数名> 列出指定函数的代码。 list main
directory dir 添加源代码搜索路径。 dir /path/to/src
shell sh 在不退出 GDB 的情况下执行一个 shell 命令。 shell ls -l
set (无) 设置 GDB 内部变量或选项。 set confirm off (禁用确认提示)
show (无) 显示 GDB 变量或选项的当前值。 show confirm

实战调试流程示例

假设我们有一个简单的 C++ 程序 buggy.cpp

// buggy.cpp
#include <iostream>
int add(int a, int b) {
    int sum = a + b;
    std::cout << "Inside add: a=" << a << ", b=" << b << std::endl;
    return sum;
}
int main() {
    int x = 10;
    int y = 20;
    int result = add(x, y);
    std::cout << "Result: " << result << std::endl;
    // Bug: 这里我们想让 y 变成 30,但忘了赋值
    y = 30; 
    result = add(x, y);
    std::cout << "New Result: " << result << std::endl; // 这行会输出 30,但我们期望是 40
    return 0;
}

编译时务必加上 -g 选项,以包含调试信息!

如何高效掌握GDB命令信息?核心命令、参数解析及调试场景应用指南-图3
(图片来源网络,侵删)
g++ -g buggy.cpp -o buggy

调试步骤:

  1. 启动 GDB 并加载程序

    gdb ./buggy
  2. 设置断点 我们想在 main 函数和 add 函数入口都设置断点。

    (gdb) break main
    Breakpoint 1 at 0x555555555169: file buggy.cpp, line 10.
    (gdb) break add
    Breakpoint 2 at 0x55555555514d: file buggy.cpp, line 4.
  3. 运行程序

    (gdb) run
    Starting program: /path/to/buggy 
    Breakpoint 1, main () at buggy.cpp:10
    10      int x = 10;
  4. 查看变量并单步执行 程序在 main 函数的第一行停住了,我们来查看 xy 的值,然后单步执行。

    (gdb) print x
    $1 = 10
    (gdb) print y
    $2 = 4198432 // y 是未初始化的垃圾值
    (gdb) next
    11      int y = 20;
    (gdb) next
    12      int result = add(x, y);
    (gdb) print y
    $3 = 20 // y 被正确赋值为 20
  5. 进入函数并观察调用栈 现在我们执行到 add(x, y) 这一行,使用 step 进入函数。

    (gdb) step
    add (a=10, b=20) at buggy.cpp:4
    4       int sum = a + b;
    (gdb) backtrace
    #0  add (a=10, b=20) at buggy.cpp:4
    #1  0x00005555555551a7 in main () at buggy.cpp:12
  6. 继续执行 我们可以 continue 执行,直到程序结束或下一个断点。

    (gdb) continue
    Continuing.
    Inside add: a=10, b=20
    Result: 30
    Breakpoint 1, main () at buggy.cpp:10
    10      int x = 10;
  7. 发现 Bug 程序再次在 main 函数的第一行停住,这说明 main 函数执行完了又从头开始?不,GDB 在 main 函数开始处停住是因为 break main 断点,我们继续执行。

    (gdb) continue
    Continuing.
    Inside add: a=10, b=30
    New Result: 30  <-- BUG HERE! Expected 40.
    [Inferior 1 (process 12345) exited normally]

    程序输出 New Result: 30,但我们期望 x(10) + y(30) 等于 40,这说明第二次调用 add 时,传入的 b 不是 30

  8. 重新调试,定位问题 让我们重新运行,这次在第二次调用 add 前设置一个条件断点。

    (gdb) run
    ...
    (gdb) # 程序停在第一次 main
    (gdb) continue
    ...
    (gdb) # 程序停在第二次 main

    这次,我们想在第二次调用 add 之前检查 y 的值,我们可以使用 info locals 或者直接打印。

    (gdb) next
    12      int result = add(x, y);
    (gdb) print y
    $4 = 20  <-- 问题找到了!y 还是 20,没有被赋值为 30

    通过查看源代码 buggy.cpp,我们发现 y = 30; 这一行被注释掉了,这就是 Bug 的根源。


高级技巧

  • GDB Python API: GDB 可以通过 Python 脚本进行扩展,实现高度定制化的调试功能。
  • TUI (Text User Interface): gdb -tui 或在 GDB 内部按 Ctrl+X A 可以打开一个图形化界面,左侧显示源代码,右侧显示 GDB 终端。
  • catch 命令: 用于捕获特定事件,如 catch throw (捕获 C++ 异常抛出)、catch catch (捕获 C++ 异常捕获)。
  • set follow-fork-mode: 调试多进程程序时,决定在 fork() 之后是跟父进程还是子进程。

这份指南应该能覆盖您日常使用 GDB 的 95% 以上的场景,GDB 是一个非常强大的工具,熟练掌握它将极大地提升您定位和解决复杂 Bug 的能力。

分享:
扫描分享到社交APP
上一篇
下一篇