/* * Copyright (C) 2002-2005 ReactOS Team (and the authors from the programmers section) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * * PROJECT: ReactOS kernel * FILE: ntoskrnl/mm/anonmem.c * PURPOSE: Implementing anonymous memory. * * PROGRAMMERS: David Welch * Casper Hornstrup * KJK::Hyperion * Ge van Geldorp * Eric Kohl * Royce Mitchell III * Aleksey Bragin * Jason Filby * Art Yerkes * Gunnar Andre' Dalsnes * Filip Navara * Thomas Weidenmueller * Alex Ionescu * Trevor McCort * Steven Edwards */ /* INCLUDE *****************************************************************/ #include #define NDEBUG #include /* FUNCTIONS *****************************************************************/ NTSTATUS NTAPI MmWritePageVirtualMemory(PMMSUPPORT AddressSpace, PMEMORY_AREA MemoryArea, PVOID Address, PMM_PAGEOP PageOp) { SWAPENTRY SwapEntry; PFN_TYPE Page; NTSTATUS Status; PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace); /* * Check for paging out from a deleted virtual memory area. */ if (MemoryArea->DeleteInProgress) { PageOp->Status = STATUS_UNSUCCESSFUL; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_UNSUCCESSFUL); } Page = MmGetPfnForProcess(Process, Address); /* * Get that the page actually is dirty. */ if (!MmIsDirtyPage(Process, Address)) { PageOp->Status = STATUS_SUCCESS; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_SUCCESS); } /* * Speculatively set the mapping to clean. */ MmSetCleanPage(Process, Address); /* * If necessary, allocate an entry in the paging file for this page */ SwapEntry = MmGetSavedSwapEntryPage(Page); if (SwapEntry == 0) { SwapEntry = MmAllocSwapPage(); if (SwapEntry == 0) { MmSetDirtyPage(Process, Address); PageOp->Status = STATUS_PAGEFILE_QUOTA_EXCEEDED; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_PAGEFILE_QUOTA_EXCEEDED); } } /* * Write the page to the pagefile */ Status = MmWriteToSwapPage(SwapEntry, Page); if (!NT_SUCCESS(Status)) { DPRINT1("MM: Failed to write to swap page (Status was 0x%.8X)\n", Status); MmSetDirtyPage(Process, Address); PageOp->Status = STATUS_UNSUCCESSFUL; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_UNSUCCESSFUL); } /* * Otherwise we have succeeded. */ MmSetSavedSwapEntryPage(Page, SwapEntry); PageOp->Status = STATUS_SUCCESS; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_SUCCESS); } NTSTATUS NTAPI MmPageOutVirtualMemory(PMMSUPPORT AddressSpace, PMEMORY_AREA MemoryArea, PVOID Address, PMM_PAGEOP PageOp) { PFN_TYPE Page; BOOLEAN WasDirty; SWAPENTRY SwapEntry; NTSTATUS Status; PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace); DPRINT("MmPageOutVirtualMemory(Address 0x%.8X) PID %d\n", Address, Process->UniqueProcessId); /* * Check for paging out from a deleted virtual memory area. */ if (MemoryArea->DeleteInProgress) { PageOp->Status = STATUS_UNSUCCESSFUL; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_UNSUCCESSFUL); } /* * Disable the virtual mapping. */ MmDisableVirtualMapping(Process, Address, &WasDirty, &Page); if (Page == 0) { KeBugCheck(MEMORY_MANAGEMENT); } /* * Paging out non-dirty data is easy. */ if (!WasDirty) { MmLockAddressSpace(AddressSpace); MmDeleteVirtualMapping(Process, Address, FALSE, NULL, NULL); MmDeleteAllRmaps(Page, NULL, NULL); if ((SwapEntry = MmGetSavedSwapEntryPage(Page)) != 0) { MmCreatePageFileMapping(Process, Address, SwapEntry); MmSetSavedSwapEntryPage(Page, 0); } MmUnlockAddressSpace(AddressSpace); MmReleasePageMemoryConsumer(MC_USER, Page); PageOp->Status = STATUS_SUCCESS; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_SUCCESS); } /* * If necessary, allocate an entry in the paging file for this page */ SwapEntry = MmGetSavedSwapEntryPage(Page); if (SwapEntry == 0) { SwapEntry = MmAllocSwapPage(); if (SwapEntry == 0) { MmShowOutOfSpaceMessagePagingFile(); MmEnableVirtualMapping(Process, Address); PageOp->Status = STATUS_UNSUCCESSFUL; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_PAGEFILE_QUOTA); } } /* * Write the page to the pagefile */ Status = MmWriteToSwapPage(SwapEntry, Page); if (!NT_SUCCESS(Status)) { DPRINT1("MM: Failed to write to swap page (Status was 0x%.8X)\n", Status); MmEnableVirtualMapping(Process, Address); PageOp->Status = STATUS_UNSUCCESSFUL; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_UNSUCCESSFUL); } /* * Otherwise we have succeeded, free the page */ DPRINT("MM: Swapped out virtual memory page 0x%.8X!\n", Page << PAGE_SHIFT); MmLockAddressSpace(AddressSpace); MmDeleteVirtualMapping(Process, Address, FALSE, NULL, NULL); MmCreatePageFileMapping(Process, Address, SwapEntry); MmUnlockAddressSpace(AddressSpace); MmDeleteAllRmaps(Page, NULL, NULL); MmSetSavedSwapEntryPage(Page, 0); MmReleasePageMemoryConsumer(MC_USER, Page); PageOp->Status = STATUS_SUCCESS; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_SUCCESS); } NTSTATUS NTAPI MmNotPresentFaultVirtualMemory(PMMSUPPORT AddressSpace, MEMORY_AREA* MemoryArea, PVOID Address, BOOLEAN Locked) /* * FUNCTION: Move data into memory to satisfy a page not present fault * ARGUMENTS: * AddressSpace = Address space within which the fault occurred * MemoryArea = The memory area within which the fault occurred * Address = The absolute address of fault * RETURNS: Status * NOTES: This function is called with the address space lock held. */ { PFN_TYPE Page; NTSTATUS Status; PMM_REGION Region; PMM_PAGEOP PageOp; PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace); /* * There is a window between taking the page fault and locking the * address space when another thread could load the page so we check * that. */ if (MmIsPagePresent(NULL, Address)) { return(STATUS_SUCCESS); } /* * Check for the virtual memory area being deleted. */ if (MemoryArea->DeleteInProgress) { return(STATUS_UNSUCCESSFUL); } /* * Get the segment corresponding to the virtual address */ Region = MmFindRegion(MemoryArea->StartingAddress, &MemoryArea->Data.VirtualMemoryData.RegionListHead, Address, NULL); if (Region->Type == MEM_RESERVE || Region->Protect == PAGE_NOACCESS) { return(STATUS_ACCESS_VIOLATION); } /* * FIXME */ if (Region->Protect & PAGE_GUARD) { return(STATUS_GUARD_PAGE_VIOLATION); } /* * Get or create a page operation */ PageOp = MmGetPageOp(MemoryArea, Process->UniqueProcessId, (PVOID)PAGE_ROUND_DOWN(Address), NULL, 0, MM_PAGEOP_PAGEIN, FALSE); if (PageOp == NULL) { DPRINT1("MmGetPageOp failed"); KeBugCheck(MEMORY_MANAGEMENT); } /* * Check if someone else is already handling this fault, if so wait * for them */ if (PageOp->Thread != PsGetCurrentThread()) { MmUnlockAddressSpace(AddressSpace); Status = KeWaitForSingleObject(&PageOp->CompletionEvent, 0, KernelMode, FALSE, NULL); /* * Check for various strange conditions */ if (Status != STATUS_SUCCESS) { DPRINT1("Failed to wait for page op\n"); KeBugCheck(MEMORY_MANAGEMENT); } if (PageOp->Status == STATUS_PENDING) { DPRINT1("Woke for page op before completion\n"); KeBugCheck(MEMORY_MANAGEMENT); } /* * If this wasn't a pagein then we need to restart the handling */ if (PageOp->OpType != MM_PAGEOP_PAGEIN) { MmLockAddressSpace(AddressSpace); KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_MM_RESTART_OPERATION); } /* * If the thread handling this fault has failed then we don't retry */ if (!NT_SUCCESS(PageOp->Status)) { MmLockAddressSpace(AddressSpace); KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); Status = PageOp->Status; MmReleasePageOp(PageOp); return(Status); } MmLockAddressSpace(AddressSpace); KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_SUCCESS); } /* * Try to allocate a page */ Status = MmRequestPageMemoryConsumer(MC_USER, FALSE, &Page); if (Status == STATUS_NO_MEMORY) { MmUnlockAddressSpace(AddressSpace); Status = MmRequestPageMemoryConsumer(MC_USER, TRUE, &Page); MmLockAddressSpace(AddressSpace); } if (!NT_SUCCESS(Status)) { DPRINT1("MmRequestPageMemoryConsumer failed, status = %x\n", Status); KeBugCheck(MEMORY_MANAGEMENT); } /* * Handle swapped out pages. */ if (MmIsPageSwapEntry(NULL, Address)) { SWAPENTRY SwapEntry; MmDeletePageFileMapping(Process, Address, &SwapEntry); Status = MmReadFromSwapPage(SwapEntry, Page); if (!NT_SUCCESS(Status)) { KeBugCheck(MEMORY_MANAGEMENT); } MmSetSavedSwapEntryPage(Page, SwapEntry); } /* * Set the page. If we fail because we are out of memory then * try again */ Status = MmCreateVirtualMapping(Process, (PVOID)PAGE_ROUND_DOWN(Address), Region->Protect, &Page, 1); while (Status == STATUS_NO_MEMORY) { MmUnlockAddressSpace(AddressSpace); Status = MmCreateVirtualMapping(Process, Address, Region->Protect, &Page, 1); MmLockAddressSpace(AddressSpace); } if (!NT_SUCCESS(Status)) { DPRINT1("MmCreateVirtualMapping failed, not out of memory\n"); KeBugCheck(MEMORY_MANAGEMENT); return(Status); } /* * Add the page to the process's working set */ MmInsertRmap(Page, Process, (PVOID)PAGE_ROUND_DOWN(Address)); /* * Finish the operation */ PageOp->Status = STATUS_SUCCESS; KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE); MmReleasePageOp(PageOp); return(STATUS_SUCCESS); } static VOID MmModifyAttributes(PMMSUPPORT AddressSpace, PVOID BaseAddress, ULONG RegionSize, ULONG OldType, ULONG OldProtect, ULONG NewType, ULONG NewProtect) /* * FUNCTION: Modify the attributes of a memory region */ { PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace); /* * If we are switching a previously committed region to reserved then * free any allocated pages within the region */ if (NewType == MEM_RESERVE && OldType == MEM_COMMIT) { ULONG i; for (i=0; i < PAGE_ROUND_UP(RegionSize)/PAGE_SIZE; i++) { PFN_TYPE Page; if (MmIsPageSwapEntry(Process, (char*)BaseAddress + (i * PAGE_SIZE))) { SWAPENTRY SwapEntry; MmDeletePageFileMapping(Process, (char*)BaseAddress + (i * PAGE_SIZE), &SwapEntry); MmFreeSwapPage(SwapEntry); } else { MmDeleteVirtualMapping(Process, (char*)BaseAddress + (i*PAGE_SIZE), FALSE, NULL, &Page); if (Page != 0) { SWAPENTRY SavedSwapEntry; SavedSwapEntry = MmGetSavedSwapEntryPage(Page); if (SavedSwapEntry != 0) { MmFreeSwapPage(SavedSwapEntry); MmSetSavedSwapEntryPage(Page, 0); } MmDeleteRmap(Page, Process, (char*)BaseAddress + (i * PAGE_SIZE)); MmReleasePageMemoryConsumer(MC_USER, Page); } } } } /* * If we are changing the protection attributes of a committed region then * alter the attributes for any allocated pages within the region */ if (NewType == MEM_COMMIT && OldType == MEM_COMMIT && OldProtect != NewProtect) { ULONG i; for (i=0; i < PAGE_ROUND_UP(RegionSize)/PAGE_SIZE; i++) { if (MmIsPagePresent(Process, (char*)BaseAddress + (i*PAGE_SIZE))) { MmSetPageProtect(Process, (char*)BaseAddress + (i*PAGE_SIZE), NewProtect); } } } } /* * @implemented */ NTSTATUS NTAPI NtAllocateVirtualMemory(IN HANDLE ProcessHandle, IN OUT PVOID* UBaseAddress, IN ULONG_PTR ZeroBits, IN OUT PSIZE_T URegionSize, IN ULONG AllocationType, IN ULONG Protect) /* * FUNCTION: Allocates a block of virtual memory in the process address space * ARGUMENTS: * ProcessHandle = The handle of the process which owns the virtual memory * BaseAddress = A pointer to the virtual memory allocated. If you * supply a non zero value the system will try to * allocate the memory at the address supplied. It round * it down to a multiple of the page size. * ZeroBits = (OPTIONAL) You can specify the number of high order bits * that must be zero, ensuring that the memory will be * allocated at a address below a certain value. * RegionSize = The number of bytes to allocate * AllocationType = Indicates the type of virtual memory you like to * allocated, can be a combination of MEM_COMMIT, * MEM_RESERVE, MEM_RESET, MEM_TOP_DOWN. * Protect = Indicates the protection type of the pages allocated. * RETURNS: Status */ { PEPROCESS Process; MEMORY_AREA* MemoryArea; ULONG_PTR MemoryAreaLength; ULONG Type; NTSTATUS Status; PMMSUPPORT AddressSpace; PVOID BaseAddress; ULONG RegionSize; PVOID PBaseAddress; ULONG PRegionSize; ULONG MemProtection; PHYSICAL_ADDRESS BoundaryAddressMultiple; KPROCESSOR_MODE PreviousMode; PAGED_CODE(); DPRINT("NtAllocateVirtualMemory(*UBaseAddress %x, " "ZeroBits %d, *URegionSize %x, AllocationType %x, Protect %x)\n", *UBaseAddress,ZeroBits,*URegionSize,AllocationType, Protect); /* Check for valid protection flags */ MemProtection = Protect & ~(PAGE_GUARD|PAGE_NOCACHE); if (MemProtection != PAGE_NOACCESS && MemProtection != PAGE_READONLY && MemProtection != PAGE_READWRITE && MemProtection != PAGE_WRITECOPY && MemProtection != PAGE_EXECUTE && MemProtection != PAGE_EXECUTE_READ && MemProtection != PAGE_EXECUTE_READWRITE && MemProtection != PAGE_EXECUTE_WRITECOPY) { DPRINT1("Invalid page protection\n"); return STATUS_INVALID_PAGE_PROTECTION; } /* Check for valid Zero bits */ if (ZeroBits > 21) { DPRINT1("Too many zero bits\n"); return STATUS_INVALID_PARAMETER_3; } /* Check for valid Allocation Types */ if ((AllocationType & ~(MEM_COMMIT | MEM_RESERVE | MEM_RESET | MEM_PHYSICAL | MEM_TOP_DOWN | MEM_WRITE_WATCH))) { DPRINT1("Invalid Allocation Type\n"); return STATUS_INVALID_PARAMETER_5; } /* Check for at least one of these Allocation Types to be set */ if (!(AllocationType & (MEM_COMMIT | MEM_RESERVE | MEM_RESET))) { DPRINT1("No memory allocation base type\n"); return STATUS_INVALID_PARAMETER_5; } /* MEM_RESET is an exclusive flag, make sure that is valid too */ if ((AllocationType & MEM_RESET) && (AllocationType != MEM_RESET)) { DPRINT1("Invalid use of MEM_RESET\n"); return STATUS_INVALID_PARAMETER_5; } /* MEM_WRITE_WATCH can only be used if MEM_RESERVE is also used */ if ((AllocationType & MEM_WRITE_WATCH) && !(AllocationType & MEM_RESERVE)) { DPRINT1("MEM_WRITE_WATCH used without MEM_RESERVE\n"); return STATUS_INVALID_PARAMETER_5; } /* MEM_PHYSICAL can only be used with MEM_RESERVE, and can only be R/W */ if (AllocationType & MEM_PHYSICAL) { /* First check for MEM_RESERVE exclusivity */ if (AllocationType != (MEM_RESERVE | MEM_PHYSICAL)) { DPRINT1("MEM_PHYSICAL used with other flags then MEM_RESERVE or" "MEM_RESERVE was not present at all\n"); return STATUS_INVALID_PARAMETER_5; } /* Then make sure PAGE_READWRITE is used */ if (Protect != PAGE_READWRITE) { DPRINT1("MEM_PHYSICAL used without PAGE_READWRITE\n"); return STATUS_INVALID_PAGE_PROTECTION; } } PreviousMode = KeGetPreviousMode(); _SEH2_TRY { if (PreviousMode != KernelMode) { ProbeForWritePointer(UBaseAddress); ProbeForWriteUlong(URegionSize); } PBaseAddress = *UBaseAddress; PRegionSize = *URegionSize; } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Return the exception code */ _SEH2_YIELD(return _SEH2_GetExceptionCode()); } _SEH2_END; BoundaryAddressMultiple.QuadPart = 0; BaseAddress = (PVOID)PAGE_ROUND_DOWN(PBaseAddress); RegionSize = PAGE_ROUND_UP((ULONG_PTR)PBaseAddress + PRegionSize) - PAGE_ROUND_DOWN(PBaseAddress); /* * We've captured and calculated the data, now do more checks * Yes, MmCreateMemoryArea does similar checks, but they don't return * the right status codes that a caller of this routine would expect. */ if ((ULONG_PTR)BaseAddress >= USER_SHARED_DATA) { DPRINT1("Virtual allocation base above User Space\n"); return STATUS_INVALID_PARAMETER_2; } if (!RegionSize) { DPRINT1("Region size is invalid (zero)\n"); return STATUS_INVALID_PARAMETER_4; } if ((USER_SHARED_DATA - (ULONG_PTR)BaseAddress) < RegionSize) { DPRINT1("Region size would overflow into kernel-memory\n"); return STATUS_INVALID_PARAMETER_4; } /* * Copy on Write is reserved for system use. This case is a certain failure * but there may be other cases...needs more testing */ if ((!BaseAddress || (AllocationType & MEM_RESERVE)) && (Protect & (PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY))) { DPRINT1("Copy on write is not supported by VirtualAlloc\n"); return STATUS_INVALID_PAGE_PROTECTION; } Status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_VM_OPERATION, PsProcessType, PreviousMode, (PVOID*)(&Process), NULL); if (!NT_SUCCESS(Status)) { DPRINT("NtAllocateVirtualMemory() = %x\n",Status); return(Status); } Type = (AllocationType & MEM_COMMIT) ? MEM_COMMIT : MEM_RESERVE; DPRINT("Type %x\n", Type); AddressSpace = &Process->Vm; MmLockAddressSpace(AddressSpace); if (PBaseAddress != 0) { MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, BaseAddress); if (MemoryArea != NULL) { MemoryAreaLength = (ULONG_PTR)MemoryArea->EndingAddress - (ULONG_PTR)MemoryArea->StartingAddress; if (((ULONG)BaseAddress + RegionSize) > (ULONG)MemoryArea->EndingAddress) { DPRINT("BaseAddress + RegionSize %x is larger than MemoryArea's EndingAddress %x\n", (ULONG)BaseAddress + RegionSize, MemoryArea->EndingAddress); MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); return STATUS_MEMORY_NOT_ALLOCATED; } if (AllocationType == MEM_RESET) { if (MmIsPagePresent(Process, BaseAddress)) { /* FIXME: mark pages as not modified */ } else { /* FIXME: if pages are in paging file discard them and bring in pages of zeros */ } MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); /* MEM_RESET does not modify any attributes of region */ return STATUS_SUCCESS; } if (MemoryArea->Type == MEMORY_AREA_VIRTUAL_MEMORY && MemoryAreaLength >= RegionSize) { Status = MmAlterRegion(AddressSpace, MemoryArea->StartingAddress, &MemoryArea->Data.VirtualMemoryData.RegionListHead, BaseAddress, RegionSize, Type, Protect, MmModifyAttributes); MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); DPRINT("NtAllocateVirtualMemory() = %x\n",Status); /* Give the caller rounded BaseAddress and area length */ if (NT_SUCCESS(Status)) { *UBaseAddress = BaseAddress; *URegionSize = RegionSize; DPRINT("*UBaseAddress %x *URegionSize %x\n", BaseAddress, RegionSize); } return(Status); } else if (MemoryAreaLength >= RegionSize) { /* Region list initialized? */ if (MemoryArea->Data.SectionData.RegionListHead.Flink) { Status = MmAlterRegion(AddressSpace, MemoryArea->StartingAddress, &MemoryArea->Data.SectionData.RegionListHead, BaseAddress, RegionSize, Type, Protect, MmModifyAttributes); } else { Status = STATUS_ACCESS_VIOLATION; } MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); DPRINT("NtAllocateVirtualMemory() = %x\n",Status); /* Give the caller rounded BaseAddress and area length */ if (NT_SUCCESS(Status)) { *UBaseAddress = BaseAddress; *URegionSize = RegionSize; DPRINT("*UBaseAddress %x *URegionSize %x\n", BaseAddress, RegionSize); } return(Status); } else { MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); return(STATUS_UNSUCCESSFUL); } } } Status = MmCreateMemoryArea(AddressSpace, MEMORY_AREA_VIRTUAL_MEMORY, &BaseAddress, RegionSize, Protect, &MemoryArea, PBaseAddress != 0, AllocationType & MEM_TOP_DOWN, BoundaryAddressMultiple); if (!NT_SUCCESS(Status)) { MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); DPRINT("NtAllocateVirtualMemory() = %x\n",Status); return(Status); } MemoryAreaLength = (ULONG_PTR)MemoryArea->EndingAddress - (ULONG_PTR)MemoryArea->StartingAddress; MmInitializeRegion(&MemoryArea->Data.VirtualMemoryData.RegionListHead, MemoryAreaLength, Type, Protect); if ((AllocationType & MEM_COMMIT) && (Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE))) { const ULONG nPages = PAGE_ROUND_UP(MemoryAreaLength) >> PAGE_SHIFT; MmReserveSwapPages(nPages); } *UBaseAddress = BaseAddress; *URegionSize = MemoryAreaLength; DPRINT("*UBaseAddress %x *URegionSize %x\n", BaseAddress, RegionSize); MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); return(STATUS_SUCCESS); } static VOID MmFreeVirtualMemoryPage(PVOID Context, MEMORY_AREA* MemoryArea, PVOID Address, PFN_TYPE Page, SWAPENTRY SwapEntry, BOOLEAN Dirty) { PEPROCESS Process = (PEPROCESS)Context; if (Page != 0) { SWAPENTRY SavedSwapEntry; SavedSwapEntry = MmGetSavedSwapEntryPage(Page); if (SavedSwapEntry != 0) { MmFreeSwapPage(SavedSwapEntry); MmSetSavedSwapEntryPage(Page, 0); } MmDeleteRmap(Page, Process, Address); MmReleasePageMemoryConsumer(MC_USER, Page); } else if (SwapEntry != 0) { MmFreeSwapPage(SwapEntry); } } VOID NTAPI MmFreeVirtualMemory(PEPROCESS Process, PMEMORY_AREA MemoryArea) { PLIST_ENTRY current_entry; PMM_REGION current; ULONG i; DPRINT("MmFreeVirtualMemory(Process %p MemoryArea %p)\n", Process, MemoryArea); /* Mark this memory area as about to be deleted. */ MemoryArea->DeleteInProgress = TRUE; /* * Wait for any ongoing paging operations. Notice that since we have * flagged this memory area as deleted no more page ops will be added. */ if (MemoryArea->PageOpCount > 0) { ULONG_PTR MemoryAreaLength = (ULONG_PTR)MemoryArea->EndingAddress - (ULONG_PTR)MemoryArea->StartingAddress; const ULONG nPages = PAGE_ROUND_UP(MemoryAreaLength) >> PAGE_SHIFT; for (i = 0; i < nPages && MemoryArea->PageOpCount != 0; ++i) { PMM_PAGEOP PageOp; PageOp = MmCheckForPageOp(MemoryArea, Process->UniqueProcessId, (PVOID)((ULONG_PTR)MemoryArea->StartingAddress + (i * PAGE_SIZE)), NULL, 0); if (PageOp != NULL) { NTSTATUS Status; MmUnlockAddressSpace(&Process->Vm); Status = KeWaitForSingleObject(&PageOp->CompletionEvent, 0, KernelMode, FALSE, NULL); if (Status != STATUS_SUCCESS) { DPRINT1("Failed to wait for page op\n"); KeBugCheck(MEMORY_MANAGEMENT); } MmLockAddressSpace(&Process->Vm); MmReleasePageOp(PageOp); } } } /* Free all the individual segments. */ current_entry = MemoryArea->Data.VirtualMemoryData.RegionListHead.Flink; while (current_entry != &MemoryArea->Data.VirtualMemoryData.RegionListHead) { current = CONTAINING_RECORD(current_entry, MM_REGION, RegionListEntry); current_entry = current_entry->Flink; ExFreePool(current); } /* Actually free the memory area. */ MmFreeMemoryArea(&Process->Vm, MemoryArea, MmFreeVirtualMemoryPage, (PVOID)Process); } /* * @implemented */ NTSTATUS NTAPI NtFreeVirtualMemory(IN HANDLE ProcessHandle, IN PVOID* PBaseAddress, IN PSIZE_T PRegionSize, IN ULONG FreeType) /* * FUNCTION: Frees a range of virtual memory * ARGUMENTS: * ProcessHandle = Points to the process that allocated the virtual * memory * BaseAddress = Points to the memory address, rounded down to a * multiple of the pagesize * RegionSize = Limits the range to free, rounded up to a multiple of * the paging size * FreeType = Can be one of the values: MEM_DECOMMIT, or MEM_RELEASE * RETURNS: Status */ { MEMORY_AREA* MemoryArea; NTSTATUS Status; PEPROCESS Process; PMMSUPPORT AddressSpace; PVOID BaseAddress; ULONG RegionSize; PAGED_CODE(); DPRINT("NtFreeVirtualMemory(ProcessHandle %x, *PBaseAddress %x, " "*PRegionSize %x, FreeType %x)\n",ProcessHandle,*PBaseAddress, *PRegionSize,FreeType); if (!(FreeType & (MEM_RELEASE | MEM_DECOMMIT))) { DPRINT1("Invalid FreeType\n"); return STATUS_INVALID_PARAMETER_4; } if (ExGetPreviousMode() != KernelMode) { _SEH2_TRY { /* Probe user pointers */ ProbeForWriteSize_t(PRegionSize); ProbeForWritePointer(PBaseAddress); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { /* Return the exception code */ _SEH2_YIELD(return _SEH2_GetExceptionCode()); } _SEH2_END; } BaseAddress = (PVOID)PAGE_ROUND_DOWN((*PBaseAddress)); RegionSize = PAGE_ROUND_UP((ULONG_PTR)(*PBaseAddress) + (*PRegionSize)) - PAGE_ROUND_DOWN((*PBaseAddress)); Status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_VM_OPERATION, PsProcessType, UserMode, (PVOID*)(&Process), NULL); if (!NT_SUCCESS(Status)) { return(Status); } AddressSpace = &Process->Vm; MmLockAddressSpace(AddressSpace); MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, BaseAddress); if (MemoryArea == NULL) { Status = STATUS_UNSUCCESSFUL; goto unlock_deref_and_return; } switch (FreeType) { case MEM_RELEASE: /* We can only free a memory area in one step. */ if (MemoryArea->StartingAddress != BaseAddress || MemoryArea->Type != MEMORY_AREA_VIRTUAL_MEMORY) { Status = STATUS_UNSUCCESSFUL; goto unlock_deref_and_return; } MmFreeVirtualMemory(Process, MemoryArea); Status = STATUS_SUCCESS; goto unlock_deref_and_return; case MEM_DECOMMIT: Status = MmAlterRegion(AddressSpace, MemoryArea->StartingAddress, (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW) ? &MemoryArea->Data.SectionData.RegionListHead : &MemoryArea->Data.VirtualMemoryData.RegionListHead, BaseAddress, RegionSize, MEM_RESERVE, PAGE_NOACCESS, MmModifyAttributes); goto unlock_deref_and_return; } Status = STATUS_NOT_IMPLEMENTED; unlock_deref_and_return: MmUnlockAddressSpace(AddressSpace); ObDereferenceObject(Process); return(Status); } NTSTATUS NTAPI MmProtectAnonMem(PMMSUPPORT AddressSpace, PMEMORY_AREA MemoryArea, PVOID BaseAddress, ULONG Length, ULONG Protect, PULONG OldProtect) { PMM_REGION Region; NTSTATUS Status = STATUS_SUCCESS; ULONG LengthCount = 0; /* Search all Regions in MemoryArea up to Length */ /* Every Region up to Length must be committed for success */ for (;;) { Region = MmFindRegion(MemoryArea->StartingAddress, &MemoryArea->Data.VirtualMemoryData.RegionListHead, (PVOID)((ULONG_PTR)BaseAddress + (ULONG_PTR)LengthCount), NULL); /* If a Region was found and it is committed */ if ((Region) && (Region->Type == MEM_COMMIT)) { LengthCount += Region->Length; if (Length <= LengthCount) break; continue; } /* If Region was found and it is not commited */ else if (Region) { Status = STATUS_NOT_COMMITTED; break; } /* If no Region was found at all */ else if (LengthCount == 0) { Status = STATUS_INVALID_ADDRESS; break; } } if (NT_SUCCESS(Status)) { *OldProtect = Region->Protect; Status = MmAlterRegion(AddressSpace, MemoryArea->StartingAddress, &MemoryArea->Data.VirtualMemoryData.RegionListHead, BaseAddress, Length, Region->Type, Protect, MmModifyAttributes); } return (Status); } NTSTATUS NTAPI MmQueryAnonMem(PMEMORY_AREA MemoryArea, PVOID Address, PMEMORY_BASIC_INFORMATION Info, PULONG ResultLength) { PMM_REGION Region; PVOID RegionBase = NULL; Info->BaseAddress = (PVOID)PAGE_ROUND_DOWN(Address); Region = MmFindRegion(MemoryArea->StartingAddress, &MemoryArea->Data.VirtualMemoryData.RegionListHead, Address, &RegionBase); Info->BaseAddress = RegionBase; Info->AllocationBase = MemoryArea->StartingAddress; Info->AllocationProtect = MemoryArea->Protect; Info->RegionSize = Region->Length; Info->State = Region->Type; Info->Protect = Region->Protect; Info->Type = MEM_PRIVATE; *ResultLength = sizeof(MEMORY_BASIC_INFORMATION); return(STATUS_SUCCESS); } /* EOF */