Rational Purify 使用及分析实例
蔡林
, IBM
中国软件开发中心软件工程师
2006
年
3
月
20
日
本文介绍了
IBM Rational Purify
的基本概念和在不同操作系统中使用
Purify
对
C/C++
源程序中存在的内存问题进行勘察和分析,并且提供了有关的实例以便读者在实际操作中作为参考。
本文介绍了
IBM Rational Purify
的基本概念和在不同操作系统中使用
Purify
对
C/C++
源程序中存在的内存问题进行勘察和分析,并且提供了有关的实例以便读者在实际操作中作为参考。
1.内存问题的原因及分类
在
C/C++
程序中,有关内存使用的问题是最难发现和解决的。这些问题可能导致程序莫名其妙地停止、崩溃,或者不断消耗内存直至资源耗尽。由于
C/C++
语言本身的特质和历史原因,程序员使用内存需要注意的事项较多,而且语言本身也不提供类似
Java
的垃圾清理机制。编程人员使用一定的工具来查找和调试内存相关问题是十分必要的。
总的说来,与内存有关的问题可以分成两类:内存访问错误和内存使用错误。内存访问错误包括错误地读取内存和错误地写内存。错误地读取内存可能让你的模块返回意想不到的结果,从而导致后续的模块运行异常。错误地写内存可能导致系统崩溃。内存使用方面的错误主要是指申请的内存没有正确释放,从而使程序运行逐渐减慢,直至停止。这方面的错误由于表现比较慢很难被人工察觉。程序也许运行了很久才会耗净资源,发生问题。
1.1
内存解剖
一个典型的
C++
内存布局如下图所示:
自底向上,内存中依次存放着只读的程序代码和数据,全局变量和静态变量,堆中的动态申请变量和堆栈中的自动变量。自动变量就是在函数内声明的局部变量。当函数被调用时,它们被压入栈;当函数返回时,它们就要被弹出堆栈。堆栈的使用基本上由系统控制,用户一般不会直接对其进行控制,所以堆栈的使用还是相对安全的。动态内存是一柄双刃剑:它可以提供程序员更灵活的内存使用方法,而且有些算法没有动态内存会很难实现;但是动态内存往往是内存问题存在的沃土。
1.2
内存访问错误
相对用户使用的语言,动态内存的申请一般由
malloc/new
来完成,释放由
free/delete
完成。基本的原则可以总结为:一对一,不混用。也就是说一个
malloc
必须对应一且唯一的
free
;
new
对应一且唯一的
delete; malloc
不能和
delete, new
不能和
free
对应。另外在
C++
中要注意
delete
和
delete[]
的区别。
delete
用来释放单元变量,
delete[]
用来释放数组等集聚变量。有关这方面的详细信息可以参考
[C++Adv]
。
我们可以将内存访问错误大致分成以下几类:数组越界读或写、访问未初始化内存、访问已经释放的内存和重复释放内存或释放非法内存。
下面的代码集中显示了上述问题的典型例子:
|
由以上的程序,我们可以看到:在第
5
行分配内存时,忽略了字符串终止符
"/0"
所占空间导致了第
8
行的数组越界写(
Array Bounds Write
)和第
9
行的数组越界读(
Array Bounds Read
)
;
在第
7
行,打印尚未赋值的
str2
将产生访问未初始化内存错误(
Uninitialized Memory Read
)
;
在第
11
行使用已经释放的变量将导致释放内存读和写错误
(Freed Memory Read and Freed Memory Write)
;最后由于
str3
和
str2
所指的是同一片内存,第
12
行又一次释放了已经被释放的空间
(Free Freed Memory)
。
这个包含许多错误的程序可以编译连接,而且可以在很多平台上运行。但是这些错误就像定时炸弹,会在特殊配置下触发,造成不可预见的错误。这就是内存错误难以发现的一个主要原因。
1.3
内存使用错误
内存使用错误主要是指内存泄漏,也就是指申请的动态内存没有被正确地释放,或者是没有指针可以访问这些内存。这些小的被人遗忘的内存块占据了一定的地址空间。当系统压力增大时,这些越来越多的小块将最终导致系统内存耗尽。内存使用错误比内存访问错误更加难以发现。这主要有两点原因:第一,内存使用错误是
"
慢性病
"
,它的症状可能不会在少数、短时间的运行中体现;第二,内存使用错误是因为
"
不做为
"
(忘记释放内存)而不是
"
做错
"
造成的。这样由于忽略造成的错误在检查局部代码时很难发现,尤其是当系统相当复杂的时候。
2.Purify的原理及使用
IBM Rational PurifyPlus
是一组程序运行时的分析软件。她包括了程序性能瓶颈分析软件
Quantify
,
程序覆盖面分析软件
PureCoverage
,和本文的主角:程序运行错误分析软件
Purify
。
Purify
可以发现程序运行时的内存访问,内存泄漏和其他难以发现的问题。
同时她也是市场上唯一支持多种平台的类似工具,并且可以和很多主流开发工具集成。
Purify
可以检查应用的每一个模块,甚至可以查出复杂的多线程或进程应用中的错误。另外她不仅可以检查
C/C++
,还可以对
Java
或
.NET
中的内存泄漏问题给出报告。
2.1 Purify
的原理
程序运行时的分析可以采用多种方法。
Purify
使用了具有专利的目标代码插入技术(
OCI
:
Object Code Insertion
)。她在程序的目标代码中插入了特殊的指令用来检查内存的状态和使用情况。这样做的好处是不需要修改源代码,只需要重新编译就可以对程序进行分析。
对于所有程序中使用的动态内存,
Purify
将它们按照状态进行归类。这可以由下图来说明(来自
[DEV205]
):
参见本文中以上给出的代码,在程序第
5
行执行后,
str2
处于黄色状态。当在第
7
行进行读的时候,系统就会报告一个访问未初始化内存错误(
Uninitialized Memory Read
)。因为只有在绿色状态下,内存才可以被合法访问。
为了检查数据越界错误(
ABR
,
ABW
),
Purify
还在每个分配的内存前后插入了红色区域。这样一来,超过边界的访问指令必定落在非法区域,从而触发
ABR
或者
ABW
错误报告。这里需要指出一点。访问未初始化内存错误
UMR
在某些情况下其实是合法的操作,例如内存拷贝。所以在分析报告时可以把
UMR
放到最后,或者干脆从结果中滤除。
2.2 Purify
的使用
这里简单介绍一下
Purify
在
Windows
和
UNIX
环境下的使用。
在
Windows
中,只要运行
Purify
,填入需要分析的程序及参数就可。
Purify
会自动插入检测代码并显示报告。报告的格式如下(来自
[DEV205]
):
蓝色的图标代表一些运行的信息,比如开始和结束等。黄色是
Purify
给出的警告。通常
UMR
会作为警告列出。红色则代表严重的错误。每一种相同的错误,尤其是在循环中的,会被集中在一起显示,并且标明发生的次数。由每个错误的详细信息,用户可以知道相应的内存地址和源代码的位置,并直接修改。另外用户还可以设置不同的滤过器,用来隐藏暂时不关心的消息。
在
UNIX
系统中,使用
Purify
需要重新编译程序。通常的做法是修改
Makefile
中的编译器变量。下面是用来编译本文中程序的
Makefile
:
|
首先运行
Purify
安装目录下的
purifyplus_setup.sh
来设置环境变量,然后运行
make
重新编译程序。需要指出的是,程序必须编译成调试版本。在
gcc
中,也就是必须使用
"-g"
选项。在重新编译的程序运行结束后,
Purify
会打印出一个分析报告。它的格式和含义与
Windows
平台大同小异。
下面是本文中的程序在
Linux
上
Purify
运行的结果:
|
我们对照程序可以发现
Purify
查出了程序中所有的错误。对于每个错误,她不但给出了源代码的位置还指出这些内存最初分配的源代码位置。这对于查找问题提供了很大帮助。对于程序
12
行的解释,
Purify
将其认为是不匹配的内存释放(
FMM: Freeing mismatched memory
),因为她认为这样的释放方式不符合严格的规定。
Purify
在其报告和文档中使用了很多的缩写,在此一并列出,以便读者在使用时参考(来自
[Purify]
):
2.3 Purify
的一些特性
这里简单介绍一下
Purify
提供的几个特性。有关这些特性的详细信息,请查阅文档
[Purify]
。
- 观察点(Watchpoint):通过在程序或者调试器中调用Purify 提供的观察点函数,Purify可以报告有关被观察对象的读写或其他操作。
- 与Rational其他产品的集成:在Puify的用户界面中可以方便地进入ClearCase和ClearQuest。Purify还可以和PureCoverage同时使用,对程序进行分析。
- Purify的定制:无论是Purify报告中的消息,还是界面中的元素,都可以进行一定程度的定制。另外通过修改配置文件和调用Purify API,用户还可以自动记录运行日志,发送电子邮件等。
- Purify提供的API:为了更好地把Purify融合到自动化测试的体系中,Purify提供了一系列的公开函数。用户完全可以通过脚本的方式自动运行,记录,和分析Purify。
3.总结
当使用
C/C++
进行开发时,采用良好的一致的编程规范是防止内存问题第一道也是最重要的措施。在此前提下,
IBM Rational Purify
作为一种运行时分析软件可以很好地帮助您发现忽略的内存问题,或成为软件自动测试中的一个重要组成部分。
4.参考资料
[C++Adv]
[DEV205] Essentials of Rational PurifyPlus
[Purify] IBM Rational PurifyPlus for Linux and UNIX Documentation
[DEV205] Essentials of Rational PurifyPlus
[Purify] IBM Rational PurifyPlus for Linux and UNIX Documentation