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 "common.h" 13 #include "list.h" 14 #include "mutex.h" 15 #include "stats.h" 16 #include "string_utils.h" 17 18 namespace scudo { 19 20 // This allocator wraps the platform allocation primitives, and as such is on 21 // the slower side and should preferably be used for larger sized allocations. 22 // Blocks allocated will be preceded and followed by a guard page, and hold 23 // their own header that is not checksummed: the guard pages and the Combined 24 // header should be enough for our purpose. 25 26 namespace LargeBlock { 27 28 struct Header { 29 LargeBlock::Header *Prev; 30 LargeBlock::Header *Next; 31 uptr BlockEnd; 32 uptr MapBase; 33 uptr MapSize; 34 [[no_unique_address]] MapPlatformData Data; 35 }; 36 37 constexpr uptr getHeaderSize() { 38 return roundUpTo(sizeof(Header), 1U << SCUDO_MIN_ALIGNMENT_LOG); 39 } 40 41 static Header *getHeader(uptr Ptr) { 42 return reinterpret_cast<Header *>(Ptr - getHeaderSize()); 43 } 44 45 static Header *getHeader(const void *Ptr) { 46 return getHeader(reinterpret_cast<uptr>(Ptr)); 47 } 48 49 } // namespace LargeBlock 50 51 class MapAllocatorNoCache { 52 public: 53 void initLinkerInitialized(UNUSED s32 ReleaseToOsInterval) {} 54 void init(UNUSED s32 ReleaseToOsInterval) {} 55 bool retrieve(UNUSED uptr Size, UNUSED LargeBlock::Header **H, 56 UNUSED bool *Zeroed) { 57 return false; 58 } 59 bool store(UNUSED LargeBlock::Header *H) { return false; } 60 bool canCache(UNUSED uptr Size) { return false; } 61 void disable() {} 62 void enable() {} 63 void releaseToOS() {} 64 bool setOption(Option O, UNUSED sptr Value) { 65 if (O == Option::ReleaseInterval || O == Option::MaxCacheEntriesCount || 66 O == Option::MaxCacheEntrySize) 67 return false; 68 // Not supported by the Secondary Cache, but not an error either. 69 return true; 70 } 71 }; 72 73 template <typename Config> class MapAllocatorCache { 74 public: 75 // Ensure the default maximum specified fits the array. 76 static_assert(Config::SecondaryCacheDefaultMaxEntriesCount <= 77 Config::SecondaryCacheEntriesArraySize, 78 ""); 79 80 void initLinkerInitialized(s32 ReleaseToOsInterval) { 81 setOption(Option::MaxCacheEntriesCount, 82 static_cast<sptr>(Config::SecondaryCacheDefaultMaxEntriesCount)); 83 setOption(Option::MaxCacheEntrySize, 84 static_cast<sptr>(Config::SecondaryCacheDefaultMaxEntrySize)); 85 setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval)); 86 } 87 void init(s32 ReleaseToOsInterval) { 88 memset(this, 0, sizeof(*this)); 89 initLinkerInitialized(ReleaseToOsInterval); 90 } 91 92 bool store(LargeBlock::Header *H) { 93 bool EntryCached = false; 94 bool EmptyCache = false; 95 const u64 Time = getMonotonicTime(); 96 const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount); 97 { 98 ScopedLock L(Mutex); 99 if (EntriesCount >= MaxCount) { 100 if (IsFullEvents++ == 4U) 101 EmptyCache = true; 102 } else { 103 for (u32 I = 0; I < MaxCount; I++) { 104 if (Entries[I].Block) 105 continue; 106 if (I != 0) 107 Entries[I] = Entries[0]; 108 Entries[0].Block = reinterpret_cast<uptr>(H); 109 Entries[0].BlockEnd = H->BlockEnd; 110 Entries[0].MapBase = H->MapBase; 111 Entries[0].MapSize = H->MapSize; 112 Entries[0].Data = H->Data; 113 Entries[0].Time = Time; 114 EntriesCount++; 115 EntryCached = true; 116 break; 117 } 118 } 119 } 120 s32 Interval; 121 if (EmptyCache) 122 empty(); 123 else if ((Interval = atomic_load_relaxed(&ReleaseToOsIntervalMs)) >= 0) 124 releaseOlderThan(Time - static_cast<u64>(Interval) * 1000000); 125 return EntryCached; 126 } 127 128 bool retrieve(uptr Size, LargeBlock::Header **H, bool *Zeroed) { 129 const uptr PageSize = getPageSizeCached(); 130 const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount); 131 ScopedLock L(Mutex); 132 if (EntriesCount == 0) 133 return false; 134 for (u32 I = 0; I < MaxCount; I++) { 135 if (!Entries[I].Block) 136 continue; 137 const uptr BlockSize = Entries[I].BlockEnd - Entries[I].Block; 138 if (Size > BlockSize) 139 continue; 140 if (Size < BlockSize - PageSize * 4U) 141 continue; 142 *H = reinterpret_cast<LargeBlock::Header *>(Entries[I].Block); 143 *Zeroed = Entries[I].Time == 0; 144 Entries[I].Block = 0; 145 (*H)->BlockEnd = Entries[I].BlockEnd; 146 (*H)->MapBase = Entries[I].MapBase; 147 (*H)->MapSize = Entries[I].MapSize; 148 (*H)->Data = Entries[I].Data; 149 EntriesCount--; 150 return true; 151 } 152 return false; 153 } 154 155 bool canCache(uptr Size) { 156 return atomic_load_relaxed(&MaxEntriesCount) != 0U && 157 Size <= atomic_load_relaxed(&MaxEntrySize); 158 } 159 160 bool setOption(Option O, sptr Value) { 161 if (O == Option::ReleaseInterval) { 162 const s32 Interval = 163 Max(Min(static_cast<s32>(Value), 164 Config::SecondaryCacheMaxReleaseToOsIntervalMs), 165 Config::SecondaryCacheMinReleaseToOsIntervalMs); 166 atomic_store_relaxed(&ReleaseToOsIntervalMs, Interval); 167 return true; 168 } else if (O == Option::MaxCacheEntriesCount) { 169 const u32 MaxCount = static_cast<u32>(Value); 170 if (MaxCount > Config::SecondaryCacheEntriesArraySize) 171 return false; 172 atomic_store_relaxed(&MaxEntriesCount, MaxCount); 173 return true; 174 } else if (O == Option::MaxCacheEntrySize) { 175 atomic_store_relaxed(&MaxEntrySize, static_cast<uptr>(Value)); 176 return true; 177 } 178 // Not supported by the Secondary Cache, but not an error either. 179 return true; 180 } 181 182 void releaseToOS() { releaseOlderThan(UINT64_MAX); } 183 184 void disable() { Mutex.lock(); } 185 186 void enable() { Mutex.unlock(); } 187 188 private: 189 void empty() { 190 struct { 191 void *MapBase; 192 uptr MapSize; 193 MapPlatformData Data; 194 } MapInfo[Config::SecondaryCacheEntriesArraySize]; 195 uptr N = 0; 196 { 197 ScopedLock L(Mutex); 198 for (uptr I = 0; I < Config::SecondaryCacheEntriesArraySize; I++) { 199 if (!Entries[I].Block) 200 continue; 201 MapInfo[N].MapBase = reinterpret_cast<void *>(Entries[I].MapBase); 202 MapInfo[N].MapSize = Entries[I].MapSize; 203 MapInfo[N].Data = Entries[I].Data; 204 Entries[I].Block = 0; 205 N++; 206 } 207 EntriesCount = 0; 208 IsFullEvents = 0; 209 } 210 for (uptr I = 0; I < N; I++) 211 unmap(MapInfo[I].MapBase, MapInfo[I].MapSize, UNMAP_ALL, 212 &MapInfo[I].Data); 213 } 214 215 void releaseOlderThan(u64 Time) { 216 ScopedLock L(Mutex); 217 if (!EntriesCount) 218 return; 219 for (uptr I = 0; I < Config::SecondaryCacheEntriesArraySize; I++) { 220 if (!Entries[I].Block || !Entries[I].Time || Entries[I].Time > Time) 221 continue; 222 releasePagesToOS(Entries[I].Block, 0, 223 Entries[I].BlockEnd - Entries[I].Block, 224 &Entries[I].Data); 225 Entries[I].Time = 0; 226 } 227 } 228 229 struct CachedBlock { 230 uptr Block; 231 uptr BlockEnd; 232 uptr MapBase; 233 uptr MapSize; 234 [[no_unique_address]] MapPlatformData Data; 235 u64 Time; 236 }; 237 238 HybridMutex Mutex; 239 CachedBlock Entries[Config::SecondaryCacheEntriesArraySize]; 240 u32 EntriesCount; 241 atomic_u32 MaxEntriesCount; 242 atomic_uptr MaxEntrySize; 243 uptr LargestSize; 244 u32 IsFullEvents; 245 atomic_s32 ReleaseToOsIntervalMs; 246 }; 247 248 template <typename Config> class MapAllocator { 249 public: 250 void initLinkerInitialized(GlobalStats *S, s32 ReleaseToOsInterval = -1) { 251 Cache.initLinkerInitialized(ReleaseToOsInterval); 252 Stats.initLinkerInitialized(); 253 if (LIKELY(S)) 254 S->link(&Stats); 255 } 256 void init(GlobalStats *S, s32 ReleaseToOsInterval = -1) { 257 memset(this, 0, sizeof(*this)); 258 initLinkerInitialized(S, ReleaseToOsInterval); 259 } 260 261 void *allocate(uptr Size, uptr AlignmentHint = 0, uptr *BlockEnd = nullptr, 262 FillContentsMode FillContents = NoFill); 263 264 void deallocate(void *Ptr); 265 266 static uptr getBlockEnd(void *Ptr) { 267 return LargeBlock::getHeader(Ptr)->BlockEnd; 268 } 269 270 static uptr getBlockSize(void *Ptr) { 271 return getBlockEnd(Ptr) - reinterpret_cast<uptr>(Ptr); 272 } 273 274 void getStats(ScopedString *Str) const; 275 276 void disable() { 277 Mutex.lock(); 278 Cache.disable(); 279 } 280 281 void enable() { 282 Cache.enable(); 283 Mutex.unlock(); 284 } 285 286 template <typename F> void iterateOverBlocks(F Callback) const { 287 for (const auto &H : InUseBlocks) 288 Callback(reinterpret_cast<uptr>(&H) + LargeBlock::getHeaderSize()); 289 } 290 291 uptr canCache(uptr Size) { return Cache.canCache(Size); } 292 293 bool setOption(Option O, sptr Value) { return Cache.setOption(O, Value); } 294 295 void releaseToOS() { Cache.releaseToOS(); } 296 297 private: 298 typename Config::SecondaryCache Cache; 299 300 HybridMutex Mutex; 301 DoublyLinkedList<LargeBlock::Header> InUseBlocks; 302 uptr AllocatedBytes; 303 uptr FreedBytes; 304 uptr LargestSize; 305 u32 NumberOfAllocs; 306 u32 NumberOfFrees; 307 LocalStats Stats; 308 }; 309 310 // As with the Primary, the size passed to this function includes any desired 311 // alignment, so that the frontend can align the user allocation. The hint 312 // parameter allows us to unmap spurious memory when dealing with larger 313 // (greater than a page) alignments on 32-bit platforms. 314 // Due to the sparsity of address space available on those platforms, requesting 315 // an allocation from the Secondary with a large alignment would end up wasting 316 // VA space (even though we are not committing the whole thing), hence the need 317 // to trim off some of the reserved space. 318 // For allocations requested with an alignment greater than or equal to a page, 319 // the committed memory will amount to something close to Size - AlignmentHint 320 // (pending rounding and headers). 321 template <typename Config> 322 void *MapAllocator<Config>::allocate(uptr Size, uptr AlignmentHint, 323 uptr *BlockEnd, 324 FillContentsMode FillContents) { 325 DCHECK_GE(Size, AlignmentHint); 326 const uptr PageSize = getPageSizeCached(); 327 const uptr RoundedSize = 328 roundUpTo(Size + LargeBlock::getHeaderSize(), PageSize); 329 330 if (AlignmentHint < PageSize && Cache.canCache(RoundedSize)) { 331 LargeBlock::Header *H; 332 bool Zeroed; 333 if (Cache.retrieve(RoundedSize, &H, &Zeroed)) { 334 if (BlockEnd) 335 *BlockEnd = H->BlockEnd; 336 void *Ptr = reinterpret_cast<void *>(reinterpret_cast<uptr>(H) + 337 LargeBlock::getHeaderSize()); 338 if (FillContents && !Zeroed) 339 memset(Ptr, FillContents == ZeroFill ? 0 : PatternFillByte, 340 H->BlockEnd - reinterpret_cast<uptr>(Ptr)); 341 const uptr BlockSize = H->BlockEnd - reinterpret_cast<uptr>(H); 342 { 343 ScopedLock L(Mutex); 344 InUseBlocks.push_back(H); 345 AllocatedBytes += BlockSize; 346 NumberOfAllocs++; 347 Stats.add(StatAllocated, BlockSize); 348 Stats.add(StatMapped, H->MapSize); 349 } 350 return Ptr; 351 } 352 } 353 354 MapPlatformData Data = {}; 355 const uptr MapSize = RoundedSize + 2 * PageSize; 356 uptr MapBase = 357 reinterpret_cast<uptr>(map(nullptr, MapSize, "scudo:secondary", 358 MAP_NOACCESS | MAP_ALLOWNOMEM, &Data)); 359 if (UNLIKELY(!MapBase)) 360 return nullptr; 361 uptr CommitBase = MapBase + PageSize; 362 uptr MapEnd = MapBase + MapSize; 363 364 // In the unlikely event of alignments larger than a page, adjust the amount 365 // of memory we want to commit, and trim the extra memory. 366 if (UNLIKELY(AlignmentHint >= PageSize)) { 367 // For alignments greater than or equal to a page, the user pointer (eg: the 368 // pointer that is returned by the C or C++ allocation APIs) ends up on a 369 // page boundary , and our headers will live in the preceding page. 370 CommitBase = roundUpTo(MapBase + PageSize + 1, AlignmentHint) - PageSize; 371 const uptr NewMapBase = CommitBase - PageSize; 372 DCHECK_GE(NewMapBase, MapBase); 373 // We only trim the extra memory on 32-bit platforms: 64-bit platforms 374 // are less constrained memory wise, and that saves us two syscalls. 375 if (SCUDO_WORDSIZE == 32U && NewMapBase != MapBase) { 376 unmap(reinterpret_cast<void *>(MapBase), NewMapBase - MapBase, 0, &Data); 377 MapBase = NewMapBase; 378 } 379 const uptr NewMapEnd = CommitBase + PageSize + 380 roundUpTo((Size - AlignmentHint), PageSize) + 381 PageSize; 382 DCHECK_LE(NewMapEnd, MapEnd); 383 if (SCUDO_WORDSIZE == 32U && NewMapEnd != MapEnd) { 384 unmap(reinterpret_cast<void *>(NewMapEnd), MapEnd - NewMapEnd, 0, &Data); 385 MapEnd = NewMapEnd; 386 } 387 } 388 389 const uptr CommitSize = MapEnd - PageSize - CommitBase; 390 const uptr Ptr = reinterpret_cast<uptr>( 391 map(reinterpret_cast<void *>(CommitBase), CommitSize, "scudo:secondary", 392 MAP_RESIZABLE, &Data)); 393 LargeBlock::Header *H = reinterpret_cast<LargeBlock::Header *>(Ptr); 394 H->MapBase = MapBase; 395 H->MapSize = MapEnd - MapBase; 396 H->BlockEnd = CommitBase + CommitSize; 397 H->Data = Data; 398 if (BlockEnd) 399 *BlockEnd = CommitBase + CommitSize; 400 { 401 ScopedLock L(Mutex); 402 InUseBlocks.push_back(H); 403 AllocatedBytes += CommitSize; 404 if (LargestSize < CommitSize) 405 LargestSize = CommitSize; 406 NumberOfAllocs++; 407 Stats.add(StatAllocated, CommitSize); 408 Stats.add(StatMapped, H->MapSize); 409 } 410 return reinterpret_cast<void *>(Ptr + LargeBlock::getHeaderSize()); 411 } 412 413 template <typename Config> void MapAllocator<Config>::deallocate(void *Ptr) { 414 LargeBlock::Header *H = LargeBlock::getHeader(Ptr); 415 const uptr Block = reinterpret_cast<uptr>(H); 416 const uptr CommitSize = H->BlockEnd - Block; 417 { 418 ScopedLock L(Mutex); 419 InUseBlocks.remove(H); 420 FreedBytes += CommitSize; 421 NumberOfFrees++; 422 Stats.sub(StatAllocated, CommitSize); 423 Stats.sub(StatMapped, H->MapSize); 424 } 425 if (Cache.canCache(CommitSize) && Cache.store(H)) 426 return; 427 void *Addr = reinterpret_cast<void *>(H->MapBase); 428 const uptr Size = H->MapSize; 429 MapPlatformData Data = H->Data; 430 unmap(Addr, Size, UNMAP_ALL, &Data); 431 } 432 433 template <typename Config> 434 void MapAllocator<Config>::getStats(ScopedString *Str) const { 435 Str->append( 436 "Stats: MapAllocator: allocated %zu times (%zuK), freed %zu times " 437 "(%zuK), remains %zu (%zuK) max %zuM\n", 438 NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees, FreedBytes >> 10, 439 NumberOfAllocs - NumberOfFrees, (AllocatedBytes - FreedBytes) >> 10, 440 LargestSize >> 20); 441 } 442 443 } // namespace scudo 444 445 #endif // SCUDO_SECONDARY_H_ 446