X-Git-Url: https://git.reactos.org/?p=reactos.git;a=blobdiff_plain;f=reactos%2Fntoskrnl%2Fmm%2FARM3%2Fpagfault.c;h=662d1ea9702abb6c8516f569a858e7a663fe2251;hp=4d3fb903f57ad8ac9719a0eda9456ba01d24f2b6;hb=9e6083e5c2c6aa8ce0529ab3097760c5595c132a;hpb=28f0bba7b4f8d01838d41f97e00eb7c81aa3b967 diff --git a/reactos/ntoskrnl/mm/ARM3/pagfault.c b/reactos/ntoskrnl/mm/ARM3/pagfault.c index 4d3fb903f57..662d1ea9702 100644 --- a/reactos/ntoskrnl/mm/ARM3/pagfault.c +++ b/reactos/ntoskrnl/mm/ARM3/pagfault.c @@ -17,12 +17,198 @@ /* GLOBALS ********************************************************************/ +#define HYDRA_PROCESS (PEPROCESS)1 #if MI_TRACE_PFNS BOOLEAN UserPdeFault = FALSE; #endif /* PRIVATE FUNCTIONS **********************************************************/ +NTSTATUS +NTAPI +MiCheckForUserStackOverflow(IN PVOID Address, + IN PVOID TrapInformation) +{ + PETHREAD CurrentThread = PsGetCurrentThread(); + PTEB Teb = CurrentThread->Tcb.Teb; + PVOID StackBase, DeallocationStack, NextStackAddress; + SIZE_T GuranteedSize; + NTSTATUS Status; + + /* Do we own the address space lock? */ + if (CurrentThread->AddressSpaceOwner == 1) + { + /* This isn't valid */ + DPRINT1("Process owns address space lock\n"); + ASSERT(KeAreAllApcsDisabled() == TRUE); + return STATUS_GUARD_PAGE_VIOLATION; + } + + /* Are we attached? */ + if (KeIsAttachedProcess()) + { + /* This isn't valid */ + DPRINT1("Process is attached\n"); + return STATUS_GUARD_PAGE_VIOLATION; + } + + /* Read the current settings */ + StackBase = Teb->NtTib.StackBase; + DeallocationStack = Teb->DeallocationStack; + GuranteedSize = Teb->GuaranteedStackBytes; + DPRINT("Handling guard page fault with Stacks Addresses 0x%p and 0x%p, guarantee: %lx\n", + StackBase, DeallocationStack, GuranteedSize); + + /* Guarantees make this code harder, for now, assume there aren't any */ + ASSERT(GuranteedSize == 0); + + /* So allocate only the minimum guard page size */ + GuranteedSize = PAGE_SIZE; + + /* Does this faulting stack address actually exist in the stack? */ + if ((Address >= StackBase) || (Address < DeallocationStack)) + { + /* That's odd... */ + DPRINT1("Faulting address outside of stack bounds. Address=%p, StackBase=%p, DeallocationStack=%p\n", + Address, StackBase, DeallocationStack); + return STATUS_GUARD_PAGE_VIOLATION; + } + + /* This is where the stack will start now */ + NextStackAddress = (PVOID)((ULONG_PTR)PAGE_ALIGN(Address) - GuranteedSize); + + /* Do we have at least one page between here and the end of the stack? */ + if (((ULONG_PTR)NextStackAddress - PAGE_SIZE) <= (ULONG_PTR)DeallocationStack) + { + /* We don't -- Windows would try to make this guard page valid now */ + DPRINT1("Close to our death...\n"); + ASSERT(FALSE); + return STATUS_STACK_OVERFLOW; + } + + /* Don't handle this flag yet */ + ASSERT((PsGetCurrentProcess()->Peb->NtGlobalFlag & FLG_DISABLE_STACK_EXTENSION) == 0); + + /* Update the stack limit */ + Teb->NtTib.StackLimit = (PVOID)((ULONG_PTR)NextStackAddress + GuranteedSize); + + /* Now move the guard page to the next page */ + Status = ZwAllocateVirtualMemory(NtCurrentProcess(), + &NextStackAddress, + 0, + &GuranteedSize, + MEM_COMMIT, + PAGE_READWRITE | PAGE_GUARD); + if ((NT_SUCCESS(Status) || (Status == STATUS_ALREADY_COMMITTED))) + { + /* We did it! */ + DPRINT("Guard page handled successfully for %p\n", Address); + return STATUS_PAGE_FAULT_GUARD_PAGE; + } + + /* Fail, we couldn't move the guard page */ + DPRINT1("Guard page failure: %lx\n", Status); + ASSERT(FALSE); + return STATUS_STACK_OVERFLOW; +} + +FORCEINLINE +BOOLEAN +MiIsAccessAllowed( + _In_ ULONG ProtectionMask, + _In_ BOOLEAN Write, + _In_ BOOLEAN Execute) +{ + #define _BYTE_MASK(Bit0, Bit1, Bit2, Bit3, Bit4, Bit5, Bit6, Bit7) \ + (Bit0) | ((Bit1) << 1) | ((Bit2) << 2) | ((Bit3) << 3) | \ + ((Bit4) << 4) | ((Bit5) << 5) | ((Bit6) << 6) | ((Bit7) << 7) + static const UCHAR AccessAllowedMask[2][2] = + { + { // Protect 0 1 2 3 4 5 6 7 + _BYTE_MASK(0, 1, 1, 1, 1, 1, 1, 1), // READ + _BYTE_MASK(0, 0, 1, 1, 0, 0, 1, 1), // EXECUTE READ + }, + { + _BYTE_MASK(0, 0, 0, 0, 1, 1, 1, 1), // WRITE + _BYTE_MASK(0, 0, 0, 0, 0, 0, 1, 1), // EXECUTE WRITE + } + }; + + /* We want only the lower access bits */ + ProtectionMask &= MM_PROTECT_ACCESS; + + /* Look it up in the table */ + return (AccessAllowedMask[Write != 0][Execute != 0] >> ProtectionMask) & 1; +} + +NTSTATUS +NTAPI +MiAccessCheck(IN PMMPTE PointerPte, + IN BOOLEAN StoreInstruction, + IN KPROCESSOR_MODE PreviousMode, + IN ULONG_PTR ProtectionMask, + IN PVOID TrapFrame, + IN BOOLEAN LockHeld) +{ + MMPTE TempPte; + + /* Check for invalid user-mode access */ + if ((PreviousMode == UserMode) && (PointerPte > MiHighestUserPte)) + { + return STATUS_ACCESS_VIOLATION; + } + + /* Capture the PTE -- is it valid? */ + TempPte = *PointerPte; + if (TempPte.u.Hard.Valid) + { + /* Was someone trying to write to it? */ + if (StoreInstruction) + { + /* Is it writable?*/ + if ((TempPte.u.Hard.Write) || (TempPte.u.Hard.CopyOnWrite)) + { + /* Then there's nothing to worry about */ + return STATUS_SUCCESS; + } + + /* Oops! This isn't allowed */ + return STATUS_ACCESS_VIOLATION; + } + + /* Someone was trying to read from a valid PTE, that's fine too */ + return STATUS_SUCCESS; + } + + /* Check if the protection on the page allows what is being attempted */ + if (!MiIsAccessAllowed(ProtectionMask, StoreInstruction, FALSE)) + { + return STATUS_ACCESS_VIOLATION; + } + + /* Check if this is a guard page */ + if ((ProtectionMask & MM_PROTECT_SPECIAL) == MM_GUARDPAGE) + { + NT_ASSERT(ProtectionMask != MM_DECOMMIT); + + /* Attached processes can't expand their stack */ + if (KeIsAttachedProcess()) return STATUS_ACCESS_VIOLATION; + + /* No support for transition PTEs yet */ + ASSERT(((TempPte.u.Soft.Transition == 1) && + (TempPte.u.Soft.Prototype == 0)) == FALSE); + + /* Remove the guard page bit, and return a guard page violation */ + TempPte.u.Soft.Protection = ProtectionMask & ~MM_GUARDPAGE; + NT_ASSERT(TempPte.u.Long != 0); + MI_WRITE_INVALID_PTE(PointerPte, TempPte); + return STATUS_GUARD_PAGE_VIOLATION; + } + + /* Nothing to do */ + return STATUS_SUCCESS; +} + PMMPTE NTAPI MiCheckVirtualAddress(IN PVOID VirtualAddress, @@ -35,8 +221,70 @@ MiCheckVirtualAddress(IN PVOID VirtualAddress, /* No prototype/section support for now */ *ProtoVad = NULL; - /* Check if this is a page table address */ - if (MI_IS_PAGE_TABLE_ADDRESS(VirtualAddress)) + /* User or kernel fault? */ + if (VirtualAddress <= MM_HIGHEST_USER_ADDRESS) + { + /* Special case for shared data */ + if (PAGE_ALIGN(VirtualAddress) == (PVOID)MM_SHARED_USER_DATA_VA) + { + /* It's a read-only page */ + *ProtectCode = MM_READONLY; + return MmSharedUserDataPte; + } + + /* Find the VAD, it might not exist if the address is bogus */ + Vad = MiLocateAddress(VirtualAddress); + if (!Vad) + { + /* Bogus virtual address */ + *ProtectCode = MM_NOACCESS; + return NULL; + } + + /* ReactOS does not handle physical memory VADs yet */ + ASSERT(Vad->u.VadFlags.VadType != VadDevicePhysicalMemory); + + /* Check if it's a section, or just an allocation */ + if (Vad->u.VadFlags.PrivateMemory) + { + /* ReactOS does not handle AWE VADs yet */ + ASSERT(Vad->u.VadFlags.VadType != VadAwe); + + /* This must be a TEB/PEB VAD */ + if (Vad->u.VadFlags.MemCommit) + { + /* It's committed, so return the VAD protection */ + *ProtectCode = (ULONG)Vad->u.VadFlags.Protection; + } + else + { + /* It has not yet been committed, so return no access */ + *ProtectCode = MM_NOACCESS; + } + + /* In both cases, return no PTE */ + return NULL; + } + else + { + /* ReactOS does not supoprt these VADs yet */ + ASSERT(Vad->u.VadFlags.VadType != VadImageMap); + ASSERT(Vad->u2.VadFlags2.ExtendableFile == 0); + + /* Return the proto VAD */ + *ProtoVad = Vad; + + /* Get the prototype PTE for this page */ + PointerPte = (((ULONG_PTR)VirtualAddress >> PAGE_SHIFT) - Vad->StartingVpn) + Vad->FirstPrototypePte; + ASSERT(PointerPte != NULL); + ASSERT(PointerPte <= Vad->LastContiguousPte); + + /* Return the Prototype PTE and the protection for the page mapping */ + *ProtectCode = (ULONG)Vad->u.VadFlags.Protection; + return PointerPte; + } + } + else if (MI_IS_PAGE_TABLE_ADDRESS(VirtualAddress)) { /* This should never happen, as these addresses are handled by the double-maping */ if (((PMMPTE)VirtualAddress >= MiAddressToPte(MmPagedPoolStart)) && @@ -51,58 +299,20 @@ MiCheckVirtualAddress(IN PVOID VirtualAddress, *ProtectCode = MM_READWRITE; return NULL; } - - /* Should not be a session address */ - ASSERT(MI_IS_SESSION_ADDRESS(VirtualAddress) == FALSE); - - /* Special case for shared data */ - if (PAGE_ALIGN(VirtualAddress) == (PVOID)MM_SHARED_USER_DATA_VA) - { - /* It's a read-only page */ - *ProtectCode = MM_READONLY; - return MmSharedUserDataPte; - } - - /* Find the VAD, it might not exist if the address is bogus */ - Vad = MiLocateAddress(VirtualAddress); - if (!Vad) - { - /* Bogus virtual address */ - *ProtectCode = MM_NOACCESS; - return NULL; - } - - /* This must be a VM VAD */ - ASSERT(Vad->u.VadFlags.VadType == VadNone); - - /* Check if it's a section, or just an allocation */ - if (Vad->u.VadFlags.PrivateMemory == TRUE) + else if (MI_IS_SESSION_ADDRESS(VirtualAddress)) { - /* This must be a TEB/PEB VAD */ - ASSERT(Vad->u.VadFlags.MemCommit == TRUE); - *ProtectCode = (ULONG)Vad->u.VadFlags.Protection; - return NULL; + /* ReactOS does not have an image list yet, so bail out to failure case */ + ASSERT(IsListEmpty(&MmSessionSpace->ImageList)); } - else - { - /* Return the proto VAD */ - ASSERT(Vad->u2.VadFlags2.ExtendableFile == 0); - *ProtoVad = Vad; - - /* Get the prototype PTE for this page */ - PointerPte = (((ULONG_PTR)VirtualAddress >> PAGE_SHIFT) - Vad->StartingVpn) + Vad->FirstPrototypePte; - ASSERT(PointerPte <= Vad->LastContiguousPte); - ASSERT(PointerPte != NULL); - /* Return the Prototype PTE and the protection for the page mapping */ - *ProtectCode = (ULONG)Vad->u.VadFlags.Protection; - return PointerPte; - } + /* Default case -- failure */ + *ProtectCode = MM_NOACCESS; + return NULL; } #if (_MI_PAGING_LEVELS == 2) -BOOLEAN FORCEINLINE +BOOLEAN MiSynchronizeSystemPde(PMMPDE PointerPde) { MMPDE SystemPde; @@ -122,6 +332,85 @@ MiSynchronizeSystemPde(PMMPDE PointerPde) return (BOOLEAN)SystemPde.u.Hard.Valid; } +NTSTATUS +FASTCALL +MiCheckPdeForSessionSpace(IN PVOID Address) +{ + MMPTE TempPde; + PMMPTE PointerPde; + PVOID SessionAddress; + ULONG Index; + + /* Is this a session PTE? */ + if (MI_IS_SESSION_PTE(Address)) + { + /* Make sure the PDE for session space is valid */ + PointerPde = MiAddressToPde(MmSessionSpace); + if (!PointerPde->u.Hard.Valid) + { + /* This means there's no valid session, bail out */ + DbgPrint("MiCheckPdeForSessionSpace: No current session for PTE %p\n", + Address); + DbgBreakPoint(); + return STATUS_ACCESS_VIOLATION; + } + + /* Now get the session-specific page table for this address */ + SessionAddress = MiPteToAddress(Address); + PointerPde = MiAddressToPte(Address); + if (PointerPde->u.Hard.Valid) return STATUS_WAIT_1; + + /* It's not valid, so find it in the page table array */ + Index = ((ULONG_PTR)SessionAddress - (ULONG_PTR)MmSessionBase) >> 22; + TempPde.u.Long = MmSessionSpace->PageTables[Index].u.Long; + if (TempPde.u.Hard.Valid) + { + /* The copy is valid, so swap it in */ + InterlockedExchange((PLONG)PointerPde, TempPde.u.Long); + return STATUS_WAIT_1; + } + + /* We don't seem to have allocated a page table for this address yet? */ + DbgPrint("MiCheckPdeForSessionSpace: No Session PDE for PTE %p, %p\n", + PointerPde->u.Long, SessionAddress); + DbgBreakPoint(); + return STATUS_ACCESS_VIOLATION; + } + + /* Is the address also a session address? If not, we're done */ + if (!MI_IS_SESSION_ADDRESS(Address)) return STATUS_SUCCESS; + + /* It is, so again get the PDE for session space */ + PointerPde = MiAddressToPde(MmSessionSpace); + if (!PointerPde->u.Hard.Valid) + { + /* This means there's no valid session, bail out */ + DbgPrint("MiCheckPdeForSessionSpace: No current session for VA %p\n", + Address); + DbgBreakPoint(); + return STATUS_ACCESS_VIOLATION; + } + + /* Now get the PDE for the address itself */ + PointerPde = MiAddressToPde(Address); + if (!PointerPde->u.Hard.Valid) + { + /* Do the swap, we should be good to go */ + Index = ((ULONG_PTR)Address - (ULONG_PTR)MmSessionBase) >> 22; + PointerPde->u.Long = MmSessionSpace->PageTables[Index].u.Long; + if (PointerPde->u.Hard.Valid) return STATUS_WAIT_1; + + /* We had not allocated a page table for this session address yet, fail! */ + DbgPrint("MiCheckPdeForSessionSpace: No Session PDE for VA %p, %p\n", + PointerPde->u.Long, Address); + DbgBreakPoint(); + return STATUS_ACCESS_VIOLATION; + } + + /* It's valid, so there's nothing to do */ + return STATUS_SUCCESS; +} + NTSTATUS FASTCALL MiCheckPdeForPagedPool(IN PVOID Address) @@ -129,9 +418,9 @@ MiCheckPdeForPagedPool(IN PVOID Address) PMMPDE PointerPde; NTSTATUS Status = STATUS_SUCCESS; - /* No session support in ReactOS yet */ - ASSERT(MI_IS_SESSION_ADDRESS(Address) == FALSE); - ASSERT(MI_IS_SESSION_PTE(Address) == FALSE); + /* Check session PDE */ + if (MI_IS_SESSION_ADDRESS(Address)) return MiCheckPdeForSessionSpace(Address); + if (MI_IS_SESSION_PTE(Address)) return MiCheckPdeForSessionSpace(Address); // // Check if this is a fault while trying to access the page table itself @@ -165,15 +454,11 @@ MiCheckPdeForPagedPool(IN PVOID Address) // if (PointerPde->u.Hard.Valid == 0) { -#ifdef _M_AMD64 - ASSERT(FALSE); -#else // // Copy it from our double-mapped system page directory // InterlockedExchangePte(PointerPde, MmSystemPagePtes[((ULONG_PTR)PointerPde & (SYSTEM_PD_SIZE - 1)) / sizeof(MMPTE)].u.Long); -#endif } // @@ -239,21 +524,21 @@ MiZeroPfn(IN PFN_NUMBER PageFrameNumber) NTSTATUS NTAPI MiResolveDemandZeroFault(IN PVOID Address, - IN ULONG Protection, + IN PMMPTE PointerPte, IN PEPROCESS Process, IN KIRQL OldIrql) { PFN_NUMBER PageFrameNumber = 0; - PMMPTE PointerPte = MiAddressToPte(Address); MMPTE TempPte; BOOLEAN NeedZero = FALSE, HaveLock = FALSE; ULONG Color; + PMMPFN Pfn1; DPRINT("ARM3 Demand Zero Page Fault Handler for address: %p in process: %p\n", Address, Process); /* Must currently only be called by paging path */ - if ((Process) && (OldIrql == MM_NOIRQL)) + if ((Process > HYDRA_PROCESS) && (OldIrql == MM_NOIRQL)) { /* Sanity check */ ASSERT(MI_IS_PAGE_TABLE_ADDRESS(PointerPte)); @@ -273,8 +558,16 @@ MiResolveDemandZeroFault(IN PVOID Address, /* Check if we need a zero page */ NeedZero = (OldIrql != MM_NOIRQL); - /* Get the next system page color */ - Color = MI_GET_NEXT_COLOR(); + /* Session-backed image views must be zeroed */ + if ((Process == HYDRA_PROCESS) && + ((MI_IS_SESSION_IMAGE_ADDRESS(Address)) || + ((Address >= MiSessionViewStart) && (Address < MiSessionSpaceWs)))) + { + NeedZero = TRUE; + } + + /* Hardcode unknown color */ + Color = 0xFFFFFFFF; } /* Check if the PFN database should be acquired */ @@ -287,58 +580,82 @@ MiResolveDemandZeroFault(IN PVOID Address, /* We either manually locked the PFN DB, or already came with it locked */ ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); - - /* Do we need a zero page? */ ASSERT(PointerPte->u.Hard.Valid == 0); + + /* Assert we have enough pages */ + ASSERT(MmAvailablePages >= 32); + #if MI_TRACE_PFNS if (UserPdeFault) MI_SET_USAGE(MI_USAGE_PAGE_TABLE); if (!UserPdeFault) MI_SET_USAGE(MI_USAGE_DEMAND_ZERO); #endif if (Process) MI_SET_PROCESS2(Process->ImageFileName); if (!Process) MI_SET_PROCESS2("Kernel Demand 0"); - if ((NeedZero) && (Process)) + + /* Do we need a zero page? */ + if (Color != 0xFFFFFFFF) { /* Try to get one, if we couldn't grab a free page and zero it */ PageFrameNumber = MiRemoveZeroPageSafe(Color); - if (PageFrameNumber) - { - /* We got a genuine zero page, stop worrying about it */ - NeedZero = FALSE; - } - else + if (!PageFrameNumber) { /* We'll need a free page and zero it manually */ PageFrameNumber = MiRemoveAnyPage(Color); + NeedZero = TRUE; } } - else if (!NeedZero) - { - /* Process or system doesn't want a zero page, grab anything */ - PageFrameNumber = MiRemoveAnyPage(Color); - } else { - /* System wants a zero page, obtain one */ - PageFrameNumber = MiRemoveZeroPage(Color); + /* Get a color, and see if we should grab a zero or non-zero page */ + Color = MI_GET_NEXT_COLOR(); + if (!NeedZero) + { + /* Process or system doesn't want a zero page, grab anything */ + PageFrameNumber = MiRemoveAnyPage(Color); + } + else + { + /* System wants a zero page, obtain one */ + PageFrameNumber = MiRemoveZeroPage(Color); + } } /* Initialize it */ MiInitializePfn(PageFrameNumber, PointerPte, TRUE); - /* Increment demand zero faults */ - KeGetCurrentPrcb()->MmDemandZeroCount++; + /* Do we have the lock? */ + if (HaveLock) + { + /* Release it */ + KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); + + /* Update performance counters */ + if (Process > HYDRA_PROCESS) Process->NumberOfPrivatePages++; + } - /* Release PFN lock if needed */ - if (HaveLock) KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); + /* Increment demand zero faults */ + InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount); /* Zero the page if need be */ if (NeedZero) MiZeroPfn(PageFrameNumber); - /* Build the PTE */ - MI_MAKE_HARDWARE_PTE(&TempPte, - PointerPte, - Protection, - PageFrameNumber); + /* Fault on user PDE, or fault on user PTE? */ + if (PointerPte <= MiHighestUserPte) + { + /* User fault, build a user PTE */ + MI_MAKE_HARDWARE_PTE_USER(&TempPte, + PointerPte, + PointerPte->u.Soft.Protection, + PageFrameNumber); + } + else + { + /* This is a user-mode PDE, create a kernel PTE for it */ + MI_MAKE_HARDWARE_PTE(&TempPte, + PointerPte, + PointerPte->u.Soft.Protection, + PageFrameNumber); + } /* Set it dirty if it's a writable page */ if (MI_IS_PAGE_WRITEABLE(&TempPte)) MI_MAKE_DIRTY_PAGE(&TempPte); @@ -346,10 +663,21 @@ MiResolveDemandZeroFault(IN PVOID Address, /* Write it */ MI_WRITE_VALID_PTE(PointerPte, TempPte); + /* Did we manually acquire the lock */ + if (HaveLock) + { + /* Get the PFN entry */ + Pfn1 = MI_PFN_ELEMENT(PageFrameNumber); + + /* Windows does these sanity checks */ + ASSERT(Pfn1->u1.Event == 0); + ASSERT(Pfn1->u3.e1.PrototypePte == 0); + } + // // It's all good now // - DPRINT("Paged pool page has now been paged in\n"); + DPRINT("Demand zero page has now been paged in\n"); return STATUS_PAGE_FAULT_DEMAND_ZERO; } @@ -360,12 +688,14 @@ MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction, IN PMMPTE PointerPte, IN PMMPTE PointerProtoPte, IN KIRQL OldIrql, - IN PMMPFN Pfn1) + IN PMMPFN* LockedProtoPfn) { MMPTE TempPte; - PMMPTE OriginalPte; + PMMPTE OriginalPte, PageTablePte; ULONG_PTR Protection; PFN_NUMBER PageFrameIndex; + PMMPFN Pfn1, Pfn2; + BOOLEAN OriginalProtection, DirtyPage; /* Must be called with an valid prototype PTE, with the PFN lock held */ ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); @@ -378,26 +708,79 @@ MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction, Pfn1 = MiGetPfnEntry(PageFrameIndex); Pfn1->u3.e1.PrototypePte = 1; - /* FIXME: Increment the share count for the page table */ + /* Increment the share count for the page table */ + // FIXME: This doesn't work because we seem to bump the sharecount to two, and MiDeletePte gets annoyed and ASSERTs. + // This could be beause MiDeletePte is now being called from strange code in Rosmm + PageTablePte = MiAddressToPte(PointerPte); + Pfn2 = MiGetPfnEntry(PageTablePte->u.Hard.PageFrameNumber); + //Pfn2->u2.ShareCount++; + DBG_UNREFERENCED_LOCAL_VARIABLE(Pfn2); /* Check where we should be getting the protection information from */ if (PointerPte->u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED) { /* Get the protection from the PTE, there's no real Proto PTE data */ Protection = PointerPte->u.Soft.Protection; + + /* Remember that we did not use the proto protection */ + OriginalProtection = FALSE; } else { /* Get the protection from the original PTE link */ OriginalPte = &Pfn1->OriginalPte; Protection = OriginalPte->u.Soft.Protection; + + /* Remember that we used the original protection */ + OriginalProtection = TRUE; + + /* Check if this was a write on a read only proto */ + if ((StoreInstruction) && !(Protection & MM_READWRITE)) + { + /* Clear the flag */ + StoreInstruction = 0; + } + } + + /* Check if this was a write on a non-COW page */ + DirtyPage = FALSE; + if ((StoreInstruction) && ((Protection & MM_WRITECOPY) != MM_WRITECOPY)) + { + /* Then the page should be marked dirty */ + DirtyPage = TRUE; + + /* ReactOS check */ + ASSERT(Pfn1->OriginalPte.u.Soft.Prototype != 0); + } + + /* Did we get a locked incoming PFN? */ + if (*LockedProtoPfn) + { + /* Drop a reference */ + ASSERT((*LockedProtoPfn)->u3.e2.ReferenceCount >= 1); + MiDereferencePfnAndDropLockCount(*LockedProtoPfn); + *LockedProtoPfn = NULL; } /* Release the PFN lock */ KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); - /* Remove caching bits */ - Protection &= ~(MM_NOCACHE | MM_NOACCESS); + /* Remove special/caching bits */ + Protection &= ~MM_PROTECT_SPECIAL; + + /* Setup caching */ + if (Pfn1->u3.e1.CacheAttribute == MiWriteCombined) + { + /* Write combining, no caching */ + MI_PAGE_DISABLE_CACHE(&TempPte); + MI_PAGE_WRITE_COMBINED(&TempPte); + } + else if (Pfn1->u3.e1.CacheAttribute == MiNonCached) + { + /* Write through, no caching */ + MI_PAGE_DISABLE_CACHE(&TempPte); + MI_PAGE_WRITE_THROUGH(&TempPte); + } /* Check if this is a kernel or user address */ if (Address < MmSystemRangeStart) @@ -411,55 +794,181 @@ MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction, MI_MAKE_HARDWARE_PTE(&TempPte, PointerPte, Protection, PageFrameIndex); } + /* Set the dirty flag if needed */ + if (DirtyPage) TempPte.u.Hard.Dirty = TRUE; + /* Write the PTE */ MI_WRITE_VALID_PTE(PointerPte, TempPte); + /* Reset the protection if needed */ + if (OriginalProtection) Protection = MM_ZERO_ACCESS; + /* Return success */ + ASSERT(PointerPte == MiAddressToPte(Address)); 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) +MiResolveTransitionFault(IN PVOID FaultingAddress, + IN PMMPTE PointerPte, + IN PEPROCESS CurrentProcess, + IN KIRQL OldIrql, + OUT PVOID *InPageBlock) { - MMPTE TempPte; - PMMPFN Pfn1; PFN_NUMBER PageFrameIndex; - NTSTATUS Status; + PMMPFN Pfn1; + MMPTE TempPte; + PMMPTE PointerToPteForProtoPage; + DPRINT1("Transition fault on 0x%p with PTE 0x%p in process %s\n", + FaultingAddress, PointerPte, CurrentProcess->ImageFileName); - /* 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); + /* Windowss does this check */ + ASSERT(*InPageBlock == NULL); - /* Read the prototype PTE and check if it's valid */ - TempPte = *PointerProtoPte; - if (TempPte.u.Hard.Valid == 1) - { - /* One more user of this mapped page */ - PageFrameIndex = PFN_FROM_PTE(&TempPte); - Pfn1 = MiGetPfnEntry(PageFrameIndex); - Pfn1->u2.ShareCount++; + /* ARM3 doesn't support this path */ + ASSERT(OldIrql != MM_NOIRQL); - /* Call it a transition */ - InterlockedIncrement(&KeGetCurrentPrcb()->MmTransitionCount); + /* Capture the PTE and make sure it's in transition format */ + TempPte = *PointerPte; + ASSERT((TempPte.u.Soft.Valid == 0) && + (TempPte.u.Soft.Prototype == 0) && + (TempPte.u.Soft.Transition == 1)); - /* Complete the prototype PTE fault -- this will release the PFN lock */ - return MiCompleteProtoPteFault(StoreInstruction, - Address, - PointerPte, - PointerProtoPte, - OldIrql, - NULL); + /* Get the PFN and the PFN entry */ + PageFrameIndex = TempPte.u.Trans.PageFrameNumber; + DPRINT1("Transition PFN: %lx\n", PageFrameIndex); + Pfn1 = MiGetPfnEntry(PageFrameIndex); + + /* One more transition fault! */ + InterlockedIncrement(&KeGetCurrentPrcb()->MmTransitionCount); + + /* This is from ARM3 -- Windows normally handles this here */ + ASSERT(Pfn1->u4.InPageError == 0); + + /* Not supported in ARM3 */ + ASSERT(Pfn1->u3.e1.ReadInProgress == 0); + + /* Windows checks there's some free pages and this isn't an in-page error */ + ASSERT(MmAvailablePages > 0); + ASSERT(Pfn1->u4.InPageError == 0); + + /* ReactOS checks for this */ + ASSERT(MmAvailablePages > 32); + + /* Was this a transition page in the valid list, or free/zero list? */ + if (Pfn1->u3.e1.PageLocation == ActiveAndValid) + { + /* All Windows does here is a bunch of sanity checks */ + DPRINT1("Transition in active list\n"); + ASSERT((Pfn1->PteAddress >= MiAddressToPte(MmPagedPoolStart)) && + (Pfn1->PteAddress <= MiAddressToPte(MmPagedPoolEnd))); + ASSERT(Pfn1->u2.ShareCount != 0); + ASSERT(Pfn1->u3.e2.ReferenceCount != 0); + } + else + { + /* Otherwise, the page is removed from its list */ + DPRINT1("Transition page in free/zero list\n"); + MiUnlinkPageFromList(Pfn1); + MiReferenceUnusedPageAndBumpLockCount(Pfn1); + } + + /* At this point, there should no longer be any in-page errors */ + ASSERT(Pfn1->u4.InPageError == 0); + + /* Check if this was a PFN with no more share references */ + if (Pfn1->u2.ShareCount == 0) MiDropLockCount(Pfn1); + + /* Bump the share count and make the page valid */ + Pfn1->u2.ShareCount++; + Pfn1->u3.e1.PageLocation = ActiveAndValid; + + /* Prototype PTEs are in paged pool, which itself might be in transition */ + if (FaultingAddress >= MmSystemRangeStart) + { + /* Check if this is a paged pool PTE in transition state */ + PointerToPteForProtoPage = MiAddressToPte(PointerPte); + TempPte = *PointerToPteForProtoPage; + if ((TempPte.u.Hard.Valid == 0) && (TempPte.u.Soft.Transition == 1)) + { + /* This isn't yet supported */ + DPRINT1("Double transition fault not yet supported\n"); + ASSERT(FALSE); + } + } + + /* Build the transition PTE -- maybe a macro? */ + ASSERT(PointerPte->u.Hard.Valid == 0); + ASSERT(PointerPte->u.Trans.Prototype == 0); + ASSERT(PointerPte->u.Trans.Transition == 1); + TempPte.u.Long = (PointerPte->u.Long & ~0xFFF) | + (MmProtectToPteMask[PointerPte->u.Trans.Protection]) | + MiDetermineUserGlobalPteMask(PointerPte); + + /* Is the PTE writeable? */ + if (((Pfn1->u3.e1.Modified) && (TempPte.u.Hard.Write)) && + (TempPte.u.Hard.CopyOnWrite == 0)) + { + /* Make it dirty */ + TempPte.u.Hard.Dirty = TRUE; + } + else + { + /* Make it clean */ + TempPte.u.Hard.Dirty = FALSE; + } + + /* Write the valid PTE */ + MI_WRITE_VALID_PTE(PointerPte, TempPte); + + /* Return success */ + return STATUS_PAGE_FAULT_TRANSITION; +} + +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, PteContents; + PMMPFN Pfn1; + PFN_NUMBER PageFrameIndex; + NTSTATUS Status; + PVOID InPageBlock = NULL; + + /* 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 and check if it's valid */ + TempPte = *PointerProtoPte; + if (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, + OutPfn); } /* Make sure there's some protection mask */ @@ -471,19 +980,72 @@ MiResolveProtoPteFault(IN BOOLEAN StoreInstruction, return STATUS_ACCESS_VIOLATION; } - /* This is the only thing we support right now */ - ASSERT(TempPte.u.Soft.PageFileHigh == 0); - ASSERT(TempPte.u.Proto.ReadOnly == 0); - ASSERT(PointerPte > MiHighestUserPte); + /* There is no such thing as a decommitted prototype PTE */ + NT_ASSERT(TempPte.u.Long != MmDecommittedPte.u.Long); + + /* Check for access rights on the PTE proper */ + PteContents = *PointerPte; + if (PteContents.u.Soft.PageFileHigh != MI_PTE_LOOKUP_NEEDED) + { + if (!PteContents.u.Proto.ReadOnly) + { + /* Check for page acess in software */ + Status = MiAccessCheck(PointerProtoPte, + StoreInstruction, + KernelMode, + TempPte.u.Soft.Protection, + TrapInformation, + TRUE); + ASSERT(Status == STATUS_SUCCESS); + + /* Check for copy on write page */ + if ((TempPte.u.Soft.Protection & MM_WRITECOPY) == MM_WRITECOPY) + { + /* Not yet supported */ + ASSERT(FALSE); + } + } + } + else + { + /* Check for copy on write page */ + if ((PteContents.u.Soft.Protection & MM_WRITECOPY) == MM_WRITECOPY) + { + /* Not yet supported */ + ASSERT(FALSE); + } + } + + /* Check for clone PTEs */ + if (PointerPte <= MiHighestUserPte) ASSERT(Process->CloneRoot == NULL); + + /* We don't support mapped files yet */ ASSERT(TempPte.u.Soft.Prototype == 0); - ASSERT(TempPte.u.Soft.Transition == 0); - /* Resolve the demand zero fault */ - Status = MiResolveDemandZeroFault(Address, - (ULONG)PointerProtoPte->u.Soft.Protection, - Process, - OldIrql); - ASSERT(NT_SUCCESS(Status)); + /* We might however have transition PTEs */ + if (TempPte.u.Soft.Transition == 1) + { + /* Resolve the transition fault */ + ASSERT(OldIrql != MM_NOIRQL); + Status = MiResolveTransitionFault(Address, + PointerProtoPte, + Process, + OldIrql, + &InPageBlock); + ASSERT(NT_SUCCESS(Status)); + } + else + { + /* We also don't support paged out pages */ + ASSERT(TempPte.u.Soft.PageFileHigh == 0); + + /* Resolve the demand zero fault */ + Status = MiResolveDemandZeroFault(Address, + PointerProtoPte, + Process, + OldIrql); + ASSERT(NT_SUCCESS(Status)); + } /* Complete the prototype PTE fault -- this will release the PFN lock */ ASSERT(PointerPte->u.Hard.Valid == 0); @@ -492,7 +1054,7 @@ MiResolveProtoPteFault(IN BOOLEAN StoreInstruction, PointerPte, PointerProtoPte, OldIrql, - NULL); + OutPfn); } NTSTATUS @@ -504,12 +1066,15 @@ MiDispatchFault(IN BOOLEAN StoreInstruction, IN BOOLEAN Recursive, IN PEPROCESS Process, IN PVOID TrapInformation, - IN PVOID Vad) + IN PMMVAD Vad) { MMPTE TempPte; KIRQL OldIrql, LockIrql; NTSTATUS Status; PMMPTE SuperProtoPte; + PMMPFN Pfn1, OutPfn = NULL; + PFN_NUMBER PageFrameIndex; + PFN_COUNT PteCount, ProcessedPtes; DPRINT("ARM3 Page Fault Dispatcher for address: %p in process: %p\n", Address, Process); @@ -545,86 +1110,219 @@ MiDispatchFault(IN BOOLEAN StoreInstruction, /* Has the PTE been made valid yet? */ if (!SuperProtoPte->u.Hard.Valid) { - UNIMPLEMENTED; - while (TRUE); + ASSERT(FALSE); } - else + else if (PointerPte->u.Hard.Valid == 1) { - /* Resolve the fault -- this will release the PFN lock */ - ASSERT(PointerPte->u.Hard.Valid == 0); - 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; + ASSERT(FALSE); } + + /* Resolve the fault -- this will release the PFN lock */ + Status = MiResolveProtoPteFault(StoreInstruction, + Address, + PointerPte, + PointerProtoPte, + &OutPfn, + 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; } else { - /* We currently only handle very limited paths */ - ASSERT(PointerPte->u.Soft.Prototype == 1); + /* We only handle the lookup path */ ASSERT(PointerPte->u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED); + /* Is there a non-image VAD? */ + if ((Vad) && + (Vad->u.VadFlags.VadType != VadImageMap) && + !(Vad->u2.VadFlags2.ExtendableFile)) + { + /* One day, ReactOS will cluster faults */ + ASSERT(Address <= MM_HIGHEST_USER_ADDRESS); + DPRINT("Should cluster fault, but won't\n"); + } + + /* Only one PTE to handle for now */ + PteCount = 1; + ProcessedPtes = 0; + /* Lock the PFN database */ LockIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); - /* For our current usage, this should be true */ + /* We only handle the valid path */ ASSERT(SuperProtoPte->u.Hard.Valid == 1); - ASSERT(TempPte.u.Hard.Valid == 0); + + /* Capture the PTE */ + TempPte = *PointerProtoPte; + + /* Loop to handle future case of clustered faults */ + while (TRUE) + { + /* For our current usage, this should be true */ + if (TempPte.u.Hard.Valid == 1) + { + /* Bump the share count on the PTE */ + PageFrameIndex = PFN_FROM_PTE(&TempPte); + Pfn1 = MI_PFN_ELEMENT(PageFrameIndex); + Pfn1->u2.ShareCount++; + } + else if ((TempPte.u.Soft.Prototype == 0) && + (TempPte.u.Soft.Transition == 1)) + { + /* This is a standby page, bring it back from the cache */ + PageFrameIndex = TempPte.u.Trans.PageFrameNumber; + DPRINT("oooh, shiny, a soft fault! 0x%lx\n", PageFrameIndex); + Pfn1 = MI_PFN_ELEMENT(PageFrameIndex); + ASSERT(Pfn1->u3.e1.PageLocation != ActiveAndValid); + + /* Should not yet happen in ReactOS */ + ASSERT(Pfn1->u3.e1.ReadInProgress == 0); + ASSERT(Pfn1->u4.InPageError == 0); + + /* Get the page */ + MiUnlinkPageFromList(Pfn1); + + /* Bump its reference count */ + ASSERT(Pfn1->u2.ShareCount == 0); + InterlockedIncrement16((PSHORT)&Pfn1->u3.e2.ReferenceCount); + Pfn1->u2.ShareCount++; + + /* Make it valid again */ + /* This looks like another macro.... */ + Pfn1->u3.e1.PageLocation = ActiveAndValid; + ASSERT(PointerProtoPte->u.Hard.Valid == 0); + ASSERT(PointerProtoPte->u.Trans.Prototype == 0); + ASSERT(PointerProtoPte->u.Trans.Transition == 1); + TempPte.u.Long = (PointerProtoPte->u.Long & ~0xFFF) | + MmProtectToPteMask[PointerProtoPte->u.Trans.Protection]; + TempPte.u.Hard.Valid = 1; + TempPte.u.Hard.Accessed = 1; + + /* Is the PTE writeable? */ + if (((Pfn1->u3.e1.Modified) && (TempPte.u.Hard.Write)) && + (TempPte.u.Hard.CopyOnWrite == 0)) + { + /* Make it dirty */ + TempPte.u.Hard.Dirty = TRUE; + } + else + { + /* Make it clean */ + TempPte.u.Hard.Dirty = FALSE; + } + + /* Write the valid PTE */ + MI_WRITE_VALID_PTE(PointerProtoPte, TempPte); + ASSERT(PointerPte->u.Hard.Valid == 0); + } + else + { + /* Page is invalid, get out of the loop */ + break; + } + + /* One more done, was it the last? */ + if (++ProcessedPtes == PteCount) + { + /* Complete the fault */ + MiCompleteProtoPteFault(StoreInstruction, + Address, + PointerPte, + PointerProtoPte, + LockIrql, + &OutPfn); + + /* THIS RELEASES THE PFN LOCK! */ + break; + } + + /* No clustered faults yet */ + ASSERT(FALSE); + } + + /* Did we resolve the fault? */ + if (ProcessedPtes) + { + /* Bump the transition count */ + InterlockedExchangeAddSizeT(&KeGetCurrentPrcb()->MmTransitionCount, ProcessedPtes); + ProcessedPtes--; + + /* Loop all the processing we did */ + ASSERT(ProcessedPtes == 0); + + /* Complete this as a transition fault */ + ASSERT(OldIrql == KeGetCurrentIrql()); + ASSERT(OldIrql <= APC_LEVEL); + ASSERT(KeAreAllApcsDisabled() == TRUE); + return STATUS_PAGE_FAULT_TRANSITION; + } + + /* We did not -- PFN lock is still held, prepare to resolve prototype PTE fault */ + OutPfn = MI_PFN_ELEMENT(SuperProtoPte->u.Hard.PageFrameNumber); + MiReferenceUsedPageAndBumpLockCount(OutPfn); + ASSERT(OutPfn->u3.e2.ReferenceCount > 1); + ASSERT(PointerPte->u.Hard.Valid == 0); /* Resolve the fault -- this will release the PFN lock */ Status = MiResolveProtoPteFault(StoreInstruction, Address, PointerPte, PointerProtoPte, - NULL, + &OutPfn, NULL, NULL, Process, LockIrql, TrapInformation); - ASSERT(Status == STATUS_SUCCESS); + //ASSERT(Status != STATUS_ISSUE_PAGING_IO); + //ASSERT(Status != STATUS_REFAULT); + //ASSERT(Status != STATUS_PTE_CHANGED); + + /* Did the routine clean out the PFN or should we? */ + if (OutPfn) + { + /* We had a locked PFN, so acquire the PFN lock to dereference it */ + ASSERT(PointerProtoPte != NULL); + OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); + + /* Dereference the locked PFN */ + MiDereferencePfnAndDropLockCount(OutPfn); + ASSERT(OutPfn->u3.e2.ReferenceCount >= 1); + + /* And now release the lock */ + KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); + } /* Complete this as a transition fault */ ASSERT(OldIrql == KeGetCurrentIrql()); ASSERT(OldIrql <= APC_LEVEL); ASSERT(KeAreAllApcsDisabled() == TRUE); - return STATUS_PAGE_FAULT_TRANSITION; + return Status; } } // - // The PTE must be invalid + // The PTE must be invalid but not completely empty. It must also not be a + // prototype PTE as that scenario should've been handled above. These are + // all Windows checks // ASSERT(TempPte.u.Hard.Valid == 0); - - /* Check if the PTE is completely empty */ - if (TempPte.u.Long == 0) - { - /* The address is not from any pageable area! */ - KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA, - (ULONG_PTR)Address, - StoreInstruction, - (ULONG_PTR)TrapInformation, - 2); - } + ASSERT(TempPte.u.Soft.Prototype == 0); + ASSERT(TempPte.u.Long != 0); // - // No prototype, transition or page file software PTEs in ARM3 yet + // No transition or page file software PTEs in ARM3 yet, so this must be a + // demand zero page. These are all ReactOS checks // - ASSERT(TempPte.u.Soft.Prototype == 0); ASSERT(TempPte.u.Soft.Transition == 0); ASSERT(TempPte.u.Soft.PageFileHigh == 0); @@ -633,7 +1331,7 @@ MiDispatchFault(IN BOOLEAN StoreInstruction, // we want. Go handle it! // Status = MiResolveDemandZeroFault(Address, - (ULONG)PointerPte->u.Soft.Protection, + PointerPte, Process, MM_NOIRQL); ASSERT(KeAreAllApcsDisabled() == TRUE); @@ -679,17 +1377,75 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, PMMVAD Vad; PFN_NUMBER PageFrameIndex; ULONG Color; - + BOOLEAN IsSessionAddress; + PMMPFN Pfn1; DPRINT("ARM3 FAULT AT: %p\n", Address); /* Check for page fault on high IRQL */ if (OldIrql > APC_LEVEL) { - // There are some special cases where this is okay, but not in ARM3 yet - DbgPrint("MM:***PAGE FAULT AT IRQL > 1 Va %p, IRQL %lx\n", - Address, - OldIrql); - ASSERT(OldIrql <= APC_LEVEL); +#if (_MI_PAGING_LEVELS < 3) + /* Could be a page table for paged pool, which we'll allow */ + if (MI_IS_SYSTEM_PAGE_TABLE_ADDRESS(Address)) MiSynchronizeSystemPde((PMMPDE)PointerPte); + MiCheckPdeForPagedPool(Address); +#endif + /* Check if any of the top-level pages are invalid */ + if ( +#if (_MI_PAGING_LEVELS == 4) + (PointerPxe->u.Hard.Valid == 0) || +#endif +#if (_MI_PAGING_LEVELS >= 3) + (PointerPpe->u.Hard.Valid == 0) || +#endif + (PointerPde->u.Hard.Valid == 0) || + (PointerPte->u.Hard.Valid == 0)) + { + /* This fault is not valid, print out some debugging help */ + DbgPrint("MM:***PAGE FAULT AT IRQL > 1 Va %p, IRQL %lx\n", + Address, + OldIrql); + if (TrapInformation) + { + PKTRAP_FRAME TrapFrame = TrapInformation; +#ifdef _M_IX86 + DbgPrint("MM:***EIP %p, EFL %p\n", TrapFrame->Eip, TrapFrame->EFlags); + DbgPrint("MM:***EAX %p, ECX %p EDX %p\n", TrapFrame->Eax, TrapFrame->Ecx, TrapFrame->Edx); + DbgPrint("MM:***EBX %p, ESI %p EDI %p\n", TrapFrame->Ebx, TrapFrame->Esi, TrapFrame->Edi); +#elif defined(_M_AMD64) + DbgPrint("MM:***RIP %p, EFL %p\n", TrapFrame->Rip, TrapFrame->EFlags); + DbgPrint("MM:***RAX %p, RCX %p RDX %p\n", TrapFrame->Rax, TrapFrame->Rcx, TrapFrame->Rdx); + DbgPrint("MM:***RBX %p, RSI %p RDI %p\n", TrapFrame->Rbx, TrapFrame->Rsi, TrapFrame->Rdi); +#endif + } + + /* Tell the trap handler to fail */ + return STATUS_IN_PAGE_ERROR | 0x10000000; + } + + /* Not yet implemented in ReactOS */ + ASSERT(MI_IS_PAGE_LARGE(PointerPde) == FALSE); + ASSERT(((StoreInstruction) && (PointerPte->u.Hard.CopyOnWrite)) == FALSE); + + /* Check if this was a write */ + if (StoreInstruction) + { + /* Was it to a read-only page? */ + Pfn1 = MI_PFN_ELEMENT(PointerPte->u.Hard.PageFrameNumber); + if (!(PointerPte->u.Long & PTE_READWRITE) && + !(Pfn1->OriginalPte.u.Soft.Protection & MM_READWRITE)) + { + /* Crash with distinguished bugcheck code */ + KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY, + (ULONG_PTR)Address, + PointerPte->u.Long, + (ULONG_PTR)TrapInformation, + 10); + } + } + + /* Nothing is actually wrong */ + DPRINT1("Fault at IRQL %u is ok (%p)\n", OldIrql, Address); + return STATUS_SUCCESS; } /* Check for kernel fault address */ @@ -698,86 +1454,138 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, /* Bail out, if the fault came from user mode */ if (Mode == UserMode) return STATUS_ACCESS_VIOLATION; - /* PXEs and PPEs for kernel mode are mapped for everything we need */ -#if (_MI_PAGING_LEVELS >= 3) +#if (_MI_PAGING_LEVELS == 2) + if (MI_IS_SYSTEM_PAGE_TABLE_ADDRESS(Address)) MiSynchronizeSystemPde((PMMPDE)PointerPte); + MiCheckPdeForPagedPool(Address); +#endif + + /* Check if the higher page table entries are invalid */ if ( #if (_MI_PAGING_LEVELS == 4) + /* AMD64 system, check if PXE is invalid */ (PointerPxe->u.Hard.Valid == 0) || #endif - (PointerPpe->u.Hard.Valid == 0)) +#if (_MI_PAGING_LEVELS >= 3) + /* PAE/AMD64 system, check if PPE is invalid */ + (PointerPpe->u.Hard.Valid == 0) || +#endif + /* Always check if the PDE is valid */ + (PointerPde->u.Hard.Valid == 0)) { - /* The address is not from any pageable area! */ + /* PXE/PPE/PDE (still) not valid, kill the system */ KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA, (ULONG_PTR)Address, StoreInstruction, (ULONG_PTR)TrapInformation, 2); } -#endif -#if (_MI_PAGING_LEVELS == 2) - /* Check if we have a situation that might need synchronization - of the PDE with the system page directory */ - if (MI_IS_SYSTEM_PAGE_TABLE_ADDRESS(Address)) - { - /* This could be a paged pool commit with an unsychronized PDE. - NOTE: This way it works on x86, verify for other architectures! */ - if (MiSynchronizeSystemPde((PMMPDE)PointerPte)) return STATUS_SUCCESS; - } -#endif + /* Not handling session faults yet */ + IsSessionAddress = MI_IS_SESSION_ADDRESS(Address); - /* Check if the PDE is invalid */ - if (PointerPde->u.Hard.Valid == 0) + /* The PDE is valid, so read the PTE */ + TempPte = *PointerPte; + if (TempPte.u.Hard.Valid == 1) { + /* Check if this was system space or session space */ + if (!IsSessionAddress) + { + /* Check if the PTE is still valid under PFN lock */ + OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); + TempPte = *PointerPte; + if (TempPte.u.Hard.Valid) + { + /* Check if this was a write */ + if (StoreInstruction) + { + /* Was it to a read-only page? */ + Pfn1 = MI_PFN_ELEMENT(PointerPte->u.Hard.PageFrameNumber); + if (!(PointerPte->u.Long & PTE_READWRITE) && + !(Pfn1->OriginalPte.u.Soft.Protection & MM_READWRITE)) + { + /* Crash with distinguished bugcheck code */ + KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY, + (ULONG_PTR)Address, + PointerPte->u.Long, + (ULONG_PTR)TrapInformation, + 11); + } + } + } + + /* Release PFN lock and return all good */ + KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); + return STATUS_SUCCESS; + } + } #if (_MI_PAGING_LEVELS == 2) - /* Sync this PDE and check, if that made it valid */ - if (!MiSynchronizeSystemPde(PointerPde)) -#endif + /* Check if this was a session PTE that needs to remap the session PDE */ + if (MI_IS_SESSION_PTE(Address)) + { + /* Do the remapping */ + Status = MiCheckPdeForSessionSpace(Address); + if (!NT_SUCCESS(Status)) { - /* PDE (still) not valid, kill the system */ + /* It failed, this address is invalid */ KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA, (ULONG_PTR)Address, StoreInstruction, (ULONG_PTR)TrapInformation, - 2); + 6); } } +#else - /* The PDE is valid, so read the PTE */ - TempPte = *PointerPte; - if (TempPte.u.Hard.Valid == 1) +_WARN("Session space stuff is not implemented yet!") + +#endif + + /* Check for a fault on the page table or hyperspace */ + if (MI_IS_PAGE_TABLE_OR_HYPER_ADDRESS(Address)) { - // - // Only two things can go wrong here: - // Executing NX page (we couldn't care less) - // Writing to a read-only page (the stuff ARM3 works with is write, - // so again, moot point). - // - - // - // Otherwise, the PDE was probably invalid, and all is good now - // - return STATUS_SUCCESS; +#if (_MI_PAGING_LEVELS < 3) + /* Windows does this check but I don't understand why -- it's done above! */ + ASSERT(MiCheckPdeForPagedPool(Address) != STATUS_WAIT_1); +#endif + /* Handle this as a user mode fault */ + goto UserFault; } /* Get the current thread */ CurrentThread = PsGetCurrentThread(); - // Check for a fault on the page table or hyperspace - if (MI_IS_PAGE_TABLE_OR_HYPER_ADDRESS(Address)) + /* What kind of address is this */ + if (!IsSessionAddress) { - ASSERT(TempPte.u.Long != (MM_READWRITE << MM_PTE_SOFTWARE_PROTECTION_BITS)); - ASSERT(TempPte.u.Soft.Prototype == 0); + /* Use the system working set */ + WorkingSet = &MmSystemCacheWs; + CurrentProcess = NULL; - /* Use the process working set */ - CurrentProcess = (PEPROCESS)CurrentThread->Tcb.ApcState.Process; - WorkingSet = &CurrentProcess->Vm; + /* Make sure we don't have a recursive working set lock */ + if ((CurrentThread->OwnsProcessWorkingSetExclusive) || + (CurrentThread->OwnsProcessWorkingSetShared) || + (CurrentThread->OwnsSystemWorkingSetExclusive) || + (CurrentThread->OwnsSystemWorkingSetShared) || + (CurrentThread->OwnsSessionWorkingSetExclusive) || + (CurrentThread->OwnsSessionWorkingSetShared)) + { + /* Fail */ + return STATUS_IN_PAGE_ERROR | 0x10000000; + } } else { - /* Otherwise use the system working set */ - WorkingSet = &MmSystemCacheWs; - CurrentProcess = NULL; + /* Use the session process and working set */ + CurrentProcess = HYDRA_PROCESS; + WorkingSet = &MmSessionSpace->GlobalVirtualAddress->Vm; + + /* Make sure we don't have a recursive working set lock */ + if ((CurrentThread->OwnsSessionWorkingSetExclusive) || + (CurrentThread->OwnsSessionWorkingSetShared)) + { + /* Fail */ + return STATUS_IN_PAGE_ERROR | 0x10000000; + } } /* Acquire the working set lock */ @@ -788,17 +1596,55 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, TempPte = *PointerPte; if (TempPte.u.Hard.Valid == 1) { - // Only two things can go wrong here: - // Executing NX page (we couldn't care less) - // Writing to a read-only page (the stuff ARM3 works with is write, - // so again, moot point). - ASSERT(TempPte.u.Hard.Write == 1); + /* Check if this was a write */ + if (StoreInstruction) + { + /* Was it to a read-only page that is not copy on write? */ + Pfn1 = MI_PFN_ELEMENT(PointerPte->u.Hard.PageFrameNumber); + if (!(TempPte.u.Long & PTE_READWRITE) && + !(Pfn1->OriginalPte.u.Soft.Protection & MM_READWRITE) && + !(TempPte.u.Hard.CopyOnWrite)) + { + /* Case not yet handled */ + ASSERT(!IsSessionAddress); + + /* Crash with distinguished bugcheck code */ + KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY, + (ULONG_PTR)Address, + TempPte.u.Long, + (ULONG_PTR)TrapInformation, + 12); + } + } + + /* Check for read-only write in session space */ + if ((IsSessionAddress) && + (StoreInstruction) && + !(TempPte.u.Hard.Write)) + { + /* Sanity check */ + ASSERT(MI_IS_SESSION_IMAGE_ADDRESS(Address)); + + /* Was this COW? */ + if (TempPte.u.Hard.CopyOnWrite == 0) + { + /* Then this is not allowed */ + KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY, + (ULONG_PTR)Address, + (ULONG_PTR)TempPte.u.Long, + (ULONG_PTR)TrapInformation, + 13); + } + + /* Otherwise, handle COW */ + ASSERT(FALSE); + } /* Release the working set */ MiUnlockWorkingSet(CurrentThread, WorkingSet); KeLowerIrql(LockIrql); - // Otherwise, the PDE was probably invalid, and all is good now + /* Otherwise, the PDE was probably invalid, and all is good now */ return STATUS_SUCCESS; } @@ -823,6 +1669,17 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, /* Get the prototype PTE! */ ProtoPte = MiProtoPteToPte(&TempPte); + + /* Do we need to locate the prototype PTE in session space? */ + if ((IsSessionAddress) && + (TempPte.u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED)) + { + /* Yep, go find it as well as the VAD for it */ + ProtoPte = MiCheckVirtualAddress(Address, + &ProtectionCode, + &Vad); + ASSERT(ProtoPte != NULL); + } } else { @@ -839,20 +1696,24 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, (ULONG_PTR)TrapInformation, 1); } + } - /* Check for demand page */ - if ((StoreInstruction) && !(TempPte.u.Hard.Valid)) + /* Check for demand page */ + if ((StoreInstruction) && + !(ProtoPte) && + !(IsSessionAddress) && + !(TempPte.u.Hard.Valid)) + { + /* Get the protection code */ + ASSERT(TempPte.u.Soft.Transition == 0); + if (!(TempPte.u.Soft.Protection & MM_READWRITE)) { - /* Get the protection code */ - if (!(TempPte.u.Soft.Protection & MM_READWRITE)) - { - /* Bugcheck the system! */ - KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY, - (ULONG_PTR)Address, - TempPte.u.Long, - (ULONG_PTR)TrapInformation, - 14); - } + /* Bugcheck the system! */ + KeBugCheckEx(ATTEMPTED_WRITE_TO_READONLY_MEMORY, + (ULONG_PTR)Address, + TempPte.u.Long, + (ULONG_PTR)TrapInformation, + 14); } } @@ -877,56 +1738,48 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, } /* This is a user fault */ +UserFault: CurrentThread = PsGetCurrentThread(); CurrentProcess = (PEPROCESS)CurrentThread->Tcb.ApcState.Process; /* Lock the working set */ MiLockProcessWorkingSet(CurrentProcess, CurrentThread); -#if (_MI_PAGING_LEVELS == 2) - ASSERT(PointerPde->u.Hard.LargePage == 0); -#endif - - /* Check if this address range belongs to a valid allocation (VAD) */ - ProtoPte = MiCheckVirtualAddress(Address, &ProtectionCode, &Vad); - if (ProtectionCode == MM_NOACCESS) - { - /* This is a bogus VA */ - MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); - return STATUS_ACCESS_VIOLATION; - } - #if (_MI_PAGING_LEVELS == 4) +// Note to Timo: You should call MiCheckVirtualAddress and also check if it's zero pte +// also this is missing the page count increment /* Check if the PXE is valid */ if (PointerPxe->u.Hard.Valid == 0) { /* Right now, we only handle scenarios where the PXE is totally empty */ ASSERT(PointerPxe->u.Long == 0); - +#if 0 /* Resolve a demand zero fault */ Status = MiResolveDemandZeroFault(PointerPpe, MM_READWRITE, CurrentProcess, MM_NOIRQL); - +#endif /* We should come back with a valid PXE */ ASSERT(PointerPxe->u.Hard.Valid == 1); } #endif #if (_MI_PAGING_LEVELS >= 3) +// Note to Timo: You should call MiCheckVirtualAddress and also check if it's zero pte +// also this is missing the page count increment /* Check if the PPE is valid */ if (PointerPpe->u.Hard.Valid == 0) { /* Right now, we only handle scenarios where the PPE is totally empty */ ASSERT(PointerPpe->u.Long == 0); - +#if 0 /* Resolve a demand zero fault */ Status = MiResolveDemandZeroFault(PointerPde, MM_READWRITE, CurrentProcess, MM_NOIRQL); - +#endif /* We should come back with a valid PPE */ ASSERT(PointerPpe->u.Hard.Valid == 1); } @@ -938,18 +1791,37 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, /* Right now, we only handle scenarios where the PDE is totally empty */ ASSERT(PointerPde->u.Long == 0); - /* Right now, we expect a valid protection mask on the VAD */ - ASSERT(ProtectionCode != MM_NOACCESS); - /* And go dispatch the fault on the PDE. This should handle the demand-zero */ #if MI_TRACE_PFNS UserPdeFault = TRUE; #endif - /* Resolve a demand zero fault */ - Status = MiResolveDemandZeroFault(PointerPte, - MM_READWRITE, - CurrentProcess, - MM_NOIRQL); + MiCheckVirtualAddress(Address, &ProtectionCode, &Vad); + if (ProtectionCode == MM_NOACCESS) + { +#if (_MI_PAGING_LEVELS == 2) + /* Could be a page table for paged pool */ + MiCheckPdeForPagedPool(Address); +#endif + /* Has the code above changed anything -- is this now a valid PTE? */ + Status = (PointerPde->u.Hard.Valid == 1) ? STATUS_SUCCESS : STATUS_ACCESS_VIOLATION; + + /* Either this was a bogus VA or we've fixed up a paged pool PDE */ + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + return Status; + } + + /* Write a demand-zero PDE */ + MI_WRITE_INVALID_PTE(PointerPde, DemandZeroPde); + + /* Dispatch the fault */ + Status = MiDispatchFault(TRUE, + PointerPte, + PointerPde, + NULL, + FALSE, + PsGetCurrentProcess(), + TrapInformation, + NULL); #if MI_TRACE_PFNS UserPdeFault = FALSE; #endif @@ -957,18 +1829,50 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, ASSERT(KeAreAllApcsDisabled() == TRUE); ASSERT(PointerPde->u.Hard.Valid == 1); } + else + { + /* Not yet implemented in ReactOS */ + ASSERT(MI_IS_PAGE_LARGE(PointerPde) == FALSE); + } - /* Now capture the PTE. Ignore virtual faults for now */ + /* Now capture the PTE. */ TempPte = *PointerPte; - ASSERT(TempPte.u.Hard.Valid == 0); + + /* Check if the PTE is valid */ + if (TempPte.u.Hard.Valid) + { + /* Check if this is a write on a readonly PTE */ + if (StoreInstruction) + { + /* Is this a copy on write PTE? */ + if (TempPte.u.Hard.CopyOnWrite) + { + /* Not supported yet */ + ASSERT(FALSE); + } + + /* Is this a read-only PTE? */ + if (!TempPte.u.Hard.Write) + { + /* Return the status */ + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + return STATUS_ACCESS_VIOLATION; + } + } + + /* FIXME: Execution is ignored for now, since we don't have no-execute pages yet */ + + /* The fault has already been resolved by a different thread */ + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + return STATUS_SUCCESS; + } /* Quick check for demand-zero */ if (TempPte.u.Long == (MM_READWRITE << MM_PTE_SOFTWARE_PROTECTION_BITS)) { /* Resolve the fault */ - MI_WRITE_INVALID_PDE(PointerPde, DemandZeroPde); MiResolveDemandZeroFault(Address, - (ULONG)PointerPte->u.Soft.Protection, + PointerPte, CurrentProcess, MM_NOIRQL); @@ -977,128 +1881,287 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction, return STATUS_PAGE_FAULT_DEMAND_ZERO; } - /* Make sure it's not a prototype PTE */ - ASSERT(TempPte.u.Soft.Prototype == 0); - - /* Check for non-demand zero PTE */ - if (TempPte.u.Long != 0) + /* Check for zero PTE */ + if (TempPte.u.Long == 0) { - /* This is a page fault */ + /* Check if this address range belongs to a valid allocation (VAD) */ + ProtoPte = MiCheckVirtualAddress(Address, &ProtectionCode, &Vad); + if (ProtectionCode == MM_NOACCESS) + { +#if (_MI_PAGING_LEVELS == 2) + /* Could be a page table for paged pool */ + MiCheckPdeForPagedPool(Address); +#endif + /* Has the code above changed anything -- is this now a valid PTE? */ + Status = (PointerPte->u.Hard.Valid == 1) ? STATUS_SUCCESS : STATUS_ACCESS_VIOLATION; - /* FIXME: Run MiAccessCheck */ + /* Either this was a bogus VA or we've fixed up a paged pool PDE */ + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + return Status; + } - /* Dispatch the fault */ - Status = MiDispatchFault(StoreInstruction, - Address, - PointerPte, - NULL, - FALSE, - PsGetCurrentProcess(), - TrapInformation, - NULL); + /* + * Check if this is a real user-mode address or actually a kernel-mode + * page table for a user mode address + */ + if (Address <= MM_HIGHEST_USER_ADDRESS) + { + /* Add an additional page table reference */ + MiIncrementPageTableReferences(Address); + } - /* Return the status */ - ASSERT(NT_SUCCESS(Status)); - ASSERT(KeGetCurrentIrql() <= APC_LEVEL); - MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); - return Status; - } + /* Is this a guard page? */ + if ((ProtectionCode & MM_PROTECT_SPECIAL) == MM_GUARDPAGE) + { + /* The VAD protection cannot be MM_DECOMMIT! */ + NT_ASSERT(ProtectionCode != MM_DECOMMIT); -#if (_MI_PAGING_LEVELS == 2) - /* Add an additional page table reference */ - MmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)]++; - ASSERT(MmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)] <= PTE_COUNT); -#endif + /* Remove the bit */ + TempPte.u.Soft.Protection = ProtectionCode & ~MM_GUARDPAGE; + MI_WRITE_INVALID_PTE(PointerPte, TempPte); - /* Did we get a prototype PTE back? */ - if (!ProtoPte) - { - /* No, create a new PTE. First, write the protection */ - PointerPte->u.Soft.Protection = ProtectionCode; + /* Not supported */ + ASSERT(ProtoPte == NULL); + ASSERT(CurrentThread->ApcNeeded == 0); - /* Lock the PFN database since we're going to grab a page */ - OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); + /* Drop the working set lock */ + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + ASSERT(KeGetCurrentIrql() == OldIrql); + + /* Handle stack expansion */ + return MiCheckForUserStackOverflow(Address, TrapInformation); + } - /* Try to get a zero page */ - MI_SET_USAGE(MI_USAGE_PEB_TEB); - MI_SET_PROCESS2(CurrentProcess->ImageFileName); - Color = MI_GET_NEXT_PROCESS_COLOR(CurrentProcess); - PageFrameIndex = MiRemoveZeroPageSafe(Color); - if (!PageFrameIndex) + /* Did we get a prototype PTE back? */ + if (!ProtoPte) { - /* Grab a page out of there. Later we should grab a colored zero page */ - PageFrameIndex = MiRemoveAnyPage(Color); - ASSERT(PageFrameIndex); + /* Is this PTE actually part of the PDE-PTE self-mapping directory? */ + if (PointerPde == MiAddressToPde(PTE_BASE)) + { + /* Then it's really a demand-zero PDE (on behalf of user-mode) */ + MI_WRITE_INVALID_PTE(PointerPte, DemandZeroPde); + } + else + { + /* No, create a new PTE. First, write the protection */ + TempPte.u.Soft.Protection = ProtectionCode; + MI_WRITE_INVALID_PTE(PointerPte, TempPte); + } - /* Release the lock since we need to do some zeroing */ - KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); + /* Lock the PFN database since we're going to grab a page */ + OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); - /* Zero out the page, since it's for user-mode */ - MiZeroPfn(PageFrameIndex); + /* Make sure we have enough pages */ + ASSERT(MmAvailablePages >= 32); - /* Grab the lock again so we can initialize the PFN entry */ - OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); - } + /* Try to get a zero page */ + MI_SET_USAGE(MI_USAGE_PEB_TEB); + MI_SET_PROCESS2(CurrentProcess->ImageFileName); + Color = MI_GET_NEXT_PROCESS_COLOR(CurrentProcess); + PageFrameIndex = MiRemoveZeroPageSafe(Color); + if (!PageFrameIndex) + { + /* Grab a page out of there. Later we should grab a colored zero page */ + PageFrameIndex = MiRemoveAnyPage(Color); + ASSERT(PageFrameIndex); - /* Initialize the PFN entry now */ - MiInitializePfn(PageFrameIndex, PointerPte, 1); + /* Release the lock since we need to do some zeroing */ + KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); - /* One more demand-zero fault */ - KeGetCurrentPrcb()->MmDemandZeroCount++; + /* Zero out the page, since it's for user-mode */ + MiZeroPfn(PageFrameIndex); - /* And we're done with the lock */ - KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); + /* Grab the lock again so we can initialize the PFN entry */ + OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); + } - /* User fault, build a user PTE */ - MI_MAKE_HARDWARE_PTE_USER(&TempPte, - PointerPte, - PointerPte->u.Soft.Protection, - PageFrameIndex); + /* Initialize the PFN entry now */ + MiInitializePfn(PageFrameIndex, PointerPte, 1); - /* Write the dirty bit for writeable pages */ - if (MI_IS_PAGE_WRITEABLE(&TempPte)) MI_MAKE_DIRTY_PAGE(&TempPte); + /* And we're done with the lock */ + KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); - /* And now write down the PTE, making the address valid */ - MI_WRITE_VALID_PTE(PointerPte, TempPte); + /* Increment the count of pages in the process */ + CurrentProcess->NumberOfPrivatePages++; - /* Demand zero */ - Status = STATUS_PAGE_FAULT_DEMAND_ZERO; - } - else - { - /* No guard page support yet */ - ASSERT((ProtectionCode & MM_DECOMMIT) == 0); + /* One more demand-zero fault */ + InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount); + + /* Fault on user PDE, or fault on user PTE? */ + if (PointerPte <= MiHighestUserPte) + { + /* User fault, build a user PTE */ + MI_MAKE_HARDWARE_PTE_USER(&TempPte, + PointerPte, + PointerPte->u.Soft.Protection, + PageFrameIndex); + } + else + { + /* This is a user-mode PDE, create a kernel PTE for it */ + MI_MAKE_HARDWARE_PTE(&TempPte, + PointerPte, + PointerPte->u.Soft.Protection, + PageFrameIndex); + } + + /* Write the dirty bit for writeable pages */ + if (MI_IS_PAGE_WRITEABLE(&TempPte)) MI_MAKE_DIRTY_PAGE(&TempPte); + + /* And now write down the PTE, making the address valid */ + MI_WRITE_VALID_PTE(PointerPte, TempPte); + Pfn1 = MI_PFN_ELEMENT(PageFrameIndex); + ASSERT(Pfn1->u1.Event == NULL); + + /* Demand zero */ + ASSERT(KeGetCurrentIrql() <= APC_LEVEL); + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + return STATUS_PAGE_FAULT_DEMAND_ZERO; + } + + /* We should have a valid protection here */ ASSERT(ProtectionCode != 0x100); /* Write the prototype PTE */ TempPte = PrototypePte; TempPte.u.Soft.Protection = ProtectionCode; + NT_ASSERT(TempPte.u.Long != 0); MI_WRITE_INVALID_PTE(PointerPte, TempPte); + } + else + { + /* Get the protection code and check if this is a proto PTE */ + ProtectionCode = (ULONG)TempPte.u.Soft.Protection; + if (TempPte.u.Soft.Prototype) + { + /* Do we need to go find the real PTE? */ + if (TempPte.u.Soft.PageFileHigh == MI_PTE_LOOKUP_NEEDED) + { + /* Get the prototype pte and VAD for it */ + ProtoPte = MiCheckVirtualAddress(Address, + &ProtectionCode, + &Vad); + if (!ProtoPte) + { + ASSERT(KeGetCurrentIrql() <= APC_LEVEL); + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + return STATUS_ACCESS_VIOLATION; + } + } + else + { + /* Get the prototype PTE! */ + ProtoPte = MiProtoPteToPte(&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 != 0); + /* Is it read-only */ + if (TempPte.u.Proto.ReadOnly) + { + /* Set read-only code */ + ProtectionCode = MM_READONLY; + } + else + { + /* Set unknown protection */ + ProtectionCode = 0x100; + ASSERT(CurrentProcess->CloneRoot != NULL); + } + } + } + } + + /* Do we have a valid protection code? */ + if (ProtectionCode != 0x100) + { + /* Run a software access check first, including to detect guard pages */ + Status = MiAccessCheck(PointerPte, + StoreInstruction, + Mode, + ProtectionCode, + TrapInformation, + FALSE); + if (Status != STATUS_SUCCESS) + { + /* Not supported */ + ASSERT(CurrentThread->ApcNeeded == 0); + + /* Drop the working set lock */ + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + ASSERT(KeGetCurrentIrql() == OldIrql); + + /* Did we hit a guard page? */ + if (Status == STATUS_GUARD_PAGE_VIOLATION) + { + /* Handle stack expansion */ + return MiCheckForUserStackOverflow(Address, TrapInformation); + } + + /* Otherwise, fail back to the caller directly */ + return Status; + } } - /* Release the working set */ + /* Dispatch the fault */ + Status = MiDispatchFault(StoreInstruction, + Address, + PointerPte, + ProtoPte, + FALSE, + CurrentProcess, + TrapInformation, + Vad); + + /* Return the status */ + ASSERT(KeGetCurrentIrql() <= APC_LEVEL); MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); return Status; } NTSTATUS NTAPI -MmSetExecuteOptions(IN ULONG ExecuteOptions) +MmGetExecuteOptions(IN PULONG ExecuteOptions) { + PKPROCESS CurrentProcess = &PsGetCurrentProcess()->Pcb; + ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); + + *ExecuteOptions = 0; + + if (CurrentProcess->Flags.ExecuteDisable) + { + *ExecuteOptions |= MEM_EXECUTE_OPTION_DISABLE; + } + + if (CurrentProcess->Flags.ExecuteEnable) + { + *ExecuteOptions |= MEM_EXECUTE_OPTION_ENABLE; + } + + if (CurrentProcess->Flags.DisableThunkEmulation) + { + *ExecuteOptions |= MEM_EXECUTE_OPTION_DISABLE_THUNK_EMULATION; + } + + if (CurrentProcess->Flags.Permanent) + { + *ExecuteOptions |= MEM_EXECUTE_OPTION_PERMANENT; + } + if (CurrentProcess->Flags.ExecuteDispatchEnable) + { + *ExecuteOptions |= MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE; + } + + if (CurrentProcess->Flags.ImageDispatchEnable) + { + *ExecuteOptions |= MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE; + } + + return STATUS_SUCCESS; +} + +NTSTATUS +NTAPI +MmSetExecuteOptions(IN ULONG ExecuteOptions) +{ PKPROCESS CurrentProcess = &PsGetCurrentProcess()->Pcb; KLOCK_QUEUE_HANDLE ProcessLock; NTSTATUS Status = STATUS_ACCESS_DENIED;