《Undocumented Windows 2000 Secrets》翻译 --- 第四章(9)

来源:互联网 发布:上睑松弛知乎 编辑:程序博客网 时间:2024/05/10 12:16

第四章  探索Windows 2000的内存管理机制

翻译:Kendiv( fcczj@263.net )

更新:Tuesday, February 22, 2005

 

声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。

 

Windows 2000的分段和描述符

w2k_mem.exe的另一个很棒的选项是+e,该选项将显示和说明处理器的段寄存器和描述表的内容。示列4-13给出了其典型输出。CSDSES段寄存器的内容非常清晰的证明了Windows 2000为每个进程提供了平坦的4GB地址空间:起始于0x00000000,终止于0xFFFFFFFF示列4-13中最右边的标志符用来表示段的类型,该段的类型由它的描述符的Type成员给出。代码和数据段的Type属性可分别符号化为“cra”和“ewa”。省略号“-”意味着相应的属性没有设置。一个任务状态段(Task State SegmentTSS)仅能有“a”(可用)和“b”(忙)两种属性。4-5给出了所有可用的属性。示列4-13展示了Windows 2000CS段的不一致性,CS段允许执行和读取,而DSESFSSS段的属性则是可扩展和读/写访问。另一个不明显但十分重要的细节是CSFSSS段的DPL在用户模式和内核模式并不相同。DPL是描述符特权级别(Descriptor Privilege Level)。对于代码段(CS),仅当调用者位于其DPL指定的特权级时才能调用该段中的代码(参考Intel 1999c, pp. 4-8f)。在用户模式,CS段的DPL3;在内核模式,其DPL0。对于数据段(DS),其DPL是最低的特权级,在用户模式下,所有特权级都可访问它,而在内核模式下,仅允许特权0访问。

 

示列4-13.  显示CPU信息

 

IDTGDT寄存器的内容显示了GDT的范围是:0x8003F000 --- 0x8003F3FF,紧随其后的就是IDT,其地址范围是:0x8003F400 --- 0x8003FBFF。由于每个描述符占用64位,故GDTIDT分别包含128256个项。注意,GDT可容纳8,192个项,但Windows 2000仅使用了其中的一小部分。

 

4-5   代码和数据段的Type属性

 

         

CODE

c

使段一致(低特权的代码可能进入)

CODE

r

允许读访问(和仅执行访问相斥)

CODE

a

段可以访问

DATA

e

向下扩展段(堆栈段的典型属性)

DATA

w

允许写访问(和仅读取访问相斥)

DATA

a

段可以访问

TSS32

a

任务状态段可用

TSS32

b

任务状态段繁忙

 

 

W2k_mem.exe还提供了两个很有特色的选项----+g+i,这两个选项可显示GDTIDT的更多细节。示列4-14示范了+g选项的输出。它很类似于示列4-13中的“kernel-model segment:”一节,但列出了在内核模式下所有可用的段选择子(selector),而不仅仅是存储在段寄存器中的那些。W2k_mem.exe通过遍历整个GDT来获取所有的段选择子,可通过IOCTL函数SPY_IO_SEGMENT来指示Spy设备查询段信息。仅显示有效的选择子。比较示列4-134-14中的GDT选择子将十分有趣,GDT的选择子定义于ntddk.h中,汇总在4-6。显然,它们与w2k_mem.exe的输出是一致的。

 

示列4-14.   显示GDT描述符

 

4-6.  定义于ntddk.h中的GDT选择子(selector

   

        

KGDT_NULL

0x0000

空的段选择子(无效)

KGDT_R0_CODE

0x0008

内核模式的CS寄存器

KGDT_R0_DATA

0x0010

内核模式的SS寄存器

KGDT_R3_CODE

0x0018

用户模式的CS寄存器

KGDT_R3_DATA

0x0020

用户模式的DSESSS寄存器,内核模式的DSES寄存器

KGDT_TSS

0x0028

位于用户和内核的任务状态段

KGDT_R0_PCR

0x0030

内核模式的FS寄存器(处理器控制区域)

KGDT_R3_TEB

0x0038

用户模式的FS寄存器(线程环境块)

KGDT_VDM_TILE

0x0040

基地址0x00000400,限制0x0000FFFFDOS虚拟机)

KGDT_LDT

0x0048

本地描述符表

KGDT_DF_TSS

0x0050

Ntoskrnl.exe 变量 KiDoubleFaultTSS

KGDT_NMI_TSS

0x0058

Ntoskrnl.exe 变量 KiNMITSS

 

示列4-14中的选择子(selector)没有在4-6中列出,其中的某些选择子可以通过查找熟悉的基地址或其内存内容来确认它们。使用内核调试器可查找其中某些选择子的基地址对应的符号。4-7给出了我已经确认的选择子。

 

W2k_mem.exe+i选项可转储IDT中的门描述符(Gate Descriptor)。示列4-15给出了IDT的门描述符的部分内容,Intel仅定义了IDT中的前20个门描述符(Intel 1999c, pp. 5-6)。IDT中的中断0x140x1FIntel保留;剩余的0x200xFF由操作系统使用。

 

4-8中,我给出了所有可确认的特殊的中断、陷阱和任务门。大多数用户自定义的中断都指向哑元例程---KiUnexpectedinterruptnNNN(),在前面我们已经解释过它。对于某些中断处理例程的地址,内核调试器也无法解析其地址对应的符号。

 

4-7.  更多的GDT选择子(selector

基地址

            

0x0078

0x80400000

Ntoskrnl.exe的代码段

0x0080

0x80400000

Ntoskrnl.exe的数据段

0x00A0

0x814985A8

TSSEIP成员指向HalpMcaExceptionHandlerWrapper

0x00E0

0xF0430000

ROM BIOS代码段

0x00F0

0x8042DCE8

Ntoskrnl.exe函数KiI386CallAbios

0x0100

0xF0440000

ROM BIOS数据段

0x0108

0xF0440000

ROM BIOS数据段

0x0110

0xF0440000

ROM BIOS数据段

 

示列4-15.  显示IDT门描述符

 

4-8.  Windows 2000 中断、陷阱和任务门

INT

Intel定义的描述符

拥有者

处理例程/TSS

0x00

整除错误(DE

ntoskrnl.exe

KiTrap00

0x01

调试(DB

ntoskrnl.exe

KiTrap01

0x02

NMI中断

ntoskrnl.exe

KiNMITSS

0x03

断点(BP

ntoskrnl.exe

KiTrap03

0x04

溢出(OF

ntoskrnl.exe

KiTrap04

0x05

越界(BR

ntoskrnl.exe

KiTrap05

0x06

未定义的操作码(UD

ntoskrnl.exe

KiTrap06

0x07

没有数学协处理器(NM

ntoskrnl.exe

KiTrap07

0x08

Double FaultDF

ntoskrnl.exe

KiDouble

0x09

协处理器段溢出

ntoskrnl.exe

KiTrap09

0x0A

无效的TSSTS

ntoskrnl.exe

KiTrap0A

0x0B

段不存在(NP

ntoskrnl.exe

KiTrap0B

0x0C

堆栈段故障(SS

ntoskrnl.exe

KiTrap0C

0x0D

常规保护(GP

ntoskrnl.exe

KiTrap0D

0x0E

页故障(PF

ntoskrnl.exe

KiTrap0E

0x0F

Intel保留

ntoskrnl.exe

KiTrap0F

0x10

Math FaultMF

ntoskrnl.exe

KiTrap10

0x11

对齐检查(AC

ntoskrnl.exe

KiTrap11

0x12

Machine CheckMC

?

0x13

SIMD扩展

ntoskrnl.exe

KiTrap0F

0x14-0x1F

Intel保留

ntoskrnl.exe

KiTrap0F

0x2A

用户自定义

ntoskrnl.exe

KiGetTickCount

0x2B

用户自定义

ntoskrnl.exe

KiCallbackReturn

0x2C

用户自定义

ntoskrnl.exe

KiSetLowWaitHighThread

0x2D

用户自定义

ntoskrnl.exe

KiDebugSerice

0x2E

用户自定义

ntoskrnl.exe

KiSystemService

0x2F

用户自定义

ntoskrnl.exe

KiTrap0F

0x30

用户自定义

hal.dll

HalpClockInterrupt

0x38

用户自定义

hal.dll

HalpProfileInterrupt

 

 

Windows 2000的内存区域

W2k_mem.exe的最后一个还未讨论的选项是:+b选项。该选项会产生4GB地址空间中相邻内存区域的列表,这个列表非常大。W2k_mem.exe使用Spy设备的IOCTL函数SPY_IO_PAGE_ENTRY遍历整个PTE数组(位于地址0xC0000000)来生成这个列表。在作为结果的每个SPY_PAGE_ENTRY结构中,通过将它们的dSize成员与其对应的PTE线性地址相加即可得到下一个PTE的地址。列表4-30给出了该选项的实现方式。

 

DWORD WINAPI DisplayMemoryBlocks (HANDLE hDevice)

    {

    SPY_PAGE_ENTRY spe;

    PBYTE          pbPage, pbBase;

    DWORD          dBlock, dPresent, dTotal;

    DWORD          n = 0;

 

    pbPage   = 0;

    pbBase   = INVALID_ADDRESS;

    dBlock   = 0;

    dPresent = 0;

    dTotal   = 0;

 

    n += _printf (L"/r/nContiguous memory blocks:"

                  L"/r/n-------------------------/r/n/r/n");

 

    do  {

        if (!IoControl (hDevice, SPY_IO_PAGE_ENTRY,

                        &pbPage, PVOID_,

                        &spe,    SPY_PAGE_ENTRY_))

            {

            n += _printf (L" !!! Device I/O error !!!/r/n");

            break;

            }

        if (spe.fPresent)

            {

            dPresent += spe.dSize;

            }

        if (spe.pe.dValue)

            {

            dTotal += spe.dSize;

 

            if (pbBase == INVALID_ADDRESS)

                {

                n += _printf (L"%5lu : 0x%08lX ->",

                              ++dBlock, pbPage);

 

                pbBase = pbPage;

                }

            }

        else

            {

            if (pbBase != INVALID_ADDRESS)

                {

                n += _printf (L" 0x%08lX (0x%08lX bytes)/r/n",

                              pbPage-1, pbPage-pbBase);

 

                pbBase = INVALID_ADDRESS;

                }

            }

        }

    while (pbPage += spe.dSize);

 

    if (pbBase != INVALID_ADDRESS)

        {

        n += _printf (L"0x%08lX/r/n", pbPage-1);

        }

    n += _printf (L"/r/n"

                  L" Present bytes: 0x%08lX/r/n"

                  L" Total   bytes: 0x%08lX/r/n",

                  dPresent, dTotal);

    return n;

    }

列表4-30.  查找相邻的线性内存块

 

示列4-16摘录了在我的机器上使用+b选项的输出列表,可以看出其中的几个区域非常有趣。一些非常明显的地址是:0x00400000,这是w2k_mem.exe内存映像的起始地址(第13号块),还有一个是0x10000000,此处是w2k_lib.dll的基地址(第23号块)。TEBPEB页也很容易认出(第104号块),hal.dll(第105号块),ntoskrnl.exe(第105号块),win32k.sys(第106号块)。第340---350号块是系统PTE数组的一小段,第347号块是页目录的一部分。第2122号块包含SharedUserData区域,第2123号块由KPCRKPRCB和包含线程和进程状态信息的CONTEXT结构组成。

示列4-16.  相邻内存块列表示列

 

还需要补充一下,W2k_mem.exe+b选项会报告有大量的内存被使用,这可能超出了一个合理的值(比如,你机器上的物理内存数)。请注意示列4-16底部给出的汇总信息。我现在真的使用了700MB的内存吗?Windows 2000的任务管理器显示是150MB,那么这儿的又是什么呢?这种奇特的效果都是由第105号内存块产生的,该内存块表示的范围:0x80000000----0xA01A5FFF占用了0x201A6000字节,也就是说占用了538,599,424字节。这显然是不可能的。问题是整个线性地址空间:0x80000000 ---- 0x9FFFFFFF都被映射到了物理内存:0x00000000 ---- 0x1FFFFFFF,在前面我已经提及过这一点。该区域中的所有4MB页都对应地址0xC0300000处的页目录中的一个有效的PDE,我们可以使用w2k_mem +d #0x200 0xC0300800命令来证明这一点(示列4-17)。因为结果列表中的所有PDE都是奇数(译注:如果PDE为奇数,证明其P位肯定为1),所以它们对应的页都必须存在;不过,它们并不需真正占用物理内存。事实上,这一内存区域的大部分都是“空洞(hole)”,如果将其复制到缓冲区中,可发现它们都被0xFF填充。因此,对于w2k_mem.exe输出的内存使用情况,你不需要过于认真。

 

示列4-17.  地址范围是:0x80000000  --- 0x9FFFFFFFPDE

 

Windows 2000 的内存布局 
   本章的最后一部分将给出在一个Windows 2000进程“看”来,4GB线性地址空间的总体布局是什么样子。4-9给出了多个基本数据结构的内存范围。它们之间的“大洞(big hole)”有不同的用途,如,用于进程模块和设备驱动程序的加载区域,内存池,工作集链表等等。注意,有些内存地址和内存块的大小在不同的系统之间有很大的差异,这取决于物理内存和硬件的配置情况、进程的属性以及其他一些系统变量。因此,这里给出的仅仅是一个草图而已,并不是精确的布局图。

 

有些物理内存块在线性地址空间中出现的两次或更多次。例如,SharedUserData区域位于线性地址0xFFDF0000,并且并镜像到0x7FFE0000。这两个地址都指向物理内存中的同一个页,这意味着,如果向0xFFDF0000+n处写入一个字节,那么0x7FFE0000+n处的值也会随之改变。这是一个虚拟内存的世界----一个物理地址可以被映射到线性地址空间中的任何地方,即使一个物理地址在同一时间映射到多个线性地址也是可以的。回忆一下4-34-4,它们清楚地展示了线性地址的这种“虚假行为”。它们的目录和表位域正确的指向用来确定数据实际位置的结构体。如果两个PTEPFN恰好是相同的,那么它们对应的线性地址将指向物理内存相同位置。

 

4-9.  进程地址空间中的可确认的内存区域

起始地址

结束地址

十六进制大小

类型/描述

0x00000000

0x0000FFFF

10000

底部的受保护块(Lower guard block

0x00010000

0x0001FFFF

10000

WCHAR[]/环境字符串,在一个4KB页中分配

0x00020000

0x0002FFFF

10000

PROCESS_PARAMETERS/在一个4KB页中分配

0x00030000

0x0012FFFF

1000000

DWORD[4000]/进程堆栈(默认;1MB

0x7FFDD000

0x7FFDDFFF

1000

TEB/1#线程的线程环境块

0x7FFDE000

0x7FFDEFFF

1000

TEB/2#线程的线程环境块

0x7FFDF000

0x7FFDFFFF

1000

PEB/进程环境块

0x7FFE0000

0x7FFE02D7

2D8

KUSER_SHARED_DATA/用户模式下的SharedUserData

0x7FFF0000

0x7FFFFFFF

10000

顶部的受保护块(Upper guard block

0x80000000

0x800003FF

400

IVT/中断向量表

0x80036000

0x800363FF

400

KGDTENTRY[80]/全局描述符表

0x80036400

0x80036BFF

800

KIDTENTRY[100]/中断描述符表

0x800C0000

0x800FFFFF

40000

VGA/ROM BIOS

0x80244000

0x802460AA

20AB

KTSS/内核任务状态段(繁忙)

0x8046AB80

0x8046ABBF

40

KeServiceDescriptorTable

0x8046AB

0x8046ABFF

40

KeServiceDescriptorTableShadow

0x80470040

0x804700A7

68

KTSS/KiDoubleFaultTSS

0x804700A8

0x8047010F

68

KTSS/KiNMITSS

0x804704D8

0x804708B7

3E0

PROC[F8]/KiServiceTable

0x804708B8

0x804708BB

4

DWORD/KiServiceLimit

0x804708BC

0x804709B3

F8

BYTE[F8]/KiArgumentTable

0x814C6000

0x82CC5FFF

1800000

PFN[100000]/MmPfnDatabase(最大为4GB

0xA01859F0

0xA01863EB

9FC

PROC[27F]/W32pServiceTable

0xA0186670

0x A01863EE

27F

BYTE[27F]W32pArgumentTable

0xC0000000

0xC03FFFFF

400000

X86_PE[100000]/页目录和页表

0xC1000000

0xE0FFFFFF

20000000

系统缓存(MmSystemCacheStart, MmSystemCacheEnd

0xE1000000

0xE77FFFFF

6800000

页池(Paged Pool)(MmPagedPoolStart, MmPagedPoolEnd

0xF0430000

0xF043FFFF

10000

ROM BIOS代码段

0xF0440000

0xF044FFFF

10000

ROM BIOS数据段

0xFFDF0000

0xFFDF02D7

2D8

KUSER_SHARED_DATA/内核模式下的SharedUserData

0xFFDFF000

0xFFDFF053

54

KPCR/处理器控制区(内核模式FS段)

0xFFDFF120

0xFFDFF13B

1C

KPRCB/处理器控制块

0xFFDFF13C

0xFFDFF407

2CC

CONTEXT/线程CONTEXTCPU状态)

0xFFDFF620

0xFFDFF71F

100

后备链表目录(Lookaside list directories

 

……………….本章完………………

原创粉丝点击