Fish Touching🐟🎣

GDB Crash Course

Aug 19, 2022

gdb

# 前言

gdb 想必大家都听说过,作为最有名气的调试器之一,却由于其在命令行中运行的特点导致了周围用的人并不算多,加之现代编辑器中的调试功能已经成为标配,在很多时候用自带的调试器就能完成调试了。

但我依旧认为 gdb 十分重要,一是因为如果是多文件编程就不能用 VSCode 中自带的调试器(其实也是 gdb ,套了层 gui ,或许改下 json 能用,但是不打算改),二是因为市面上不同编辑器所带的调试功能用法虽然大同,但总是小异。特别是 SCNU 机房的电脑默认 VSCode 配置无法直接调试,但默认装有 gdb(神奇) ,就不用在期末考试时候换用不熟悉的 VS || DevC++ 了 :)

2022.01.05 update
不用想在机房用 g++ || gdb 了,首先机房各电脑环境不同,必须要新 Win10 系统才能有环境,其次机房的 g++ 环境似乎也有点问题,编译不出可执行文件,很神秘。写半小时代码,花半小时解决怎么运行。幸好考试简单。。
还是学一手 VS 下的 debug 吧。

# 正文

# 前期准备

# 调试的理念

💡 这部分是我自己的理解,调试用的还少,学的很浅,希望有人补充

先稍微讲下我对调试的理解

一整个软件 | 代码是十分庞大的,人脑处理不过来,更别说各类循环嵌套个三四层(my shitty code would be like)之后基本上就很难判断中间过程实现的对错了。

而最基本的调试就是把大块的代码分开,在运行过程中暂停,能够看到中间变量值来判断实现的对错(breakpoint | print)

更进一步的调试则是底层方面的调试(栈, 栈帧,内存地址,call stack,汇编,寄存器 etc.)等不那么显而易见的调试

本文只涉及最基本调试

💡 在下面操作前,先 gdb program_name 进入 gdb 界面

# 断点

# 生成断点

从上面的理解来看,调试至少要有运行过程中暂停的功能,这个功能就是 breakpoint AKA 断点

用法1: break $line_number || break 9

这时候虽然你看不见,但是 gdb 已经打好断点了 Breakpoint 1 at 0x5d94: file main.cpp, line 10.

不过有两点点需要注意,第一点如上文,输入的是第9行处打断点,实际上断点产生在第10行,这是因为 gdb 会自动找第一个有效执行语句而忽略其他预处理语句如 include namespace define etc. .第二点是如果在想在多文件编程时给其他文件打断点,需要使用 break file_name.c:60

用法2:break $function_nmae || break main

直接找到函数打断点,适合不知道在第几行但是知道函数名的更多数情况,在使用函数模块化功能以及分文件编程时十分好用!

打断点什么都好,就有一点,break 这个单词太长了,使用频率太高了,虽然 gdb 提供了 tab 自动补全,但还是麻烦,懒是人类第一生产力,gdb 内置了部分使用频率高的功能的缩写。简单来说,对于 break 命令直接输 b 也是等价的

用法3: b 9 || b main

打完断点之后在 gdb 中直接输入 run 就行了,这就能在断点处暂停执行了,想要执行下一步需要使用 next || step || continue,将在稍后详细对比

# 删除断点

要是断点给错,用 delete 命令删除,缩写为 d

用法: delete $breakpoint_index || delete 2

注意这里的 2 不是行号,而是断点序号,如果忘记断点序号,使用 info break 查看。相似的,如果想看自己打了哪些断点,也可以使用 info break 来查看。

# 代码对照

上面提到打断点要知道行号或者函数名,那不得编辑器和 gdb 分屏看?太麻烦了!懒又是人类第一生产力,gdb 其实自带了代码和 gdb 命令行的对照

用法:gdb -tui program_name (在运行 gdb 时添加参数),也可以在 gdb 中 ctrl-x 进入, ctrl-a 退出。

能够展示断点位置,当前执行情况,以及代码的对照

# 运行程序

在打完断点后就能运行了。在 gdb 中直接输入 r 就行(run 的缩写),这就能在断点处暂停执行了,想要执行下一步需要使用 next || step || continue,将在此详细对比

# Step

运行下一步代码,同时遇到自己定义的函数将会追踪进入函数,进一步debug。缩写为 s

# Next

运行下一步代码,但在遇到自己定义的函数不会追踪进入函数,而是直接获取返回值往下运行。简单来说,全程 next 只会执行完 main 函数,而 step 将会执行完每一行代码。缩写为 n

# Continue

运行到 breakpoint 或者运行完整个程序。缩写为 c

💡 实际上,上面的介绍顺序是按运行完程序所需操作数来排序的

# Until

until 运行至某行停止 (until reach the line) ,一般用来跳出循环,缩写为 u

用法:u $line_number || u 60

同时,在 gdb 下直接回车的作用是重复上一指令,对于单步调试(也就是step || next 这样一步步进行的调试)非常方便

# 变量值查看

打完断点,用 step || next || continue 执行下一步,不会给你任何有效信息,你还需要手动选择变量值进行查看。有两种方式,print || watch

# Print

单次打印一个值,但不仅是变量值,可以是任何 C 语言的有效表达式,包括数字,变量甚至是函数调用。缩写为 p

p a:将显示整数 a 的值
p ++a:将把 a 中的值加 1, 并显示出来
p name:将显示字符串 name 的值
p gdb_test(22):将以整数 22 作为参数调用 gdb_test() 函数
p gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数

# Watch

使用 watch 命令设置观察点,也就是当一个变量值发生变化时,程序会停下来。缩写为 wa

用法:watch a 当 a 发生改变时就会停止


至此,基础的 gdb 调试就算介绍完了,退出使用 quit || q 即可

# 功能补充

# Info

用于查看各种信息

info functions // 列出可执行文件的所有函数名称,可以用来打断点
info functions $regex // 支持正则表达式的搜索函数名
info breakpoints // 列出断点
info locals // 打印当前函数局部变量的值
info args // 打印传入函数的参数 arguments AKA 实参

# Backtrace

一般程序崩溃时只会告诉你 Segmentation Fault ,通过 backtrace 可以知道时哪一行出现错误,缩写为 bt

故意写错尝试下就知道了

# 其他补充

start vs run

run will start (or restart) the program from the beginning and continue execution until a breakpoint is hit or the program exits. start will start (or restart) the program and stop execution at the beginning of the main function.

finish

continue (shorthand: c) will resume execution until the next breakpoint is hit or the program exits. finish will run until the current function call completes and stop there. You can single-step through the C source using the next (shorthand: n) or step (shorthand: s) commands, both of which execute a line and stop. The difference between these two is that if the line to be executed is a function call, next executes the entire function, but step goes into the function implementation and stops at the first line.

缩写一览,从缩写反学或许是个不错的主意

b -> break
c -> continue
d -> delete
f -> frame
i -> info
j -> jump
l -> list
n -> next
p -> print
r -> run
s -> step
u -> until
aw -> awatch
bt -> backtrace
dir -> directory
disas -> disassemble
fin -> finish
ig -> ignore
ni -> nexti
rw -> rwatch
si -> stepi
tb -> tbreak
wa -> watch
win -> winheight

当数值到 X 时设置断点,在断点设置后加 if

break iter.c:6 if i == 5 || break main:looping if i == 5

CS107 Software Testing Strategies

Tover的 gdb 进阶教程

# 参考资料

gdb 调试利器 - Linux Tools Quick Tutorial

100个gdb小技巧

CS107 GDB and Debugging

GDB: break if variable equal value

GDB Manual