📚 博主的专栏
上篇文章:Makefile的应用,以及第一个Linux程序进度条
下篇文章:操作系统内存管理:地址空间、页表、写时拷贝与进程调度原理
目录
2.程序是debug模式才能能被调试,release不可被调试,why?
回车:gdb会默认记录你最近一次执行的命令,直接回车执行最近一次执行的命令
1.list / l + 文件名 + :+ 行号/函数名 :显示binFile源代码,接着上次的位置往下列,每次列10行
5.delete / d + 断点Num (编号) :删除指定断点
8. step / s :逐语句(会进入函数) | next / n :逐过程
9.display + 变量名/&变量名 :常显示,每次执行next、step,会自动打印出display 过的变量值
10.undisplay + 编号Num :取消一个常显示:
11.continue / c :从当前断点移动到下一个断点
13. info / i + i、local : i(查看进程信息)、local查看当前函数当中所含的临时变量的值等价于vs查自动变量编辑
14.breaktrace(或bt) :查看各级函数调用及参数
1. 背景
1.程序的发布方式有两种,debug模式和release模式
2.Linux gcc/g++出来的二进制程序,默认是release模式
3.要使用gdb调试,必须在源代码生成二进制程序的时候,加上 -g 选项
2.程序是debug模式才能能被调试,release不可被调试,why?
因为在debug模式下、变异形成可执行程序时,会给可执行程序添加调试信息。
如何证明?
首先:
gcc/g++默认编译,采用release模式
gcc/g++,让他debug模式编译, -g
我们可以利用在debug模式下、变异形成可执行程序时,会给可执行程序添加调试信息。
对比debug、release模式下生成的可执行文件的 体积大小(debug>release)来证明:
在调试之前、我们需要先创建一个程序(myprocess.c),并使它能够自动化构建(Makefile):
1.Makefile:(此时所编译形成的可执行文件是默认的release模式下的)
myprocess:myprocess.c
gcc -o $@ $^
.PHONY: clean
clean:
rm -f myprocess
2.Makefile:(此时所编译形成的可执行文件是默认的rdebug模式下的)
myprocess-debug:myprocess.c
gcc -o $@ $^ -g
.PHONY: clean
clean:
rm -f myprocess
3.我在myprocess.c中写了一个这样的求和代码:
int AddToTarget(int start,int end)
{
int i = start;
int sum = 0;
for(; i <= end; i++)
{
sum += i;
}
return sum;
}
int main()
{
printf("run begin...\n");
int result = 0;
result = AddToTarget(1,100);
printf("result: %d\n",result);
printf("run end...\n");
return 0;
}
在我编译运行后:
怎么证明是调试信息呢?
在命令行输入:通过管道忽略大小写查看包含debug的信息
指令readelf:读取elf可执行程序
我们在linux下变异形成的可执行程序遵循ELF格式
readelf -S myprocess-debug | grep -i debug
往下阅读可发现:
在命令行输入:通过管道忽略大小写查看包含debug的信息查看release模式的:
readelf -S myprocess | grep -i debug
不会输出任何信息
2.开始调试
首先,也许你的系统下还没有下载gdb
下载gdb指令
apt-get install gdb
调试:
gdb myprocess-debug
gdb调试命令:
回车:gdb会默认记录你最近一次执行的命令,直接回车执行最近一次执行的命令
ctrl + d | 退出 |
quit / q | 退出 |
list/l 行号 | 显示binFile源代码,接着上次的位置往下列,每次列10行 |
list/l 函数名 | 列出某个函数的源代码 |
r/ run | 运行程序 |
n / next | 单条执行 |
s / step | 单步运行,进入函数调用 |
break / b 行号 | 在某一行设置断点 |
break/b 函数名 | 在某个函数开头设置断点 |
info / i break / b | 查看断点信息 |
delete / d 断点的Num | 删除指定断点 |
finish | 执行到当前函数返回,然后挺下来等待命令 |
print / p +变量/&变量 | 打印表达式的值,通过表达式可以修改变量的值或者调用函数 |
set var | 修改变量的值 |
run / r | 从开始连续而非单步执行程序 |
delete / d + Num | 删除序号为Num的断点 |
delete / d + b | 删除所有断点 |
disable + Num | 禁用断点 |
enable + Num | 启用断点 |
info / i + break / b: | 查看当前设置了哪些断点 |
display + 变量名 / &变量名 | 跟踪查看一个变量,每次停下来都显示它的值 |
undisplay + 变量名 / &变量名 | 取消对先前设置的那些变量的跟踪 |
breaktrace / bt | 查看各级函数调用及参数 |
info / i + locals | 查看当前栈帧局部变量的值 |
查看进程:
ps ajx | grep gdb
1.list / l + 文件名 + :+ 行号/函数名 :显示binFile源代码,接着上次的位置往下列,每次列10行
list:
list / l + 行号
list / l + 文件名 + :+ 行号 / 函数名
:行号
:函数名
都是显示上下文
2.r :运行程序
3.break / b :打断点
b myprocess.c:main
从程序的入口打断点
b 20
给程序的第20行打断点
4. info / i + b :查看我们所打的断点
5.delete / d + 断点Num (编号) :删除指定断点
6.disable + 断点号 :Num禁用(关闭)断点
在vs2022中
在Linux中:
使能 - no
7.enable + 断点号Num :使能断点,打开断点
vs:
Linux
现在我们删除所有的断点,进入实际调试:
将断点设置在,第一次打印时
在vs中:有逐语句、逐过程
在Linux中:
8. step / s :逐语句(会进入函数) | next / n :逐过程
next / n :逐过程是不会进入函数内部:
step / s 逐语句:
9.print 查看变量情况(监视、自动窗口)打印表达式的值,通过表达式可以修改变量的值或者调用函数
vs:
监视
自动窗口
Linux
print / p 变量名
$数字 -- 查询次数 , = 后面是查询的变量的值
print / p &变量名 --- 取地址
9.display + 变量名/&变量名 :常显示,每次执行next、step,会自动打印出display 过的变量值
我们注意到每个显示的前面也有编号:
10.undisplay + 编号Num :取消一个常显示:
以上都是最基础的gdb调试指令
现在我们开始学习实用的调试方法
需要用到调试器的地方一般是去解决中大型项目的问题
调试工具只是帮我们来找问题的
当代码出问题了,我们应该以什么样子的思路来找问题???
以二分查找的办法查找问题
我们可以将我们的代码想象成一个有序地数组,此时想要查找出问题的部分
我们可以先放开代码的前半部分,注释掉后半部分,查找完成后、如果出现问题再分成两半来查找,前半部分没有问题,再放后半部分,这样一步步缩小范围的来查找问题所在。
我们再分析问题
在调试时,我们利用断点来使代码二分。区域性处理问题
像这样:我想直接从第一个断点在run begin停止 运行到下一个断点 run end停止,中间直接跳过,就可以像这样打断点,一个范围一个范围的解决问题。
断点的本质:帮我们快速缩小问题所在的范围,方便我们分析出问题
在vs
在Linux中
11.continue / c :从当前断点移动到下一个断点
再根据是否出问题选择next / step(step可进入函数)去查找问题。
12.finish :快速运行完当前函数,并停下
假如我现在确认问题就出在函数体内部:
在vs中:
我想要从当前的循环中先出来:
我们可以直接拖动当前的箭头拖动到指定位置
此时再继续执行,就相当于跳过了从上一次循环完,直接跳出
直接打印出上次循环完,i = ?某个值时,的result。而不是最终的结果5050。
在Linux中,我现在也想要直接跳出循环直接看result的值:
从当前,将所有循环执行后跳出到指定的第12行
直接得到结果:
13. info / i + i、local : i(查看进程信息)、local查看当前函数当中所含的临时变量的值等价于vs查自动变量
14.set var + 需要修改的变量以及赋值 :改变量的值
有些类似于:刚才在finish中所举的vs的例子
set var 通常用来
在你的代码中,可能有一个判断条件,但是他总是不执行,所以根据判断条件将某变量设置成某值
再去找为什么这个判断条件没有设置成功的原因
14.breaktrace(或bt) :查看各级函数调用及参数
在vs中调用堆栈:
刚开始点击:栈顶为main()
开始调试:进入函数:
此时栈顶就成了:AddToTarget(int start,int end)
调用栈能看到我们函数的调用链
在Linux中
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。 你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。