LV02-04-GDB-调试工具

本文主要是GDB——调试工具的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
--- ---

一、调试器

程序中的错误主要分为两类,分别为语法错误和逻辑错误:程序中的语法错误几乎都可以由编译器诊断出来,很容易就能发现并解决;逻辑错误指的是代码思路或者设计上的缺陷,程序出现逻辑错误的症状是:代码能够编译通过,没有语法错误,但是运行结果不对。对于这类错误,只能靠我们自己去发现和纠正。

程序中出现的语法错误可以借助编译器解决;但逻辑错误则只能靠自己解决,而解决逻辑错误最高效的方法,就是借助调试工具对程序进行调试。

比如,让代码一行一行运行,而不是一次性全部运行,让程序停在某个地方,然后查看当前变量的值,或者内存中的数据,这样我们不就可以监控程序运行的每个细节了吗,哪里出了问题,也会很快找到。

编译程序需要借助专业的编译器,调试程序也需要借助专业的辅助工具,即调试器。

点击查看常见调试器
Remote DebuggerRemote Debugger 是 VC/VS 自带的调试器,与整个IDE无缝衔接,使用非常方便。
WinDbg大名鼎鼎的 Windows 下的调试器,它的功能甚至超越了 Remote Debugger,它还有一个命令行版本(cdb.exe),但是这个命令行版本的调试器指令比较复杂。
LLDBXCode 自带的调试器,Mac OS X 下开发必备调试器。
GDBLinux下使用最多的一款调试器,也有 Windows 的移植版。

二、GDB 是什么?

GDB 全称 GNU symbolic debugger ,诞生于 GNU 计划(同时诞生的还有 GCC 、 Emacs 等),是 Linux 下常用的程序调试器。当下的 GDB 支持调试多种编程语言编写的程序,包括 C 、 C++ 、 Go 、 Objective-C 、 OpenCL 、 Ada 等。我们可以通过 GDB 调试器实现以下几个功能:

  • 程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量;
  • 可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果等),即支持断点调试;
  • 程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。
GDB官网 https://sourceware.org/gdb

三、GDB的使用

【说明】后边输入的命令中,带有 (gdb) 的表示在gdb调试器中输入的命令。

1.基本使用

1.1文件处理

在使用 GDB 之前,需要对程序编译进行处理,需要加上 -g 参数才能生成满足 GDB 要求的可执行文件。例如

1
gcc -g test.c -o test

这样生成的可执行文件才可以正常使用 GDB 进行调试。

【注意】

(1)只有在代码处于运行或暂停状态时才能查看变量值。

(2)设置断点后程序在指定行之前停止 。

1.2启动 GDB 调试器

当生成满足要求的可执行程序后,可以如下命令启动调试器:

1
gdb <filename> # 这是在shell中执行的命令
  • filename :生成的可执行程序名称。

【注意】 gdb 在执行完一个命令后不输入任何命令直接回车, gdb 会默认执行上一个命令。

输入下边命令后会发现终端输出以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
---Type <return> to continue, or q <return> to quit---

这是是一些提示信息,我们按下 Enter 就可以继续进入调试器,而按下 q 按键就可以退出调试器。进入调试器后,命令格式如下:

1
(gdb) <command>

(gdb) 是提示符,就像 Shell 中的 # 和 $ 一样,后边跟上命令即可。

1.3关闭调试器

我们调试结束了,需要退出调试器的时候我们可以使用如下命令退出:

1
(gdb) q

然后按下 Enter 按键就可以退出 gdb 调试了。

2.基本调试命令

2.1 list 命令

在调试器中可缩写为 l ,可以列出所调试程序的代码。使用格式如下:

1
2
3
(gdb) l [option]
# 或者
(gdb) list [option]

【参数说明】

option说明
省略gbd会将gdb当前所处的行以及后面的代码打印在屏幕上。
lineNumber打印指定行附近的代码。如list 8,gdb会将8行前后的代码打印在屏幕上。
-gbd会将gdb当前所处的行前面的代码打印在屏幕上。
functionName打印名称为functionName的函数的上下文的代码。

2.2 run 命令

在调试器中可缩写为 r ,可以开始运行程序,若无断点,则会全部执行一遍,遇到第一个断点会停下。使用格式如下:

1
2
3
(gdb) r
# 或者
(gdb) run

2.3 break 命令

在调试器中可缩写为 b ,用于设置断点。使用格式如下:

1
2
3
(gdb) b [option]
# 或者
(gdb) break [option]

【参数说明】

option说明
lineNumber在指定的代码行打断点
filename:lineNumbergbd会在名称为filename的文件中的第lineNumber行打断点。
filename:functiongdb在名称为filename的文件中的function函数入口处打断点。
*addressgdb在在程序运行的内存地址处打断点。

2.4 info 命令

用于查看断点信息,使用格式如下:

1
2
(gdb) info b     # 查看所有断点信息
(gdb) info b n # 查看第 n 个断点信息

断点信息显示格式如下:

1
2
3
4
5
6
Num     Type           Disp Enb Address            What
1 breakpoint keep y 0x00005555555546d6 in main at 00GCC.c:6
breakpoint already hit 1 time
2 breakpoint keep y 0x00005555555546c8 in main at 00GCC.c:5
3 breakpoint keep y 0x00005555555546b9 in main at 00GCC.c:3
4 breakpoint keep y 0x00005555555546b9 in main at 00GCC.c:2

2.5 delete 命令

用于删除断点。使用格式如下:

1
2
(gdb) delete  b     # 删除所有断点信息
(gdb) delete b n # 删除第 n 个断点信息

2.6 next 命令

在调试器中可缩写为 n ,用于单步运行程序,但是如果有函数调用的话,它不会进入函数体。使用格式如下:

1
2
3
(gdb) n [option]
# 或者
(gdb) next [option]

【参数说明】

option说明
省略一条一条单步执行。
count执行后边的count条指令。

2.7 step 命令

在调试器中可缩写为 s ,用于单步运行程序,如果有函数调用的话,它会进入函数体。使用格式如下:

1
2
3
(gdb) s [option]
# 或者
(gdb) step [option]

【参数说明】

option说明
省略一条一条单步执行。
n执行后边的 n 条指令。

2.8 until 命令

在调试器中可缩写为 u ,用于运行到某一行程序或者直接运行完循环,如果有函数调用的话,它会进入函数体。使用格式如下:

1
2
3
(gdb) u [option]
# 或者
(gdb) until [option]

【参数说明】

option说明
省略可以使 GDB调试器快速运行完当前的循环体,并运行至循环体外停止。
n某行代码的行号,以指示 GDB 调试器直接执行至指定位置后停止。

【注意】until 命令并非任何情况下都会发挥这个作用,只有当执行至循环体尾部(最后一行代码)时,until 命令才会发生此作用;反之,until 命令和 next 命令的功能一样,只是单步执行程序。

2.9 continue 命令

在调试器中可缩写为 c ,用于继续运行程序。当程序在某一断点处停止后,用该指令可以继续执行,直至遇到断点或者程序结束。使用格式如下:

1
2
3
(gdb) c
# 或者
(gdb) continue

2.10 print

2.10.1查看变量

在调试器中可缩写为 p ,用于继打印指定变量的值。使用格式如下:

1
2
3
(gdb) p [option --][/fmt] expr
# 或者
(gdb) print [option --][/fmt] expr

【参数说明】

  • option :表示该命令所支持的选项,这些选项可以控制 print 命令输出指定内容的变量或者表达式的值,是可选的;
点击查看 option
option说明
-address on|off查看某一指针变量的值时,是否同时打印其占用的内存地址,默认值为 on。该选项等同于单独执行 set print address on|off 命令。
-array on|off是否以便于阅读的格式输出数组中的元素,默认值为 off。该选项等同于单独执行 set printf array on|off 命令。
-array-indexes on|off对于非字符类型数组,在打印数组中每个元素值的同时,是否同时显示每个元素对应的数组下标,默认值为 off。该选项等同于单独执行 set print array-indexes on|off 命令。
-pretty on|off以便于阅读的格式打印某个结构体变量的值,默认值为 off。该选项等同于单独执行 set print pretty on|off 命令。
  • fmt :指定输出变量或表达式值时所采用的格式,是可选的;
点击查看 fmt
/fmt功 能
/x以十六进制的形式打印出整数。
/d以有符号、十进制的形式打印出整数。
/u以无符号、十进制的形式打印出整数。
/o以八进制的形式打印出整数。
/t以二进制的形式打印出整数。
/f以浮点数的形式打印变量或表达式的值。
/c以字符形式打印变量或表达式的值。

例如,

1
(gdb) p /x a  # 以十六进制显示变量a的值
  • expr :指定要查看的变量或表达式。

【注意】

(1)options 参数和 /fmt 或者 expr 之间,必须用 -- ( 2 个 - 字符)分隔。

(2)[ ] 里边的内容是可选的,可以没有。

2.10.2修改变量值

print 命令还支持修改变量值,一般格式如下:

1
(gdb) print variable=value
  • variable :表示要修改的变量名。
  • value :表示修改后的值。

2.10.3查看数组

print 命令还支持 @ 运算符,多用于查看数组,一般格式如下:

1
(gdb) print first@len
  • first :用于指定数组查看区域内的首个元素的值。
  • len :用于指定自 first 元素开始查看的元素个数。

2.10.4查看不同文件同名变量

当程序中包含多个作用域不同但名称相同的变量或表达式时,可以借助 :: 运算符明确指定要查看的目标变量或表达式。:: 运算符的语法格式如下:

1
2
(gdb) print file::variable
(gdb) print function::variable
  • file :用于指定具体的文件名。

  • funciton :用于指定具体所在函数的函数名。

  • variable :表示要查看的目标变量或表达式。

3.多进程调试

此部分为后边学习多进程编程后补充而来。注意这里的命令我前边都加了 $ ,表示在终端中的标识,下边的 (gdb) 表示在调试器中的命令前缀标识。

  • (1)文件编译处理
1
$gcc -g test.c -o test
  • (2)启动 GDB
1
$gdb test
  • (3)开始调试
1
(gdb) start # 单步运行程序,程序会在第一条功能语句停下来。
  • (4)设置调试子进程或者父进程
1
2
(gdb) set follow-fork-mode child     # 设置GDB调试子进程
(gdb) set follow-fork-mode parent # 设置GDB调试父进程
  • (5)设置调试多个进程
1
(gdb) set detach-on-fork on/off  # 设置GDB跟踪调试单个进程或多个

on 表示只调试父进程或子进程的其中一个,(根据 follow-fork-mode 来决定),这是默认的模式。off 表示父子进程都在 gdb 的控制之下,其中一个进程正常调试(根据 follow-fork-mode 来决定),另一个进程会被设置为暂停状态。然后我们可以通过下边的命令显示可以调试的所有进程:

1
(gdb) info inferiors # 显示GDB调试的进程

如下图所示:

image-20220513102606547

随后我们就可以从上边显示的信息中心选择自己需要调试的进程号,进行指定进程的调试:

1
(gdb) inferiors <进程序号1,2,3...>  # 切换GDB调试的进程

4.多线程调试

此部分为后边学习多线程编程后补充而来。注意这里的命令我前边都加了 $ ,表示在终端中的标识,下边的 (gdb) 表示在调试器中的命令前缀标识。。

  • (1)文件编译处理
1
$gcc -g test.c -o test
  • (2)启动 GDB
1
$gdb test
  • (3)开始调试
1
(gdb) start # 单步运行程序,程序会在第一条功能语句停下来。
  • (4)显示所有线程
1
(gdb) info thread
  • (5)切换线程
1
(gdb) thread <id>
  • (6)为指定线程添加断点
1
(gdb) break location thread <id>
  • (7)设置线程锁
1
(gdb) set scheduler-locking on/off # on 表示其他线程会暂停,可以单独调试一个线程

四、TUI 模式

先上一张图吧,简单来说就是可以对照源码进行调试,这就是 GDB 自带的 GDB TUI ,它看起来比直接使用 list 要更美观一点:

image-20220513102733497

1.启动方式

  • 直接使用命令启动
1
2
3
gdbtui -q 需要调试的程序名
# 或者
gdb -tui -q 需要调试的程序名
  • 快捷键
1
Ctrl + x + a

正常开启 GDB 调试,然后按快捷键就可以启动啦。

2.相关命令

  • (1) layout 命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 命令
(gdb) layout src # 显示源代码窗口
(gdb) layout asm # 显示汇编窗口
(gdb) layout regs # 显示源代码/汇编和寄存器窗口
(gdb) layout split # 显示源代码和汇编窗口
(gdb) layout next # 显示下一个layout
(gdb) layout prev # 显示上一个layout
(gdb) winheight src + num # 将代码窗口的高度扩大 num 行代码
(gdb) winheight src - num # 将代码窗口的高度降低 num 行代码

# 快捷键
Ctrl + L # 刷新窗口
Ctrl + x + 1 # 单窗口模式,显示一个窗口
Ctrl + x + 2 # 双窗口模式,显示两个窗口
Ctrl + x + a # 回到传统模式,即退出layout,回到执行layout之前的调试窗口。
  • (2) focus 命令

在默认设置下,方向键和 PageUp/PageDown 都是用来控制 GDB TUI 的 src 窗口的,所以我们在终端中常用上下键显示前一条命令和后一条命令的功能就没有了。但是我们可以通过命令将窗口焦点移动到命令窗口,这样就可以啦。我们可以在 GDB 调试中查看 focus 命令的帮助如下:

1
2
3
4
5
6
7
(gdb) help focus
# 然后我们会得到如下提示
focus, fs
Set focus to named window or next/prev window.
Usage: focus [WINDOW-NAME | next | prev]
Use "info win" to see the names of the windows currently being di
splayed.

其中的 WINDOW-NAME 可选的有如下几种:

1
2
3
4
src  : the source window
asm : the disassembly window
regs : the register display
cmd : the command window

当我们执行:

1
(gdb) help cmd

这个时候,窗口焦点就在命令行窗口啦,我们就可以正常使用上下键来选择历史命令啦