Windows 2000 Memory Internals
Along with the global separation of the 4-GB address space into user-mode and kernel-mode portions, these two halves are subdivided into various smaller blocks. As you might have guessed, most of them contain undocumented structures that serve undocumented purposes. It would be easy to forget about them if they were uninteresting. However, that's not the casesome of them are a real gold mine for anyone developing system diagnosis or debugging software.
Basic Operating System Information
Now the time has come to introduce one of the postponed command line options of the memory spy application w2k_mem.exe. If you take a look at the lower half of the program's help screen in Example 4-1, you will see a section titled "System Status Options." Let's try the option +o, named "display OS information." Example 4-12 shows a sample run on my machine. The data displayed here are the contents of the SPY_OS_INFO structure, defined in Listing 4-13 and set up by the spy device function SpyOutputOsInfo(), also included in Listing 4-13. In Example 4-12, you can already see some characteristic addresses within the 4-GB linear memory space of a process. For example, the valid user address range is reported to be 0x00010000 to 0x7FFEFFFF. You have probably read in other programming books about Windows NT or Windows 2000 that the first and last 64 KB of the user-mode half of linear memory are "no-access regions" that are there to catch wild pointers produced by common programming errors (cf. Solomon 1998, Chapter 5). The output of w2k_mem.exe proves that this is correct.
Example 4-12. Displaying Operating System Information
E:\>w2k_mem +o [...] OS information: --------------- Memory page size : 4096 bytes Memory page shift : 12 bits Memory PTI shift : 12 bits Memory PDI shift : 22 bits Memory page mask : 0xFFFFF000 Memory PTI mask : 0x003FF000 Memory PDI mask : 0xFFC00000 Memory PTE array : 0xC0000000 Memory PDE array : 0xC0300000 Lowest user address : 0x00010000 Thread environment block : 0x7FFDE000 Highest user address : 0x7FFEFFFF User probe address : 0x7FFF0000 System range start : 0x80000000 Lowest system address : 0xC0800000 Shared user data : 0xFFDF0000 Processor control region : 0xFFDFF000 Processor control block : 0xFFDFF120 Global flag : 0x00000000 i386 machine type : 0 Number of processors : 1 Product type : Windows NT Workstation (1) Version & Build number : 5.00.2195 System root : "E:\WINNT" [...]
The last three lines of Example 4-12 contain interesting information about the system, mostly extracted from the SharedUserData area at address 0xFFDF0000. The data structure maintained there by the system is called KUSER_SHARED_DATA and is defined in the DDK header file ntddk.h.
Windows 2000 Segments and Descriptors
Another fine option of w2k_mem.exe is +c, which displays and interprets the contents of the processor's segment registers and descriptor tables. Example 4-13 shows the typical output. The contents of the CS, DS, and ES segment registers clearly demonstrate that Windows 2000 provides each process with a flat 4-GB address space: These basic segments start at offset 0x00000000 and have a limit of 0xFFFFFFFF.
The flag characters in the rightmost column indicate the segment type as defined by its descriptor's Type member. The type attributes of code and data segments are symbolized by combinations of the characters "cra" and "ewa," respectively. A dash means that the corresponding attribute is not set. A Task State Segment (TSS) can have the attributes "a" (available) and "b" (busy) only. All applicable attributes are summarized in Table 4-5. Example 4-13 shows that the Windows 2000 CS segments are nonconforming and allow execute/read access, whereas the DS, ES, FS, and SS segments are of expand-up type and allow read/write access. Another inconspicuous but important detail is the different DPL of the CS, FS, and SS segments in user- and kernel-mode. DPL is the Descriptor Privilege Level. For nonconforming code segments, the DPL specifies the privilege level a caller must be on in order to be able to call into this segment (cf. Intel 1999c, pp. 4-8f). In user-mode, the required level is three; in kernel-mode, it is zero. For data segments, the DPL is the lowest privilege level required to be able to access the segment. This means that the FS and SS segments are accessible from all privilege levels in user-mode, whereas only level-0 accesses are allowed in kernel-mode.
Example 4-13. Displaying CPU Information
E:\>w2k_mem +c [...] CPU information: ---------------- User mode segments: CS : Selector = 001B, Base = 00000000, Limit = FFFFFFFF, DPL3, Type = CODE -ra DS : Selector = 0023, Base = 00000000, Limit = FFFFFFFF, DPL3, Type = DATA -wa ES : Selector = 0023, Base = 00000000, Limit = FFFFFFFF, DPL3, Type = DATA -wa FS : Selector = 0038, Base = 7FFDE000, Limit = 00000FFF, DPL3, Type = DATA -wa SS : Selector = 0023, Base = 00000000, Limit = FFFFFFFF, DPL3, Type = DATA -wa TSS : Selector = 0028, Base = 80244000, Limit = 000020AB, DPL0, Type = TSS32 b Kernel mode segments: CS : Selector = 0008, Base = 00000000, Limit = FFFFFFFF, DPL0, Type = CODE -ra DS : Selector = 0023, Base = 00000000, Limit = FFFFFFFF, DPL3, Type = DATA -wa ES : Selector = 0023, Base = 00000000, Limit = FFFFFFFF, DPL3, Type = DATA -wa FS : Selector = 0030, Base = FFDFF000, Limit = 00001FFF, DPL0, Type = DATA -wa SS : Selector = 0010, Base = 00000000, Limit = FFFFFFFF, DPL0, Type = DATA -wa TSS : Selector = 0028, Base = 80244000, Limit = 000020AB, DPL0, Type = TSS32 b IDT : Limit = 07FF, Base = 80036400 GDT : Limit = 03FF, Base = 80036000 LDT : Selector = 0000 CR0 : Contents = 8001003B CR2 : Contents = 00401050 CR3 : Contents = 06F70000 [...]
The contents of the IDT and GDT registers show that the GDT spans from linear address 0x80036000 to 800363FF, immediately followed by the IDT, occupying the address range 0x80036400 to 0x80036BFF. With each descriptor taking 64 bits, the GDT and IDT contain 128 and 256 entries, respectively. Note that the GDT could comprise as many as 8,192 entries, but Windows 2000 uses only a small fraction of them.
The w2k_mem.exe utility features two more options+g and +ithat display more details about the GDT and IDT. Example 4-14 demonstrates the output of the +g option. It is similar to the "kernel-mode segments:" section of Example 4-13, but lists all segment selectors available in kernel-mode, not just those that are stored in segment registers. w2k_mem.exe compiles this list by looping through the entire GDT,
Table 4-5. Code and Data Segment Type Attributes
SEGMENT |
ATTRIBUTE |
DESCRIPTION |
CODE |
c |
Conforming segment (may be entered by less privileged code) |
CODE |
r |
Read-access allowed (as opposed to execute-only access) |
CODE |
a |
Segment has been accessed |
DATA |
e |
Expand-down segment (typical attribute for stack segments) |
DATA |
w |
Write-access allowed (as opposed to read-only access) |
DATA |
a |
Segment has been accessed |
TSS32 |
a |
Task State Segment is available |
TSS32 |
b |
Task State Segment is busy |
querying the spy device for segment information by means of the IOCTL function SPY_IO_SEGMENT. Only valid selectors are displayed. It is interesting to compare Examples 4-13 and 4-14 with the GDT selector definitions in ntddk.h, summarized in Table 4-6. Obviously, they are in accordance with the details reported by w2k_mem.exe.
Example 4-14. Displaying GDT Descriptors
E:\>w2k_mem +g [...] GDT information: ---------------- 001 : Selector = 0008, Base = 00000000, Limit = FFFFFFFF, DPL0, Type = CODE -ra 002 : Selector = 0010, Base = 00000000, Limit = FFFFFFFF, DPL0, Type = DATA -wa 003 : Selector = 0018, Base = 00000000, Limit = FFFFFFFF, DPL3, Type = CODE -ra 004 : Selector = 0020, Base = 00000000, Limit = FFFFFFFF, DPL3, Type = DATA -wa 005 : Selector = 0028, Base = 80244000, Limit = 000020AB, DPL0, Type = TSS32 b 006 : Selector = 0030, Base = FFDFF000, Limit = 00001FFF, DPL0, Type = DATA -wa 007 : Selector = 0038, Base = 7FFDE000, Limit = 00000FFF, DPL3, Type = DATA -wa 008 : Selector = 0040, Base = 00000400, Limit = 0000FFFF, DPL3, Type = DATA -wa 009 : Selector = 0048, Base = E2E6A000, Limit = 00000177, DPL0, Type = LDT 00A : Selector = 0050, Base = 80470040, Limit = 00000068, DPL0, Type = TSS32 a 00B : Selector = 0058, Base = 804700A8, Limit = 00000068, DPL0, Type = TSS32 a 00C : Selector = 0060, Base = 00022AB0, Limit = 0000FFFF, DPL0, Type = DATA -wa 00D : Selector = 0068, Base = 000B8000, Limit = 00003FFF, DPL0, Type = DATA -w- 00E : Selector = 0070, Base = FFFF7000, Limit = 000003FF, DPL0, Type = DATA -w- 00F : Selector = 0078, Base = 80400000, Limit = 0000FFFF, DPL0, Type = CODE -r- 010 : Selector = 0080, Base = 80400000, Limit = 0000FFFF, DPL0, Type = DATA -w- 011 : Selector = 0088, Base = 00000000, Limit = 00000000, DPL0, Type = DATA -w- 014 : Selector = 00A0, Base = 814985A8, Limit = 00000068, DPL0, Type = TSS32 a 01C : Selector = 00E0, Base = F0430000, Limit = 0000FFFF, DPL0, Type = CODE cra 01D : Selector = 00E8, Base = 00000000, Limit = 0000FFFF, DPL0, Type = DATA -w- 01E : Selector = 00F0, Base = 8042DCE8, Limit = 000003B7, DPL0, Type = CODE - 01F : Selector = 00F8, Base = 00000000, Limit = 0000FFFF, DPL0, Type = DATA -w- 020 : Selector = 0100, Base = F0440000, Limit = 0000FFFF, DPL0, Type = DATA -wa 021 : Selector = 0108, Base = F0440000, Limit = 0000FFFF, DPL0, Type = DATA -wa 022 : Selector = 0110, Base = F0440000, Limit = 0000FFFF, DPL0, Type = DATA wa [...]
Table 4-6. GDT Selectors Defined in ntddk.h
SYMBOL |
VALUE |
COMMENTS |
KGDT_NULL |
0x0000 |
Null segment selector (invalid) |
KGDT_R0_CODE |
0x0008 |
CS register in kernel-mode |
KGDT_R0_DATA |
0x0010 |
SS register in kernel-mode |
KGDT_R3_CODE |
0x0018 |
CS register in user-mode |
KGDT_R3_DATA |
0x0020 |
DS, ES, and SS register in user-mode, DS and ES register in kernel-mode |
KGDT_TSS |
0x0028 |
Task State Segment in user- and kernel-mode |
KGDT_R0_PCR |
0x0030 |
FS register in kernel-mode (Processor Control Region) |
KGDT_R3_TEB |
0x0038 |
FS register in user-mode (Thread Environment Block) |
KGDT_VDM_TILE |
0x0040 |
Base 0x00000400, limit 0x0000FFFF (Virtual DOS Machine) |
KGDT_LDT |
0x0048 |
Local Descriptor Table |
KGDT_DF_TSS |
0x0050 |
ntoskrnl.exe variable KiDoubleFaultTSS |
KGDT_NMI_TSS |
0x0058 |
ntoskrnl.exe variable KiNMITSS |
The selectors in Example 4-14 that are not listed in Table 4-6 can in part be identified by looking for familiar base addresses or memory contents, and by using the Kernel Debugger to look up the symbols for some of the base addresses. Table 4-7 comprises the selectors that I have identified so far.
The +i option of w2k_mem.exe dumps the gate descriptors stored in the IDT. Example 4-15 is an excerpt from this rather long list, comprising only the first 20 entries that have a predefined meaning assigned by Intel (Intel 1999c, pp. 5-6). Interrupts 0x14 to 0x1F are reserved for Intel; the remaining range 0x20 to 0xFF is available to the operating system.
In Table 4-8, I have summarized all interrupts that refer to identifiable and nontrivial interrupt, trap, and task gates. Most of the user defined interrupts point to dummy handlers named KiUnexpectedInterruptNNN(), as explained earlier in this chapter. Some interrupt handlers are located at addresses that can't be resolved to symbols by the Kernel Debugger.
Table 4-7. More GDT Selectors
VALUE |
BASE |
DESCRIPTION |
0x0078 |
0x80400000 |
ntoskrnl.exe code segment |
0x0080 |
0x80400000 |
ntoskrnl.exe data segment |
0x00A0 |
0x814985A8 |
TSS (EIP member points to HalpMcaExceptionHandlerWrapper) |
0x00E0 |
0xF0430000 |
ROM BIOS code segment |
0x00F0 |
0x8042DCE8 |
ntoskrnl.exe function KiI386CallAbios |
0x0100 |
0xF0440000 |
ROM BIOS data segment |
0x0108 |
0xF0440000 |
ROM BIOS data segment |
0x0110 |
0xF0440000 |
ROM BIOS data segment |
Example 4-15. Displaying IDT Gate Descriptors
E:\>w2k_mem +i [...] IDT information: ---------------- 00 : Pointer = 0008:804625E6, Base = 00000000, Limit = FFFFFFFF, Type = INT32 01 : Pointer = 0008:80462736, Base = 00000000, Limit = FFFFFFFF, Type = INT32 02 : TSS = 0058, Base = 804700A8, Limit = 00000068, Type = TASK 03 : Pointer = 0008:80462A0E, Base = 00000000, Limit = FFFFFFFF, Type = INT32 04 : Pointer = 0008:80462B72, Base = 00000000, Limit = FFFFFFFF, Type = INT32 05 : Pointer = 0008:80462CB6, Base = 00000000, Limit = FFFFFFFF, Type = INT32 06 : Pointer = 0008:80462E1A, Base = 00000000, Limit = FFFFFFFF, Type = INT32 07 : Pointer = 0008:80463350, Base = 00000000, Limit = FFFFFFFF, Type = INT32 08 : TSS = 0050, Base = 80470040, Limit = 00000068, Type = TASK 09 : Pointer = 0008:8046370C, Base = 00000000, Limit = FFFFFFFF, Type = INT32 0A : Pointer = 0008:80463814, Base = 00000000, Limit = FFFFFFFF, Type = INT32 0B : Pointer = 0008:80463940, Base = 00000000, Limit = FFFFFFFF, Type = INT32 0C : Pointer = 0008:80463C44, Base = 00000000, Limit = FFFFFFFF, Type = INT32 0D : Pointer = 0008:80463E50, Base = 00000000, Limit = FFFFFFFF, Type = INT32 0E : Pointer = 0008:804648A4, Base = 00000000, Limit = FFFFFFFF, Type = INT32 0F : Pointer = 0008:80464C3F, Base = 00000000, Limit = FFFFFFFF, Type = INT32 10 : Pointer = 0008:80464D47, Base = 00000000, Limit = FFFFFFFF, Type = INT32 11 : Pointer = 0008:80464E6B, Base = 00000000, Limit = FFFFFFFF, Type = INT32 12 : TSS = 00A0, Base = 814985A8, Limit = 00000068, Type = TASK 13 : Pointer = 0008:80464C3F, Base = 00000000, Limit = FFFFFFFF, Type = INT32 [...]
Table 4-8. Windows 2000 Interrupt, Trap, and Task Gates
INT |
INTEL DESCRIPTION |
OWNER |
HANDLER/TSS |
0x00 |
Divide Error (DE) |
ntoskrnl.exe |
KiTrap00 |
0x01 |
Debug (DB) |
ntoskrnl.exe |
KiTrap01 |
0x02 |
NMI Interrupt |
ntoskrnl.exe |
KiNMITSS |
0x03 |
Breakpoint (BP) |
ntoskrnl.exe |
KiTrap03 |
0x04 |
Overflow (OF) |
ntoskrnl.exe |
KiTrap04 |
0x05 |
BOUND Range Exceeded (BR) |
ntoskrnl.exe |
KiTrap05 |
0x06 |
Undefined Opcode (UD) |
ntoskrnl.exe |
KiTrap06 |
0x07 |
No Math Coprocessor (NM) |
ntoskrnl.exe |
KiTrap07 |
0x08 |
Double Fault (DF) |
ntoskrnl.exe |
KiDouble |
0x09 |
Coprocessor Segment Overrun |
ntoskrnl.exe |
KiTrap09 |
0x0A |
Invalid TSS (TS) |
ntoskrnl.exe |
KiTrap0A |
0x0B |
Segment Not Present (NP) |
ntoskrnl.exe |
KiTrap0B |
0x0C |
Stack-Segment Fault (SS) |
ntoskrnl.exe |
KiTrap0C |
0x0D |
General Protection (GP) |
ntoskrnl.exe |
KiTrap0D |
0x0E |
Page Fault (PF) |
ntoskrnl.exe |
KiTrap0E |
0x0F |
(Intel reserved) |
ntoskrnl.exe |
KiTrap0F |
0x10 |
Math Fault (MF) |
ntoskrnl.exe |
KiTrap10 |
0x11 |
Alignment Check (AC) |
ntoskrnl.exe |
KiTrap11 |
0x12 |
Machine Check (MC) |
? |
? |
0x13 |
Streaming SIMD Extensions |
ntoskrnl.exe |
KiTrap0F |
0x14-0x1F |
(Intel reserved) |
ntoskrnl.exe |
KiTrap0F |
0x2A |
User Defined |
ntoskrnl.exe |
KiGetTickCount |
0x2B |
User Defined |
ntoskrnl.exe |
KiCallbackReturn |
0x2C |
User Defined |
ntoskrnl.exe |
KiSetLowWaitHighThread |
0x2D |
User Defined |
ntoskrnl.exe |
KiDebugService |
0x2E |
User Defined |
ntoskrnl.exe |
KiSystemService |
0x2F |
User Defined |
ntoskrnl.exe |
KiTrap0F |
0x30 |
User Defined |
hal.dll |
HalpClockInterrupt |
0x38 |
User Defined |
hal.dll |
HalpProfileInterrupt |
Windows 2000 Memory Areas
The last w2k_mem.exe option that remains to be discussed is the +b switch. It generates an enormously long list of contiguous memory regions within the 4-GB linear address space. w2k_mem.exe builds this list by walking through the entire PTE array at address 0xC0000000, using the spy device's IOCTL function SPY_IO_PAGE_ENTRY. The dSize member contained in each resulting SPY_PAGE_ENTRY structure is added to the linear address associated with the PTE to get the linear address of the next PTE to be retrieved. Listing 4-30 shows the implementation of this option.
Listing 4-30. Finding Contiguous Linear Memory Blocks
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.Size); 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; }
Example 4-16 is an excerpt from a sample run on my machine, showing some of the more interesting regions. Some very obvious addresses are 0x00400000, where the image of w2k_mem.exe starts (block #13), and 0x10000000, where the image of w2k_lib.dll is located (block #23). The TEB and PEB pages also are clearly discernible (block #104), as are the hal.dll, ntoskrnl.exe, and win32k.sys areas (blocks #105 and 106). Blocks #340 to 350 are, of course, the valid fragments of the system's PTE array, featuring the page-directory as part of block #347. Block #2122 contains the SharedUserData area, and #2123 comprises the KPCR, KPRCB, and CONTEXT structures containing thread and processor status information.
Example 4-16. A Sample List of Contiguous Memory Blocks
E:\>w2k_mem +b [...] Contiguous memory blocks: ------------------------- 1 : 0x00010000 -> 0x00010FFF (0x00001000 bytes) 2 : 0x00020000 -> 0x00020FFF (0x00001000 bytes) 3 : 0x0012D000 -> 0x00138FFF (0x0000C000 bytes) 4 : 0x00230000 -> 0x00230FFF (0x00001000 bytes) 5 : 0x00240000 -> 0x00241FFF (0x00002000 bytes) 6 : 0x00247000 -> 0x00247FFF (0x00001000 bytes) 7 : 0x0024F000 -> 0x00250FFF (0x00002000 bytes) 8 : 0x00260000 -> 0x00260FFF (0x00001000 bytes) 9 : 0x00290000 -> 0x00290FFF (0x00001000 bytes) 10 : 0x002E0000 -> 0x002E0FFF (0x00001000 bytes) 11 : 0x002E2000 -> 0x002E3FFF (0x00002000 bytes) 12 : 0x003B0000 -> 0x003B1FFF (0x00002000 bytes) 13 : 0x00400000 -> 0x00404FFF (0x00005000 bytes) 14 : 0x00406000 -> 0x00406FFF (0x00001000 bytes) 15 : 0x00410000 -> 0x00410FFF (0x00001000 bytes) 16 : 0x00419000 -> 0x00419FFF (0x00001000 bytes) 17 : 0x0041B000 -> 0x0041BFFF (0x00001000 bytes) 18 : 0x00450000 -> 0x00450FFF (0x00001000 bytes) 19 : 0x00760000 -> 0x00760FFF (0x00001000 bytes) 20 : 0x00770000 -> 0x00770FFF (0x00001000 bytes) 21 : 0x00780000 -> 0x00783FFF (0x00004000 bytes) 22 : 0x00790000 -> 0x00791FFF (0x00002000 bytes) 23 : 0x10000000 -> 0x10003FFF (0x00004000 bytes) 24 : 0x10005000 -> 0x10005FFF (0x00001000 bytes) 25 : 0x1000E000 -> 0x10016FFF (0x00009000 bytes) 26 : 0x759B0000 -> 0x759B1FFF (0x00002000 bytes) [...] 103 : 0x7FFD2000 -> 0x7FFD3FFF (0x00002000 bytes) 104 : 0x7FFDE000 -> 0x7FFE0FFF (0x00003000 bytes) 105 : 0x80000000 -> 0xA01A5FFF (0x201A6000 bytes) 106 : 0xA01B0000 -> 0xA01F2FFF (0x00043000 bytes) 107 : 0xA0200000 -> 0xA02C7FFF (0x000C8000 bytes) 108 : 0xA02F0000 -> 0xA03FFFFF (0x00110000 bytes) 109 : 0xA4000000 -> 0xA4001FFF (0x00002000 bytes) 110 : 0xBE63B000 -> 0xBE63CFFF (0x00002000 bytes) [...] 340 : 0xC0000000 -> 0xC0001FFF (0x00002000 bytes) 341 : 0xC0040000 -> 0xC0040FFF (0x00001000 bytes) 342 : 0xC01D6000 -> 0xC01D6FFF (0x00001000 bytes) 343 : 0xC01DA000 -> 0xC01DAFFF (0x00001000 bytes) 344 : 0xC01DD000 -> 0xC01E0FFF (0x00004000 bytes) 345 : 0xC01FD000 -> 0xC01FDFFF (0x00001000 bytes) 346 : 0xC01FF000 -> 0xC0280FFF (0x00082000 bytes) 347 : 0xC0290000 -> 0xC0301FFF (0x00072000 bytes) 348 : 0xC0303000 -> 0xC0386FFF (0x00084000 bytes) 349 : 0xC0389000 -> 0xC038CFFF (0x00004000 bytes) 350 : 0xC039E000 -> 0xC03FFFFF (0x00062000 bytes) [...] 2121 : 0xFFC00000 -> 0xFFD0FFFF (0x00110000 bytes) 2122 : 0xFFDF0000 -> 0xFFDF0FFF (0x00001000 bytes) 2123 : 0xFFDFF000 -> 0xFFDFFFFF (0x00001000 bytes) [...] Present bytes: 0x22AA9000 Total bytes: 0x2B8BA000 [...]
The odd thing about the +b option of w2k_mem.exe is that it reports an amount of used memory that is far beyond any reasonable value. Note the summary lines at the end of Example 4-16. Am I really using 700 MB of memory now? The Windows 2000 Task Manager indicates 150 MBso what's going on here? This strange effect comes from memory block #105, which is reported to range from 0x80000000 to 0xA01A5FFF, spanning 0x201A6000 bytes, which equals 538,599,424 bytes. This is obviously nonsense. The problem is that the entire linear address range from 0x80000000 to 0x9FFFFFFF is mapped to the physical address range 0x00000000 to 0x1FFFFFFF, as already noted earlier in this chapter. All 4-MB pages in this range have valid PDEs in the page-directory at address 0xC0300000, which can be proved by issuing the command w2k_mem +d #0x200 0xC0300800 (Example 4-17). Because all PDEs in the resulting list are odd numbers, the corresponding pages must be present; however, they are not necessarily backed up by physical memory. In fact, large portions of this memory range are really "holes" and seem to be filled with 0xFF bytes if copied to a buffer. Therefore, you shouldn't take the memory usage summary displayed by w2k_mem.exe too seriously.
Example 4-17. The PDEs of the Address Range 0x80000000 to 0x9FFFFFFF
E:\>w2k_mem +d #0x200 0xC0300800 [...] C0300800..C03009FF: 512 valid bytes Address | 00000000 - 00000004 : 00000008 - 0000000C | 0000 0004 0008 000C ---------|---------------------:---------------------|-------------------- C0300800 | 000001E3 - 004001E3 : 008001E3 - 00C001E3 | ...ã .@.ã .?.ã .À.ã C0300810 | 010001E3 - 014001E3 : 018001E3 - 01C001E3 | ...ã .@.ã .?.ã .À.ã C0300820 | 020001E3 - 024001E3 : 028001E3 - 02C001E3 | ...ã .@.ã .?.ã .À.ã C0300830 | 030001E3 - 034001E3 : 038001E3 - 03C001E3 | ...ã .@.ã .?.ã .À.ã C0300840 | 040001E3 - 044001E3 : 048001E3 - 04C001E3 | ...ã .@.ã .?.ã .À.ã C0300850 | 050001E3 - 054001E3 : 058001E3 - 05C001E3 | ...ã .@.ã .?.ã .À.ã C0300860 | 060001E3 - 064001E3 : 068001E3 - 06C001E3 | ...ã .@.ã .?.ã .À.ã C0300870 | 070001E3 - 074001E3 : 078001E3 - 07C001E3 | ...ã .@.ã .?.ã .À.ã C0300880 | 080001E3 - 084001E3 : 088001E3 - 08C001E3 | ...ã .@.ã .?.ã .À.ã C0300890 | 090001E3 - 094001E3 : 098001E3 - 09C001E3 | ...ã .@.ã .?.ã .À.ã C03008A0 | 0A0001E3 - 0A4001E3 : 0A8001E3 - 0AC001E3 | ...ã .@.ã .?.ã .À.ã C03008B0 | 0B0001E3 - 0B4001E3 : 0B8001E3 - 0BC001E3 | ...ã .@.ã .?.ã .À.ã C03008C0 | 0C0001E3 - 0C4001E3 : 0C8001E3 - 0CC001E3 | ...ã .@.ã .?.ã .À.ã C03008D0 | 0D0001E3 - 0D4001E3 : 0D8001E3 - 0DC001E3 | ...ã .@.ã .?.ã .À.ã C03008E0 | 0E0001E3 - 0E4001E3 : 0E8001E3 - 0EC001E3 | ...ã .@.ã .?.ã .À.ã C03008F0 | 0F0001E3 - 0F4001E3 : 0F8001E3 - 0FC001E3 | ...ã .@.ã .?.ã .À.ã C0300900 | 100001E3 - 104001E3 : 108001E3 - 10C001E3 | ...ã .@.ã .?.ã .À.ã C0300910 | 110001E3 - 114001E3 : 118001E3 - 11C001E3 | ...ã .@.ã .?.ã .À.ã C0300920 | 120001E3 - 124001E3 : 128001E3 - 12C001E3 | ...ã .@.ã .?.ã .À.ã C0300930 | 130001E3 - 134001E3 : 138001E3 - 13C001E3 | ...ã .@.ã .?.ã .À.ã C0300940 | 140001E3 - 144001E3 : 148001E3 - 14C001E3 | ...ã .@.ã .?.ã .À.ã C0300950 | 150001E3 - 154001E3 : 158001E3 - 15C001E3 | ...ã .@.ã .?.ã .À.ã C0300960 | 160001E3 - 164001E3 : 168001E3 - 16C001E3 | ...ã .@.ã .?.ã .À.ã C0300970 | 170001E3 - 174001E3 : 178001E3 - 17C001E3 | ...ã .@.ã .?.ã .À.ã C0300980 | 180001E3 - 184001E3 : 188001E3 - 18C001E3 | ...ã .@.ã .?.ã .À.ã C0300990 | 190001E3 - 194001E3 : 198001E3 - 19C001E3 | ...ã .@.ã .?.ã .À.ã C03009A0 | 1A0001E3 - 1A4001E3 : 1A8001E3 - 1AC001E3 | ...ã .@.ã .?.ã .À.ã C03009B0 | 1B0001E3 - 1B4001E3 : 1B8001E3 - 1BC001E3 | ...ã .@.ã .?.ã .À.ã C03009C0 | 1C0001E3 - 1C4001E3 : 1C8001E3 - 1CC001E3 | ...ã .@.ã .?.ã .À.ã C03009D0 | 1D0001E3 - 1D4001E3 : 1D8001E3 - 1DC001E3 | ...ã .@.ã .?.ã .À.ã C03009E0 | 1E0001E3 - 1E4001E3 : 1E8001E3 - 1EC001E3 | ...ã .@.ã .?.ã .À.ã C03009F0 | 1F0001E3 - 1F4001E3 : 1F8001E3 - 1FC001E3 | ...ã .@.ã .?.ã .À.ã [...]
The Windows 2000 Memory Map
The last part of this chapter is dedicated to the general layout of the 4-GB linear address space as it is "seen" by a Windows 2000 process. Table 4-9 lists the address ranges of various essential data structures. The big holes between them are used for several purposes, such as load areas for process modules and device drivers, memory pools, working set lists, and the like. Note that some addresses and block sizes might vary considerably from system to system, depending on the memory and hardware configuration, the process properties, and several other variables. Therefore, use this list only as a rough sketch, not as an accurate roadmap.
Some physical memory blocks appear twice or more in the linear address space. For example, the SharedUserData area at linear address 0xFFDF0000 is mirrored at address 0x7FFE0000. Both refer to the same page in physical memorywriting a byte to 0xFFDF0000+n mysteriously changes the value of the byte at 0x7FFE0000+n. This is the world of virtual memorya physical address can be mapped anywhere into the linear address space, even to several addresses at the same time. It's just a matter of setting up the page-directory and page-tables appropriately. Please recall Figures 4-3 and 4-4, which clearly show that linear addresses are fake. Their Directory and Table bit fields are just pointers to structures that determine the real location of the data. And if the PFNs of two PTEs happen to be identical, the corresponding linear addresses refer to the same physical memory location.
Table 4-9. Identifiable Memory Regions in the Address Space of a Process
START |
END |
HEX SIZE |
TYPE/DESCRIPTION |
0x00000000 |
0x0000FFFF |
10000 |
Lower guard block |
0x00010000 |
0x0001FFFF |
10000 |
WCHAR[]/Environment strings, allocated in 4-KB pages |
0x00020000 |
0x0002FFFF |
10000 |
PROCESS_PARAMETERS/allocated in 4-KB pages |
0x00030000 |
0x0012FFFF |
100000 |
DWORD [4000]/Process stack (default: 1 MB) |
0x7FFDD000 |
0x7FFDDFFF |
1000 |
TEB/Thread Environment Block of thread #2 |
0x7FFDE000 |
0x7FFDEFFF |
1000 |
TEB/Thread Environment Block of thread #1 |
0x7FFDF000 |
0x7FFDFFFF |
1000 |
PEB/Process Environment Block |
0x7FFE0000 |
0x7FFE02D7 |
2D8 |
KUSER_SHARED_DATA/SharedUserData in user-mode |
0x7FFF0000 |
0x7FFFFFFF |
10000 |
Upper guard block |
0x80000000 |
0x800003FF |
400 |
IVT/Interrupt Vector Table |
0x80036000 |
0x800363FF |
400 |
KGDTENTRY[80]/Global Descriptor Table |
0x80036400 |
0x80036BFF |
800 |
KIDTENTRY[100]/Interrupt Descriptor Table |
0x800C0000 |
0x800FFFFF |
40000 |
VGA/ROM BIOS |
0x80244000 |
0x802460AA |
20AB |
KTSS/user/kernel Task State Segment (busy) |
0x8046AB80 |
0x8046ABBF |
40 |
KeServiceDescriptorTable |
0x8046ABC0 |
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 (max. for 4 GB) |
0xA01859F0 |
0xA01863EB |
9FC |
PROC[27F]/W32pServiceTable |
0xA0186670 |
0xA01868EE |
27F |
BYTE[27F]/W32pArgumentTable |
0xC0000000 |
0xC03FFFFF |
400000 |
X86_PE[100000]/page-directory and page-tables |
0xC1000000 |
0xE0FFFFFF |
20000000 |
System Cache (MmSystemCacheStart, MmSystemCacheEnd) |
0xE1000000 |
0xE77FFFFF |
6800000 |
Paged Pool (MmPagedPoolStart, MmPagedPoolEnd) |
0xF0430000 |
0xF043FFFF |
10000 |
ROM BIOS code segment |
0xF0440000 |
0xF044FFFF |
10000 |
ROM BIOS data segment |
0xFFDF0000 |
0xFFDF02D7 |
2D8 |
KUSER_SHARED_DATA/SharedUserData in kernel-mode |
0xFFDFF000 |
0xFFDFF053 |
54 |
KPCR/Processor Control Region (kernel-mode FS segment) |
0xFFDFF120 |
0xFFDFF13B |
1C |
KPRCB/Processor Control Block |
0xFFDFF13C |
0xFFDFF407 |
2CC |
CONTEXT/Thread Context (CPU state) |
0xFFDFF620 |
0xFFDFF71F |
100 |
Lookaside list directories |