1 //===-- sanitizer_allocator_combined.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 // Part of the Sanitizer Allocator. 10 // 11 //===----------------------------------------------------------------------===// 12 #ifndef SANITIZER_ALLOCATOR_H 13 #error This file must be included inside sanitizer_allocator.h 14 #endif 15 16 // This class implements a complete memory allocator by using two 17 // internal allocators: 18 // PrimaryAllocator is efficient, but may not allocate some sizes (alignments). 19 // When allocating 2^x bytes it should return 2^x aligned chunk. 20 // PrimaryAllocator is used via a local AllocatorCache. 21 // SecondaryAllocator can allocate anything, but is not efficient. 22 template <class PrimaryAllocator, 23 class LargeMmapAllocatorPtrArray = DefaultLargeMmapAllocatorPtrArray> 24 class CombinedAllocator { 25 public: 26 using AllocatorCache = typename PrimaryAllocator::AllocatorCache; 27 using SecondaryAllocator = 28 LargeMmapAllocator<typename PrimaryAllocator::MapUnmapCallback, 29 LargeMmapAllocatorPtrArray, 30 typename PrimaryAllocator::AddressSpaceView>; 31 32 void InitLinkerInitialized(s32 release_to_os_interval_ms, 33 uptr heap_start = 0) { 34 primary_.Init(release_to_os_interval_ms, heap_start); 35 secondary_.InitLinkerInitialized(); 36 } 37 38 void Init(s32 release_to_os_interval_ms, uptr heap_start = 0) { 39 stats_.Init(); 40 primary_.Init(release_to_os_interval_ms, heap_start); 41 secondary_.Init(); 42 } 43 Allocate(AllocatorCache * cache,uptr size,uptr alignment)44 void *Allocate(AllocatorCache *cache, uptr size, uptr alignment) { 45 // Returning 0 on malloc(0) may break a lot of code. 46 if (size == 0) 47 size = 1; 48 if (size + alignment < size) { 49 Report("WARNING: %s: CombinedAllocator allocation overflow: " 50 "0x%zx bytes with 0x%zx alignment requested\n", 51 SanitizerToolName, size, alignment); 52 return nullptr; 53 } 54 uptr original_size = size; 55 // If alignment requirements are to be fulfilled by the frontend allocator 56 // rather than by the primary or secondary, passing an alignment lower than 57 // or equal to 8 will prevent any further rounding up, as well as the later 58 // alignment check. 59 if (alignment > 8) 60 size = RoundUpTo(size, alignment); 61 // The primary allocator should return a 2^x aligned allocation when 62 // requested 2^x bytes, hence using the rounded up 'size' when being 63 // serviced by the primary (this is no longer true when the primary is 64 // using a non-fixed base address). The secondary takes care of the 65 // alignment without such requirement, and allocating 'size' would use 66 // extraneous memory, so we employ 'original_size'. 67 void *res; 68 if (primary_.CanAllocate(size, alignment)) 69 res = cache->Allocate(&primary_, primary_.ClassID(size)); 70 else 71 res = secondary_.Allocate(&stats_, original_size, alignment); 72 if (alignment > 8) 73 CHECK_EQ(reinterpret_cast<uptr>(res) & (alignment - 1), 0); 74 return res; 75 } 76 ReleaseToOSIntervalMs()77 s32 ReleaseToOSIntervalMs() const { 78 return primary_.ReleaseToOSIntervalMs(); 79 } 80 SetReleaseToOSIntervalMs(s32 release_to_os_interval_ms)81 void SetReleaseToOSIntervalMs(s32 release_to_os_interval_ms) { 82 primary_.SetReleaseToOSIntervalMs(release_to_os_interval_ms); 83 } 84 ForceReleaseToOS()85 void ForceReleaseToOS() { 86 primary_.ForceReleaseToOS(); 87 } 88 Deallocate(AllocatorCache * cache,void * p)89 void Deallocate(AllocatorCache *cache, void *p) { 90 if (!p) return; 91 if (primary_.PointerIsMine(p)) 92 cache->Deallocate(&primary_, primary_.GetSizeClass(p), p); 93 else 94 secondary_.Deallocate(&stats_, p); 95 } 96 Reallocate(AllocatorCache * cache,void * p,uptr new_size,uptr alignment)97 void *Reallocate(AllocatorCache *cache, void *p, uptr new_size, 98 uptr alignment) { 99 if (!p) 100 return Allocate(cache, new_size, alignment); 101 if (!new_size) { 102 Deallocate(cache, p); 103 return nullptr; 104 } 105 CHECK(PointerIsMine(p)); 106 uptr old_size = GetActuallyAllocatedSize(p); 107 uptr memcpy_size = Min(new_size, old_size); 108 void *new_p = Allocate(cache, new_size, alignment); 109 if (new_p) 110 internal_memcpy(new_p, p, memcpy_size); 111 Deallocate(cache, p); 112 return new_p; 113 } 114 PointerIsMine(const void * p)115 bool PointerIsMine(const void *p) const { 116 if (primary_.PointerIsMine(p)) 117 return true; 118 return secondary_.PointerIsMine(p); 119 } 120 FromPrimary(const void * p)121 bool FromPrimary(const void *p) const { return primary_.PointerIsMine(p); } 122 GetMetaData(const void * p)123 void *GetMetaData(const void *p) { 124 if (primary_.PointerIsMine(p)) 125 return primary_.GetMetaData(p); 126 return secondary_.GetMetaData(p); 127 } 128 GetBlockBegin(const void * p)129 void *GetBlockBegin(const void *p) { 130 if (primary_.PointerIsMine(p)) 131 return primary_.GetBlockBegin(p); 132 return secondary_.GetBlockBegin(p); 133 } 134 135 // This function does the same as GetBlockBegin, but is much faster. 136 // Must be called with the allocator locked. GetBlockBeginFastLocked(const void * p)137 void *GetBlockBeginFastLocked(const void *p) { 138 if (primary_.PointerIsMine(p)) 139 return primary_.GetBlockBegin(p); 140 return secondary_.GetBlockBeginFastLocked(p); 141 } 142 GetActuallyAllocatedSize(void * p)143 uptr GetActuallyAllocatedSize(void *p) { 144 if (primary_.PointerIsMine(p)) 145 return primary_.GetActuallyAllocatedSize(p); 146 return secondary_.GetActuallyAllocatedSize(p); 147 } 148 TotalMemoryUsed()149 uptr TotalMemoryUsed() { 150 return primary_.TotalMemoryUsed() + secondary_.TotalMemoryUsed(); 151 } 152 TestOnlyUnmap()153 void TestOnlyUnmap() { primary_.TestOnlyUnmap(); } 154 InitCache(AllocatorCache * cache)155 void InitCache(AllocatorCache *cache) { 156 cache->Init(&stats_); 157 } 158 DestroyCache(AllocatorCache * cache)159 void DestroyCache(AllocatorCache *cache) { 160 cache->Destroy(&primary_, &stats_); 161 } 162 SwallowCache(AllocatorCache * cache)163 void SwallowCache(AllocatorCache *cache) { 164 cache->Drain(&primary_); 165 } 166 GetStats(AllocatorStatCounters s)167 void GetStats(AllocatorStatCounters s) const { 168 stats_.Get(s); 169 } 170 PrintStats()171 void PrintStats() { 172 primary_.PrintStats(); 173 secondary_.PrintStats(); 174 } 175 176 // ForceLock() and ForceUnlock() are needed to implement Darwin malloc zone 177 // introspection API. ForceLock()178 void ForceLock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { 179 primary_.ForceLock(); 180 secondary_.ForceLock(); 181 } 182 ForceUnlock()183 void ForceUnlock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { 184 secondary_.ForceUnlock(); 185 primary_.ForceUnlock(); 186 } 187 188 // Iterate over all existing chunks. 189 // The allocator must be locked when calling this function. ForEachChunk(ForEachChunkCallback callback,void * arg)190 void ForEachChunk(ForEachChunkCallback callback, void *arg) { 191 primary_.ForEachChunk(callback, arg); 192 secondary_.ForEachChunk(callback, arg); 193 } 194 195 private: 196 PrimaryAllocator primary_; 197 SecondaryAllocator secondary_; 198 AllocatorGlobalStats stats_; 199 }; 200