1 //===-- sanitizer_allocator_secondary.h -------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // Part of the Sanitizer Allocator. 11 // 12 //===----------------------------------------------------------------------===// 13 #ifndef SANITIZER_ALLOCATOR_H 14 #error This file must be included inside sanitizer_allocator.h 15 #endif 16 17 // Fixed array to store LargeMmapAllocator chunks list, limited to 32K total 18 // allocated chunks. To be used in memory constrained or not memory hungry cases 19 // (currently, 32 bits and internal allocator). 20 class LargeMmapAllocatorPtrArrayStatic { 21 public: 22 INLINE void *Init() { return &p_[0]; } 23 INLINE void EnsureSpace(uptr n) { CHECK_LT(n, kMaxNumChunks); } 24 private: 25 static const int kMaxNumChunks = 1 << 15; 26 uptr p_[kMaxNumChunks]; 27 }; 28 29 // Much less restricted LargeMmapAllocator chunks list (comparing to 30 // PtrArrayStatic). Backed by mmaped memory region and can hold up to 1M chunks. 31 // ReservedAddressRange was used instead of just MAP_NORESERVE to achieve the 32 // same functionality in Fuchsia case, which does not support MAP_NORESERVE. 33 class LargeMmapAllocatorPtrArrayDynamic { 34 public: 35 INLINE void *Init() { 36 uptr p = address_range_.Init(kMaxNumChunks * sizeof(uptr), 37 SecondaryAllocatorName); 38 CHECK(p); 39 return reinterpret_cast<void*>(p); 40 } 41 42 INLINE void EnsureSpace(uptr n) { 43 CHECK_LT(n, kMaxNumChunks); 44 DCHECK(n <= n_reserved_); 45 if (UNLIKELY(n == n_reserved_)) { 46 address_range_.MapOrDie( 47 reinterpret_cast<uptr>(address_range_.base()) + 48 n_reserved_ * sizeof(uptr), 49 kChunksBlockCount * sizeof(uptr)); 50 n_reserved_ += kChunksBlockCount; 51 } 52 } 53 54 private: 55 static const int kMaxNumChunks = 1 << 20; 56 static const int kChunksBlockCount = 1 << 14; 57 ReservedAddressRange address_range_; 58 uptr n_reserved_; 59 }; 60 61 #if SANITIZER_WORDSIZE == 32 62 typedef LargeMmapAllocatorPtrArrayStatic DefaultLargeMmapAllocatorPtrArray; 63 #else 64 typedef LargeMmapAllocatorPtrArrayDynamic DefaultLargeMmapAllocatorPtrArray; 65 #endif 66 67 // This class can (de)allocate only large chunks of memory using mmap/unmap. 68 // The main purpose of this allocator is to cover large and rare allocation 69 // sizes not covered by more efficient allocators (e.g. SizeClassAllocator64). 70 template <class MapUnmapCallback = NoOpMapUnmapCallback, 71 class PtrArrayT = DefaultLargeMmapAllocatorPtrArray, 72 class AddressSpaceViewTy = LocalAddressSpaceView> 73 class LargeMmapAllocator { 74 public: 75 using AddressSpaceView = AddressSpaceViewTy; 76 void InitLinkerInitialized() { 77 page_size_ = GetPageSizeCached(); 78 chunks_ = reinterpret_cast<Header**>(ptr_array_.Init()); 79 } 80 81 void Init() { 82 internal_memset(this, 0, sizeof(*this)); 83 InitLinkerInitialized(); 84 } 85 86 void *Allocate(AllocatorStats *stat, uptr size, uptr alignment) { 87 CHECK(IsPowerOfTwo(alignment)); 88 uptr map_size = RoundUpMapSize(size); 89 if (alignment > page_size_) 90 map_size += alignment; 91 // Overflow. 92 if (map_size < size) { 93 Report("WARNING: %s: LargeMmapAllocator allocation overflow: " 94 "0x%zx bytes with 0x%zx alignment requested\n", 95 SanitizerToolName, map_size, alignment); 96 return nullptr; 97 } 98 uptr map_beg = reinterpret_cast<uptr>( 99 MmapOrDieOnFatalError(map_size, SecondaryAllocatorName)); 100 if (!map_beg) 101 return nullptr; 102 CHECK(IsAligned(map_beg, page_size_)); 103 MapUnmapCallback().OnMap(map_beg, map_size); 104 uptr map_end = map_beg + map_size; 105 uptr res = map_beg + page_size_; 106 if (res & (alignment - 1)) // Align. 107 res += alignment - (res & (alignment - 1)); 108 CHECK(IsAligned(res, alignment)); 109 CHECK(IsAligned(res, page_size_)); 110 CHECK_GE(res + size, map_beg); 111 CHECK_LE(res + size, map_end); 112 Header *h = GetHeader(res); 113 h->size = size; 114 h->map_beg = map_beg; 115 h->map_size = map_size; 116 uptr size_log = MostSignificantSetBitIndex(map_size); 117 CHECK_LT(size_log, ARRAY_SIZE(stats.by_size_log)); 118 { 119 SpinMutexLock l(&mutex_); 120 ptr_array_.EnsureSpace(n_chunks_); 121 uptr idx = n_chunks_++; 122 h->chunk_idx = idx; 123 chunks_[idx] = h; 124 chunks_sorted_ = false; 125 stats.n_allocs++; 126 stats.currently_allocated += map_size; 127 stats.max_allocated = Max(stats.max_allocated, stats.currently_allocated); 128 stats.by_size_log[size_log]++; 129 stat->Add(AllocatorStatAllocated, map_size); 130 stat->Add(AllocatorStatMapped, map_size); 131 } 132 return reinterpret_cast<void*>(res); 133 } 134 135 void Deallocate(AllocatorStats *stat, void *p) { 136 Header *h = GetHeader(p); 137 { 138 SpinMutexLock l(&mutex_); 139 uptr idx = h->chunk_idx; 140 CHECK_EQ(chunks_[idx], h); 141 CHECK_LT(idx, n_chunks_); 142 chunks_[idx] = chunks_[--n_chunks_]; 143 chunks_[idx]->chunk_idx = idx; 144 chunks_sorted_ = false; 145 stats.n_frees++; 146 stats.currently_allocated -= h->map_size; 147 stat->Sub(AllocatorStatAllocated, h->map_size); 148 stat->Sub(AllocatorStatMapped, h->map_size); 149 } 150 MapUnmapCallback().OnUnmap(h->map_beg, h->map_size); 151 UnmapOrDie(reinterpret_cast<void*>(h->map_beg), h->map_size); 152 } 153 154 uptr TotalMemoryUsed() { 155 SpinMutexLock l(&mutex_); 156 uptr res = 0; 157 for (uptr i = 0; i < n_chunks_; i++) { 158 Header *h = chunks_[i]; 159 CHECK_EQ(h->chunk_idx, i); 160 res += RoundUpMapSize(h->size); 161 } 162 return res; 163 } 164 165 bool PointerIsMine(const void *p) { 166 return GetBlockBegin(p) != nullptr; 167 } 168 169 uptr GetActuallyAllocatedSize(void *p) { 170 return RoundUpTo(GetHeader(p)->size, page_size_); 171 } 172 173 // At least page_size_/2 metadata bytes is available. 174 void *GetMetaData(const void *p) { 175 // Too slow: CHECK_EQ(p, GetBlockBegin(p)); 176 if (!IsAligned(reinterpret_cast<uptr>(p), page_size_)) { 177 Printf("%s: bad pointer %p\n", SanitizerToolName, p); 178 CHECK(IsAligned(reinterpret_cast<uptr>(p), page_size_)); 179 } 180 return GetHeader(p) + 1; 181 } 182 183 void *GetBlockBegin(const void *ptr) { 184 uptr p = reinterpret_cast<uptr>(ptr); 185 SpinMutexLock l(&mutex_); 186 uptr nearest_chunk = 0; 187 // Cache-friendly linear search. 188 for (uptr i = 0; i < n_chunks_; i++) { 189 uptr ch = reinterpret_cast<uptr>(chunks_[i]); 190 if (p < ch) continue; // p is at left to this chunk, skip it. 191 if (p - ch < p - nearest_chunk) 192 nearest_chunk = ch; 193 } 194 if (!nearest_chunk) 195 return nullptr; 196 Header *h = reinterpret_cast<Header *>(nearest_chunk); 197 CHECK_GE(nearest_chunk, h->map_beg); 198 CHECK_LT(nearest_chunk, h->map_beg + h->map_size); 199 CHECK_LE(nearest_chunk, p); 200 if (h->map_beg + h->map_size <= p) 201 return nullptr; 202 return GetUser(h); 203 } 204 205 void EnsureSortedChunks() { 206 if (chunks_sorted_) return; 207 Header **chunks = AddressSpaceView::LoadWritable(chunks_, n_chunks_); 208 Sort(reinterpret_cast<uptr *>(chunks), n_chunks_); 209 for (uptr i = 0; i < n_chunks_; i++) 210 AddressSpaceView::LoadWritable(chunks[i])->chunk_idx = i; 211 chunks_sorted_ = true; 212 } 213 214 // This function does the same as GetBlockBegin, but is much faster. 215 // Must be called with the allocator locked. 216 void *GetBlockBeginFastLocked(void *ptr) { 217 mutex_.CheckLocked(); 218 uptr p = reinterpret_cast<uptr>(ptr); 219 uptr n = n_chunks_; 220 if (!n) return nullptr; 221 EnsureSortedChunks(); 222 auto min_mmap_ = reinterpret_cast<uptr>(chunks_[0]); 223 auto max_mmap_ = 224 reinterpret_cast<uptr>(chunks_[n - 1]) + chunks_[n - 1]->map_size; 225 if (p < min_mmap_ || p >= max_mmap_) 226 return nullptr; 227 uptr beg = 0, end = n - 1; 228 // This loop is a log(n) lower_bound. It does not check for the exact match 229 // to avoid expensive cache-thrashing loads. 230 while (end - beg >= 2) { 231 uptr mid = (beg + end) / 2; // Invariant: mid >= beg + 1 232 if (p < reinterpret_cast<uptr>(chunks_[mid])) 233 end = mid - 1; // We are not interested in chunks_[mid]. 234 else 235 beg = mid; // chunks_[mid] may still be what we want. 236 } 237 238 if (beg < end) { 239 CHECK_EQ(beg + 1, end); 240 // There are 2 chunks left, choose one. 241 if (p >= reinterpret_cast<uptr>(chunks_[end])) 242 beg = end; 243 } 244 245 Header *h = chunks_[beg]; 246 if (h->map_beg + h->map_size <= p || p < h->map_beg) 247 return nullptr; 248 return GetUser(h); 249 } 250 251 void PrintStats() { 252 Printf("Stats: LargeMmapAllocator: allocated %zd times, " 253 "remains %zd (%zd K) max %zd M; by size logs: ", 254 stats.n_allocs, stats.n_allocs - stats.n_frees, 255 stats.currently_allocated >> 10, stats.max_allocated >> 20); 256 for (uptr i = 0; i < ARRAY_SIZE(stats.by_size_log); i++) { 257 uptr c = stats.by_size_log[i]; 258 if (!c) continue; 259 Printf("%zd:%zd; ", i, c); 260 } 261 Printf("\n"); 262 } 263 264 // ForceLock() and ForceUnlock() are needed to implement Darwin malloc zone 265 // introspection API. 266 void ForceLock() { 267 mutex_.Lock(); 268 } 269 270 void ForceUnlock() { 271 mutex_.Unlock(); 272 } 273 274 // Iterate over all existing chunks. 275 // The allocator must be locked when calling this function. 276 void ForEachChunk(ForEachChunkCallback callback, void *arg) { 277 EnsureSortedChunks(); // Avoid doing the sort while iterating. 278 const Header *const *chunks = AddressSpaceView::Load(chunks_, n_chunks_); 279 for (uptr i = 0; i < n_chunks_; i++) { 280 const Header *t = chunks[i]; 281 callback(reinterpret_cast<uptr>(GetUser(t)), arg); 282 // Consistency check: verify that the array did not change. 283 CHECK_EQ(chunks[i], t); 284 CHECK_EQ(AddressSpaceView::Load(chunks[i])->chunk_idx, i); 285 } 286 } 287 288 private: 289 struct Header { 290 uptr map_beg; 291 uptr map_size; 292 uptr size; 293 uptr chunk_idx; 294 }; 295 296 Header *GetHeader(uptr p) { 297 CHECK(IsAligned(p, page_size_)); 298 return reinterpret_cast<Header*>(p - page_size_); 299 } 300 Header *GetHeader(const void *p) { 301 return GetHeader(reinterpret_cast<uptr>(p)); 302 } 303 304 void *GetUser(const Header *h) { 305 CHECK(IsAligned((uptr)h, page_size_)); 306 return reinterpret_cast<void*>(reinterpret_cast<uptr>(h) + page_size_); 307 } 308 309 uptr RoundUpMapSize(uptr size) { 310 return RoundUpTo(size, page_size_) + page_size_; 311 } 312 313 uptr page_size_; 314 Header **chunks_; 315 PtrArrayT ptr_array_; 316 uptr n_chunks_; 317 bool chunks_sorted_; 318 struct Stats { 319 uptr n_allocs, n_frees, currently_allocated, max_allocated, by_size_log[64]; 320 } stats; 321 StaticSpinMutex mutex_; 322 }; 323