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