GDB 快速指南



什么是 GNU 调试器?

调试器是一个运行其他程序的程序,允许用户控制这些程序,并在出现问题时检查变量。

GNU 调试器,也称为gdb,是 UNIX 系统上用于调试 C 和 C++ 程序最流行的调试器。

GNU 调试器帮助您获取以下信息

  • 如果发生核心转储,程序在哪个语句或表达式崩溃?

  • 如果在执行函数时发生错误,程序的哪一行包含对该函数的调用,参数是什么?

  • 程序变量在程序执行期间特定点的值是什么?

  • 程序中特定表达式的结果是什么?

GDB 如何调试?

GDB 允许您将程序运行到特定点,然后停止并打印出该点某些变量的值,或者一次一行地逐步执行程序,并在执行每一行后打印出每个变量的值。

GDB 使用简单的命令行界面。

注意事项

  • 尽管 GDB 可以帮助您找出与内存泄漏相关的错误,但它不是检测内存泄漏的工具。

  • GDB 不能用于编译有错误的程序,它也不能帮助修复这些错误。

GDB - 安装

在进行安装之前,请通过发出以下命令检查您的 Unix 系统上是否已安装 gdb

$gdb -help 

如果已安装 GDB,则它将显示 GDB 中所有可用的选项。如果未安装 GDB,则继续进行全新安装。

您可以按照下面讨论的简单步骤在您的系统上安装 GDB。

步骤 1:确保您具备安装 gdb 的先决条件

  • 符合 ANSI 标准的 C 编译器(推荐使用 gcc - 请注意,gdb 可以调试由其他编译器生成的代码)

  • 在您将要构建 gdb 的分区上需要 115 MB 的可用磁盘空间。

  • 在您将要安装 gdb 的分区上需要 20 MB 的可用磁盘空间。

  • GNU 的解压缩程序,gzip

  • make 实用程序 - 已知 GNU 版本可以正常工作,其他版本可能也可以。

步骤 2:ftp.gnu.org/gnu/gdb下载 gdb 源代码发行版。(我们使用gdb-6.6.tar.gz作为这些说明的示例。)将发行版文件放在您的构建目录中。

步骤 3:在您的构建目录中,解压缩 gdb-6.6.tar.gz 并从存档中提取源文件。文件提取完成后,将您的工作目录更改为自动在您的构建目录中创建的 gdb-6.6 目录。

$ build> gzip -d gdb-6.6.tar.gz 
$ build> tar xfv gdb-6.6.tar 
$ build> cd gdb-6.6 

步骤 4:运行 configure 脚本以根据您的平台配置源代码树。

$ gdb-6.6> .⁄configure 

步骤 5:使用make实用程序构建 gdb。

$ gdb-6.6> make 

步骤 6:以 root 用户身份登录并使用以下命令安装 gdb。

$ gdb-6.6> make install 

步骤 7:如果需要,可以在安装完成后删除 gdb 构建目录和存档文件以回收磁盘空间。

$ gdb-6.6> cd .. 
$ build> rm -r gdb-6.6 
$ build> rm gdb-6.6.tar 

您现在已在系统上安装了 gdb,它可以立即使用了。

GDB - 调试符号

调试符号表将编译后的二进制程序中的指令映射到源代码中相应的变量、函数或行。此映射可能类似于

  • 程序指令 ⇒ 项目名称、项目类型、原始文件、定义的行号。

符号表可以嵌入到程序中,也可以作为单独的文件存储。因此,如果您计划调试您的程序,则需要创建一个包含调试程序所需信息的符号表。

我们可以推断出关于符号表的以下事实

  • 符号表适用于程序的特定版本 - 如果程序发生更改,则必须创建一个新的表。

  • 调试版本通常比零售(非调试)版本更大且速度更慢;调试版本包含符号表和其他辅助信息。

  • 如果您想调试自己没有编译的二进制程序,则必须从作者处获取符号表。

为了让 GDB 能够逐行从符号表中读取所有这些信息,我们需要以稍微不同的方式进行编译。通常我们将程序编译为

gcc hello.cc -o hello 

与其这样做,我们需要使用 -g 标志进行编译,如下所示

gcc -g hello.cc -o hello 

GDB - 命令

GDB 提供了大量的命令,但是以下命令是最常用的命令

  • b main - 在程序的开头设置断点

  • b - 在当前行设置断点

  • b N - 在第 N 行设置断点

  • b +N - 在当前行以下 N 行设置断点

  • b fn - 在函数“fn”的开头设置断点

  • d N - 删除断点编号 N

  • info break - 列出断点

  • r - 运行程序直到断点或错误

  • c - 继续运行程序直到下一个断点或错误

  • f - 运行直到当前函数完成

  • s - 运行程序的下一行

  • s N - 运行程序的下一 N 行

  • n - 与 s 相同,但它不会进入函数

  • u N - 运行直到到达当前行之前的 N 行

  • p var - 打印变量“var”的当前值

  • bt - 打印堆栈跟踪

  • u - 在堆栈中向上移动一级

  • d - 在堆栈中向下移动一级

  • q - 退出 gdb

GDB - 调试程序

入门:启动和停止

  • gcc -g myprogram.c

    • 使用调试选项 (-g) 编译 myprogram.c。您仍然会得到一个 a.out,但它包含调试信息,允许您在 GDB 中使用变量和函数名称,而不是原始内存位置(不好玩)。

  • gdb a.out

    • 使用文件 a.out 打开 GDB,但不运行程序。您将看到一个提示符 (gdb) - 所有示例均来自此提示符。

  • r

  • r arg1 arg2

  • r < file1

    • 三种运行之前加载的“a.out”的方法。您可以直接运行它 (r)、传递参数 (r arg1 arg2) 或输入文件。您通常会在运行之前设置断点。

  • help

  • h breakpoints

    • 列出帮助主题 (help) 或获取有关特定主题的帮助 (h breakpoints)。GDB 文档齐全。

  • q - 退出 GDB

单步执行代码

单步执行允许您跟踪程序的路径,并确定导致崩溃或返回无效输入的代码。

  • l

  • l 50

  • l myfunction

    • 列出当前行 (l)、特定行 (l 50) 或函数 (l myfunction) 的 10 行源代码。

  • next

    • 运行程序直到下一行,然后暂停。如果当前行是函数,它将执行整个函数,然后暂停。next 非常适合快速浏览您的代码。

  • step

    • 运行下一条指令,而不是下一行。如果当前指令是设置变量,则它与next相同。如果它是一个函数,它将跳转到该函数,执行第一个语句,然后暂停。step 非常适合深入了解代码的细节。

  • finish

    • 完成执行当前函数,然后暂停(也称为步出)。如果您不小心进入了一个函数,这很有用。

断点或观察点

断点在调试中扮演着重要的角色。当程序到达某个点时,它们会暂停(中断)程序。您可以检查和更改变量并恢复执行。当发生某些输入失败或需要测试输入时,这很有用。

  • break 45

  • break myfunction

    • 在第 45 行或 myfunction 处设置断点。程序到达断点时将暂停。
  • watch x == 3

    • 设置观察点,当条件更改时(当 x == 3 更改时)暂停程序。观察点非常适合某些输入 (myPtr != NULL),而无需在每个函数调用上中断。

  • continue

    • 在断点/观察点暂停后恢复执行。程序将继续运行,直到遇到下一个断点/观察点。

  • delete N

    • 删除断点 N(断点在创建时编号)。

设置变量

在运行时查看和更改变量是调试的关键部分。尝试向函数提供无效输入或运行其他测试用例以查找问题的根本原因。通常,您会在程序暂停时查看/设置变量。

  • print x

    • 打印变量 x 的当前值。能够使用原始变量名称是需要 (-g) 标志的原因;常规编译的程序已删除此信息。

  • set x = 3

  • set x = y

    • 将 x 设置为设定的值 (3) 或另一个变量 (y)
  • call myfunction()

  • call myotherfunction(x)

  • call strlen(mystring)

    • 调用用户定义的函数或系统函数。这非常有用,但要注意调用有错误的函数。

  • display x

    • 持续显示变量 x 的值,该值在每次单步执行或暂停后显示。如果您不断检查某个值,这很有用。

  • undisplay x

    • 删除 display 命令显示的变量的持续显示。

回溯和更改帧

堆栈是当前函数调用的列表 - 它显示您在程序中的位置。存储单个函数调用的详细信息,例如参数。

  • bt

    • 回溯或打印当前函数堆栈以显示您在当前程序中的位置。如果 main 调用函数 a(),a() 调用 b(),b() 调用 c(),则回溯为

  • c <= current location 
    b 
    a 
    main 
    
  • up

  • down

    • 在函数堆栈中向上或向下移动到下一个帧。如果您在c中,您可以移动到ba以检查局部变量。

  • return

    • 从当前函数返回。

处理信号

信号是在某些事件(例如计时器或错误)后抛出的消息。GDB 在遇到信号时可能会暂停;您可能希望忽略它们。

  • handle [signalname] [action]

  • handle SIGUSR1 nostop

  • handle SIGUSR1 noprint

  • handle SIGUSR1 ignore

    • 指示GDB忽略特定信号(SIGUSR1)的发生。忽略的级别各有不同。

GDB - 调试示例

请仔细阅读以下示例,了解调试程序和核心转储的过程。

  • 调试示例1

    此示例演示如何捕获由于除零异常而引发的错误。

  • 调试示例2

    此示例演示一个程序,由于内存未初始化而可能导致核心转储。

这两个程序都是用C++编写的,由于不同的原因导致核心转储。在学习完这两个示例后,您应该能够调试产生核心转储的C或C++程序。

GDB - 总结

学习完本教程后,您应该已经很好地理解了如何使用GNU调试器调试C或C++程序。现在,学习其他调试器的功能应该很容易,因为它们与GDB非常相似。强烈建议您也学习其他调试器,以便熟悉它们的功能。

市场上有很多不错的调试器。

  • DBX调试器 - 此调试器随Sun Solaris一起提供,您可以使用dbx的手册页(即man dbx)获取有关此调试器的完整信息。

  • DDD调试器 - 这是dbx的图形版本,可在Linux上免费获得。要了解完整详情,请使用ddd的手册页(即man ddd)。

您可以从以下链接获取有关GNU调试器的全面详细信息:使用GDB进行调试

广告