《Undocumented Windows 2000 Secrets》翻译 --- 第四章(2)
第四章 探索 Windows 2000 的内存管理机制
翻译: Kendiv( fcczj@263.net )
更新: Sunday, February 14, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
数据结构
本章随后的示例代码的某些部分将涉及底层的内存管理机制,在前面我们已快速浏览了该机制内部的大致轮廓。为了方便,我用 C 语言定义了几个数据结构。这是因为 i386 CPU 内部的很多数据项需要使用一个二进制位或一组二进制位,而 C 的位域( bit-fIElds )唾手可得。位域可以很有效的访问一个大的数据中的一个位或从中提取一组连续的位。微软的 Visual C/C++ 可以产生非常棒的代码来完成位域的操作。 列表 4-2 是一系列 CPU 数据类型定义的一部分,该列表包含如下的内容:
l X86_REGISTER 这是一个基本的无符号 32 位整数类型,该类型可描述多个 CPU 寄存器。这包括:通用的、索引、指针、控制、调试和测试寄存器。
l X86_SELECTOR 代表一个 16 位的段选择器,如 CS 、 DS 、 ES 、 FS 、 GS 和 SS 。在 图 4-1 和 图 4-2 中,选择器可描述 48 位逻辑地址的高 8 位,或作为描述符表的索引。为了计算的方便, 16 位选择器的值被扩展到 32 位,不过高 16 位被标识为“保留”。注意, X86_SELECTOR 结构实际是两个结构的联合( union )。第一个指定了选择器的值,该值占用一个 16 位的 Word ,其名字为 wValue ,第二个采用了位域。 RPL 域指定了请求的特权级,在 Windows 2000 上其值或者为 0 (内核模式)或者为 3 (用户模式)。 TI 位用来选择 GDT 或 LDT 。
l X86_DESCRIPTOR 定义了由选择器指向的页表项的格式。这是一个 64 位的数值,由于历史演化,该结构比较让人费解。线性基地址定义了与其相关的段的起始位置,它们分散在三个位域中: Base1 、 Base2 和 Base3 , Base1 是作用最小的部分。段的界限指定了段的大小, The segment limit specifying the segment size minus one is divided into the pair Limitl and Limit2, with the former representing the least significant half. 剩余的位域存放不同的段属性( cf. Intel 1999c, pp.3-11 )。例如, G 位域定义了段的粒度。如果为零,段的限制按字节指定;否则,限制值为 4KB 的倍数。像 X86_SELECTOR 一样, X86_DESCRIPTOR 结构由一个 union 组成,以允许按不同的方式解释它的值。如果你必须复制描述符(在忽略其内部情况下)那么 dValueLow 和 dValueHigh 成员将会很有帮助。
l X86_GATE 该结构看起来有些像 X86_DESCRIPTOR 。事实上,这两个结构是相关的: X86_DESCRIPTRO 是一个 GDT 项,并描述了一个段的内存属性, X86_GATE 代表中断描述符表( IDT )中的一项,并描述了中断例程的内存属性。 IDT 可以包含任务、中断和陷阱门(不! Bill Gates 并没有存储在 IDT 中! 哈哈)。 X86_GATE 结构可匹配上述三种类型,并通过 Type 位域来进行区分。 Type 5 表示这是一个任务门; Type 6 和 14 为中断门; Type 7 和 15 为陷阱门。 Type 中最重要的位是用来描述门的位数的位:该位若为 0 则表示是 16 位门;其余情况表示 32 位门。
l X86_TABLE 是一个巧妙的结构,该结构用来读取 GDTR 或 IDTR 的当前值,分别通过汇编指令 SGDT (存储 GDT 寄存器)和 SIDT (存储 IDT 寄存器)来实现( cf. Intel 1999b, pp.3-636 )。这两个指令需要一个 48 位的内存操作数,在该操作数中存放限制值和基地址值。通过在结构体中增加一个 DWORD 来对齐 32 位的基地址, X86_TABLE 以一个 16 位的哑元成员 wReserved 开始。根据是否使用了 SGDT 或 SIDT 指令,其基地址将被解释为一个描述符指针或一个门指针,就像 PX86_DESCRIPTOR 和 PX86_GATE 中的 union 所暗示的那样。最后的 wLimit 成员在这两种类型的表中的意义均相同。
译注:
列表 4-2 中的这些结构定义可以在随书光盘的 srccommonincludew2k_spy.h 中找到。
typedef DWORD X86_REGISTER, *PX86_REGISTER, **PPX86_REGISTER;
// -----------------------------------------------------------------
typedef struct _X86_SELECTOR
{
union
{
struct
{
WORD wValue; // packed value
WORD wReserved;
};
struct
{
unsigned RPL : 2; // requested privilege level
unsigned TI : 1; // table indicator: 0=gdt, 1=ldt
unsigned Index : 13; // index into descriptor table
unsigned Reserved : 16;
};
};
}
X86_SELECTOR, *PX86_SELECTOR, **PPX86_SELECTOR;
#define X86_SELECTOR_ sizeof (X86_SELECTOR)
// -----------------------------------------------------------------
typedef struct _X86_DESCRIPTOR
{
union
{
struct
{
DWORD dValueLow; // packed value
DWORD dValueHigh;
};
struct
{
unsigned Limit1 : 16; // bits 15..00
unsigned Base1 : 16; // bits 15..00
unsigned Base2 : 8; // bits 23..16
unsigned Type : 4; // segment type
unsigned S : 1; // type (0=system, 1=code/data)
unsigned DPL : 2; // descriptor privilege level
unsigned P : 1; // segment present
unsigned Limit2 : 4; // bits 19..16
unsigned AVL : 1; // available to programmer
unsigned Reserved : 1;
unsigned DB : 1; // 0=16-bit, 1=32-bit
unsigned G : 1; // granularity (1=4KB)
unsigned Base3 : 8; // bits 31..24
};
};
}
X86_DESCRIPTOR, *PX86_DESCRIPTOR, **PPX86_DESCRIPTOR;
#define X86_DESCRIPTOR_ sizeof (X86_DESCRIPTOR)
// -----------------------------------------------------------------
typedef struct _X86_GATE
{
union
{
struct
{
DWORD dValueLow; // packed value
DWORD dValueHigh;
};
struct
{
unsigned Offset1 : 16; // bits 15..00
unsigned Selector : 16; // segment selector
unsigned Parameters : 5; // parameters
unsigned Reserved : 3;
unsigned Type : 4; // gate type and size
unsigned S : 1; // always 0
unsigned DPL : 2; // descriptor privilege level
unsigned P : 1; // segment present
unsigned Offset2 : 16; // bits 31..16
};
};
}
X86_GATE, *PX86_GATE, **PPX86_GATE;
#define X86_GATE_ sizeof (X86_GATE)
// -----------------------------------------------------------------
typedef struct _X86_TABLE
{
WORD wReserved; // force 32-bit alignment
WORD wLimit; // table limit
union
{
PX86_DESCRIPTOR pDescriptors; // used by sgdt instruction
PX86_GATE pGates; // used by sidt instruction
};
}
X86_TABLE, *PX86_TABLE, **PPX86_TABLE;
#define X86_TABLE_ sizeof (X86_TABLE)
列表 4-2. i386 的寄存器、选择器、描述符、门和表
接下来的一组与 i386 内存管理相关的结构,它们收录在 列表 4-3 中,这些结构包括:与请求式分页相关的结构和 图 4-3 和 图 4-4 给出的几个成员。
l X86_PDBR 该结构对应 CPU 的 CR3 寄存器,即众所周知的页目录基地址寄存器( PDBR )。其高 20 位为 PFN ,即 4KB 物理页数组的索引。 PFN=0 对应物理地址 0x00000000 , PFN=1 为 0x00001000 ,依此类推。 20 个位足够转换整个 4GB 地址空间。 PDBR 中的 PFN 是物理页的索引,用来控制整个页目录。 PFN 中剩余的位大多数都被保留,但 3 号位例外,它用来控制页一级的 write-through ( page-level write-through, PWT ), 4 号位如果为 1 ,则禁止页一级的高速缓冲。
l X86_PDE_4M 和 X86_PDE_4K 是页目录项( PDE )的两个可选方案,用来选择 4MB 页或者 4KB 的页。一个页目录中最多包含 1024 个 PDE 。 PFN 是页帧号,它指向下一级的页。对于一个 4MB 的 PDE ,其 PFN 位域仅有 10 个位的宽度,可寻址一个 4MB 的数据页。 4KB 的 PDE 拥有 20 位的 PFN ,可指向一个页表,由页表最终选择一个数据页。剩余的位用来定义多种属性。这些属性中最有趣的是“页大小”位 PS ,用于控制页的大小( 0=4KB , 1=4MB )和“存在”位 P ,标识下属的数据页( 4MB 模式)或页表( 4KB 模式)是否存在于物理内存中。
X86_PTE_4K 定义了页表项(属于一个页表)的内部结构。和页目录类似,一个页表可拥有 1024 个项。 X86_PTE_4K 和 X86_PDE_4K 的不同之处为:前者没有 PS 位,这根本不需要,因为页的大小肯定是 4KB 。需要注意的是,没有所谓的 4MB 的 PTE ,因为采用 4MB 页的内存模式不需要页表这一中间层。
X86_PNPE 代表一个“不存在的页”项( page-not-present entry, PNPE ),也就是说,一个 PDE 或 PTE 中的 P 位为 0 。如 Intel 的手册所说的,保留的第 31 位是“对操作系统或执行体( executive )均可用”( Intel 1999c,pp. 3-28 )。如果一个线性地址映射到了一个 PNPE ,这意味着这个地址或者还未使用或者它所指向的页已经被置换到了页面文件中。 Windows 2000 使用 PNPE 保留的第 31 位来存储页的信息。有关页信息的结构没有文档记载,不过它类似于名为 PageFile 的第 10 位,如 列表 4-3 所示,如果设置了该位,则表示页已被置换出物理内存。在这种情况下, Reserved1 和 Reserved2 位域将包含系统在页面文件中定位该页的信息,因此,当需要访问该页时,可很快的将其换回物理内存。
X86_PE 该结构是为了方便使用而加入的。它仅包含一个 union ,该 union 包括页项所有可能的状态,此处的页项是指: PDBR 的内容、所有 4MB 和 4KB 的 PDE 、 PTE ,以及所有的 PNPE 。
typedef struct _X86_PDBR // page-Directory base register (cr3)
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned Reserved1 : 3;
unsigned PWT : 1; // page-level write-through
unsigned PCD : 1; // page-level cache disabled
unsigned Reserved2 : 7;
unsigned PFN : 20; // page-frame number
};
};
}
X86_PDBR, *PX86_PDBR, **PPX86_PDBR;
#define X86_PDBR_ sizeof (X86_PDBR)
// -----------------------------------------------------------------
typedef struct _X86_PDE_4M // page-directory entry (4-MB page)
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned P : 1; // present (1 = present)
unsigned RW : 1; // read/write
unsigned US : 1; // user/supervisor
unsigned PWT : 1; // page-level write-through
unsigned PCD : 1; // page-level cache disabled
unsigned A : 1; // accessed
unsigned D : 1; // dirty
unsigned PS : 1; // page size (1 = 4-MB page)
unsigned G : 1; // global page
unsigned Available : 3; // available to programmer
unsigned Reserved : 10;
unsigned PFN : 10; // page-frame number
};
};
}
X86_PDE_4M, *PX86_PDE_4M, **PPX86_PDE_4M;
#define X86_PDE_4M_ sizeof (X86_PDE_4M)
// -----------------------------------------------------------------
typedef struct _X86_PDE_4K // page-directory entry (4-KB page)
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned P : 1; // present (1 = present)
unsigned RW : 1; // read/write
unsigned US : 1; // user/supervisor
unsigned PWT : 1; // page-level write-through
unsigned PCD : 1; // page-level cache disabled
unsigned A : 1; // accessed
unsigned Reserved : 1; // dirty
unsigned PS : 1; // page size (0 = 4-KB page)
unsigned G : 1; // global page
unsigned Available : 3; // available to programmer
unsigned PFN : 20; // page-frame number
};
};
}
X86_PDE_4K, *PX86_PDE_4K, **PPX86_PDE_4K;
#define X86_PDE_4K_ sizeof (X86_PDE_4K)
// -----------------------------------------------------------------
typedef struct _X86_PTE_4K // page-table entry (4-KB page)
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned P : 1; // present (1 = present)
unsigned RW : 1; // read/write
unsigned US : 1; // user/supervisor
unsigned PWT : 1; // page-level write-through
unsigned PCD : 1; // page-level cache disabled
unsigned A : 1; // accessed
unsigned D : 1; // dirty
unsigned Reserved : 1;
unsigned G : 1; // global page
unsigned Available : 3; // available to programmer
unsigned PFN : 20; // page-frame number
};
};
}
X86_PTE_4K, *PX86_PTE_4K, **PPX86_PTE_4K;
#define X86_PTE_4K_ sizeof (X86_PTE_4K)
// -----------------------------------------------------------------
typedef struct _X86_PNPE // page not present entry
{
union
{
struct
{
DWORD dValue; // packed value
};
struct
{
unsigned P : 1; // present (0 = not present)
unsigned Reserved1 : 9;
unsigned PageFile : 1; // page swapped to pagefile
unsigned Reserved2 : 21;
};
};
}
X86_PNPE, *PX86_PNPE, **PPX86_PNPE;
#define X86_PNPE_ sizeof (X86_PNPE)
// -----------------------------------------------------------------
typedef struct _X86_PE // general page entry
{
union
{
DWORD dValue; // packed value
X86_PDBR pdbr; // page-directory Base Register
X86_PDE_4M pde4M; // page-directory entry (4-MB page)
X86_PDE_4K pde4K; // page-directory entry (4-KB page)
X86_PTE_4K pte4K; // page-table entry (4-KB page)
X86_PNPE pnpe; // page not present entry
};
}
X86_PE, *PX86_PE, **PPX86_PE;
#define X86_PE_ sizeof (X86_PE)
列表 4-3. i386 的 PDBR 、 PDE 、 PTE 和 PNPE
在 列表 4-4 中,我增加了线性地址的结构化表示。这些结构是 图 4-3 和 4-4 中的“线性地址”的正式形式。
l X86_LINEAR_4M 该结构是指向 4MB 数据页的线性地址的正式形式,如 图 4-4 所示。页目录索引( PDI )是一个页目录的索引,页目录地址由 PDBR 给出,使用 PDI 可选择页目录中的一个 PDE 。 22 位的 Offset 成员指向一个目标地址,此目标地址对应 4MB 的物理页。
l X86_LINEAR_4K 是一个 4KB 线性地址类型的变量,如 图 4-3 所示。该结构由三个位域组成:和 4MB 地址类似,高 10 位为 PDI ,用来选择一个 PDE ;页表索引 PTI 的任务与 PDI 相似,指向由 PDE (该 PDE 由前面的 PDI 指定)确定的页表中的一个 PTE ;剩余的 12 个位是在 4KB 物理页中的偏移量。
l X86_LINEAR 是另一个为使用方便而加入的结构。该结构只是简单的将 X86_LINEAR_4K 和 X86_LINEAR_4M 联合为一个数据类型。详见 列表 4-4 。
typedef struct _X86_LINEAR_4M // linear address (4-MB page)
{
union
{
struct
{
PVOID pAddress; // packed address
};
struct
{
unsigned Offset : 22; // offset into page
unsigned PDI : 10; // page-directory index
};
};
}
X86_LINEAR_4M, *PX86_LINEAR_4M, **PPX86_LINEAR_4M;
#define X86_LINEAR_4M_ sizeof (X86_LINEAR_4M)
// -----------------------------------------------------------------
typedef struct _X86_LINEAR_4K // linear address (4-KB page)
{
union
{
struct
{
PVOID pAddress; // packed address
};
struct
{
unsigned Offset : 12; // offset into page
unsigned PTI : 10; // page-table index
unsigned PDI : 10; // page-directory index
};
};
}
X86_LINEAR_4K, *PX86_LINEAR_4K, **PPX86_LINEAR_4K;
#define X86_LINEAR_4K_ sizeof (X86_LINEAR_4K)
// -----------------------------------------------------------------
typedef struct _X86_LINEAR // general linear address
{
union
{
PVOID pAddress; // packed address
X86_LINEAR_4M linear4M; // linear address (4-MB page)
X86_LINEAR_4K linear4K; // linear address (4-KB page)
};
}
X86_LINEAR, *PX86_LINEAR, **PPX86_LINEAR;
#define X86_LINEAR_ sizeof (X86_LINEAR)
列表 4-4. i386 的线性地址
宏和常量
列表 4-5 给出的定义是对 列表 4-2 到 列表 4-4 所示结构的补充,让我们可以更容易的和 i386 内存管理一起工作。 列表 4-5 的定义可以分为三大组。第一组用于控制线性地址:
1. X86_PAGE_MASK 、 X86_PDI_MASK 和 X86_PTI_MASK 都是位掩码( bit mask ),用来选择线性地址中的某一部分。它们都基于常量: PAGE_SHIFT (12) 、 PDI-SHIFT (22) 和 PTI-SHIFT (12) ,这些常量定义于 Windows 2000 DDK 的头文件 ntddk.h 中。 X86_PAGE_MASK 等价于 0xFFFFF000 ,可有效的屏蔽 4KB 线性地址( X86_LINEAR_4K )中的偏移量部分。 X86_PDI_MASK 等价于 0xFFC00000 ,显然这可从线性地址中提取高 10 位的 PDI 。 X86_PTI_MASK 等价于 0x003FF0000 ,用于屏蔽线性地址中除 PTI 外的所有位。
2. X86_PAGE() 、 X86_PDI() 和 X86_PTI() 使用上面的常量来计算给定线性地址的页索引、 PDI 和 PTI 。 X86_PAGE() 一般用来从 Windows 2000 的 PTE 数组(该数组首地址为: 0xC0000000 )中读取一个 PTE 。 X86_PDI() 和 X86_PTI() 只是针对给定的指针,简单的使用 X86_PDI_MASK 或 X86_PTI_MASK ,并将得到的索引移动到最右边。
3. X86_OFFSET_4M() 和 X86_OFFSET_4K() 分别从 4MB 或 4KB 线性地址中提取偏移量部分。
4. X86_PAGE_4M 和 X86_PAGE_4K 根据 DDK 中的常量 PDI_SHIFT 和 PTI_SHIFT 来计算 4MB 和 4KB 页的大小。 X86_PAGE_4M=4,194,304 , X86_PAGE_4K=4,096 。注意, X86_PAGE_4K 等价于 DDK 常量 PAGE_SIZE ,该常量也定义于 ntddk.h 中。
5. X86_PAGES_4M 和 X86_PAGES_4K 分别表示 4GB 地址空间中可容纳的 4MB 或 4KB 页的总数。 X86_PAGES_4M 等价于 1,024 , X86_PAGES_4K 等价于 1,048,576 。
#define X86_PAGE_MASK (0 - (1 << PAGE_SHIFT))
#define X86_PAGE(_p) (((DWORD) (_p) & X86_PAGE_MASK) >> PAGE_SHIFT)
#define X86_PDI_MASK (0 - (1 << PDI_SHIFT))
#define X86_PDI(_p) (((DWORD) (_p) & X86_PDI_MASK) >> PDI_SHIFT)
#define X86_PTI_MASK ((0 - (1 << PTI_SHIFT)) & ~X86_PDI_MASK)
#define X86_PTI(_p) (((DWORD) (_p) & X86_PTI_MASK) >> PTI_SHIFT)
#define X86_OFFSET(_p,_m) ((DWORD_PTR) (_p) & ~(_m))
#define X86_OFFSET_4M(_p) X86_OFFSET (_p, X86_PDI_MASK)
#define X86_OFFSET_4K(_p) X86_OFFSET (_p, X86_PDI_MASK|X86_PTI_MASK)
#define X86_PAGE_4M (1 << PDI_SHIFT)
#define X86_PAGE_4K (1 << PTI_SHIFT)
#define X86_PAGES_4M (1 << (32 - PDI_SHIFT))
#define X86_PAGES_4K (1 << (32 - PTI_SHIFT))
// -----------------------------------------------------------------
#define X86_PAGES 0xC0000000
#define X86_PTE_ARRAY ((PX86_PE) X86_PAGES)
#define X86_PDE_ARRAY (X86_PTE_ARRAY + (X86_PAGES >> PTI_SHIFT))
// -----------------------------------------------------------------
#define X86_SEGMENT_OTHER 0
#define X86_SEGMENT_CS 1
#define X86_SEGMENT_DS 2
#define X86_SEGMENT_ES 3
#define X86_SEGMENT_FS 4
#define X86_SEGMENT_GS 5
#define X86_SEGMENT_SS 6
#define X86_SEGMENT_TSS 7
// -----------------------------------------------------------------
#define X86_SELECTOR_RPL 0x0003
#define X86_SELECTOR_TI 0x0004
#define X86_SELECTOR_INDEX 0xFFF8
#define X86_SELECTOR_SHIFT 3
#define X86_SELECTOR_LIMIT (X86_SELECTOR_INDEX >>
X86_SELECTOR_SHIFT)
// -----------------------------------------------------------------
#define X86_DESCRIPTOR_SYS_TSS16A 0x1
#define X86_DESCRIPTOR_SYS_LDT 0x2
#define X86_DESCRIPTOR_SYS_TSS16B 0x3
#define X86_DESCRIPTOR_SYS_CALL16 0x4
#define X86_DESCRIPTOR_SYS_TASK 0x5
#define X86_DESCRIPTOR_SYS_INT16 0x6
#define X86_DESCRIPTOR_SYS_TRAP16 0x7
#define X86_DESCRIPTOR_SYS_TSS32A 0x9
#define X86_DESCRIPTOR_SYS_TSS32B 0xB
#define X86_DESCRIPTOR_SYS_CALL32 0xC
#define X86_DESCRIPTOR_SYS_INT32 0xE
#define X86_DESCRIPTOR_SYS_TRAP32 0xF
// -----------------------------------------------------------------
#define X86_DESCRIPTOR_APP_ACCESSED 0x1
#define X86_DESCRIPTOR_APP_READ_WRITE 0x2
#define X86_DESCRIPTOR_APP_EXECUTE_READ 0x2
#define X86_DESCRIPTOR_APP_EXPAND_DOWN 0x4
#define X86_DESCRIPTOR_APP_CONFORMING 0x4
#define X86_DESCRIPTOR_APP_CODE 0x8
列表 4-5. 附加的 i386 内存管理相关定义
第二组宏和常量与 Windows 2000 的 PDE 、 PTE 数组有关。和其他几个系统地址不同,这些数组的基地址并没有在系统启动时作为一个全局变量出现,而是被定义成了一个常量。可以通过反编译内存管理 API 函数: MmGetPhysicalAddress() 和 MmIsAddressValid() 来证明,在这些函数里,这些地址都以“魔术数字”的形式出现。这些常量并没有包括在 DDK 头文件中,不过 列表 4-5 展示了如何定义它们。
l X86_PAGES 是一个硬编码的地址和指针(指向 0xC0000000 ), 0xC0000000 是 Windows 2000 的 PTE 数组开始的地方。
X86_PTE_ARRAY 等价于 X86_PAGES ,但是被转型为 PX86_PE ,也就是说,指向一个 X86_PE 类型的数组, X86_PE 定义于列表 4-2 。
X86_PDE_ARRAY 是一个巧妙的定义,它通过 PTE 数组的位置来计算 PDE 数组的基地址,这需要用到 PTI_SHIFT 常量。将线性地址映射为 PTE 地址的通用格式为:(( LinearAdress >> 12 ) *4 ) +0xC0000000 ,线性地址 0xC0000000 转换后的地址为页目录的基地址。
列表 4-5 的最后两部分包括选择器和特殊类型的描述符,以及对 列表 4-2 的补充。
l X86_SELECTOR_RPL 、 X86_SELECTOR_TI 和 X86_SELECTOR_INDEX 都是位掩码,分别对应 X86_SELECTOR 结构中的 RPL 、 TI 和 Index 成员。
l X86_SELECTOR_SHIFT 是一个右移因子,用来使选择器的 Index 的数值向右对齐。
l X86_SELECTOR_LIMIT 定义了选择器可使用的最大索引值,该限制为 8,191 。这个值确定了描述符表的最大尺寸。每个选择器索引均指向一个描述符,每个描述符包含 64 个位(即 8 个字节)。所以,描述符表的最大尺寸为: 8,192*8=64KB 。
l X86_DESCRIPTOR_SYS_* 是一组常量,用于定义系统描述符类型。如果描述符的 S 位被设为 0 ,那么描述的 Type 成员将采用这一组类型中的某一个。请参考 列表 4-2 中的 X86_DESCRIPTOR 的定义。系统描述符类型在 Intel 手册中有详细介绍( Intel 1999c, pp. 3-15f ), 表 4-1 给出了所有可用的系统描述符类型。
列表 4-5 中的 X86_DESCRIPTOR_APP_* 常量也可用于定义描述符的 Type 成员,前提是描述符的 S 位不为 0 。此时,该应用程序描述符可能需要引用一个代码或数据段。因为应用程序描述符类型的属性受 Type 域的第四个位影响,所以 X86_DESCRIPTOR_APP_* 常量被定义为单位掩码( single-bit mask ),这样一些位就可针对数据和代码段有不同的解释。
l X86_DESCRIPTOR_APP_ACCESSED 如果一个段可以被访问,则采用
l X86_DESCRIPTOR_APP_READ_WRITE 决定一个数据段是否允许只读或读 / 写访问。
l X86_DESCRIPTOR_APP_CONFORMATING 说明一个代码段是否相匹配。也就是说,它是否可以被以被弱特权代码( less privileged code )调用(参考 Intel 1999c,pp. 4-13ff )。
l X86_DESCRIPTOR_APP_CODE 用来区别代码段和数据段。注意,堆栈属于数据段的范畴,而且必须总是可写的。
稍后,当下一章中的 Memory Spy 程序开始运行时,我们将重温系统描述符。 表 4-1 算是 i386 内存管理的一个简短总结。有关本话题的更多内容,请参考 Intel Pentium 手册( Intel 1999a , 1999b , 1999c )。
表 4-1. 系统描述符类型
名 称
值
描 述
X86_DESCRIPTOR_SYS_TSS16A
0x1
16 位任务状态段(可用)
X86_DESCRIPTOR_SYS_LDT
0x2
本地描述符表( LDT )
X86_DESCRIPTOR_SYS_TSS16B
0x3
16 位任务状态段(繁忙)
X86_DESCRIPTOR_SYS_CALL16
0x4
16 位调用门
X86_DESCRIPTOR_SYS_TASK
0x5
任务门
X86_DESCRIPTOR_SYS_INT16
0x6
16 位中断门
X86_DESCRIPTOR_SYS_TRAP16
0x7
16 位陷阱门
X86_DESCRIPTOR_SYS_TSS32A
0x9
32 位任务状态段(可用)
X86_DESCRIPTOR_SYS_TSS32B
0xB
32 位任务状态段(繁忙)
X86_DESCRIPTOR_SYS_CALL32
0xC
32 位调用门
X86_DESCRIPTOR_SYS_INT32
0xE
32 位中断门
X86_DESCRIPTOR_SYS_TRAP32
0XF
32 位陷阱门