Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 1 | //===-- primary_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 | |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 9 | #include "tests/scudo_unit_test.h" |
| 10 | |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 11 | #include "primary32.h" |
| 12 | #include "primary64.h" |
| 13 | #include "size_class_map.h" |
| 14 | |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 15 | #include <condition_variable> |
| 16 | #include <mutex> |
| 17 | #include <thread> |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 18 | #include <vector> |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 19 | |
| 20 | // Note that with small enough regions, the SizeClassAllocator64 also works on |
| 21 | // 32-bit architectures. It's not something we want to encourage, but we still |
| 22 | // should ensure the tests pass. |
| 23 | |
| 24 | template <typename Primary> static void testPrimary() { |
| 25 | const scudo::uptr NumberOfAllocations = 32U; |
| 26 | auto Deleter = [](Primary *P) { |
| 27 | P->unmapTestOnly(); |
| 28 | delete P; |
| 29 | }; |
| 30 | std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter); |
| 31 | Allocator->init(/*ReleaseToOsInterval=*/-1); |
| 32 | typename Primary::CacheT Cache; |
| 33 | Cache.init(nullptr, Allocator.get()); |
| 34 | for (scudo::uptr I = 0; I <= 16U; I++) { |
| 35 | const scudo::uptr Size = 1UL << I; |
| 36 | if (!Primary::canAllocate(Size)) |
| 37 | continue; |
| 38 | const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size); |
| 39 | void *Pointers[NumberOfAllocations]; |
| 40 | for (scudo::uptr J = 0; J < NumberOfAllocations; J++) { |
| 41 | void *P = Cache.allocate(ClassId); |
| 42 | memset(P, 'B', Size); |
| 43 | Pointers[J] = P; |
| 44 | } |
| 45 | for (scudo::uptr J = 0; J < NumberOfAllocations; J++) |
| 46 | Cache.deallocate(ClassId, Pointers[J]); |
| 47 | } |
| 48 | Cache.destroy(nullptr); |
| 49 | Allocator->releaseToOS(); |
Dynamic Tools Team | 3e8c65b | 2019-10-18 20:00:32 +0000 | [diff] [blame] | 50 | scudo::ScopedString Str(1024); |
| 51 | Allocator->getStats(&Str); |
| 52 | Str.output(); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 53 | } |
| 54 | |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 55 | template <typename SizeClassMapT> struct TestConfig1 { |
| 56 | using SizeClassMap = SizeClassMapT; |
| 57 | static const scudo::uptr PrimaryRegionSizeLog = 18U; |
| 58 | static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN; |
| 59 | static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX; |
| 60 | static const bool MaySupportMemoryTagging = false; |
Kostya Kortchinsky | c936954 | 2021-02-10 10:17:18 -0800 | [diff] [blame^] | 61 | typedef scudo::uptr PrimaryCompactPtrT; |
| 62 | static const scudo::uptr PrimaryCompactPtrScale = 0; |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 63 | }; |
| 64 | |
| 65 | template <typename SizeClassMapT> struct TestConfig2 { |
| 66 | using SizeClassMap = SizeClassMapT; |
| 67 | static const scudo::uptr PrimaryRegionSizeLog = 24U; |
| 68 | static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN; |
| 69 | static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX; |
| 70 | static const bool MaySupportMemoryTagging = false; |
Kostya Kortchinsky | c936954 | 2021-02-10 10:17:18 -0800 | [diff] [blame^] | 71 | typedef scudo::uptr PrimaryCompactPtrT; |
| 72 | static const scudo::uptr PrimaryCompactPtrScale = 0; |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 73 | }; |
| 74 | |
| 75 | template <typename SizeClassMapT> struct TestConfig3 { |
| 76 | using SizeClassMap = SizeClassMapT; |
| 77 | static const scudo::uptr PrimaryRegionSizeLog = 24U; |
| 78 | static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN; |
| 79 | static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX; |
| 80 | static const bool MaySupportMemoryTagging = true; |
Kostya Kortchinsky | c936954 | 2021-02-10 10:17:18 -0800 | [diff] [blame^] | 81 | typedef scudo::uptr PrimaryCompactPtrT; |
| 82 | static const scudo::uptr PrimaryCompactPtrScale = 0; |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 83 | }; |
| 84 | |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 85 | TEST(ScudoPrimaryTest, BasicPrimary) { |
| 86 | using SizeClassMap = scudo::DefaultSizeClassMap; |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 87 | #if !SCUDO_FUCHSIA |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 88 | testPrimary<scudo::SizeClassAllocator32<TestConfig1<SizeClassMap>>>(); |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 89 | #endif |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 90 | testPrimary<scudo::SizeClassAllocator64<TestConfig2<SizeClassMap>>>(); |
| 91 | testPrimary<scudo::SizeClassAllocator64<TestConfig3<SizeClassMap>>>(); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 92 | } |
| 93 | |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 94 | struct SmallRegionsConfig { |
| 95 | using SizeClassMap = scudo::DefaultSizeClassMap; |
| 96 | static const scudo::uptr PrimaryRegionSizeLog = 20U; |
| 97 | static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN; |
| 98 | static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX; |
| 99 | static const bool MaySupportMemoryTagging = false; |
Kostya Kortchinsky | c936954 | 2021-02-10 10:17:18 -0800 | [diff] [blame^] | 100 | typedef scudo::uptr PrimaryCompactPtrT; |
| 101 | static const scudo::uptr PrimaryCompactPtrScale = 0; |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 102 | }; |
| 103 | |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 104 | // The 64-bit SizeClassAllocator can be easily OOM'd with small region sizes. |
| 105 | // For the 32-bit one, it requires actually exhausting memory, so we skip it. |
| 106 | TEST(ScudoPrimaryTest, Primary64OOM) { |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 107 | using Primary = scudo::SizeClassAllocator64<SmallRegionsConfig>; |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 108 | using TransferBatch = Primary::CacheT::TransferBatch; |
| 109 | Primary Allocator; |
| 110 | Allocator.init(/*ReleaseToOsInterval=*/-1); |
| 111 | typename Primary::CacheT Cache; |
| 112 | scudo::GlobalStats Stats; |
| 113 | Stats.init(); |
| 114 | Cache.init(&Stats, &Allocator); |
| 115 | bool AllocationFailed = false; |
| 116 | std::vector<TransferBatch *> Batches; |
| 117 | const scudo::uptr ClassId = Primary::SizeClassMap::LargestClassId; |
| 118 | const scudo::uptr Size = Primary::getSizeByClassId(ClassId); |
| 119 | for (scudo::uptr I = 0; I < 10000U; I++) { |
| 120 | TransferBatch *B = Allocator.popBatch(&Cache, ClassId); |
| 121 | if (!B) { |
| 122 | AllocationFailed = true; |
| 123 | break; |
| 124 | } |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 125 | for (scudo::u32 J = 0; J < B->getCount(); J++) |
Kostya Kortchinsky | c936954 | 2021-02-10 10:17:18 -0800 | [diff] [blame^] | 126 | memset(Allocator.decompactPtr(ClassId, B->get(J)), 'B', Size); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 127 | Batches.push_back(B); |
| 128 | } |
| 129 | while (!Batches.empty()) { |
| 130 | Allocator.pushBatch(ClassId, Batches.back()); |
| 131 | Batches.pop_back(); |
| 132 | } |
| 133 | Cache.destroy(nullptr); |
| 134 | Allocator.releaseToOS(); |
Dynamic Tools Team | 3e8c65b | 2019-10-18 20:00:32 +0000 | [diff] [blame] | 135 | scudo::ScopedString Str(1024); |
| 136 | Allocator.getStats(&Str); |
| 137 | Str.output(); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 138 | EXPECT_EQ(AllocationFailed, true); |
| 139 | Allocator.unmapTestOnly(); |
| 140 | } |
| 141 | |
| 142 | template <typename Primary> static void testIteratePrimary() { |
| 143 | auto Deleter = [](Primary *P) { |
| 144 | P->unmapTestOnly(); |
| 145 | delete P; |
| 146 | }; |
| 147 | std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter); |
| 148 | Allocator->init(/*ReleaseToOsInterval=*/-1); |
| 149 | typename Primary::CacheT Cache; |
| 150 | Cache.init(nullptr, Allocator.get()); |
| 151 | std::vector<std::pair<scudo::uptr, void *>> V; |
| 152 | for (scudo::uptr I = 0; I < 64U; I++) { |
| 153 | const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize; |
| 154 | const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size); |
| 155 | void *P = Cache.allocate(ClassId); |
| 156 | V.push_back(std::make_pair(ClassId, P)); |
| 157 | } |
| 158 | scudo::uptr Found = 0; |
| 159 | auto Lambda = [V, &Found](scudo::uptr Block) { |
| 160 | for (const auto &Pair : V) { |
| 161 | if (Pair.second == reinterpret_cast<void *>(Block)) |
| 162 | Found++; |
| 163 | } |
| 164 | }; |
| 165 | Allocator->disable(); |
| 166 | Allocator->iterateOverBlocks(Lambda); |
| 167 | Allocator->enable(); |
| 168 | EXPECT_EQ(Found, V.size()); |
| 169 | while (!V.empty()) { |
| 170 | auto Pair = V.back(); |
| 171 | Cache.deallocate(Pair.first, Pair.second); |
| 172 | V.pop_back(); |
| 173 | } |
| 174 | Cache.destroy(nullptr); |
| 175 | Allocator->releaseToOS(); |
Dynamic Tools Team | 3e8c65b | 2019-10-18 20:00:32 +0000 | [diff] [blame] | 176 | scudo::ScopedString Str(1024); |
| 177 | Allocator->getStats(&Str); |
| 178 | Str.output(); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 179 | } |
| 180 | |
| 181 | TEST(ScudoPrimaryTest, PrimaryIterate) { |
| 182 | using SizeClassMap = scudo::DefaultSizeClassMap; |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 183 | #if !SCUDO_FUCHSIA |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 184 | testIteratePrimary<scudo::SizeClassAllocator32<TestConfig1<SizeClassMap>>>(); |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 185 | #endif |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 186 | testIteratePrimary<scudo::SizeClassAllocator64<TestConfig2<SizeClassMap>>>(); |
| 187 | testIteratePrimary<scudo::SizeClassAllocator64<TestConfig3<SizeClassMap>>>(); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 188 | } |
| 189 | |
| 190 | static std::mutex Mutex; |
| 191 | static std::condition_variable Cv; |
Kostya Kortchinsky | c72ca56 | 2020-07-27 09:13:42 -0700 | [diff] [blame] | 192 | static bool Ready; |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 193 | |
| 194 | template <typename Primary> static void performAllocations(Primary *Allocator) { |
Peter Collingbourne | 0d4ff65 | 2020-09-10 12:38:42 -0700 | [diff] [blame] | 195 | static thread_local typename Primary::CacheT Cache; |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 196 | Cache.init(nullptr, Allocator); |
| 197 | std::vector<std::pair<scudo::uptr, void *>> V; |
| 198 | { |
| 199 | std::unique_lock<std::mutex> Lock(Mutex); |
| 200 | while (!Ready) |
| 201 | Cv.wait(Lock); |
| 202 | } |
| 203 | for (scudo::uptr I = 0; I < 256U; I++) { |
| 204 | const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize / 4; |
| 205 | const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size); |
| 206 | void *P = Cache.allocate(ClassId); |
| 207 | if (P) |
| 208 | V.push_back(std::make_pair(ClassId, P)); |
| 209 | } |
| 210 | while (!V.empty()) { |
| 211 | auto Pair = V.back(); |
| 212 | Cache.deallocate(Pair.first, Pair.second); |
| 213 | V.pop_back(); |
| 214 | } |
| 215 | Cache.destroy(nullptr); |
| 216 | } |
| 217 | |
| 218 | template <typename Primary> static void testPrimaryThreaded() { |
Kostya Kortchinsky | c72ca56 | 2020-07-27 09:13:42 -0700 | [diff] [blame] | 219 | Ready = false; |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 220 | auto Deleter = [](Primary *P) { |
| 221 | P->unmapTestOnly(); |
| 222 | delete P; |
| 223 | }; |
| 224 | std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter); |
| 225 | Allocator->init(/*ReleaseToOsInterval=*/-1); |
| 226 | std::thread Threads[32]; |
| 227 | for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++) |
| 228 | Threads[I] = std::thread(performAllocations<Primary>, Allocator.get()); |
| 229 | { |
| 230 | std::unique_lock<std::mutex> Lock(Mutex); |
| 231 | Ready = true; |
| 232 | Cv.notify_all(); |
| 233 | } |
| 234 | for (auto &T : Threads) |
| 235 | T.join(); |
| 236 | Allocator->releaseToOS(); |
Dynamic Tools Team | 3e8c65b | 2019-10-18 20:00:32 +0000 | [diff] [blame] | 237 | scudo::ScopedString Str(1024); |
| 238 | Allocator->getStats(&Str); |
| 239 | Str.output(); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 240 | } |
| 241 | |
| 242 | TEST(ScudoPrimaryTest, PrimaryThreaded) { |
| 243 | using SizeClassMap = scudo::SvelteSizeClassMap; |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 244 | #if !SCUDO_FUCHSIA |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 245 | testPrimaryThreaded<scudo::SizeClassAllocator32<TestConfig1<SizeClassMap>>>(); |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 246 | #endif |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 247 | testPrimaryThreaded<scudo::SizeClassAllocator64<TestConfig2<SizeClassMap>>>(); |
| 248 | testPrimaryThreaded<scudo::SizeClassAllocator64<TestConfig3<SizeClassMap>>>(); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 249 | } |
Dynamic Tools Team | c283bab | 2019-10-07 17:37:39 +0000 | [diff] [blame] | 250 | |
| 251 | // Through a simple allocation that spans two pages, verify that releaseToOS |
| 252 | // actually releases some bytes (at least one page worth). This is a regression |
| 253 | // test for an error in how the release criteria were computed. |
| 254 | template <typename Primary> static void testReleaseToOS() { |
| 255 | auto Deleter = [](Primary *P) { |
| 256 | P->unmapTestOnly(); |
| 257 | delete P; |
| 258 | }; |
| 259 | std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter); |
| 260 | Allocator->init(/*ReleaseToOsInterval=*/-1); |
| 261 | typename Primary::CacheT Cache; |
| 262 | Cache.init(nullptr, Allocator.get()); |
| 263 | const scudo::uptr Size = scudo::getPageSizeCached() * 2; |
| 264 | EXPECT_TRUE(Primary::canAllocate(Size)); |
Dynamic Tools Team | 3e8c65b | 2019-10-18 20:00:32 +0000 | [diff] [blame] | 265 | const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size); |
Dynamic Tools Team | c283bab | 2019-10-07 17:37:39 +0000 | [diff] [blame] | 266 | void *P = Cache.allocate(ClassId); |
| 267 | EXPECT_NE(P, nullptr); |
| 268 | Cache.deallocate(ClassId, P); |
| 269 | Cache.destroy(nullptr); |
| 270 | EXPECT_GT(Allocator->releaseToOS(), 0U); |
| 271 | } |
| 272 | |
| 273 | TEST(ScudoPrimaryTest, ReleaseToOS) { |
| 274 | using SizeClassMap = scudo::DefaultSizeClassMap; |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 275 | #if !SCUDO_FUCHSIA |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 276 | testReleaseToOS<scudo::SizeClassAllocator32<TestConfig1<SizeClassMap>>>(); |
Dynamic Tools Team | 09e6d48 | 2019-11-26 18:18:14 -0800 | [diff] [blame] | 277 | #endif |
Peter Collingbourne | 6be4919 | 2020-12-15 14:26:10 -0800 | [diff] [blame] | 278 | testReleaseToOS<scudo::SizeClassAllocator64<TestConfig2<SizeClassMap>>>(); |
| 279 | testReleaseToOS<scudo::SizeClassAllocator64<TestConfig3<SizeClassMap>>>(); |
Dynamic Tools Team | c283bab | 2019-10-07 17:37:39 +0000 | [diff] [blame] | 280 | } |