💭 写在前面
本章我们将带着大家高雅的学一学令众多习惯图形化页面的朋友难受的 gdb 调试,这部分知识可以选择性学习学习,以后倘若遇到一些问题时能在 Linux 内简单调试,还是很香的。然后在讲讲 gcc 和 g++,系统讲解程序运行时的各个过程。
Ⅰ. GDB 调试
0x00 调试前的准备
我们先来创建一个用来演示 GCD 调试功能的目录:
既然要调试,我们就必须要有个代码,我们这里写一个数字累加的代码:
🚩 运行结果:
结果是5050,没有问题。如果我们代码出现了一些问题需要我们调试,我们就可以使用 gdb。
如果此时你出现了报错,说什么不支持 for 循环里面定义变量,你可以输入:
$ gcc hello.c -o hello -std=c99
0x01 Linux 默认集成环境
在你当前的代码目录下直接执行 gcb + 形成的可执行程序:
$ gdb [可执行程序]
此时就进入了 gdb 的调试命令行中:
(如果想退出,直接按 quit 就可以退出了)
gcb 读取我们的 hello 程序时出现了 "没有调试符号被发现" 的警告:
这是什么意思呢?
我们的 gdb 中有一条命令叫做 list(简写成 l),但是我们输入后出现以下提示:
因为 —— 默认形成的可执行程序无法调试!!!
相信大家都知道,C语言的发布方式有两种,一种是 debug 一种是 release。
我们在 VS 里面可以直接调的原因是,VS2019 的默认集成环境是 debug。
而在我们的 Linux 中的默认集成环境是 release,换言之,
在我们 Linux 中如果你想发布一个程序,可以直接发布,无需加任何选项。
但是如果你想调试,以 debug 形式发布,那么你就需要在编译时在后面添加一个 -g 选项:
$ gcc hello.c -o hello.g _g
🔺 总结:Linux 默认形成的可执行程序是动态链接且是 release 方式发布。
如果想静态链接,加 -static
如果想动态链接,加 -g
0x02 readelf 读取 ELF 文件信息
release 和 debug 的区别:你的可执行程序里本来就有调试信息, 只是 debug 中才有。
首先,这两个版本也都是可以运行的:
并且我要告诉你的是:debug 版本比 release 版本多几千个字节,这是什么?
毫无疑问,这些就是一个可执行程序的调试信息,它在 debug 版本中有所显现。
📚 如果你想看调试信息,你可以输入:
$ readelf -S [可执行程序] # 以段的形式读取可执行程序,用于显示读取ELF文件中信息
💭 我们先看看 release 版的:
💭 我们再来读一读 debug 版的:
因为 debug 版本是能给你的可执行程序添加调试信息的,所以体积自然比 release 版本要大些。
所以我们调试的得是 debug 版本的可执行程序,预备工作全部做好,下面我们来正式学习 gdb。
0x03 显示代码 gcb(list)
现在我们是 debug 版本了,我们也顺理成章地能够使用前面我们说的 list 了。
(gdb) list [n] # 显示代码,可带行号 (gdb) list [function] # 显示带某函数的代码块 (gdb) list [begin, end] # 显示区间内的代码 ...
💭 操作演示:
一般在 VS 下调试的时候,除了让你看到代码,还会让你看到进行到了哪里,这里也是一样的。
你按下回车后,gdb 会自动记录你的上一条指令,直接按回车就是上一条命令:
(这么做就能把代码从第一行开始,将所有代码块逐个显示出来了)
0x04 断点
💭 假设我们想在下面代码的第15行处打个断点:
这要是放在 VS 下我们直接滑鼠选中对应行然后无脑按 F11 就行了。
而在 gdb 下打断点的方式是用 breakpoint:
(gdb) breakpoint [n] # 在第n行处打一个断点
💭 操作演示:我们在代码第15行打个断点看看:
此时如果你想查看断点,可以使用 info 查看你打的断点:
(gdb) info b # 查看断点
💭 操作演示:查看断点信息
我们再在第17行新增一个断点,此时我们就能看到两个断点了:
如果想要删除断点,在 VS 下我们直接再点以下小红点就搞定了:
(图形化界面无疑是成功的)
但是在 gdb 中,我们需要知道要删除的断点的编号:
(gdb) d [Num] # 删除Num号断点
💭 操作演示:删除1号断点(记不得要删除的断点的编号可以 info b)
此时 1 号断点已被成功删除,再次删除则会显示已经没有这个断点:
0x05 调试
准备开始调试,记得把刚才删除的断点再打回去,调试的指令如下:
(gdb) run # 开始调试
💭 操作演示:输入完 r 按回车开始调试:
(此时就悬停在了第15行)
如果我们把场上断点全部干掉了,此时按 r 调试程序就会直接跑完:
(这和 VS 也是一样的)
如果你想查看变量的内容,我们可以使用 print 命令:
(gdb) print [val]
💭 操作演示:查看变量内容
💭 操作演示:逐语句
如果想逐语句执行(逐语句即一步一步往后走),逐语句指令如下:
(gdb) step # 逐语句
我们 s 两次后,此时走到了函数的调用处。此时如果你不想进入该函数,就不要再按 s 逐语句了。
此时我们应该逐过程执行,我们可以使用 next 命令:
(gdb) next # 逐过程
💭 操作演示:逐过程
0x06 监视
我们在 VS 中调试代码的时候,有时候要 细 ♂ 细 ♂ 观 ♂ 察 某个变量时,我们会打开监视窗口:
在 gdb 下我们就可以使用 display 常显示来监视:
$ display [val] # 监视一个变量,每次停下来都显示它的值 $ display [v1, v2, v3...] # 同时添加多个变量,用括号隔开
💭 操作演示:常显示 i 变量
当然,我们也可以同时监视多个值:
(同时常显示三个变量)
直接输入 display 可以查看监视列表:
$ display # 查看当前监视列表
💭 操作演示:查看监视列表
如果想把某个变量从监视窗口移除,我们可以使用 undisplay:
$ undisplay [n] # 删除n号变量
💭 操作演示:删除3号变量
0x07 跳转(until & c & finish)
我们调试的时候在文本特别大的时候我们有时候会跳转,VS 里我们可以直接拖动箭头跳转。
gdb 调试下我们可以使用 until 指令跳转到指定行:
$ until [n] # 跳转到指定行
💭 操作演示:跳转到20行
如果想从一个断点跳转至另一个断点,我们可以使用 c:
$ c # 直接跳转到另一个断点
有时候难免手贱不小心进了不想进入了函数,就比如不小心逐语句进了 printf 函数。
这个在 VS 下逐语句是不会进去的,但是在 Linux 下会进入,此时如果你反悔了象出来,
就可以输入 finish,它可以做到直接执行完成一个函数就停下来。
$ finish # 执行到当前函数返回,然后停下来等待命令
0x08 对于 gdb 的态度
掌握上面单独介绍的 b、d、l、s、n、display、until、r、c、finish 其实就差不多了。
还有一些 gdb 的指令我们上面没有介绍,这里做一个整合:
list/l 行号:显示binFile源代码,接着上次的位置往下列,每次列10行。 list/l 函数名:列出某个函数的源代码。 r 或 run:运行程序。 n 或 next:单条执行。 s或step:进入函数调用。 break(b) 行号:在某一行设置断点。 break 函数名:在某个函数开头设置断点。 info break :查看断点信息。 finish:执行到当前函数返回,然后挺下来等待命令。 print(p):打印表达式的值,通过表达式可以修改变量的值或者调用函数。 p 变量:打印变量值。 set var:修改变量的值。 continue(或c):从当前位置开始连续而非单步执行程序。 run(或r):从开始连续而非单步执行程序。 delete breakpoints:删除所有断点。 delete breakpoints n:删除序号为n的断点。 disable breakpoints:禁用断点。 enable breakpoints:启用断点。 info(或i) breakpoints:参看当前设置了哪些断点。 display 变量名:跟踪查看一个变量,每次停下来都显示它的值。 undisplay:取消对先前设置的那些变量的跟踪。 until X行号:跳至X行。 breaktrace(或bt):查看各级函数调用及参数。 info(i) locals:查看当前栈帧局部变量的值。 quit:退出gdb。