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
getOptionsForConfig()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
testSecondaryBasic(void)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
TEST(ScudoSecondaryTest,SecondaryBasic)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
SetUpMapAllocatorTest111 void SetUp() override { Allocator->init(nullptr); }
112
TearDownMapAllocatorTest113 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.
TEST_F(MapAllocatorTest,SecondaryCombinations)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
TEST_F(MapAllocatorTest,SecondaryIterate)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
TEST_F(MapAllocatorTest,SecondaryOptions)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 {
SetUpMapAllocatorWithReleaseTest198 void SetUp() override { Allocator->init(nullptr, /*ReleaseToOsInterval=*/0); }
199
performAllocationsMapAllocatorWithReleaseTest200 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
TEST_F(MapAllocatorWithReleaseTest,SecondaryThreadsRace)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