Unix/Linux C++应用开发-程序调试

通常软件开发中,很难有工程师能够一次性写出正确无误的程序代码。而程序调试以及测试步骤将会在整个软件开发过程中占据相当大的份量。本章开始主要介绍Linux系统下C++应用程序调试工具的使用情况。重点以Linux系统下gdb调试工具使用介绍,配合实际C++应用程序调试实例作详细讲述。让初学者在学习Linux下C++应用程序开发之前,首先掌握基本的程序调试手段。

1.调试工具说明

对于经常在Windows平台进行应用程序开发的开发者来讲,往往并不需要太过在意相应的调试工具的命令使用。因为Windows这类图形化平台为开发者提供了可视化开发工具。开发者只需要根据提供的图形化调试功能选项,即可轻松完成当前应用程序的调试工作。

但是对于Unix以及Linux这类操作系统来讲,则没有那么的幸运。凡是在该平台编辑的应用程序在程序编译的时候,需要采用操作系统提供的调试工具来实现命令行方式的调试应用。各类商用的Unix根据不同的厂家提供的调试工具并不一致,如Unix主机常用dbx调试工具、GNU提供的gdb等。但是对于Linux操作系统,通常默认支持GNU提供的gdb调试工具。本章主要介绍在Linux系统下gdb调试工具的具体使用。

Linux系统下的gdb调试工具是GNU组织下的一个受通用公共许可证保护的自由软件。相比于其它平台的调试工具,gdb提供了比较完备的应用程序调试功能。在gdb工具中,可以让开发的应用程序运行到指定的位置,可以查看指定位置的相关变量、堆栈等信息。并且该工具支持大量的计算机语言调试。本章主要以C++语言程序作为调试对象。

Linux系统下的gdb调试工具为开发者调试应用程序提供了比较完备的调试功能。在详细了解该工具的使用之前,首先通过一些该工具的简单操作以及相应的简单实例的调试,帮助大家对gdb调试工具有一个初步的认识。

2.gdb工具基本操作

对于软件开发中的程序调试,一般在程序员开发的应用程序时大致有如下几个步骤。

1)第一类,通过编译器编译程序产生的错误称为语法类错误;

2)第二类则是指应用程序编译通过并生成可执行程序,但执行时并没有按照要求得到正确结果的错误;

3)还有因为程序内部隐含的特定条件下的处理错误而引起程序core的现象,该类错误一般会产生相应的core文件便于调试。

Linux下针对第一种应用程序语法类错误的解决方法,是开发者根据编译器给出的错误提示进行代码修改。这类代码往往违反编译器语法规定。但是面对隐含性错误以及应用程序core的情况,则需要专业的调试工具来实现单步跟踪调试,此时gdb正是派上用场的时候。
1)启动gdb

[developer@localhost developer]$ gdb          //在shell下执行gdb启动程序命令

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)     //以下为gdb工具启动信息

Copyright 2003 Free Software Foundation, Inc.

GDB is free software, covered by the GNU GeneralPublic License, and you are

welcome to change it and/or distribute copies of itunder certain conditions.

Type "show copying" to see theconditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as"i386-redhat-linux-gnu".

(gdb)

出现上述提示,表示gdb工具程序已经启动,随后提供的“(gdb)”即为当前可编辑操作命令处。当前Linux平台下如果支持相应的GNU相关工具,那么在启动该工具命令之后,显示该工具基本信息表明该工具启动成功。其中展示的基本信息中主要包括gdb工具的版本,相应的版权以及相关欢迎信息等。
2)退出gdb
gdb通过quit命令来退出该工具,当前调试会话下输入该命令,并按下回车键。输出结果如下所示。

[developer@localhost developer]$ gdb             //gdb工具启动

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)    //以下为gdb工具启动信息

Copyright 2003 Free Software Foundation, Inc.

GDB is free software, covered by the GNU GeneralPublic License, and you are

welcome to change it and/or distribute copies of itunder certain conditions.

Type "show copying" to see theconditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as"i386-redhat-linux-gnu".

(gdb) quit    //当前调试会话中执行quit命令退出gdb工具

[developer@localhost developer]$

该信息表明退出gdb调试工具,返回当前shell进程中。对于该类基本命令操作的使用还不清楚可以直接在工具启动后的当前编辑处使用help后加相应的命令,随即会显示该工具操作的使用说明。
3)应用程序如何加入调试信息
对于Linux系统下的C++应用程序,通常可以分为release与debug两个应用版本。在软件项目没有正式完成发布之前,通常为debug版本。该版本中加入了对应的调试信息,但是该版本的应用程序编译器对其并没有作任何的优化操作。该版本主要用于开发者调试程序使用。

而release版本表示软件开发调试完毕正式交付的发布版本。该版本中编译器针对应用程序作了相应优化,使当前程序代码以及运行的速度上得到最大的优化,提供高应用程序运行的效率。

Linux系统下release与debug两个版本最主要的区别在于编译程序时加入的编译选项。对于需要调试程序来讲,通常需要使用debug版本,然后可以使用g++编译应用程序时加上-g命令,将会加入相应的应用程序调试信息,便于开发者调试。gdb工具的相关选项命令可以通过gdb –h命令来列举,该命令显示结果如下。

[developer@localhost developer]$ gdb –h    //当前shell下执行gdb –h命令输出帮助菜单

This is the GNU debugger.  Usage:               //以下为gdb工具操作命令帮助菜单

    gdb[options] [executable-file [core-file or process-id]]

    gdb[options] --args executable-file [inferior-arguments ...]

Options:

  --args             Arguments after executable-fileare passed to inferior

 --[no]async        Enable(disable) asynchronous version of CLI

  -bBAUDRATE     Set serial port baud rateused for remote debugging.

 --batch            Exit afterprocessing options.

通过gdb –h命令以及进入gdb工具后的help帮助命令。初学者可以简单的查询gdb相关选项以及调试命令的简要使用说明。下面小节将会通过一个简单完整的实例程序来演示Linux下gdb调试工具的基本应用操作情况。该实例并不包含任何错误,目的仅仅是通过gdb调试工具了解程序执行的完整过程。
4)一个完整的简单C++程序调试实例
通过前面两小节对gdb调试工具基本概念以及相应操作的介绍,下面会通过一个完整的C++程序调试的实例,帮助读者直观地了解Linux系统下C++应用程序调试的一般过程。
a.准备实例
本完整实例主要实现获取当前时间字符串的功能,采用系统针对时间处理的API接口来获取所需要格式的时间字符串,实例程序完整代码编辑如下所示。

//实例chapter0301

//chapter0301.cpp

#include <iostream>

#include <string>

using namespace std;

/*基本功能,实现获取当前时间,将其格式化为对应的整型数表示*/

string date_string()

{

         time_tclock = time(NULL);  //调用时间函数,将返回结果付给时间结构体变量

         structtm* stm = localtime(&clock); //调用求取本地时间函数,返回结果存放于时间结构体中

         charbuffer[15];         //时间字符串缓冲区

         sprintf(buffer,"%4d%02d%02d%02d%02d%02d",  //格式化时间为整型数存放于相应缓冲数组中

                   stm->tm_year+ 1900,                                            //当前日期:年

                   stm->tm_mon+ 1,                                                   //当前日期:月

                   stm->tm_mday,                                                      //当前日期:日

                   stm->tm_hour,                                                         //当前时间点:时

                   stm->tm_min,                                                           //当前时间点:分

                   stm->tm_sec);                                                          //当前时间点:秒

         returnbuffer;                                                                      //处理完毕返回该数组名

}

/*主函数入口*/

int main()

{

         stringdate;                                                                         //定义时间表示字符串

         cout<<"Getcurrent time:"<<endl;                                   //提示获取当前时间

         date= date_string();                                                         //调用获取当前时间函数,返回值直接存放至时间字符串

         cout<<date<<endl;                                                           //打印输出时间字符串信息

         return0;

}

Linux平台下采用g++编译器编译源程序,为了便于gdb工具调试程序,在命令方式编译时需加上-g选项,用于添加调试信息。Linux系统下需要编译的源文件为chapter0301.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter0301.o          #makefile变量定义,目的是灵活可替换程序中间文件
CC=g++                                      #makefile变量定义,目的是灵活可替换编译命令
chapter0301: $(OBJECTS)        #具体的目标程序,以及随后的编译条件
    $(CC)$(OBJECTS) -g -o chapter0301

clean:                                          #makefile清除当前编译结果操作
    rm -fchapter0301 core $(OBJECTS)  #具体的rm删除命令操作
submit:                                         #makefile提交当前可执行程序、库以及头文件操作
    cp -f -rchapter0301 ../bin         #具体的提交拷贝操作,提交可执行程序以及当前目录头文件
    cp -f -r *.h../include

在当前shell下执行make命令,生成可执行程序文件,随后通过makesubmit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[developer @localhost src]$ make                            //make执行编译代码命令
g++ -c -o chapter0301.o chapter0301.cpp                //执行g++编译命令,生成工程文件
g++ chapter0301.o -g -ochapter0301                   //执行g++编译命令,生成可执行程序

[developer @localhost src]$ makesubmit            //makesubmit提交可执行程序、头文件
cp -f -r chapter0301 ../bin                      //执行cp拷贝命令,将可执行程序拷贝至bin目录
cp -f -r *.h ../include                //执行cp拷贝命令,将所有头文件拷贝至include目录

[developer @localhost src]$ cd../bin                                     //定位至bin目录
[developer@localhost bin]$./chapter0301                           //执行程序
Get current time:
20081212145309

上述实例程序主要由主函数与一个获取当前时间字符串函数组成。主要功能为通过封装获取当前时间的函数调用,在当前屏幕中打印输出当前时间信息。要了解程序中当前时间获取函数主要处理流程,需要初学者首先了解系统中C++程序处理时间的一般方法以及Linux系统时间的基本概念。
b.使用gdb调试
回归主题,本实例主要内容是采用gdb调试工具初步认识完成应用程序基本运行流程。通过gdb一系列的命令操作,来了解一个完整的C++应用程序在运行时的具体处理细节。首先,程序在编译之初已经通过增加-g选项来添加相关的调试信息。因此,需要采用gdb工具调试该应用程序,只需要在当前shell中输入gdb后加相应调试的可执行程序即可,该操作结果如下。

[developer@localhost bin]$ gdb chapter0301        //通过gdb命令加载调试程序

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)  //以下为gdb启动信息

Copyright 2003 Free Software Foundation, Inc.

GDB is free software, covered by the GNU GeneralPublic License, and you are

welcome to change it and/or distribute copies of itunder certain conditions.

Type "show copying" to see theconditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as"i386-redhat-linux-gnu"...

(gdb)                                                                            //gdb调试会话命令操作处

此时通过调试工具与可执行程序的关联,其作用类似于在该调试工具中打开了处理的文件,进入了gdb工作界面。为了了解该完整C++实例程序具体处理流程以及相应的内部变量存放的数据,下面将会通过几个常用的gdb调试命令达到目的。基本调试过程演示如下所示。

(gdb) break main                              //通过break命令在程序主函数入口设置断点
Breakpoint 1 at 0x80489f
5: file chapter0301.cpp, line 23.       //显示断点基本信息
(gdb) break date_string                   //通过break命令在当前时间获取函数处设置断点
Breakpoint 2 at 0x8048923: file chapter0301.cpp,line 7.
(gdb) info break                               //通过info命令查看当前程序拥有的断点信息
Num Type          Disp Enb Address    What
1  breakpoint     keep y   0x080489f
5 in
main at chapter0301.cpp:23
2  breakpoint     keep y   0x08048923 in
date_string() at chapter0301.cpp:7
(gdb)

调试中断点
为了弄清上述操作步骤,需要了解程序调试中断点的基本概念。对于断点,初学者在此处可以直接理解为程序处理指定的停止点。即通过断点标识的设定,让程序在调试工具中运行到指定的位置停住。上述操作中,共设置了两个程序断点,分别为main主程序入口点以及date_string获取当前时间函数入口点。

此处可以简单的了解到,断点可以通过break命令加指定点来设定。该指定点可以为指定位置的代码行数,也可以为指定地点函数名称等。设置完程序处理断点之后,通过info break命令组合可以查看当前调试会话中断点的基本信息情况,具体打印出来的信息说明会在后面详细讲述,此处仅仅作为演示。

当程序的断点设置好之后,可以在调试工具中运行该可执行程序,从而运行定位到断点处。在gdb中运行可执行程序可以使用“run”,或者使用其单个字符‘r’表示,运行流程调试如下所示。

(gdb) run                              //当前调试会话中run命令执行可执行程序
Starting program: /mnt/hgfs/share/worktest/linux_c++/gdb/chapter0301
Breakpoint 1, main () at chapter0301.cpp:23 //显示程序停在主函数入口第一个断点处信息
23         string date;               //并且列出当前断口处执行的第一行代码
(gdb) n                                  //通过next单步调试命令,单步执行程序
24         cout<<"Get current time:"<<endl;  //程序执行至打印获取当前时间提示信息
(gdb) n                                                        //继续通过next命令单步执行
Get current time:
25         date = date_string();   //在提示信息打印之后,程序单步执行至时间求取方法处
(gdb) n                                     //再次next单步调试后,程序运行至第二个断点处
Breakpoint 2, date_string() () at chapter0301.cpp:7     //显示第二个断点位置,表明进入了具体求取时间方法中
7                time_t clock = time(NULL);                         //执行调用日历日期方法
(gdb) n                                                                          //单步next执行
8          struct tm* stm = localtime(&clock); //执行调用日历时间转换为本地时间的方法
(gdb) n
11         sprintf(buffer, "%4d%02d%02d%02d%02d%02d",      //单步执行通过sprintf格式化时间字符串
(gdb) n
18         return buffer;                         //单步执行返回求取的时间字符串
(gdb) n
19      }
(gdb) n                                               //单步执行后,跳回至主程序执行
main () at chapter0301.cpp:26          //打印程序执行位置信息
26         cout<<date<<endl;               //单步执行至打印输出时间信息
(gdb) n
20081213103557                              //单步执行至输出时间结果字符串变量值
27                return 0;                        //最终返回0给操作系统作处理
(gdb) n
28      }
(gdb)

上述过程在gdb工具中运行了run命令,开始运行可执行程序到第一个指定的断点处。从演示的结果看,程序运行到第一个指定的断点处停住,并且打印了相关的提示信息,说明了程序具体的位置,并且显示断点在文件中具体的行数等信息,同时还打印了进入断点处第一个即将执行的语句。

随后将会通过next命令来单步的执行程序,从而了解整个可执行程序的流程走向。从上述演示来看,最后代码会在调用当前时间函数处跳转至指定的断点,即进入函数内部执行,通过单步调试初学者可以了解到整个程序中执行的步骤,此处仅仅是通过gdb调试工具了解应用程序处理流程。下面将会就具体的gdb工具中的调试命令作详细介绍。

推荐阅读更多精彩内容