5.7 MS C-Runtime Library内建的检测功能示例
BoundsChecker是一个运行时错误检测工具,它主要定位程序在运行时期发生的各种错误。它通过驻留在 Visual C++开发环境内部的自动调试处理程序来加速应用程序的开发,缩短产品发布的时间。BoundsChecker 对于编程中的错误,大多数是C++中特有的提供了清晰的详细的分析。它能够检测和诊断出在静态,堆栈内存中的错误以及内存和资源泄漏问题。在运行状态下,BoundsChecker验证超过8700APIs和OLE方法,包括最新的Windows APIs,ODBC, ActiveX,DirectX, COM 和 Internet APIs。
BoundsChecker采用一种被称为 Code Injection的技术,来截获对分配内存和释放内存的函数的调用。简单地说,当你的程序开始运行时,BoundsChecker的DLL被自动载入进程的地址空间,然后它会修改进程中对内存分配和释放的函数调用,让这些调用首先转入它的代码,然后再执行原来的代码。BoundsChecker在做这些动作的时,无须修改被调试程序的源代码或工程配置文件,这使得使用它非常的简便、直接。
程序员在开发过程中可能会经常遇到这样的问题:调试时语法没有问题,代码也没有错误,但应用程序运行就是不正常甚至死机,其实这有可能是由于逻辑错误引起的内存溢出或资源泄露等问题,这些错误一般是不容易被检测出来的。而这类错误就是BoundsChecker错误检测范围之一。
通过对被测应用程序的操作,BoundsChecker提供清晰的、详细的程序错误分析,自动查明静态的堆栈错误及内存/资源泄露,并能够迅速的定位出错的源代码,即使在没有源代码的情况下也可检查第三方组件的错误。
BoundsChecker能检测的错误包括:
1)指针操作和内存、资源泄露错误。
比如:内存泄露;资源泄露;对指针变量的错误操作。
2)内存操作方面的错误。
比如:内存读、写溢出;使用未初始化的内存。
3)API函数使用错误。
BoundsChecker 7.2支持的语言和主机平台包括C++, Delphi
Windows NT, Windows95/98/2000
支持Visual C++ 6.0 SP6,Visual Studio .NET 2002,Visual Studio .NET 2003。
安装说明:
1.首先请确定你已经卸载了旧版本的程序。
2.运行Setup目录中的文件进行安装。
3.启动安装程序,使用Setup/Crack目录中的bc72.dat文件注册程序。
4.完成即0K。
5.调试方式有两种
a. 直接启动BC.exe
b. 集成在VC中,启动VC的debug
BoundsChecker 集成在6.0的菜单项和工具条的界面如下:
图(1)菜单项
图(2)工具条
三、 BoundsChecker两种工作模式
使用BoundsChecker对程序的运行时错误进行检测,有两种使用模式可供选择。一种模式叫做ActiveCheck,一种模式叫做FinalCheck。下面分别进行介绍。
ActiveCheck是BoundsChecker提供的一种方便、快捷的错误检测模式,它能检测的错误种类有限,只包括:内存泄露错误、资源泄露错误、API函数使用错误。要想使用ActiveCheck模式来检测程序的运行时错误,只需在VC++集成开发环境中打开BoundsChecker功能,然后从调试状态运行程序即可。此时ActiveCheck会在后台自动运行,随时检测程序是否发生了错误。下面说一下具体的使用步骤。
首先,在VC++集成开发环境中打开你要对其进行测试的程序,同时保证项目处于Debug编译状态下。
其次,确保VC++集成开发环境中[BoundsChecker/Error Detection]菜单项和[BoundsChecker/Log Events]菜单项处于被选中的状态。只有这两项被选中,BoundsChecker才会在程序运行过程中发挥作用。 最后,在VC++集成开发环境中选择[Build/ Start Debug/Go]菜单命令,在Debug状态下运行程序,ActiveCheck也在后台开始运行了。
FinalCheck具有BoundsChecker提供的所有检错功能。FinalCheck是ActiveCheck的超集,它除了能够检测出ActiveCheck能够检测出的错误,还能发现很多ActiveCheck不能检测到的错误,包括:指针操作错误、内存操作溢出、使用未初始化的内存等等,并且,对于ActiveCheck能检测出的错误,FinalCheck能够给出关于错误更详细的信息。所以,我们可以把FinalCheck认为是ActiveCheck的功能增强版。我们付出的代价是:程序的运行速度会变慢,有时甚至会变的很慢。要想在FinalCheck模式下测试程序,不能使用VC++集成开发环境提供的编译连接器来构造程序,而必须要使用BoundsChecker提供的编译连接器来编译连接程序。当BoundsChecker的编译连接器编译连接程序时,会向程序中插装一些错误检测代码,这也就是FinalCheck能够比ActiveCheck找到更多错误的原因。下面就介绍一下如何在FinalCheck模式下对程序进行测试:
(1)在VC++集成开发环境中打开你所要测试的项目。
(2)由于要使用BoundsChecker的编译连接器重新编译连接程序,所以我们为BoundsChecker独自构造一个文件夹。在VC++集成开发环境中,具体操作方法是:
A)点击[ Build/Configurations...]菜单命令。
B)在弹出的对话框中点击Ad按钮。在Configuration 编辑框中添入你为BoundsChecker创建的文件夹的名称,这个名称是任意的,比如我们取名为BoundChecker。
C)在Copy settings from组合框中选中XXX—Win32 Debug项,然后点击OK按钮,接着点击Close按钮。现在,我们已经为FinalCheck构造好了一个文件夹。
(3)点击[Build/Set Active Configuration…]菜单命令,选中你刚才为BoundsChecker建的文件夹,然后点击OK按钮。这样BoundsChecker编译连接程序时生成的中间文件、可执行程序,都会被放到该文件夹下。
(4)选择[BoundsChecker/Rebuild All with BoundsChecker]菜单命令,对程序重新进行编译连接,也就是在这时,BoundsChecker向被测程序的代码中加入了错误检测码。编译连接完成后,BoundsChecker会在你为BoundsChecker构造的文件夹中生成可执行文件。 在FinalCheck模式下对程序进行检测的准备工作都已经做好,这时可以启动程序开始测试了,作步骤与在ActiveChecker模式下没什么区别。具体步骤如下:
确保VC++集成开发环境中[BoundsChecker/ Error Detection]菜单项和[BoundsChecker/ Log Events]菜单项处于选中状态,别外设置[BoundsChecker / Setting] Memory Tracking 选项中的Enable FinalCheckt为选中状态。
点击[ Build/Start Debug]菜单,选中“Go” 菜单项。程序开始在Debug状态下运行。按照你制定好的测试用例,对程序进行操作。
当BoundsChecker 检测到了错误时,会弹出窗口向你汇报,你可以当时就进行处理,也可以等到你的操作全部完成,退出程序之后再对列出的这些错误进行分析。这完全取决于你是否 选中了[BoundsChecker/Display Error and Pause] 菜单项。
退出程序后,BoundsChecker会给出错误检测结果列表。该错误列表与ActiveChecker给出的错误列表的查看方法完全一样。只不过这个列表中所报告的信息会更多、更详细一些。
ActiveChecker、FinalCheck这两种模式,比较而言 各有长短。ActiveChecker使用方便,只需在Debug状态下直接运行程序即可,并且程序的运行速度较快,但检测的错误种类有限; FinalCheck模式下,需要使用BoundsChecker的编译连接器重新编译连接生成可执行程序,并且程序的运行速度比较慢,但检测的错误种 类、提供的错误相关信息要多于ActiveChecker。所以,何时使用何种模式,应根据当时的具体情况而定。
1.资源泄漏检测:BoundsChecker 能够自动定位难以发现的内存泄漏,并监视堆栈和静态内存的状况。这样就节约了你的时间,使您能够开发出更加可靠,不出问题的应用程序。
2.Active API 检查:该特点可以减少你的调试时间,提供工业中最为全面的 Windows API 校验。这样的结果就会带来更高质量的代码,在程序发布时就不会失败。
3.IDE 集成:BoundsChecker 让你透明的调试。它提供在 C++ 中直接访问BoundsChecker 的菜单,工具条和设置,使得开发人员能够立即修复错误。
4.兼容性检查:BoundsChecker 允许你轻松的生成和发布跨Microsoft 平台的应用程序。BoundsChecker 在多平台上校验代码,然后产生一个报告指出所有与 Windows 平台兼容性相关的问题。
程序特点:
1.本地应用程序死锁检查
2.内存和资源查看器
3.COM调用报告
4.NET调用报告
5.垃圾收集(Garbage collection)通知
BoundsChecker错误检测范围主要包括:
1).指针和泄露错误
接口泄露
内存泄露
资源泄露
未分配的指针错误
2).内存错误
动态存储溢出
无效的句柄被锁定
句柄没有被锁定
内存分配冲突
栈空间溢出
静态存储溢出
3).API和OLE错误
API函数返回失败
API函数未执行
无效的变量(包括指针变量、字符串变量等)
OLE接口方法的变量无效
OLE接口方法失败
线程调用库函数错误
5.1 内存泄漏检测示例
代码段
类TempClass.cpp;
TempClass::TempClass()
{
cout<<"构造对象TempClass"<<endl;
}
void TempClass::Print()
{
cout<<"TempClass.Print()"<<endl;
}
#include "stdafx.h"
#include "OSTREAM.H"
#include "TempClass.h"
int main(int argc, char* argv[])
{
printf("-----BoundsChecker用例-----------!/n");
TempClass* myTempPoint = new TempClass();
// delete myTempPoint;
// myTempPoint = NULL;
/*
if( NULL == myTempPoint )
{
cout<<"为空"<<endl;
}
else
{
myTempPoint->Print();
}*/
return 0;
}
图(3)测试结果窗口
双击者点击下方的标签进入到Memory Leaks
图(4)Memory Leaks窗口
图(5)Memory Leak的详细资料
图(6)源代码窗口
结果表明在文件BoundChecker.cpp 下main函数的11行myTempPoint已经分配分间而程序退出时发生了内存泄漏。
5.2 野指针检测示例
#include "stdafx.h"
#include "OSTREAM.H"
#include "TempClass.h"
int main(int argc, char* argv[])
{
printf("-----BoundsChecker用例-----------!/n");
TempClass* myTempPoint = new TempClass();
delete myTempPoint;
// myTempPoint = NULL;
if( NULL == myTempPoint )
{
cout<<"为空"<<endl;
}
else
{
myTempPoint->Print();
}
return 0;
}
图(7)ActiveCheck模式下Debug的结果
在ActiveCheck模式下是无法检查到摇摆指针的
FinalCheck检测:
图(8)设置Enable FinalCheck选项
图(9)切换模式后debug目录下文件对比
图(10)FinalCheck模式下Debug的结果
结果表明在main函数myTempPoint为野指针,指针所指对象已经被释放。
5.3 数组越界检测示例
int iArTemp[8];
for( int i = 0; i <= 8; i++ )
{
iArTemp[i] = i;
}
cout<<"打印数组数据"<<endl;
for( i = 0; i <= 8; i++ )
{
cout<<iArTemp[i]<<endl;
}
return 0;
这里选择Display Error And Pause 选项,所以在Debug过程中,将会即时弹出检查出的错误信息,如下图所示。
图(11)debug 时弹出的错误信息
图(12)debug时弹出的错误信息
Explain: 获取帮助。
Memory/Resource Viewer:查看内存和资源分配情况。
Suppress:终止某类型的报错。
Debug:切换到Debug窗口。
Halt:中断。
Continue:继续检测
Don’t Show this Error: 可以屏蔽某类型错误。(下拉框可以选择条件)
Disable event Logg:是否将事件写入检测结果中。
图(13)检测结果
图(14)Errors选项的结果
结果表明在main()函数中局部变量iAriTemp发生读、写越界的错误,注意在ActiveCheck 模式下是检查不到出数组越界,必需选择FinalCheck模式。
5.4 GDI 资源泄漏检测示例
void CGDICheckerDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
CDC pDC ;
pDC.CreateCompatibleDC( &dc );
CRect rc;
GetClientRect(rc);
CBitmap* pOldBmp = NULL;
m_imgBk.LoadBitmap( IDB_TEST_BMP );
pOldBmp = pDC.SelectObject( &m_imgBk );
dc.BitBlt(rc.left,rc.top,rc.Width(),rc.Height(),&pDC,0,0,SRCCOPY);
// pDC.SelectObject(pOldBmp);
}
图(15)GDI的测试结果
图(16) GDI资源泄漏
结果表明,释放DC时DC仍然占有对象的资源,同时也给出错误发生在OnPaint()函数中,对于GDI的资源泄漏也必需在FinalCheck模式下才可以检测出来。
5.5 句柄资源泄漏检测示例
#include "stdafx.h"
#include "windows.h"
#include "ostream.h"
DWORD WINAPI TestThread( LPVOID lpParameter );
int iIndex = 0;
int main(int argc, char* argv[])
{
HANDLE hThread1;
hThread1 = CreateThread( NULL, 0, TestThread, NULL, 0, NULL );
// CloseHandle( hThread1 ); // Resource Leak
while( iIndex++ < 10 )
cout<<"main Thread is running"<<endl;
return 0;
}
DWORD WINAPI TestThread( LPVOID lpParameter )
{
while( iIndex++ < 10 )
cout<<"TestThread is running"<<endl;
return 0;
}
图(17)测试结果
结果表明程序退出时发生了资源泄漏,资源被CreateThread分配。
5.6 死锁检测示例
#include "windows.h"
#include "ostream.h"
DWORD WINAPI TestThread1( LPVOID lpParameter );
DWORD WINAPI TestThread2( LPVOID lpParameter );
int iTickets = 10;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;
int main(int argc, char* argv[])
{
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread( NULL, 0, TestThread1, NULL, 0, NULL );
hThread2 = CreateThread( NULL, 0, TestThread2, NULL, 0, NULL );
CloseHandle( hThread1 );
CloseHandle( hThread2 );
InitializeCriticalSection( &g_csA );
InitializeCriticalSection( &g_csB );
Sleep( 4000 );
DeleteCriticalSection( &g_csA );
DeleteCriticalSection( &g_csB );
return 0;
}
DWORD WINAPI TestThread1( LPVOID lpParameter )
{
while( TRUE )
{
EnterCriticalSection( &g_csA );
Sleep(10);
EnterCriticalSection( &g_csB );
if( iTickets > 0 )
{
Sleep(10);
cout<<"TestThread1 Sell Tickets: "<<iTickets--<<endl;
LeaveCriticalSection( &g_csB );
LeaveCriticalSection( &g_csA );
}
else
{
LeaveCriticalSection( &g_csB );
LeaveCriticalSection( &g_csA );
break;
}
}
return 0;
}
DWORD WINAPI TestThread2( LPVOID lpParameter )
{
while( TRUE )
{
EnterCriticalSection( &g_csB );
Sleep(1);
EnterCriticalSection( &g_csA );
if( iTickets > 0 )
{
Sleep(1);
cout<<"TestThread2 Sell Tickets: "<<iTickets--<<endl;
LeaveCriticalSection( &g_csA );
LeaveCriticalSection( &g_csB );
}
else
{
LeaveCriticalSection( &g_csA );
LeaveCriticalSection( &g_csB );
break;
}
}
return 0;
}
图(18)死锁测试结果
图(19) 测试结果
图(18)和图(19)中可以看出线程Thread:0x09EC 拥有边界资源Critical Section:0x00431180,同时也在等待边界资源Critical Section:0x00431120的使用权。但刚好线程Thread:0x0C74 拥有边界资源Critical Section:0x00431120 却在等待Critical Section:0x00431180的使用权,所以两个线程都在等待对方释放资源。
5.7 MS C-Runtime Library内建的检测功能示例
MFC封装和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。MS C-Runtime Library在实现malloc/free,strdup等函数时已经内建了内存泄漏的检测功能。
要在非MFC程序中打开内存泄漏的检测功能非常容易,你只要在程序的入口处加入几行代码:示例如下:
#include "stdafx.h"
#include "OSTREAM.H"
#include "TempClass.h"
#include <CRTDBG.H>
int main(int argc, char* argv[])
{
printf("-----BoundsChecker用例-----------!/n");
int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
_CrtSetDbgFlag( tmpFlag );
TempClass* myTempPoint = new TempClass();
// delete myTempPoint;
// myTempPoint = NULL;
return 0;
}
这样,在程序结束的时候,也就是winmain,main或dllmain函数返回之后,如果还有内存块没有释放,它们的信息会被打印到Debug窗口里。
Detected memory leaks!
Dumping objects ->
{49} normal block at 0x00031140, 1 bytes long.
Data: < > CD
Object dump complete.
可以配置属性来忽略你不感兴趣的错误方法如下:
在BoundsChecker菜单项中可以选择设置要禁止的报错内容,从可用的.DPsup文件中挑选要禁止的具体内容,也可以点击Add加入指定的DPsup文件。如下图所示
图(20)Suppression设置框
在选择了Display Error And Pause 选项时,会在调式过程中即时弹出检查出的错误信息,如下图所示。可以点击Suppress按钮来禁止该类型的报错。当你确定要禁止该类型的报错后,BoundsCheckes之后将不再给出这类型的错误提示。
图(21)Suppression设置
图(22)设置过滤选项
图(23)设置过滤选项
6.3设置
图(24)设置界面
图(25)忽略API
这样就可以忽略API的报错。
*详细设置可以点击开始->所有程序->Compuware BounderChecker->Documentation下查看BoundsChecker Quick Ref.pdf和Understanding BoundsChecker.pdf文档。可以直接打开安装目录C:/Program Files/Compuware查看。
6.4 代码控制
在你不想要写入检查日志的地代码段中加入以下代码即可
#include "nmapilib.h"
//代码被监控
StopEvtReporting();
//…不需要被监控的代码段
StartEvtReporting();
//代码被监控
*上面API必需链接NmApiLib.lib(C:/Program Files/Compuware/BoundsChecker/ERptApi)。
6.5设置应用程序关联Bounds Checker
以记事本为例:
打开注册表,在HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options下创建notepad.exe项,将名称修改为Debugger,类型为字符串类型,值为BoundsChecker 的安装目录C:/Program Files/Compuware/BoundsChecker/bc7.exe,启动记事本,系统将会启动BoundsChecker,然后根据你的需要进行设置即可。注意在修改注册表时必需以管理员的身份。(可以查阅Understanding BoundsChecker.pdf 第81页)
在某些情况下,我们需要忽略BoundsChecker报告的一些错误,这些情况包括:
1. 误报
BoundsChecker 指定程序中的某段代码存在错误,但经过我们的仔细检查,证实程序确实没有这个错误,这是BoundsChecker的误报。工具毕竟是工具,它只能依照为它制定的算法行事,所以会有误报的情形发生。但千万不要轻易认定某一个错误为误报,一定要对错误进行仔细的分析,确定是真正的误报。
2. 第三方的代码
BoundsChecker指定的错误发生位置在第三方提供的代码中,包括第三方提供的程序库、DLL、OCX等。对于这种情况,我们也要先进行认真的检查,确定不是由于我们错误的使用第三方的代码引起的。如果最后确定不是我们的原因,则这样的错误报告可以忽略。
3. dll 检测
调试dll文件时必需由EXE文件加载,另外在设置选项Modules and Filesl选择Add Moule 来加入你想检测的dll 文件
4. 其他
还有一点需要强调,使用BoundsChecker对程序进行测试时,需要有程序的源代码。如果没有源码,BoundsChecker虽然也可以打开EXE文件将其执行起来,但得出的测试结果经常是不正确的,因此也就没有太大的意义。
BoundsCheck的工作流程有以下四步:
1、 配置BoundsCheck去收集你想要的数据
a、 选择你想要收集的数据
b、 定义应用程序要被监控的部分
c、 选择你想要禁止或过滤的应用
2、 运行应用程序
a、 程序运行时,错误描述会出现在Program Error Detected对话框中
b、 被禁止的错误提示部分不会提示
c、 必要是可以查看日志和创建过滤
d、 检查内存和资源的使用情况
3、 程序终止时查看数据
a、 在日志中过滤掉你不想看到的事件
b、 为将来要运行的应用程序创建一个禁止方案
4、 你可以保存当前的设置,禁止和过过滤方案以备以后使用