Linux 上的 GNU 调试器入门:速成课程
通过学习如何使用 GNU 调试器来揭示代码中不需要的错误,提高代码质量并防止出现意外结果。
调试是程序员和安全研究人员不可或缺的技能。深入掌握调试可以让您了解较低级别的可执行文件并捕获任何潜在的错误。
GNU 调试器或 GDB 是一种永恒的调试工具,多年来一直受到程序员的依赖。以下是如何在 Linux 上使用 GDB。
准备示例程序
要探索 GDB 的功能,您需要一个可执行文件来进行试验。为了进行演示,您将在具有可用源代码和调试符号的键检查程序上运行一次 GDB,在没有源代码的情况下运行一次,并在一个简单的多线程程序上将消息打印到屏幕上,这两个程序都是用 C 编写并使用 GCC 编译的( GNU C 编译器)。
您可以使用任何其他 C 编译器,但请确保不要剥离二进制文件。
您很可能会在自己的程序上运行 GDB。因此,请确保使用 gcc 的 -g 标志来编译它们,以启用调试符号。
如果没有调试符号并且带有大量剥离的二进制文件,您将不得不调试程序的反汇编。这需要您对汇编语言以及 Linux 上内存分配的工作原理有深入的了解,以了解堆栈和寄存器中的数据。
在 GDB 中运行程序
您可以通过多种方式在 GDB 中运行程序。输入gdb
如果您的程序需要命令行参数才能正常运行,请确保在程序名称后面添加参数。以下是将程序加载到 GDB 并使用参数执行它的语法:
gdb <program>
run <args>
或者 :
gdb
file <program>
run <args>
使用 GDB 设置断点
调试中的断点是在代码中手动设置的硬停止,当程序到达断点时停止执行流程。设置断点允许您单步执行代码并检查每个执行阶段如何影响数据和变量。
在GDB中,当您使用调试符号调试程序时,您可以通过函数名称设置断点,也可以根据行号设置断点。语法如下:
break main
break 47
要查看当前调试会话中的所有断点,请键入:
info breakpoints
要删除特定断点或多个断点,请键入:
delete 2
delete 3-5
GDB 还允许您设置条件断点,这意味着程序只有在执行过程中满足特定条件时才会停止。它可能是变量值的变化或不成功的函数调用,或者任何你想要的。以下是设置条件断点的语法:
break <location> if n == 2
如果您希望在遇到断点后继续执行程序,请输入继续命令:
continue
单步执行代码
单步调试代码对于理解程序如何处理数据至关重要。通过逐步执行程序中的各种函数并检查数据状态,您可以更好地了解程序如何实现您在代码中编写的逻辑。
它还可以帮助您检测崩溃的根源并以外科手术般的精确度研究程序行为,因为您可以按照自己的意愿单步执行每一行代码。您可以在 GDB 中通过三种主要方式单步调试代码:
- step:这个命令告诉GDB单步执行到源文件的下一行。这使您基本上可以逐行遍历源代码的长度。
- next: 该命令执行当前函数内的下一行源代码,然后停止。 next 将函数视为单行,因此如果您在函数调用之前使用 next,它会将其视为单行并跳过它,这与 step 命令不同。
- finish: finish 命令执行当前函数内的所有剩余行,然后停止。
检查变量
当您单步执行代码时,您可能需要检查变量的值以了解程序逻辑如何更改它们。以下是在 GDB 中查看变量值的语法:
print <variable>
如果您希望在每次更新变量值时打印变量值的变化,则应使用 display 命令。当您想要跟踪和打印循环中变量的值时,这特别有用:
display <variable>
设置观察点
观察点和条件断点密切相关,因为它们都响应程序中的更改。观察点用于跟踪代码中数据的变化。例如,您可能希望程序在变量值发生变化时中断。以下是使用 GDB 执行此操作的方法:
watch <variable_name>
使用 GDB 进行特定于线程的调试
GDB 允许您在使用多线程程序时执行特定于线程的调试。为了进行演示,我们将使用一个简单的 C 程序,该程序使用四个线程来打印每个线程的消息。
要查看程序中当前生成的线程,请使用 info 命令:
info threads
要使用特定线程,您可以使用其索引号从列表中选择它。例如:
thread 2
选择线程后,您可以使用 step、next 和 finish 命令单步执行其执行流程,如上所示。
使用 GDB 进行远程调试
您还可以远程调试位于不同系统上的程序。为此,您需要在目标计算机上设置 gdbserver。您可以使用发行版的默认包管理器或系统上安装的其他包管理器轻松安装它。
例如,要在基于 Ubuntu 或 Debian 的系统上安装 gdbserver,请使用 APT:
sudo apt install gdbserver
安装后,进入二进制文件的文件夹并运行以下命令来启动 gdbserver:
gdbserver <ip>:<port> <binary>
gdbserver 应该返回它已启动并正在侦听您定义的端口的输出。现在在客户端计算机上,启动 GDB,然后使用 target 命令连接到远程服务器:
target remote <server_ip>:<port>
编写 GDB 脚本来自动调试
GDB 允许程序员编写自动执行 GDB 命令的 GDB 脚本。当您尝试多次调试代码的同一部分时,这会非常有帮助。您可以使用 GDB 脚本来自动执行整个过程,而不必在每次加载二进制文件时设置断点、单步执行代码并打印变量值。
这是一个例子:
set logging enabled on
set logging file sample.out
break main
command 1
backtrace
print N
continue
end
quit
在上面的脚本中,您告诉 GDB 启用日志记录并将日志保存到名为 sample.out 的文件中,然后在 main 函数处设置断点。
对于断点 1,在本例中是函数 main 处的断点,运行以下命令:backtrace、print、继续。基本上,GDB将首先运行回溯,然后打印变量“N”的值,继续执行,最后退出。
要执行此脚本,请使用:
gdb -x <script> <binary>
现在您知道如何使用 GDB 调试程序了!
调试是一项基本技能,并且使用 GDB 进行调试是一项值得掌握的重要技能。 GDB 的不同功能(例如单步执行代码、设置断点、选择性线程调试等)使其成为在 Linux 上调试二进制文件时的强大工具。
如果您希望在 Windows 上调试应用程序,您可能会考虑了解有关 Windows 本机调试器 WinDBG 的更多信息。