LInux中调试器的使用-gdb/cgdb
下面提供一个示例程序,便于使用 cgdb 调试时练习定位 bug。
#include <stdio.h>
int Sum(int s, int e)
{
int result = 0;
for(int i = s; i <= e; i++)
{
result += i;
}
return result;
}
int main()
{
int start = 1;
int end = 100;
printf("I will begin\n");
int n = Sum(start, end);
printf("running done, result is: [%d-%d]=%d\n", start, end, n);
return 0;
}
我们在 lesson8
文件夹中创建 mycmd.c
执行 gcc mycmd.c -o mycmd -std=c99
后, 生成 mycmd
文件。使用 gdb mycmd
命令进行调试。并输入 list
命令:
程序显示为 No symbol table is loaded. Use the "file" command.
这说明当前文件不具有调试功能。这是因为gcc默认是已release模式发布的,不包含调试信息。所以程序无法调试。
我们可以使用 readelf
扫描可执行文件,并读取可执行程序的二进制文件的构成段,然后查找有没有 debug
文件信息来确定是否有调试功能。
readelf -S mycmd | grep -i debug
使用readelf 读取文件信息,使用管道搜索debug关键词
如果没有debug信息,说明他无法调试。
**如何以debug模式发布:**在最后加上-g选项 gcc mycmd.c -o mycmd -std=c99 -g
再次使用 readelf 读取文件,可见:
说明此时文件为可调式文件,之后我们可以进行调试。
此时再次执行 gdb mycmd
指令, 运行 list
命令后,显示如下,此时说明文件已经变成了 debug
版本。
从图中可以看到,罗列出来的代码并没有从第一行开始,我们可以使用 l 0
从第一行开始罗列。
编写Makefile文件
我们可以对编译的过程优化一下,根据之前学过的知识,我们可以先编写Makefile文件, 然后使用 make
对 mycmd.c
指令进行自动化编译。
先 touch Makefile
创建文件后, 使用 vim Makefile
指令打开编写如下代码:
mycmd: mycmd.c
gcc -std=c99 -o $@ $^ -g
.PHONY: clean
clean:
rm -f mycmd
-std=c99
:严格使用 C99 标准。
-std=gnu99
:使用 C99 标准并同时包含 GNU 扩展。
-g
:生成调试信息。
-Wall
:打开大部分常见的警告。
编写完成后,使用 make
指令运行 Makefile文件,自动化生成 mycmd 可执行二进制文件。然后进行调试。
如果你在 make
后,遇到如下问题:
这是因为,编译器默认使用了比 C99 更早的 C 语言标准(可能是 gnu89),导致出现如图错误。我们仅需在 Makefile中指定使用 c99标准进行编译即可。
选择更加可是化的调试器
使用 gdb 是有问题的,我们默认开不到代码,很不方便,因此我们推荐使用cgdb 来调试代码,这样我们不仅可以看到代码,还可以看到调试的信息。
使用 sudo yum install -y cgdb
命令(centos)来安装cgdb 。
使用 cgdb mycmd
命令开始调试,显示如下:
相比于gdb调试,界面看起来舒服太多了。
cgdb相关指令:
list/l 行号0: 查看代码
b行号: 打断点
info b: 查看断点
d 断点编号: 删除
b + 文件名 :函数名 为某个文件中的某个函数打断点
删除断点的时候,需要按照断点的编号来进行删除
在一轮调试周期中,断点编号是线性递增的
r 表示运行
n/next 表示单步执行,不进入函数内部
注意:
按esc键可以让输入焦点进入到vi窗口,再按i键回到gdb窗口。
gbd启动调试的时候,只是开启了gbd,程序并没有运行起来。
r/run, 表示的是在gbd的场景中,启动我们自己的mycmd程序。
在没有断点情况下,r/run命令就是让我们的程序运行结束
断点的本质是:让程序运行到指定行后暂停。
cgbd中常见指令汇总
命令 | 代码 |
---|---|
list/l | 显示源代码,从上次位置在开始,每次列出10行 |
list/l 函数名 | 列出指定函数的源代码 |
list/l 文件名:行号 | 列出指定文件的源代码 |
r/run | 从程序开始连续执行 |
n/next | 单步执行,不进入函数内部 |
s/step | 单步执行,进入函数内部 |
break/b [文件名:] 行号 | 在指定行号设置断点 |
break/b 函数名 | 在函数开头设置断点 |
info break/b | 查看当前所有断点的信息 |
finish | 执行到当前函数返回,然后停止 |
print/p 表达式 | 打印表达式 |
p 变量 | 打印指定便量的值 |
set var 便量=值 | 修改变量的值 |
continue/c | 从当前位置开始连续执行程序 |
delete/d beakpoints n | 删除序号为n的断点 |
disable breakpoints | 禁用所有断点 |
enable breakpoints | 启用所有断点 |
info/i breakpoints | 查看当前设置的断点列表 |
display 变量 | 跟踪指定变量的值(每次停止时) |
undisplay 编号 | 取消对指定变量编号的变量的跟踪显示 |
until x 行号 | 执行到指定行号 |
backtrace/bt | 查看当前执行栈的局部变量值 |
info/i locals | 查看当前栈帧的局部变量值 |
quit | 退出GDB调试器 |