免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1006 | 回复: 0
打印 上一主题 下一主题

《Linux程序设计》学习笔记10——调试 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-07-14 11:21 |只看该作者 |倒序浏览


  Normal
  0
  
  7.8 磅
  0
  2
  
  false
  false
  false
  
   
   
   
   
   
   
   
   
   
   
   
   
  
  MicrosoftInternetExplorer4



/* Style Definitions */
table.MsoNormalTable
        {mso-style-name:普通表格;
        mso-tstyle-rowband-size:0;
        mso-tstyle-colband-size:0;
        mso-style-noshow:yes;
        mso-style-parent:"";
        mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
        mso-para-margin:0cm;
        mso-para-margin-bottom:.0001pt;
        mso-pagination:widow-orphan;
        font-size:10.0pt;
        font-family:"Times New Roman";
        mso-fareast-font-family:"Times New Roman";
        mso-ansi-language:#0400;
        mso-fareast-language:#0400;
        mso-bidi-language:#0400;}
欢迎转载,请保留作者信息
bill@华中科技大学
http://billstone.cublog.cn
  
软件中每个重要代码段都会有缺陷,一般来说,每100行代码会有两到三个错误。

常用调试技巧
调试和测试Linux程序的方法一般是先运行程序并观察其输出结果,如果不能正常工作,我们就需要决定应该采取哪些措施。可以修改程序然后重新尝试(代码检查-试运行-出错法),也可以在程序中增加一些语句以获得更多关于程序内部运行情况的信息(取样法),还可以直接检查程序的执行情况(受控执行法)。
当程序的运行情况和预期不同时,重新阅读程序通常是一个好办法。有些工具可以帮助你完成代码检查工作,编译器就是其中比较明显的一个。如果程序有语法错误,它就会告诉你。
提示:在编译程序时为了获得更多的信息可以使用gcc -Wall -pedantic -ansi
取样法是指在程序中添加一些代码以手机与程序运行时的行为相关的更多信息的方法。取样法的常见做法是,在程序中添加printf函数调用以打印出变量在程序运行的不同阶段的值。不过需要注意的是,添加额外的代码时必须要非常小心地避免引入新的漏洞。
取样法的实现有两种技巧。一种是用C语言的预处理器有选择的包括取样代码,这样只需重新编译程序就可以达到包含或去除调试代码的目的。实现方式很简单,只需使用下面的结果:

  
  #ifdef
  DEBUG
          printf(“variable x has value %d\n”,
  x);
  #endif
  

在编译程序时可以加上编译器标志-DDEBUG,这样就可以将额外的调试代码添加进来。如果更进一步可以设计得稍微复杂一些,实际上它也是使用了C语言的预处理功能,典型结构如下:

  
  #define
  BASIC_DEBUG 1
  #define
  EXTRA_DEBUG 2
  #define
  SUPER_DEBUG 4
   
  #if (DEBUG
  & EXTRA_DEBUG)
          printf(……);
  #endif
  

这种情况下,我们必须总是定义DEBUG宏,但我们可以设置它为代表一组调试信息或代表一个调试级别。比如编译器标志-DDEBUG=5将启用BASIC_DEBUG和SUPER_DEBUG,但不包括EXTRA_DEBUG;标志-DDEBUG=0将禁止所有的调试信息。
另一种技巧则无需重新编译,它在程序中增加一个作为调试标志的全局变量,这使得用户可以在命令行上通过-d选项切换是否启用调试模式,即使程序已经发行了,仍然可以这样做,该方法同时还会在程序中增加一个用于记录调试信息的函数。典型结构如下:

  
  if
  (debug){
          sprintf(msg, …);
          write_debug(msg);
  }
  

这样做法的好处是如果用户遇到了问题,他们自己就可以在运行程序时打开调试功能,替你完成诊断错误的工作;而明显的不足则是它会使程序的长度大大增加。
受控执行法就是使用某些工具在程序运行或者源代码级别上查看程序的比较详细的状态信息。这种工具包括adb、sdb和dbx等。一般情况下,我们可以使用gdb对程序进行受控调试运行。不过,为了能够调试程序,我们需要在编译它时加上一个或者多个特殊的编译器选项,比如-g标志就是对程序进行调试性编译时常用的一个选项。

使用gdb进行调试
Gdb是一个功能强大的调试器,它是一个自由软件,能够用在许多UNIX平台上。它同时也是Linux系统的默认调试器。关于它的详细使用信息可以参考手册,在我的博客上有几篇
文章
专门介绍了gdb。

其他调试工具
除了像gdb这样彻底的调试器外,Linux系统一般还会提供许多能够帮助你完成调试工作的其他工作。其中有的是提供关于程序的静态信息,另外一些则是提供动态分析。
静态分析只能通过程序的源代码提供信息。ctags、cxref和cflow等就是一些静态分析程序,它们可以通过源文件提供有关函数调用和函数所在位置的有用信息。
动态分析提供的是与程序执行过程中的行为有关的信息。prof和gprof等就是一些动态分析程序,它们提供的信息包括已经执行了哪些函数以及这些函数的执行时间。
工具lint是C语言编译器的一个前端,它增加了一些常识性的测试并可以产生一些警告信息。它可以检测出未经复制的变量使用、函数的参数未使用等异常情况。
ctags、cxref和cflow这三个工具构成了X/Open规范的一部分内容,因此具有软件开发能力的UNIX系统都会有这三个工具。
工具ctags为程序的所有函数创建索引。每个函数对于一个列表,在列表中累出该函数在程序中的调用位置,就像书籍的索引。
工具cxref分析C语言源代码并生成一个交叉引用表。它可以显示每个符号(变量、#define定义和函数)都在程序的哪个位置使用过。
工具cflow打印出一个函数调用树。该图示按函数之间的正向调用顺序显示了函数之间调用的关系,它可以让我们看清楚一个程序的框架结构,理解它的操作流程,理解对某个函数的改动将会产生怎样的影响。有些版本的cflow除了可以处理源代码外,还可以处理目标文件。
如果想查找程序的性能问题,常用的一种技巧是使用执行记录。它通常需要特殊的编译器选项和辅助程序的支持。程序的执行记录可以显示执行它所花费的时间具体都用在什么操作上了。
编译程序时,给编译器加上-p标志(针对prof工具)或-pg标志(针对gprof工具)就可以创建出profile程序。而工具prof(或者gprof)就可以根据profile程序运行时所产生的执行记录文件打印出一个报告。当使用了上述标志的程序运行时,监控数据将被写入当前目录下的文件mon.out(工具gprof使用gmon.out)中。详细细节请查看手册,51cto有
一篇文章
“使用GNU gprof进行Linux平台下的程序分析”专门讲解了如何使用gprof工具。

断言
经常有这样的情况,程序运行中出现的问题与不正确的假设有关但并非代码的错误。这些不正确的假设往往是被直观认为不会发生的事件,因此我们需要对系统的内部逻辑做出确认。
针对这种情况,X/Open提供了assert宏,它的作用就是测试某个假设是否成立,如果不成立就立即停止程序的运行。

  
  #include
  
   
  void assert(int expression)
  

assert宏对表达式进行求值,如果结果非零,它就往标准错误写一些诊断信息,然后调用abort函数结束程序的运行。
头文件assert.h定义的宏受NDEBUG的影响。如果程序在处理这个头文件时已经定义了NDEBUG就不定义assert宏。这意味着,可以在编译期间使用-DNDEBUG关闭断言功能。
断言的常用用法以及注意事项大概有以下几个方面:
(1)在函数开始处检验传入参数的合法性如

  
  int resetBufferSize(int nNewSize) {
  //功能:改变缓冲区大小
  //参数:nNewSize 缓冲区新长度
  //返回值:缓冲区当前长度
  //说明:保持原信息内容不变 nNewSize表示清除缓冲区
  
  
  assert(nNewSize >= 0);
  assert(nNewSize
  }
  

(2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败
    不建议使用:assert(nOffset>=0
&& nOffset+nSize    建议使用:
assert(nOffset >= 0);
               assert(nOffset+nSize     (3)不能使用改变环境的语句,因为assert在NDEBUG被定义时无效。如果这么做,会使用程序在真正运行时遇到问题
    错误:assert(i++
这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。
    正确:assert(i           i++;

内存调试
动态内存分配时一个很容易出现持续漏洞的领域,而且漏洞一旦出现还很难查找。如果在程序中惯用malloc和free函数来分配内存,你就必须清楚自己分配过的每一块内存,并且要确定没有使用已经释放的内存块,这一点非常重要。
内存块通常都是由malloc函数分配给指针变量。如果指针变量的取值发生了变化,又没有其他指针指向这块内存,这块内存变得无法使用,这就是一种内存泄露现象。
如果在一个已分配的内存块尾部的后面写数据,就很可能会损坏malloc库用于记录内存分配情况的数据结构。出现这种情况,经过一段时间,一个malloc调用,甚至是一个free调用都会引发段错误并导致程序崩溃。
实际上,目前已有工具可以帮助解决这两类问题。ElectricFence函数库可以用Linux的虚拟内存机制来保护malloc和free所使用的内存,当它发现内存被破坏时就通知程序的运行。ElectricFence会将malloc及其管理函数替换为适应计算机处理器虚拟内存机制的版本,从而保护系统不受非法内存访问的破坏。
Valgrind工具可以检测出前面提出的许多问题,特别是它可以检测出数组访问错误和内存泄露。程序不需要重新编译就可以直接使用该工具,甚至还可以用它来调试一个正在运行程序的内存访问情况。
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/47687/showart_1995516.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP