Note: this patch only implements the code paths, they are not excercised yet.

[NTOS]: Implement handling a very special case of "prototype PTE", the one used to map the shared user data for user-mode applications.
[NTOS]: MiCheckVirtualAddress detects this (Windows behavior) and returns a prototype PTE that's marked MM_READONLY. This is our MmSharedUserDataPte from before. This gets sent to MiDispatchFault which calls MiResolveProtoPteFault to handle it. In turn, this calls MiCompleteProtoPteFault. All these code paths have heavy ASSERTions to only allow them to be hit for the shared user data page, however, in the far distant future when prototype PTEs are used for section objects, we'll at least have the right checks and code flow (many of these ASSERTions will then have to be removed).
[NTOS]: The end result is that we also now have STATUS_PAGE_FAULT_TRANSITION, not just STATUS_PAGE_FAULT_DEMAND_ZERO, and that prototype PTEs are somewhat understood and some assumptions have been removed.

svn path=/trunk/; revision=48201
This commit is contained in:
Sir Richard 2010-07-22 20:52:23 +00:00
parent dd68882cd0
commit da305551a9

View file

@ -34,6 +34,14 @@ MiCheckVirtualAddress(IN PVOID VirtualAddress,
/* Only valid for user VADs for now */
ASSERT(VirtualAddress <= MM_HIGHEST_USER_ADDRESS);
/* Special case for shared data */
if (PAGE_ALIGN(VirtualAddress) == (PVOID)USER_SHARED_DATA)
{
/* It's a read-only page */
*ProtectCode = MM_READONLY;
return MmSharedUserDataPte;
}
/* Find the VAD, it must exist, since we only handle PEB/TEB */
Vad = MiLocateAddress(VirtualAddress);
ASSERT(Vad);
@ -242,20 +250,100 @@ MiResolveDemandZeroFault(IN PVOID Address,
return STATUS_PAGE_FAULT_DEMAND_ZERO;
}
NTSTATUS
NTAPI
MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction,
IN PVOID Address,
IN PMMPTE PointerPte,
IN PMMPTE PointerProtoPte,
IN KIRQL OldIrql,
IN PMMPFN Pfn1)
{
MMPTE TempPte;
PFN_NUMBER PageFrameIndex;
/* Must be called with an valid prototype PTE, with the PFN lock held */
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
ASSERT(PointerProtoPte->u.Hard.Valid == 1);
/* Quick-n-dirty */
ASSERT(PointerPte->u.Soft.PageFileHigh == 0xFFFFF);
/* Get the page */
PageFrameIndex = PFN_FROM_PTE(PointerProtoPte);
/* Release the PFN lock */
KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
/* Build the user PTE */
ASSERT(Address < MmSystemRangeStart);
MI_MAKE_HARDWARE_PTE_USER(&TempPte, PointerPte, MM_READONLY, PageFrameIndex);
/* Write the PTE */
MI_WRITE_VALID_PTE(PointerPte, TempPte);
/* Return success */
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
MiResolveProtoPteFault(IN BOOLEAN StoreInstruction,
IN PVOID Address,
IN PMMPTE PointerPte,
IN PMMPTE PointerProtoPte,
IN OUT PMMPFN *OutPfn,
OUT PVOID *PageFileData,
OUT PMMPTE PteValue,
IN PEPROCESS Process,
IN KIRQL OldIrql,
IN PVOID TrapInformation)
{
MMPTE TempPte;
PMMPFN Pfn1;
PFN_NUMBER PageFrameIndex;
/* Must be called with an invalid, prototype PTE, with the PFN lock held */
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
ASSERT(PointerPte->u.Hard.Valid == 0);
ASSERT(PointerPte->u.Soft.Prototype == 1);
/* Read the prototype PTE -- it must be valid since we only handle shared data */
TempPte = *PointerProtoPte;
ASSERT(TempPte.u.Hard.Valid == 1);
/* One more user of this mapped page */
PageFrameIndex = PFN_FROM_PTE(&TempPte);
Pfn1 = MiGetPfnEntry(PageFrameIndex);
Pfn1->u2.ShareCount++;
/* Call it a transition */
InterlockedIncrement(&KeGetCurrentPrcb()->MmTransitionCount);
/* Complete the prototype PTE fault -- this will release the PFN lock */
return MiCompleteProtoPteFault(StoreInstruction,
Address,
PointerPte,
PointerProtoPte,
OldIrql,
NULL);
}
NTSTATUS
NTAPI
MiDispatchFault(IN BOOLEAN StoreInstruction,
IN PVOID Address,
IN PMMPTE PointerPte,
IN PMMPTE PrototypePte,
IN PMMPTE PointerProtoPte,
IN BOOLEAN Recursive,
IN PEPROCESS Process,
IN PVOID TrapInformation,
IN PVOID Vad)
{
MMPTE TempPte;
KIRQL OldIrql;
KIRQL OldIrql, LockIrql;
NTSTATUS Status;
PMMPTE SuperProtoPte;
DPRINT("ARM3 Page Fault Dispatcher for address: %p in process: %p\n",
Address,
Process);
@ -263,17 +351,54 @@ MiDispatchFault(IN BOOLEAN StoreInstruction,
//
// Make sure APCs are off and we're not at dispatch
//
OldIrql = KeGetCurrentIrql ();
OldIrql = KeGetCurrentIrql();
ASSERT(OldIrql <= APC_LEVEL);
ASSERT(KeAreAllApcsDisabled () == TRUE);
ASSERT(KeAreAllApcsDisabled() == TRUE);
//
// Grab a copy of the PTE
//
TempPte = *PointerPte;
/* No prototype */
ASSERT(PrototypePte == NULL);
/* Do we have a prototype PTE? */
if (PointerProtoPte)
{
/* This should never happen */
ASSERT(!MI_IS_PHYSICAL_ADDRESS(PointerProtoPte));
/* We currently only handle the shared user data PTE path */
ASSERT(Address < MmSystemRangeStart);
ASSERT(PointerPte->u.Soft.Prototype == 1);
ASSERT(PointerPte->u.Soft.PageFileHigh == 0xFFFFF);
ASSERT(Vad == NULL);
/* Lock the PFN database */
LockIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
/* For the shared data page, this should be true */
SuperProtoPte = MiAddressToPte(PointerProtoPte);
ASSERT(SuperProtoPte->u.Hard.Valid == 1);
ASSERT(TempPte.u.Hard.Valid == 0);
/* Resolve the fault -- this will release the PFN lock */
Status = MiResolveProtoPteFault(StoreInstruction,
Address,
PointerPte,
PointerProtoPte,
NULL,
NULL,
NULL,
Process,
LockIrql,
TrapInformation);
ASSERT(Status == STATUS_SUCCESS);
/* Complete this as a transition fault */
ASSERT(OldIrql == KeGetCurrentIrql());
ASSERT(OldIrql <= APC_LEVEL);
ASSERT(KeAreAllApcsDisabled() == TRUE);
return STATUS_PAGE_FAULT_TRANSITION;
}
//
// The PTE must be invalid, but not totally blank
@ -321,7 +446,7 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
IN PVOID TrapInformation)
{
KIRQL OldIrql = KeGetCurrentIrql(), LockIrql;
PMMPTE PointerPte;
PMMPTE PointerPte, ProtoPte;
PMMPDE PointerPde;
MMPTE TempPte;
PETHREAD CurrentThread;
@ -562,64 +687,94 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
ASSERT(TempPte.u.Long == 0);
/* Check if this address range belongs to a valid allocation (VAD) */
MiCheckVirtualAddress(Address, &ProtectionCode, &Vad);
/* Right now, we expect a valid protection mask on the VAD */
ProtoPte = MiCheckVirtualAddress(Address, &ProtectionCode, &Vad);
ASSERT(ProtectionCode != MM_NOACCESS);
PointerPte->u.Soft.Protection = ProtectionCode;
/* Lock the PFN database since we're going to grab a page */
OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
/* Grab a page out of there. Later we should grab a colored zero page */
PageFrameIndex = MiRemoveAnyPage(0);
ASSERT(PageFrameIndex);
/* Release the lock since we need to do some zeroing */
KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
/* Zero out the page, since it's for user-mode */
MiZeroPfn(PageFrameIndex);
/* Grab the lock again so we can initialize the PFN entry */
OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
/* Initialize the PFN entry now */
MiInitializePfn(PageFrameIndex, PointerPte, 1);
/* And we're done with the lock */
KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
/* One more demand-zero fault */
InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount);
/* Was the fault on an actual user page, or a kernel page for the user? */
if (PointerPte <= MiHighestUserPte)
/* Did we get a prototype PTE back? */
if (!ProtoPte)
{
/* User fault, build a user PTE */
MI_MAKE_HARDWARE_PTE_USER(&TempPte,
PointerPte,
PointerPte->u.Soft.Protection,
PageFrameIndex);
/* No, create a new PTE. First, write the protection */
PointerPte->u.Soft.Protection = ProtectionCode;
/* Lock the PFN database since we're going to grab a page */
OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
/* Grab a page out of there. Later we should grab a colored zero page */
PageFrameIndex = MiRemoveAnyPage(0);
ASSERT(PageFrameIndex);
/* Release the lock since we need to do some zeroing */
KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
/* Zero out the page, since it's for user-mode */
MiZeroPfn(PageFrameIndex);
/* Grab the lock again so we can initialize the PFN entry */
OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
/* Initialize the PFN entry now */
MiInitializePfn(PageFrameIndex, PointerPte, 1);
/* And we're done with the lock */
KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
/* One more demand-zero fault */
InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount);
/* Was the fault on an actual user page, or a kernel page for the user? */
if (PointerPte <= MiHighestUserPte)
{
/* User fault, build a user PTE */
MI_MAKE_HARDWARE_PTE_USER(&TempPte,
PointerPte,
PointerPte->u.Soft.Protection,
PageFrameIndex);
}
else
{
/* Session, kernel, or user PTE, figure it out and build it */
MI_MAKE_HARDWARE_PTE(&TempPte,
PointerPte,
PointerPte->u.Soft.Protection,
PageFrameIndex);
}
/* Write the dirty bit for writeable pages */
if (TempPte.u.Hard.Write) TempPte.u.Hard.Dirty = TRUE;
/* And now write down the PTE, making the address valid */
MI_WRITE_VALID_PTE(PointerPte, TempPte);
/* Demand zero */
Status = STATUS_PAGE_FAULT_DEMAND_ZERO;
}
else
{
/* Session, kernel, or user PTE, figure it out and build it */
MI_MAKE_HARDWARE_PTE(&TempPte,
PointerPte,
PointerPte->u.Soft.Protection,
PageFrameIndex);
/* The only "prototype PTE" we support is the shared user data path */
ASSERT(ProtectionCode == MM_READONLY);
/* Write the prototype PTE */
TempPte = PrototypePte;
TempPte.u.Soft.Protection = ProtectionCode;
MI_WRITE_INVALID_PTE(PointerPte, TempPte);
/* Handle the fault */
Status = MiDispatchFault(StoreInstruction,
Address,
PointerPte,
ProtoPte,
FALSE,
CurrentProcess,
TrapInformation,
Vad);
ASSERT(Status == STATUS_PAGE_FAULT_TRANSITION);
ASSERT(PointerPte->u.Hard.Valid == 1);
ASSERT(PointerPte->u.Hard.PageFrameNumber == MmSharedUserDataPte->u.Hard.PageFrameNumber);
}
/* Write the dirty bit for writeable pages */
if (TempPte.u.Hard.Write) TempPte.u.Hard.Dirty = TRUE;
/* And now write down the PTE, making the address valid */
MI_WRITE_VALID_PTE(PointerPte, TempPte);
/* Release the working set */
MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
return STATUS_PAGE_FAULT_DEMAND_ZERO;
return Status;
}
/* EOF */