浅谈windows句柄表

                浅谈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。定义以下:

 

[cpp]  view plain copy
  1. <span style="font-size:18px;">  typedef struct _HANDLE_TABLE {  
  2.       ULONG_PTR TableCode;//指针指向句柄表的存储结构。  
  3.       struct _EPROCESS *QuotaProcess;  // 所属进程的指针  
  4.       HANDLE UniqueProcessId;     // 这个进程的 ProcessID  
  5.       EX_PUSH_LOCK  HandleTableLock[HANDLE_TABLE_LOCKS];//句柄表所,仅在句柄表扩展时使用。  
  6.       LIST_ENTRY HandleTableList;   // 全部的 HandleTAble 在内核中造成一个 List ,这是 Entry  
  7.       EX_PUSH_LOCK hangleConventionEvent//若在访问句柄表时发生了竞争则在此锁上等待。  
  8.       PHANDLE_TRACE_DEBUG_INFO DebugInfo;  
  9.       LONG extrainfoPag;  
  10.       ULONG FirstFree; // 空闲链表表头句柄索引。  
  11.       ULONG LastFree; // 最近被释放的句柄索引。  
  12.       ULONG NextHandleNeedingPool;//下一次句柄表扩展的起始句柄索引。  
  13.       LONG HandleCount;//正在使用的句柄表项数量。  
  14.     
  15.   union{  
  16.         ULONG Flags;//标志域  
  17.         BOOLEAN StrictFIFO:1;//是否使用FIFO风格的重用。  
  18.        };  
  19.   } HANDLE_TABLE, *PHANDLE_TABLE;</span>  

咱们看到该结构包含不少的成员,但此处咱们仅讨论对咱们有帮助的几个。

咱们看到HANDLE_TABLETableCode成员是一个指针,实际上它的高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

  1. typedef struct _HANDLE_TABLE_ENTRY   
  2. {  
  3.     union {  
  4.             PVOID Object;//内核对象指针。  
  5.               ULONG ObAttributes;//内核对象属性。  
  6.               PHANDLE_TABLE_ENTRY_INFO InfoTable;//  
  7.               ULONG_PTR Value;  
  8.       };  
  9.     union {  
  10.             union {  
  11.                     ACCESS_MASK GrantedAccess;  
  12.                     struct{  
  13.                               USHORT GrantedAccessIndex;  
  14.                           USHORT CreatorBackTraceIndex;  
  15.                          };  
  16.                       };  
  17.             LONG NextFreeTableEntry;  
  18.           };  
  19. } HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;  

该结构由两个大的union组成,占8字节。句柄表项存储了该表项对应的对象的地址,以及其余的一些属性信息。

 在第一个union中有一个Object指针,它指向了句柄所表明的内核对象。第二个union中,若是句柄表项指向了一个有效的对象,那么GrantedAccess成员记录了该句柄的访问掩码。当句柄为空闲时,NextFreeTableEntry成员存储下一空闲节点的索引值,用以链接句柄表的空闲链表。

 window提供的APIGetCurrentThreadGetCurrentProcess,能够返回当前线程和进程的句柄。其实在句柄表中是没有存储当前进程的句柄和当前线程句柄。GetCurrentThread返回句柄的值是-2,当咱们以此句柄做为参数传递给windows其余API函数时,在函数内部若是检测到该句柄值为-2,不会查找句柄表,当即返回到线程对象地址。相似的调用GetCurrentProcess时,其实返回的是-1,当使用此进程句柄时,一样不须要查找句柄表,会当即返回指向当前进程对象的指针。

本文使用比较通俗的语言,对windows句柄表的结构作了简单的介绍。目的是让对句柄表存在疑问的童鞋对句柄表有个清晰的认识。有说的不对的地方,欢迎指正!

相关文章
相关标签/搜索