推动Windows的限制:句柄

这是我推出Windows  系列的第五篇文章,在这篇文章中  ,我探索了Windows管理的资源的数量和大小上限,例如物理内存,虚拟内存,进程和线程。这是整个推动限制系列的索引。虽然他们可以独立存在,但他们认为你是按顺序阅读的。

推动Windows的限制:物理内存

推动Windows的限制:虚拟内存

推动Windows的限制:分页和非分页池

推动Windows的限制:进程和线程

推动Windows的限制:句柄

推动Windows的限制:USER和GDI对象 - 第1部分

推动Windows的限制:USER和GDI对象 - 第2部分

这一次我要去执行句柄来查找和解释它们的极限。句柄是表示与应用程序交互的基本操作系统对象的开放实例的数据结构,例如文件,注册表键,同步原语和共享内存。有两个与进程可以创建的句柄数量相关的限制:系统为进程设置的句柄的最大数量,以及可用于存储句柄的内存量以及应用程序正在使用其句柄引用的对象的数量。

在大多数情况下,句柄的限制远远超出了典型的应用程序或系统所使用的范围。但是,没有考虑到限制的应用程序可能会以开发人员无法预料的方式推动应用程序。出现更常见的一类问题是因为这些资源的生存期必须由应用程序来管理,就像虚拟内存一样,即使对于最好的开发人员来说,资源生命周期管理也是具有挑战性的。无法释放不需要的资源的应用程序会导致资源泄漏,最终导致限制被打击,从而导致对应用程序,其他应用程序或一般系统的行为进行奇怪和困难的诊断。 

与往常一样,我建议您阅读以前的文章,因为他们解释了这篇文章引用的一些概念,如分页池。

把手和对象

在%SystemRoot%\ System32 \ Ntoskrnl.exe映像中实现的Windows的内核模式核心由各种子系统组成,如内存管理器,进程管理器,I / O管理器,配置管理器(注册表)执行的所有部分。这些子系统中的每一个都使用对象管理器定义一个或多个类型来表示它们暴露给应用程序的资源。例如,Configuration Manager将密钥 对象定义  为表示打开的注册表项; 内存管理器将Section 对象定义  为共享内存; 执行官定义了S 信号,M utant  (互斥体的内部名称)和   Event 同步对象(这些对象包装由操作系统的内核子系统定义的基本数据结构); I / O管理器将File  对象定义  为表示设备驱动程序资源的打开实例,其中包括文件系统文件; 和进程管理器创建  线程 和  过程 对象,我在我最后一个推动极限后讨论。每个Windows版本都引入了Windows 7的新对象类型,总共定义了42个。通过运行 具有管理权限的Sysinternals Winobj实用程序并导航到对象管理器名称空间中的ObjectTypes目录,您可以看到定义  的对象:

图片

当应用程序想要管理其中一个资源时,它首先必须调用相应的API来创建或打开资源。例如,  CreateFile  函数打开或创建一个文件,  RegOpenKeyEx  函数打开一个注册表项,并且  CreateSemaphoreEx函数打开或创建一个信号量。如果函数成功,Windows会 在应用程序进程句柄表分配一个  句柄 ,  并返回句柄值,哪些应用程序将被视为不透明,但实际上是句柄表中返回句柄的索引。

通过手柄,应用程序可以通过将句柄值传递给后续API函数(如ReadFile,  SetEvent,  SetThreadPriority和  MapViewOfFile)来查询或操作对象  系统可以通过索引到句柄表来查找句柄引用的对象,以找到相应的句柄入口,其中包含一个指向对象的指针。句柄入口还存储进程在打开对象的时候被授予的访问权限,这使系统能够确保它不允许进程对没有请求权限的对象执行操作。例如,如果进程成功打开一个文件进行读取访问,则句柄条目如下所示:

图片

如果进程试图写入文件,则该功能将失败,因为访问权限未被授予,并且缓存的读取访问意味着系统不必再次执行更昂贵的访问检查。

最大手柄数量

您可以使用我在本系列中使用的Testlimit工具来探索第一个限制,以实践探索限制。它可以在这里的Windows Internals书页上下载  为了测试进程可以创建的句柄数量,Testlimit实现了-h开关,该开关指示它创建尽可能多的句柄。通过CreateEvent创建事件对象,   然后使用DuplicateHandle反复复制系统返回的句柄  通过复制句柄,Testlimit避免了创建新的事件,它所消耗的唯一资源就是句柄表项的资源。以下是在64位系统上使用-h选项的Testlimit的结果:

图片

结果并不代表进程可以创建的句柄总数,但是,因为系统DLL在进程初始化期间打开了各种对象。通过向任务管理器或进程资源管理器添加句柄计数列,您可以看到进程的总句柄计数。在这种情况下,Testlimit显示的总数是16,711,680:

图片

在32位系统上运行Testlimit时,它可以创建的句柄数稍有不同:

图片

其总手数也不同,16,744,448:

图片

差异从哪里来?答案在于负责管理句柄表的执行者设置每个进程的句柄限制以及句柄表条目的大小。在Windows为资源设置硬编码上限的罕见情况之一中,Executive定义了16,777,216(16 * 1024 * 1024)作为进程可以分配的最大句柄数。任何在任何时候都有超过一万个句柄的过程可能要么设计得不好,要么会产生句柄泄漏,所以1600万的限制本质上是无限的,并且可以简单地帮助防止泄漏的过程影响系统的其余部分。了解为什么数字任务管理器显示的数字不等于硬编码最大值需要查看执行组织处理表的方式。

句柄表项必须足够大以存储授予访问掩码和对象指针。访问掩码是32位,但指针大小显然取决于它是32位还是64位系统。因此,句柄条目在32位Windows上是8字节,而在64位Windows上是12字节。64位Windows在64位边界上对齐句柄条目数据结构,所以64位句柄条目实际上消耗了16个字节。下面是在64位Windows上处理条目的定义,如使用dt(dump type)命令的内核调试器所示:

图片

输出显示结构实际上是一个联合,有时可以存储除对象指针和访问掩码以外的信息,但这两个字段是高亮显示的。

Executive根据需要在页面大小的块中分配句柄表,将其分为句柄表条目。这意味着x86和x64上的一个4096字节的页面可以在32位Windows上存储512个条目,在64位Windows上存储256个条目。执行者通过将硬编码的最大值16,777,216除以32位Windows上的处理条目数(32,768和64位Windows上的处理条目数)除以65,536来为处理条目分配最大页数。因为执行者使用每个页面的第一个条目作为自己的跟踪信息,所以一个进程可用的句柄数量实际上是16,777,216减去这些数字,这就解释了Testlimit得到的结果:16,777,216-65,536是16,711,680和16,777,216-65,536-32,768是16,744,448。

手柄和分页池

影响句柄的第二个限制是存储句柄表所需的内存量,执行者从页面缓冲池中分配。执行者使用三级方案,类似于处理器内存管理单元(MMU)管理虚拟到物理地址转换的方式,以跟踪它分配的句柄表页面。我们已经看到了存储实际句柄表条目的最低和中等级别的组织。顶层用作指向中级表的指针,在32位Windows上每页包含1024个条目。因此,存储最大句柄数所需的页面总数可以在32位Windows上计算为16,777,216 / 512 * 4096,即128MB。这与任务管理器中显示的Testlimit的分页池使用情况一致:

图片

在64位Windows上,顶级指针页面中有256个指针。这意味着完整句柄表的总页面缓冲池使用量为16,777,216 / 256 * 4096,即256MB。在64位Windows上查看Testlimit的分页池使用情况来确认计算:

图片

分页池通常足够大以容纳这些大小,但正如我前面所述,创建多个句柄的进程几乎肯定会耗尽其他资源,并且如果达到每进程句柄限制,它可能会自行失败因为它不能打开任何其他对象。

处理泄漏

把手的数量会随着时间的推移而增加。处理程序泄漏如此隐蔽的原因是,与Testlimit创建的句柄不同,它们都指向相同的对象,而泄漏句柄的进程也可能泄漏对象。例如,如果进程创建事件但未能关闭它们,则会泄漏句柄条目和事件对象。事件对象消耗非分页池,除了分页池之外,泄漏还会影响非分页池。

您可以使用Process Explorer的句柄视图以图形方式查看进程正在泄漏的对象,因为它突出显示了绿色的新手柄和红色的关闭手柄; 如果你看到大量的绿色与罕见的红色,那么你可能会看到泄漏。您可以通过打开命令提示符流程,选择Process Explorer中的流程,打开句柄视图下方窗格,然后在命令提示符中更改目录,来观察Process Explorer的句柄突出显示。旧的工作目录的句柄将以红色突出显示,而新的以绿色显示:

图片 

默认情况下,Process Explorer仅显示引用具有名称的对象的句柄,这意味着除非 从“视图”菜单中选择“ 显示未命名的句柄和映射”否则不会看到进程正在使用的所有句柄  以下是命令提示符句柄表中的一些未命名的句柄:

图片

就像大多数错误一样,只有泄漏代码的开发者才能修复它。如果您在可以托管多个组件或扩展的进程中发现泄漏,例如资源管理器,服务主机或Internet Explorer,那么问题是负责泄露的组件是什么。弄清楚这一点可能会使您通过禁用或卸载有问题的扩展来避免该问题,通过检查更新来修复问题,或向供应商报告错误。

幸运的是,Windows包含一个句柄跟踪工具,可以用来帮助识别泄漏和负责任的软件。它在每个进程的基础上启用,并且在主动时导致执行者在创建或关闭每个句柄时记录堆栈跟踪。您可以通过使用Application Verifier(可从Microsoft免费下载)或使用  Windows  D ebugger  (Windbg)来启用它  如果您希望系统从启动时跟踪进程的句柄活动,则应该使用Application Verifier。无论哪种情况,您都需要使用调试器和  !htrace  debugger命令来查看跟踪信息。

为了展示实际中的追踪,我启动了Windbg,并将其附加到之前打开的命令提示符处。然后使用 - enable 开关执行!htrace命令来打开句柄跟踪:

图片

我让进程的执行继续,并再次改变目录。然后,我切换回Windbg,停止进程的执行,并且执行htrace而没有任何选项,它列出了从上一个!htrace快照(使用-snapshot 选项创建以来的进程执行的所有打开和关闭操作,  跟踪已启用。以下是相同会话的命令输出:

图片

事件从最近的操作打印到最少,所以从底部读取,命令提示符打开句柄0xb8,然后关闭它,接下来打开句柄0x22c,最后关闭句柄0xec。如果在目录更改后刷新,Process Explorer将显示绿色的句柄0x22c,红色的0xec,但是除非在该句柄的打开和关闭之间发生刷新,否则可能不会看到0xb8。0x22c打开的堆栈显示它是命令提示符(cmd.exe)执行其ChangeDirectory函数的结果。将处理值列添加到Process Explorer确认新的句柄是0x22c:

图片

如果你只是在寻找漏洞,你应该使用!htrace和  -diff 开关,它只显示自上次快照或开始跟踪以来只有新的句柄。执行该命令只显示处理0x22c,如预期的那样:

图片

最后,一个伟大的视频提供了更多关于调试处理泄漏的技巧,第9频道采访了Jeff Dailey,他是微软升级工程师,他为了生活进行调试:https:  //channel9.msdn.com/posts/jeff_dailey/Understanding-handle -leaks-和如何使用的,HTRACE找到的,他们/

下一次,我将研究其他一些基于句柄的资源(GDI对象和USER对象)的限制。对这些资源的处理由Windows子系统管理,而不是执行者,因此使用不同的资源并具有不同的限制。


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值