1 //===-- memprof_allocator.cpp --------------------------------------------===//
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 // This file is a part of MemProfiler, a memory profiler.
10 //
11 // Implementation of MemProf's memory allocator, which uses the allocator
12 // from sanitizer_common.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #include "memprof_allocator.h"
17 #include "memprof_mapping.h"
18 #include "memprof_meminfoblock.h"
19 #include "memprof_mibmap.h"
20 #include "memprof_stack.h"
21 #include "memprof_thread.h"
22 #include "sanitizer_common/sanitizer_allocator_checks.h"
23 #include "sanitizer_common/sanitizer_allocator_interface.h"
24 #include "sanitizer_common/sanitizer_allocator_report.h"
25 #include "sanitizer_common/sanitizer_errno.h"
26 #include "sanitizer_common/sanitizer_file.h"
27 #include "sanitizer_common/sanitizer_flags.h"
28 #include "sanitizer_common/sanitizer_internal_defs.h"
29 #include "sanitizer_common/sanitizer_list.h"
30 #include "sanitizer_common/sanitizer_stackdepot.h"
31 
32 #include <sched.h>
33 #include <time.h>
34 
35 namespace __memprof {
36 
37 static int GetCpuId(void) {
38   // _memprof_preinit is called via the preinit_array, which subsequently calls
39   // malloc. Since this is before _dl_init calls VDSO_SETUP, sched_getcpu
40   // will seg fault as the address of __vdso_getcpu will be null.
41   if (!memprof_init_done)
42     return -1;
43   return sched_getcpu();
44 }
45 
46 // Compute the timestamp in ms.
47 static int GetTimestamp(void) {
48   // timespec_get will segfault if called from dl_init
49   if (!memprof_timestamp_inited) {
50     // By returning 0, this will be effectively treated as being
51     // timestamped at memprof init time (when memprof_init_timestamp_s
52     // is initialized).
53     return 0;
54   }
55   timespec ts;
56   clock_gettime(CLOCK_REALTIME, &ts);
57   return (ts.tv_sec - memprof_init_timestamp_s) * 1000 + ts.tv_nsec / 1000000;
58 }
59 
60 static MemprofAllocator &get_allocator();
61 
62 // The memory chunk allocated from the underlying allocator looks like this:
63 // H H U U U U U U
64 //   H -- ChunkHeader (32 bytes)
65 //   U -- user memory.
66 
67 // If there is left padding before the ChunkHeader (due to use of memalign),
68 // we store a magic value in the first uptr word of the memory block and
69 // store the address of ChunkHeader in the next uptr.
70 // M B L L L L L L L L L  H H U U U U U U
71 //   |                    ^
72 //   ---------------------|
73 //   M -- magic value kAllocBegMagic
74 //   B -- address of ChunkHeader pointing to the first 'H'
75 
76 constexpr uptr kMaxAllowedMallocBits = 40;
77 
78 // Should be no more than 32-bytes
79 struct ChunkHeader {
80   // 1-st 4 bytes.
81   u32 alloc_context_id;
82   // 2-nd 4 bytes
83   u32 cpu_id;
84   // 3-rd 4 bytes
85   u32 timestamp_ms;
86   // 4-th 4 bytes
87   // Note only 1 bit is needed for this flag if we need space in the future for
88   // more fields.
89   u32 from_memalign;
90   // 5-th and 6-th 4 bytes
91   // The max size of an allocation is 2^40 (kMaxAllowedMallocSize), so this
92   // could be shrunk to kMaxAllowedMallocBits if we need space in the future for
93   // more fields.
94   atomic_uint64_t user_requested_size;
95   // 23 bits available
96   // 7-th and 8-th 4 bytes
97   u64 data_type_id; // TODO: hash of type name
98 };
99 
100 static const uptr kChunkHeaderSize = sizeof(ChunkHeader);
101 COMPILER_CHECK(kChunkHeaderSize == 32);
102 
103 struct MemprofChunk : ChunkHeader {
104   uptr Beg() { return reinterpret_cast<uptr>(this) + kChunkHeaderSize; }
105   uptr UsedSize() {
106     return atomic_load(&user_requested_size, memory_order_relaxed);
107   }
108   void *AllocBeg() {
109     if (from_memalign)
110       return get_allocator().GetBlockBegin(reinterpret_cast<void *>(this));
111     return reinterpret_cast<void *>(this);
112   }
113 };
114 
115 class LargeChunkHeader {
116   static constexpr uptr kAllocBegMagic =
117       FIRST_32_SECOND_64(0xCC6E96B9, 0xCC6E96B9CC6E96B9ULL);
118   atomic_uintptr_t magic;
119   MemprofChunk *chunk_header;
120 
121 public:
122   MemprofChunk *Get() const {
123     return atomic_load(&magic, memory_order_acquire) == kAllocBegMagic
124                ? chunk_header
125                : nullptr;
126   }
127 
128   void Set(MemprofChunk *p) {
129     if (p) {
130       chunk_header = p;
131       atomic_store(&magic, kAllocBegMagic, memory_order_release);
132       return;
133     }
134 
135     uptr old = kAllocBegMagic;
136     if (!atomic_compare_exchange_strong(&magic, &old, 0,
137                                         memory_order_release)) {
138       CHECK_EQ(old, kAllocBegMagic);
139     }
140   }
141 };
142 
143 void FlushUnneededMemProfShadowMemory(uptr p, uptr size) {
144   // Since memprof's mapping is compacting, the shadow chunk may be
145   // not page-aligned, so we only flush the page-aligned portion.
146   ReleaseMemoryPagesToOS(MemToShadow(p), MemToShadow(p + size));
147 }
148 
149 void MemprofMapUnmapCallback::OnMap(uptr p, uptr size) const {
150   // Statistics.
151   MemprofStats &thread_stats = GetCurrentThreadStats();
152   thread_stats.mmaps++;
153   thread_stats.mmaped += size;
154 }
155 void MemprofMapUnmapCallback::OnUnmap(uptr p, uptr size) const {
156   // We are about to unmap a chunk of user memory.
157   // Mark the corresponding shadow memory as not needed.
158   FlushUnneededMemProfShadowMemory(p, size);
159   // Statistics.
160   MemprofStats &thread_stats = GetCurrentThreadStats();
161   thread_stats.munmaps++;
162   thread_stats.munmaped += size;
163 }
164 
165 AllocatorCache *GetAllocatorCache(MemprofThreadLocalMallocStorage *ms) {
166   CHECK(ms);
167   return &ms->allocator_cache;
168 }
169 
170 // Accumulates the access count from the shadow for the given pointer and size.
171 u64 GetShadowCount(uptr p, u32 size) {
172   u64 *shadow = (u64 *)MEM_TO_SHADOW(p);
173   u64 *shadow_end = (u64 *)MEM_TO_SHADOW(p + size);
174   u64 count = 0;
175   for (; shadow <= shadow_end; shadow++)
176     count += *shadow;
177   return count;
178 }
179 
180 // Clears the shadow counters (when memory is allocated).
181 void ClearShadow(uptr addr, uptr size) {
182   CHECK(AddrIsAlignedByGranularity(addr));
183   CHECK(AddrIsInMem(addr));
184   CHECK(AddrIsAlignedByGranularity(addr + size));
185   CHECK(AddrIsInMem(addr + size - SHADOW_GRANULARITY));
186   CHECK(REAL(memset));
187   uptr shadow_beg = MEM_TO_SHADOW(addr);
188   uptr shadow_end = MEM_TO_SHADOW(addr + size - SHADOW_GRANULARITY) + 1;
189   if (shadow_end - shadow_beg < common_flags()->clear_shadow_mmap_threshold) {
190     REAL(memset)((void *)shadow_beg, 0, shadow_end - shadow_beg);
191   } else {
192     uptr page_size = GetPageSizeCached();
193     uptr page_beg = RoundUpTo(shadow_beg, page_size);
194     uptr page_end = RoundDownTo(shadow_end, page_size);
195 
196     if (page_beg >= page_end) {
197       REAL(memset)((void *)shadow_beg, 0, shadow_end - shadow_beg);
198     } else {
199       if (page_beg != shadow_beg) {
200         REAL(memset)((void *)shadow_beg, 0, page_beg - shadow_beg);
201       }
202       if (page_end != shadow_end) {
203         REAL(memset)((void *)page_end, 0, shadow_end - page_end);
204       }
205       ReserveShadowMemoryRange(page_beg, page_end - 1, nullptr);
206     }
207   }
208 }
209 
210 struct Allocator {
211   static const uptr kMaxAllowedMallocSize = 1ULL << kMaxAllowedMallocBits;
212 
213   MemprofAllocator allocator;
214   StaticSpinMutex fallback_mutex;
215   AllocatorCache fallback_allocator_cache;
216 
217   uptr max_user_defined_malloc_size;
218   atomic_uint8_t rss_limit_exceeded;
219 
220   // Holds the mapping of stack ids to MemInfoBlocks.
221   MIBMapTy MIBMap;
222 
223   bool destructing;
224   bool constructed = false;
225 
226   // ------------------- Initialization ------------------------
227   explicit Allocator(LinkerInitialized)
228       : destructing(false), constructed(true) {}
229   ~Allocator() { FinishAndPrint(); }
230 
231   static void PrintCallback(const uptr Key, LockedMemInfoBlock *const &Value,
232                             void *Arg) {
233     SpinMutexLock(&Value->mutex);
234     Value->mib.Print(Key, bool(Arg));
235   }
236 
237   void FinishAndPrint() {
238     if (common_flags()->print_module_map)
239       DumpProcessMap();
240     if (!flags()->print_terse)
241       Printf("Live on exit:\n");
242     allocator.ForceLock();
243     allocator.ForEachChunk(
244         [](uptr chunk, void *alloc) {
245           u64 user_requested_size;
246           Allocator *A = (Allocator *)alloc;
247           MemprofChunk *m =
248               A->GetMemprofChunk((void *)chunk, user_requested_size);
249           if (!m)
250             return;
251           uptr user_beg = ((uptr)m) + kChunkHeaderSize;
252           u64 c = GetShadowCount(user_beg, user_requested_size);
253           long curtime = GetTimestamp();
254           MemInfoBlock newMIB(user_requested_size, c, m->timestamp_ms, curtime,
255                               m->cpu_id, GetCpuId());
256           InsertOrMerge(m->alloc_context_id, newMIB, A->MIBMap);
257         },
258         this);
259 
260     destructing = true;
261     MIBMap.ForEach(PrintCallback,
262                    reinterpret_cast<void *>(flags()->print_terse));
263     StackDepotPrintAll();
264     allocator.ForceUnlock();
265   }
266 
267   void InitLinkerInitialized() {
268     SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
269     allocator.InitLinkerInitialized(
270         common_flags()->allocator_release_to_os_interval_ms);
271     max_user_defined_malloc_size = common_flags()->max_allocation_size_mb
272                                        ? common_flags()->max_allocation_size_mb
273                                              << 20
274                                        : kMaxAllowedMallocSize;
275   }
276 
277   bool RssLimitExceeded() {
278     return atomic_load(&rss_limit_exceeded, memory_order_relaxed);
279   }
280 
281   void SetRssLimitExceeded(bool limit_exceeded) {
282     atomic_store(&rss_limit_exceeded, limit_exceeded, memory_order_relaxed);
283   }
284 
285   // -------------------- Allocation/Deallocation routines ---------------
286   void *Allocate(uptr size, uptr alignment, BufferedStackTrace *stack,
287                  AllocType alloc_type) {
288     if (UNLIKELY(!memprof_inited))
289       MemprofInitFromRtl();
290     if (RssLimitExceeded()) {
291       if (AllocatorMayReturnNull())
292         return nullptr;
293       ReportRssLimitExceeded(stack);
294     }
295     CHECK(stack);
296     const uptr min_alignment = MEMPROF_ALIGNMENT;
297     if (alignment < min_alignment)
298       alignment = min_alignment;
299     if (size == 0) {
300       // We'd be happy to avoid allocating memory for zero-size requests, but
301       // some programs/tests depend on this behavior and assume that malloc
302       // would not return NULL even for zero-size allocations. Moreover, it
303       // looks like operator new should never return NULL, and results of
304       // consecutive "new" calls must be different even if the allocated size
305       // is zero.
306       size = 1;
307     }
308     CHECK(IsPowerOfTwo(alignment));
309     uptr rounded_size = RoundUpTo(size, alignment);
310     uptr needed_size = rounded_size + kChunkHeaderSize;
311     if (alignment > min_alignment)
312       needed_size += alignment;
313     CHECK(IsAligned(needed_size, min_alignment));
314     if (size > kMaxAllowedMallocSize || needed_size > kMaxAllowedMallocSize ||
315         size > max_user_defined_malloc_size) {
316       if (AllocatorMayReturnNull()) {
317         Report("WARNING: MemProfiler failed to allocate 0x%zx bytes\n", size);
318         return nullptr;
319       }
320       uptr malloc_limit =
321           Min(kMaxAllowedMallocSize, max_user_defined_malloc_size);
322       ReportAllocationSizeTooBig(size, malloc_limit, stack);
323     }
324 
325     MemprofThread *t = GetCurrentThread();
326     void *allocated;
327     if (t) {
328       AllocatorCache *cache = GetAllocatorCache(&t->malloc_storage());
329       allocated = allocator.Allocate(cache, needed_size, 8);
330     } else {
331       SpinMutexLock l(&fallback_mutex);
332       AllocatorCache *cache = &fallback_allocator_cache;
333       allocated = allocator.Allocate(cache, needed_size, 8);
334     }
335     if (UNLIKELY(!allocated)) {
336       SetAllocatorOutOfMemory();
337       if (AllocatorMayReturnNull())
338         return nullptr;
339       ReportOutOfMemory(size, stack);
340     }
341 
342     uptr alloc_beg = reinterpret_cast<uptr>(allocated);
343     uptr alloc_end = alloc_beg + needed_size;
344     uptr beg_plus_header = alloc_beg + kChunkHeaderSize;
345     uptr user_beg = beg_plus_header;
346     if (!IsAligned(user_beg, alignment))
347       user_beg = RoundUpTo(user_beg, alignment);
348     uptr user_end = user_beg + size;
349     CHECK_LE(user_end, alloc_end);
350     uptr chunk_beg = user_beg - kChunkHeaderSize;
351     MemprofChunk *m = reinterpret_cast<MemprofChunk *>(chunk_beg);
352     m->from_memalign = alloc_beg != chunk_beg;
353     CHECK(size);
354 
355     m->cpu_id = GetCpuId();
356     m->timestamp_ms = GetTimestamp();
357     m->alloc_context_id = StackDepotPut(*stack);
358 
359     uptr size_rounded_down_to_granularity =
360         RoundDownTo(size, SHADOW_GRANULARITY);
361     if (size_rounded_down_to_granularity)
362       ClearShadow(user_beg, size_rounded_down_to_granularity);
363 
364     MemprofStats &thread_stats = GetCurrentThreadStats();
365     thread_stats.mallocs++;
366     thread_stats.malloced += size;
367     thread_stats.malloced_overhead += needed_size - size;
368     if (needed_size > SizeClassMap::kMaxSize)
369       thread_stats.malloc_large++;
370     else
371       thread_stats.malloced_by_size[SizeClassMap::ClassID(needed_size)]++;
372 
373     void *res = reinterpret_cast<void *>(user_beg);
374     atomic_store(&m->user_requested_size, size, memory_order_release);
375     if (alloc_beg != chunk_beg) {
376       CHECK_LE(alloc_beg + sizeof(LargeChunkHeader), chunk_beg);
377       reinterpret_cast<LargeChunkHeader *>(alloc_beg)->Set(m);
378     }
379     MEMPROF_MALLOC_HOOK(res, size);
380     return res;
381   }
382 
383   void Deallocate(void *ptr, uptr delete_size, uptr delete_alignment,
384                   BufferedStackTrace *stack, AllocType alloc_type) {
385     uptr p = reinterpret_cast<uptr>(ptr);
386     if (p == 0)
387       return;
388 
389     MEMPROF_FREE_HOOK(ptr);
390 
391     uptr chunk_beg = p - kChunkHeaderSize;
392     MemprofChunk *m = reinterpret_cast<MemprofChunk *>(chunk_beg);
393 
394     u64 user_requested_size =
395         atomic_exchange(&m->user_requested_size, 0, memory_order_acquire);
396     if (memprof_inited && memprof_init_done && constructed && !destructing) {
397       u64 c = GetShadowCount(p, user_requested_size);
398       long curtime = GetTimestamp();
399 
400       MemInfoBlock newMIB(user_requested_size, c, m->timestamp_ms, curtime,
401                           m->cpu_id, GetCpuId());
402       InsertOrMerge(m->alloc_context_id, newMIB, MIBMap);
403     }
404 
405     MemprofStats &thread_stats = GetCurrentThreadStats();
406     thread_stats.frees++;
407     thread_stats.freed += user_requested_size;
408 
409     void *alloc_beg = m->AllocBeg();
410     if (alloc_beg != m) {
411       // Clear the magic value, as allocator internals may overwrite the
412       // contents of deallocated chunk, confusing GetMemprofChunk lookup.
413       reinterpret_cast<LargeChunkHeader *>(alloc_beg)->Set(nullptr);
414     }
415 
416     MemprofThread *t = GetCurrentThread();
417     if (t) {
418       AllocatorCache *cache = GetAllocatorCache(&t->malloc_storage());
419       allocator.Deallocate(cache, alloc_beg);
420     } else {
421       SpinMutexLock l(&fallback_mutex);
422       AllocatorCache *cache = &fallback_allocator_cache;
423       allocator.Deallocate(cache, alloc_beg);
424     }
425   }
426 
427   void *Reallocate(void *old_ptr, uptr new_size, BufferedStackTrace *stack) {
428     CHECK(old_ptr && new_size);
429     uptr p = reinterpret_cast<uptr>(old_ptr);
430     uptr chunk_beg = p - kChunkHeaderSize;
431     MemprofChunk *m = reinterpret_cast<MemprofChunk *>(chunk_beg);
432 
433     MemprofStats &thread_stats = GetCurrentThreadStats();
434     thread_stats.reallocs++;
435     thread_stats.realloced += new_size;
436 
437     void *new_ptr = Allocate(new_size, 8, stack, FROM_MALLOC);
438     if (new_ptr) {
439       CHECK_NE(REAL(memcpy), nullptr);
440       uptr memcpy_size = Min(new_size, m->UsedSize());
441       REAL(memcpy)(new_ptr, old_ptr, memcpy_size);
442       Deallocate(old_ptr, 0, 0, stack, FROM_MALLOC);
443     }
444     return new_ptr;
445   }
446 
447   void *Calloc(uptr nmemb, uptr size, BufferedStackTrace *stack) {
448     if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
449       if (AllocatorMayReturnNull())
450         return nullptr;
451       ReportCallocOverflow(nmemb, size, stack);
452     }
453     void *ptr = Allocate(nmemb * size, 8, stack, FROM_MALLOC);
454     // If the memory comes from the secondary allocator no need to clear it
455     // as it comes directly from mmap.
456     if (ptr && allocator.FromPrimary(ptr))
457       REAL(memset)(ptr, 0, nmemb * size);
458     return ptr;
459   }
460 
461   void CommitBack(MemprofThreadLocalMallocStorage *ms,
462                   BufferedStackTrace *stack) {
463     AllocatorCache *ac = GetAllocatorCache(ms);
464     allocator.SwallowCache(ac);
465   }
466 
467   // -------------------------- Chunk lookup ----------------------
468 
469   // Assumes alloc_beg == allocator.GetBlockBegin(alloc_beg).
470   MemprofChunk *GetMemprofChunk(void *alloc_beg, u64 &user_requested_size) {
471     if (!alloc_beg)
472       return nullptr;
473     MemprofChunk *p = reinterpret_cast<LargeChunkHeader *>(alloc_beg)->Get();
474     if (!p) {
475       if (!allocator.FromPrimary(alloc_beg))
476         return nullptr;
477       p = reinterpret_cast<MemprofChunk *>(alloc_beg);
478     }
479     // The size is reset to 0 on deallocation (and a min of 1 on
480     // allocation).
481     user_requested_size =
482         atomic_load(&p->user_requested_size, memory_order_acquire);
483     if (user_requested_size)
484       return p;
485     return nullptr;
486   }
487 
488   MemprofChunk *GetMemprofChunkByAddr(uptr p, u64 &user_requested_size) {
489     void *alloc_beg = allocator.GetBlockBegin(reinterpret_cast<void *>(p));
490     return GetMemprofChunk(alloc_beg, user_requested_size);
491   }
492 
493   uptr AllocationSize(uptr p) {
494     u64 user_requested_size;
495     MemprofChunk *m = GetMemprofChunkByAddr(p, user_requested_size);
496     if (!m)
497       return 0;
498     if (m->Beg() != p)
499       return 0;
500     return user_requested_size;
501   }
502 
503   void Purge(BufferedStackTrace *stack) { allocator.ForceReleaseToOS(); }
504 
505   void PrintStats() { allocator.PrintStats(); }
506 
507   void ForceLock() NO_THREAD_SAFETY_ANALYSIS {
508     allocator.ForceLock();
509     fallback_mutex.Lock();
510   }
511 
512   void ForceUnlock() NO_THREAD_SAFETY_ANALYSIS {
513     fallback_mutex.Unlock();
514     allocator.ForceUnlock();
515   }
516 };
517 
518 static Allocator instance(LINKER_INITIALIZED);
519 
520 static MemprofAllocator &get_allocator() { return instance.allocator; }
521 
522 void InitializeAllocator() { instance.InitLinkerInitialized(); }
523 
524 void MemprofThreadLocalMallocStorage::CommitBack() {
525   GET_STACK_TRACE_MALLOC;
526   instance.CommitBack(this, &stack);
527 }
528 
529 void PrintInternalAllocatorStats() { instance.PrintStats(); }
530 
531 void memprof_free(void *ptr, BufferedStackTrace *stack, AllocType alloc_type) {
532   instance.Deallocate(ptr, 0, 0, stack, alloc_type);
533 }
534 
535 void memprof_delete(void *ptr, uptr size, uptr alignment,
536                     BufferedStackTrace *stack, AllocType alloc_type) {
537   instance.Deallocate(ptr, size, alignment, stack, alloc_type);
538 }
539 
540 void *memprof_malloc(uptr size, BufferedStackTrace *stack) {
541   return SetErrnoOnNull(instance.Allocate(size, 8, stack, FROM_MALLOC));
542 }
543 
544 void *memprof_calloc(uptr nmemb, uptr size, BufferedStackTrace *stack) {
545   return SetErrnoOnNull(instance.Calloc(nmemb, size, stack));
546 }
547 
548 void *memprof_reallocarray(void *p, uptr nmemb, uptr size,
549                            BufferedStackTrace *stack) {
550   if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
551     errno = errno_ENOMEM;
552     if (AllocatorMayReturnNull())
553       return nullptr;
554     ReportReallocArrayOverflow(nmemb, size, stack);
555   }
556   return memprof_realloc(p, nmemb * size, stack);
557 }
558 
559 void *memprof_realloc(void *p, uptr size, BufferedStackTrace *stack) {
560   if (!p)
561     return SetErrnoOnNull(instance.Allocate(size, 8, stack, FROM_MALLOC));
562   if (size == 0) {
563     if (flags()->allocator_frees_and_returns_null_on_realloc_zero) {
564       instance.Deallocate(p, 0, 0, stack, FROM_MALLOC);
565       return nullptr;
566     }
567     // Allocate a size of 1 if we shouldn't free() on Realloc to 0
568     size = 1;
569   }
570   return SetErrnoOnNull(instance.Reallocate(p, size, stack));
571 }
572 
573 void *memprof_valloc(uptr size, BufferedStackTrace *stack) {
574   return SetErrnoOnNull(
575       instance.Allocate(size, GetPageSizeCached(), stack, FROM_MALLOC));
576 }
577 
578 void *memprof_pvalloc(uptr size, BufferedStackTrace *stack) {
579   uptr PageSize = GetPageSizeCached();
580   if (UNLIKELY(CheckForPvallocOverflow(size, PageSize))) {
581     errno = errno_ENOMEM;
582     if (AllocatorMayReturnNull())
583       return nullptr;
584     ReportPvallocOverflow(size, stack);
585   }
586   // pvalloc(0) should allocate one page.
587   size = size ? RoundUpTo(size, PageSize) : PageSize;
588   return SetErrnoOnNull(instance.Allocate(size, PageSize, stack, FROM_MALLOC));
589 }
590 
591 void *memprof_memalign(uptr alignment, uptr size, BufferedStackTrace *stack,
592                        AllocType alloc_type) {
593   if (UNLIKELY(!IsPowerOfTwo(alignment))) {
594     errno = errno_EINVAL;
595     if (AllocatorMayReturnNull())
596       return nullptr;
597     ReportInvalidAllocationAlignment(alignment, stack);
598   }
599   return SetErrnoOnNull(instance.Allocate(size, alignment, stack, alloc_type));
600 }
601 
602 void *memprof_aligned_alloc(uptr alignment, uptr size,
603                             BufferedStackTrace *stack) {
604   if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(alignment, size))) {
605     errno = errno_EINVAL;
606     if (AllocatorMayReturnNull())
607       return nullptr;
608     ReportInvalidAlignedAllocAlignment(size, alignment, stack);
609   }
610   return SetErrnoOnNull(instance.Allocate(size, alignment, stack, FROM_MALLOC));
611 }
612 
613 int memprof_posix_memalign(void **memptr, uptr alignment, uptr size,
614                            BufferedStackTrace *stack) {
615   if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) {
616     if (AllocatorMayReturnNull())
617       return errno_EINVAL;
618     ReportInvalidPosixMemalignAlignment(alignment, stack);
619   }
620   void *ptr = instance.Allocate(size, alignment, stack, FROM_MALLOC);
621   if (UNLIKELY(!ptr))
622     // OOM error is already taken care of by Allocate.
623     return errno_ENOMEM;
624   CHECK(IsAligned((uptr)ptr, alignment));
625   *memptr = ptr;
626   return 0;
627 }
628 
629 uptr memprof_malloc_usable_size(const void *ptr, uptr pc, uptr bp) {
630   if (!ptr)
631     return 0;
632   uptr usable_size = instance.AllocationSize(reinterpret_cast<uptr>(ptr));
633   return usable_size;
634 }
635 
636 void MemprofSoftRssLimitExceededCallback(bool limit_exceeded) {
637   instance.SetRssLimitExceeded(limit_exceeded);
638 }
639 
640 } // namespace __memprof
641 
642 // ---------------------- Interface ---------------- {{{1
643 using namespace __memprof;
644 
645 #if !SANITIZER_SUPPORTS_WEAK_HOOKS
646 // Provide default (no-op) implementation of malloc hooks.
647 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_malloc_hook, void *ptr,
648                              uptr size) {
649   (void)ptr;
650   (void)size;
651 }
652 
653 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_free_hook, void *ptr) {
654   (void)ptr;
655 }
656 #endif
657 
658 uptr __sanitizer_get_estimated_allocated_size(uptr size) { return size; }
659 
660 int __sanitizer_get_ownership(const void *p) {
661   return memprof_malloc_usable_size(p, 0, 0) != 0;
662 }
663 
664 uptr __sanitizer_get_allocated_size(const void *p) {
665   return memprof_malloc_usable_size(p, 0, 0);
666 }
667 
668 int __memprof_profile_dump() {
669   instance.FinishAndPrint();
670   // In the future we may want to return non-zero if there are any errors
671   // detected during the dumping process.
672   return 0;
673 }
674