浅谈windows句柄表 windows
windows定义了不少内核对象:进程对象、线程对象、互斥量对象、信号量对象、事件对象、文件对象等等。在调用相应的函数建立这些对象后,咱们均可以经过HANDLE类型的句柄来引用它们。或许你在一些书上看到过说句柄至关于指针,它指向具体的对象。在某种程度上来讲这是不错的,可是进一步深刻探究时就会发现这样的说法很不许确。说到句柄就不能不提句柄表,句柄必须经过句柄表才能找到所引用的内核对象,可是不少书中对句柄表倒是一带而过,不加深究。这是由于Microsoft并未就句柄表发布过任何官方的文档,经过逆向工程得到结论又很难让人信服。 函数
相信没有什么能比windows公布的源代码更有说服力了,接下来我会经过windows公布的WRK(windows research kernel)源代码,来为你们深刻解析一下windows句柄表。虽然是基于WRK的,不能保证其余版本的windows系统也采用相同的机制,可是我想它们之间或许都是大同小异的。 spa
windows使用句柄对进程中的各类对象进行引用。实际上windows句柄就是一个索引,它存储了关联对象在句柄表的索引值,每一个索引对应句柄表中的一个表项。经过句柄存储的索引,就能够很容易得到该句柄项对应的对象的指针。 .net
句柄表中存储了不少了句柄表项,相似下面的结构: 线程
索引 指针 |
该索引对应对象指针 对象 |
1 blog |
0xfffffddf 索引 |
2 进程 |
0xkdkkdh |
3 |
0xkdkhkd |
4 |
0x3jdkkg |
上图仅仅为了演示用,并不表明实际意义。
句柄表是针对于进程而言的,在一个进程使用的句柄,直接在另外一个进程中使用是毫无心义的。换句话说,句柄仅在一个进程内有效。当一个进程的句柄传递给另外一个句柄后,句柄就再也不有效。举例来讲:在进程A中索引值为8的句柄,用于引用a对象。在进程B的句柄表中,虽然索引为8的句柄项可能不为空,可是有多是引用的b对象。此对象非彼对象。
在windows Server 2003(与WRK有相同的内核)中,句柄表是一个多层次的结构。它的类型为:HANDLE_TABLE。定义以下:
咱们看到该结构包含不少的成员,但此处咱们仅讨论对咱们有帮助的几个。
咱们看到HANDLE_TABLE的TableCode成员是一个指针,实际上它的高30位指向存储句柄表的存储结构,该存储结构初始时为4KB大小的一个页面。低2位表明当前句柄表的层数。最多为三层,可是初始时只有一层,之后随着句柄数量的不断增长会不断扩展。
每一个句柄表项大小为8Byte,其结构为HANGLE_TABLE_ENTRY。
,因为windows在为句柄表分配内存时是按页面大小4KB来申请内存的。所以每次为句柄表申请一个新的页面时,句柄表就增长了512项。
前面咱们曾提过,windows句柄表是层次结构的。下图就是当句柄表为三层时的结构图:
若是低2位为0,说明句柄表只有一层。TableCode指向的页面直接存储的句柄,因为每一个表项占8Byte,所以4KB页面最多能够存储512个表项。
下图为当句柄表为一层时的结构图:
若是低2的值为1,此时句柄表有两层,TableCode指向最高层页面。而最高层页面用于存储指向下一层页面的指针。因为32位系统下指针占4Byte,所以最高层页面能够存储1024个指针。每一个指针一样指向4KB的存储表项的页面。能够存储512个句柄表项。此时整个windows句柄表能够存储1024*512个表项。以下图所示:
若是低2位值为2,此时句柄表有三层,以下图所示:
最高层和次高层都是存储的指针,最低层页面存储句柄项,所以此时能够整个句柄表能够存储1024*1024*512个句柄表项。
可是window限定了每一个进程句柄表存储的句柄表项不得超过:2^24=16777216。
实际上,在每一个最低层页面的第一个表项都有特殊用途,因此每一个最低层页面真正供进程使用的表项为511个。
在进程建立时,系统会给新进程分配一个单层的句柄表。随着进程中句柄数量的不断增长,句柄表会由单层扩展为二层,最后被扩展为三层。
在HANDLE_TABLE结构中,FirstFree域记录了当前句柄表中的空闲句柄单链表。说其是单链表,可是每一个元素之间不是经过指针而是经过句柄索引值来链接的。句柄值按HANDLE_VALUE_INC宏定义逐个递增,windows定义该宏的值为4,为何是4?咱们能够经过windbg的!handle命令咱们能够查看一下windows自带的计算器程序的句柄表的状况:
能够看到第一个表项对应的句柄值为4,并且后面的句柄值都是4的倍数。这是为何呢?这由于microsoft将句柄的低两位用来存储该索引对应的句柄表的层次号。在前面曾介绍过,TableCode成员的低两位用以控制句柄表的层次。而此处句柄表的低两位是用于指明该索引所处的层次,有助于快速的定位索引。所以全部的句柄必须右移两位,也就是除以4才能获得它的实际在句柄表中的索引。这也就是句柄值都是4的倍数的缘由。
FirstFree成员存储链表头句柄的索引值。在索引项HANDLE_TABLE_ENTRY结构(立刻会介绍)中,咱们看到有一个名为NextFreeTableEntry的成员。该成员存储下一个空闲句柄索引值。 当进程须要建立新的句柄,该句柄会被加入到句柄。这时就能够从FirstFree取得第一个空闲句柄索引,假设该索引指向x表项,并将该x表项的NextFreeTableEntry成员赋值给FirstFree。此时,原来链表头的NextFreeTableEntry就变成了如今的FirstFree,成为链表头。
伪代码以下:
1:从FirstFree取出索引,该索引指向的句柄表项x。
2:FirstFree=x.NextFreeTableEntry;
在释放x句柄项时,将x句柄索引赋值给FirstFree,并将赋值前的FirstFree的值赋值给x.NextFreeTableEntry。
伪代码以下:
1:Index temp=FirstFree;
2:将x表项的索引赋值给FirstFree
3:x.NextFreeTableEntry=temp;
NextHandleNeedingPool成员记录了下一次对句柄表进行扩展时,扩展页面的第一个索引。也就是说当句柄表全部已分配的页面都满了以后,下一个页面的其实索引值。所以,windows句柄表只是在确实不够用的时候才进行简单的线性增加。并不会一次分配多个页面或扩展多层。
前面咱们曾屡次提到句柄表项,其实它是HANDLE_TABLE_ENTRY结构,定义以下:
[cpp] view plaincopy
该结构由两个大的union组成,占8字节。句柄表项存储了该表项对应的对象的地址,以及其余的一些属性信息。
在第一个union中有一个Object指针,它指向了句柄所表明的内核对象。第二个union中,若是句柄表项指向了一个有效的对象,那么GrantedAccess成员记录了该句柄的访问掩码。当句柄为空闲时,NextFreeTableEntry成员存储下一空闲节点的索引值,用以链接句柄表的空闲链表。
window提供的API:GetCurrentThread和GetCurrentProcess,能够返回当前线程和进程的句柄。其实在句柄表中是没有存储当前进程的句柄和当前线程句柄。GetCurrentThread返回句柄的值是-2,当咱们以此句柄做为参数传递给windows其余API函数时,在函数内部若是检测到该句柄值为-2,不会查找句柄表,当即返回到线程对象地址。相似的调用GetCurrentProcess时,其实返回的是-1,当使用此进程句柄时,一样不须要查找句柄表,会当即返回指向当前进程对象的指针。
本文使用比较通俗的语言,对windows句柄表的结构作了简单的介绍。目的是让对句柄表存在疑问的童鞋对句柄表有个清晰的认识。有说的不对的地方,欢迎指正!