how to debug with some tips or tools

调试-linux程序设计

clip_image001

错误查找及纠正

有几种原因会造成程序的缺陷,针对每种原因,都有一些建议的方法来查找和纠正。比如功能定义错误,设计规划错误和代码编写错误

clip_image003

程序调试的5个阶段

clip_image005

使用printf来排错

       相信这个方法是N多人使用的方法

使用-Wall选项来排错

       在使用gcc编译的时候加上-Wall选项,可以提前发现很多的warnings

注释法

       将怀疑有错的程序块注释起来,查看一下结果,如此反复即可。

使用内置宏__FILE__

       灵活使用内置宏__LINE__,__FILE__,__DATE____TIME可以很快速地定位问题所在。

使用assert断言来预防错误的发生

       诸如分母不能为0的情况。

专业级别的gdb调试

       关于gdb的调试,参考:

clip_image006 clip_image008 clip_image010

静态分析调试工具

ctags

       参考:http://ctags.sourceforge.net/

       相信使用过vim的童鞋都晓得这个工具,ctagsGenerate tag files for source code)是vim下方便代码阅读的工具。尽管ctags也可以支持其它编辑器,但是它正式支持的只有VIM。并且VIM中已经默认安装了Ctags,它可以帮助程序员很容易地浏览源代码。

ctags 最先是用来生成C代码的tags文件,后来扩展成可以生成各类语言的tags, 有些语言也有专有的tags生成工具(比如javajtags, python ptags)

clip_image012

cxref

参考:http://www.gedanken.demon.co.uk/cxref/

Cxref是一款在LaTeXHTMLRTFSGML中将C代码生成文件的程序,包括从C程序源代码的交叉引用。它用于ANSI C,包含大多数gcc扩展,可以从代码中的注释进行生成适当格式的程序文档,交叉引用来自于代码本身并不需要额外的工作。

clip_image014

cflow

参考:http://www.gnu.org/software/cflow/

GNU cflow analyzes a collection of C source files and prints a graph, charting control flow within the program.

GNU cflow is able to produce both direct and inverted flowgraphs for C sources. Optionally a cross-reference listing can be generated. Two output formats are implemented: POSIX and GNU (extended).

Input files can optionally be preprocessed before analyzing.

The package also provides Emacs major mode for examining the produced flowcharts in Emacs.

clip_image016

动态分析调试工具

gprof

gprofGNU profiler工具。可以显示程序运行的“flat profile”,包括每个函数的调用次数,每个函数消耗的处理器时间。也可以显示调用图,包括函数的调用关系,每个函数调用花费了多少时间。还可以显示注释的源代码,是程序源代码的一个复本,标记有程序中每行代码的执行次数。

gprof编译程序


在编译或链接源程序的时候在编译器的命令行参数中加入“-pg”选项,编译时编译器会自动在目标代码中插入用于性能测试的代码片断,这些代码在程序在运行时采集并记录函数的调用关系和调用次数,以及采集并记录函数自身执行时间和子函数的调用时间,程序运行结束后,会在程序退出的路径下生成一个gmon.out文件。这个文件就是记录并保存下来的监控数据。可以通过命令行方式的gprof或图形化的Kprof来解读这些数据并对程序的性能进行分析。另外,如果想查看库函数的profiling,需要在编译是再加入“-lc_p”编译参数代替“-lc”编译参数,这样程序会链接libc_p.a库,才可以产生库函数的profiling信息。如果想执行一行一行的profiling,还需要加入“-g”编译参数。
例如如下命令行:
gcc -Wall -g -pg -lc_p example.c -o example

clip_image018

工具lint

lint是最著名的C语言工具之一,是由贝尔实验室SteveJohnson1979PCC(PortableC Compiler)基础上开发的静态代码分析,一般由UNIX系统提供。与大多数C语言编译器相比,lint可以对程序进行更加广泛的错误分析,是一种更加严密的编译工具。最初,lint这个工具用来扫描C源文件并对源程序中不可移植的代码提出警告。但是现在大多数lint实用程序已经变得更加严密,它不但可以检查出可移植性问题,而且可以检查出那些虽然可移植并且完全合乎语法但却很可能是错误的特性。

随着历史的推移,Lint后来形成了一系列的工具,包括PC-Lint/FlexeLint(Gimpel),LintPlus(Cleanscape)以及Splint

clip_image019

内存调试工具ElectricFence

clip_image020

参考:http://directory.fsf.org/wiki/Electric_Fence

       实际运行环境中经常比较困扰的问题是内存产生轻微越界,但是并没有立刻产生问题, 给调查带来很大难度。

如果程序问题调查没有头绪,可以借助 Electric Fence (简称 Efence )工具检查内存问题并产生 CORE 文件,比较精确的定位问题场所。

       在发生如下问题的情况下此工具会转储 CORE

1 )动态分配内存边界外的读写操作

2 )访问已经 free 的内存

3 free 的地址不是 malloc() 分配的

Valgrind

       参考:http://valgrind.org/

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。

clip_image021

no segmentation fault does not means program code is fine

分配页策略,no segmentation fault does not means program code is fine

       The OS will not allocate partial pages to a programSo some illegal access may not cause some error info. The reason are

       操作系统不会将不完整的页分配给程序,例如,如果要运行的程序总共大约有10000字节,如果完全加载,会占用3个内存页(一个页占4096个字节),它不会仅占用2.5个页,因为页是虚拟内存系统能够操作的最小内存单元,这是调试时要着重了解的情况,这也导致了程序的一些错误内存访问不会触发段错误,换言之,在调试会话期间,没有引起段错误并不能直接说明代码是没有问题的。

debugging in a multiple-activities context

多活动上下文中的调试

       套接字socket是一种类似于文件描述符的抽象,正如人们使用文件描述符在文件系统对象上执行IO操作一样,通过套接字向网络连接读写信息。

       进程和线程之间的主要区别是:与进程一样,虽然每个线程有自己的局部变量,但是多线程环境中父程序的全局变量被所有线程共享,并作为在线程之间通信的主要方法。

       并行编程架构主要有两种:共享内存和消息传递

l  共享内存:多个CPU都具有对某些共同的物理内存的访问权限,在一个CPU上运行的代码与在其他CPU上运行的代码通信,方法是通过在这个共享内存上读写,这与多线程应用程序中的线程通过共享地址空间与另一个线程通信基本一样;

l  消息传递:在各个CPU上运行的代码只能访问该CPU的本地内存,它通过通信媒介发送成为消息的字符串来与其他CPU上的代码通信,通常这是某种网络,通过某种通信协议()或者适用于消息传递引用程序的专门软件基础结构。

程序崩溃处理

程序崩溃处理

       有人说C语言是低级语言,这有一部分原因是因为应用程序的内存管理大部分需要由程序员来实现。虽然这种方法非常有用,但是也给程序员添加了很多的麻烦。

       也有人说,C语言是相对较小且容易学习的语言,然而,只有不考虑标准C语言库的典型实现时,C语言才比较小,这个库相当庞大,很多程序员认为C语言是易用语言,那是因为他们还没有遇到指针。

       一般而言,程序错误会导致下面两件事情的发生:

l  导致程序做一些程序员没有打算做的事情;

l  导致程序崩溃

 

相信很多调试过程序的兄弟都碰到过段错误即segmentation fault,则合格主要原因是试图在未经允许的情况下访问一个内存单元。硬件会感知这件事并执行对操作系统的跳转。

堆区域

       调用malloc函数分配的内存;

栈区域

       用来动态分配数据的空间,函数调用的数据(包括参数、局部变量和返回地址)都存储在栈上。

查看程序在Linux上的精确内存布局情况

       可以通过使用info proc mappings来详细查看该程序在Linux上的精确内存布局情况,例如:

clip_image002

此时我们还可以看到这个进程号为14455,所以我们还可以通过文件/proc/14455/maps来查看该信息。通过这些信息,我们有可能看到文本和数据区域,以及堆和栈。

分配页策略

       操作系统不会将不完整的页分配给程序,例如,如果要运行的程序总共大约有10000字节,如果完全加载,会占用3个内存页(一个页占4096个字节),它不会仅占用2.5个页,因为页是虚拟内存系统能够操作的最小内存单元,这是调试时要着重了解的情况,这也导致了程序的一些错误内存访问不会触发段错误,换言之,在调试会话期间,没有引起段错误并不能直接说明代码是没有问题的。

页的角色细节

       当程序执行时,它会连续访问程序中的各个区域,导致硬件按照以下几种情况所示处理页表:

l  每次程序使用全局变量时,需要具有对数据区域的读写访问权限;

l  每次程序访问局部变量时,程序会访问栈,需要对栈区域具有读写访问权限;

l  每次程序进入或离开函数时,对该栈进行一次或多次访问,需要对栈区具有读写访问权限;

l  每次程序访问通过调用malloc或者new创建的存储器时,都会发生堆访问,也需要读写访问权限;

l  程序执行的每个机器指令是从文本区域取出的,因此需要具有读和执行文件;

信号

       这里需要注意的是,进程抛出的信号,实际上没有任何内容发送给进程。所发生的事情只不过是操作系统将信号记录到进程表中,以便下次进程接收信号时得到CPU上的时间片,执行恰当的信号处理程序。

自定义信号的复杂性

       使用GDB/DDD/Eclipse调试时,自定义信号处理程序可能会使程序变得复杂,无论是直接使用还是通过DDD GUI,每当发出任何信号时,GDB都会停止进程,所以,有可能意味着GDB会因为与调试无关的工作而频繁的停止,此时可以使用handle命令告诉GDB在某些信号发生时不要停止。

总线错误的原因

l  访问不存在的物理地址;

l  在很多架构上,要求访问32位量的机器指令要求字对齐,而导致视图在奇数号地址上访问具有4字节的数的指针错误可能引起总线错误。

总线错误是处理器层的异常,导致在Unix系统上发出SIGBUS信号,默认情况下,SIGBUS会导致转储内存并终止。

核心文件

       有些信号表示让某个进程继续是不妥当的,甚至是不可能的,在这些情况中,默认动作是提前终止进程,并编写一个名为核心文件core file文件,俗称转储核心

       核心文件包含程序崩溃时对程序状态的详细描述:栈的内容、CPU寄存器的内容、程序的静态分配变量的值。

       我们可以通过file命令来查看文件的详细信息。

为什么需要核心文件

l  只有在运行了一段长时间后才发生段错误,所以在调试器中无法重新创建该错误;

l  程序的行为取决于随机的环境事件,因此再次运行程序可能不会再现段错误;

l  当新手用户运行程序时发生的段错误,需要发送核心文件给开发人员。

重载功能

       GDB注意到重新编译了程序后,它会自动加载新的可执行文件,因此不需要退出和重启GDB

调试设计的内容

l  确认原则;

l  使用核心文件进行崩溃进程的“死后”分析;

l  纠正、编译并重新运行程序后,甚至不需要退出GDB

l  Printf()风格调试的不足之处;

l  利用你的智慧,这是无可替代的;

l  如果你过去使用printf风格调试的,就会发现使用printf跟踪这些程序错误中的部分错误原来有多难,虽然在调试中使用printf诊断代码有一定的好处,但是作为一种通用目的的工具,它远远不足以用来跟踪实际代码中发生的大部分程序错误。