//===- MSFBuilderTest.cpp Tests manipulation of MSF stream metadata ------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/MSF/MSFBuilder.h" #include "llvm/DebugInfo/MSF/MSFCommon.h" #include "llvm/Testing/Support/Error.h" #include "gmock/gmock-matchers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using namespace llvm; using namespace llvm::msf; using namespace testing; namespace { class MSFBuilderTest : public testing::Test { protected: void initializeSimpleSuperBlock(msf::SuperBlock &SB) { initializeSuperBlock(SB); SB.NumBlocks = 1000; SB.NumDirectoryBytes = 8192; } void initializeSuperBlock(msf::SuperBlock &SB) { ::memset(&SB, 0, sizeof(SB)); ::memcpy(SB.MagicBytes, msf::Magic, sizeof(msf::Magic)); SB.FreeBlockMapBlock = 1; SB.BlockMapAddr = 1; SB.BlockSize = 4096; SB.NumDirectoryBytes = 0; SB.NumBlocks = 2; // one for the Super Block, one for the directory } BumpPtrAllocator Allocator; }; } // namespace TEST_F(MSFBuilderTest, ValidateSuperBlockAccept) { // Test that a known good super block passes validation. SuperBlock SB; initializeSuperBlock(SB); EXPECT_THAT_ERROR(msf::validateSuperBlock(SB), Succeeded()); } TEST_F(MSFBuilderTest, ValidateSuperBlockReject) { // Test that various known problems cause a super block to be rejected. SuperBlock SB; initializeSimpleSuperBlock(SB); // Mismatched magic SB.MagicBytes[0] = 8; EXPECT_THAT_ERROR(msf::validateSuperBlock(SB), Failed()); initializeSimpleSuperBlock(SB); // Block 0 is reserved for super block, can't be occupied by the block map SB.BlockMapAddr = 0; EXPECT_THAT_ERROR(msf::validateSuperBlock(SB), Failed()); initializeSimpleSuperBlock(SB); // Block sizes have to be powers of 2. SB.BlockSize = 3120; EXPECT_THAT_ERROR(msf::validateSuperBlock(SB), Failed()); initializeSimpleSuperBlock(SB); // The directory itself has a maximum size. SB.NumDirectoryBytes = SB.BlockSize * SB.BlockSize / 4; EXPECT_THAT_ERROR(msf::validateSuperBlock(SB), Succeeded()); SB.NumDirectoryBytes = SB.NumDirectoryBytes + 4; EXPECT_THAT_ERROR(msf::validateSuperBlock(SB), Failed()); } TEST_F(MSFBuilderTest, TestUsedBlocksMarkedAsUsed) { // Test that when assigning a stream to a known list of blocks, the blocks // are correctly marked as used after adding, but no other incorrect blocks // are accidentally marked as used. std::vector Blocks = {4, 5, 6, 7, 8, 9, 10, 11, 12}; // Allocate some extra blocks at the end so we can verify that they're free // after the initialization. uint32_t NumBlocks = msf::getMinimumBlockCount() + Blocks.size() + 10; auto ExpectedMsf = MSFBuilder::create(Allocator, 4096, NumBlocks); ASSERT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; EXPECT_THAT_EXPECTED(Msf.addStream(Blocks.size() * 4096, Blocks), Succeeded()); for (auto B : Blocks) { EXPECT_FALSE(Msf.isBlockFree(B)); } uint32_t FreeBlockStart = Blocks.back() + 1; for (uint32_t I = FreeBlockStart; I < NumBlocks; ++I) { EXPECT_TRUE(Msf.isBlockFree(I)); } } TEST_F(MSFBuilderTest, TestAddStreamNoDirectoryBlockIncrease) { // Test that adding a new stream correctly updates the directory. This only // tests the case where the directory *DOES NOT* grow large enough that it // crosses a Block boundary. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; auto ExpectedL1 = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedL1, Succeeded()); MSFLayout &L1 = *ExpectedL1; auto OldDirBlocks = L1.DirectoryBlocks; EXPECT_EQ(1U, OldDirBlocks.size()); auto ExpectedMsf2 = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf2, Succeeded()); auto &Msf2 = *ExpectedMsf2; EXPECT_THAT_EXPECTED(Msf2.addStream(4000), Succeeded()); EXPECT_EQ(1U, Msf2.getNumStreams()); EXPECT_EQ(4000U, Msf2.getStreamSize(0)); auto Blocks = Msf2.getStreamBlocks(0); EXPECT_EQ(1U, Blocks.size()); auto ExpectedL2 = Msf2.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedL2, Succeeded()); MSFLayout &L2 = *ExpectedL2; auto NewDirBlocks = L2.DirectoryBlocks; EXPECT_EQ(1U, NewDirBlocks.size()); } TEST_F(MSFBuilderTest, TestAddStreamWithDirectoryBlockIncrease) { // Test that adding a new stream correctly updates the directory. This only // tests the case where the directory *DOES* grow large enough that it // crosses a Block boundary. This is because the newly added stream occupies // so many Blocks that need to be indexed in the directory that the directory // crosses a Block boundary. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; EXPECT_THAT_EXPECTED(Msf.addStream(4096 * 4096 / sizeof(uint32_t)), Succeeded()); auto ExpectedL1 = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedL1, Succeeded()); MSFLayout &L1 = *ExpectedL1; auto DirBlocks = L1.DirectoryBlocks; EXPECT_EQ(2U, DirBlocks.size()); } TEST_F(MSFBuilderTest, TestGrowStreamNoBlockIncrease) { // Test growing an existing stream by a value that does not affect the number // of blocks it occupies. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; EXPECT_THAT_EXPECTED(Msf.addStream(1024), Succeeded()); EXPECT_EQ(1024U, Msf.getStreamSize(0)); auto OldStreamBlocks = Msf.getStreamBlocks(0); EXPECT_EQ(1U, OldStreamBlocks.size()); EXPECT_THAT_ERROR(Msf.setStreamSize(0, 2048), Succeeded()); EXPECT_EQ(2048U, Msf.getStreamSize(0)); auto NewStreamBlocks = Msf.getStreamBlocks(0); EXPECT_EQ(1U, NewStreamBlocks.size()); EXPECT_EQ(OldStreamBlocks, NewStreamBlocks); } TEST_F(MSFBuilderTest, TestGrowStreamWithBlockIncrease) { // Test that growing an existing stream to a value large enough that it causes // the need to allocate new Blocks to the stream correctly updates the // stream's // block list. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; EXPECT_THAT_EXPECTED(Msf.addStream(2048), Succeeded()); EXPECT_EQ(2048U, Msf.getStreamSize(0)); std::vector OldStreamBlocks = Msf.getStreamBlocks(0); EXPECT_EQ(1U, OldStreamBlocks.size()); EXPECT_THAT_ERROR(Msf.setStreamSize(0, 6144), Succeeded()); EXPECT_EQ(6144U, Msf.getStreamSize(0)); std::vector NewStreamBlocks = Msf.getStreamBlocks(0); EXPECT_EQ(2U, NewStreamBlocks.size()); EXPECT_EQ(OldStreamBlocks[0], NewStreamBlocks[0]); EXPECT_NE(NewStreamBlocks[0], NewStreamBlocks[1]); } TEST_F(MSFBuilderTest, TestShrinkStreamNoBlockDecrease) { // Test that shrinking an existing stream by a value that does not affect the // number of Blocks it occupies makes no changes to stream's block list. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; EXPECT_THAT_EXPECTED(Msf.addStream(2048), Succeeded()); EXPECT_EQ(2048U, Msf.getStreamSize(0)); std::vector OldStreamBlocks = Msf.getStreamBlocks(0); EXPECT_EQ(1U, OldStreamBlocks.size()); EXPECT_THAT_ERROR(Msf.setStreamSize(0, 1024), Succeeded()); EXPECT_EQ(1024U, Msf.getStreamSize(0)); std::vector NewStreamBlocks = Msf.getStreamBlocks(0); EXPECT_EQ(1U, NewStreamBlocks.size()); EXPECT_EQ(OldStreamBlocks, NewStreamBlocks); } TEST_F(MSFBuilderTest, TestShrinkStreamWithBlockDecrease) { // Test that shrinking an existing stream to a value large enough that it // causes the need to deallocate new Blocks to the stream correctly updates // the stream's block list. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; EXPECT_THAT_EXPECTED(Msf.addStream(6144), Succeeded()); EXPECT_EQ(6144U, Msf.getStreamSize(0)); std::vector OldStreamBlocks = Msf.getStreamBlocks(0); EXPECT_EQ(2U, OldStreamBlocks.size()); EXPECT_THAT_ERROR(Msf.setStreamSize(0, 2048), Succeeded()); EXPECT_EQ(2048U, Msf.getStreamSize(0)); std::vector NewStreamBlocks = Msf.getStreamBlocks(0); EXPECT_EQ(1U, NewStreamBlocks.size()); EXPECT_EQ(OldStreamBlocks[0], NewStreamBlocks[0]); } TEST_F(MSFBuilderTest, TestRejectReusedStreamBlock) { // Test that attempting to add a stream and assigning a block that is already // in use by another stream fails. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; EXPECT_THAT_EXPECTED(Msf.addStream(6144), Succeeded()); std::vector Blocks = {2, 3}; EXPECT_THAT_EXPECTED(Msf.addStream(6144, Blocks), Failed()); } TEST_F(MSFBuilderTest, TestBlockCountsWhenAddingStreams) { // Test that when adding multiple streams, the number of used and free Blocks // allocated to the MSF file are as expected. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; // one for the super block, one for the directory block map uint32_t NumUsedBlocks = Msf.getNumUsedBlocks(); EXPECT_EQ(msf::getMinimumBlockCount(), NumUsedBlocks); EXPECT_EQ(0U, Msf.getNumFreeBlocks()); const uint32_t StreamSizes[] = {4000, 6193, 189723}; for (int I = 0; I < 3; ++I) { EXPECT_THAT_EXPECTED(Msf.addStream(StreamSizes[I]), Succeeded()); NumUsedBlocks += bytesToBlocks(StreamSizes[I], 4096); EXPECT_EQ(NumUsedBlocks, Msf.getNumUsedBlocks()); EXPECT_EQ(0U, Msf.getNumFreeBlocks()); } } TEST_F(MSFBuilderTest, BuildMsfLayout) { // Test that we can generate an MSFLayout structure from a valid layout // specification. auto ExpectedMsf = MSFBuilder::create(Allocator, 4096); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; const uint32_t StreamSizes[] = {4000, 6193, 189723}; uint32_t ExpectedNumBlocks = msf::getMinimumBlockCount(); for (int I = 0; I < 3; ++I) { EXPECT_THAT_EXPECTED(Msf.addStream(StreamSizes[I]), Succeeded()); ExpectedNumBlocks += bytesToBlocks(StreamSizes[I], 4096); } ++ExpectedNumBlocks; // The directory itself should use 1 block auto ExpectedLayout = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; EXPECT_EQ(4096U, L.SB->BlockSize); EXPECT_EQ(ExpectedNumBlocks, L.SB->NumBlocks); EXPECT_EQ(1U, L.DirectoryBlocks.size()); EXPECT_EQ(3U, L.StreamMap.size()); EXPECT_EQ(3U, L.StreamSizes.size()); for (int I = 0; I < 3; ++I) { EXPECT_EQ(StreamSizes[I], L.StreamSizes[I]); uint32_t ExpectedNumBlocks = bytesToBlocks(StreamSizes[I], 4096); EXPECT_EQ(ExpectedNumBlocks, L.StreamMap[I].size()); } } TEST_F(MSFBuilderTest, UseDirectoryBlockHint) { Expected ExpectedMsf = MSFBuilder::create( Allocator, 4096, msf::getMinimumBlockCount() + 1, false); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; uint32_t B = msf::getFirstUnreservedBlock(); EXPECT_THAT_ERROR(Msf.setDirectoryBlocksHint({B + 1}), Succeeded()); EXPECT_THAT_EXPECTED(Msf.addStream(2048, {B + 2}), Succeeded()); auto ExpectedLayout = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; EXPECT_EQ(msf::getMinimumBlockCount() + 2, L.SB->NumBlocks); EXPECT_EQ(1U, L.DirectoryBlocks.size()); EXPECT_EQ(1U, L.StreamMap[0].size()); EXPECT_EQ(B + 1, L.DirectoryBlocks[0]); EXPECT_EQ(B + 2, L.StreamMap[0].front()); } TEST_F(MSFBuilderTest, DirectoryBlockHintInsufficient) { Expected ExpectedMsf = MSFBuilder::create(Allocator, 4096, msf::getMinimumBlockCount() + 2); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; uint32_t B = msf::getFirstUnreservedBlock(); EXPECT_THAT_ERROR(Msf.setDirectoryBlocksHint({B + 1}), Succeeded()); uint32_t Size = 4096 * 4096 / 4; EXPECT_THAT_EXPECTED(Msf.addStream(Size), Succeeded()); auto ExpectedLayout = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; EXPECT_EQ(2U, L.DirectoryBlocks.size()); EXPECT_EQ(B + 1, L.DirectoryBlocks[0]); } TEST_F(MSFBuilderTest, DirectoryBlockHintOverestimated) { Expected ExpectedMsf = MSFBuilder::create(Allocator, 4096, msf::getMinimumBlockCount() + 2); EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; uint32_t B = msf::getFirstUnreservedBlock(); EXPECT_THAT_ERROR(Msf.setDirectoryBlocksHint({B + 1, B + 2}), Succeeded()); ASSERT_THAT_EXPECTED(Msf.addStream(2048), Succeeded()); auto ExpectedLayout = Msf.generateLayout(); ASSERT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; EXPECT_EQ(1U, L.DirectoryBlocks.size()); EXPECT_EQ(B + 1, L.DirectoryBlocks[0]); } TEST_F(MSFBuilderTest, StreamDoesntUseFpmBlocks) { Expected ExpectedMsf = MSFBuilder::create(Allocator, 4096); ASSERT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; // A block is 4096 bytes, and every 4096 blocks we have 2 reserved FPM blocks. // By creating add a stream that spans 4096*4096*3 bytes, we ensure that we // cross over a couple of reserved FPM blocks, and that none of them are // allocated to the stream. constexpr uint32_t StreamSize = 4096 * 4096 * 3; Expected SN = Msf.addStream(StreamSize); ASSERT_THAT_EXPECTED(SN, Succeeded()); auto ExpectedLayout = Msf.generateLayout(); ASSERT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; auto BlocksRef = L.StreamMap[*SN]; std::vector Blocks(BlocksRef.begin(), BlocksRef.end()); EXPECT_EQ(StreamSize, L.StreamSizes[*SN]); for (uint32_t I = 0; I <= 3; ++I) { // Pages from both FPMs are always allocated. EXPECT_FALSE(L.FreePageMap.test(2 + I * 4096)); EXPECT_FALSE(L.FreePageMap.test(1 + I * 4096)); } for (uint32_t I = 1; I <= 3; ++I) { EXPECT_THAT(Blocks, Not(Contains(1 + I * 4096))); EXPECT_THAT(Blocks, Not(Contains(2 + I * 4096))); } }