首页 未命名正文

linux编程_从一个实例来熟悉GDB与高效调试

云返利网 未命名 2020-05-26 09:07:51 19 0

GDB的全称是GNU project debugger,是类Unix系统上一个十分壮大的调试器。这里通过一个简朴的例子(插入算法)来先容若何使用gdb举行调试,特别是若何通过中止来高效地找出死循环;我们还可以看到,在修正了程序错误并重新编译后,我们仍然可以通过原先的GDB session举行调试(而不需要重开一个GDB),这避免了一些重复的设置事情;同时,在某些受限环境中(好比某些实时或嵌入式系统),往往只有一个Linux字符界面可供调试。这种情况下,可以使用job在代码编辑器、编译器(编译环境)、调试器之间做到无缝切换。这也是高效调试的一个方式。

先来看看这段插入排序算法(a.cpp),内里有一些错误。

 

// a.cpp
#include <stdio.h>
#include <stdlib.h>

int x[10];
int y[10];
int num_inputs;
int num_y = 0;

void get_args(int ac, char **av)
{ 
   num_inputs = ac - 1;
   for (int i = 0; i < num_inputs; i++)
      x[i] = atoi(av[i+1]);
}

void scoot_over(int jj)
{ 
   for (int k = num_y-1; k > jj; k++)
      y[k] = y[k-1];
}

void insert(int new_y)
{ 
   if (num_y = 0)
   {
      y[0] = new_y;
      return;
   }

   for (int j = 0; j < num_y; j++)
   {
      if (new_y < y[j])
      {
         scoot_over(j);
         y[j] = new_y;
         return;
      }
   }
}

void process_data()
{
   for (num_y = 0; num_y < num_inputs; num_y++)
      insert(x[num_y]);
}

void print_results()
{ 
   for (int i = 0; i < num_inputs; i++)
      printf("%d\n",y[i]);
}

int main(int argc, char ** argv)
{ 
   get_args(argc,argv);
   process_data();
   print_results();
   return 0;
}


代码就不剖析了,稍微花点时间应该就能明了。你能发现几个错误?

 

使用gcc编译:

gcc -g -Wall -o insert_sort a.cpp

"-g"告诉gcc在二进制文件中加入调试信息,如符号表信息,这样gdb在调试时就可以把地址和函数、变量名对应起来。在调试的时刻你就可以凭据变量名查看它的值、在源代码的某一行加一个断点等,这是调试的先决条件。“-Wall”是把所有的忠告开关打开,这样编译时若是遇到warning就会打印出来。一样平常情况下建议打开所有的忠告开关。

运行编译后的程序(./insert_sort),才发现程序基本停不下来。上调试器!(有些bug可能一眼就能看出来,这里使用GDB只是为了先容相关的基本功能)

TUI模式

现在版本的GDB都支持所谓的终端用户接口模式(Terminal User Interface),就是在显示GDB下令行的同时可以显示源代码。利益是你可以随时看到当前执行到哪条语句。之以是叫TUI,应该是从GUI抄过来的。注重,可以通过ctrl + x + a来打开或关闭TUI模式。

gdb -tui ./insert_sort

死循环

进入GDB后运行run下令,传入下令行参数,也就是要排序的数组。固然,程序也是停不下来:

为了让程序停下来,我们可以发送一个中止信号(ctrl + c),GDB捕捉到该信号后会挂起被调试历程。注重,什么时刻发送这个中止有点技巧,完全取决于我们的履历和程序的特点。像这个简朴的程序,正常情况下险些马上就会执行完毕。若是感觉到延迟就说明已经发生了死循环(或其他什么),这时刻发出中止一定落在死循环的循环体中。这样我们才气通过检查上下文来找到有用信息。大型程序若是正常情况下就需要跑个几秒钟甚至几分钟,那么你至少需要等到它超时后再去中止。

此时,程序暂停在第44行(第44行还未执行),TUI模式下第44行会被高亮显示。我们知道,这一行是某个死循环体中的一部分。

由于暂停的代码有一定的随机性,可以多运行几回,看看每次停留的语句有什么差别。后面执行run下令的时刻可以不用再输入下令行参数(“12 5”),GDB会记着。另有,再执行run的时刻GDB会问是否重头最先执行程序,固然我们要从头最先执行。

基本确定位置后(如上面的44行),由于这个程序很小,可以单步(step)一条条语句查看。不难发现问题出在第24行,详细的步骤就省略了。

无缝切换

在编码、调试的时刻,除非你有集成开发环境,一样平常你会需要打开三个窗口:代码编辑器(好比很多人用的VIM)、编译器(新开的窗口运行gcc或者make下令、执行程序等)、调试器。集成开发环境固然好,但某些倒闭的场所下你无法使用任何GUI工具,好比一些仅提供字符界面的嵌入式装备——你只有一个Linux下令行可以使用。显然,若是在VIM中修改好代码后需要先关闭VIM才气敲入gcc的编译下令,或者调试历程中发现问题需要先关闭调试器才气重新打开VIM修改代码、编译、再重新打开调试器,那么不言而喻,这个历程太痛苦了!

幸亏可以通过Linux的作业管理机制,通过ctrl + z把当前义务挂起,返回终端做其他事情。通过jobs下令可以查看当前shell有哪些义务。好比,当我暂停GDB时,jobs显示我的VIM编辑器历程与GDB现在都处于挂起状态。

以下是些相关的下令,对照常用

fg %1         // 打开VIM,1是VIM对应的作业号
fg %2         // 打开GDB
bg %1         // 让VIM到后台运行
kill %1 && fg // 彻底杀死VIM历程

GDB的“在线刷新”

好了,适才先容了无缝切换,那我们可以在不关闭GDB的情况下(注重,ctrl + z不是关闭GDB这个历程,只是挂起)切换到VIM中去修改代码来消除死循环(把第24行的“if (num_y = 0)" 改成"if (num_y == 0)")。动作序列可以是:

ctrl + z // 挂起GDB
jobs     // 查看VIM对应的作业号,假设为1
fg %1    // 进入VIM,修改代码..
ctrl + z // 修改完后挂起VIM
gcc -g -Wall -o insert_sort a.cpp // 重新编译程序
fg %2    // 进入GDB,假设GDB的作业号为2

现在,我们又返回GDB调试界面了!但在调试前另有一步,若何让GDB识别新的程序(由于程序已经重新编译)?只要再次运行run就可以了。由于GDB没有关闭,以是之前设置的断点、运行run时传入的下令行参数等还保留着,不需要重新输入。很好用吧!

GDB自动检测到程序发生改变,重新加载符号。

其他bug

关于本例中的其他bug,这里就不多说了。有兴趣的同砚,可以和我讨论。

GDB调试程序用法 http://www.linuxidc.com/Linux/2013-06/86044.htm

GDB+GDBserver无源码调试Android 动态链接库的技巧 http://www.linuxidc.com/Linux/2013-06/85936.htm

使用hello-gl2确立ndk-GDB环境(有源码和无源码调试环境) http://www.linuxidc.com/Linux/2013-06/85935.htm

在Ubuntu上用GDB调试printf源码 http://www.linuxidc.com/Linux/2013-03/80346.htm

Linux下用GDB调试可加载模块 http://www.linuxidc.com/Linux/2013-01/77969.htm

壮大的C/C++ 程序调试工具GDB  http://www.linuxidc.com/Linux/2016-09/135171.htm

使用GDB下令行调试器调试C/C++程序 http://www.linuxidc.com/Linux/2014-11/109845.htm

GDB调试下令总结  http://www.linuxidc.com/Linux/2016-08/133988.htm

GDB调试工具入门  http://www.linuxidc.com/Linux/2016-09/135168.htm

【关于云返利网】

云返利网是阿里云、腾讯云、华为云产品推广返利平台,在各个品牌云产品官网优惠活动之外,云返利网还提供返利。您可以无门槛获得阿里云、华为云、腾讯云所有产品返利,在官网下单后就可以领取,无论是自己用、公司用还是帮客户采购,您个人都可以获得返利。云返利网的目标是让返利更多、更快、更简单!详情咨询13121395187(微信同号)