1 //===-- secondary.h ---------------------------------------------*- 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 #ifndef SCUDO_SECONDARY_H_
10 #define SCUDO_SECONDARY_H_
11 
12 #include "chunk.h"
13 #include "common.h"
14 #include "list.h"
15 #include "mem_map.h"
16 #include "memtag.h"
17 #include "mutex.h"
18 #include "options.h"
19 #include "stats.h"
20 #include "string_utils.h"
21 #include "thread_annotations.h"
22 
23 namespace scudo {
24 
25 // This allocator wraps the platform allocation primitives, and as such is on
26 // the slower side and should preferably be used for larger sized allocations.
27 // Blocks allocated will be preceded and followed by a guard page, and hold
28 // their own header that is not checksummed: the guard pages and the Combined
29 // header should be enough for our purpose.
30 
31 namespace LargeBlock {
32 
33 struct alignas(Max<uptr>(archSupportsMemoryTagging()
34                              ? archMemoryTagGranuleSize()
35                              : 1,
36                          1U << SCUDO_MIN_ALIGNMENT_LOG)) Header {
37   LargeBlock::Header *Prev;
38   LargeBlock::Header *Next;
39   uptr CommitBase;
40   uptr CommitSize;
41   MemMapT MemMap;
42 };
43 
44 static_assert(sizeof(Header) % (1U << SCUDO_MIN_ALIGNMENT_LOG) == 0, "");
45 static_assert(!archSupportsMemoryTagging() ||
46                   sizeof(Header) % archMemoryTagGranuleSize() == 0,
47               "");
48 
getHeaderSize()49 constexpr uptr getHeaderSize() { return sizeof(Header); }
50 
addHeaderTag(uptr Ptr)51 template <typename Config> static uptr addHeaderTag(uptr Ptr) {
52   if (allocatorSupportsMemoryTagging<Config>())
53     return addFixedTag(Ptr, 1);
54   return Ptr;
55 }
56 
getHeader(uptr Ptr)57 template <typename Config> static Header *getHeader(uptr Ptr) {
58   return reinterpret_cast<Header *>(addHeaderTag<Config>(Ptr)) - 1;
59 }
60 
getHeader(const void * Ptr)61 template <typename Config> static Header *getHeader(const void *Ptr) {
62   return getHeader<Config>(reinterpret_cast<uptr>(Ptr));
63 }
64 
65 } // namespace LargeBlock
66 
unmap(LargeBlock::Header * H)67 static inline void unmap(LargeBlock::Header *H) {
68   // Note that the `H->MapMap` is stored on the pages managed by itself. Take
69   // over the ownership before unmap() so that any operation along with unmap()
70   // won't touch inaccessible pages.
71   MemMapT MemMap = H->MemMap;
72   MemMap.unmap(MemMap.getBase(), MemMap.getCapacity());
73 }
74 
75 namespace {
76 struct CachedBlock {
77   uptr CommitBase = 0;
78   uptr CommitSize = 0;
79   uptr BlockBegin = 0;
80   MemMapT MemMap = {};
81   u64 Time = 0;
82 
isValidCachedBlock83   bool isValid() { return CommitBase != 0; }
84 
invalidateCachedBlock85   void invalidate() { CommitBase = 0; }
86 };
87 } // namespace
88 
89 template <typename Config> class MapAllocatorNoCache {
90 public:
init(UNUSED s32 ReleaseToOsInterval)91   void init(UNUSED s32 ReleaseToOsInterval) {}
retrieve(UNUSED Options Options,UNUSED uptr Size,UNUSED uptr Alignment,UNUSED uptr HeadersSize,UNUSED LargeBlock::Header ** H,UNUSED bool * Zeroed)92   bool retrieve(UNUSED Options Options, UNUSED uptr Size, UNUSED uptr Alignment,
93                 UNUSED uptr HeadersSize, UNUSED LargeBlock::Header **H,
94                 UNUSED bool *Zeroed) {
95     return false;
96   }
store(UNUSED Options Options,LargeBlock::Header * H)97   void store(UNUSED Options Options, LargeBlock::Header *H) { unmap(H); }
canCache(UNUSED uptr Size)98   bool canCache(UNUSED uptr Size) { return false; }
disable()99   void disable() {}
enable()100   void enable() {}
releaseToOS()101   void releaseToOS() {}
disableMemoryTagging()102   void disableMemoryTagging() {}
unmapTestOnly()103   void unmapTestOnly() {}
setOption(Option O,UNUSED sptr Value)104   bool setOption(Option O, UNUSED sptr Value) {
105     if (O == Option::ReleaseInterval || O == Option::MaxCacheEntriesCount ||
106         O == Option::MaxCacheEntrySize)
107       return false;
108     // Not supported by the Secondary Cache, but not an error either.
109     return true;
110   }
111 
getStats(UNUSED ScopedString * Str)112   void getStats(UNUSED ScopedString *Str) {
113     Str->append("Secondary Cache Disabled\n");
114   }
115 };
116 
117 static const uptr MaxUnusedCachePages = 4U;
118 
119 template <typename Config>
mapSecondary(const Options & Options,uptr CommitBase,uptr CommitSize,uptr AllocPos,uptr Flags,MemMapT & MemMap)120 bool mapSecondary(const Options &Options, uptr CommitBase, uptr CommitSize,
121                   uptr AllocPos, uptr Flags, MemMapT &MemMap) {
122   Flags |= MAP_RESIZABLE;
123   Flags |= MAP_ALLOWNOMEM;
124 
125   const uptr MaxUnusedCacheBytes = MaxUnusedCachePages * getPageSizeCached();
126   if (useMemoryTagging<Config>(Options) && CommitSize > MaxUnusedCacheBytes) {
127     const uptr UntaggedPos = Max(AllocPos, CommitBase + MaxUnusedCacheBytes);
128     return MemMap.remap(CommitBase, UntaggedPos - CommitBase, "scudo:secondary",
129                         MAP_MEMTAG | Flags) &&
130            MemMap.remap(UntaggedPos, CommitBase + CommitSize - UntaggedPos,
131                         "scudo:secondary", Flags);
132   } else {
133     const uptr RemapFlags =
134         (useMemoryTagging<Config>(Options) ? MAP_MEMTAG : 0) | Flags;
135     return MemMap.remap(CommitBase, CommitSize, "scudo:secondary", RemapFlags);
136   }
137 }
138 
139 // Template specialization to avoid producing zero-length array
140 template <typename T, size_t Size> class NonZeroLengthArray {
141 public:
142   T &operator[](uptr Idx) { return values[Idx]; }
143 
144 private:
145   T values[Size];
146 };
147 template <typename T> class NonZeroLengthArray<T, 0> {
148 public:
149   T &operator[](uptr UNUSED Idx) { UNREACHABLE("Unsupported!"); }
150 };
151 
152 template <typename Config> class MapAllocatorCache {
153 public:
154   using CacheConfig = typename Config::Secondary::Cache;
155 
getStats(ScopedString * Str)156   void getStats(ScopedString *Str) {
157     ScopedLock L(Mutex);
158     uptr Integral;
159     uptr Fractional;
160     computePercentage(SuccessfulRetrieves, CallsToRetrieve, &Integral,
161                       &Fractional);
162     Str->append("Stats: MapAllocatorCache: EntriesCount: %d, "
163                 "MaxEntriesCount: %u, MaxEntrySize: %zu\n",
164                 EntriesCount, atomic_load_relaxed(&MaxEntriesCount),
165                 atomic_load_relaxed(&MaxEntrySize));
166     Str->append("Stats: CacheRetrievalStats: SuccessRate: %u/%u "
167                 "(%zu.%02zu%%)\n",
168                 SuccessfulRetrieves, CallsToRetrieve, Integral, Fractional);
169     for (CachedBlock Entry : Entries) {
170       if (!Entry.isValid())
171         continue;
172       Str->append("StartBlockAddress: 0x%zx, EndBlockAddress: 0x%zx, "
173                   "BlockSize: %zu %s\n",
174                   Entry.CommitBase, Entry.CommitBase + Entry.CommitSize,
175                   Entry.CommitSize, Entry.Time == 0 ? "[R]" : "");
176     }
177   }
178 
179   // Ensure the default maximum specified fits the array.
180   static_assert(CacheConfig::DefaultMaxEntriesCount <=
181                     CacheConfig::EntriesArraySize,
182                 "");
183 
init(s32 ReleaseToOsInterval)184   void init(s32 ReleaseToOsInterval) NO_THREAD_SAFETY_ANALYSIS {
185     DCHECK_EQ(EntriesCount, 0U);
186     setOption(Option::MaxCacheEntriesCount,
187               static_cast<sptr>(CacheConfig::DefaultMaxEntriesCount));
188     setOption(Option::MaxCacheEntrySize,
189               static_cast<sptr>(CacheConfig::DefaultMaxEntrySize));
190     setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval));
191   }
192 
store(const Options & Options,LargeBlock::Header * H)193   void store(const Options &Options, LargeBlock::Header *H) EXCLUDES(Mutex) {
194     if (!canCache(H->CommitSize))
195       return unmap(H);
196 
197     bool EntryCached = false;
198     bool EmptyCache = false;
199     const s32 Interval = atomic_load_relaxed(&ReleaseToOsIntervalMs);
200     const u64 Time = getMonotonicTimeFast();
201     const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount);
202     CachedBlock Entry;
203     Entry.CommitBase = H->CommitBase;
204     Entry.CommitSize = H->CommitSize;
205     Entry.BlockBegin = reinterpret_cast<uptr>(H + 1);
206     Entry.MemMap = H->MemMap;
207     Entry.Time = Time;
208     if (useMemoryTagging<Config>(Options)) {
209       if (Interval == 0 && !SCUDO_FUCHSIA) {
210         // Release the memory and make it inaccessible at the same time by
211         // creating a new MAP_NOACCESS mapping on top of the existing mapping.
212         // Fuchsia does not support replacing mappings by creating a new mapping
213         // on top so we just do the two syscalls there.
214         Entry.Time = 0;
215         mapSecondary<Config>(Options, Entry.CommitBase, Entry.CommitSize,
216                              Entry.CommitBase, MAP_NOACCESS, Entry.MemMap);
217       } else {
218         Entry.MemMap.setMemoryPermission(Entry.CommitBase, Entry.CommitSize,
219                                          MAP_NOACCESS);
220       }
221     } else if (Interval == 0) {
222       Entry.MemMap.releaseAndZeroPagesToOS(Entry.CommitBase, Entry.CommitSize);
223       Entry.Time = 0;
224     }
225     do {
226       ScopedLock L(Mutex);
227       if (useMemoryTagging<Config>(Options) && QuarantinePos == -1U) {
228         // If we get here then memory tagging was disabled in between when we
229         // read Options and when we locked Mutex. We can't insert our entry into
230         // the quarantine or the cache because the permissions would be wrong so
231         // just unmap it.
232         break;
233       }
234       if (CacheConfig::QuarantineSize && useMemoryTagging<Config>(Options)) {
235         QuarantinePos =
236             (QuarantinePos + 1) % Max(CacheConfig::QuarantineSize, 1u);
237         if (!Quarantine[QuarantinePos].isValid()) {
238           Quarantine[QuarantinePos] = Entry;
239           return;
240         }
241         CachedBlock PrevEntry = Quarantine[QuarantinePos];
242         Quarantine[QuarantinePos] = Entry;
243         if (OldestTime == 0)
244           OldestTime = Entry.Time;
245         Entry = PrevEntry;
246       }
247       if (EntriesCount >= MaxCount) {
248         if (IsFullEvents++ == 4U)
249           EmptyCache = true;
250       } else {
251         for (u32 I = 0; I < MaxCount; I++) {
252           if (Entries[I].isValid())
253             continue;
254           if (I != 0)
255             Entries[I] = Entries[0];
256           Entries[0] = Entry;
257           EntriesCount++;
258           if (OldestTime == 0)
259             OldestTime = Entry.Time;
260           EntryCached = true;
261           break;
262         }
263       }
264     } while (0);
265     if (EmptyCache)
266       empty();
267     else if (Interval >= 0)
268       releaseOlderThan(Time - static_cast<u64>(Interval) * 1000000);
269     if (!EntryCached)
270       Entry.MemMap.unmap(Entry.MemMap.getBase(), Entry.MemMap.getCapacity());
271   }
272 
retrieve(Options Options,uptr Size,uptr Alignment,uptr HeadersSize,LargeBlock::Header ** H,bool * Zeroed)273   bool retrieve(Options Options, uptr Size, uptr Alignment, uptr HeadersSize,
274                 LargeBlock::Header **H, bool *Zeroed) EXCLUDES(Mutex) {
275     const uptr PageSize = getPageSizeCached();
276     const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount);
277     // 10% of the requested size proved to be the optimal choice for
278     // retrieving cached blocks after testing several options.
279     constexpr u32 FragmentedBytesDivisor = 10;
280     bool Found = false;
281     CachedBlock Entry;
282     uptr EntryHeaderPos = 0;
283     {
284       ScopedLock L(Mutex);
285       CallsToRetrieve++;
286       if (EntriesCount == 0)
287         return false;
288       u32 OptimalFitIndex = 0;
289       uptr MinDiff = UINTPTR_MAX;
290       for (u32 I = 0; I < MaxCount; I++) {
291         if (!Entries[I].isValid())
292           continue;
293         const uptr CommitBase = Entries[I].CommitBase;
294         const uptr CommitSize = Entries[I].CommitSize;
295         const uptr AllocPos =
296             roundDown(CommitBase + CommitSize - Size, Alignment);
297         const uptr HeaderPos = AllocPos - HeadersSize;
298         if (HeaderPos > CommitBase + CommitSize)
299           continue;
300         if (HeaderPos < CommitBase ||
301             AllocPos > CommitBase + PageSize * MaxUnusedCachePages) {
302           continue;
303         }
304         Found = true;
305         const uptr Diff = HeaderPos - CommitBase;
306         // immediately use a cached block if it's size is close enough to the
307         // requested size.
308         const uptr MaxAllowedFragmentedBytes =
309             (CommitBase + CommitSize - HeaderPos) / FragmentedBytesDivisor;
310         if (Diff <= MaxAllowedFragmentedBytes) {
311           OptimalFitIndex = I;
312           EntryHeaderPos = HeaderPos;
313           break;
314         }
315         // keep track of the smallest cached block
316         // that is greater than (AllocSize + HeaderSize)
317         if (Diff > MinDiff)
318           continue;
319         OptimalFitIndex = I;
320         MinDiff = Diff;
321         EntryHeaderPos = HeaderPos;
322       }
323       if (Found) {
324         Entry = Entries[OptimalFitIndex];
325         Entries[OptimalFitIndex].invalidate();
326         EntriesCount--;
327         SuccessfulRetrieves++;
328       }
329     }
330     if (!Found)
331       return false;
332 
333     *H = reinterpret_cast<LargeBlock::Header *>(
334         LargeBlock::addHeaderTag<Config>(EntryHeaderPos));
335     *Zeroed = Entry.Time == 0;
336     if (useMemoryTagging<Config>(Options))
337       Entry.MemMap.setMemoryPermission(Entry.CommitBase, Entry.CommitSize, 0);
338     uptr NewBlockBegin = reinterpret_cast<uptr>(*H + 1);
339     if (useMemoryTagging<Config>(Options)) {
340       if (*Zeroed) {
341         storeTags(LargeBlock::addHeaderTag<Config>(Entry.CommitBase),
342                   NewBlockBegin);
343       } else if (Entry.BlockBegin < NewBlockBegin) {
344         storeTags(Entry.BlockBegin, NewBlockBegin);
345       } else {
346         storeTags(untagPointer(NewBlockBegin), untagPointer(Entry.BlockBegin));
347       }
348     }
349     (*H)->CommitBase = Entry.CommitBase;
350     (*H)->CommitSize = Entry.CommitSize;
351     (*H)->MemMap = Entry.MemMap;
352     return true;
353   }
354 
canCache(uptr Size)355   bool canCache(uptr Size) {
356     return atomic_load_relaxed(&MaxEntriesCount) != 0U &&
357            Size <= atomic_load_relaxed(&MaxEntrySize);
358   }
359 
setOption(Option O,sptr Value)360   bool setOption(Option O, sptr Value) {
361     if (O == Option::ReleaseInterval) {
362       const s32 Interval = Max(
363           Min(static_cast<s32>(Value), CacheConfig::MaxReleaseToOsIntervalMs),
364           CacheConfig::MinReleaseToOsIntervalMs);
365       atomic_store_relaxed(&ReleaseToOsIntervalMs, Interval);
366       return true;
367     }
368     if (O == Option::MaxCacheEntriesCount) {
369       const u32 MaxCount = static_cast<u32>(Value);
370       if (MaxCount > CacheConfig::EntriesArraySize)
371         return false;
372       atomic_store_relaxed(&MaxEntriesCount, MaxCount);
373       return true;
374     }
375     if (O == Option::MaxCacheEntrySize) {
376       atomic_store_relaxed(&MaxEntrySize, static_cast<uptr>(Value));
377       return true;
378     }
379     // Not supported by the Secondary Cache, but not an error either.
380     return true;
381   }
382 
releaseToOS()383   void releaseToOS() { releaseOlderThan(UINT64_MAX); }
384 
disableMemoryTagging()385   void disableMemoryTagging() EXCLUDES(Mutex) {
386     ScopedLock L(Mutex);
387     for (u32 I = 0; I != CacheConfig::QuarantineSize; ++I) {
388       if (Quarantine[I].isValid()) {
389         MemMapT &MemMap = Quarantine[I].MemMap;
390         MemMap.unmap(MemMap.getBase(), MemMap.getCapacity());
391         Quarantine[I].invalidate();
392       }
393     }
394     const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount);
395     for (u32 I = 0; I < MaxCount; I++) {
396       if (Entries[I].isValid()) {
397         Entries[I].MemMap.setMemoryPermission(Entries[I].CommitBase,
398                                               Entries[I].CommitSize, 0);
399       }
400     }
401     QuarantinePos = -1U;
402   }
403 
disable()404   void disable() NO_THREAD_SAFETY_ANALYSIS { Mutex.lock(); }
405 
enable()406   void enable() NO_THREAD_SAFETY_ANALYSIS { Mutex.unlock(); }
407 
unmapTestOnly()408   void unmapTestOnly() { empty(); }
409 
410 private:
empty()411   void empty() {
412     MemMapT MapInfo[CacheConfig::EntriesArraySize];
413     uptr N = 0;
414     {
415       ScopedLock L(Mutex);
416       for (uptr I = 0; I < CacheConfig::EntriesArraySize; I++) {
417         if (!Entries[I].isValid())
418           continue;
419         MapInfo[N] = Entries[I].MemMap;
420         Entries[I].invalidate();
421         N++;
422       }
423       EntriesCount = 0;
424       IsFullEvents = 0;
425     }
426     for (uptr I = 0; I < N; I++) {
427       MemMapT &MemMap = MapInfo[I];
428       MemMap.unmap(MemMap.getBase(), MemMap.getCapacity());
429     }
430   }
431 
releaseIfOlderThan(CachedBlock & Entry,u64 Time)432   void releaseIfOlderThan(CachedBlock &Entry, u64 Time) REQUIRES(Mutex) {
433     if (!Entry.isValid() || !Entry.Time)
434       return;
435     if (Entry.Time > Time) {
436       if (OldestTime == 0 || Entry.Time < OldestTime)
437         OldestTime = Entry.Time;
438       return;
439     }
440     Entry.MemMap.releaseAndZeroPagesToOS(Entry.CommitBase, Entry.CommitSize);
441     Entry.Time = 0;
442   }
443 
releaseOlderThan(u64 Time)444   void releaseOlderThan(u64 Time) EXCLUDES(Mutex) {
445     ScopedLock L(Mutex);
446     if (!EntriesCount || OldestTime == 0 || OldestTime > Time)
447       return;
448     OldestTime = 0;
449     for (uptr I = 0; I < CacheConfig::QuarantineSize; I++)
450       releaseIfOlderThan(Quarantine[I], Time);
451     for (uptr I = 0; I < CacheConfig::EntriesArraySize; I++)
452       releaseIfOlderThan(Entries[I], Time);
453   }
454 
455   HybridMutex Mutex;
456   u32 EntriesCount GUARDED_BY(Mutex) = 0;
457   u32 QuarantinePos GUARDED_BY(Mutex) = 0;
458   atomic_u32 MaxEntriesCount = {};
459   atomic_uptr MaxEntrySize = {};
460   u64 OldestTime GUARDED_BY(Mutex) = 0;
461   u32 IsFullEvents GUARDED_BY(Mutex) = 0;
462   atomic_s32 ReleaseToOsIntervalMs = {};
463   u32 CallsToRetrieve GUARDED_BY(Mutex) = 0;
464   u32 SuccessfulRetrieves GUARDED_BY(Mutex) = 0;
465 
466   CachedBlock Entries[CacheConfig::EntriesArraySize] GUARDED_BY(Mutex) = {};
467   NonZeroLengthArray<CachedBlock, CacheConfig::QuarantineSize>
468       Quarantine GUARDED_BY(Mutex) = {};
469 };
470 
471 template <typename Config> class MapAllocator {
472 public:
473   void init(GlobalStats *S,
474             s32 ReleaseToOsInterval = -1) NO_THREAD_SAFETY_ANALYSIS {
475     DCHECK_EQ(AllocatedBytes, 0U);
476     DCHECK_EQ(FreedBytes, 0U);
477     Cache.init(ReleaseToOsInterval);
478     Stats.init();
479     if (LIKELY(S))
480       S->link(&Stats);
481   }
482 
483   void *allocate(const Options &Options, uptr Size, uptr AlignmentHint = 0,
484                  uptr *BlockEnd = nullptr,
485                  FillContentsMode FillContents = NoFill);
486 
487   void deallocate(const Options &Options, void *Ptr);
488 
getBlockEnd(void * Ptr)489   static uptr getBlockEnd(void *Ptr) {
490     auto *B = LargeBlock::getHeader<Config>(Ptr);
491     return B->CommitBase + B->CommitSize;
492   }
493 
getBlockSize(void * Ptr)494   static uptr getBlockSize(void *Ptr) {
495     return getBlockEnd(Ptr) - reinterpret_cast<uptr>(Ptr);
496   }
497 
getHeadersSize()498   static constexpr uptr getHeadersSize() {
499     return Chunk::getHeaderSize() + LargeBlock::getHeaderSize();
500   }
501 
disable()502   void disable() NO_THREAD_SAFETY_ANALYSIS {
503     Mutex.lock();
504     Cache.disable();
505   }
506 
enable()507   void enable() NO_THREAD_SAFETY_ANALYSIS {
508     Cache.enable();
509     Mutex.unlock();
510   }
511 
iterateOverBlocks(F Callback)512   template <typename F> void iterateOverBlocks(F Callback) const {
513     Mutex.assertHeld();
514 
515     for (const auto &H : InUseBlocks) {
516       uptr Ptr = reinterpret_cast<uptr>(&H) + LargeBlock::getHeaderSize();
517       if (allocatorSupportsMemoryTagging<Config>())
518         Ptr = untagPointer(Ptr);
519       Callback(Ptr);
520     }
521   }
522 
canCache(uptr Size)523   bool canCache(uptr Size) { return Cache.canCache(Size); }
524 
setOption(Option O,sptr Value)525   bool setOption(Option O, sptr Value) { return Cache.setOption(O, Value); }
526 
releaseToOS()527   void releaseToOS() { Cache.releaseToOS(); }
528 
disableMemoryTagging()529   void disableMemoryTagging() { Cache.disableMemoryTagging(); }
530 
unmapTestOnly()531   void unmapTestOnly() { Cache.unmapTestOnly(); }
532 
533   void getStats(ScopedString *Str);
534 
535 private:
536   typename Config::Secondary::template CacheT<Config> Cache;
537 
538   mutable HybridMutex Mutex;
539   DoublyLinkedList<LargeBlock::Header> InUseBlocks GUARDED_BY(Mutex);
540   uptr AllocatedBytes GUARDED_BY(Mutex) = 0;
541   uptr FreedBytes GUARDED_BY(Mutex) = 0;
542   uptr FragmentedBytes GUARDED_BY(Mutex) = 0;
543   uptr LargestSize GUARDED_BY(Mutex) = 0;
544   u32 NumberOfAllocs GUARDED_BY(Mutex) = 0;
545   u32 NumberOfFrees GUARDED_BY(Mutex) = 0;
546   LocalStats Stats GUARDED_BY(Mutex);
547 };
548 
549 // As with the Primary, the size passed to this function includes any desired
550 // alignment, so that the frontend can align the user allocation. The hint
551 // parameter allows us to unmap spurious memory when dealing with larger
552 // (greater than a page) alignments on 32-bit platforms.
553 // Due to the sparsity of address space available on those platforms, requesting
554 // an allocation from the Secondary with a large alignment would end up wasting
555 // VA space (even though we are not committing the whole thing), hence the need
556 // to trim off some of the reserved space.
557 // For allocations requested with an alignment greater than or equal to a page,
558 // the committed memory will amount to something close to Size - AlignmentHint
559 // (pending rounding and headers).
560 template <typename Config>
allocate(const Options & Options,uptr Size,uptr Alignment,uptr * BlockEndPtr,FillContentsMode FillContents)561 void *MapAllocator<Config>::allocate(const Options &Options, uptr Size,
562                                      uptr Alignment, uptr *BlockEndPtr,
563                                      FillContentsMode FillContents) {
564   if (Options.get(OptionBit::AddLargeAllocationSlack))
565     Size += 1UL << SCUDO_MIN_ALIGNMENT_LOG;
566   Alignment = Max(Alignment, uptr(1U) << SCUDO_MIN_ALIGNMENT_LOG);
567   const uptr PageSize = getPageSizeCached();
568 
569   // Note that cached blocks may have aligned address already. Thus we simply
570   // pass the required size (`Size` + `getHeadersSize()`) to do cache look up.
571   const uptr MinNeededSizeForCache = roundUp(Size + getHeadersSize(), PageSize);
572 
573   if (Alignment < PageSize && Cache.canCache(MinNeededSizeForCache)) {
574     LargeBlock::Header *H;
575     bool Zeroed;
576     if (Cache.retrieve(Options, Size, Alignment, getHeadersSize(), &H,
577                        &Zeroed)) {
578       const uptr BlockEnd = H->CommitBase + H->CommitSize;
579       if (BlockEndPtr)
580         *BlockEndPtr = BlockEnd;
581       uptr HInt = reinterpret_cast<uptr>(H);
582       if (allocatorSupportsMemoryTagging<Config>())
583         HInt = untagPointer(HInt);
584       const uptr PtrInt = HInt + LargeBlock::getHeaderSize();
585       void *Ptr = reinterpret_cast<void *>(PtrInt);
586       if (FillContents && !Zeroed)
587         memset(Ptr, FillContents == ZeroFill ? 0 : PatternFillByte,
588                BlockEnd - PtrInt);
589       {
590         ScopedLock L(Mutex);
591         InUseBlocks.push_back(H);
592         AllocatedBytes += H->CommitSize;
593         FragmentedBytes += H->MemMap.getCapacity() - H->CommitSize;
594         NumberOfAllocs++;
595         Stats.add(StatAllocated, H->CommitSize);
596         Stats.add(StatMapped, H->MemMap.getCapacity());
597       }
598       return Ptr;
599     }
600   }
601 
602   uptr RoundedSize =
603       roundUp(roundUp(Size, Alignment) + getHeadersSize(), PageSize);
604   if (Alignment > PageSize)
605     RoundedSize += Alignment - PageSize;
606 
607   ReservedMemoryT ReservedMemory;
608   const uptr MapSize = RoundedSize + 2 * PageSize;
609   if (UNLIKELY(!ReservedMemory.create(/*Addr=*/0U, MapSize, nullptr,
610                                       MAP_ALLOWNOMEM))) {
611     return nullptr;
612   }
613 
614   // Take the entire ownership of reserved region.
615   MemMapT MemMap = ReservedMemory.dispatch(ReservedMemory.getBase(),
616                                            ReservedMemory.getCapacity());
617   uptr MapBase = MemMap.getBase();
618   uptr CommitBase = MapBase + PageSize;
619   uptr MapEnd = MapBase + MapSize;
620 
621   // In the unlikely event of alignments larger than a page, adjust the amount
622   // of memory we want to commit, and trim the extra memory.
623   if (UNLIKELY(Alignment >= PageSize)) {
624     // For alignments greater than or equal to a page, the user pointer (eg: the
625     // pointer that is returned by the C or C++ allocation APIs) ends up on a
626     // page boundary , and our headers will live in the preceding page.
627     CommitBase = roundUp(MapBase + PageSize + 1, Alignment) - PageSize;
628     const uptr NewMapBase = CommitBase - PageSize;
629     DCHECK_GE(NewMapBase, MapBase);
630     // We only trim the extra memory on 32-bit platforms: 64-bit platforms
631     // are less constrained memory wise, and that saves us two syscalls.
632     if (SCUDO_WORDSIZE == 32U && NewMapBase != MapBase) {
633       MemMap.unmap(MapBase, NewMapBase - MapBase);
634       MapBase = NewMapBase;
635     }
636     const uptr NewMapEnd =
637         CommitBase + PageSize + roundUp(Size, PageSize) + PageSize;
638     DCHECK_LE(NewMapEnd, MapEnd);
639     if (SCUDO_WORDSIZE == 32U && NewMapEnd != MapEnd) {
640       MemMap.unmap(NewMapEnd, MapEnd - NewMapEnd);
641       MapEnd = NewMapEnd;
642     }
643   }
644 
645   const uptr CommitSize = MapEnd - PageSize - CommitBase;
646   const uptr AllocPos = roundDown(CommitBase + CommitSize - Size, Alignment);
647   if (!mapSecondary<Config>(Options, CommitBase, CommitSize, AllocPos, 0,
648                             MemMap)) {
649     MemMap.unmap(MemMap.getBase(), MemMap.getCapacity());
650     return nullptr;
651   }
652   const uptr HeaderPos = AllocPos - getHeadersSize();
653   LargeBlock::Header *H = reinterpret_cast<LargeBlock::Header *>(
654       LargeBlock::addHeaderTag<Config>(HeaderPos));
655   if (useMemoryTagging<Config>(Options))
656     storeTags(LargeBlock::addHeaderTag<Config>(CommitBase),
657               reinterpret_cast<uptr>(H + 1));
658   H->CommitBase = CommitBase;
659   H->CommitSize = CommitSize;
660   H->MemMap = MemMap;
661   if (BlockEndPtr)
662     *BlockEndPtr = CommitBase + CommitSize;
663   {
664     ScopedLock L(Mutex);
665     InUseBlocks.push_back(H);
666     AllocatedBytes += CommitSize;
667     FragmentedBytes += H->MemMap.getCapacity() - CommitSize;
668     if (LargestSize < CommitSize)
669       LargestSize = CommitSize;
670     NumberOfAllocs++;
671     Stats.add(StatAllocated, CommitSize);
672     Stats.add(StatMapped, H->MemMap.getCapacity());
673   }
674   return reinterpret_cast<void *>(HeaderPos + LargeBlock::getHeaderSize());
675 }
676 
677 template <typename Config>
deallocate(const Options & Options,void * Ptr)678 void MapAllocator<Config>::deallocate(const Options &Options, void *Ptr)
679     EXCLUDES(Mutex) {
680   LargeBlock::Header *H = LargeBlock::getHeader<Config>(Ptr);
681   const uptr CommitSize = H->CommitSize;
682   {
683     ScopedLock L(Mutex);
684     InUseBlocks.remove(H);
685     FreedBytes += CommitSize;
686     FragmentedBytes -= H->MemMap.getCapacity() - CommitSize;
687     NumberOfFrees++;
688     Stats.sub(StatAllocated, CommitSize);
689     Stats.sub(StatMapped, H->MemMap.getCapacity());
690   }
691   Cache.store(Options, H);
692 }
693 
694 template <typename Config>
getStats(ScopedString * Str)695 void MapAllocator<Config>::getStats(ScopedString *Str) EXCLUDES(Mutex) {
696   ScopedLock L(Mutex);
697   Str->append("Stats: MapAllocator: allocated %u times (%zuK), freed %u times "
698               "(%zuK), remains %u (%zuK) max %zuM, Fragmented %zuK\n",
699               NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees,
700               FreedBytes >> 10, NumberOfAllocs - NumberOfFrees,
701               (AllocatedBytes - FreedBytes) >> 10, LargestSize >> 20,
702               FragmentedBytes >> 10);
703   Cache.getStats(Str);
704 }
705 
706 } // namespace scudo
707 
708 #endif // SCUDO_SECONDARY_H_
709