gdb调试是c/c++代码调试最重要的工具了,我们今天来系统熟悉一下。
——————————————————————————————————————
(注意,如果针对cmake项目,需要把编译模式从release改成debug,否则无法定位到准确的行数)
run
backtrace
——————————————————————————————————————
先上一段充满bug的代码:
#include
#include
// this function is 100% correctly implemented
void print_heart();
void times_two(int *num) {
// calculate 2*num
for (int i = 0; i < *num; i++) {
*num++;
}
}
int main(int argc, char *argv[]) {
// parse arguments
if (argc != 2) {
printf("usage: %s \n", argv[0]);
return 1;
}
print_heart();
// allocate memory
int *number = malloc(sizeof(int));
if (number = NULL)
return 1;
*number = atoi(argv[0]);
// calcualte and print result
times_two(number);
printf("Result: %d\n", *number);
// free memory
free(number);
return 0;
}
void print_heart() {
int i, j;
for (i = 0; i < 3;i++)
{
for (j = 0; j < 5-2*i;j++)
{
printf(" ");
}
for (j = 0; j < 5+4*i;j++)
{
printf("❤");
}
for (j = 0; j < 9-4*i;j++)
{
printf(" ");
}
for (j = 0; j < 5+4*i;j++)
{
printf("❤");
}
printf("\n");
}
for (i = 0; i < 3; i++)
{
for (j = 0; j < 29;j++)
printf("❤");
printf("\n");
}
for (i = 0; i < 6+ 1; i++)
{
for (j = 0; j < 2*i+1; j++)
printf(" ");
for (j = 0; j < 27-4*i; j++)
printf("❤");
printf("\n");
}
for (i = 0; i <1; i++)
{
for (j = 0; j < 14; j++)
printf(" ");
for (j = 0; j < 1; j++)
printf("❤");
printf("\n");
}
}
这个代码的作用是打印一个骚气的爱心,然后,将输入的数字×2.(显然,刚开始,代码不能实现这样的功能)
用gcc编译改代码:
gcc -g -std=c99 -o bug bug.c
#run
./bug 3
你会发现有core dump,所以,接下来开始debug
启动gdb
gdb --args ./bug 10
这时,你会发现,Terminal左边出现了(gdb)
然后输入“run”
从终端可以看到,在28行出现错误,然后对应代码;
28行涉及到的变量包括number,和右边的参数。我们可以用gdb显示变量的数值,所以来看看number吧。
通过检查,发现number指向了一个nullptr,这显然不是我们期望的。再来看看argv[0], 发现它是一个
我们希望的是把输入char改成int类型,但是,发现它不是这样的。所以,对代码修改。
重新编译,使用gdb
从结果上看,还是一样的错误。
但是,这时候如果打印会发现;
所以,右边的数值是我们期待的,去除了一个bug。。。只是左边还是一个nullptr。
因为程序一旦碰到bug结束,就要重新运行,所以调试的时候,我们希望一步一步,就需要加breakpoint。在之前的代码里,在28行出现了问题,所以我们需要在这之前添加断点,我们关注的变量是number,因此,我选择了在25行添加断点。
注意,断点打在25行意味着,25行还没有被执行!
我们执行next,来跑下一行代码,对应的就是25行,这时候,可以发现,和我们的预期一样,number是一个有效地址。继续next,然后在28行,地址重新变为了0。
所以问题其实就找到了。在26行的判断语句中,我们把number重新赋值为null。所以,赶紧修改, 重新编译
继续在28行打断点,然后next break bug.c 28
发现,*number = 10, 现在正常了
然后,自信的continue
但是,结果是不对的。。。重新输入run,next。
通过step,我们可以进入函数,
输入list,我们可以在终端查看代码
print i 会发现 i对应的是一个奇怪的数字,因为还没有初始化。
接着,next
这时候,print 就得到了 0
通过操作,我们发现,在for循环中,增加的是地址,而我们希望的是,增加地址对应的数的大小。
所以,修改代码。
接下来重新编译,然后这次我们在函数循环的开始处加断点,这时候,我们观察num对应的地址,没有改变,说明改变是生效可行的。
我们假设程序是正确的,直接continue,这时候,其实还是naive了。结果是错的。
重新run,继续debug,
我们在循环开始的地方加上断点,因为我们想知道进入循环之前的状态
我们加上一个条件断点,因为我们关心当在循环中满足条件时,为什么没有正常退出。我们关注×num最后一次做加法的情况,也就是×num=19==》后来测试发现,在下一行观察number和i似乎更有效。。。。
这时候,其实结果很明显了
懂得都懂,不懂的也不方便多说,这行水太深。我们对程序进行修改。
还有一个重要功能:
backtrace: 可以查看代码嵌套的过程。并且,非常适合找到segmentation对应的行数。
-------------------------------------------------------------------------------------------------------
更新: 当程序启动后,依旧另外启动终端,将GDB介入,进行debug,方法如下:
首先利用关键词找到需要debug的程序的Process ID。
ps -ef | grep [keyword]
然后,执行
sudo gdb -p 29925
#将29925替换为你要找的pid
这样,你就可以介入进行debug了