Tian Jiale's Blog

GDB 简要使用说明

什么是 GDB?

GDB 是 GNU Project 调试器,它允许您查看另一个程序在执行时“内部”发生了什么——或者在它崩溃时另一个程序正在做什么。

GDB 可以做四种主要的事情(加上支持这些事情的其他事情)来帮助你在行动中捕捉错误:

  • 启动程序,指定任何可能影响其行为的内容。
  • 使您的程序在指定条件下停止。
  • 检查程序停止时发生了什么。
  • 更改程序中的内容,这样您就可以尝试纠正一个 bug 的影响,然后继续了解另一个 bug。

这些程序可能与 GDB (本机)在同一台机器上执行,也可能在另一台机器(远程)上执行,或者在模拟器上执行。GDB 可以在大多数流行的 UNIX 和 Microsoft Windows 变体,以及 Mac OS X 上运行。

GDB 支持如下语言:

  • Ada
  • Assembly
  • C
  • C++
  • D
  • Fortran
  • Go
  • Objective-C
  • OpenCL
  • Modula-2
  • Pascal
  • Rust

如何启动 GDB?

如果我们使用 [tldr](tldr pages) 来查看 gdb 使用方法,我们可以得到如下提示:

$ tldr gdb
gdb
The GNU Debugger.More information: https://www.gnu.org/software/gdb.

 - Debug an executable:
   gdb {{executable}}

 - Attach a process to gdb:
   gdb -p {{procID}}

 - Debug with a core file:
   gdb -c {{core}} {{executable}}

 - Execute given GDB commands upon start:
   gdb -ex "{{commands}}" {{executable}}

 - Start gdb and pass arguments to the executable:
   gdb --args {{executable}} {{argument1}} {{argument2}}

这是 GDB 的几种常见用法,分别是:

  • 执行一个程序并 debug;
  • 通过指定程序的 proceID 进入一个正在运行的程序并 debug;
  • 通过指定程序出错时生成的 [core 文件](Core Files in Linux (perforce.com)) 还原程序出错时的现场来 debug;
  • 执行一个程序并传入参数并 debug。

我们编写一个简单的 C 语言程序作为我们调试程序。

#include <stdio.h>

int main()
{
  int a = 1, b = 2, c = 0;
  c = a + b;
  printf("c = %d\n", c);

  return 0;
}

编译链接为可执行文件同时附带调试信息:

gcc -g a.c -o a

使用如下命令开启 GDB 调试:

gdb a

进入 GDB 后将显示类似如下的信息:

GNU gdb (Debian 10.1-1.7) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a...
(No debugging symbols found in a)
(gdb)

它给我们提示可以使用 apropos word 来查找相关的命令,所以在不查询文档时可以通过这种方法快速查找某个命令如何使用。

更多启动相关命令见官方文档 Running (Debugging with GDB) (sourceware.org)

如何在 GDB 中调试程序?

调试无非是了解程序的运行逻辑,以及在某个执行步骤中程序的状态,所以作为入门只需要知道如何中断、启动、查询,以及如何使用 display 来提供代码展示。

中断

中断可以使用断点(Breakpoints)、观察点(Watchpoints)、捕捉点(Catchpoints)。断点是程序运行到此处则中断;观察点用于观察某个变量甚至是由运算符组合的许多变量,如果变量值发生变化则中断;捕捉点则是在程序触发某种事件时中断,如 catch、throw、assert、exec、syscall、fork、vfork、signal。

可以使用 list 命令来查询代码,每次执行将输出十行代码,如果需要获取更多可以多次使用 list 命令,同时 list 也支持打印指定行的代码,用法如下:

(gdb) list
(gdb) list 3,5

使用如下命令设置断点:

(gdb) break locspec

locspecc 可以指定函数名、行号、指令地址等,break 可以缩写为 b。

使用如下命令来查询断点:

(gdb) info breakpoints
(gdb) info b

使用如下命令设置观察点:

(gdb) watch expr

使用如下方式来查询观察点:

(gdb) info watchpoints

使用如下方式来设置捕捉点:

(gdb) catch event

捕捉点同样使用 info b 来查看,值得一提的是,此命令同样会列出我们设置的观察点。

启动

启动分为从最开始启动程序和中断后继续执行程序。

从最开始启动程序的命令有starti 和 run两个命令。其中 starti 是开启程序,并停止在第一个指令,该指令在 linux 为 _start 而非 main 函数,因为堆栈、全局函数等一系列程序初始状态尚未设置。run 命令则是开启程序并在到达设置的断点处中断。

中断后继续执行程序,其实是 Continuing and Stepping ,这里只介绍最常用的几个命令:

(gdb) continue / c
# 在程序上次停止的地址处恢复程序执行;绕过在该地址设置的任何断点,直到遇到下一个断点。

(gdb) step / s
# 继续运行程序,直到控制到达另一个源代码行,然后停止它并将控制返回给 GDB,如果遇到函数则进入函数。

(gdb) step count
# 按步骤继续运行,但要计算次数。如果在计数步骤之前出现断点或与单步执行无关的信号,则单步执行立即停止。

(gdb) next [count] / n [count]
# 继续到当前(最内层)堆栈帧中的下一个源行。这与 step 类似,但是在代码行中出现的函数调用不会停止执行,即将函数看作一个原子指令。

(gdb) finish
# 继续运行,直到所选堆栈帧中的函数返回之后。

(gdb) until locspec
# 继续运行程序,直到程序到达解析 locspec 的代码位置,或者当前堆栈帧返回。

(gdb) stepi / si
# 执行一个指令,然后停止并返回到调试器。

(gdb) nexti / ni
# 执行一个指令,但如果是函数调用,则继续执行,直到函数返回。

查询

在程序执行过程中,我们可能需要获取某些信息,如前文提到的查询断点指令 info breakpoints,如下是常用的查询:

(gdb) print expr       # 打印表达式的值
(gdb) info breakpoints # 查询断点
(gdb) info frame       # 查询堆栈信息
(gdb) info registers   # 查询寄存器的值

更友好的页面

为了更清晰地直到程序运行到哪里,我们可以显示源代码,甚至是汇编代码。

显示当前运行的源代码:

(gdb) layout src

我们可以得到如下页面:

┌─a.c────────────────────────────────────────────────────────────────────────────┐
│   1           #include <stdio.h>                                               │
│   2                                                                            │
│   3           int main()                                                       │
│   4           {                                                                │
│B+>5             int a = 1, b = 2, c = 0;                                       │
│   6             c = a + b;                                                     │
│   7             printf("c = %d\n", c);                                         │
│   8                                                                            │
│   9             return 0;                                                      │
│   10          }                                                                │
│                                                                                │
│                                                                                │
│                                                                                │
│                                                                                │
│                                                                                │
│                                                                                │
│                                                                                │
│                                                                                │
│                                                                                │
└────────────────────────────────────────────────────────────────────────────────┘
native process 134526 In: main                           L5    PC: 0x55555555513d
(gdb) layout asm
(gdb)

显示汇编代码:

(gdb) layout src

得到如下页面:

┌────────────────────────────────────────────────────────────────────────────────┐
│   0x555555555135 <main>           push   %rbp                                  │
│   0x555555555136 <main+1>         mov    %rsp,%rbp                             │
│   0x555555555139 <main+4>         sub    $0x10,%rsp                            │
│B+>0x55555555513d <main+8>         movl   $0x1,-0x4(%rbp)                       │
│   0x555555555144 <main+15>        movl   $0x2,-0x8(%rbp)                       │
│   0x55555555514b <main+22>        movl   $0x0,-0xc(%rbp)                       │
│   0x555555555152 <main+29>        mov    -0x4(%rbp),%edx                       │
│   0x555555555155 <main+32>        mov    -0x8(%rbp),%eax                       │
│   0x555555555158 <main+35>        add    %edx,%eax                             │
│   0x55555555515a <main+37>        mov    %eax,-0xc(%rbp)                       │
│   0x55555555515d <main+40>        mov    -0xc(%rbp),%eax                       │
│   0x555555555160 <main+43>        mov    %eax,%esi                             │
│   0x555555555162 <main+45>        lea    0xe9b(%rip),%rdi        # 0x555555556 │
│   0x555555555169 <main+52>        mov    $0x0,%eax                             │
│   0x55555555516e <main+57>        call   0x555555555030 <printf@plt>           │
│   0x555555555173 <main+62>        mov    $0x0,%eax                             │
│   0x555555555178 <main+67>        leave                                        │
│   0x555555555179 <main+68>        ret                                          │
│   0x55555555517a                      nopw   0x0(%rax,%rax,1)                  │
└────────────────────────────────────────────────────────────────────────────────┘
native process 134526 In: main                           L5    PC: 0x55555555513d
(gdb) layout asm
(gdb)