文章目录
一 铺垫
二 指令集和使用
前几篇博客中,我们分别学习了与编辑、编译、自动化构建代码、上传代码的工具。而今天,我们将学习最后一个工具 —— Linux 下的调试器 gdb 。
而 gdb 这个调试工具的指令有很多,且在 Linux 下并没有图形化界面,所以 gdb 调试的体验并不是很好。
所以对于 gdb ,我们的学习目标就是会常用的操作。
一、铺垫
在讲解使用之前,我们先进行一下铺垫:
程序的发布方式有两种,debug 模式和 release 模式 ,分别是调试版本和发布版本。
debug 模式是程序员在自主编写代码时的模式,在 debug 模式下是 含有调试信息 的;而 release 模式下是 不含调试信息 的 ,且 release 进行了各种优化,方便用户使用。
在 Linux 的 gcc/g++ 编译器下编译的代码往往都是 release 版本 。其实对于这点也很容易想通,我们平常的开发一般是在 ide 上进行,当产品发布时再到 Linux 上进行线上发布,且测试人员测的也是 release 版本的代码。所以为什么是 release 版本就很容易想通了。
事实胜于雄辩,我们再证明一下 :
调试代码:
#include <stdio.h>
int addToTop(int top)
{
printf("enter addToTop\n");
int sum = 0;
for (int i = 1; i <= top; ++i)
{
sum += i;
}
printf("quit addToTop\n");
return sum;
}
int main()
{
int top = 100;
int res = addToTop(top);
printf("result = %d\n", res);
return 0;
}
Makefile:
test:test.c
gcc test.c -o test -std=c99
.PHONY:clean
clean:
rm -f test
(注:在循环内定义变量为 c99 标准,所以编译时需要加上 -std=c99 )
Release :
编译一下文件,并将生成可执行程序改名为 test-release 。
按 gdb test-release 进入调试:
![](https://img-blog.csdnimg.cn/img_convert/e3f3a732cc4dd2e0295a09380b6fb849.png)
no debugging symbols found:没有调试信息
而 release 版本是没有调试信息的,没有调试信息就无法调试。
按 quit 退出调试。
在 Linux 中,编译时添加 -g 选项,就是让 gcc/g++ 编译时以 debug 形式编译 。
Debug :
所以修改一下 Makefile :
gcc test.c -o test -g -std=c99
然后继续进行编译,并将编译出的内容改名为 test-debug :
![](https://img-blog.csdnimg.cn/img_convert/c85611835778af992af818bc27a788e3.png)
gdb-test-debug 开始调试:
![](https://img-blog.csdnimg.cn/img_convert/b80a296b1297127cbd8e91ffd111ade9.png)
这时就可以调试了,验证完毕,按 quit 退出调试。
对比生成文件大小 :
![](https://img-blog.csdnimg.cn/img_convert/155f73f3393629fd45b6cfb772808c22.png)
debug 的文件,比 release 大。就是因为里面包含了调试信息等内容。
拓展 :
readelf 指令:读取可执行程序的二进制构成,可执行程序遵守二进制排布规则:elf
用 readelf -S test-debug | grep -i debug 忽略大小写筛一下含有 debug 的信息:
![](https://img-blog.csdnimg.cn/img_convert/68ef14e6304fb51844fd3f3a68304f60.png)
我们的铺垫总结起来无非就是三点 :
程序有两种发布方式:debug 模式 和 release模式
Linux 下 gcc/g++ 编译处的二进制程序默认是 release 模式
要启用 gdb 调试,必须在编译生成可执行程序时加上 -g 选项
二、指令集和使用
1.指令集
/ 代表的就是或者,比如 list / l 就是两者都可以,后一个通常为简写,一般使用简写。
gdb file :开始调试
list/l 行号:显示源代码,接着上次的位置往下列,每次列10行。list 通常简写为 l 。
list 0/1 :从源代码的第一行开始显示代码,每次列 10 行
list/l 函数名:列出某个函数的源代码,10行
r / run:运行程序,类似于 vs 上的 f5 ,如果有断点就停在第一个断点处,否则直接执行程序
n / next:单条执行。也被叫做 逐过程 ,和 vs 上的 f5 一样,遇到函数会直接把函数执行完
s /step:进入函数调用。也被叫做 逐语句 ,按住会进入函数,包括库函数
break / 行号:在某一行设置断点
break 函数名:在某个函数开头设置断点
info breakpoints/break/b :查看断点信息,查看的断点以编号形式排布,从 1 开始。
finish:执行到当前函数返回,然后停下来等待命令
print / p:打印表达式的值,通过表达式可以修改变量的值或者调用函数
p 变量:打印变量值,类比于短暂监视窗口
set var:修改变量的值。常用语循环,比如跳转到第 n 次循环
continue / c:从当前位置开始连续而非单步执行程序。说白了就是从一个断点处,直接运行到下一个断点处,类比于 vs 上的 f5 在断点进行跳转
delete breakpoints:删除所有断点
delete breakpoints n:删除序号为 n 的断点
disable breakpoints:禁用断点,改变断点使能
enable breakpoints:启用断点
display 变量名:跟踪查看一个变量,每次停下来都显示它的值(监视窗口常显示)
undisplay 编号:取消对先前设置的那些变量的跟踪,若使用 undisplag 则全部取消
until 行号:在函数内进行指定位置跳转,执行完区间代码
breaktrace / bt:查看各级函数调用及参数。说白了就是查看调用链
info / i locals:查看当前栈帧局部变量的值
quit:退出gdb调试
提示:gdb 会记住最近一次的指令,比如上次 l 0 进行翻阅查看,之后一直回车就可以显示出内容。
2.演示
list / l :显示内容
![](https://img-blog.csdnimg.cn/img_convert/1becb27c206100edc0b910e25b5f5f41.png)
显示函数部分的话,只要求显示片段含有函数名。
list 是查看内容的指令,它不影响实际的调试指令,也就是说 list 显示的内容并不会干扰到当前的调试次序。
断点操作 :
b 行号 和 b 函数名:打断点
![](https://img-blog.csdnimg.cn/img_convert/95cbc742cceb9a9aacd327544319cb5c.png)
info breakpoints/break/b :查看断点信息
![](https://img-blog.csdnimg.cn/img_convert/a0696d3a307601fed516bf0f56bb82a4.png)
解释:
num:断点编号,从 1 开始
enb:代表着断点使能。y代表打开;n代表关闭
what:说明断点是什么,在哪个函数中,在哪个文件,第几行。
过渡步骤:运行 r 跳转到断点,在断点处停下来
此刻 info b 查看断点信息:
![](https://img-blog.csdnimg.cn/img_convert/673d54c15f46753643850278299881a9.png)
显示信息: breakpoint already hit 1 time 代表断点命中一次
d 断点编号:删除断点(是编号不是行号) d break :删除所有断点
![](https://img-blog.csdnimg.cn/img_convert/eacbc18b17285518d93380a217517a46.png)
d break 可以删除所有断点,同理 d breakpoints 和 break breakpoints 也都对
删除时会询问是否删除,y 代表 yes ,n 代表 no
disable b num :关闭断点使能 enable b num :打开断点使能
![](https://img-blog.csdnimg.cn/img_convert/ae3ea3ecd3ceb5ca84467bbc2bd60f5e.png)
分别显示了断点打开和关闭的过程
info b :可以直接关闭所有的断点使能
这一过程就像 vs 上设置空断点一样
info :查看 info 可以查阅的信息
![](https://img-blog.csdnimg.cn/img_convert/ef861ea9b892c2c14973b63166a41e82.png)
逐过程和逐语句 :
next/n :逐过程
一步可以走掉一个函数,相当于 vs 中的 f10
![](https://img-blog.csdnimg.cn/img_convert/c4c082e41572278c68bad9a03de7a807.png)
一次走过了函数,函数中打印的内容也会显示出来
还会显示当前行数,以及当前行的内容
s :逐语句
一次走一条代码,可进入函数,同样的库函数也会进入
调用一个函数,就要把函数调用的数据进行压栈
调试位置变成整个代码的第一行
addToTop函数第一行为整个程序的第五行
显示行号和内容
bt :查看调用链
![](https://img-blog.csdnimg.cn/img_convert/a0d9f159d2689d2757eed1c0b6ba1336.png)
看到一个函数调用的过程,压栈的过程,main函数在栈底,addtotop就像被压栈了,被压到栈顶
查询数据和常显示数据:
p 变量名/地址:暂时查询变量
![](https://img-blog.csdnimg.cn/img_convert/f94f153adfec57117e1abb9466fef768.png)
使用完该指令之后,会给变量一个对应的编号
这个数据只是短暂显示,下次执行指令会消失
display 变量名:常显示变量的数据
![](https://img-blog.csdnimg.cn/img_convert/46cc91d59a0d1e7758e3408be218f457.png)
常显示变量数据,类似于 vs 中的监视窗口,display 显示的数据伴随着调试过程一直存在
且会按照 display 的顺序给上相应的编号,显示为倒序显示
undisplay 编号 :取消变量的显示
undisplay 取消显示后,每次执行不会显示刚刚取消的变量
until 行号:不打断点,在函数内进行指定位置跳转,执行完区间代码
![](https://img-blog.csdnimg.cn/img_convert/668eeeab87d03513d2de0054b31bdd28.png)
刚刚还在执行循环中的内容,使用 until 直接跳转到 11 行,把循环执行完毕
finish:执行到当前函数返回,然后停下来等待命令
假设当前已经进入行数,想要直接跑完这个函数,函数中没有任何断点,跑完之后就停下来:
![](https://img-blog.csdnimg.cn/img_convert/4b58b4d4fad4ff0de07e85758630e13d.png)
这样就直接跑完了函数,并显示了返回结果
再次 n 就是返回主函数,并执行完调用函数的语句,返回下一行
continue / c:从一个断点处,直接运行至下一个断点
跳转到下一个断点处,并把跳转过程中打印的内容显示出来
set var :修改变量的值,很适用与循环跳转到指定循环次
![](https://img-blog.csdnimg.cn/img_convert/956da92b8051afc1fb5f02299e27e3ec.png)
在循环中使用 set var i=100 ,直接跳转到 i=99 时的循环次
其实调试本质上就是查找问题的过程,调试完成再根据查找到的问题,修改程序,随后再进行测试,看问题是否已经解决 。
而调试一个程序员必备的技能,我们在日常写代码时,就要慢慢培养自己调试和快速定位错误,即排错的能力。
但是对于 gdb ,这个调试器其实使用起来还是挺有难度的。一是因为它没有图形化界面;二是因为它指令繁杂。所以使用 gdb 调试只需要掌握基本就好。