diff --git a/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTest.c b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTest.c
new file mode 100644
index 0000000000..fbc6e3ba46
--- /dev/null
+++ b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTest.c
@@ -0,0 +1,477 @@
+/** @file
+ Host-based unit tests for GptLib.
+
+ These tests exercise PartitionValidGptTable(), PartitionCheckGptEntry()
+ and PartitionRestoreGptTable() against an in-memory mock disk, covering
+ their behavior on well-formed GPT structures.
+
+ Copyright (c) 2026, SUSE LLC. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "GptLibUnitTestCommon.h"
+
+#define UNIT_TEST_NAME "GptLibUnitTest"
+#define UNIT_TEST_VERSION "1.0"
+
+// ---------------------------------------------------------------------------
+// PartitionValidGptTable() tests
+// ---------------------------------------------------------------------------
+
+/**
+ Verify that a well-formed primary GPT header is accepted.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestValidPrimaryHeaderIsAccepted (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER Header;
+
+ SetupDiskWithValidPrimary ();
+
+ UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
+ UT_ASSERT_EQUAL (Header.MyLBA, PRIMARY_PART_HEADER_LBA);
+ UT_ASSERT_EQUAL (Header.AlternateLBA, LAST_LBA);
+ UT_ASSERT_EQUAL (Header.NumberOfPartitionEntries, NUM_PARTITION_ENTRIES);
+ UT_ASSERT_EQUAL (Header.SizeOfPartitionEntry, PARTITION_ENTRY_SIZE);
+
+ return UNIT_TEST_PASSED;
+}
+
+/**
+ Verify that a well-formed backup GPT header is accepted.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestValidBackupHeaderIsAccepted (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER Header;
+
+ SetupDiskWithValidPrimary ();
+
+ //
+ // Backup header at the last LBA with its entry array after the usable
+ // region, as laid out by real partitioning tools. The validator must not
+ // reject an entry array located past FirstUsableLBA.
+ //
+ WriteGptTableAt (
+ LAST_LBA,
+ PRIMARY_PART_HEADER_LBA,
+ LAST_USABLE_LBA + 1,
+ NUM_PARTITION_ENTRIES,
+ PARTITION_ENTRY_SIZE,
+ 2
+ );
+
+ UT_ASSERT_TRUE (ValidateAt (LAST_LBA, &Header));
+ UT_ASSERT_EQUAL (Header.MyLBA, LAST_LBA);
+ UT_ASSERT_EQUAL (Header.AlternateLBA, PRIMARY_PART_HEADER_LBA);
+
+ return UNIT_TEST_PASSED;
+}
+
+/**
+ Verify that the minimum (92-byte) header size is accepted.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestMinimumHeaderSizeIsAccepted (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER Header;
+
+ SetupDiskWithValidPrimary ();
+
+ GetPrimaryHeader ()->Header.HeaderSize = TEST_GPT_HEADER_MIN_SIZE;
+ UpdateGptHeaderCrc (GetPrimaryHeader ());
+
+ UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
+
+ return UNIT_TEST_PASSED;
+}
+
+/**
+ Verify that a larger power-of-two (256-byte) partition entry size is accepted.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestLargerPowerOfTwoEntrySizeIsAccepted (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER Header;
+
+ SetupDiskWithValidPrimary ();
+
+ //
+ // SizeOfPartitionEntry of 256 (128 * 2^1) is legal per the UEFI spec.
+ //
+ WriteGptTableAt (PRIMARY_PART_HEADER_LBA, LAST_LBA, PRIMARY_PART_HEADER_LBA + 1, 8, 256, 2);
+
+ UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &Header));
+ UT_ASSERT_EQUAL (Header.SizeOfPartitionEntry, 256U);
+
+ return UNIT_TEST_PASSED;
+}
+
+// ---------------------------------------------------------------------------
+// PartitionCheckGptEntry() tests
+// ---------------------------------------------------------------------------
+
+/**
+ Verify that valid partition entries report no flags.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestValidEntriesReportNoFlags (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER Header;
+ EFI_PARTITION_ENTRY Entries[4];
+ EFI_PARTITION_ENTRY_STATUS Status[4];
+ UINTN Index;
+
+ InitCheckEntryHeader (&Header, 4, sizeof (EFI_PARTITION_ENTRY));
+ ZeroMem (Entries, sizeof (Entries));
+ ZeroMem (Status, sizeof (Status));
+
+ FillPartitionEntry (&Entries[0], &mPartitionGuid1, 34, 100);
+ FillPartitionEntry (&Entries[1], &mPartitionGuid2, 101, 200);
+
+ PartitionCheckGptEntry (&Header, Entries, Status);
+
+ for (Index = 0; Index < 4; Index++) {
+ UT_ASSERT_FALSE (Status[Index].OutOfRange);
+ UT_ASSERT_FALSE (Status[Index].Overlap);
+ UT_ASSERT_FALSE (Status[Index].OsSpecific);
+ }
+
+ return UNIT_TEST_PASSED;
+}
+
+/**
+ Verify that an OS-specific partition attribute is reported.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestOsSpecificAttributeIsReported (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER Header;
+ EFI_PARTITION_ENTRY Entries[1];
+ EFI_PARTITION_ENTRY_STATUS Status[1];
+
+ InitCheckEntryHeader (&Header, 1, sizeof (EFI_PARTITION_ENTRY));
+ ZeroMem (Entries, sizeof (Entries));
+ ZeroMem (Status, sizeof (Status));
+
+ FillPartitionEntry (&Entries[0], &mPartitionGuid1, 40, 100);
+ Entries[0].Attributes = BIT1;
+
+ PartitionCheckGptEntry (&Header, Entries, Status);
+
+ UT_ASSERT_TRUE (Status[0].OsSpecific);
+ UT_ASSERT_FALSE (Status[0].OutOfRange);
+
+ return UNIT_TEST_PASSED;
+}
+
+/**
+ Verify that unused partition entries are ignored.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestUnusedEntriesAreIgnored (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER Header;
+ EFI_PARTITION_ENTRY Entries[2];
+ EFI_PARTITION_ENTRY_STATUS Status[2];
+
+ InitCheckEntryHeader (&Header, 2, sizeof (EFI_PARTITION_ENTRY));
+ ZeroMem (Entries, sizeof (Entries));
+ ZeroMem (Status, sizeof (Status));
+
+ //
+ // Unused (zero PartitionTypeGUID) entry with nonsense LBAs must be skipped
+ // and must not participate in the overlap scan.
+ //
+ Entries[0].StartingLBA = MAX_UINT64;
+ Entries[0].EndingLBA = 0;
+ FillPartitionEntry (&Entries[1], &mPartitionGuid1, 40, 100);
+
+ PartitionCheckGptEntry (&Header, Entries, Status);
+
+ UT_ASSERT_FALSE (Status[0].OutOfRange);
+ UT_ASSERT_FALSE (Status[0].Overlap);
+ UT_ASSERT_FALSE (Status[1].OutOfRange);
+ UT_ASSERT_FALSE (Status[1].Overlap);
+
+ return UNIT_TEST_PASSED;
+}
+
+/**
+ Verify that the entry stride follows SizeOfPartitionEntry.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestEntryStrideFollowsSizeOfPartitionEntry (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER Header;
+ UINT8 Buffer[2 * 256];
+ EFI_PARTITION_ENTRY_STATUS Status[2];
+ EFI_PARTITION_ENTRY *DecoyEntry;
+
+ InitCheckEntryHeader (&Header, 2, 256);
+ ZeroMem (Buffer, sizeof (Buffer));
+ ZeroMem (Status, sizeof (Status));
+
+ //
+ // Real entries at 256-byte stride: entry 0 at offset 0, entry 1 at offset
+ // 256, deliberately overlapping. A decoy non-overlapping but out-of-range
+ // entry is placed at offset 128: a checker that wrongly walked with a
+ // 128-byte stride would flag OutOfRange instead of Overlap on index 1.
+ //
+ FillPartitionEntry ((EFI_PARTITION_ENTRY *)(VOID *)&Buffer[0], &mPartitionGuid1, 40, 100);
+ FillPartitionEntry ((EFI_PARTITION_ENTRY *)(VOID *)&Buffer[256], &mPartitionGuid2, 90, 150);
+ DecoyEntry = (EFI_PARTITION_ENTRY *)(VOID *)&Buffer[128];
+ FillPartitionEntry (DecoyEntry, &mPartitionGuid1, 1000, 2000);
+
+ PartitionCheckGptEntry (&Header, (EFI_PARTITION_ENTRY *)(VOID *)Buffer, Status);
+
+ UT_ASSERT_TRUE (Status[0].Overlap);
+ UT_ASSERT_TRUE (Status[1].Overlap);
+ UT_ASSERT_FALSE (Status[1].OutOfRange);
+
+ return UNIT_TEST_PASSED;
+}
+
+// ---------------------------------------------------------------------------
+// PartitionRestoreGptTable() tests
+// ---------------------------------------------------------------------------
+
+/**
+ Verify that a corrupted primary GPT is restored from the backup.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestRestorePrimaryFromBackup (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER BackupHeader;
+ EFI_PARTITION_TABLE_HEADER RestoredHeader;
+
+ SetupDiskWithValidPrimary ();
+
+ //
+ // Wipe the primary and keep only a valid backup whose entry array follows
+ // the usable region.
+ //
+ WriteGptTableAt (
+ LAST_LBA,
+ PRIMARY_PART_HEADER_LBA,
+ LAST_USABLE_LBA + 1,
+ NUM_PARTITION_ENTRIES,
+ PARTITION_ENTRY_SIZE,
+ 2
+ );
+ ZeroMem (GetDiskLba (PRIMARY_PART_HEADER_LBA), SECTOR_SIZE);
+ ZeroMem (GetDiskLba (PRIMARY_PART_HEADER_LBA + 1), PART_ARRAY_SIZE);
+
+ UT_ASSERT_FALSE (ValidateAt (PRIMARY_PART_HEADER_LBA, &RestoredHeader));
+ UT_ASSERT_TRUE (ValidateAt (LAST_LBA, &BackupHeader));
+
+ UT_ASSERT_TRUE (PartitionRestoreGptTable (&mBlockIo, &mDiskIo, &BackupHeader));
+
+ UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &RestoredHeader));
+ UT_ASSERT_EQUAL (RestoredHeader.MyLBA, PRIMARY_PART_HEADER_LBA);
+ UT_ASSERT_EQUAL (RestoredHeader.AlternateLBA, LAST_LBA);
+ UT_ASSERT_EQUAL (RestoredHeader.PartitionEntryLBA, PRIMARY_PART_HEADER_LBA + 1);
+
+ return UNIT_TEST_PASSED;
+}
+
+/**
+ Verify that a corrupted backup GPT is restored from the primary.
+
+ @param[in] Context Unit test context.
+
+ @retval UNIT_TEST_PASSED The test passed.
+**/
+STATIC
+UNIT_TEST_STATUS
+EFIAPI
+TestRestoreBackupFromPrimary (
+ IN UNIT_TEST_CONTEXT Context
+ )
+{
+ EFI_PARTITION_TABLE_HEADER PrimaryHeader;
+ EFI_PARTITION_TABLE_HEADER RestoredHeader;
+
+ SetupDiskWithValidPrimary ();
+
+ UT_ASSERT_TRUE (ValidateAt (PRIMARY_PART_HEADER_LBA, &PrimaryHeader));
+ UT_ASSERT_FALSE (ValidateAt (LAST_LBA, &RestoredHeader));
+
+ UT_ASSERT_TRUE (PartitionRestoreGptTable (&mBlockIo, &mDiskIo, &PrimaryHeader));
+
+ UT_ASSERT_TRUE (ValidateAt (LAST_LBA, &RestoredHeader));
+ UT_ASSERT_EQUAL (RestoredHeader.MyLBA, LAST_LBA);
+ UT_ASSERT_EQUAL (RestoredHeader.AlternateLBA, PRIMARY_PART_HEADER_LBA);
+ UT_ASSERT_EQUAL (RestoredHeader.PartitionEntryLBA, LAST_USABLE_LBA + 1);
+
+ return UNIT_TEST_PASSED;
+}
+
+// ---------------------------------------------------------------------------
+// Test runner
+// ---------------------------------------------------------------------------
+
+/**
+ Configure and run the GptLib unit test suites.
+
+ @retval EFI_SUCCESS The unit tests ran to completion.
+ @retval other An error occurred setting up or running the tests.
+**/
+EFI_STATUS
+EFIAPI
+UefiTestMain (
+ VOID
+ )
+{
+ EFI_STATUS Status;
+ UNIT_TEST_FRAMEWORK_HANDLE Framework;
+ UNIT_TEST_SUITE_HANDLE ValidSuite;
+ UNIT_TEST_SUITE_HANDLE EntrySuite;
+ UNIT_TEST_SUITE_HANDLE RestoreSuite;
+
+ Framework = NULL;
+
+ Status = InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCallerBaseName, UNIT_TEST_VERSION);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = CreateUnitTestSuite (&ValidSuite, Framework, "PartitionValidGptTable tests", "GptLib.ValidGptTable", NULL, NULL);
+ if (EFI_ERROR (Status)) {
+ goto Exit;
+ }
+
+ Status = CreateUnitTestSuite (&EntrySuite, Framework, "PartitionCheckGptEntry tests", "GptLib.CheckGptEntry", NULL, NULL);
+ if (EFI_ERROR (Status)) {
+ goto Exit;
+ }
+
+ Status = CreateUnitTestSuite (&RestoreSuite, Framework, "PartitionRestoreGptTable tests", "GptLib.RestoreGptTable", NULL, NULL);
+ if (EFI_ERROR (Status)) {
+ goto Exit;
+ }
+
+ AddTestCase (ValidSuite, "Valid primary header is accepted", "ValidPrimary", TestValidPrimaryHeaderIsAccepted, NULL, NULL, NULL);
+ AddTestCase (ValidSuite, "Valid backup header is accepted", "ValidBackup", TestValidBackupHeaderIsAccepted, NULL, NULL, NULL);
+ AddTestCase (ValidSuite, "Minimum (92-byte) header size is accepted", "MinHeaderSize", TestMinimumHeaderSizeIsAccepted, NULL, NULL, NULL);
+ AddTestCase (ValidSuite, "256-byte partition entry size is accepted", "EntrySize256", TestLargerPowerOfTwoEntrySizeIsAccepted, NULL, NULL, NULL);
+
+ AddTestCase (EntrySuite, "Valid entries report no flags", "ValidEntries", TestValidEntriesReportNoFlags, NULL, NULL, NULL);
+ AddTestCase (EntrySuite, "OS-specific attribute is reported", "OsSpecificAttribute", TestOsSpecificAttributeIsReported, NULL, NULL, NULL);
+ AddTestCase (EntrySuite, "Unused entries are ignored", "UnusedEntriesIgnored", TestUnusedEntriesAreIgnored, NULL, NULL, NULL);
+ AddTestCase (EntrySuite, "Entry stride follows SizeOfPartitionEntry", "EntryStride", TestEntryStrideFollowsSizeOfPartitionEntry, NULL, NULL, NULL);
+
+ AddTestCase (RestoreSuite, "Restore primary from backup", "RestorePrimary", TestRestorePrimaryFromBackup, NULL, NULL, NULL);
+ AddTestCase (RestoreSuite, "Restore backup from primary", "RestoreBackup", TestRestoreBackupFromPrimary, NULL, NULL, NULL);
+
+ Status = RunAllTestSuites (Framework);
+
+Exit:
+ if (Framework != NULL) {
+ FreeUnitTestFramework (Framework);
+ }
+
+ return Status;
+}
+
+///
+/// Avoid ECC error for function name that starts with lower case letter
+///
+#define GptLibUnitTestMain main
+
+/**
+ Standard POSIX C entry point for host based unit test execution.
+
+ @param[in] Argc Number of arguments
+ @param[in] Argv Array of pointers to arguments
+
+ @retval 0 Success
+ @retval other Error
+**/
+INT32
+GptLibUnitTestMain (
+ IN INT32 Argc,
+ IN CHAR8 *Argv[]
+ )
+{
+ UefiTestMain ();
+ return 0;
+}
diff --git a/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.c b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.c
new file mode 100644
index 0000000000..e7061a7e8f
--- /dev/null
+++ b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.c
@@ -0,0 +1,298 @@
+/** @file
+ Shared mock disk and GPT construction helpers used by the GptLib
+ host-based unit tests.
+
+ Copyright (c) 2026, SUSE LLC. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include
+#include
+#include "GptLibUnitTestCommon.h"
+
+UINT8 mDiskImage[DISK_IMAGE_SIZE];
+
+EFI_BLOCK_IO_MEDIA mBlockIoMedia;
+EFI_BLOCK_IO_PROTOCOL mBlockIo;
+EFI_DISK_IO_PROTOCOL mDiskIo;
+BOOLEAN mWriteProtected;
+
+CONST EFI_GUID mEspTypeGuid = {
+ 0xC12A7328, 0xF81F, 0x11D2, { 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B }
+};
+
+CONST EFI_GUID mPartitionGuid1 = {
+ 0x11111111, 0x2222, 0x3333, { 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77 }
+};
+
+CONST EFI_GUID mPartitionGuid2 = {
+ 0x88888888, 0x9999, 0xAAAA, { 0xBB, 0xBB, 0xCC, 0xCC, 0xDD, 0xDD, 0xEE, 0xEE }
+};
+
+/**
+ Mock implementation of EFI_DISK_IO_PROTOCOL.ReadDisk backed by the in-memory
+ disk image.
+
+ @param[in] This Pointer to the EFI_DISK_IO_PROTOCOL instance.
+ @param[in] MediaId ID of the medium to read from.
+ @param[in] Offset Starting byte offset on the logical disk.
+ @param[in] BufferSize Number of bytes to read.
+ @param[out] Buffer Buffer into which the data is read.
+
+ @retval EFI_SUCCESS The data was read successfully.
+ @retval EFI_DEVICE_ERROR The request was invalid or out of range.
+**/
+STATIC
+EFI_STATUS
+EFIAPI
+MockReadDisk (
+ IN EFI_DISK_IO_PROTOCOL *This,
+ IN UINT32 MediaId,
+ IN UINT64 Offset,
+ IN UINTN BufferSize,
+ OUT VOID *Buffer
+ )
+{
+ if ((Buffer == NULL) ||
+ (MediaId != mBlockIoMedia.MediaId) ||
+ (Offset > DISK_IMAGE_SIZE) ||
+ (BufferSize > (DISK_IMAGE_SIZE - Offset)))
+ {
+ return EFI_DEVICE_ERROR;
+ }
+
+ CopyMem (Buffer, mDiskImage + Offset, BufferSize);
+ return EFI_SUCCESS;
+}
+
+/**
+ Mock implementation of EFI_DISK_IO_PROTOCOL.WriteDisk backed by the in-memory
+ disk image, honoring the write-protected flag.
+
+ @param[in] This Pointer to the EFI_DISK_IO_PROTOCOL instance.
+ @param[in] MediaId ID of the medium to write to.
+ @param[in] Offset Starting byte offset on the logical disk.
+ @param[in] BufferSize Number of bytes to write.
+ @param[in] Buffer Buffer holding the data to write.
+
+ @retval EFI_SUCCESS The data was written successfully.
+ @retval EFI_WRITE_PROTECTED The mock medium is write-protected.
+ @retval EFI_DEVICE_ERROR The request was invalid or out of range.
+**/
+STATIC
+EFI_STATUS
+EFIAPI
+MockWriteDisk (
+ IN EFI_DISK_IO_PROTOCOL *This,
+ IN UINT32 MediaId,
+ IN UINT64 Offset,
+ IN UINTN BufferSize,
+ IN VOID *Buffer
+ )
+{
+ if (mWriteProtected) {
+ return EFI_WRITE_PROTECTED;
+ }
+
+ if ((Buffer == NULL) ||
+ (MediaId != mBlockIoMedia.MediaId) ||
+ (Offset > DISK_IMAGE_SIZE) ||
+ (BufferSize > (DISK_IMAGE_SIZE - Offset)))
+ {
+ return EFI_DEVICE_ERROR;
+ }
+
+ CopyMem (mDiskImage + Offset, Buffer, BufferSize);
+ return EFI_SUCCESS;
+}
+
+/**
+ Return a pointer into the in-memory disk image at the given LBA.
+
+ @param[in] Lba Logical block address to locate.
+
+ @return Pointer to the start of the requested block within the disk image.
+**/
+UINT8 *
+GetDiskLba (
+ IN EFI_LBA Lba
+ )
+{
+ return mDiskImage + (UINTN)(Lba * SECTOR_SIZE);
+}
+
+/**
+ Return a pointer to the primary GPT header within the in-memory disk image.
+
+ @return Pointer to the primary EFI_PARTITION_TABLE_HEADER.
+**/
+EFI_PARTITION_TABLE_HEADER *
+GetPrimaryHeader (
+ VOID
+ )
+{
+ return (EFI_PARTITION_TABLE_HEADER *)(VOID *)GetDiskLba (PRIMARY_PART_HEADER_LBA);
+}
+
+/**
+ Recompute and store the header CRC32 for the given GPT header.
+
+ @param[in,out] Header The GPT header whose CRC32 field is updated.
+**/
+VOID
+UpdateGptHeaderCrc (
+ IN OUT EFI_PARTITION_TABLE_HEADER *Header
+ )
+{
+ Header->Header.CRC32 = 0;
+ Header->Header.CRC32 = CalculateCrc32 (Header, Header->Header.HeaderSize);
+}
+
+/**
+ Populate a partition entry with the ESP type GUID and the given attributes.
+
+ @param[in,out] Entry The partition entry to fill.
+ @param[in] UniquePartitionGuid Unique partition GUID to assign.
+ @param[in] StartingLba Starting LBA of the partition.
+ @param[in] EndingLba Ending LBA of the partition.
+**/
+VOID
+FillPartitionEntry (
+ IN OUT EFI_PARTITION_ENTRY *Entry,
+ IN CONST EFI_GUID *UniquePartitionGuid,
+ IN EFI_LBA StartingLba,
+ IN EFI_LBA EndingLba
+ )
+{
+ CopyGuid (&Entry->PartitionTypeGUID, &mEspTypeGuid);
+ CopyGuid (&Entry->UniquePartitionGUID, UniquePartitionGuid);
+ Entry->StartingLBA = StartingLba;
+ Entry->EndingLBA = EndingLba;
+}
+
+/**
+ Construct a complete GPT header and partition entry array at the given LBA in
+ the in-memory disk image, recalculating all CRCs.
+
+ @param[in] HeaderLba LBA at which to write the GPT header.
+ @param[in] AlternateLba Value stored in the header AlternateLBA field.
+ @param[in] EntryArrayLba LBA at which to write the partition entry array.
+ @param[in] NumEntries Number of partition entries in the array.
+ @param[in] EntrySize Size in bytes of each partition entry.
+ @param[in] NumPartitions Number of populated partition entries.
+**/
+VOID
+WriteGptTableAt (
+ IN EFI_LBA HeaderLba,
+ IN EFI_LBA AlternateLba,
+ IN EFI_LBA EntryArrayLba,
+ IN UINT32 NumEntries,
+ IN UINT32 EntrySize,
+ IN UINT32 NumPartitions
+ )
+{
+ EFI_PARTITION_TABLE_HEADER *Header;
+ UINT8 *Array;
+ UINT32 ArraySize;
+
+ ArraySize = NumEntries * EntrySize;
+ Array = GetDiskLba (EntryArrayLba);
+ ZeroMem (Array, ArraySize);
+ if (NumPartitions >= 1) {
+ FillPartitionEntry ((EFI_PARTITION_ENTRY *)(VOID *)Array, &mPartitionGuid1, 34, 133);
+ }
+
+ if (NumPartitions >= 2) {
+ FillPartitionEntry ((EFI_PARTITION_ENTRY *)(VOID *)(Array + EntrySize), &mPartitionGuid2, 134, 233);
+ }
+
+ Header = (EFI_PARTITION_TABLE_HEADER *)(VOID *)GetDiskLba (HeaderLba);
+ ZeroMem (Header, SECTOR_SIZE);
+ Header->Header.Signature = EFI_PTAB_HEADER_ID;
+ Header->Header.Revision = TEST_GPT_REVISION_V1;
+ Header->Header.HeaderSize = sizeof (EFI_PARTITION_TABLE_HEADER);
+ Header->MyLBA = HeaderLba;
+ Header->AlternateLBA = AlternateLba;
+ Header->FirstUsableLBA = FIRST_USABLE_LBA;
+ Header->LastUsableLBA = LAST_USABLE_LBA;
+ Header->PartitionEntryLBA = EntryArrayLba;
+ Header->NumberOfPartitionEntries = NumEntries;
+ Header->SizeOfPartitionEntry = EntrySize;
+ Header->PartitionEntryArrayCRC32 = CalculateCrc32 (Array, ArraySize);
+ UpdateGptHeaderCrc (Header);
+}
+
+/**
+ Reset the mock disk and protocol state and lay down a valid primary GPT.
+**/
+VOID
+SetupDiskWithValidPrimary (
+ VOID
+ )
+{
+ ZeroMem (mDiskImage, sizeof (mDiskImage));
+
+ ZeroMem (&mBlockIoMedia, sizeof (mBlockIoMedia));
+ mBlockIoMedia.MediaId = 1;
+ mBlockIoMedia.BlockSize = SECTOR_SIZE;
+ mBlockIoMedia.LastBlock = LAST_LBA;
+
+ ZeroMem (&mBlockIo, sizeof (mBlockIo));
+ mBlockIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION;
+ mBlockIo.Media = &mBlockIoMedia;
+
+ ZeroMem (&mDiskIo, sizeof (mDiskIo));
+ mDiskIo.Revision = EFI_DISK_IO_PROTOCOL_REVISION;
+ mDiskIo.ReadDisk = MockReadDisk;
+ mDiskIo.WriteDisk = MockWriteDisk;
+
+ mWriteProtected = FALSE;
+
+ WriteGptTableAt (
+ PRIMARY_PART_HEADER_LBA,
+ LAST_LBA,
+ PRIMARY_PART_HEADER_LBA + 1,
+ NUM_PARTITION_ENTRIES,
+ PARTITION_ENTRY_SIZE,
+ 2
+ );
+}
+
+/**
+ Invoke PartitionValidGptTable against the mock disk at the given LBA.
+
+ @param[in] Lba LBA of the GPT header to validate.
+ @param[out] Header Buffer to receive the validated GPT header.
+
+ @retval TRUE The GPT table at Lba is valid.
+ @retval FALSE The GPT table at Lba is invalid.
+**/
+BOOLEAN
+ValidateAt (
+ IN EFI_LBA Lba,
+ OUT EFI_PARTITION_TABLE_HEADER *Header
+ )
+{
+ return PartitionValidGptTable (&mBlockIo, &mDiskIo, Lba, Header);
+}
+
+/**
+ Initialize a GPT header in memory for PartitionCheckGptEntry tests.
+
+ @param[out] Header The GPT header to initialize.
+ @param[in] NumEntries Number of partition entries to record.
+ @param[in] EntrySize Size in bytes of each partition entry.
+**/
+VOID
+InitCheckEntryHeader (
+ OUT EFI_PARTITION_TABLE_HEADER *Header,
+ IN UINT32 NumEntries,
+ IN UINT32 EntrySize
+ )
+{
+ ZeroMem (Header, sizeof (*Header));
+ Header->FirstUsableLBA = FIRST_USABLE_LBA;
+ Header->LastUsableLBA = 200;
+ Header->NumberOfPartitionEntries = NumEntries;
+ Header->SizeOfPartitionEntry = EntrySize;
+}
diff --git a/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.h b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.h
new file mode 100644
index 0000000000..677dbc4c4f
--- /dev/null
+++ b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestCommon.h
@@ -0,0 +1,110 @@
+/** @file
+ Shared mock disk and GPT construction helpers used by the GptLib
+ host-based unit tests.
+
+ Copyright (c) 2026, SUSE LLC. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#define SECTOR_SIZE 512U
+#define DISK_SECTORS 4096U
+#define DISK_IMAGE_SIZE (DISK_SECTORS * SECTOR_SIZE)
+#define LAST_LBA (DISK_SECTORS - 1U)
+#define NUM_PARTITION_ENTRIES 128U
+#define PARTITION_ENTRY_SIZE 128U
+#define PART_ARRAY_SIZE (NUM_PARTITION_ENTRIES * PARTITION_ENTRY_SIZE)
+#define FIRST_USABLE_LBA 34U
+#define LAST_USABLE_LBA (DISK_SECTORS - 34U)
+
+//
+// The only GPT header revision defined by the UEFI specification, and the
+// minimum on-disk header size (through PartitionEntryArrayCRC32).
+//
+#define TEST_GPT_REVISION_V1 0x00010000U
+#define TEST_GPT_HEADER_MIN_SIZE 92U
+
+extern UINT8 mDiskImage[DISK_IMAGE_SIZE];
+
+extern EFI_BLOCK_IO_MEDIA mBlockIoMedia;
+extern EFI_BLOCK_IO_PROTOCOL mBlockIo;
+extern EFI_DISK_IO_PROTOCOL mDiskIo;
+extern BOOLEAN mWriteProtected;
+
+extern CONST EFI_GUID mEspTypeGuid;
+extern CONST EFI_GUID mPartitionGuid1;
+extern CONST EFI_GUID mPartitionGuid2;
+
+UINT8 *
+GetDiskLba (
+ IN EFI_LBA Lba
+ );
+
+EFI_PARTITION_TABLE_HEADER *
+GetPrimaryHeader (
+ VOID
+ );
+
+VOID
+UpdateGptHeaderCrc (
+ IN OUT EFI_PARTITION_TABLE_HEADER *Header
+ );
+
+VOID
+FillPartitionEntry (
+ IN OUT EFI_PARTITION_ENTRY *Entry,
+ IN CONST EFI_GUID *UniquePartitionGuid,
+ IN EFI_LBA StartingLba,
+ IN EFI_LBA EndingLba
+ );
+
+/**
+ Write a self-consistent GPT table (header plus partition entry array) to the
+ mock disk at the requested locations, computing correct header and
+ entry-array CRC32 values.
+
+ @param[in] HeaderLba LBA where the GPT header is written. Also stored
+ in the header's MyLBA field.
+ @param[in] AlternateLba Value stored in the header's AlternateLBA field.
+ @param[in] EntryArrayLba LBA where the partition entry array is written.
+ @param[in] NumEntries NumberOfPartitionEntries value.
+ @param[in] EntrySize SizeOfPartitionEntry value.
+ @param[in] NumPartitions Number of non-empty partition entries to populate.
+**/
+VOID
+WriteGptTableAt (
+ IN EFI_LBA HeaderLba,
+ IN EFI_LBA AlternateLba,
+ IN EFI_LBA EntryArrayLba,
+ IN UINT32 NumEntries,
+ IN UINT32 EntrySize,
+ IN UINT32 NumPartitions
+ );
+
+/**
+ Reset the mock disk to a single valid primary GPT: header at LBA 1, a
+ 128 x 128-byte entry array at LBA 2 holding two partitions.
+**/
+VOID
+SetupDiskWithValidPrimary (
+ VOID
+ );
+
+BOOLEAN
+ValidateAt (
+ IN EFI_LBA Lba,
+ OUT EFI_PARTITION_TABLE_HEADER *Header
+ );
+
+VOID
+InitCheckEntryHeader (
+ OUT EFI_PARTITION_TABLE_HEADER *Header,
+ IN UINT32 NumEntries,
+ IN UINT32 EntrySize
+ );
diff --git a/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf
new file mode 100644
index 0000000000..a387e6afda
--- /dev/null
+++ b/MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf
@@ -0,0 +1,40 @@
+## @file
+# Host-based unit tests for GptLib.
+#
+# Exercises PartitionValidGptTable(), PartitionCheckGptEntry() and
+# PartitionRestoreGptTable() against an in-memory mock disk, covering their
+# behavior on well-formed GPT structures.
+#
+# Copyright (c) 2026, SUSE LLC. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+[Defines]
+ INF_VERSION = 0x00010006
+ BASE_NAME = GptLibUnitTestHost
+ FILE_GUID = 2F5B4C8E-6D3A-4B1F-9E07-8A2C5D14E6B3
+ 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]
+ GptLibUnitTestCommon.h
+ GptLibUnitTestCommon.c
+ GptLibUnitTest.c
+
+[Packages]
+ MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
+ UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec
+
+[LibraryClasses]
+ BaseLib
+ BaseMemoryLib
+ DebugLib
+ GptLib
+ UnitTestLib
diff --git a/MdeModulePkg/MdeModulePkg.ci.yaml b/MdeModulePkg/MdeModulePkg.ci.yaml
index 37dc5caaf6..6056e263a6 100644
--- a/MdeModulePkg/MdeModulePkg.ci.yaml
+++ b/MdeModulePkg/MdeModulePkg.ci.yaml
@@ -24,6 +24,7 @@
"8005", "UNIVERSAL_PAYLOAD_PCI_ROOT_BRIDGE.HID",
"8001", "UefiSortLibUnitTestMain",
"8001", "MediaSanitizeUnitTestMain",
+ "8001", "GptLibUnitTestMain",
],
## Both file path and directory path are accepted.
"IgnoreFiles": [
diff --git a/MdeModulePkg/Test/MdeModulePkgHostTest.dsc b/MdeModulePkg/Test/MdeModulePkgHostTest.dsc
index 4f04a6dd79..05fd4652ff 100644
--- a/MdeModulePkg/Test/MdeModulePkgHostTest.dsc
+++ b/MdeModulePkg/Test/MdeModulePkgHostTest.dsc
@@ -77,6 +77,11 @@
HobLib|MdePkg/Test/Mock/Library/GoogleTest/MockHobLib/MockHobLib.inf
}
+ MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf {
+
+ GptLib|MdeModulePkg/Library/GptLib/GptLib.inf
+ }
+
#
# Build HOST_APPLICATION Libraries
#