⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 jiurl玩玩win2k内存篇 内存共享(一) protopte.htm

📁 关于win2000核心编程的文章
💻 HTM
📖 第 1 页 / 共 3 页
字号:
      这时就已经指向了新的物理页,于是他们就可以把相应的PTE指向新的物理页,并把PTE变为有效。<BR><BR>&nbsp;&nbsp;&nbsp; 
      载入内存的应用程序,载入内存的动态链接库,或者一个文件映射,他们都位于用户地址空间。为了描述方便我们把这些都叫做共享的东西。一个共享的东西都会有一个位于系统地址空间的 
      Segment 结构,这个共享的东西的 Prototype PTE 都顺序的放在这个 Segment 结构的 
      ProtoPtes数组中。比如一个共享的东西,映射到地址空间需要100页,那么它就有100个 Prototype PTE 放在它的 Segment 
      结构的 ProtoPtes数组中,并且它的第0页对应的 Prototype PTE 在数组的第0项,它的第1页对应的 Prototype PTE 
      在数组的第1项。Segment 结构0x38字节长,其中比较重要的是偏移+34处的 4个字节长的 ProtoPtes。ProtoPtes 中为 
      ProtoPtes数组 的首地址,不过通常情况下,ProtoPtes数组 就紧跟在 0x38字节长的 Segment 结构之后。Segment 
      结构偏移+8处4个字节长的 Total Ptes 指明了 ProtoPtes数组 中元素的个数。Segment 结构偏移+0处的4个字节长的 
      ControlArea,是一个指向 ControlArea 结构的指针,ControlArea 结构是一个很重要的结构,通过它我们可以找到相应的 
      File Object ,ControlArea 结构还有指回 Segment 结构 
      的指针。<BR><BR>有密切关系的几个结构是,映射了一个共享东西到一个进程地址空间,这块空间的 Vad&nbsp;<BR><BR>typedef 
      struct _VAD_HEADER {<BR>/*00*/ PVOID StartVPN;<BR>/*04*/ PVOID 
      EndVPN;<BR>/*08*/ _VAD_HEADER* ParentLink;<BR>/*0C*/ _VAD_HEADER* 
      LeftLink;<BR>/*10*/ _VAD_HEADER* RightLink;<BR>/*14*/ ULONG CommitCharge : 
      20;<BR>/*14*/ ULONG Flags : 12;<BR>/*18*/ PVOID ControlArea;<BR>/*1C*/ 
      PVOID FirstProtoPte;<BR>/*20*/ PVOID LastPTE;<BR>/*24*/ ULONG 
      Unknown;<BR>/*28*/ LIST_ENTRY Secured;<BR>/*30*/ } VAD_HEADER, 
      *PVAD_HEADER;<BR><BR>Vad 结构偏移+18处4个字节的 ControlArea 是指向 ControlArea 
      结构的指针。<BR><BR>被共享物理页的 PfnDataBaseEntry</FONT> 
      <P><FONT face=宋体>Zeroed 0x00<BR>Free 0x01<BR>Standby 0x02<BR>Modified 
      0x03<BR>Modified no-write 0x04<BR>Bad 0x05<BR>Active (Valid) 
      0x06<BR>Transition 0x07<BR><BR>struct PfnDataBaseEntry 
      (大小24个字节,即0x18个字节)<BR>/*00*/ uint32 flink<BR>/*04*/ uint32 
      pteaddress<BR>/*08*/ uint32 blink / share count<BR>/*0C*/ byte 
      flags<BR>/*0D*/ byte page state<BR>/*0E*/ uint16 reference count<BR>/*10*/ 
      uint32 restore pte<BR>/*14*/ uint32 containing page<BR><BR>处于 Active 
      (Valid) 状态,被共享的物理页的 PfnDataBaseEntry 结构偏移+4处4个字节的 pteaddress ,<BR>是 
      Prototype PTE 的地址。</FONT> 
      <P><FONT face=宋体><B>实现机制</B></FONT> 
      <P><FONT face=宋体>对于某个进程使用的某个共享页,PTE 有效时和正常一样,访问时 CPU 
      通过页目录,页表把虚拟地址转换成物理地址,访问内存。PTE 无效时,使用 Prototype PTE 保证共享机制的正确。PTE 无效时,系统设置 
      无效 PTE 的 Prototype 标志位。这样当一个进程访问该页的时候,因为PTE无效,所以会引起 Page-Fault 
      异常(Exception)。从而使 CPU 转去执行异常处理程序,异常处理程序检查PTE发现设置了 Prototype 
      标志,就会做相应的处理,重新使PTE有效,并指向正确的物理页。最后 CPU 
      重新执行引起异常的指令,这时该指令所访问的虚拟地址已经是正确的共享物理页了,并且该虚拟地址的PTE也有效了,于是就可以顺利执行。下面我们针对 x86 
      CPU 做更详细的说明。x86 CPU 的无效页表项定义如下<BR><BR>struct _HARDWARE_PTE_X86 
      (sizeof=4)<BR>bits0-0 Valid<BR>bits1-31 reserved<BR><BR>首先要说明的是,这个格式是由 CPU 
      定义的,CPU 将按照这个定义,对每一位做出解释,然后决定处理方式。<BR>对于无效页来说,CPU只定义了bits0-0,用来判断是否有效。无效页的 
      bits0-0 值为0。其他都留给操作系统使用。Win2k 用 bits10-10 Prototype 来表示是否使用 
      Prototype,该位为0表示不使用,为1表示使用。<BR><BR>当某页的页表项无效,并设置了 Prototype 
      ,当某条指令访问该页时,比如指令 MOV EAX , AddressInPrototypePage ,执行这条指令时,CPU 
      会自动通过页目录和页表把 虚拟地址AddressInPrototypePage 转换成物理地址,在地址转换过程中,CPU 
      在从页表项得到物理页地址的同时,会进行页保护检查,比如看该页表项是否有效,是否是只读等等。在这里我们指令中地址的页表项无效,于是就会引发异常。异常也是由 
      CPU 实现的。这里引起的是一个 Page Fault 异常,它的中断号是 0xe (十进制14),需要注意的是 Page Fault 的中断号是 
      0xe 这是由 CPU 定义的( x86 CPU 的 从 0 - 31 这32个中断是由 CPU 定义的,CPU 
      将根据这个定义做相应工作)。在发生异常时,CPU 
      自动把一些寄存器压入堆栈,然后根据中断号,(通过IDTR找到中断描述符表)在中断描述符表中找到相应的中断描述符,根据中断描述符中的地址,转到异常处理程序。中断描述符是由Win2k设置,异常处理程序也是由Win2k决定。对于 
      Win2k Build 2195 来说,中断 0xe 的处理程序是 ntoskrnl!KiTrap0E 地址在 804648a4 。当转到 
      KiTrap0E 时,CPU 
      已经在堆栈中压入了下面的内容<BR><BR>|-------------|&nbsp;<BR>|&nbsp;&nbsp;&nbsp; 
      EFLAGS&nbsp;&nbsp; |<BR>|-------------|<BR>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
      CS&nbsp;&nbsp;&nbsp; 
      &nbsp;|<BR>|-------------|<BR>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EIP&nbsp;&nbsp;&nbsp;&nbsp; 
      |<BR>|-------------|<BR>|&nbsp; Error Code |<BR>|-------------|&lt;---- [ 
      ESP ]<BR><BR><BR>page-fault 异常 (#PF) 的 Error Code 定义如下( CPU 定义 
      )<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp; 
      3 | 2 | 1 | 0 
      |<BR>+---------------------------------------------------+&nbsp;<BR>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
      Reserved&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|RSVD|U/S|R/W| 
      P 
      |<BR>+---------------------------------------------------+&nbsp;<BR><BR>P&nbsp;&nbsp; 
      0 错误由无效页引起<BR>&nbsp;&nbsp;&nbsp; 1 错误由违反页保护引起<BR>W/R 0 
      引起错误的内存访问是读<BR>&nbsp;&nbsp;&nbsp; 1 引起错误的内存访问是写<BR>U/S 0 
      访问错误时处理器处在管理模式<BR>&nbsp;&nbsp;&nbsp; 1 访问错误时处理器处在用户模式<BR><BR>需要说明的是堆栈中压入的 
      EIP 就是引发异常的指令地址,将来将根据这个地址重新执行该指令。而寄存器 cr2 中是引发异常时访问的地址。<BR><BR>#PF异常处理程序 
      KiTrap0E(由Win2k提供)将会调用 ntoskrnl!MmAccessFault ,MmAccessFault 通过 CR2 
      中的访问地址,计算出相应的 PDE,PTE地址,检查 PTE 发现设置了 Prototype 标志(这个标志是由 Win2k 
      定义的,也是由它来处理),最终会调用 ntoskrnl!MiResolveProtoPteFault 来完成 Prototype 
      的相关工作。<BR>把PTE指向正确的共享物理页,并把PTE重新设为有效。<BR><BR>执行完异常处理程序之后,CPU 
      重新执行引起异常的指令,这时指令可以正常执行了。至此 Prototype 已经被实现了。<BR><BR>对于 MmAccessFault 处理 
      Prototype 我们做更详细的说明。<BR><BR>通过分析汇编代码可以知道</FONT> 
      <P><FONT face=宋体>&nbsp;&nbsp;&nbsp; MmAccessFault 根据引起异常的访问地址,计算出相应的 
      PDE,PTE地址,发现 PTE 设置了 Prototype 标志。检查 PTE ,如果PTE 的高 20 bit 不为 0xfffff 
      ,将根据公式 
      PrototypePteAddress=0xe1000000+((Pte&gt;&gt;2)&amp;0x3ffffe00)+(Pte&amp;0xff)*2; 
      计算出 PrototypePteAddress 。<BR>&nbsp;&nbsp;&nbsp; 
      注意这个公式中,((Pte&gt;&gt;2)&amp;0x3ffffe00)的值的范围 和 
      (Pte&amp;0xff)*2的值的范围。由于是无效PTE,最低位本身就是0,(Pte&amp;0xff)*2的值最低2位都会为0,这也保证了 
      PrototypePteAddress 以4字节为边界。如果 PTE 的高 20 bit 为 0xfffff,将调用 
      MiCheckVirtualAddress()。MiCheckVirtualAddress()中调用MiLocateAddress()。MiLocateAddress()将返回InValidAddress所在范围的 
      Vad 的地址。然后用下面的方法计算 PrototypePteAddress=( 
      (InValidAddress&gt;&gt;0xa)&amp;0x3ffffc -TheVad.StartVPN&lt;&lt;0x2 ) + 
      TheVad.FirstProtoPte 也就是计算 InValidAddress 的虚拟页号,用 InValidAddress 的虚拟页号减去 
      Vad 中该范围的其实虚拟页号,然后找到 ProtoPtes数组 中的相应项的地址,就找到了 PrototypePteAddress 
      。</FONT> 
      <P><FONT face=宋体>&nbsp;&nbsp; 然后 MmAccessFault 用 
      InValidAddress,InValidAddress_PteAddress,InValidAddress_PrototypePteAddress 
      等7个参数,调用 MiResolveProtoPteFault()。 MiResolveProtoPteFault()将根据 
      PrototypePte 的各种标志,做不同的处理。对于 PrototypePte 最低位为1,PrototypePte 有效。表明 
      PrototypePte 中的物理页号,就是正确的被共享的物理页的页号。调用MiCompleteProtoPteFault(),将 
      PrototypePte 中的物理页号设置到 Pte 中,把 Pte 变为有效就可以了。对于 PrototypePte 
      最低位为0,PrototypePte 无效。就会检查 PrototypePte 中,看被共享的物理页是不是被放入了 Standby 链,如果是,通过 
      PrototypePte 中的物理页号,把该物理页移回来,把 PrototypePte 变为有效,把 Pte 指向这个物理页,把 Pte 
      也变为有效。如果原来被共享的物理页,已经彻底的不在了,就申请新的物理页,从文件中重新读入被共享的内容,把 PrototypePte 
      指向这个新的物理页,并且把 PrototypePte 变为有效,把 Pte 指向这个物理页,把 Pte 也变为有效。</FONT> 
      <P><FONT face=宋体>下面我们使用 SoftICE 来观察几种不同的情况</FONT> 
      <P><FONT face=宋体>因为所有的 ProtoPteFault 都会调用 MiResolveProtoPteFault() , 
      所以我们在这个函数中下断点。对于建立好堆栈帧之后,MiResolveProtoPteFault()的三个重要的参数分别位于 EBP+0C 
      InValidAddress , EBP+10 InValidAddress_PteAddress , EBP+14 
      InValidAddress_PrototypePteAddress 。<BR><BR>使用 kd 反汇编 
      MiResolveProtoPteFault (以下具体地址只针对 Win2k Build 2195)<BR><BR>u 
      MiResolveProtoPteFault<BR>ntoskrnl!MiResolveProtoPteFault:<BR>8043f6ab 55 
      push ebp<BR>8043f6ac 8bec mov ebp,esp<BR>8043f6ae 51 push ecx<BR>8043f6af 
      51 push ecx<BR>...<BR><BR>可以看到在 8043f6ae 
      处,堆栈帧就建完了,我么就在这个地址上下断点。<BR><BR>:bl<BR>00) * BPX #0008:8043F6AE DO "dd 
      ebp+c l 10;dd ebp-&gt;10 l 10;dd ebp-&gt;14 l 10;"<BR>...<BR><BR>并且断到之后,显示 
      ebp+c 的内容,也就是三个重要参数的值。<BR>显示 ebp-&gt;10 的内容,InValidAddress_PteAddress 
      地址处的内容,InValidAddress_Pte。<BR>显示 epb-&gt;14 
      的内容,InValidAddress_PrototypePteAddress 
      地址处的内容,InValidAddress_PrototypePte。我们根据显示的 Pte 和 ProtoPte 的值,ProtoPteFault 
      是很容易弄出来的,而且也很多,我们可以从中选择合适 Pte 和 ProtoPte 来进行分析。<BR><BR>1 进程的 PTE 和 
      ProtoPtes数组<BR><BR>Break due to BPX #0008:8043F6AE DO "dd ebp+c l 10;dd 
      ebp-&gt;10 l 10;dd ebp-&gt;14 l<BR>10;" (ET=388.52 
      microseconds)<BR>0010:EBA24CC4 77D3C3A7 C01DF4F0 E17C19F0 EBA24CFC 
      ...w......|..L..<BR>// InValidAddress= 77D3C3A7<BR>// PteAddress= 
      C01DF4F0<BR>// ProtoPteAddress= E17C19F0<BR>0010:C01DF4F0 01F064F8 
      01F064FA 01F064FC 01F064FE .d...d...d...d..<BR>// Pte<BR>0010:E17C19F0 
      076A7121 07648121 075E9121 075CA121 !qj.!.d.!.^.!.\.<BR>// 
      ProtoPte<BR>:query 77d3c3a7<BR>Context Address Range Flags MMCI PTE 
      Name<BR>Explorer 77D20000-77D8E000 07100001 810F3908 E17C1980 
      rpcrt4.dll<BR>// 使用 query 命令,查找 InValidAddress 所在范围的Vad。<BR>// 其中 MMCI 就是 
      ControlArea,PTE 是 FirstProtoPte<BR><BR>可以看到 C01DF4F0 处的4个PTE 都无效, 
      bits10-10 Prototype 标志位都为1,并且高20bit不为0xfffff。<BR>所以使用 
      PrototypePteAddress=0xe1000000+((Pte&gt;&gt;2)&amp;0x3ffffe00)+(Pte&amp;0xff)*2 
      可以算出他们对应的 ProtoPte 的地址。计算得到 E17C19F0 
      ,E17C19F4,E17C19F8,E17C19FC。从中我们可以看出他们是顺序对应的。<BR><BR>Break due to BPX 
      #0008:8043F6AE DO "dd ebp+c l 10;dd ebp-&gt;10 l 10;dd ebp-&gt;14 
      l<BR>10;" (ET=4.80 milliseconds)<BR>0010:EB73FCC4 75B05CA1 C01D6C14 
      E356E214 EB73FCFC .\.u.l....V...s.<BR>0010:C01D6C14 095B8C0A 04A8C025 
      04A2A025 04AEC025 ..[.%...%...%... // Pte<BR>0010:E356E214 04A6E121 
      04A8C121 04A2A121 04AEC121 !...!...!...!... // ProtoPte<BR>:query 
      75b05ca1<BR>Context Address Range Flags MMCI PTE Name<BR>NOTEPAD 
      75A90000-75CD2000 0710000F 810802C8 E356E040 
      mshtml.dll<BR><BR>对于有效的Pte,和有效的 
      ProtoPte,他们都指向了相同的物理页。<BR>(有效的Pte,ProtoPte,高20位为物理页帧号)<BR><BR><BR>2 
      PTE无效,高20bit不等于0xfffff。ProtoPte 有效。<BR><BR>Break due to BPX #0008:8043F6AE 
      DO "dd ebp+c l 10;dd ebp-&gt;10 l 10;dd ebp-&gt;14 l<BR>10" (ET=314.65 
      microseconds)<BR>0010:EBA00CC4 77F90333 C01DFE40 E13C9560 EBA00CFC 
      3..w@...`.&lt;.....<BR>// InValidAddress=77F90333<BR>0010:C01DFE40 
      00F254B0 04E61025 04E13025 04E14025 .T..%...%0..%@..<BR>// 
      Pte=00F254B0<BR>0010:E13C9560 04E80121 04E61121 04E13121 04E14121 
      !...!...!1..!A..<BR>// ProtoPte=04E80121 物理页帧号为 
      04e80<BR><BR>根据ProtoPte中的物理页帧号,在 PFN DataBase 中找该物理页的 
      PfnDataBaseEntry<BR>对于 Win2k Build 2195 来说,PfnDatabase 的首地址在 
      81456000。<BR><BR>:dd 81456000+4e80*18 l 18<BR>0010:814CBC00 00000011 
      E13C9560 0000000A 00010608 ....`.&lt;.........<BR>0010:814CBC10 93E6A478 
      00001D79 0000003C E13C95DC x...y...&lt;.....&lt;.<BR><BR>可以看到 
      PfnDataBaseEntry 的 /*04*/ pteaddress 值为 E13C9560 ,正是 ProtoPte 
      的地址。<BR>共享计数/*08*/ share count为 0000000A,说明正在有10个进程使用这个共享物理页。/*0D*/ byte 
      page state 为 06 ,说明物理页处于 Active (Valid) 状态。<BR><BR>:query 
      77f90333<BR>Context Address Range Flags MMCI PTE Name<BR>Explorer 
      77F80000-77FF8000 07100003 813E6A08 E13C9520 ntdll.dll<BR><BR>... // 
      我们从断点处开始继续执行<BR>MiCompleteProtoPteFault() // 看到执行了 
      MiCompleteProtoPteFault()<BR>... // 直到 MiResolveProtoPteFault() 
      返回处。<BR><BR>:dd c01dfe40 l 10<BR>0010:C01DFE40 04E80025 04E61025 04E13025 
      04E14025 %...%...%0..%@..<BR>// 
      可以看到Pte已经指向了ProtoPte中指向的物理页,而且Pte有效了。<BR>:dd e13c9560 l 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -