From 1dc39274fc024ffacf5a7ab535b6a0a535369057 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Mon, 21 Oct 2024 23:26:36 -0700 Subject: [PATCH 1/9] UnitTestFrameworkPkg/MemoryAllocationLibPosix: Add DEBUG_CLEAR_MEMORY() Add use of DEBUG_CLEAR_MEMORY() macros on all allocation and free operations in MemoryAllocationLibPosix to match behavior of all other MemoryAllocationLib instances. Signed-off-by: Michael D Kinney --- .../MemoryAllocationLibPosix.c | 92 +++++++++++++------ 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.c b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.c index 54029283fbbf..4ebdef18631f 100644 --- a/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.c +++ b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.c @@ -27,12 +27,19 @@ /// typedef struct { UINT32 Signature; - VOID *AllocatedBufffer; + VOID *AllocatedBuffer; UINTN TotalPages; VOID *AlignedBuffer; UINTN AlignedPages; } PAGE_HEAD; +#define POOL_HEAD_PRIVATE_SIGNATURE SIGNATURE_32 ('P', 'O', 'H', 'D') + +typedef struct { + UINT32 Signature; + UINT32 TotalSize; +} POOL_HEAD; + /** Allocates one or more 4KB pages of type EfiBootServicesData. @@ -165,16 +172,17 @@ AllocateAlignedPages ( // // We need reserve Alignment pages for PAGE_HEAD, as meta data. // - PageHead.Signature = PAGE_HEAD_PRIVATE_SIGNATURE; - PageHead.TotalPages = Pages + EFI_SIZE_TO_PAGES (Alignment) * 2; - PageHead.AlignedPages = Pages; - PageHead.AllocatedBufffer = malloc (EFI_PAGES_TO_SIZE (PageHead.TotalPages)); - if (PageHead.AllocatedBufffer == NULL) { - return NULL; - } + PageHead.Signature = PAGE_HEAD_PRIVATE_SIGNATURE; + PageHead.TotalPages = Pages + EFI_SIZE_TO_PAGES (Alignment) * 2; + PageHead.AlignedPages = Pages; + PageHead.AllocatedBuffer = malloc (EFI_PAGES_TO_SIZE (PageHead.TotalPages)); + + ASSERT (PageHead.AllocatedBuffer != NULL); - PageHead.AlignedBuffer = (VOID *)(((UINTN)PageHead.AllocatedBufffer + AlignmentMask) & ~AlignmentMask); - if ((UINTN)PageHead.AlignedBuffer - (UINTN)PageHead.AllocatedBufffer < sizeof (PAGE_HEAD)) { + DEBUG_CLEAR_MEMORY (PageHead.AllocatedBuffer, EFI_PAGES_TO_SIZE (PageHead.TotalPages)); + + PageHead.AlignedBuffer = (VOID *)(((UINTN)PageHead.AllocatedBuffer + AlignmentMask) & ~AlignmentMask); + if ((UINTN)PageHead.AlignedBuffer - (UINTN)PageHead.AllocatedBuffer < sizeof (PAGE_HEAD)) { PageHead.AlignedBuffer = (VOID *)((UINTN)PageHead.AlignedBuffer + Alignment); } @@ -265,21 +273,24 @@ FreeAlignedPages ( ) { PAGE_HEAD *PageHeadPtr; + VOID *AllocatedBuffer; + UINTN Length; - // - // NOTE: Partial free is not supported. Just keep it. - // - PageHeadPtr = (VOID *)((UINTN)Buffer - sizeof (PAGE_HEAD)); - if (PageHeadPtr->Signature != PAGE_HEAD_PRIVATE_SIGNATURE) { - return; - } + ASSERT (Buffer != NULL); - if (PageHeadPtr->AlignedPages != Pages) { - return; - } + PageHeadPtr = ((PAGE_HEAD *)Buffer) - 1; + + ASSERT (PageHeadPtr != NULL); + ASSERT (PageHeadPtr->Signature == PAGE_HEAD_PRIVATE_SIGNATURE); + ASSERT (PageHeadPtr->AlignedPages == Pages); + ASSERT (PageHeadPtr->AllocatedBuffer != NULL); + + AllocatedBuffer = PageHeadPtr->AllocatedBuffer; + Length = EFI_PAGES_TO_SIZE (PageHeadPtr->TotalPages); - PageHeadPtr->Signature = 0; - free (PageHeadPtr->AllocatedBufffer); + DEBUG_CLEAR_MEMORY (AllocatedBuffer, Length); + + free (AllocatedBuffer); } /** @@ -293,13 +304,27 @@ FreeAlignedPages ( @return A pointer to the allocated buffer or NULL if allocation fails. -**/VOID * +**/ +VOID * EFIAPI AllocatePool ( IN UINTN AllocationSize ) { - return malloc (AllocationSize); + POOL_HEAD *PoolHead; + UINTN TotalSize; + + TotalSize = sizeof (POOL_HEAD) + AllocationSize; + PoolHead = malloc (TotalSize); + if (PoolHead == NULL) { + return NULL; + } + + DEBUG_CLEAR_MEMORY (PoolHead, TotalSize); + PoolHead->Signature = POOL_HEAD_PRIVATE_SIGNATURE; + PoolHead->TotalSize = (UINT32)TotalSize; + + return (VOID *)(PoolHead + 1); } /** @@ -365,7 +390,7 @@ AllocateZeroPool ( { VOID *Buffer; - Buffer = malloc (AllocationSize); + Buffer = AllocatePool (AllocationSize); if (Buffer == NULL) { return NULL; } @@ -444,7 +469,7 @@ AllocateCopyPool ( { VOID *Memory; - Memory = malloc (AllocationSize); + Memory = AllocatePool (AllocationSize); if (Memory == NULL) { return NULL; } @@ -538,7 +563,7 @@ ReallocatePool ( { VOID *NewBuffer; - NewBuffer = malloc (NewSize); + NewBuffer = AllocatePool (NewSize); if ((NewBuffer != NULL) && (OldBuffer != NULL)) { memcpy (NewBuffer, OldBuffer, MIN (OldSize, NewSize)); } @@ -634,5 +659,16 @@ FreePool ( IN VOID *Buffer ) { - free (Buffer); + POOL_HEAD *PoolHead; + + ASSERT (Buffer != NULL); + + PoolHead = ((POOL_HEAD *)Buffer) - 1; + + ASSERT (PoolHead != NULL); + ASSERT (PoolHead->Signature == POOL_HEAD_PRIVATE_SIGNATURE); + ASSERT (PoolHead->TotalSize >= sizeof (POOL_HEAD)); + + DEBUG_CLEAR_MEMORY (PoolHead, PoolHead->TotalSize); + free (PoolHead); } From 2a6c83f3b92aa643aa9a412fd4bd659edfeb00b1 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Mon, 21 Oct 2024 23:28:15 -0700 Subject: [PATCH 2/9] UnitTestFrameworkPkg/UnitTestLib: Implement Free*() services Implement FreeUnitTestEntry(), FreeUnitTestSuiteEntry(), and FreeUnitTestFramework() so the UnitTestLib does not introduce any memory leaks. Signed-off-by: Michael D Kinney --- .../Library/UnitTestLib/UnitTestLib.c | 99 +++++++++++++++---- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c b/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c index 953f1959bc11..2f019825c4b5 100644 --- a/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c +++ b/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c @@ -131,6 +131,59 @@ CompareFingerprints ( return (CompareMem (FingerprintA, FingerprintB, UNIT_TEST_FINGERPRINT_SIZE) == 0); } +STATIC +EFI_STATUS +FreeUnitTestTestEntry ( + IN UNIT_TEST_LIST_ENTRY *TestEntry + ) +{ + if (TestEntry) { + if (TestEntry->UT.Description) { + FreePool (TestEntry->UT.Description); + } + + if (TestEntry->UT.Name) { + FreePool (TestEntry->UT.Name); + } + + FreePool (TestEntry); + } + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +FreeUnitTestSuiteEntry ( + IN UNIT_TEST_SUITE_LIST_ENTRY *SuiteEntry + ) +{ + UNIT_TEST_LIST_ENTRY *TestCase; + UNIT_TEST_LIST_ENTRY *NextTestCase; + + if (SuiteEntry) { + TestCase = (UNIT_TEST_LIST_ENTRY *)GetFirstNode (&(SuiteEntry->UTS.TestCaseList)); + while ((LIST_ENTRY *)TestCase != &(SuiteEntry->UTS.TestCaseList)) { + NextTestCase = (UNIT_TEST_LIST_ENTRY *)GetNextNode (&(SuiteEntry->UTS.TestCaseList), (LIST_ENTRY *)TestCase); + RemoveEntryList ((LIST_ENTRY *)TestCase); + FreeUnitTestTestEntry (TestCase); + TestCase = NextTestCase; + } + + if (SuiteEntry->UTS.Title) { + FreePool (SuiteEntry->UTS.Title); + } + + if (SuiteEntry->UTS.Name) { + FreePool (SuiteEntry->UTS.Name); + } + + FreePool (SuiteEntry); + } + + return EFI_SUCCESS; +} + /** Cleanup a test framework. @@ -151,27 +204,35 @@ FreeUnitTestFramework ( IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle ) { - // TODO: Finish this function. - return EFI_SUCCESS; -} + UNIT_TEST_FRAMEWORK *Framework; + UNIT_TEST_SUITE_LIST_ENTRY *Suite; + UNIT_TEST_SUITE_LIST_ENTRY *NextSuite; -STATIC -EFI_STATUS -FreeUnitTestSuiteEntry ( - IN UNIT_TEST_SUITE_LIST_ENTRY *SuiteEntry - ) -{ - // TODO: Finish this function. - return EFI_SUCCESS; -} + Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle; + if (Framework) { + Suite = (UNIT_TEST_SUITE_LIST_ENTRY *)GetFirstNode (&Framework->TestSuiteList); + while ((LIST_ENTRY *)Suite != &Framework->TestSuiteList) { + NextSuite = (UNIT_TEST_SUITE_LIST_ENTRY *)GetNextNode (&Framework->TestSuiteList, (LIST_ENTRY *)Suite); + RemoveEntryList ((LIST_ENTRY *)Suite); + FreeUnitTestSuiteEntry (Suite); + Suite = NextSuite; + } + + if (Framework->Title) { + FreePool (Framework->Title); + } + + if (Framework->ShortTitle) { + FreePool (Framework->ShortTitle); + } + + if (Framework->VersionString) { + FreePool (Framework->VersionString); + } + + FreePool (Framework); + } -STATIC -EFI_STATUS -FreeUnitTestTestEntry ( - IN UNIT_TEST_LIST_ENTRY *TestEntry - ) -{ - // TODO: Finish this function. return EFI_SUCCESS; } From 91c71cd54136a732c6b7f2ae7548e5f6791468f4 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Mon, 21 Oct 2024 23:29:20 -0700 Subject: [PATCH 3/9] UnitTestFraworkPkg: Enable DEBUG_CLEAR_MEMORY in host tests Update DSC files to always enable DEBUG_CLEAR_MEMORY() macros so memory is cleared on every memory allocation/free operation. Signed-off-by: Michael D Kinney --- UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc | 2 +- .../Test/UnitTestFrameworkPkgHostTestExpectFail.dsc | 2 +- UnitTestFrameworkPkg/UnitTestFrameworkPkg.dsc | 2 +- UnitTestFrameworkPkg/UnitTestFrameworkPkgCommon.dsc.inc | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc b/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc index b1b8eb0fe58b..1d45d4ba477b 100644 --- a/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc +++ b/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc @@ -19,7 +19,7 @@ !include UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc [PcdsPatchableInModule] - gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x17 + gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x1F [Components] # diff --git a/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTestExpectFail.dsc b/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTestExpectFail.dsc index d89659882dd7..face95d6c259 100644 --- a/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTestExpectFail.dsc +++ b/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTestExpectFail.dsc @@ -21,7 +21,7 @@ !include UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc [PcdsPatchableInModule] - gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x17 + gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x1F [Components] # diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dsc b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dsc index 75be90a55f7e..c2ec6db73e6c 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dsc +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dsc @@ -23,7 +23,7 @@ !include MdePkg/MdeLibs.dsc.inc [PcdsPatchableInModule] - gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x17 + gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x1F [Components] UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.inf diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkgCommon.dsc.inc b/UnitTestFrameworkPkg/UnitTestFrameworkPkgCommon.dsc.inc index 0de6e4362bf4..3edc273758f9 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkgCommon.dsc.inc +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkgCommon.dsc.inc @@ -14,7 +14,7 @@ PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf [PcdsFixedAtBuild] - gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x17 + gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x1F [BuildOptions] MSFT:*_*_*_CC_FLAGS = -D DISABLE_NEW_DEPRECATED_INTERFACES -D EDKII_UNIT_TEST_FRAMEWORK_ENABLED From 4e9f9942a2f0deaa23e11a0395aab1347a8ffa84 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Fri, 27 Dec 2024 15:34:11 -0800 Subject: [PATCH 4/9] BaseTools/Conf: Simplify VS20xx HOST_APPLICATION builds Add Empty_C_File_Host_Application_Build.c to BaseTools/Conf that is a C source file with only comments that is used to compile into an OBJ file using CC_FLAGS for a HOST_APPLICATION module and the OBJ is passed into the VS20xx DLINK action to provide the context required to select the correct default windows application libraries. Update build_rule.template to compile the empty C file and generate OBJ in the OUTPUT_DIR of the HOST_APPLICATION component and use the OBJ in the DLINK action. This simplifies CC_FLAGS and DLINK_FLAGS for all modules of type HOST_APPLICATION. Signed-off-by: Michael D Kinney --- .../Empty_C_File_Host_Application_Build.c | 7 +++++ BaseTools/Conf/build_rule.template | 26 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 BaseTools/Conf/Empty_C_File_Host_Application_Build.c diff --git a/BaseTools/Conf/Empty_C_File_Host_Application_Build.c b/BaseTools/Conf/Empty_C_File_Host_Application_Build.c new file mode 100644 index 000000000000..3a7380f88cf2 --- /dev/null +++ b/BaseTools/Conf/Empty_C_File_Host_Application_Build.c @@ -0,0 +1,7 @@ +/** @file + This is an empty C source file used in VS20xx HOST_APPLICATION + builds. + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ diff --git a/BaseTools/Conf/build_rule.template b/BaseTools/Conf/build_rule.template index 370cf5010748..0f32cc398e76 100755 --- a/BaseTools/Conf/build_rule.template +++ b/BaseTools/Conf/build_rule.template @@ -322,7 +322,7 @@ "$(OBJCOPY)" $(OBJCOPY_FLAGS) ${dst} -[Static-Library-File.USER_DEFINED, Static-Library-File.HOST_APPLICATION] +[Static-Library-File.USER_DEFINED] *.lib @@ -340,6 +340,30 @@ "$(DLINK)" -o ${dst} $(DLINK_FLAGS) $(DLINK_SPATH) -filelist $(STATIC_LIBRARY_FILES_LIST) $(DLINK2_FLAGS) + +[Static-Library-File.HOST_APPLICATION] + + *.lib + + + $(MAKE_FILE) + + + $(DEBUG_DIR)(+)$(MODULE_NAME) + + + "$(CC)" /Fo$(OUTPUT_DIR)/Empty_C_File_Host_Application_Build.obj $(CC_FLAGS) $(INC) $(EDK_TOOLS_PATH)/Conf/Empty_C_File_Host_Application_Build.c + "$(DLINK)" $(DLINK_FLAGS) $(DLINK_SPATH) $(OUTPUT_DIR)/Empty_C_File_Host_Application_Build.obj @$(STATIC_LIBRARY_FILES_LIST) + + + "$(CC)" -o $(OUTPUT_DIR)/Empty_C_File_Host_Application_Build.obj $(CC_FLAGS) $(INC) $(EDK_TOOLS_PATH)/Conf/Empty_C_File_Host_Application_Build.c + "$(DLINK)" $(DLINK_FLAGS) $(DLINK_SPATH) $(OUTPUT_DIR)/Empty_C_File_Host_Application_Build.obj @$(STATIC_LIBRARY_FILES_LIST) + + + "$(DLINK)" $(DLINK_FLAGS) -Wl,--start-group,@$(STATIC_LIBRARY_FILES_LIST),--end-group $(DLINK2_FLAGS) + + + "$(DLINK)" -o ${dst} $(DLINK_FLAGS) $(DLINK_SPATH) -filelist $(STATIC_LIBRARY_FILES_LIST) $(DLINK2_FLAGS) [Dynamic-Library-File] From 09dc757df34674ea6c7d98b97265b4de4b01e68a Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Tue, 22 Oct 2024 00:28:19 -0700 Subject: [PATCH 5/9] UnitTestFrameworkPkg: Use /MTd and enable Address Sanitizers * Update host based unit test VS20xx builds to use /MTd instead of /MT to enable use of debug libraries for host based unit tests. * Enable /fsanitize=address for host based unit test VS2019 builds * Enable /fsanitize=address for host based unit test VS2022 builds * Enable -fsanitize=address for host based unit test GCC builds * Add UNIT_TESTING_ADDRESS_SANITIZER_ENABLE define that is set to TRUE by default so it is always enabled, but can be set to FALSE to temporarily disable during development/debug of unit tests. * Add address sanitizer information to ReadMe.md Enabling the Address Sanitizer can detect double frees, buffer overflow, buffer underflow, access to invalid addresses, and various exceptions. These can be detected in both the unit test case sources as well as the code under test. Signed-off-by: Michael D Kinney --- .../Library/GoogleTestLib/GoogleTestLib.inf | 2 +- .../UnitTestDebugAssertLibHost.inf | 2 +- UnitTestFrameworkPkg/ReadMe.md | 51 ++++++++++++++++++- .../UnitTestFrameworkPkgHost.dsc.inc | 25 +++++++-- 4 files changed, 71 insertions(+), 9 deletions(-) diff --git a/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf b/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf index 0c522832e9d8..7835a333ea27 100644 --- a/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf +++ b/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf @@ -28,6 +28,6 @@ UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec [BuildOptions] - MSFT:*_*_*_CC_FLAGS == /c /EHs /Zi /Od /MT + MSFT:*_*_*_CC_FLAGS == /c /EHs /Zi /Od /MTd GCC:*_*_IA32_CC_FLAGS == -g -c -fshort-wchar -fexceptions -O0 -m32 -malign-double -fno-pie GCC:*_*_X64_CC_FLAGS == -g -c -fshort-wchar -fexceptions -O0 -m64 -fno-pie "-DEFIAPI=__attribute__((ms_abi))" diff --git a/UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.inf b/UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.inf index 9a7673f179cf..7458df794866 100644 --- a/UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.inf +++ b/UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.inf @@ -31,6 +31,6 @@ UnitTestLib [BuildOptions] - MSFT:*_*_*_CC_FLAGS == /c /EHs /Zi /Od /MT + MSFT:*_*_*_CC_FLAGS == /c /EHs /Zi /Od /MTd GCC:*_*_IA32_CC_FLAGS == -g -c -fshort-wchar -fexceptions -O0 -m32 -malign-double -fno-pie GCC:*_*_X64_CC_FLAGS == -g -c -fshort-wchar -fexceptions -O0 -m64 -fno-pie "-DEFIAPI=__attribute__((ms_abi))" diff --git a/UnitTestFrameworkPkg/ReadMe.md b/UnitTestFrameworkPkg/ReadMe.md index 50d8e1b51d8f..9c069fb7a7f9 100644 --- a/UnitTestFrameworkPkg/ReadMe.md +++ b/UnitTestFrameworkPkg/ReadMe.md @@ -34,7 +34,22 @@ GoogleTest requires less overhead to register test suites and test cases compare There are also a number of tools that layer on top of GoogleTest that improve developer productivity. One example is the VS Code extension [C++ TestMate](https://marketplace.visualstudio.com/items?itemName=matepek.vscode-catch2-test-adapter) -that may be used to implement, run, and debug unit tests implemented using GoogleTest. +that may be used to implement, run, and debug unit tests implemented using GoogleTest. The following +is an example of the C++ TestMate JSON configuration to find unit tests and configure the environment +for unit test execution. + +``` +"testMate.cpp.test.advancedExecutables": [ + { + "pattern": "Build/**/*Test*", + "cwd": "${absDirpath}", + "env": { + "GTEST_CATCH_EXCEPTIONS": "0", + "ASAN_OPTIONS": "detect_leaks=0", + } + } +], +``` If a component can be tested with host-based unit tests, then GoogleTest is recommended. The MdePkg contains a port of the BaseSafeIntLib unit tests in the GoogleTest style so the differences between @@ -69,6 +84,7 @@ reviewed. The paths to the SecureBootVariableLib unit tests are: | JUNIT XML Reports | YES | YES | | Execute subset of tests | NO | YES | | VS Code Extensions | NO | YES | +| Address Sanitizer | Cmocka | YES | ## Framework Libraries @@ -1007,12 +1023,14 @@ See this example in `UnitTestFrameworkPkgHostTest.dsc`... Also, based on the type of tests that are being created, the associated DSC include file from the UnitTestFrameworkPkg for Host or Target based tests should also be included at the top of the DSC -file. +file. This provides the default defines and library class mappings requires for unit testing. ``` !include UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc ``` + > **NOTE**: DSC files for host based unit tests must **not** include default mappings from packages such as `MdePkg/MdeLibs.dsc.inc`. This DSC files provides default defines and library mappings for firmware builds that may not be compatible with host based unit test builds. Instead, the DSC file for host based unit tests must provide all the settings required for host based unit tests. + Lastly, in the case that the test build has specific dependent libraries associated with it, they should be added in the \ sub-section for the INF file in the `[Components]` section of the DSC file. Note that it is within this sub-section where you can @@ -1373,6 +1391,15 @@ stuart_update -c ./.pytool/CISettings.py TOOL_CHAIN_TAG=VS2022 stuart_ci_build -c ./.pytool/CISettings.py TOOL_CHAIN_TAG=VS2022 -t NOOPT -p MdePkg ``` +#### Disabling Address Sanitizer + +By default, the address sanitizer feature is enabled for all host based unit test builds. It can be disabled for +development/debug purposes by setting the DSC define `UNIT_TESTING_ADDRESS_SANITIZER_ENABLE` to `FALSE`. + +``` +stuart_ci_build -c ./.pytool/CISettings.py TOOL_CHAIN_TAG=VS2022 -t NOOPT -p MdePkg BLD_*_UNIT_TESTING_ADDRESS_SANITIZER_ENABLE=FALSE +``` + ### Evaluating the Results In your immediate output, any build failures will be highlighted. You can see these below as "WARNING" and "ERROR" messages. @@ -1463,6 +1490,26 @@ c:\_uefi\MdePkg\Test\UnitTest\Library\BaseSafeIntLib\TestBaseSafeIntLib.c:35: er ``` +### Manually Running Unit Test Executables + +The host based unit test executed using `stuart_ci_build` sets up the environment to run host based unit tests +including environment variable settings. If host based unit test executable are run manually either from a +shell or using VS Code extensions such as `C++ TestMate`, then the environment must be setup correctly. + +#### Windows Environment Variable Settings + +``` +set GTEST_CATCH_EXCEPTIONS=0 +set ASAN_OPTIONS=detect_leaks=0 +``` + +#### Linux Environment Variable Settings + +``` +export GTEST_CATCH_EXCEPTIONS=0 +export ASAN_OPTIONS=detect_leaks=0 +``` + ### XML Reporting Mode Unit test applications using Framework are built using Cmocka that requires the diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc b/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc index 2012c3ae9a32..1c288558781d 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc @@ -8,6 +8,9 @@ !include UnitTestFrameworkPkg/UnitTestFrameworkPkgCommon.dsc.inc +[Defines] + UNIT_TESTING_ADDRESS_SANITIZER_ENABLE = TRUE + [LibraryClasses.common.HOST_APPLICATION] BaseLib|MdePkg/Library/BaseLib/UnitTestHostBaseLib.inf UnitTestHostBaseLib|MdePkg/Library/BaseLib/UnitTestHostBaseLib.inf @@ -26,10 +29,18 @@ NULL|MdePkg/Library/StackCheckLibNull/StackCheckLibNullHostApplication.inf [BuildOptions] - MSFT:*_*_*_CC_FLAGS = /MT + MSFT:*_*_*_CC_FLAGS = /MTd GCC:*_*_*_CC_FLAGS = -fno-pie +!if $(UNIT_TESTING_ADDRESS_SANITIZER_ENABLE) + # + # Enable Address Sanitizer for VS2019, VS2022, and GCC + # + MSFT:*_VS2019_*_CC_FLAGS = /fsanitize=address + MSFT:*_VS2022_*_CC_FLAGS = /fsanitize=address + GCC:*_*_*_CC_FLAGS = -fsanitize=address +!endif !ifdef $(UNIT_TESTING_DEBUG) - MSFT:*_*_*_CC_FLAGS = /MTd -D UNIT_TESTING_DEBUG=1 + MSFT:*_*_*_CC_FLAGS = -D UNIT_TESTING_DEBUG=1 GCC:*_*_*_CC_FLAGS = -D UNIT_TESTING_DEBUG=1 XCODE:*_*_*_CC_FLAGS = -D UNIT_TESTING_DEBUG=1 !endif @@ -41,9 +52,7 @@ # MSFT # MSFT:*_*_*_CC_FLAGS = /EHs - MSFT:*_*_*_DLINK_FLAGS == /out:"$(BIN_DIR)\$(MODULE_NAME_GUID).exe" /pdb:"$(BIN_DIR)\$(MODULE_NAME_GUID).pdb" /IGNORE:4001 /NOLOGO /SUBSYSTEM:CONSOLE /DEBUG /STACK:0x40000,0x40000 /WHOLEARCHIVE - MSFT:*_*_IA32_DLINK_FLAGS = /MACHINE:I386 - MSFT:*_*_X64_DLINK_FLAGS = /MACHINE:AMD64 + MSFT:*_*_*_DLINK_FLAGS == /nologo /SUBSYSTEM:CONSOLE /DEBUG /out:"$(BIN_DIR)\$(MODULE_NAME_GUID).exe" /pdb:"$(BIN_DIR)\$(MODULE_NAME_GUID).pdb" MSFT:*_VS2015_IA32_DLINK_FLAGS = /LIBPATH:"%VS2015_PREFIX%Lib" /LIBPATH:"%VS2015_PREFIX%VC\Lib" /LIBPATH:"%UniversalCRTSdkDir%lib\%UCRTVersion%\ucrt\x86" /LIBPATH:"%WindowsSdkDir%lib\%WindowsSDKLibVersion%\um\x86" MSFT:*_VS2015x86_IA32_DLINK_FLAGS = /LIBPATH:"%VS2015_PREFIX%Lib" /LIBPATH:"%VS2015_PREFIX%VC\Lib" /LIBPATH:"%UniversalCRTSdkDir%lib\%UCRTVersion%\ucrt\x86" /LIBPATH:"%WindowsSdkDir%lib\%WindowsSDKLibVersion%\um\x86" @@ -68,6 +77,12 @@ # GCC:*_*_*_DLINK_FLAGS = -Wl,--whole-archive GCC:*_*_*_DLINK2_FLAGS == -Wl,--no-whole-archive -lgcov -lpthread -lstdc++ -lm +!if $(UNIT_TESTING_ADDRESS_SANITIZER_ENABLE) + # + # Enable Address Sanitizer for GCC for HOST_APPLICATION + # + GCC:*_*_*_DLINK_FLAGS = -fsanitize=address +!endif # # Need to do this link via gcc and not ld as the pathing to libraries changes from OS version to OS version From 48d3e04cd69627b7a18199e4e49501e5062ceced Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Tue, 22 Oct 2024 17:01:22 -0700 Subject: [PATCH 6/9] UnitTestFrameworkPkg: Add failing unit tests cases for sanitizer Add GoogleTest and Framework based unit tests that are expected to fail and be caught by Address Sanitizer. These unit tests verify that an address sanitizer is enabled and detecting the following conditions. It also provide examples of the expected output when an Address Sanitizer detected these types of issues. * double free * buffer overflow * buffer underflow * null ptr * invalid address * divide by zero Signed-off-by: Michael D Kinney --- .../SampleGoogleTest/SampleGoogleTest.cpp | 139 ++++++++++++++++++ .../SampleUnitTestBufferOverflow.c | 117 +++++++++++++++ .../SampleUnitTestBufferOverflow.inf | 32 ++++ .../SampleUnitTestBufferUnderflow.c | 117 +++++++++++++++ .../SampleUnitTestBufferUnderflow.inf | 32 ++++ .../SampleUnitTestDoubleFree.c | 117 +++++++++++++++ .../SampleUnitTestDoubleFree.inf | 32 ++++ .../SampleUnitTestInvalidAddress.c | 110 ++++++++++++++ .../SampleUnitTestInvalidAddress.inf | 31 ++++ .../SampleUnitTestNullAddress.c | 110 ++++++++++++++ .../SampleUnitTestNullAddress.inf | 31 ++++ ...UnitTestFrameworkPkgHostTestExpectFail.dsc | 16 +- .../UnitTestFrameworkPkg.ci.yaml | 7 +- 13 files changed, 889 insertions(+), 2 deletions(-) create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.c create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.inf create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.c create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.inf create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.c create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.inf create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.c create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.inf create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.c create mode 100644 UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.inf diff --git a/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp b/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp index 2c2765e1e5ab..f3643dc864de 100644 --- a/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp +++ b/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp @@ -12,6 +12,7 @@ extern "C" { #include #include #include + #include } /** @@ -294,6 +295,144 @@ TEST (MacroTestsMessages, MacroTraceMessage) { ASSERT_TRUE (TRUE); } +/** + Sample unit test that performs double free +**/ +TEST (SanitizerTests, DoubleFreeDeathTest) { + UINT8 *Pointer; + + Pointer = (UINT8 *)AllocatePool (100); + ASSERT_NE (Pointer, (UINT8 *)NULL); + FreePool (Pointer); + // + // Second free that should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (FreePool (Pointer), "ERROR: AddressSanitizer: heap-use-after-free"); +} + +/** + Sample unit test that performs read past end of allocated buffer +**/ +TEST (SanitizerTests, BufferOverflowReadDeathTest) { + UINT8 *Pointer; + UINT8 Value; + + Pointer = (UINT8 *)AllocatePool (100); + ASSERT_NE (Pointer, (UINT8 *)NULL); + + // + // Read past end of allocated buffer that should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (Value = Pointer[110], "ERROR: AddressSanitizer: heap-buffer-overflow"); + ASSERT_EQ (Value, Value); + + FreePool (Pointer); +} + +/** + Sample unit test that performs write past end of allocated buffer +**/ +TEST (SanitizerTests, BufferOverflowWriteDeathTest) { + UINT8 *Pointer; + + Pointer = (UINT8 *)AllocatePool (100); + ASSERT_NE (Pointer, (UINT8 *)NULL); + + // + // Write past end of allocated buffer that should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (Pointer[110] = 0, "ERROR: AddressSanitizer: heap-buffer-overflow"); + + FreePool (Pointer); +} + +/** + Sample unit test that performs read before beginning of allocated buffer +**/ +TEST (SanitizerTests, BufferUnderflowReadDeathTest) { + UINT8 *Pointer; + UINT8 Value; + + Pointer = (UINT8 *)AllocatePool (100); + ASSERT_NE (Pointer, (UINT8 *)NULL); + + // + // Read past end of allocated buffer that should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (Value = Pointer[-10], "ERROR: AddressSanitizer: heap-buffer-overflow"); + ASSERT_EQ (Value, Value); + + FreePool (Pointer); +} + +/** + Sample unit test that performs read from address 0 (NULL) +**/ +TEST (SanitizerTests, NullPointerReadDeathTest) { + UINT8 Value; + + // + // Read from address 0 should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (Value = *(UINT8 *)(NULL), "ERROR: AddressSanitizer: "); + ASSERT_EQ (Value, Value); +} + +/** + Sample unit test that performs write to address 0 (NULL) +**/ +TEST (SanitizerTests, NullPointerWriteDeathTest) { + // + // Write to address 0 should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (*(UINT8 *)(NULL) = 0, "ERROR: AddressSanitizer: "); +} + +/** + Sample unit test that performs read from invalid address -1 +**/ +TEST (SanitizerTests, InvalidPointerReadDeathTest) { + UINT8 Value; + + // + // Read from address -1 should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (Value = *(UINT8 *)(-1), "ERROR: AddressSanitizer: "); + ASSERT_EQ (Value, Value); +} + +/** + Sample unit test that performs write to invalid address -1 +**/ +TEST (SanitizerTests, InvalidPointerWriteDeathTest) { + // + // Write to address -1 should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (*(UINT8 *)(-1) = 0, "ERROR: AddressSanitizer: "); +} + +UINTN +DivideWithNoParameterChecking ( + UINTN Dividend, + UINTN Divisor + ) +{ + // + // Perform integer division with no check for divide by zero + // + return (Dividend / Divisor); +} + +/** + Sample unit test that performs a divide by 0 +**/ +TEST (SanitizerTests, DivideByZeroDeathTest) { + // + // Divide by 0 should be caught by address sanitizer, log details, and exit + // + EXPECT_DEATH (DivideWithNoParameterChecking (10, 0), "ERROR: AddressSanitizer: "); +} + int main ( int argc, diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.c b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.c new file mode 100644 index 000000000000..1244854e41ff --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.c @@ -0,0 +1,117 @@ +/** @file + Sample UnitTest built for execution on a Host machine. + This test case generates a buffer overflow that is caught by a sanitizer. + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TEST_NAME "Sample Unit Test Sanitize Buffer Overflow" +#define UNIT_TEST_VERSION "0.1" + +/** + Sample unit test the performs a buffer overflow + + @param[in] Context [Optional] An optional parameter that enables: + 1) test-case reuse with varied parameters and + 2) test-case re-entry for Target tests that need a + reboot. This parameter is a VOID* and it is the + responsibility of the test author to ensure that the + contents are well understood by all test cases that may + consume it. + + @retval UNIT_TEST_PASSED The Unit test has completed and the test + case was successful. + @retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. +**/ +UNIT_TEST_STATUS +EFIAPI +SanitizeBufferOverflow ( + IN UNIT_TEST_CONTEXT Context + ) +{ + UINT8 *Pointer; + + Pointer = (UINT8 *)AllocatePool (100); + UT_ASSERT_NOT_NULL (Pointer); + Pointer[110] = 0; + FreePool (Pointer); + + return UNIT_TEST_PASSED; +} + +/** + Initialize the unit test framework, suite, and unit tests for the + sample unit tests and run the unit tests. + + @retval EFI_SUCCESS All test cases were dispatched. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + initialize the unit tests. +**/ +EFI_STATUS +EFIAPI +UefiTestMain ( + VOID + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE SanitizeTests; + + Framework = NULL; + + DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_NAME, UNIT_TEST_VERSION)); + + // + // Start setting up the test framework for running the tests. + // + Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status)); + goto EXIT; + } + + // + // Populate the Macro Tests with ASSERT() enabled + // + Status = CreateUnitTestSuite (&SanitizeTests, Framework, "Sanitize tests", "Sanitize tests", NULL, NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for SanitizeTests\n")); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + AddTestCase (SanitizeTests, "Sanitize Buffer Overflow", "SanitizeBufferOverflow", SanitizeBufferOverflow, NULL, NULL, NULL); + + // + // Execute the tests. + // + Status = RunAllTestSuites (Framework); + +EXIT: + if (Framework) { + FreeUnitTestFramework (Framework); + } + + return Status; +} + +/** + Standard POSIX C entry point for host based unit test execution. +**/ +int +main ( + int argc, + char *argv[] + ) +{ + return UefiTestMain (); +} diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.inf b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.inf new file mode 100644 index 000000000000..7f4c8aedf99c --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.inf @@ -0,0 +1,32 @@ +## @file +# Sample UnitTest built for execution on a Host machine. +# This test case generates a buffer overflow that is caught by a sanitizer. +# +# Copyright (c) 2024, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = SampleUnitTestBufferOverflow + FILE_GUID = 3772390C-CF33-4826-BB2F-A279C1AC12E0 + MODULE_TYPE = HOST_APPLICATION + VERSION_STRING = 1.0 + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + SampleUnitTestBufferOverflow.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + UnitTestLib + MemoryAllocationLib diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.c b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.c new file mode 100644 index 000000000000..3159af439e67 --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.c @@ -0,0 +1,117 @@ +/** @file + Sample UnitTest built for execution on a Host machine. + This test case generates a buffer underflow that is caught by a sanitizer. + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TEST_NAME "Sample Unit Test Sanitize Buffer Underflow" +#define UNIT_TEST_VERSION "0.1" + +/** + Sample unit test the performs a buffer underflow + + @param[in] Context [Optional] An optional parameter that enables: + 1) test-case reuse with varied parameters and + 2) test-case re-entry for Target tests that need a + reboot. This parameter is a VOID* and it is the + responsibility of the test author to ensure that the + contents are well understood by all test cases that may + consume it. + + @retval UNIT_TEST_PASSED The Unit test has completed and the test + case was successful. + @retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. +**/ +UNIT_TEST_STATUS +EFIAPI +SanitizeBufferUnderflow ( + IN UNIT_TEST_CONTEXT Context + ) +{ + UINT8 *Pointer; + + Pointer = (UINT8 *)AllocatePool (100); + UT_ASSERT_NOT_NULL (Pointer); + Pointer[-10] = 0; + FreePool (Pointer); + + return UNIT_TEST_PASSED; +} + +/** + Initialize the unit test framework, suite, and unit tests for the + sample unit tests and run the unit tests. + + @retval EFI_SUCCESS All test cases were dispatched. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + initialize the unit tests. +**/ +EFI_STATUS +EFIAPI +UefiTestMain ( + VOID + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE SanitizeTests; + + Framework = NULL; + + DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_NAME, UNIT_TEST_VERSION)); + + // + // Start setting up the test framework for running the tests. + // + Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status)); + goto EXIT; + } + + // + // Populate the Macro Tests with ASSERT() enabled + // + Status = CreateUnitTestSuite (&SanitizeTests, Framework, "Sanitize tests", "Sanitize tests", NULL, NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for SanitizeTests\n")); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + AddTestCase (SanitizeTests, "Sanitize Buffer Underflow", "SanitizeBufferUnderflow", SanitizeBufferUnderflow, NULL, NULL, NULL); + + // + // Execute the tests. + // + Status = RunAllTestSuites (Framework); + +EXIT: + if (Framework) { + FreeUnitTestFramework (Framework); + } + + return Status; +} + +/** + Standard POSIX C entry point for host based unit test execution. +**/ +int +main ( + int argc, + char *argv[] + ) +{ + return UefiTestMain (); +} diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.inf b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.inf new file mode 100644 index 000000000000..eccff61dfd65 --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.inf @@ -0,0 +1,32 @@ +## @file +# Sample UnitTest built for execution on a Host machine. +# This test case generates a buffer underflow that is caught by a sanitizer. +# +# Copyright (c) 2024, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = SampleUnitTestBufferUnderflow + FILE_GUID = ECA331D2-D794-4798-B145-770A570F3309 + MODULE_TYPE = HOST_APPLICATION + VERSION_STRING = 1.0 + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + SampleUnitTestBufferUnderflow.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + UnitTestLib + MemoryAllocationLib diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.c b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.c new file mode 100644 index 000000000000..8c635e8a16a2 --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.c @@ -0,0 +1,117 @@ +/** @file + Sample UnitTest built for execution on a Host machine. + This test case generates a double free that is caught by a sanitizer. + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TEST_NAME "Sample Unit Test Sanitize Double Free" +#define UNIT_TEST_VERSION "0.1" + +/** + Sample unit test the performs a double free + + @param[in] Context [Optional] An optional parameter that enables: + 1) test-case reuse with varied parameters and + 2) test-case re-entry for Target tests that need a + reboot. This parameter is a VOID* and it is the + responsibility of the test author to ensure that the + contents are well understood by all test cases that may + consume it. + + @retval UNIT_TEST_PASSED The Unit test has completed and the test + case was successful. + @retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. +**/ +UNIT_TEST_STATUS +EFIAPI +SanitizeDoubleFree ( + IN UNIT_TEST_CONTEXT Context + ) +{ + UINT8 *Pointer; + + Pointer = (UINT8 *)AllocatePool (100); + UT_ASSERT_NOT_NULL (Pointer); + FreePool (Pointer); + FreePool (Pointer); + + return UNIT_TEST_PASSED; +} + +/** + Initialize the unit test framework, suite, and unit tests for the + sample unit tests and run the unit tests. + + @retval EFI_SUCCESS All test cases were dispatched. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + initialize the unit tests. +**/ +EFI_STATUS +EFIAPI +UefiTestMain ( + VOID + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE SanitizeTests; + + Framework = NULL; + + DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_NAME, UNIT_TEST_VERSION)); + + // + // Start setting up the test framework for running the tests. + // + Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status)); + goto EXIT; + } + + // + // Populate the Macro Tests with ASSERT() enabled + // + Status = CreateUnitTestSuite (&SanitizeTests, Framework, "Sanitize tests", "Sanitize tests", NULL, NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for SanitizeTests\n")); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + AddTestCase (SanitizeTests, "Sanitize Double Free", "SanitizeDoubleFree", SanitizeDoubleFree, NULL, NULL, NULL); + + // + // Execute the tests. + // + Status = RunAllTestSuites (Framework); + +EXIT: + if (Framework) { + FreeUnitTestFramework (Framework); + } + + return Status; +} + +/** + Standard POSIX C entry point for host based unit test execution. +**/ +int +main ( + int argc, + char *argv[] + ) +{ + return UefiTestMain (); +} diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.inf b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.inf new file mode 100644 index 000000000000..9903a82886bb --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.inf @@ -0,0 +1,32 @@ +## @file +# Sample UnitTest built for execution on a Host machine. +# This test case generates a double free that is caught by a sanitizer. +# +# Copyright (c) 2024, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = SampleUnitTestDoubleFree + FILE_GUID = B40BBE1C-90AB-4DE9-9B49-E734666FF9A7 + MODULE_TYPE = HOST_APPLICATION + VERSION_STRING = 1.0 + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + SampleUnitTestDoubleFree.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + UnitTestLib + MemoryAllocationLib diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.c b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.c new file mode 100644 index 000000000000..74c2bd7e2ced --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.c @@ -0,0 +1,110 @@ +/** @file + Sample UnitTest built for execution on a Host machine. + This test case accesses an invalid address that is caught by a sanitizer. + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#include +#include +#include +#include +#include +#include + +#define UNIT_TEST_NAME "Sample Unit Test Sanitize Invalid Address" +#define UNIT_TEST_VERSION "0.1" + +/** + Sample unit test that accesses an invalid address + + @param[in] Context [Optional] An optional parameter that enables: + 1) test-case reuse with varied parameters and + 2) test-case re-entry for Target tests that need a + reboot. This parameter is a VOID* and it is the + responsibility of the test author to ensure that the + contents are well understood by all test cases that may + consume it. + + @retval UNIT_TEST_PASSED The Unit test has completed and the test + case was successful. + @retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. +**/ +UNIT_TEST_STATUS +EFIAPI +SanitizeInvalidAddress ( + IN UNIT_TEST_CONTEXT Context + ) +{ + *(UINT8 *)(-1) = 0; + return UNIT_TEST_PASSED; +} + +/** + Initialize the unit test framework, suite, and unit tests for the + sample unit tests and run the unit tests. + + @retval EFI_SUCCESS All test cases were dispatched. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + initialize the unit tests. +**/ +EFI_STATUS +EFIAPI +UefiTestMain ( + VOID + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE SanitizeTests; + + Framework = NULL; + + DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_NAME, UNIT_TEST_VERSION)); + + // + // Start setting up the test framework for running the tests. + // + Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status)); + goto EXIT; + } + + // + // Populate the Macro Tests with ASSERT() enabled + // + Status = CreateUnitTestSuite (&SanitizeTests, Framework, "Sanitize tests", "Sanitize tests", NULL, NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for SanitizeTests\n")); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + AddTestCase (SanitizeTests, "Sanitize Invalid Address", "SanitizeInvalidAddress", SanitizeInvalidAddress, NULL, NULL, NULL); + + // + // Execute the tests. + // + Status = RunAllTestSuites (Framework); + +EXIT: + if (Framework) { + FreeUnitTestFramework (Framework); + } + + return Status; +} + +/** + Standard POSIX C entry point for host based unit test execution. +**/ +int +main ( + int argc, + char *argv[] + ) +{ + return UefiTestMain (); +} diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.inf b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.inf new file mode 100644 index 000000000000..1804f286416f --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.inf @@ -0,0 +1,31 @@ +## @file +# Sample UnitTest built for execution on a Host machine. +# This test case accesses an invalid address that is caught by a sanitizer. +# +# Copyright (c) 2024, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = SampleUnitTestInvalidAddress + FILE_GUID = 60CED393-1342-45E9-9D8B-589F02255674 + MODULE_TYPE = HOST_APPLICATION + VERSION_STRING = 1.0 + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + SampleUnitTestInvalidAddress.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + UnitTestLib diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.c b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.c new file mode 100644 index 000000000000..bae76bcdfe6c --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.c @@ -0,0 +1,110 @@ +/** @file + Sample UnitTest built for execution on a Host machine. + This test case dereferences a NULL pointer that is caught by a sanitizer. + + Copyright (c) 2024, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#include +#include +#include +#include +#include +#include + +#define UNIT_TEST_NAME "Sample Unit Test Sanitize NULL Pointer" +#define UNIT_TEST_VERSION "0.1" + +/** + Sample unit test that dereferences a NULL pointer. + + @param[in] Context [Optional] An optional parameter that enables: + 1) test-case reuse with varied parameters and + 2) test-case re-entry for Target tests that need a + reboot. This parameter is a VOID* and it is the + responsibility of the test author to ensure that the + contents are well understood by all test cases that may + consume it. + + @retval UNIT_TEST_PASSED The Unit test has completed and the test + case was successful. + @retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. +**/ +UNIT_TEST_STATUS +EFIAPI +SanitizeNullAddress ( + IN UNIT_TEST_CONTEXT Context + ) +{ + *(UINT8 *)(NULL) = 0; + return UNIT_TEST_PASSED; +} + +/** + Initialize the unit test framework, suite, and unit tests for the + sample unit tests and run the unit tests. + + @retval EFI_SUCCESS All test cases were dispatched. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + initialize the unit tests. +**/ +EFI_STATUS +EFIAPI +UefiTestMain ( + VOID + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE SanitizeTests; + + Framework = NULL; + + DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_NAME, UNIT_TEST_VERSION)); + + // + // Start setting up the test framework for running the tests. + // + Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status)); + goto EXIT; + } + + // + // Populate the Macro Tests with ASSERT() enabled + // + Status = CreateUnitTestSuite (&SanitizeTests, Framework, "Sanitize tests", "Sanitize tests", NULL, NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for SanitizeTests\n")); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + AddTestCase (SanitizeTests, "Sanitize NULL Address", "SanitizeNullAddress", SanitizeNullAddress, NULL, NULL, NULL); + + // + // Execute the tests. + // + Status = RunAllTestSuites (Framework); + +EXIT: + if (Framework) { + FreeUnitTestFramework (Framework); + } + + return Status; +} + +/** + Standard POSIX C entry point for host based unit test execution. +**/ +int +main ( + int argc, + char *argv[] + ) +{ + return UefiTestMain (); +} diff --git a/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.inf b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.inf new file mode 100644 index 000000000000..0b7e8af3ce68 --- /dev/null +++ b/UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.inf @@ -0,0 +1,31 @@ +## @file +# Sample UnitTest built for execution on a Host machine. +# This test case dereferences a NULL pointer that is caught by a sanitizer. +# +# Copyright (c) 2024, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = SampleUnitTestNullAddress + FILE_GUID = FDD2EFA0-74BE-4628-A860-A60EF81B9023 + MODULE_TYPE = HOST_APPLICATION + VERSION_STRING = 1.0 + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + SampleUnitTestNullAddress.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + UnitTestLib diff --git a/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTestExpectFail.dsc b/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTestExpectFail.dsc index face95d6c259..73b4cb1b929e 100644 --- a/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTestExpectFail.dsc +++ b/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTestExpectFail.dsc @@ -1,8 +1,13 @@ ## @file -# UnitTestFrameworkPkg DSC file used to build host-based unit tests that archive +# UnitTestFrameworkPkg DSC file used to build host-based unit tests that are # always expected to fail to demonstrate the format of the log file and reports # when failures occur. # +# For Google Test based unit tests, in order to see full log of errors from the +# sanitizer, the Google Test handling of exceptions must be disabled by either +# setting the environment variable GTEST_CATCH_EXCEPTIONS=0 or passing +# --gtest-catch-exceptions=0 on the command line when executing unit tests. +# # Copyright (c) 2024, Intel Corporation. All rights reserved.
# SPDX-License-Identifier: BSD-2-Clause-Patent # @@ -42,3 +47,12 @@ MSFT:*_*_*_CC_FLAGS = /wd4723 } + + # + # Unit tests that perform illegal actions that are caught by a sanitizer + # + UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.inf + UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.inf + UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.inf + UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.inf + UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.inf diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml index c6a412c96078..cf51b21e00d4 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml @@ -66,7 +66,12 @@ "UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTestExpectFail/SampleGoogleTestHostExpectFail.inf", "UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTestGenerateException/SampleGoogleTestHostGenerateException.inf", "UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestExpectFail/SampleUnitTestHostExpectFail.inf", - "UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestGenerateException/SampleUnitTestHostGenerateException.inf" + "UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestGenerateException/SampleUnitTestHostGenerateException.inf", + "UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestDoubleFree/SampleUnitTestDoubleFree.inf", + "UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferOverflow/SampleUnitTestBufferOverflow.inf", + "UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestBufferUnderflow/SampleUnitTestBufferUnderflow.inf", + "UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestNullAddress/SampleUnitTestNullAddress.inf", + "UnitTestFrameworkPkg/Test/UnitTest/Sample/SampleUnitTestInvalidAddress/SampleUnitTestInvalidAddress.inf" ], "DscPath": "Test/UnitTestFrameworkPkgHostTest.dsc" }, From a90bc4313e8690a278a57156ab68c1eb33e5cfe6 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Wed, 23 Oct 2024 18:43:07 -0700 Subject: [PATCH 7/9] UnitTestFrameworkPkg/UnitTestLib: Reduce sanitizer false positive Use snprintf() in host based unit tests to format log messages and add host specific wrapper for LongJump() that is decorated with NORETURN to provide hint to address sanitizer that LongJump() never returns. Signed-off-by: Michael D Kinney --- .../UnitTestDebugAssertLibHost.cpp | 24 ++++++++++++++++++- .../Library/UnitTestLib/AssertCmocka.c | 18 +++++++++----- .../Library/UnitTestLib/RunTestsCmocka.c | 12 ++++++---- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.cpp b/UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.cpp index a4405cc205ba..ad8487ad1b6b 100644 --- a/UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.cpp +++ b/UnitTestFrameworkPkg/Library/UnitTestDebugAssertLib/UnitTestDebugAssertLibHost.cpp @@ -24,6 +24,27 @@ extern "C" { /// BASE_LIBRARY_JUMP_BUFFER *gUnitTestExpectAssertFailureJumpBuffer = NULL; + /** + LongJump wrapper for host-based unit test environments that is declared + NORETURN to avoid false positives from address sanitizer. + + @param JumpBuffer A pointer to CPU context buffer. + @param Value The value to return when the SetJump() context is + restored and must be non-zero. + **/ + static + VOID + NORETURN + EFIAPI + HostLongJump ( + IN BASE_LIBRARY_JUMP_BUFFER *JumpBuffer, + IN UINTN Value + ) + { + LongJump (JumpBuffer, Value); + UNREACHABLE (); + } + /** Unit test library replacement for DebugAssert() in DebugLib. @@ -47,7 +68,8 @@ extern "C" { if (gUnitTestExpectAssertFailureJumpBuffer != NULL) { UT_LOG_INFO ("Detected expected ASSERT: %a(%d): %a\n", FileName, LineNumber, Description); - LongJump (gUnitTestExpectAssertFailureJumpBuffer, 1); + HostLongJump (gUnitTestExpectAssertFailureJumpBuffer, 1); + UNREACHABLE (); } else { if (GetActiveFrameworkHandle () != NULL) { AsciiStrCpyS (Message, sizeof (Message), "Detected unexpected ASSERT("); diff --git a/UnitTestFrameworkPkg/Library/UnitTestLib/AssertCmocka.c b/UnitTestFrameworkPkg/Library/UnitTestLib/AssertCmocka.c index 0d8e36c938aa..b681f0480ae7 100644 --- a/UnitTestFrameworkPkg/Library/UnitTestLib/AssertCmocka.c +++ b/UnitTestFrameworkPkg/Library/UnitTestLib/AssertCmocka.c @@ -381,21 +381,27 @@ UnitTestExpectAssertFailure ( } if (UnitTestStatus == UNIT_TEST_PASSED) { - UT_LOG_INFO ( - "[ASSERT PASS] %a:%d: UT_EXPECT_ASSERT_FAILURE(%a) detected expected assert\n", + snprintf ( + TempStr, + sizeof (TempStr), + "[ASSERT PASS] %s:%d: UT_EXPECT_ASSERT_FAILURE(%s) detected expected assert\n", FileName, - LineNumber, + (int)LineNumber, FunctionCall ); + UT_LOG_INFO (TempStr); } if (UnitTestStatus == UNIT_TEST_SKIPPED) { - UT_LOG_WARNING ( - "[ASSERT WARN] %a:%d: UT_EXPECT_ASSERT_FAILURE(%a) disabled\n", + snprintf ( + TempStr, + sizeof (TempStr), + "[ASSERT WARN] %s:%d: UT_EXPECT_ASSERT_FAILURE(%s) disabled\n", FileName, - LineNumber, + (int)LineNumber, FunctionCall ); + UT_LOG_WARNING (TempStr); } if (UnitTestStatus == UNIT_TEST_ERROR_TEST_FAILED) { diff --git a/UnitTestFrameworkPkg/Library/UnitTestLib/RunTestsCmocka.c b/UnitTestFrameworkPkg/Library/UnitTestLib/RunTestsCmocka.c index f24b65174c5c..5788a75b7f48 100644 --- a/UnitTestFrameworkPkg/Library/UnitTestLib/RunTestsCmocka.c +++ b/UnitTestFrameworkPkg/Library/UnitTestLib/RunTestsCmocka.c @@ -111,10 +111,14 @@ CmockaUnitTestTeardownFunctionRunner ( // stdout and stderr in their xml format // if (UnitTest->Log != NULL) { - print_message ("UnitTest: %s - %s\n", UnitTest->Name, UnitTest->Description); - print_message ("Log Output Start\n"); - print_message ("%s", UnitTest->Log); - print_message ("Log Output End\n"); + // + // UnitTest->Log can be a large buffer that is larger than what DEBUG() + // can support. Use printf() directly. + // + printf ("UnitTest: %s - %s\n", UnitTest->Name, UnitTest->Description); + printf ("Log Output Start\n"); + printf (UnitTest->Log); + printf ("Log Output End\n"); } // From 2117ba3b5b18de36fc33d1f56789835366bf67a1 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Mon, 25 Nov 2024 11:34:54 -0800 Subject: [PATCH 8/9] 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 | 93 ++++ .../AllocateBelowAddress.c | 435 ++++++++++++++++++ .../MemoryAllocationLibPosix.inf | 6 +- .../MemoryAllocationLibPosix/WinInclude.h | 23 + .../SampleGoogleTest/SampleGoogleTest.cpp | 210 +++++++++ .../UnitTestFrameworkPkg.ci.yaml | 1 + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec | 5 + .../UnitTestFrameworkPkgHost.dsc.inc | 1 + 8 files changed, 773 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 000000000000..46b15987bd89 --- /dev/null +++ b/UnitTestFrameworkPkg/Include/Library/HostMemoryAllocationBelowAddressLib.h @@ -0,0 +1,93 @@ +/** @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(). + + @retval TRUE The buffer was freed. + @retval FALSE The buffer could not be freed.. +**/ +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 000000000000..df856dc456f6 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/AllocateBelowAddress.c @@ -0,0 +1,435 @@ +/** @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 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 + ) +{ + 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) { + return NULL; + } + + // + // Reduce maximum address by the requested allocation length + // + MaximumAddress -= Length; + + AllocatedAddress = HostAllocateBufferInRange ( + (UINTN)MaximumAddress, + (UINTN)Length + ); + if (AllocatedAddress == NULL) { + 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(). + + @retval TRUE The buffer was freed. + @retval FALSE The buffer could not be freed.. +**/ +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) { + return NULL; + } + + // + // Make sure alignment is smaller than the largest supported virtual address + // + if (Alignment > MAX_UINTN) { + 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)) { + 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) { + 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 44ec3fd51722..1443600013e2 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 000000000000..1197018282c4 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/WinInclude.h @@ -0,0 +1,23 @@ +/** @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 +**/ + +#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 diff --git a/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp b/UnitTestFrameworkPkg/Test/GoogleTest/Sample/SampleGoogleTest/SampleGoogleTest.cpp index f3643dc864de..63c472bd4126 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 cf51b21e00d4..970ba900b820 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 ef0a148d4876..0553af4edc37 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 1c288558781d..27ed1df009b5 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 From 00791b2efb0641452dde1530623790e0d24474d0 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Tue, 3 Dec 2024 19:39:56 -0800 Subject: [PATCH 9/9] BaseTools/Plugin/HostBasedUnitTestRunner: Set ASAN env vars The environment variable `GTEST_CATCH_EXCEPTION` must be set to `0` for so all exceptions are handled by the address sanitizer and not GoogleTest. This allows stack back trace and other details to be logged by the address sanitizer so the source of the issue identified address sanitizer can be determined. The environment variable `ASAN_OPTIONS` must be set to `detect_leaks=0` to disable memory leak detection. The unit test frameworks may have memory leaks and some firmware code under test use cases may perform a memory allocation without a matching memory free as their expected behavior. Signed-off-by: Michael D Kinney --- .../HostBasedUnitTestRunner/HostBasedUnitTestRunner.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BaseTools/Plugin/HostBasedUnitTestRunner/HostBasedUnitTestRunner.py b/BaseTools/Plugin/HostBasedUnitTestRunner/HostBasedUnitTestRunner.py index b7ed863203af..c39dae236c9b 100644 --- a/BaseTools/Plugin/HostBasedUnitTestRunner/HostBasedUnitTestRunner.py +++ b/BaseTools/Plugin/HostBasedUnitTestRunner/HostBasedUnitTestRunner.py @@ -52,6 +52,12 @@ def do_post_build(self, thebuilder): failure_count = 0 + # Do not catch exceptions in gtest so they are handled by address sanitizer + shell_env.set_shell_var('GTEST_CATCH_EXCEPTIONS', '0') + + # Disable address sanitizer memory leak detection + shell_env.set_shell_var('ASAN_OPTIONS', 'detect_leaks=0') + # Set up the reporting type for Cmocka. shell_env.set_shell_var('CMOCKA_MESSAGE_OUTPUT', 'xml')