淮安市建设监理协会网站天津手机网站制作
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:51
当前位置: 首页 > news >正文
淮安市建设监理协会网站,天津手机网站制作,上海做响应式网站的公司,做资源下载网站条件文章目录一、前言二、调试版本与发布版本1、见见gdb2、程序员与测试人员3、为什么Release不能调试但DeBug可以调试❓三、使用gdb调试代码1、指令集汇总2、命令演示⌨ 行号显示⌨ 断点设置⌨ 查看断点信息⌨ 删除断点⌨ 开启 / 禁用断点⌨ 运行 / 调试⌨ 逐过程和逐语句⌨ 打印 … 文章目录一、前言二、调试版本与发布版本1、见见gdb2、程序员与测试人员3、为什么Release不能调试但DeBug可以调试❓三、使用gdb调试代码1、指令集汇总2、命令演示⌨ 行号显示⌨ 断点设置⌨ 查看断点信息⌨ 删除断点⌨ 开启 / 禁用断点⌨ 运行 / 调试⌨ 逐过程和逐语句⌨ 打印 / 追踪变量⌨ 查看函数调用⌨ 修改变量的值排查问题三剑客⌨ 指定行号跳转⌨ 强制执行函数⌨ 跳转到下一断点四、实战演练Swap Two Numbers五、总结与提炼一、前言
学习了【vim】知道了如何编辑一个代码文本学习了【gcc】知道了如何编译一个代码文本学习了【make/Makefile】知道了如何自动化构建一个代码文本
但是如何对一段代码去进行调试呢此时就要使用到Linux下的调试器gdb了。对于这个调试器来说不像是VS中那样的图形化界面形式而是采用纯命令行的形式进行调试。可能我的讲解会比较晦涩难懂在学习的过程中主要是会一些gdb下基本的操作即可
二、调试版本与发布版本
1、见见gdb
下面是本次调试所要使用到的代码 1 #include stdio.h2 3 int AddToTop(int top)4 {5 printf(Enter AddToTop\n);6 7 int count 0;8 for(int i 1;i top; i)9 {10 count i;11 }12 13 printf(Quit AddToTop\n); 14 return count;15 }16 17 int main(void)18 {19 int top 100;20 int ret AddToTop(top);21 22 printf(ret %d\n, ret);23 return 0;24 }下面是Makefile中的内容用于自动化编译 1 mytest:test.c2 gcc -o mytest test.c -stdc99 3 .PHONY:clean4 clean:5 rm -rf mytest注-stdc99表示以c99的标准来编译代码 如果要进入gdb开始调试那直接gdb 可执行程序即可不过进去之后发现似乎有一些奇怪的内容【no debugging symbols found】翻译过来就是没有调试信息。那这是为何呢是gdb出问题了吗 先不要着急如果有经常调试的通过就可以知道只有在【DeBug】的环境下才会有我们想要的调试信息所以可以初步推断这可能不是一个【DeBug】版本的可执行程序先使用q(quit)退出gdb
让我们先看下去了解一下其他的知识再来解决这个问题
2、程序员与测试人员 接下去我们就来说说有关【DeBug】和【Release】版本的不同之处 【Debug】—— 调试版本 【Release】—— 发布版本
在使用VS的时候我们可以直接使用鼠标来进行操作当前程序以DeBug或者是Release的形式进行运行那么运行出来的可执行程序版本也是不同的我们程序员在编写代码后运行一般是使用【DeBug】环境进行运行。因为在企业里写软件项目将代码写完后程序员自己要做简单的测试保证代码没有问题当程序员自己测试完没有问题之后就会将这个可执行程序给到测试人员进行测试而且会给出自己的单元测试报告。对于测试人员来说所处的模式是【Release】也就是将来客户要使用的这款软件的发布版本当测试在测的过程中一定会发现一些问题。此时测试人员就会把报告再打回研发部。研发部做修改重新生成Release版本的可行性程序给到测试人员继续测试最后只有当测试通过了再将生成的【单元测试报告】与产品经理进行核对之后没有问题那这个软件才可以真正地面向市场
3、为什么Release不能调试但DeBug可以调试❓ 其实对于我们刚才直接make自动化生成的可执行程序是通过gcc直接编译产生得到的它是一个【Release】版本的可执行程序因此无法进行调试 若是我们想要使用gcc/g去生成一个可执行程序时默认是【Release】版本的而不是【DeBug】但若是我们想要去生成一个【DeBug】版本的可执行程序也是可以的只需要修改一下我们的Makefile即可给gcc后面带上一个-g的命令选项此时再去make一下的话生成的就是【DeBug】版本的了 为了之前的【Release】版本不被覆盖我们将其重命名一下为mytest-release在生成【DeBug】版本后一样对其进行一个重命名为mytest-debug 通过观察上图中两个可执行文件的大小便可以发现虽然它们都是可执行程序但是容量大小却不一样这是为什么呢❓ 因为以Release版本发布的软件是给客户的客户是不需要调试信息的 往可执行程序里添加很多的调试信息意味着软件的体积会变大 一方面用户下载需要时间了另一方面用户下载好之后将软件启动、运行都需要更多的时间体验不好。一般能不加就不加 但是对于DeBug来说会自动加调试信息容量体积比Release大
但是就这么说说太抽象了我们得看看这个可执行文件里的调试信息究竟是怎样的
readelf -S mytest-debug在C生万物 | 程序环境和预处理中我有说到过对于【可执行文件】它是一个二进制文件若是查看它的源码就可以发现里面都是一堆乱码 当我们面对一堆二进制乱码措手不及的时候给大家提到过一个东西叫做readelf其实它是Linux中的一个指令可以用来读取【elf】格式的文件
加上-S的命令选项以符号表的形式来进行读取这个文件就以一个列表的形式展现出了这个带调试信息的可执行程序下面展现几个比较常见的。例如这里的 .rodata就是read only data即只读全局数据区.data就是已初始化全局数据区.bss就是未初始化全局数据区 不过这些呢是这个可执行文件中的所有内容若是我们只是先要查看一些debug的调试信息就要对这些东西进行一个筛选才行此时就可以使用到grep命令来进行一个筛选。便可以查看到所有的debug调试信息了 上面是查看【DeBug】版本下的调试信息在【Release】版本有没有呢我们也是读取并搜寻一下这个文件便可以发现对于【Release】版本来说是不存在调试信息的所以什么都没有被打印出来 【总结一下】 程序的发布方式有两种debug模式和release模式 Linux gcc/g出来的二进制程序默认是release模式 要使用gdb调试必须在源代码生成二进制程序的时候, 加上-g选项 好说到这里对于调试相关背景就全部讲完了接下去我们正式进入【gdb】的学习⌨️
三、使用gdb调试代码
1、指令集汇总
因为这个调试器是在Linux环境下的是纯命令行模式所以会有很多的指令做好心里准备
注括号里面是该指令的全称 l(list) 行号/函数名 —— 显示对应的code每次10行 r(run) —— F5【无断点直接运行、有断点从第一个断点处开始运行】 b(breakpoint) 行号 —— 在那一行打断点 b 源文件函数名 —— 在该函数的第一行打上断点 b 源文件行号 —— 在该源文件中的这行加上一个断点吧 info b —— 查看断点的信息 breakpoint already hit 1 time【此断点被命中一次】 d(delete) 当前要删除断点的编号 —— 删除一个断点【不可以d 行号】 若当前没有跳出过gdb则断点的编号会持续累加 d breakpoints —— 删除所有的断点 disable b(breakpoints) —— 使所有断点无效【默认缺省】 enable b(breakpoints) —— 使所有断点有效【默认缺省】 disable b(breakpoint) 编号 —— 使一个断点无效【禁用断点】 enable b(breakpoint) 编号 —— 使一个断点有效【开启断点】 相当于VS中的空断点 enable breakpount —— 使一个断点有效【开启断电】 n(next) —— 逐过程【相当于F10为了查找是哪个函数出错了】 s(step) —— 逐语句【相当于F11】 bt —— 看到底层函数调用的过程【函数压栈】 set var —— 修改变量的值 p(print) 变量名 —— 打印变量值 display —— 跟踪查看一个变量每次停下来都显示它的值【变量/结构体…】 undisplay 变量名编号 —— 取消对先前设置的那些变量的跟踪 排查问题三剑客 until 行号 —— 进行指定位置跳转执行完区间代码 finish —— 在一个函数内部执行到当前函数返回然后停下来等待命令 c(continue) —— 从一个断点处直接运行至下一个断点处【VS下不断按F5】 2、命令演示
看了上面的这些命令后相信你一定回到了刚开始学习Linux指令的时候那种恐惧感不过没关系我会一一地演示这些指令让你在看完本文后有一个基本的调试能力
首先我们进入到gdb然后它会等待我们输入指令 ⌨ 行号显示
l(list) 行号/函数名 —— 显示对应的code每次10行
首先若是直接【L】的话便会随机显示出该源文件中的随机10行内容这不是我们想要的 若是【L 0】或者是【L 1】的话那就是从第一行开始往下列10行的内容 注意这里的L是小写而且与数字之间要有一个空格 接下去若是想要看到我们所写的全部代码只需要多Enter几次就可以了gdb会自动记忆你上次敲入的指令 ⌨ 断点设置
b 行号 —— 在那一行打断点 b 源文件函数名 —— 在该函数的第一行打上断点 b 源文件行号 —— 在该源文件中的这行加上一个断点 ⌨ 查看断点信息
info b —— 查看断点的信息
若是直接执行【info】的话出来的就是所有的调试信息 但若是我们只想查看一下所打的断点的信息那就在后面加个b/breakpoint 来简要介绍一下断点的一些字段信息 Num —— 编号 Type —— 类型 Disp —— 状态 Enb —— 是否可用 Address —— 地址 What —— 在此文件的哪个函数的第几行 最后的话就是每个断点信息的下面这块breakpoint already hit 1 time即此断点被命中1次
⌨ 删除断点
d 当前要删除断点的编号 —— 删除一个断点【不可以d 行号】 d breakpoints —— 删除所有的断点 此时若继续将这个20行的断点打上时就可以发现其编号为【4】而并不是从1开始这是因为我们没有退出过gdb所以会持续上一次的编号继续往下 ⌨ 开启 / 禁用断点
disable b(breakpoints) —— 使所有断点无效【默认缺省】 enable b(breakpoints) —— 使所有断点有效【默认缺省】 disable b(breakpoint) 编号 —— 使一个断点无效【禁用断点】 enable b(breakpoint) 编号 —— 使一个断点有效【开启断点】 其实对于禁用断点和启用断点VS中也是有的它叫做【空断点】。我们一起来看看 ⌨ 运行 / 调试
r(run) —— F5【无断点直接运行、有断点从第一个断点处开始运行】
首先若是将断点删除掉使用【r】指令运行的话就会直接运行到程序结束 再加上断点去运行的话就会在打的断点处停下来 ⌨ 逐过程和逐语句
n(next) —— 逐过程【相当于F10为了查找是哪个函数出错了】
可以看到我从第一个断点处也就是20行的位置开始执行按下【n】之后因为在其后即22行有一个断点此时就会直接运行到断点处 s(step) —— 逐语句【相当于F11一次走一条代码可进入函数同样的库函数也会进入】
此时我们按下【s】也就相当于是【step】让程序一步一步地走继而进入了AddToTop这个函数若是你在printf()语句要执行时按下【s】的话gdb就会进入printf()库函数内部去执行这里就不展示了 接下去我们可以就继续【n】然后进行逐过程调试来到for循环中那么逐过程也就是变量i的累加和计数器count的累加所以会反复执行通过图中最左侧可以看出是第8行和第10行在反复执行可以看到后面我没有再按【n】了但是依旧会执行上面的步骤这点上面也有提到过因为gdb会自动化记忆你上一次执行过的命令所以若是不想再敲了直接Enter就可以了 ⌨ 打印 / 追踪变量
p(print) 变量名 —— 打印变量值
都执行了那么多次了不知道【i】和【count】发生了怎样的变化将它们打印出来看看吧 通过继续执行【n】然后再去打印就可以发现i的值和count的值发生了变化 但是你不觉得这样每次去打印会显得很繁琐吗那一定会的所以我们有更好的办法 display —— 跟踪查看一个变量每次停下来都显示它的值【变量/结构体…】 我们也可以去追踪一下这两个变量的地址不过可以看到对于地址来说是不会发生改变的 undisplay 变量名编号 —— 取消对先前设置的那些变量的跟踪
但是呢每次都追踪打印这么多内容又太多了我想把它们取消了可以吗答当然是可以的既然有display那就有undisplay ⌨ 查看函数调用
bt —— 看到底层函数调用的过程【函数压栈】
通过仔细观察刚才追踪的4个变量最左侧的编号就可以看到它们的排列的顺序是倒着的。因为变量i和变量count是我们先追踪的它们的地址是我们后追踪的所以可以看出这很像是一个压栈的过程 其实不仅是对于它们AddToTop函数和main函数也呈现这样的关系。此时我们就可以通过【bt】这个指令来查看函数压栈的过程此时便可以看到因为 ⌨ 修改变量的值
set var —— 修改变量的值
对于这个修改变量的值很像是在VS里调试之前设置的那种条件断点可以使调试开始后直接运行到此断点处。不过对于【set var】而言是在调试过程中进行设置 我们也可以到VS中来看一下条件断点是如何设置的 在右击选择【条件】后就可以输入你本次执行代码想要从哪个条件开始然后按下Enter启动调试之后就会从你设置的条件处开始执行 排查问题三剑客 掌握了上面的这些你就可以在Linux下调一些简单的代码了不过想做到高效地进行调试就需要学习一下【三剑客】 ⌨ 指定行号跳转
until 行号 —— 进行指定位置跳转执行完区间代码
可以看到当前在for循环内容执行累加的逻辑但若是我们一直这么执行下去就没有时间排错了除了上面的哪一种【set var】之外还有一种方法其实起到直接结束当前循环的作用那就是进行指定行号跳转通过观察下图可以看到当我们运行了until 13之后程序直接就给出了我们最终的结果count而且即将要执行最后的打印语句说明我们跳转成功了 不过呢在我使用这个【until】的时候遇到了一个bug此时我在循环内部也就是第10行的位置打了一个断点用过VS调试器的都知道一直按F5就可以立即执行下一次循环。我在上面是没有打上断点的所以使用【until】的时候也没有出现问题但是在下面我在循环内部设置了一个断点此时再去使用【until 13】进行跳转的时候就出现问题了并没有马上跳转到指定的行号而是接着执行循环中的内容 ⌨ 强制执行函数
finish —— 在一个函数内部执行到当前函数返回然后停下来等待命令
有时候我们会有这样的需求在初步排查的时候推断可能是某个函数内部的逻辑出了问题但是呢又不想一步步地进到函数内部进行调试在VS中其实很简单只需要在函数下方设个断点然后F5直接运行到断点处即可但是在Linux下的gdb中我们可以使用【finsh】指令来直接使一个函数执行完毕。从下图我们可以看到首先【s】进到函数内部接下去我直接使用finish可以看到它直接回到了调用函数的位置returned了一个返回值 然后可以看到在获取到返回值后也就直接进行了printf打印 ⌨ 跳转到下一断点
c(continue) —— 从一个断点处直接运行至下一个断点处【VS下不断按F5】
这点也是我刚才在上面有提到过的在VS中我们要直接跳转到下一个断点处只修要按下F5即可那在gdb中该如何操作呢你需要敲个【c】就可以了从下图我们可以看出对于这个指令的用处可谓是非常大当我处于第一个断点也就是20行的时候直接敲下【c】就可以运行到第二个断点处也就是第10行。之后若反复敲【c】因为这是一个单语句的循环所以循环的下一次还是会执行到此处。上面的这两个功能就和我们在VS中用的F5是一个道理 四、实战演练Swap Two Numbers
纸上得来终觉浅绝知此事要躬行 ————————————
以下是本次调试所要使用到的代码相信你一定非常熟悉了也就是使用指针交换两数如果想看细节讲解可以看看我的函数章节 1 #include stdio.h2 3 void swap(int* x, int* y)4 {5 int t *x;6 *x *y;7 *y t; 8 }9 int main(void)10 {11 int a 10;12 int b 20;13 14 printf(a %d, b %d\n, a, b);15 16 swap(a, b);17 18 printf(a %d, b %d\n, a, b);19 return 0;20 }
首先我们在程序第16行设置上一个断点然后【r】从第15行开始运行 然后我们使用【s】进入到swap函数中因为我首先不想调试想先立马看看运行结果但是此时又已经进入调试了那么我们就可以使用到【finish】来立马执行完这个函数然后观察一下结果可以看到最后打印出结果的时候a和b的值确实发生了交换 既然清楚了二者会进行一个交换接下去我们就逐语句【n】进行一个单步追踪吧因为提前看了执行结果所以我们要重新开始调试按下【r】即可它会询问你是否需要重新开始调试选择y之后就可以重新从16行开始进行调试 首先通过【display】记录一下两个变量的值和地址 接着按【s】进入到swap函数里追踪一下指针x和指针y的内容也就是它们所存放的地址就可以看到函数内部已经接受到了这两个变量的地址 但是我们后面不想再查看它们了只需要看值是否被修改此时就可以取消对它们的跟踪【undisplay】 然后对我们要观察的值变化继续做一个追踪并且在执行完第一个语句t *x时临时变量t中已经存放了变量a的值也就是指针所指向的那块空间中的值 接下去执行*x *y此时*x中的值就发生了变化因为指针x可以直接找到变量a的地址所以可以对其中的内容做修改就变为了20 接下去执行*y t同理指针y可以直接找到变量b的地址所以可以对其中的内容做修改将原本保存在临时变量t中的10赋值给到*y也就修改了其中的内容 再按【n】的话这个swap函数就执行结束了回到了main函数就可以清楚地看到函数内部的修改带动了函数外部值的变化真正地通过【传址调用】交换了两个数 敲黑板重点来了
但是我不仅想让你看到这些更加重要的一点不知你有没有发现刚才我们在main函数中追踪的变量a和变量b的值与地址到了swap()中就不见了这是为什么呢 其实就是因为两个函数的函数栈帧不同变量a和变量b位于main函数的函数栈帧中而指针变量x和指针变量y位于swap函数的函数栈帧中是互不相干的临时变量除了函数栈帧就会被销毁【如果听不懂这些请看反汇编深挖【函数栈帧】的创建和销毁】 但是因为在swap()内部接受到了外部两个变量的地址所以在函数内部就可以通过这两个指针找到这两个变量的地址从而对这两个空间中的内容做一个修改的操作
通过以上的实验演练你是否对调试器GDB的使用更上一层楼
五、总结与提炼 最后来总结一下本文所学习的内容 在本文的开始我们在初见GDB的时候发现了【no debugging symbols】的报错信息于是便谈到了一个可执行程序的【DeBug】版本和【Release】版本知道了对于前者来说是我们程序员使用的环境是存在调试信息的对于后者来说是测试人员所处的环境是不存在调试信息的。而对于gcc/g而言默认生成的可执行程序就是【Release】版本的因此我们要加上一个-g命令选项使其在make之后生成一个【DeBug】版本的可执行程序这样就可以进行调试了接着我们正式进入到了调试器GDB的使用介绍了很多相关的指令这些都是我整理出来的常见指令其实对于GDB来说还有很多指令但是真正常用的也就这些只要你认真地看下来将它们都使用熟练了相信你的调试功底会大幅度提升对你在VS中的调试也是有所帮助的最后在学习了GDB的许多调试指令后我们便进行了【Swap Two Numbers】的实战演练不仅巩固了C语言中有关传址调用以及函数栈帧的相关指知识而且使我们对于调试器GDB的使用更上一层楼↑ 以上就是本文要介绍的所有内容感谢您的阅读❤️
相关文章
-
淮安市建设工程初级职称申报网站可以做兼职笔译的网站
淮安市建设工程初级职称申报网站可以做兼职笔译的网站
- 技术栈
- 2026年03月21日
-
淮安市广德育建设网站网页设计教育培训
淮安市广德育建设网站网页设计教育培训
- 技术栈
- 2026年03月21日
-
淮安软件园网站建设哪个网站做调查赚钱多
淮安软件园网站建设哪个网站做调查赚钱多
- 技术栈
- 2026年03月21日
-
淮安网站seowordpress+小说系统
淮安网站seowordpress+小说系统
- 技术栈
- 2026年03月21日
-
淮安住房与城乡建设部网站seo网页优化平台
淮安住房与城乡建设部网站seo网页优化平台
- 技术栈
- 2026年03月21日
-
淮安住房与城乡建设部网站网站右侧二维码代码
淮安住房与城乡建设部网站网站右侧二维码代码
- 技术栈
- 2026年03月21日
