Geoff Chappell - Software Analyst
The MMPFNENTRY structure (formally _MMPFNENTRY) is nowadays a set of bit flags that are thought to exist only in the u3 member of an MMPFN. The 32-bit u3 is in essence 16 bits of flags and a 16-bit reference count, in that order originally, but later reversed.
For its original placement in u3, dating from version 4.0, the MMPFNENTRY is the 4-byte e1 branch. Though the flags are all in the word at offset 0x0C in the MMPFN, they are formally ULONG bit fields with space for the reference count left at the end as one 16-bit field named DontUse. The reference count is instead accessed through the e2 branch of the union, also 4 bytes, but defining the ReferenceCount as a USHORT (at offset 0x0E in the MMPFN) after leaving space for the flags as a USHORT named ShortFlags.
The flags and reference count are swapped for the version 5.2 from Windows Server 2003 SP1, and in all versions since. This change reduced the MMPFNENTRY to two bytes. It then is just 16 bits of flags that follow the ReferenceCount. The flags were not rearranged, but they formally change type to be USHORT bit fields. Version 6.0 reorganises them into two bytes of UCHAR bit fields. The 1607 release of Windows 10 discontinues the MMPFNENTRY in favour of separating the two bytes of flags into their own structures, named MMPFNENTRY1 and MMPFNENTRY3.
The MMPFNENTRY has so long been a container just for 16 bits of flags that it’s only natural to wonder in what sense are they any sort of entry. For that, look back to when the MMPFN has neither a u3 nor bit flags at offset 0x0C. Before version 4.0, its only bit flags are at offset 0x14 in combination with the PteFrame. This dword is in the style of a Page Table Entry (PTE), with a 20-bit Page Frame Number (PFN) in the high bits, leaving the low 12 bits for flags. It is here thought that this combination is the original MMPFNENTRY and that when version 4.0 moved these flags to offset 0x0C and left the PteFrame behind, the name MMPFNENTRY followed the flags.
Inevitably, pressure for more flags kept building. Whatever was gained by separating the old flags from PteFrame, it left 12 bits spare at offset 0x14. By version 5.1, PteFrame is recast into another union, u4, with new flags in the high bits. Some flags that moved with the MMPFNENTRY then move back to their old site.
The MMPFNENTRY starts as a four-byte structure of ULONG bit fields before 64-bit Windows exists. It is the whole dword at offset 0x14 in the MMPFN before version 4.0 and then moved to offset 0x0C in u3.
Mask | Definition | Versions | Remarks |
---|---|---|---|
0x00000001 |
ULONG Modified : 1; |
3.10 to early 5.2 | |
0x00000002 |
ULONG ReadInProgress : 1; |
3.10 to early 5.2 | |
0x00000004 |
ULONG WriteInProgress : 1; |
3.10 to early 5.2 | |
0x00000008 |
ULONG PrototypePte : 1; |
3.10 to early 5.2 | |
0x00000070 (3.10 to 5.1); 0x000000F0 |
ULONG PageColor : 3; |
3.10 to 5.1 | |
ULONG PageColor : 4; |
early 5.2 only | ||
0x00000080 (3.10 to 5.1) |
ULONG ParityError : 1; |
3.10 to 5.1 | next as 0x00008000 |
0x00000700 |
ULONG PageLocation : 3; |
3.10 to early 5.2 | |
0x00000800 (3.10 to 5.0) |
ULONG InPageError : 1; |
3.10 to 5.0 | next in u4 |
0x00001000 (5.0) |
ULONG VerifierAllocation : 1; |
5.0 only | next in u4 |
0x00002000 (5.0); 0x00000800 |
ULONG RemovalRequested : 1; |
5.0 to early 5.2 | |
0x00003000 |
ULONG CacheAttribute : 2; |
5.1 to early 5.2 | |
ULONG Reserved : 4; |
4.0 only | ||
ULONG Reserved : 1; |
5.0 only | ||
0x00004000 |
ULONG Rom : 1; |
5.1 to early 5.2 | |
0x00008000 |
ULONG LockCharged : 1; |
5.0 to 5.1 | next in u4 |
ULONG ParityError : 1; |
early 5.2 only | previously 0x00000080 | |
0xFFFFF000 (3.10 to 3.51); 0xFFFF0000 |
ULONG PteFrame : 20; |
3.10 to 3.51 | next in MMPFN |
ULONG DontUse : 16; |
4.0 to early 5.2 |
The multi-bit PageLocation and CacheAttribute take their values from the MMLISTS and MI_PFN_CACHE_ATTRIBUTE enumerations, respectively.
When version 4.0 moved 12 bits of flags from the dword at offset 0x14 to the word at offset 0x0C without rearrangement, it gained 4 bits of space but is not known to have defined any of these bits. Version 5.0 is known (from public symbol files) to define one of the new four as Reserved, apparently having found use for only three. Presumably, those that are not used in version 4.0 are also defined as Reserved.
The version 5.2 from Windows Server 2003 SP1 reduced the MMPFNENTRY to a two-byte structure of USHORT bit fields. That each bit retained its position from the four-byte MMPFNENTRY was very short-lived: the few that Windows Vista didn’t rearrange (see next section) were instead moved away:
Mask | Definition | Versions | Remarks |
---|---|---|---|
0x0001 |
USHORT Modified : 1; |
late 5.2 only | next as 0x10 in first byte |
0x0002 |
USHORT ReadInProgress : 1; |
late 5.2 only | next as 0x20 in first byte |
0x0004 |
USHORT WriteInProgress : 1; |
late 5.2 only | next as 0x08 in first byte |
0x0008 |
USHORT PrototypePte : 1; |
late 5.2 only | next in u4 |
0x00F0 |
USHORT PageColor : 4; |
late 5.2 only | next in u4 |
0x0700 |
USHORT PageLocation : 3; |
late 5.2 only | next as 0x07 in first byte |
0x0800 |
USHORT RemovalRequested : 1; |
late 5.2 only | next as 0x40 in second byte |
0x3000 |
USHORT CacheAttribute : 2; |
late 5.2 only | next as 0xC0 in first byte |
0x4000 |
USHORT Rom : 1; |
late 5.2 only | next as 0x08 in second byte |
0x8000 |
USHORT ParityError : 1; |
late 5.2 only | next as 0x80 in second byte |
Windows Vista reorganised the MMPFNENTRY into a two-byte structure of UCHAR bit fields. The 1607 release of Windows 10 then redefines each byte as its own structure, MMPFNENTRY1 and MMPFNENTRY3 respectively, each still as UCHAR bit fields.
The flags in the new MMPFNENTRY1 or in the first byte of the two-byte MMPFNENTRY are accessed from an MMPFN through its u3.e1 nesting.
Offset / Mask | Definition | Versions | Remarks |
---|---|---|---|
0x00 / 0x07 |
UCHAR PageLocation : 3; |
6.0 and higher | previously 0x0700 |
0x00 / 0x08 |
UCHAR WriteInProgress : 1; |
6.0 and higher | previously 0x0004 |
0x00 / 0x10 |
UCHAR Modified : 1; |
6.0 and higher | previously 0x0001 |
0x00 / 0x20 |
UCHAR ReadInProgress : 1; |
6.0 and higher | previously 0x0002 |
0x00 / 0xC0 |
UCHAR CacheAttribute : 2; |
6.0 and higher | previously 0x3000 |
All these flags were previously USHORT bit fields in the two-byte MMPFNENTRY.
The flags in the new MMPFNENTRY3 are accessed from an MMPFN through its u3.e3 nesting. Before 1607, these flags are in the second byte of the two-byte MMPFNENTRY and are accessed through u3.e1, just like the flags in the first byte.
Offset / Mask | Definition | Versions | Remarks |
---|---|---|---|
0x01 / 0x07 |
UCHAR Priority : 3; |
6.0 and higher | previously in u4 |
0x01 / 0x08 |
UCHAR Rom : 1; |
6.0 to 6.1 | previously 0x4000 |
UCHAR OnProtectedStandby; |
6.2 and higher | ||
0x01 / 0x10 |
UCHAR InPageError : 1; |
6.0 and higher | previously in u4 |
0x01 / 0x20 |
UCHAR KernelStack : 1; |
6.0 to 6.1 | previously in u4 |
UCHAR Spare : 1; |
6.2 to 6.3 | ||
UCHAR SystemChargedPage : 1; |
10.0 and higher | ||
0x01 / 0x40 |
UCHAR RemovalRequested : 1; |
6.0 and higher | previously 0x0800 |
0x01 / 0x80 |
UCHAR ParityError : 1; |
6.0 and higher | previously 0x8000 |
Of the several flags that version 6.0 moves to the MMPFNENTRY from u4, the ancient InPageError is special for moving back and forth. It moved with the MMPFNENTRY when version 4.0 separated the flags from the PteFrame. It rejoined the PteFrame in u4 for version 5.1. Its move for version 6.0 is a coming home.