作者: Bruce   日期: 2012年06月03日 04:20 周日 发表评论 (0)
查看评论 --END*1-->
0 条评论
--END*2-->1,837 人阅读
 

程序BUG往往因为无知和无意识悄然埋下。在网络库中,我写了这么一段关闭socket的代码:

01 void CTcpSocket::Destroy(BOOL bNotifyClosed)
02 {
03     if (m_nLinkStatus != LinkNotOpen)
04     {
05         m_nLinkStatus = LinkNotOpen;
06  
07         ::shutdown(m_hSock, SD_SEND);
08         ::closesocket(m_hSock);
09         if (bNotifyClosed)
10             OnClose(::WSAGetLastError());  // Tag#1:通知应用层socket已关闭
11         m_hSock = INVALID_SOCKET;          // Tag#2:socket句柄置为无效
12     }
13 }

因为这段代码,服务器程序没跑多久就出现异常而Crash掉。现在要讨论的主题就是与Tag1、Tag2的两处代码相关。

一、关于delete this

应用层创建和释放socket对象,而socket对象生命期由引用计数类托管,网络库在调用OnClose通知应用层socket句柄关闭的时候,socket对象开始做清理工作并递减引用计数,发现引用计数为0,进行delete this(即“自杀”)。delete this是一个”饱含争议“的操作,有认为It’s usefull,也有认为It’s a bad practice,甚至有认为这是面试时唯一可以用来考验C++程序员的问题(The Best C++ Interview Question – Ever!)。暂不管是usefull还是bad idea,先来看delete this的合法性问题(这里不讨论delete的语意,可以参考《Inside The C++ Object Model》)。C++ Faqs中是这么阐述:

只要你小心,一个对象通过成员函数请求自杀(delete this)是没有问题的。下面是对“小心”的定义。

  1. 你必须100%确定this对象是通过new分配的(而不是通过new[]、placement new、栈上局部对象、全局对象、或是另一个对象的成员)。
  2. 你必须100%确定这个成员函数是this对象调用的最后一个成员函数。
  3. 你必须100%确定这个成员函数余下的代码(delete this之后)不会再访问this对象的任何一块内存(包括调用任何其他成员函数或访问任何成员数据)。
  4. 你必须100%确定在delete this之后,不再去访问该this指针。换句话说,你不能对它做检验操作,用来和其他指针比较(包括NULL),用来打印,做转换(cast)等任何操作。

通常如果this指针指向的是一个不具有virtual析构函数的基类对象时往往会出现警告。

既然delete this有其合法性,我当且认为delete this本身并非一个bad practice,而要看delete this是否得当(这里我想起电影《钱学森》中的一句话:手上没有剑和有剑不用不是一回事。C++就是这么一柄利剑,很多强大特性需要去权衡考虑用或不用)。正如人也会有自杀一样,有些是因为万千烦恼而自寻短见,有些则是舍身取义而自我牺牲,我们惋惜前者,敬佩后者。如果C++对象自杀能避免以上“忌讳”而达到了资源安全释放的目的,也就可以为之。

二、堆破坏检测

说完了delete this,接下来要说堆破坏的问题。上面Tag2处的代码即犯了“小心”delete this的第3条忌讳,OnClose触发应用层socket对象delete this,而网络库却还在该对象的成员(m_hSock)进行写入操作,另外应用层还有别处在申请堆内存,结果发生堆破坏而造成程序Crash。堆破坏是开发过程中常见的一个问题(尤其对于这种多人模块开发),可以借助PageHeap(页堆)工具来检测堆破坏。

1、什么是页堆

从Windows2000开始系统在堆管理器(即PageHeap管理器)引入“校验层”,该层处于Ntdll.dll模块内,可以验证程序所有的动态内存操作(分配、释放及其他堆操作)。当启用页堆管理器,让应用层序在调试器下启动时,如果遇到问题,调试器将会中断,但不指名是什么错误(如果不是在调试器下启动,则遇到问题只会崩溃而无任何反馈)。

页堆有两种类型,正常页堆(Normal Page Heap)完全页堆(Full-page Heap)

完全页堆:当分配一块内存时,通过调整内存块的起始分配位置,使其结尾恰好与系统分页边界对齐,然后在边界相邻处再多分配一个不可访问的页作为保护区域。这样,一旦出现内存读写越界时,系统捕获到这个异常然后中断执行并将该异常交给调试器处理,从而有机会及时检查内存越界的位置。

因为每次分配的内存都需要以这种形式布局,对于小片内存分配,即使分配1个字节,也要分配一个内存页和一个保留页,这就需要大量内存。所以在使用完全页堆前确保虚拟内存呢能满足这样的分配需求。

正常页堆:类似于CRT调试内存分配函数,通过分配少量的填充信息,在释放内存块时检查填充区域,来检测内存是否被破坏。此方法的优点是极大的减少了内存耗用量,缺点是只能在释放内存块时检测,不方便跟踪出错代码的位置。

2、页堆工具

PageHeap.exe、GFlagsApplication Verifier是三种外壳工具,都是用来方便配置Page Heap选项(也可以手动配置,位于注册表目录:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\YourAppName\),当Windows开始启动一个进程时,通过检查这个注册表目录的设置,对该进程启用相应的Page Heap选项。

我一般使用GFlags,功能比较全,包含在WinDbg调试器安装包内。使用GFlags配置页堆选项的例子:

列出当前启动了页堆选项的程序列表

C:\Windows\system32>D:\DebugTools\Debugging_Tools_for_Windows\gflags.exe /p

配置正常页堆

C:\Windows\system32>D:\DebugTools\Debugging_Tools_for_Windows\gflags.exe /p /enable appname.exe

配置完全页堆

C:\Windows\system32>D:\DebugTools\Debugging_Tools_for_Windows\gflags.exe /p /enable appname.exe /full

取消页堆设置

C:\Windows\system32>D:\DebugTools\Debugging_Tools_for_Windows\gflags.exe /p  /disable appname.exe

一些特殊选项

/unaligned

这个选项只能用于完全页堆。当我们从Windows堆管理器申请一块内存时,内存总是8字节对齐的(64位上为16字节),页堆默认情况下也会遵守这个规则。但是这会导致分配的内存块的结尾不能跟页边界精确对齐,可能存在0-7个字节的间隙,显然,对于间隙范围内的访问不会立即被发现。/unaligned用于修正这个缺陷,它指定页堆管理器不必遵守8字节对齐规则,保证内存块尾部精确对齐边界。

/backwads

这个选项只能用于完全堆,它使得分配的内存块头部(而不是尾部)与边界对齐,通过这个选项来检测头部分越界访问。

3、页堆检测的有效范围

只要是最终(直接或间接的)调入到Ntdll.dll堆管理函数(即RtlAllocateHeap、RtlFreeHeap)分配函数,页堆检测功能都是有效的。这些分配函数包括:

kernel32导出的HeapAlloc、HeapFree、HeapReAlloc、GlobalAlloc、GlobalFree、GlobalReAlloc、LocalAlloc、LocalFree、LocalReAlloc;

msvcrt.dll导出的malloc、free、realloc、msize、expand、new、delete、new[]、delete []。

4、页堆能发现的错误类型

错误 正常页堆 整页堆
堆句柄无效 立即发现 立即发现
堆块指针无效 立即发现 立即发现
多线程不同步访问堆 立即发现 立即发现
假设重新分配返回相同地址 90% 在实际释放后发现 90% 立即发现
内存块重复释放 90% 立即发现 90% 立即发现
访问已释放的内存块 90% 在实际释放后发现 90% 立即发现
访问块结尾之后的内容 在释放后发现 立即发现
访问块开始之前的内容 在释放后发现 立即发现(特殊标志)

参考微软帮助:How to use Pageheap.exe in Windows XP, Windows 2000, and Windows Server 2003

delete this及堆破坏检测方法的更多相关文章

  1. windbg调试堆破坏

    堆破坏 所谓的堆破坏,是说没控制好自己的指针,把不属于你分配的那块内存给写覆盖了.这块内存可能是你程序的数据,也可能是堆的管理结构.那么这个会导致怎样的后果呢?可能的情况我们来yy下 把程序里的计算结 ...

  2. VS2005内存泄漏检测方法[转载]

    一.非MFC程序可以用以下方法检测内存泄露: 1. 程序开始包含如下定义: #ifdef _DEBUG #define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __ ...

  3. C++中内存泄漏的检测方法介绍

    C++中内存泄漏的检测方法介绍 首先我们需要知道程序有没有内存泄露,然后定位到底是哪行代码出现内存泄露了,这样才能将其修复. 最简单的方法当然是借助于专业的检测工具,比较有名如BoundsCheck, ...

  4. Oracle数据库delete删除普通堆表千万条记录

    Oracle数据库delete删除普通堆表千万条历史记录. 直接删除的影响: 1.可能由于undo表空间不足从而导致最终删除失败的问题: 2.可能导致undo表空间过度使用,影响到其他用户正常操作. ...

  5. 目标检测方法总结(R-CNN系列)

    目标检测方法系列--R-CNN, SPP, Fast R-CNN, Faster R-CNN, YOLO, SSD 目录 相关背景 从传统方法到R-CNN 从R-CNN到SPP Fast R-CNN ...

  6. 小白日记39:kali渗透测试之Web渗透-SQL手工注入(一)-检测方法

    SQL手工注入(一) SQL注入:通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令.[SQL注入原理] ##服务端程序将用户输入参数作为查询 ...

  7. Android手机安全软件的恶意程序检测靠谱吗--LBE安全大师、腾讯手机管家、360手机卫士恶意软件检测方法研究

    转载请注明出处,谢谢. Android系统开放,各大论坛活跃,应用程序分发渠道广泛,这也就为恶意软件的传播提供了良好的环境.好在手机上安装了安全软件,是否能有效的检测出恶意软件呢?下边针对LBE安全大 ...

  8. R-CNN,SPP-NET, Fast-R-CNN,Faster-R-CNN, YOLO, SSD, R-FCN系列深度学习检测方法梳理

    1. R-CNN:Rich feature hierarchies for accurate object detection and semantic segmentation 技术路线:selec ...

  9. 目标检测方法——R-FCN

    R-FCN论文阅读(R-FCN: Object Detection via Region-based Fully Convolutional Networks ) 目录 作者及相关链接 方法概括 方法 ...

随机推荐

  1. C#对 Dictionary进行排序 转

    C# .net 3.5 以上的版本引入 Linq 后,字典Dictionary排序变得十分简单,用一句类似 sql 数据库查询语句即可搞定:不过,.net 2.0 排序要稍微麻烦一点,为便于使用,将总 ...

  2. Pjax.js防刷新技术

    自我感觉良好,所以拿出现在自己用的 Pjax.js 分享给大家 当然 这个版本是 经过本人修改后的版本,跟其它 拿过来就用的 不一样 而且区别还不小 大多的 Pjax 都是 跟后台无关的,而这个版本是 ...

  3. 【MPI学习2】MPI并行程序设计模式:对等模式 & 主从模式

    这里的内容主要是都志辉老师<高性能计算之并行编程技术——MPI并行程序设计> 书上有一些代码是FORTAN的,我在学习的过程中,将其都转换成C的代码,便于统一记录. 这章内容分为两个部分: ...

  4. windows 7 + vs2010 sp1编译 x64位版qt4

    由于qt官方没有发布预编译的64位版qt4,要使用64位版qt4,只能自己编译,编译过程如下: 1,下载源码并解压到D:\qt-src\qt-everywhere-opensource-src-4.8 ...

  5. 附加到iis进程调试时找不到w3wp.exe

    在进程列表的下面,有个show processes in all sessions,把它勾上就能看到了

  6. vs操作快捷键

      注释:        先CTRL+K,然后CTRL+C取消注释: 先CTRL+K,然后CTRL+U 解析命名空间:shift+alt+f10   或Ctrl + .    调试快捷键 F6:   ...

  7. 那天有个小孩跟我说LINQ(八)学会Func

    文章已经迁移到:http://www.ayjs.net/2013/08/68/ 文章已经迁移到:http://www.ayjs.net/2013/08/68/ 文章已经迁移到:http://www.a ...

  8. 年前辞职-WCF入门学习(5)

    前言 第五集比较简单,视频也只有7分多钟,但是用处还是挺大的.下面我会介绍. 本来想第六集一起介绍的,后来发现第六集内容比较多,有半个多小时,就不一起了.网站规定6小时内只能发布一篇文章到首页,,那我 ...

  9. angularJS中-$route路由-$http(ajax)的使用

    后台请求使用的是nodeJS驱动(后面帖代码),很简单的RESTFUL, 页面使用的是bottstarp3.0(懒人神器); 第一个例子: 在本地架设NODEJS, angular的所有请求都是请求本 ...

  10. All thanks

    How to make a salad?——Silun Wang 这是一次成功的团队合作,我为所有组员感到骄傲和自豪!感谢你们! 上个学期期末,和@老钱他们一起去五道口吃Pizza,谈到了大三上学期的 ...