四、gdb调试
1.gdb简介
gdb是GNU开源组织发布的一个强大的UNIX下的程序调试工具,是命令行调试工具。一般来说,gdb主要完成如下四个功能:
- 启动程序,按照自定义要求随心所欲运行程序。
- 可让被调试的程序在指定的调试的断点处停住。(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事。
- 动态的改变程序的执行环境
程序的发布方式有两种,debug模式和release模式 ,Linux gcc/g++编译出来的二进制程序,默认是release模式 ,要使用gdb调试,必须在源代码生成二进制程序的时候,加上 -g 选项:
gcc 源文件 -o 目标文件 -g
这就生成了debug版本的可执行程序:
可以使用命令:
readelf -S 可执行文件 | grep -i debug
查看debug信息。
通过debug版本的和release版本的可执行程序对比发现,debug版本的可执行程序比release版本的可执行程序文件大小要大一些,debug的可执行程序大小为10048B,release版本的可执行程序大小为8512B:
因此,也可以看出gcc/g++编译出来的可执行程序是release版本的。
2.gdb调试
以插入排序的代码InsertSort.c为例:
1. #include<stdio.h> 2. #include <stdlib.h> 3. 4. //打印 5. void Print(int* a, int n) 6. { 7. for (int i = 0; i < n; i++) 8. { 9. printf("%d ", a[i]); 10. } 11. 12. printf("\n"); 13. } 14. 15. //直接插入排序 16. void InsertSort(int* a, int n) 17. { 18. //多趟排序 19. for (int i = 0; i < n - 1; i++) 20. { 21. //把temp插入到数组的[0,end]有序区间中 22. int end = i; 23. int temp = a[end + 1]; 24. while (end >= 0) 25. { 26. if (temp < a[end]) 27. { 28. a[end + 1] = a[end]; 29. end--; 30. } 31. else 32. { 33. break; 34. } 35. } 36. 37. a[end + 1] = temp; 38. 39. } 40. 41. } 42. 43. void TestInsertSort() 44. { 45. int arr[] = { 9,1,2,5,7,4,8,6,3,5 }; 46. InsertSort(arr, sizeof(arr) / sizeof(arr[0])); 47. Print(arr, sizeof(arr)/sizeof(arr[0])); 48. } 49. 50. int main() 51. { 52. TestInsertSort(); 53. 54. return 0; 55. }
来了解gdb调试。
(1)r运行程序
r/run:运行程序
运行程序之前,要先以debug模式生成可执行程序,再进入debug模式,然后运行程序:
默认情况下,run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处。
(2)start启动程序
start 指令会执行程序至 main函数的起始位置,即在 main() 函数的第一行语句处停止执行(该行代码尚未执行)。
而后要调试可以直接执行s或n。
(3)l列出代码
从第n行开始列出10行代码:
l/list n
从第1行开始,列出10行代码:
继续输入l,会跟着上面的代码继续显示,如下面显示11-20行,21-20行:
(4)b设置断点
在第n行打断点:
b n
在第23行打断点:
这步操作相当于VS的F9。
输入r运行,就会在23行停下来:
这就相当于VS的F5。
如果给函数名打断点,其实是给函数内容的第一行打了断点:
(5)disable禁用断点
使用disable可以禁用断点,使用如下命令禁用断点:
disable 断点编号
如下,禁用编号为2的断点,发现断电仅仅是被禁用而已,但是断点还在:
(6)delete删除断点
如果不想要某个断点了,可以使用delete删除断点:
删除某一断点:
delete 编号
删除所有断点:
delete
(7)info b查看断点信息
可以使用以下命令查看断点信息:
info b/break
查看可执行程序InsertSort的断点信息:
(8)s逐语句执行
单步执行代码:
s/step
执行main函数代码,执行完45行,再执行46行,再进入InsertSort函数内部执行,如果遇到断点则会停下:
相当于VS的F11。
(9)n逐过程执行
逐过程执行:
n/next
执行main函数代码,直接将main函数执行完毕,中途不会进入main里面的子函数,遇到断点会停下:
假如将23行设置为断点,那么执行到断点处会停下:
相当于VS的F10。
(10)display显示跟踪查看变量值
跟踪查看一个变量,每次停下来都显示它的值:
dispaly 变量名
如跟踪变量temp的值:
也可以查看它的地址:
(11)undisplay取消跟踪显示变量值
对于使用display的每一个变量,系统都会分配一个编号,如果不想显示这个变量值,可以使用如下命令:
undisplay 编号
如果想取消跟踪显示所有变量,可以直接使用:
undisplay
(12)finish退出函数
假如想退出某个函数,比如已经确认了bug不在该函数内,可以使用finish退出该函数:
finish
(13)info查看当前设置了哪些断点
使用info命令查看当前设置了哪些断点:
i/info b
显示当前设置了哪些断点:
(14)continue让函数从当前位置一直执行至下一个断点处
从当前位置开始连续而非单步执行程序:
c/continue
先运行到第一个断点处,从第一个断点处执行c,就运行到第二个断点处:
(15)until跳至某行
在一个函数内直接执行到第n行:
until n
从第45行直接执行到第47行:
调试时可以使用finish和continue快速确定在哪个函数崩了,再用until确定再哪一行崩了,比如从第一行until到第100行没有报错,但是从第100行until到第200行报错了,那么错误就在第100行至第200行。
(16)bt调用堆栈
可以使用如下命令查看函数调用的栈帧:
bt
查看执行到当前代码行时,调用的堆栈: