gdb调试教程

gdb调试是c/c++代码调试最重要的工具了,我们今天来系统熟悉一下。

——————————————————————————————————————

太长不看版

(注意,如果针对cmake项目,需要把编译模式从release改成debug,否则无法定位到准确的行数)

  • 进入gdb debugger界面==>gdb filename  示例:gdb helloworld.cpp
  • 查看segmentation fault 位置:
    run
    backtrace
  • 加断点: break filename:line   
    • 示例:break helloworld.cpp:10
  • 加条件断点: break filename:line if (condition) 
    • 示例:break helloworld.cpp:10 if (i ==10)
  •  查看局部变量: print(variable) 
    • 示例:print i 
  • 运行 run
  • 继续运行 continue
  • 运行下一个指令 next
  • 进入一个函数体 step into 
  • 观察一个变量 watch 

——————————————————————————————————————

手把手教学版

先上一段充满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调试教程_第1张图片

启动gdb

gdb --args ./bug 10

这时,你会发现,Terminal左边出现了(gdb)

然后输入“run”

gdb调试教程_第2张图片

从终端可以看到,在28行出现错误,然后对应代码;

28行涉及到的变量包括number,和右边的参数。我们可以用gdb显示变量的数值,所以来看看number吧。

通过检查,发现number指向了一个nullptr,这显然不是我们期望的。再来看看argv[0], 发现它是一个

我们希望的是把输入char改成int类型,但是,发现它不是这样的。所以,对代码修改。

重新编译,使用gdb

gdb调试教程_第3张图片

从结果上看,还是一样的错误。

但是,这时候如果打印会发现;

所以,右边的数值是我们期待的,去除了一个bug。。。只是左边还是一个nullptr。

因为程序一旦碰到bug结束,就要重新运行,所以调试的时候,我们希望一步一步,就需要加breakpoint。在之前的代码里,在28行出现了问题,所以我们需要在这之前添加断点,我们关注的变量是number,因此,我选择了在25行添加断点。

gdb调试教程_第4张图片

注意,断点打在25行意味着,25行还没有被执行

我们执行next,来跑下一行代码,对应的就是25行,这时候,可以发现,和我们的预期一样,number是一个有效地址。继续next,然后在28行,地址重新变为了0。 

gdb调试教程_第5张图片

所以问题其实就找到了。在26行的判断语句中,我们把number重新赋值为null。所以,赶紧修改, 重新编译

gdb调试教程_第6张图片

继续在28行打断点,然后next break bug.c 28

gdb调试教程_第7张图片

发现,*number = 10, 现在正常了

gdb调试教程_第8张图片

然后,自信的continue

gdb调试教程_第9张图片

但是,结果是不对的。。。重新输入run,next。

通过step,我们可以进入函数,

gdb调试教程_第10张图片

输入list,我们可以在终端查看代码

print i 会发现 i对应的是一个奇怪的数字,因为还没有初始化。

接着,next

这时候,print 就得到了 0

gdb调试教程_第11张图片

通过操作,我们发现,在for循环中,增加的是地址,而我们希望的是,增加地址对应的数的大小。

所以,修改代码。

gdb调试教程_第12张图片

接下来重新编译,然后这次我们在函数循环的开始处加断点,这时候,我们观察num对应的地址,没有改变,说明改变是生效可行的。

gdb调试教程_第13张图片

我们假设程序是正确的,直接continue,这时候,其实还是naive了。结果是错的。

gdb调试教程_第14张图片

重新run,继续debug,

我们在循环开始的地方加上断点,因为我们想知道进入循环之前的状态

我们加上一个条件断点,因为我们关心当在循环中满足条件时,为什么没有正常退出。我们关注×num最后一次做加法的情况,也就是×num=19==》后来测试发现,在下一行观察number和i似乎更有效。。。。

这时候,其实结果很明显了

懂得都懂,不懂的也不方便多说,这行水太深。我们对程序进行修改。

gdb调试教程_第15张图片

还有一个重要功能:

backtrace: 可以查看代码嵌套的过程。并且,非常适合找到segmentation对应的行数。

-------------------------------------------------------------------------------------------------------

更新: 当程序启动后,依旧另外启动终端,将GDB介入,进行debug,方法如下:

首先利用关键词找到需要debug的程序的Process ID。

ps -ef | grep [keyword]

然后,执行

sudo gdb -p 29925
#将29925替换为你要找的pid

这样,你就可以介入进行debug了

你可能感兴趣的:(编程工具,gdb,c++,debug)