reactos/boot/environ/lib/mm/heapalloc.c
2018-03-18 16:10:41 +01:00

717 lines
20 KiB
C

/*
* COPYRIGHT: See COPYING.ARM in the top level directory
* PROJECT: ReactOS UEFI Boot Library
* FILE: boot/environ/lib/mm/heapalloc.c
* PURPOSE: Boot Library Memory Manager Heap Allocator
* PROGRAMMER: Alex Ionescu (alex.ionescu@reactos.org)
*/
/* INCLUDES ******************************************************************/
#include "bl.h"
/* DATA VARIABLES ************************************************************/
#define BL_HEAP_POINTER_FLAG_BITS 3
typedef struct _BL_HEAP_POINTER
{
union
{
struct
{
ULONG_PTR BufferFree : 1;
ULONG_PTR BufferOnHeap : 1;
ULONG_PTR NotUsed : 1;
ULONG_PTR BufferPointer : ((8 * sizeof(ULONG_PTR)) - BL_HEAP_POINTER_FLAG_BITS);
};
PVOID P;
};
} BL_HEAP_POINTER, *PBL_HEAP_POINTER;
typedef struct _BL_FREE_HEAP_ENTRY
{
BL_HEAP_POINTER BufferNext;
BL_HEAP_POINTER BufferPrevious;
BL_HEAP_POINTER FreeNext;
BL_HEAP_POINTER FreePrevious;
} BL_FREE_HEAP_ENTRY, *PBL_FREE_HEAP_ENTRY;
typedef struct _BL_BUSY_HEAP_ENTRY
{
BL_HEAP_POINTER BufferNext;
BL_HEAP_POINTER BufferPrevious;
UCHAR Buffer[ANYSIZE_ARRAY];
} BL_BUSY_HEAP_ENTRY, *PBL_BUSY_HEAP_ENTRY;
typedef struct _BL_HEAP_BOUNDARIES
{
LIST_ENTRY ListEntry;
ULONG_PTR HeapEnd;
ULONG_PTR HeapLimit;
ULONG_PTR HeapBase;
PBL_BUSY_HEAP_ENTRY HeapStart;
} BL_HEAP_BOUNDARIES, *PBL_HEAP_BOUNDARIES;
ULONG HapInitializationStatus;
LIST_ENTRY MmHeapBoundaries;
ULONG HapMinimumHeapSize;
ULONG HapAllocationAttributes;
PBL_FREE_HEAP_ENTRY* MmFreeList;
/* INLINES *******************************************************************/
FORCEINLINE
PBL_FREE_HEAP_ENTRY
MmHapDecodeLink (
_In_ BL_HEAP_POINTER Link
)
{
/* Decode the buffer pointer by ignoring the flags */
return (PBL_FREE_HEAP_ENTRY)(Link.BufferPointer << BL_HEAP_POINTER_FLAG_BITS);
}
FORCEINLINE
ULONG
MmHapBufferSize (
_In_ PVOID FreeEntry
)
{
PBL_FREE_HEAP_ENTRY Entry = FreeEntry;
/* The space between the next buffer header and this one is the size */
return (ULONG_PTR)MmHapDecodeLink(Entry->BufferNext) - (ULONG_PTR)Entry;
}
FORCEINLINE
ULONG
MmHapUserBufferSize (
_In_ PVOID FreeEntry
)
{
PBL_FREE_HEAP_ENTRY Entry = FreeEntry;
/* Get the size of the buffer as the user sees it */
return MmHapBufferSize(Entry) - FIELD_OFFSET(BL_BUSY_HEAP_ENTRY, Buffer);
}
/* FUNCTIONS *****************************************************************/
NTSTATUS
MmHapHeapAllocatorExtend (
_In_ ULONG ExtendSize
)
{
ULONG HeapSize, AlignedSize, HeapLimit;
PBL_HEAP_BOUNDARIES Heap, NewHeap;
NTSTATUS Status;
PBL_BUSY_HEAP_ENTRY HeapBase = NULL;
/* Compute a new heap, and add 2 more pages for the free list */
HeapSize = ExtendSize + (2 * PAGE_SIZE);
if (HeapSize < ExtendSize)
{
return STATUS_INTEGER_OVERFLOW;
}
/* Make sure the new heap is at least the minimum configured size */
if (HapMinimumHeapSize > HeapSize)
{
HeapSize = HapMinimumHeapSize;
}
/* Align it on a page boundary */
AlignedSize = ALIGN_UP_BY(HeapSize, PAGE_SIZE);
if (!AlignedSize)
{
return STATUS_INTEGER_OVERFLOW;
}
/* Check if we already have a heap */
if (!IsListEmpty(&MmHeapBoundaries))
{
/* Find the first heap*/
Heap = CONTAINING_RECORD(MmHeapBoundaries.Flink,
BL_HEAP_BOUNDARIES,
ListEntry);
/* Check if we have a page free above the heap */
HeapLimit = Heap->HeapLimit + PAGE_SIZE;
if (HeapLimit <= Heap->HeapEnd)
{
EfiPrintf(L"Heap extension TODO\r\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
}
/* We do not -- allocate one */
Status = MmPapAllocatePagesInRange((PVOID*)&HeapBase,
BlLoaderHeap,
AlignedSize >> PAGE_SHIFT,
HapAllocationAttributes,
0,
NULL,
0);
if (!NT_SUCCESS(Status))
{
EfiPrintf(L"HEAP ALLOCATION FAILED\r\n");
EfiStall(1000000);
return Status;
}
/* Set the heap bottom, limit, and top */
NewHeap = (PBL_HEAP_BOUNDARIES)HeapBase->Buffer;
NewHeap->HeapBase = (ULONG_PTR)HeapBase;
NewHeap->HeapLimit = (ULONG_PTR)HeapBase + AlignedSize;
NewHeap->HeapStart = (PBL_BUSY_HEAP_ENTRY)(NewHeap + 1);
/* Set the buffer links */
HeapBase->BufferPrevious.P = NULL;
HeapBase->BufferNext.P = NewHeap->HeapStart;
/* Set the buffer at the top of the heap and mark it as being free */
NewHeap->HeapStart->BufferPrevious.P = HeapBase;
NewHeap->HeapStart->BufferNext.P = NewHeap->HeapStart;
NewHeap->HeapStart->BufferNext.BufferFree = 1;
NewHeap->HeapStart->BufferNext.BufferOnHeap = 1;
/* Is this the first heap ever? */
if (IsListEmpty(&MmHeapBoundaries))
{
/* We will host the free list at the top of the heap */
MmFreeList = (PBL_FREE_HEAP_ENTRY*)((ULONG_PTR)NewHeap->HeapLimit - 8 * sizeof(PBL_FREE_HEAP_ENTRY));
NewHeap->HeapLimit = (ULONG_PTR)MmFreeList;
RtlZeroMemory(MmFreeList, 8 * sizeof(PBL_FREE_HEAP_ENTRY));
}
/* Remove a page on top */
HeapLimit = NewHeap->HeapLimit;
NewHeap->HeapEnd = NewHeap->HeapLimit;
NewHeap->HeapLimit -= PAGE_SIZE;
/* Add us into the heap list */
InsertTailList(&MmHeapBoundaries, &NewHeap->ListEntry);
return STATUS_SUCCESS;
}
ULONG
MmHapGetBucketId (
_In_ ULONG Size
)
{
ULONG BucketIndex = 0;
/* Use the last bucket if this is a large allocation */
if (Size >= PAGE_SIZE)
{
return 7;
}
/* Otherwise, use a higher index for each new power of two */
while (Size >> BucketIndex)
{
BucketIndex++;
}
/* Allocations are at least 16 bytes (2^4 = 5th index) */
return BucketIndex - 5;
}
VOID
MmHapReportHeapCorruption (
_In_ PBL_FREE_HEAP_ENTRY BufferEntry
)
{
#if 0
BOOLEAN DebuggerEnabled;
BlStatusPrint(L"Heap corruption in the links surrounding %p!\r\n", BufferEntry);
DebuggerEnabled = BlBdDebuggerEnabled();
if (DebuggerEnabled)
{
BlStatusPrint(L"\n*** Fatal Error 0x%08x :\n (0x%p, 0x%p, 0x%p, 0x%p)\n\r\n", 2, BufferEntry, NULL, NULL, NULL);
__debugbreak();
}
#else
EfiPrintf(L"Heap corruption in the links surrounding %p!\r\n", BufferEntry);
#endif
}
PVOID
MmHapCheckFreeLinks (
_In_ PVOID BufferEntry
)
{
PBL_FREE_HEAP_ENTRY Prev, Next;
PBL_FREE_HEAP_ENTRY Entry = BufferEntry;
/* Get the previous and next free pointers */
Prev = MmHapDecodeLink(Entry->FreePrevious);
Next = MmHapDecodeLink(Entry->FreeNext);
/* Make sure that both the previous and next entries point to this one */
if (((Next) && (MmHapDecodeLink(Next->FreePrevious)) != Entry) ||
((Prev) && (MmHapDecodeLink(Prev->FreeNext)) != Entry))
{
/* They don't, so the free headers are corrupted */
MmHapReportHeapCorruption(Entry);
return NULL;
}
/* They do, return the free entry as valid */
return Entry;
}
PVOID
MmHapCheckBufferLinks (
_In_ PVOID BufferEntry
)
{
PBL_FREE_HEAP_ENTRY Prev, Next;
PBL_FREE_HEAP_ENTRY Entry = BufferEntry;
/* Get the previous and next buffer pointers */
Prev = MmHapDecodeLink(Entry->BufferPrevious);
Next = MmHapDecodeLink(Entry->BufferNext);
/* Make sure that both the previous and next entries point to this one */
if (((Next) && (MmHapDecodeLink(Next->BufferPrevious)) != Entry) ||
((Prev) && (MmHapDecodeLink(Prev->BufferNext)) != Entry))
{
/* They don't, so the heap headers are corrupted */
MmHapReportHeapCorruption(Entry);
return NULL;
}
/* They, do the entry is valid */
return Entry;
}
PBL_FREE_HEAP_ENTRY
MmHapRemoveBufferFromFreeList (
_In_ PBL_FREE_HEAP_ENTRY FreeEntry
)
{
PBL_FREE_HEAP_ENTRY Prev, Next;
/* Firest, make sure the free entry is valid */
FreeEntry = MmHapCheckFreeLinks(FreeEntry);
if (!FreeEntry)
{
return FreeEntry;
}
/* Get the previous and next entry */
Prev = MmHapDecodeLink(FreeEntry->FreePrevious);
Next = MmHapDecodeLink(FreeEntry->FreeNext);
/* Update the next entry to point to our previous entry */
if (Next)
{
Next->FreePrevious.P = Prev;
}
/* Are we at the head? */
if (Prev)
{
/* Nope, so update our previous entry to point to our next entry */
Prev->FreeNext.P = Next;
}
else
{
/* Yep, so update the appropriate bucket listhead */
MmFreeList[MmHapGetBucketId(MmHapBufferSize(FreeEntry))] = Prev;
}
/* Return the (now removed) entry */
return FreeEntry;
}
PBL_FREE_HEAP_ENTRY
MmHapCoalesceFreeBuffer (
_In_ PBL_FREE_HEAP_ENTRY FreeEntry
)
{
PBL_FREE_HEAP_ENTRY Prev, Next;
/* First make sure that this is a valid buffer entry */
if (!MmHapCheckBufferLinks(FreeEntry))
{
return NULL;
}
/* Get the next entry and check if it's free */
Next = MmHapDecodeLink(FreeEntry->BufferNext);
if (!(Next->BufferNext.BufferOnHeap) && (Next->BufferNext.BufferFree))
{
/* Remove the next buffer from the free list since we're coalescing */
Next = MmHapRemoveBufferFromFreeList(Next);
if (!Next)
{
return NULL;
}
/* The forward link of the *new* free buffer should now point to us */
MmHapDecodeLink(Next->BufferNext)->BufferPrevious.P = FreeEntry;
/* Our forward link should point to the *new* free buffer as well */
FreeEntry->BufferNext.P = MmHapDecodeLink(Next->BufferNext);
/* Mark our buffer as free */
FreeEntry->BufferNext.BufferFree = 1;
}
/* Get the previous entry and check if it's free */
Prev = MmHapDecodeLink(FreeEntry->BufferPrevious);
if (!(Prev) || !(Prev->BufferNext.BufferFree))
{
return FreeEntry;
}
/* It's free, so remove it */
Prev = MmHapRemoveBufferFromFreeList(Prev);
if (!Prev)
{
return NULL;
}
/* The previous link of our next buffer should now point to our *previous* */
MmHapDecodeLink(FreeEntry->BufferNext)->BufferPrevious.P = Prev;
/* Our previous link should point the next free buffer now */
Prev->BufferNext.P = MmHapDecodeLink(FreeEntry->BufferNext);
/* Set the new freed buffer as the previous buffer, and mark it free */
FreeEntry = Prev;
FreeEntry->BufferNext.BufferFree = 1;
return FreeEntry;
}
PBL_FREE_HEAP_ENTRY
MmHapAddToFreeList (
_In_ PBL_BUSY_HEAP_ENTRY Entry,
_In_ ULONG Flags
)
{
PBL_FREE_HEAP_ENTRY FreeEntry, Head;
ULONG BucketId;
BL_LIBRARY_PARAMETERS LocalParameters;
/* First, check if the entry is valid */
Entry = MmHapCheckBufferLinks(Entry);
if (!Entry)
{
return NULL;
}
/* Check if we should zero the entry */
LocalParameters = BlpLibraryParameters;
if ((LocalParameters.LibraryFlags & BL_LIBRARY_FLAG_ZERO_HEAP_ALLOCATIONS_ON_FREE) &&
!(Flags))
{
/* Yep, zero it out */
RtlZeroMemory(Entry->Buffer, MmHapUserBufferSize(Entry));
}
/* Now mark the entry as free */
Entry->BufferNext.BufferFree = 1;
/* Now that this buffer is free, try to coalesce it */
FreeEntry = MmHapCoalesceFreeBuffer((PBL_FREE_HEAP_ENTRY)Entry);
if (!FreeEntry)
{
return FreeEntry;
}
/* Compute the bucket ID for the free list */
BucketId = MmHapGetBucketId(MmHapBufferSize(Entry));
/* Get the current head for this bucket, if one exists */
Head = MmFreeList ? MmFreeList[BucketId] : NULL;
/* Update the head's backlink to point to this newly freed entry */
if (Head)
{
Head->FreePrevious.P = FreeEntry;
}
/* Nobody behind us, the old head in front of us */
FreeEntry->FreePrevious.P = NULL;
FreeEntry->FreeNext.P = Head;
/* Put us at the head of list now, and return the entry */
MmFreeList[BucketId] = FreeEntry;
return FreeEntry;
}
PBL_BUSY_HEAP_ENTRY
MmHapFindBufferInFreeList (
_In_ ULONG Size
)
{
PBL_FREE_HEAP_ENTRY FreeEntry = NULL;
PBL_BUSY_HEAP_ENTRY NextEntry;
ULONG BucketId;
/* Get the appropriate bucket for our size */
BucketId = MmHapGetBucketId(Size);
if (BucketId >= 8)
{
return NULL;
}
/* Keep going as long as we don't have a free entry */
while (!FreeEntry)
{
/* Fet the first free entry in this list */
FreeEntry = MmFreeList ? MmFreeList[BucketId] : NULL;
/* Loop as long as there's entries in the list */
while (FreeEntry)
{
/* Can this free entry satisfy our needs? */
if (MmHapBufferSize(FreeEntry) >= Size)
{
/* All good */
break;
}
/* It cannot, keep going to the next one */
FreeEntry = MmHapDecodeLink(FreeEntry->FreeNext);
}
/* Try the next list -- have we exhausted all the lists? */
if (++BucketId >= 8)
{
/* Have we not found an entry yet? Fail if so... */
if (!FreeEntry)
{
return NULL;
}
}
}
/* We should have an entry if we're here. Remove it from the free list */
NT_ASSERT(FreeEntry != NULL);
FreeEntry = MmHapRemoveBufferFromFreeList(FreeEntry);
if (!FreeEntry)
{
return NULL;
}
/* Make sure it's not corrupted */
FreeEntry = MmHapCheckBufferLinks(FreeEntry);
if (!FreeEntry)
{
return NULL;
}
/* Do we have space for at least another buffer? */
if ((MmHapBufferSize(FreeEntry) - Size) >= sizeof(BL_FREE_HEAP_ENTRY))
{
/* Go to where the new next buffer will start */
NextEntry = (PBL_BUSY_HEAP_ENTRY)((ULONG_PTR)FreeEntry + Size);
/* Make the new next buffer point to the next buffer */
NextEntry->BufferNext.P = MmHapDecodeLink(FreeEntry->BufferNext);
/* Make the old next buffer point back to the new one */
MmHapDecodeLink(FreeEntry->BufferNext)->BufferPrevious.P = NextEntry;
/* Point the new next buffer point back to us */
NextEntry->BufferPrevious.P = FreeEntry;
/* Point us to the new next buffer */
FreeEntry->BufferNext.P = NextEntry;
/* And insert the new next buffer into the free list */
MmHapAddToFreeList(NextEntry, 1);
}
/* Return the entry, which is now allocated */
return (PBL_BUSY_HEAP_ENTRY)FreeEntry;
}
NTSTATUS
MmHaInitialize (
_In_ ULONG HeapSize,
_In_ ULONG HeapAttributes
)
{
NTSTATUS Status;
/* No free list to begin with */
MmFreeList = NULL;
/* Configure the minimum heap size and allocation attributes */
HapMinimumHeapSize = ALIGN_UP_BY(HeapSize, PAGE_SIZE);
HapAllocationAttributes = HeapAttributes & 0x20000;
/* Initialize the heap boundary list */
InitializeListHead(&MmHeapBoundaries);
/* Initialize a heap big enough to handle a one pointer long allocation */
Status = MmHapHeapAllocatorExtend(sizeof(PVOID));
if (NT_SUCCESS(Status))
{
/* The heap is ready! */
HapInitializationStatus = 1;
Status = STATUS_SUCCESS;
}
/* Return initialization status */
return Status;
}
PVOID
BlMmAllocateHeap (
_In_ SIZE_T Size
)
{
ULONG BufferSize;
PBL_HEAP_BOUNDARIES Heap;
PBL_BUSY_HEAP_ENTRY BusyEntry, FreeEntry, NextEntry;
/* Ignore heap allocation if the heap allocator isn't ready yet */
if (HapInitializationStatus != 1)
{
return NULL;
}
/* Align the buffer size to the minimum size required */
BufferSize = ALIGN_UP_BY(Size + FIELD_OFFSET(BL_BUSY_HEAP_ENTRY, Buffer),
FIELD_OFFSET(BL_BUSY_HEAP_ENTRY, Buffer));
/* Watch out for overflow */
if (BufferSize <= Size)
{
return NULL;
}
/* Make sure it's at least big enough to hold a free entry later on */
if (BufferSize < sizeof(BL_FREE_HEAP_ENTRY))
{
BufferSize = sizeof(BL_FREE_HEAP_ENTRY);
}
/* Loop while we try to allocate memory */
while (1)
{
/* Find a free buffer for this allocation */
BusyEntry = MmHapFindBufferInFreeList(BufferSize);
if (BusyEntry)
{
break;
}
/* We couldn't find a free buffer. Do we have any heaps? */
if (!IsListEmpty(&MmHeapBoundaries))
{
/* Get the current heap */
Heap = CONTAINING_RECORD(MmHeapBoundaries.Flink,
BL_HEAP_BOUNDARIES,
ListEntry);
/* Check if we have space in the heap page for this allocation? */
FreeEntry = Heap->HeapStart;
NextEntry = (PBL_BUSY_HEAP_ENTRY)((ULONG_PTR)FreeEntry + BufferSize);
if ((NextEntry >= FreeEntry) &&
((ULONG_PTR)NextEntry <=
Heap->HeapLimit - FIELD_OFFSET(BL_BUSY_HEAP_ENTRY, Buffer)))
{
/* Update the heap top pointer past this allocation */
Heap->HeapStart = NextEntry;
/* Make this allocation point to the slot */
FreeEntry->BufferNext.P = Heap->HeapStart;
/* And make the free heap entry point back to us */
Heap->HeapStart->BufferPrevious.P = FreeEntry;
/* Mark the heap entry as being free and on the heap */
Heap->HeapStart->BufferNext.BufferFree = 1;
Heap->HeapStart->BufferNext.BufferOnHeap = 1;
/* The previously freed entry on the heap page is now ours */
BusyEntry = FreeEntry;
break;
}
}
/* We have no heaps or space on any heap -- extend the heap and retry */
if (!NT_SUCCESS(MmHapHeapAllocatorExtend(BufferSize)))
{
EfiPrintf(L"Heap extension failed!\r\n");
return NULL;
}
EfiPrintf(L"Heap extended -- trying again\r\n");
}
/* Clear all the bits, marking this entry as allocated */
BusyEntry->BufferNext.P = MmHapDecodeLink(BusyEntry->BufferNext);
/* Return the entry's data buffer */
//EfiPrintf(L"Returning buffer at 0x%p\r\n", &BusyEntry->Buffer);
return &BusyEntry->Buffer;
}
NTSTATUS
BlMmFreeHeap (
_In_ PVOID Buffer
)
{
PBL_BUSY_HEAP_ENTRY BusyEntry;
PBL_HEAP_BOUNDARIES Heap;
PLIST_ENTRY NextEntry;
/* If the heap is not initialized, fail */
if (HapInitializationStatus != 1)
{
return STATUS_UNSUCCESSFUL;
}
/* Get the heap header */
//EfiPrintf(L"Freeing entry at: %p\r\n", Buffer);
if (Buffer)
{
/* Don't free heap until we discover the corruption */
return STATUS_SUCCESS;
}
BusyEntry = CONTAINING_RECORD(Buffer, BL_BUSY_HEAP_ENTRY, Buffer);
/* Loop all the heaps */
NextEntry = MmHeapBoundaries.Flink;
while (NextEntry != &MmHeapBoundaries)
{
/* Get the current heap in the list */
Heap = CONTAINING_RECORD(NextEntry, BL_HEAP_BOUNDARIES, ListEntry);
/* Is this entry part of this heap? */
if (((ULONG_PTR)Heap->HeapBase <= (ULONG_PTR)BusyEntry) &&
((ULONG_PTR)BusyEntry < (ULONG_PTR)Heap->HeapStart))
{
/* Ignore double-free */
if (BusyEntry->BufferNext.BufferFree)
{
return STATUS_INVALID_PARAMETER;
}
/* It is -- add it to the free list */
MmHapAddToFreeList(BusyEntry, 0);
return STATUS_SUCCESS;
}
/* It isn't, move to the next heap */
NextEntry = NextEntry->Flink;
}
/* The entry is not on any valid heap */
return STATUS_INVALID_PARAMETER;
}