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