1 //===-- secondary_test.cpp --------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "memtag.h" 10 #include "tests/scudo_unit_test.h" 11 12 #include "allocator_config.h" 13 #include "secondary.h" 14 15 #include <condition_variable> 16 #include <memory> 17 #include <mutex> 18 #include <random> 19 #include <stdio.h> 20 #include <thread> 21 #include <vector> 22 23 template <typename Config> static scudo::Options getOptionsForConfig() { 24 if (!Config::MaySupportMemoryTagging || !scudo::archSupportsMemoryTagging() || 25 !scudo::systemSupportsMemoryTagging()) 26 return {}; 27 scudo::AtomicOptions AO; 28 AO.set(scudo::OptionBit::UseMemoryTagging); 29 return AO.load(); 30 } 31 32 template <typename Config> static void testSecondaryBasic(void) { 33 using SecondaryT = scudo::MapAllocator<Config>; 34 scudo::Options Options = getOptionsForConfig<Config>(); 35 36 scudo::GlobalStats S; 37 S.init(); 38 std::unique_ptr<SecondaryT> L(new SecondaryT); 39 L->init(&S); 40 const scudo::uptr Size = 1U << 16; 41 void *P = L->allocate(Options, Size); 42 EXPECT_NE(P, nullptr); 43 memset(P, 'A', Size); 44 EXPECT_GE(SecondaryT::getBlockSize(P), Size); 45 L->deallocate(Options, P); 46 47 // If the Secondary can't cache that pointer, it will be unmapped. 48 if (!L->canCache(Size)) { 49 EXPECT_DEATH( 50 { 51 // Repeat few time to avoid missing crash if it's mmaped by unrelated 52 // code. 53 for (int i = 0; i < 10; ++i) { 54 P = L->allocate(Options, Size); 55 L->deallocate(Options, P); 56 memset(P, 'A', Size); 57 } 58 }, 59 ""); 60 } 61 62 const scudo::uptr Align = 1U << 16; 63 P = L->allocate(Options, Size + Align, Align); 64 EXPECT_NE(P, nullptr); 65 void *AlignedP = reinterpret_cast<void *>( 66 scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align)); 67 memset(AlignedP, 'A', Size); 68 L->deallocate(Options, P); 69 70 std::vector<void *> V; 71 for (scudo::uptr I = 0; I < 32U; I++) 72 V.push_back(L->allocate(Options, Size)); 73 std::shuffle(V.begin(), V.end(), std::mt19937(std::random_device()())); 74 while (!V.empty()) { 75 L->deallocate(Options, V.back()); 76 V.pop_back(); 77 } 78 scudo::ScopedString Str; 79 L->getStats(&Str); 80 Str.output(); 81 L->unmapTestOnly(); 82 } 83 84 struct NoCacheConfig { 85 typedef scudo::MapAllocatorNoCache SecondaryCache; 86 static const bool MaySupportMemoryTagging = false; 87 }; 88 89 struct TestConfig { 90 typedef scudo::MapAllocatorCache<TestConfig> SecondaryCache; 91 static const bool MaySupportMemoryTagging = false; 92 static const scudo::u32 SecondaryCacheEntriesArraySize = 128U; 93 static const scudo::u32 SecondaryCacheQuarantineSize = 0U; 94 static const scudo::u32 SecondaryCacheDefaultMaxEntriesCount = 64U; 95 static const scudo::uptr SecondaryCacheDefaultMaxEntrySize = 1UL << 20; 96 static const scudo::s32 SecondaryCacheMinReleaseToOsIntervalMs = INT32_MIN; 97 static const scudo::s32 SecondaryCacheMaxReleaseToOsIntervalMs = INT32_MAX; 98 }; 99 100 TEST(ScudoSecondaryTest, SecondaryBasic) { 101 testSecondaryBasic<NoCacheConfig>(); 102 testSecondaryBasic<scudo::DefaultConfig>(); 103 testSecondaryBasic<TestConfig>(); 104 } 105 106 struct MapAllocatorTest : public Test { 107 using Config = scudo::DefaultConfig; 108 using LargeAllocator = scudo::MapAllocator<Config>; 109 110 void SetUp() override { Allocator->init(nullptr); } 111 112 void TearDown() override { Allocator->unmapTestOnly(); } 113 114 std::unique_ptr<LargeAllocator> Allocator = 115 std::make_unique<LargeAllocator>(); 116 scudo::Options Options = getOptionsForConfig<Config>(); 117 }; 118 119 // This exercises a variety of combinations of size and alignment for the 120 // MapAllocator. The size computation done here mimic the ones done by the 121 // combined allocator. 122 TEST_F(MapAllocatorTest, SecondaryCombinations) { 123 constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16); 124 constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign); 125 for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) { 126 for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16; 127 AlignLog++) { 128 const scudo::uptr Align = 1U << AlignLog; 129 for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) { 130 if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0) 131 continue; 132 const scudo::uptr UserSize = 133 scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign); 134 const scudo::uptr Size = 135 HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0); 136 void *P = Allocator->allocate(Options, Size, Align); 137 EXPECT_NE(P, nullptr); 138 void *AlignedP = reinterpret_cast<void *>( 139 scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align)); 140 memset(AlignedP, 0xff, UserSize); 141 Allocator->deallocate(Options, P); 142 } 143 } 144 } 145 scudo::ScopedString Str; 146 Allocator->getStats(&Str); 147 Str.output(); 148 } 149 150 TEST_F(MapAllocatorTest, SecondaryIterate) { 151 std::vector<void *> V; 152 const scudo::uptr PageSize = scudo::getPageSizeCached(); 153 for (scudo::uptr I = 0; I < 32U; I++) 154 V.push_back(Allocator->allocate(Options, (std::rand() % 16) * PageSize)); 155 auto Lambda = [V](scudo::uptr Block) { 156 EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)), 157 V.end()); 158 }; 159 Allocator->disable(); 160 Allocator->iterateOverBlocks(Lambda); 161 Allocator->enable(); 162 while (!V.empty()) { 163 Allocator->deallocate(Options, V.back()); 164 V.pop_back(); 165 } 166 scudo::ScopedString Str; 167 Allocator->getStats(&Str); 168 Str.output(); 169 } 170 171 TEST_F(MapAllocatorTest, SecondaryOptions) { 172 // Attempt to set a maximum number of entries higher than the array size. 173 EXPECT_FALSE( 174 Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4096U)); 175 // A negative number will be cast to a scudo::u32, and fail. 176 EXPECT_FALSE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, -1)); 177 if (Allocator->canCache(0U)) { 178 // Various valid combinations. 179 EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4U)); 180 EXPECT_TRUE( 181 Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20)); 182 EXPECT_TRUE(Allocator->canCache(1UL << 18)); 183 EXPECT_TRUE( 184 Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17)); 185 EXPECT_FALSE(Allocator->canCache(1UL << 18)); 186 EXPECT_TRUE(Allocator->canCache(1UL << 16)); 187 EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 0U)); 188 EXPECT_FALSE(Allocator->canCache(1UL << 16)); 189 EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4U)); 190 EXPECT_TRUE( 191 Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20)); 192 EXPECT_TRUE(Allocator->canCache(1UL << 16)); 193 } 194 } 195 196 struct MapAllocatorWithReleaseTest : public MapAllocatorTest { 197 void SetUp() override { Allocator->init(nullptr, /*ReleaseToOsInterval=*/0); } 198 199 void performAllocations() { 200 std::vector<void *> V; 201 const scudo::uptr PageSize = scudo::getPageSizeCached(); 202 { 203 std::unique_lock<std::mutex> Lock(Mutex); 204 while (!Ready) 205 Cv.wait(Lock); 206 } 207 for (scudo::uptr I = 0; I < 128U; I++) { 208 // Deallocate 75% of the blocks. 209 const bool Deallocate = (rand() & 3) != 0; 210 void *P = Allocator->allocate(Options, (std::rand() % 16) * PageSize); 211 if (Deallocate) 212 Allocator->deallocate(Options, P); 213 else 214 V.push_back(P); 215 } 216 while (!V.empty()) { 217 Allocator->deallocate(Options, V.back()); 218 V.pop_back(); 219 } 220 } 221 222 std::mutex Mutex; 223 std::condition_variable Cv; 224 bool Ready = false; 225 }; 226 227 TEST_F(MapAllocatorWithReleaseTest, SecondaryThreadsRace) { 228 std::thread Threads[16]; 229 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++) 230 Threads[I] = 231 std::thread(&MapAllocatorWithReleaseTest::performAllocations, this); 232 { 233 std::unique_lock<std::mutex> Lock(Mutex); 234 Ready = true; 235 Cv.notify_all(); 236 } 237 for (auto &T : Threads) 238 T.join(); 239 scudo::ScopedString Str; 240 Allocator->getStats(&Str); 241 Str.output(); 242 } 243