From e78fb8a366fcd39329f86ce5a095d88d9be8dcb6 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Mon, 25 Nov 2024 11:34:54 -0800 Subject: [PATCH] UnitTestFrameworkPkg/MemoryAllocationLibPosix: Add allocate below address Add HostMemoryAllocationBelowAddressLib class and implementation that uses OS specific services to perform pool and page allocations below a specified address in a host based unit test application execution environment. This library class is only required for mocking buffers that are assumed to be below a specific address by code under test. Signed-off-by: Michael D Kinney --- .../HostMemoryAllocationBelowAddressLib.h | 90 ++++ .../AllocateBelowAddress.c | 438 ++++++++++++++++++ .../MemoryAllocationLibPosix.inf | 6 +- .../MemoryAllocationLibPosix/WinInclude.h | 28 ++ .../SampleGoogleTest/SampleGoogleTest.cpp | 210 +++++++++ .../UnitTestFrameworkPkg.ci.yaml | 1 + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec | 5 + .../UnitTestFrameworkPkgHost.dsc.inc | 1 + 8 files changed, 778 insertions(+), 1 deletion(-) create mode 100644 UnitTestFrameworkPkg/Include/Library/HostMemoryAllocationBelowAddressLib.h create mode 100644 UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/AllocateBelowAddress.c create mode 100644 UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/WinInclude.h diff --git a/UnitTestFrameworkPkg/Include/Library/HostMemoryAllocationBelowAddressLib.h b/UnitTestFrameworkPkg/Include/Library/HostMemoryAllocationBelowAddressLib.h new file mode 100644 index 0000000000..36ba3d6298 --- /dev/null +++ b/UnitTestFrameworkPkg/Include/Library/HostMemoryAllocationBelowAddressLib.h @@ -0,0 +1,90 @@ +/** @file + HostMemoryAllocationBelowAddressLib class + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef HOST_MEMORY_ALLOCATION_BELOW_ADDRESS_LIB_H_ + +/** + Allocate memory below a specifies address. + + @param[in] MaximumAddress The address below which the memory allocation must + be performed. + @param[in] Length The size, in bytes, of the memory allocation. + + @retval !NULL Pointer to the allocated memory. + @retval NULL The memory allocation failed. +**/ +VOID * +EFIAPI +HostAllocatePoolBelowAddress ( + IN UINT64 MaximumAddress, + IN UINT64 Length + ); + +/** + Free memory allocated with AllocateMemoryHostAllocatePoolBelowAddress(). + + @param[in] Address Pointer to buffer previously allocated with + HostAllocatePoolBelowAddress(). +**/ +VOID +EFIAPI +HostFreePoolBelowAddress ( + IN VOID *Address + ); + +/** + Allocates one or more 4KB pages below a specified address at a specified + alignment. + + Allocates the number of 4KB pages specified by Pages below MaximumAddress with + an alignment specified by Alignment. The allocated buffer is returned. If + Pages is 0, then NULL is returned. If there is not enough memory below the + requested address at the specified alignment remaining to satisfy the request, + then NULL is returned. + + If Alignment is not a power of two and Alignment is not zero, then ASSERT(). + If Pages plus EFI_SIZE_TO_PAGES (Alignment) overflows, then ASSERT(). + + @param[in] MaximumAddress The address below which the memory allocation must + @param[in] Pages The number of 4 KB pages to allocate. + @param[in] Alignment The requested alignment of the allocation. Must be + a power of two. If Alignment is zero, then byte + alignment is used. + + @return A pointer to the allocated buffer or NULL if allocation fails. +**/ +VOID * +EFIAPI +HostAllocateAlignedPagesBelowAddress ( + IN UINT64 MaximumAddress, + IN UINTN Pages, + IN UINT64 Alignment + ); + +/** + Frees one or more 4KB pages that were previously allocated with + HostAllocateAlignedPagesBelowAddress(). + + Frees the number of 4KB pages specified by Pages from the buffer specified by + Buffer. Buffer must have been allocated with HostAllocateAlignedPagesBelowAddress(). + If it is not possible to free allocated pages, then this function will perform + no actions. + + If Buffer was not allocated with HostAllocateAlignedPagesBelowAddress(), then + ASSERT(). If Pages is zero, then ASSERT(). + + @param[in] Buffer The pointer to the buffer of pages to free. + @param[in] Pages The number of 4 KB pages to free. +**/ +VOID +EFIAPI +HostFreeAlignedPagesBelowAddress ( + IN VOID *Buffer, + IN UINTN Pages + ); + +#endif diff --git a/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/AllocateBelowAddress.c b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/AllocateBelowAddress.c new file mode 100644 index 0000000000..4eb386c1ed --- /dev/null +++ b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/AllocateBelowAddress.c @@ -0,0 +1,438 @@ +/** @file + Instance of Memory Below Address Allocation Library based on Windows APIs + and Linux APIs. + + Uses Windows APIs VirtualAlloc() and VirtualFree() to allocate and free memory + below a specified virtual address. + + Uses Linux APIs mmap() and munmap() to allocate and free memory below a + specified virtual address. + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#if defined (_WIN32) || defined (_WIN64) + #include "WinInclude.h" +#elif defined (__linux__) + #include + #include + #include + #include +#else + #error Unsupported target +#endif + +#include +#include + +/// +/// Signature for PAGE_HEAD_BELOW_ADDRESS structure +/// Used to verify that buffer being freed was allocated by this library. +/// +#define PAGE_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE SIGNATURE_64 ('P', 'A', 'H', 'B', 'e', 'l', 'A', 'd') + +/// +/// Structure placed immediately before an aligned allocation to store the +/// information required to free the entire allocated buffer. +/// +typedef struct { + UINT64 Signature; + VOID *AllocatedBuffer; + UINTN TotalPages; + VOID *AlignedBuffer; + UINTN AlignedPages; +} PAGE_HEAD_BELOW_ADDRESS; + +/// +/// Signature for POOL_HEAD_BELOW_ADDRESS structure +/// Used to verify that buffer being freed was allocated by this library. +/// +#define POOL_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE SIGNATURE_64 ('P', 'O', 'H', 'B', 'e', 'l', 'A', 'd') + +/// +/// Structure placed immediately before an pool allocation to store the +/// information required to free the entire allocated buffer. +/// +typedef struct { + UINT64 Signature; + UINT64 TotalSize; +} POOL_HEAD_BELOW_ADDRESS; + +// +// Lowest address that can be allocated by this library +// +#define MINIMUM_ALLOCATION_ADDRESS BASE_64KB + +// +// The page size of the host +// +static UINTN mPageSize = 0; + +/** + Use system services to get the host page size. + + @return Host page size in bytes. +**/ +static +UINTN +HostGetPageSize ( + VOID + ) +{ + #if defined (_WIN32) || defined (_WIN64) + SYSTEM_INFO SystemInfo; + + GetSystemInfo (&SystemInfo); + return (UINTN)SystemInfo.dwPageSize; + #elif defined (__linux__) + return sysconf (_SC_PAGESIZE); + #else + return 0; + #endif +} + +/** + Use system services to allocate a buffer between a minimum and maximum + address aligned to the requested page size. + + @param[in] MaximumAddress The address below which the memory allocation must + be performed. + @param[in] Length The size, in bytes, of the memory allocation. + + @retval !NULL Pointer to the allocated memory. + @retval NULL The memory allocation failed. +**/ +static +VOID * +HostAllocateBufferInRange ( + UINTN MaximumAddress, + UINTN Length + ) +{ + UINTN Address; + VOID *AllocatedAddress; + + if (mPageSize == 0) { + mPageSize = HostGetPageSize (); + if (mPageSize == 0) { + return NULL; + } + } + + // + // Round maximum address down to the nearest page boundary + // + MaximumAddress &= ~(mPageSize - 1); + + for (Address = MaximumAddress; Address >= MINIMUM_ALLOCATION_ADDRESS; Address -= mPageSize) { + #if defined (_WIN32) || defined (_WIN64) + AllocatedAddress = VirtualAlloc ( + (VOID *)Address, + Length, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE + ); + if (AllocatedAddress != NULL) { + return AllocatedAddress; + } + + #elif defined (__linux__) + AllocatedAddress = mmap ( + (VOID *)Address, + Length, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, + -1, + 0 + ); + if (AllocatedAddress != MAP_FAILED) { + return AllocatedAddress; + } + + #else + return NULL; + #endif + } + + return NULL; +} + +/** + Use system services to free memory allocated with HostAllocateBufferInRange(). + + @param[in] Buffer Pointer to buffer previously allocated with + HostAllocateBufferInRange(). + @param[in] Length Length, in bytes, of buffer previously allocated with + HostAllocateBufferInRange(). +**/ +static +VOID +HostFreeBufferInRange ( + IN VOID *Buffer, + IN UINTN Length + ) +{ + #if defined (_WIN32) || defined (_WIN64) + if (!VirtualFree (Buffer, 0, MEM_RELEASE)) { + ASSERT (FALSE); + } + + #elif defined (__linux__) + if (munmap (Buffer, Length) == -1) { + ASSERT (FALSE); + } + + #endif +} + +/** + Allocate memory below a specific address. + + @param[in] MaximumAddress The address below which the memory allocation must + be performed. + @param[in] Length The size, in bytes, of the memory allocation. + + @retval !NULL Pointer to the allocated memory. + @retval NULL The memory allocation failed. +**/ +VOID * +EFIAPI +HostAllocatePoolBelowAddress ( + IN UINT64 MaximumAddress, + IN UINT64 Length + ) +{ + VOID *AllocatedAddress; + POOL_HEAD_BELOW_ADDRESS *PoolHead; + + if (Length == 0) { + return NULL; + } + + // + // Limit maximum address to the largest supported virtual address + // + MaximumAddress = MIN (MaximumAddress, MAX_UINTN); + + // + // Increase requested allocation length by the size of the pool header + // + Length += sizeof (POOL_HEAD_BELOW_ADDRESS); + + // + // Make sure allocation length is smaller than maximum address + // + if (Length > MaximumAddress) { + DEBUG ((DEBUG_ERROR, "HostAllocatePoolBelowAddress: Length > MaximumAddress\n")); + return NULL; + } + + // + // Reduce maximum address by the requested allocation length + // + MaximumAddress -= Length; + + AllocatedAddress = HostAllocateBufferInRange ( + (UINTN)MaximumAddress, + (UINTN)Length + ); + if (AllocatedAddress == NULL) { + DEBUG ((DEBUG_ERROR, "HostAllocatePoolBelowAddress: HostAllocateBufferInRange failed\n")); + return NULL; + } + + DEBUG_CLEAR_MEMORY (AllocatedAddress, (UINTN)Length); + PoolHead = (POOL_HEAD_BELOW_ADDRESS *)AllocatedAddress; + PoolHead->Signature = POOL_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE; + PoolHead->TotalSize = Length; + return (VOID *)(PoolHead + 1); +} + +/** + Free memory allocated with HostAllocatePoolBelowAddress(). + + @param[in] Buffer Pointer to buffer previously allocated with + HostAllocatePoolBelowAddress(). +**/ +VOID +EFIAPI +HostFreePoolBelowAddress ( + IN VOID *Buffer + ) +{ + POOL_HEAD_BELOW_ADDRESS *PoolHead; + UINTN Length; + + ASSERT (Buffer != NULL); + + PoolHead = ((POOL_HEAD_BELOW_ADDRESS *)Buffer) - 1; + + ASSERT (PoolHead != NULL); + ASSERT (PoolHead->Signature == POOL_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE); + ASSERT (PoolHead->TotalSize >= sizeof (POOL_HEAD_BELOW_ADDRESS)); + ASSERT (PoolHead->TotalSize <= MAX_UINTN); + + Length = (UINTN)PoolHead->TotalSize; + DEBUG_CLEAR_MEMORY (PoolHead, Length); + + HostFreeBufferInRange (PoolHead, Length); +} + +/** + Allocates one or more 4KB pages below a specified address at a specified + alignment. + + Allocates the number of 4KB pages specified by Pages below MaximumAddress with + an alignment specified by Alignment. The allocated buffer is returned. If + Pages is 0, then NULL is returned. If there is not enough memory below the + requested address at the specified alignment remaining to satisfy the request, + then NULL is returned. + + If Alignment is not a power of two and Alignment is not zero, then ASSERT(). + If Pages plus EFI_SIZE_TO_PAGES (Alignment) overflows, then ASSERT(). + + @param[in] MaximumAddress The address below which the memory allocation must + @param[in] Pages The number of 4 KB pages to allocate. + @param[in] Alignment The requested alignment of the allocation. Must be + a power of two. If Alignment is zero, then byte + alignment is used. + + @return A pointer to the allocated buffer or NULL if allocation fails. +**/ +VOID * +EFIAPI +HostAllocateAlignedPagesBelowAddress ( + IN UINT64 MaximumAddress, + IN UINTN Pages, + IN UINT64 Alignment + ) +{ + PAGE_HEAD_BELOW_ADDRESS PageHead; + PAGE_HEAD_BELOW_ADDRESS *PageHeadPtr; + UINTN AlignmentMask; + UINTN Length; + + if (Pages == 0) { + return NULL; + } + + // + // Make sure alignment is a power of two + // + if ((Alignment & (Alignment - 1)) != 0) { + DEBUG ((DEBUG_ERROR, "HostAllocateAlignedPagesBelowAddress: Alignment is not a power of two\n")); + return NULL; + } + + // + // Make sure alignment is smaller than the largest supported virtual address + // + if (Alignment > MAX_UINTN) { + DEBUG ((DEBUG_ERROR, "HostAllocateAlignedPagesBelowAddress: Alignment > MAX_UINTN\n")); + return NULL; + } + + // + // Make sure alignment is at least 4KB + // + Alignment = MAX (Alignment, SIZE_4KB); + + // + // Initialize local page head structure + // + PageHead.Signature = PAGE_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE; + PageHead.AlignedPages = Pages; + PageHead.TotalPages = Pages + 2 * EFI_SIZE_TO_PAGES ((UINTN)Alignment); + + // + // Limit maximum address to the largest supported virtual address + // + MaximumAddress = MIN (MaximumAddress, MAX_UINTN); + + // + // Make sure total page allocation fits below maximum address + // + if (PageHead.TotalPages >= EFI_SIZE_TO_PAGES (MaximumAddress)) { + DEBUG ((DEBUG_ERROR, "HostAllocateAlignedPagesBelowAddress: TotalPages >= MaximumAddress\n")); + return NULL; + } + + // + // Determine the length of the allocation in bytes + // + Length = EFI_PAGES_TO_SIZE (PageHead.TotalPages); + + // + // Reduce maximum address by the total allocation length + // + MaximumAddress -= Length; + + // + // Allocate buffer large enough to support aligned page request + // + PageHead.AllocatedBuffer = HostAllocateBufferInRange ( + (UINTN)MaximumAddress, + Length + ); + if (PageHead.AllocatedBuffer == NULL) { + DEBUG ((DEBUG_ERROR, "HostAllocateAlignedPagesBelowAddress: HostAllocateBufferInRange failed\n")); + return NULL; + } + + DEBUG_CLEAR_MEMORY (PageHead.AllocatedBuffer, Length); + + AlignmentMask = ((UINTN)Alignment - 1); + PageHead.AlignedBuffer = (VOID *)(((UINTN)PageHead.AllocatedBuffer + AlignmentMask) & ~AlignmentMask); + if ((UINTN)PageHead.AlignedBuffer - (UINTN)PageHead.AllocatedBuffer < sizeof (PAGE_HEAD_BELOW_ADDRESS)) { + PageHead.AlignedBuffer = (VOID *)((UINTN)PageHead.AlignedBuffer + (UINTN)Alignment); + } + + PageHeadPtr = (PAGE_HEAD_BELOW_ADDRESS *)((UINTN)PageHead.AlignedBuffer) - 1; + memcpy (PageHeadPtr, &PageHead, sizeof (PageHead)); + + return PageHead.AlignedBuffer; +} + +/** + Frees one or more 4KB pages that were previously allocated with + HostAllocateAlignedPagesBelowAddress(). + + Frees the number of 4KB pages specified by Pages from the buffer specified by + Buffer. Buffer must have been allocated with HostAllocateAlignedPagesBelowAddress(). + If it is not possible to free allocated pages, then this function will perform + no actions. + + If Buffer was not allocated with HostAllocateAlignedPagesBelowAddress(), then + ASSERT(). If Pages is zero, then ASSERT(). + + @param[in] Buffer The pointer to the buffer of pages to free. + @param[in] Pages The number of 4 KB pages to free. +**/ +VOID +EFIAPI +HostFreeAlignedPagesBelowAddress ( + IN VOID *Buffer, + IN UINTN Pages + ) +{ + PAGE_HEAD_BELOW_ADDRESS *PageHeadPtr; + VOID *AllocatedBuffer; + UINTN Length; + + ASSERT (Buffer != NULL); + + PageHeadPtr = ((PAGE_HEAD_BELOW_ADDRESS *)Buffer) - 1; + + ASSERT (PageHeadPtr != NULL); + ASSERT (PageHeadPtr->Signature == PAGE_HEAD_BELOW_ADDRESS_PRIVATE_SIGNATURE); + ASSERT (PageHeadPtr->AlignedPages == Pages); + ASSERT (PageHeadPtr->AllocatedBuffer != NULL); + + AllocatedBuffer = PageHeadPtr->AllocatedBuffer; + Length = EFI_PAGES_TO_SIZE (PageHeadPtr->TotalPages); + + DEBUG_CLEAR_MEMORY (AllocatedBuffer, Length); + + HostFreeBufferInRange (AllocatedBuffer, Length); +} diff --git a/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf index 44ec3fd517..1443600013 100644 --- a/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf +++ b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf @@ -16,12 +16,16 @@ MODULE_TYPE = UEFI_DRIVER VERSION_STRING = 1.0 LIBRARY_CLASS = MemoryAllocationLib|HOST_APPLICATION + LIBRARY_CLASS = HostMemoryAllocationBelowAddressLib|HOST_APPLICATION [Sources] MemoryAllocationLibPosix.c + AllocateBelowAddress.c + WinInclude.h [Packages] MdePkg/MdePkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec [LibraryClasses] - BaseLib + DebugLib diff --git a/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/WinInclude.h b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/WinInclude.h new file mode 100644 index 0000000000..cdde46c868 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/WinInclude.h @@ -0,0 +1,28 @@ +/** @file + Include windows.h addressing conflicts with forced include of Base.h + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef WIN_INCLUDE_H_ +#define WIN_INCLUDE_H_ + +#define GUID _WINNT_DUP_GUID_____ +#define _LIST_ENTRY _WINNT_DUP_LIST_ENTRY_FORWARD +#define LIST_ENTRY _WINNT_DUP_LIST_ENTRY +#undef VOID + +#pragma warning (push) +#pragma warning (disable : 4668) + +#include + +#pragma warning (pop) + +#undef GUID +#undef _LIST_ENTRY +#undef LIST_ENTRY +#define VOID void + +#endif diff --git a/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp b/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp index 9164f66da3..c13c66f97a 100644 --- a/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp +++ b/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp @@ -13,6 +13,7 @@ extern "C" { #include #include #include + #include } /** @@ -433,6 +434,215 @@ TEST (SanitizerTests, DivideByZeroDeathTest) { EXPECT_DEATH (DivideWithNoParameterChecking (10, 0), "ERROR: AddressSanitizer: "); } +/** + Sample unit test that allocates and frees buffers below 4GB +**/ +TEST (MemoryAllocationTests, Below4GB) { + VOID *Buffer1; + VOID *Buffer2; + UINT8 EmptyBuffer[0x100]; + + // + // Length 0 always fails + // + Buffer1 = HostAllocatePoolBelowAddress (BASE_4GB - 1, 0); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Length == Maximum Address always fails + // + Buffer1 = HostAllocatePoolBelowAddress (BASE_4GB - 1, SIZE_4GB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Length > Maximum Address always fails + // + Buffer1 = HostAllocatePoolBelowAddress (BASE_4GB - 1, SIZE_8GB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Maximum Address 0 always fails + // + Buffer1 = HostAllocatePoolBelowAddress (0, SIZE_4KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Maximum Address < 64KB always fails + // + Buffer1 = HostAllocatePoolBelowAddress (BASE_64KB - 1, SIZE_4KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Not enough memory available always fails + // + Buffer1 = HostAllocatePoolBelowAddress (BASE_128KB - 1, SIZE_64KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Allocation of 4KB buffer below 4GB must succeed + // + Buffer1 = HostAllocatePoolBelowAddress (BASE_4GB - 1, SIZE_4KB); + ASSERT_NE (Buffer1, (VOID *)NULL); + ASSERT_LT ((UINTN)Buffer1, BASE_4GB); + + // + // Allocated buffer must support read and write + // + *(UINT8 *)Buffer1 = 0x5A; + ASSERT_EQ (*(UINT8 *)Buffer1, 0x5A); + + // + // Allocation of 1MB buffer below 4GB must succeed + // + Buffer2 = HostAllocatePoolBelowAddress (BASE_4GB - 1, SIZE_1MB); + ASSERT_NE (Buffer2, (VOID *)NULL); + ASSERT_LT ((UINTN)Buffer2, BASE_4GB); + + // + // Allocated buffer must support read and write + // + *(UINT8 *)Buffer2 = 0x5A; + ASSERT_EQ (*(UINT8 *)Buffer2, 0x5A); + + // + // Allocations must return different values + // + ASSERT_NE (Buffer1, Buffer2); + + // + // Free buffers below 4GB must not ASSERT + // + HostFreePoolBelowAddress (Buffer1); + HostFreePoolBelowAddress (Buffer2); + + // + // Expect ASSERT() tests + // + EXPECT_ANY_THROW (HostFreePoolBelowAddress (NULL)); + EXPECT_ANY_THROW (HostFreePoolBelowAddress (EmptyBuffer + 0x80)); + Buffer1 = AllocatePool (0x100); + EXPECT_ANY_THROW (HostFreePoolBelowAddress ((UINT8 *)Buffer1 + 0x80)); + FreePool (Buffer1); +} + +/** + Sample unit test that allocates and frees aligned pages below 4GB +**/ +TEST (MemoryAllocationTests, AlignedBelow4GB) { + VOID *Buffer1; + VOID *Buffer2; + UINT8 EmptyBuffer[0x100]; + + // + // Pages 0 always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, 0, SIZE_4KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Alignment not a power of 2 always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, SIZE_4KB, 5); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Alignment not a power of 2 always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, SIZE_4KB, SIZE_16KB + 1); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Alignment larger than largest supported virtual address always fails + // Only applies to 32-bit architectures + // + if (sizeof (UINTN) == sizeof (UINT32)) { + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, SIZE_4KB, SIZE_4GB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + } + + // + // Length == Maximum Address always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, EFI_SIZE_TO_PAGES (SIZE_4GB), SIZE_4KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Length > Maximum Address always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, EFI_SIZE_TO_PAGES (SIZE_8GB), SIZE_4KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Alignment >= Maximum Address always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, EFI_SIZE_TO_PAGES (SIZE_4GB), SIZE_4GB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Maximum Address 0 always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (0, EFI_SIZE_TO_PAGES (SIZE_4KB), SIZE_4KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Maximum Address <= 64KB always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_64KB - 1, EFI_SIZE_TO_PAGES (SIZE_4KB), SIZE_4KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Not enough memory available always fails + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_128KB - 1, EFI_SIZE_TO_PAGES (SIZE_64KB), SIZE_4KB); + ASSERT_EQ (Buffer1, (VOID *)NULL); + + // + // Allocation of 4KB buffer below 4GB must succeed + // + Buffer1 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, EFI_SIZE_TO_PAGES (SIZE_4KB), SIZE_4KB); + ASSERT_NE (Buffer1, (VOID *)NULL); + ASSERT_LT ((UINTN)Buffer1, BASE_4GB); + + // + // Allocated buffer must support read and write + // + *(UINT8 *)Buffer1 = 0x5A; + ASSERT_EQ (*(UINT8 *)Buffer1, 0x5A); + + // + // Allocation of 1MB buffer below 4GB must succeed + // + Buffer2 = HostAllocateAlignedPagesBelowAddress (BASE_4GB - 1, EFI_SIZE_TO_PAGES (SIZE_1MB), SIZE_1MB); + ASSERT_NE (Buffer2, (VOID *)NULL); + ASSERT_LT ((UINTN)Buffer2, BASE_4GB); + + // + // Allocated buffer must support read and write + // + *(UINT8 *)Buffer2 = 0x5A; + ASSERT_EQ (*(UINT8 *)Buffer2, 0x5A); + + // + // Allocations must return different values + // + ASSERT_NE (Buffer1, Buffer2); + + // + // Free buffers below 4GB must not ASSERT + // + HostFreeAlignedPagesBelowAddress (Buffer1, EFI_SIZE_TO_PAGES (SIZE_4KB)); + HostFreeAlignedPagesBelowAddress (Buffer2, EFI_SIZE_TO_PAGES (SIZE_1MB)); + + // + // Expect ASSERT() tests + // + EXPECT_ANY_THROW (HostFreeAlignedPagesBelowAddress (NULL, 0)); + EXPECT_ANY_THROW (HostFreeAlignedPagesBelowAddress (EmptyBuffer + 0x80, 1)); + Buffer1 = AllocatePool (0x100); + EXPECT_ANY_THROW (HostFreeAlignedPagesBelowAddress ((UINT8 *)Buffer1 + 0x80, 1)); + FreePool (Buffer1); +} + int main ( int argc, diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml index cf51b21e00..970ba900b8 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml @@ -96,6 +96,7 @@ "Library/SubhookLib/subhook/**/*.*" # not going to spell check a submodule ], "ExtendWords": [ # words to extend to the dictionary for this package + "noreplace", "Pointee", "gmock", "GMOCK", diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec index ef0a148d48..0553af4edc 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec @@ -39,6 +39,11 @@ SubhookLib|Include/Library/SubhookLib.h FunctionMockLib|Include/Library/FunctionMockLib.h + ## @libraryclass Host only memory allocation library that supports allocating + # buffers below a specified address. + # + HostMemoryAllocationBelowAddressLib|Include/Library/HostMemoryAllocationBelowAddressLib.h + [LibraryClasses.Common.Private] ## @libraryclass Provides a unit test result report # diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc b/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc index 1c28855878..27ed1df009 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc @@ -23,6 +23,7 @@ UnitTestLib|UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLibCmocka.inf DebugLib|UnitTestFrameworkPkg/Library/Posix/DebugLibPosix/DebugLibPosix.inf MemoryAllocationLib|UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf + HostMemoryAllocationBelowAddressLib|UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf UefiBootServicesTableLib|UnitTestFrameworkPkg/Library/UnitTestUefiBootServicesTableLib/UnitTestUefiBootServicesTableLib.inf PeiServicesTablePointerLib|UnitTestFrameworkPkg/Library/UnitTestPeiServicesTablePointerLib/UnitTestPeiServicesTablePointerLib.inf NULL|UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.inf