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