blob: 9d5741ef57506595a08b541ea5bf6bfc1093f019 [file] [log] [blame]
Austin Schuh745610d2015-09-06 18:19:50 -07001// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
2// Copyright (c) 2005, Google Inc.
3// All rights reserved.
Brian Silverman20350ac2021-11-17 18:19:55 -08004//
Austin Schuh745610d2015-09-06 18:19:50 -07005// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
Brian Silverman20350ac2021-11-17 18:19:55 -08008//
Austin Schuh745610d2015-09-06 18:19:50 -07009// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
Brian Silverman20350ac2021-11-17 18:19:55 -080018//
Austin Schuh745610d2015-09-06 18:19:50 -070019// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31// ---
32// Author: Sanjay Ghemawat <opensource@google.com>
33
34#include <config.h>
35
36// Disable the glibc prototype of mremap(), as older versions of the
37// system headers define this function with only four arguments,
38// whereas newer versions allow an optional fifth argument:
39#ifdef HAVE_MMAP
40# define mremap glibc_mremap
41# include <sys/mman.h>
42# undef mremap
43#endif
44
45#include <stddef.h>
46#ifdef HAVE_STDINT_H
47#include <stdint.h>
48#endif
49#include <algorithm>
50#include "base/logging.h"
51#include "base/spinlock.h"
Brian Silverman20350ac2021-11-17 18:19:55 -080052#include "maybe_emergency_malloc.h"
Austin Schuh745610d2015-09-06 18:19:50 -070053#include "maybe_threads.h"
54#include "malloc_hook-inl.h"
55#include <gperftools/malloc_hook.h>
56
57// This #ifdef should almost never be set. Set NO_TCMALLOC_SAMPLES if
58// you're porting to a system where you really can't get a stacktrace.
59#ifdef NO_TCMALLOC_SAMPLES
60 // We use #define so code compiles even if you #include stacktrace.h somehow.
61# define GetStackTrace(stack, depth, skip) (0)
62#else
63# include <gperftools/stacktrace.h>
64#endif
65
66// __THROW is defined in glibc systems. It means, counter-intuitively,
67// "This function will never throw an exception." It's an optional
68// optimization tool, but we may need to use it to match glibc prototypes.
69#ifndef __THROW // I guess we're not on a glibc system
70# define __THROW // __THROW is just an optimization, so ok to make it ""
71#endif
72
73using std::copy;
74
75
76// Declaration of default weak initialization function, that can be overridden
77// by linking-in a strong definition (as heap-checker.cc does). This is
78// extern "C" so that it doesn't trigger gold's --detect-odr-violations warning,
79// which only looks at C++ symbols.
80//
81// This function is declared here as weak, and defined later, rather than a more
82// straightforward simple weak definition, as a workround for an icc compiler
83// issue ((Intel reference 290819). This issue causes icc to resolve weak
84// symbols too early, at compile rather than link time. By declaring it (weak)
85// here, then defining it below after its use, we can avoid the problem.
86extern "C" {
87ATTRIBUTE_WEAK void MallocHook_InitAtFirstAllocation_HeapLeakChecker();
88}
89
90namespace {
91
92void RemoveInitialHooksAndCallInitializers(); // below.
93
94pthread_once_t once = PTHREAD_ONCE_INIT;
95
96// These hooks are installed in MallocHook as the only initial hooks. The first
97// hook that is called will run RemoveInitialHooksAndCallInitializers (see the
98// definition below) and then redispatch to any malloc hooks installed by
99// RemoveInitialHooksAndCallInitializers.
100//
101// Note(llib): there is a possibility of a race in the event that there are
102// multiple threads running before the first allocation. This is pretty
103// difficult to achieve, but if it is then multiple threads may concurrently do
104// allocations. The first caller will call
105// RemoveInitialHooksAndCallInitializers via one of the initial hooks. A
106// concurrent allocation may, depending on timing either:
107// * still have its initial malloc hook installed, run that and block on waiting
108// for the first caller to finish its call to
109// RemoveInitialHooksAndCallInitializers, and proceed normally.
110// * occur some time during the RemoveInitialHooksAndCallInitializers call, at
111// which point there could be no initial hooks and the subsequent hooks that
112// are about to be set up by RemoveInitialHooksAndCallInitializers haven't
113// been installed yet. I think the worst we can get is that some allocations
114// will not get reported to some hooks set by the initializers called from
115// RemoveInitialHooksAndCallInitializers.
116
117void InitialNewHook(const void* ptr, size_t size) {
118 perftools_pthread_once(&once, &RemoveInitialHooksAndCallInitializers);
119 MallocHook::InvokeNewHook(ptr, size);
120}
121
122void InitialPreMMapHook(const void* start,
123 size_t size,
124 int protection,
125 int flags,
126 int fd,
127 off_t offset) {
128 perftools_pthread_once(&once, &RemoveInitialHooksAndCallInitializers);
129 MallocHook::InvokePreMmapHook(start, size, protection, flags, fd, offset);
130}
131
132void InitialPreSbrkHook(ptrdiff_t increment) {
133 perftools_pthread_once(&once, &RemoveInitialHooksAndCallInitializers);
134 MallocHook::InvokePreSbrkHook(increment);
135}
136
137// This function is called at most once by one of the above initial malloc
138// hooks. It removes all initial hooks and initializes all other clients that
139// want to get control at the very first memory allocation. The initializers
140// may assume that the initial malloc hooks have been removed. The initializers
141// may set up malloc hooks and allocate memory.
142void RemoveInitialHooksAndCallInitializers() {
143 RAW_CHECK(MallocHook::RemoveNewHook(&InitialNewHook), "");
144 RAW_CHECK(MallocHook::RemovePreMmapHook(&InitialPreMMapHook), "");
145 RAW_CHECK(MallocHook::RemovePreSbrkHook(&InitialPreSbrkHook), "");
146
147 // HeapLeakChecker is currently the only module that needs to get control on
148 // the first memory allocation, but one can add other modules by following the
149 // same weak/strong function pattern.
150 MallocHook_InitAtFirstAllocation_HeapLeakChecker();
151}
152
153} // namespace
154
155// Weak default initialization function that must go after its use.
156extern "C" void MallocHook_InitAtFirstAllocation_HeapLeakChecker() {
157 // Do nothing.
158}
159
160namespace base { namespace internal {
161
162// This lock is shared between all implementations of HookList::Add & Remove.
163// The potential for contention is very small. This needs to be a SpinLock and
164// not a Mutex since it's possible for Mutex locking to allocate memory (e.g.,
165// per-thread allocation in debug builds), which could cause infinite recursion.
166static SpinLock hooklist_spinlock(base::LINKER_INITIALIZED);
167
168template <typename T>
169bool HookList<T>::Add(T value_as_t) {
170 AtomicWord value = bit_cast<AtomicWord>(value_as_t);
171 if (value == 0) {
172 return false;
173 }
174 SpinLockHolder l(&hooklist_spinlock);
175 // Find the first slot in data that is 0.
176 int index = 0;
177 while ((index < kHookListMaxValues) &&
178 (base::subtle::NoBarrier_Load(&priv_data[index]) != 0)) {
179 ++index;
180 }
181 if (index == kHookListMaxValues) {
182 return false;
183 }
184 AtomicWord prev_num_hooks = base::subtle::Acquire_Load(&priv_end);
185 base::subtle::NoBarrier_Store(&priv_data[index], value);
186 if (prev_num_hooks <= index) {
187 base::subtle::NoBarrier_Store(&priv_end, index + 1);
188 }
189 return true;
190}
191
192template <typename T>
193void HookList<T>::FixupPrivEndLocked() {
194 AtomicWord hooks_end = base::subtle::NoBarrier_Load(&priv_end);
195 while ((hooks_end > 0) &&
196 (base::subtle::NoBarrier_Load(&priv_data[hooks_end - 1]) == 0)) {
197 --hooks_end;
198 }
199 base::subtle::NoBarrier_Store(&priv_end, hooks_end);
200}
201
202template <typename T>
203bool HookList<T>::Remove(T value_as_t) {
204 if (value_as_t == 0) {
205 return false;
206 }
207 SpinLockHolder l(&hooklist_spinlock);
208 AtomicWord hooks_end = base::subtle::NoBarrier_Load(&priv_end);
209 int index = 0;
210 while (index < hooks_end && value_as_t != bit_cast<T>(
211 base::subtle::NoBarrier_Load(&priv_data[index]))) {
212 ++index;
213 }
214 if (index == hooks_end) {
215 return false;
216 }
217 base::subtle::NoBarrier_Store(&priv_data[index], 0);
218 FixupPrivEndLocked();
219 return true;
220}
221
222template <typename T>
223int HookList<T>::Traverse(T* output_array, int n) const {
224 AtomicWord hooks_end = base::subtle::Acquire_Load(&priv_end);
225 int actual_hooks_end = 0;
226 for (int i = 0; i < hooks_end && n > 0; ++i) {
227 AtomicWord data = base::subtle::Acquire_Load(&priv_data[i]);
228 if (data != 0) {
229 *output_array++ = bit_cast<T>(data);
230 ++actual_hooks_end;
231 --n;
232 }
233 }
234 return actual_hooks_end;
235}
236
237template <typename T>
238T HookList<T>::ExchangeSingular(T value_as_t) {
239 AtomicWord value = bit_cast<AtomicWord>(value_as_t);
240 AtomicWord old_value;
241 SpinLockHolder l(&hooklist_spinlock);
242 old_value = base::subtle::NoBarrier_Load(&priv_data[kHookListSingularIdx]);
243 base::subtle::NoBarrier_Store(&priv_data[kHookListSingularIdx], value);
244 if (value != 0) {
245 base::subtle::NoBarrier_Store(&priv_end, kHookListSingularIdx + 1);
246 } else {
247 FixupPrivEndLocked();
248 }
249 return bit_cast<T>(old_value);
250}
251
252// Initialize a HookList (optionally with the given initial_value in index 0).
253#define INIT_HOOK_LIST { 0 }
254#define INIT_HOOK_LIST_WITH_VALUE(initial_value) \
255 { 1, { reinterpret_cast<AtomicWord>(initial_value) } }
256
257// Explicit instantiation for malloc_hook_test.cc. This ensures all the methods
258// are instantiated.
259template struct HookList<MallocHook::NewHook>;
260
261HookList<MallocHook::NewHook> new_hooks_ =
262 INIT_HOOK_LIST_WITH_VALUE(&InitialNewHook);
263HookList<MallocHook::DeleteHook> delete_hooks_ = INIT_HOOK_LIST;
264HookList<MallocHook::PreMmapHook> premmap_hooks_ =
265 INIT_HOOK_LIST_WITH_VALUE(&InitialPreMMapHook);
266HookList<MallocHook::MmapHook> mmap_hooks_ = INIT_HOOK_LIST;
267HookList<MallocHook::MunmapHook> munmap_hooks_ = INIT_HOOK_LIST;
268HookList<MallocHook::MremapHook> mremap_hooks_ = INIT_HOOK_LIST;
269HookList<MallocHook::PreSbrkHook> presbrk_hooks_ =
270 INIT_HOOK_LIST_WITH_VALUE(InitialPreSbrkHook);
271HookList<MallocHook::SbrkHook> sbrk_hooks_ = INIT_HOOK_LIST;
272
273// These lists contain either 0 or 1 hooks.
274HookList<MallocHook::MmapReplacement> mmap_replacement_ = { 0 };
275HookList<MallocHook::MunmapReplacement> munmap_replacement_ = { 0 };
276
277#undef INIT_HOOK_LIST_WITH_VALUE
278#undef INIT_HOOK_LIST
279
280} } // namespace base::internal
281
282using base::internal::kHookListMaxValues;
283using base::internal::new_hooks_;
284using base::internal::delete_hooks_;
285using base::internal::premmap_hooks_;
286using base::internal::mmap_hooks_;
287using base::internal::mmap_replacement_;
288using base::internal::munmap_hooks_;
289using base::internal::munmap_replacement_;
290using base::internal::mremap_hooks_;
291using base::internal::presbrk_hooks_;
292using base::internal::sbrk_hooks_;
293
294// These are available as C bindings as well as C++, hence their
295// definition outside the MallocHook class.
296extern "C"
297int MallocHook_AddNewHook(MallocHook_NewHook hook) {
298 RAW_VLOG(10, "AddNewHook(%p)", hook);
299 return new_hooks_.Add(hook);
300}
301
302extern "C"
303int MallocHook_RemoveNewHook(MallocHook_NewHook hook) {
304 RAW_VLOG(10, "RemoveNewHook(%p)", hook);
305 return new_hooks_.Remove(hook);
306}
307
308extern "C"
309int MallocHook_AddDeleteHook(MallocHook_DeleteHook hook) {
310 RAW_VLOG(10, "AddDeleteHook(%p)", hook);
311 return delete_hooks_.Add(hook);
312}
313
314extern "C"
315int MallocHook_RemoveDeleteHook(MallocHook_DeleteHook hook) {
316 RAW_VLOG(10, "RemoveDeleteHook(%p)", hook);
317 return delete_hooks_.Remove(hook);
318}
319
320extern "C"
321int MallocHook_AddPreMmapHook(MallocHook_PreMmapHook hook) {
322 RAW_VLOG(10, "AddPreMmapHook(%p)", hook);
323 return premmap_hooks_.Add(hook);
324}
325
326extern "C"
327int MallocHook_RemovePreMmapHook(MallocHook_PreMmapHook hook) {
328 RAW_VLOG(10, "RemovePreMmapHook(%p)", hook);
329 return premmap_hooks_.Remove(hook);
330}
331
332extern "C"
333int MallocHook_SetMmapReplacement(MallocHook_MmapReplacement hook) {
334 RAW_VLOG(10, "SetMmapReplacement(%p)", hook);
335 // NOTE this is a best effort CHECK. Concurrent sets could succeed since
336 // this test is outside of the Add spin lock.
337 RAW_CHECK(mmap_replacement_.empty(), "Only one MMapReplacement is allowed.");
338 return mmap_replacement_.Add(hook);
339}
340
341extern "C"
342int MallocHook_RemoveMmapReplacement(MallocHook_MmapReplacement hook) {
343 RAW_VLOG(10, "RemoveMmapReplacement(%p)", hook);
344 return mmap_replacement_.Remove(hook);
345}
346
347extern "C"
348int MallocHook_AddMmapHook(MallocHook_MmapHook hook) {
349 RAW_VLOG(10, "AddMmapHook(%p)", hook);
350 return mmap_hooks_.Add(hook);
351}
352
353extern "C"
354int MallocHook_RemoveMmapHook(MallocHook_MmapHook hook) {
355 RAW_VLOG(10, "RemoveMmapHook(%p)", hook);
356 return mmap_hooks_.Remove(hook);
357}
358
359extern "C"
360int MallocHook_AddMunmapHook(MallocHook_MunmapHook hook) {
361 RAW_VLOG(10, "AddMunmapHook(%p)", hook);
362 return munmap_hooks_.Add(hook);
363}
364
365extern "C"
366int MallocHook_RemoveMunmapHook(MallocHook_MunmapHook hook) {
367 RAW_VLOG(10, "RemoveMunmapHook(%p)", hook);
368 return munmap_hooks_.Remove(hook);
369}
370
371extern "C"
372int MallocHook_SetMunmapReplacement(MallocHook_MunmapReplacement hook) {
373 RAW_VLOG(10, "SetMunmapReplacement(%p)", hook);
374 // NOTE this is a best effort CHECK. Concurrent sets could succeed since
375 // this test is outside of the Add spin lock.
376 RAW_CHECK(munmap_replacement_.empty(),
377 "Only one MunmapReplacement is allowed.");
378 return munmap_replacement_.Add(hook);
379}
380
381extern "C"
382int MallocHook_RemoveMunmapReplacement(MallocHook_MunmapReplacement hook) {
383 RAW_VLOG(10, "RemoveMunmapReplacement(%p)", hook);
384 return munmap_replacement_.Remove(hook);
385}
386
387extern "C"
388int MallocHook_AddMremapHook(MallocHook_MremapHook hook) {
389 RAW_VLOG(10, "AddMremapHook(%p)", hook);
390 return mremap_hooks_.Add(hook);
391}
392
393extern "C"
394int MallocHook_RemoveMremapHook(MallocHook_MremapHook hook) {
395 RAW_VLOG(10, "RemoveMremapHook(%p)", hook);
396 return mremap_hooks_.Remove(hook);
397}
398
399extern "C"
400int MallocHook_AddPreSbrkHook(MallocHook_PreSbrkHook hook) {
401 RAW_VLOG(10, "AddPreSbrkHook(%p)", hook);
402 return presbrk_hooks_.Add(hook);
403}
404
405extern "C"
406int MallocHook_RemovePreSbrkHook(MallocHook_PreSbrkHook hook) {
407 RAW_VLOG(10, "RemovePreSbrkHook(%p)", hook);
408 return presbrk_hooks_.Remove(hook);
409}
410
411extern "C"
412int MallocHook_AddSbrkHook(MallocHook_SbrkHook hook) {
413 RAW_VLOG(10, "AddSbrkHook(%p)", hook);
414 return sbrk_hooks_.Add(hook);
415}
416
417extern "C"
418int MallocHook_RemoveSbrkHook(MallocHook_SbrkHook hook) {
419 RAW_VLOG(10, "RemoveSbrkHook(%p)", hook);
420 return sbrk_hooks_.Remove(hook);
421}
422
423// The code below is DEPRECATED.
424extern "C"
425MallocHook_NewHook MallocHook_SetNewHook(MallocHook_NewHook hook) {
426 RAW_VLOG(10, "SetNewHook(%p)", hook);
427 return new_hooks_.ExchangeSingular(hook);
428}
429
430extern "C"
431MallocHook_DeleteHook MallocHook_SetDeleteHook(MallocHook_DeleteHook hook) {
432 RAW_VLOG(10, "SetDeleteHook(%p)", hook);
433 return delete_hooks_.ExchangeSingular(hook);
434}
435
436extern "C"
437MallocHook_PreMmapHook MallocHook_SetPreMmapHook(MallocHook_PreMmapHook hook) {
438 RAW_VLOG(10, "SetPreMmapHook(%p)", hook);
439 return premmap_hooks_.ExchangeSingular(hook);
440}
441
442extern "C"
443MallocHook_MmapHook MallocHook_SetMmapHook(MallocHook_MmapHook hook) {
444 RAW_VLOG(10, "SetMmapHook(%p)", hook);
445 return mmap_hooks_.ExchangeSingular(hook);
446}
447
448extern "C"
449MallocHook_MunmapHook MallocHook_SetMunmapHook(MallocHook_MunmapHook hook) {
450 RAW_VLOG(10, "SetMunmapHook(%p)", hook);
451 return munmap_hooks_.ExchangeSingular(hook);
452}
453
454extern "C"
455MallocHook_MremapHook MallocHook_SetMremapHook(MallocHook_MremapHook hook) {
456 RAW_VLOG(10, "SetMremapHook(%p)", hook);
457 return mremap_hooks_.ExchangeSingular(hook);
458}
459
460extern "C"
461MallocHook_PreSbrkHook MallocHook_SetPreSbrkHook(MallocHook_PreSbrkHook hook) {
462 RAW_VLOG(10, "SetPreSbrkHook(%p)", hook);
463 return presbrk_hooks_.ExchangeSingular(hook);
464}
465
466extern "C"
467MallocHook_SbrkHook MallocHook_SetSbrkHook(MallocHook_SbrkHook hook) {
468 RAW_VLOG(10, "SetSbrkHook(%p)", hook);
469 return sbrk_hooks_.ExchangeSingular(hook);
470}
471// End of DEPRECATED code section.
472
473// Note: embedding the function calls inside the traversal of HookList would be
474// very confusing, as it is legal for a hook to remove itself and add other
475// hooks. Doing traversal first, and then calling the hooks ensures we only
476// call the hooks registered at the start.
477#define INVOKE_HOOKS(HookType, hook_list, args) do { \
478 HookType hooks[kHookListMaxValues]; \
479 int num_hooks = hook_list.Traverse(hooks, kHookListMaxValues); \
480 for (int i = 0; i < num_hooks; ++i) { \
481 (*hooks[i])args; \
482 } \
483 } while (0)
484
485// There should only be one replacement. Return the result of the first
486// one, or false if there is none.
487#define INVOKE_REPLACEMENT(HookType, hook_list, args) do { \
488 HookType hooks[kHookListMaxValues]; \
489 int num_hooks = hook_list.Traverse(hooks, kHookListMaxValues); \
490 return (num_hooks > 0 && (*hooks[0])args); \
491 } while (0)
492
493
494void MallocHook::InvokeNewHookSlow(const void* p, size_t s) {
Brian Silverman20350ac2021-11-17 18:19:55 -0800495 if (tcmalloc::IsEmergencyPtr(p)) {
496 return;
497 }
Austin Schuh745610d2015-09-06 18:19:50 -0700498 INVOKE_HOOKS(NewHook, new_hooks_, (p, s));
499}
500
501void MallocHook::InvokeDeleteHookSlow(const void* p) {
Brian Silverman20350ac2021-11-17 18:19:55 -0800502 if (tcmalloc::IsEmergencyPtr(p)) {
503 return;
504 }
Austin Schuh745610d2015-09-06 18:19:50 -0700505 INVOKE_HOOKS(DeleteHook, delete_hooks_, (p));
506}
507
508void MallocHook::InvokePreMmapHookSlow(const void* start,
509 size_t size,
510 int protection,
511 int flags,
512 int fd,
513 off_t offset) {
514 INVOKE_HOOKS(PreMmapHook, premmap_hooks_, (start, size, protection, flags, fd,
515 offset));
516}
517
518void MallocHook::InvokeMmapHookSlow(const void* result,
519 const void* start,
520 size_t size,
521 int protection,
522 int flags,
523 int fd,
524 off_t offset) {
525 INVOKE_HOOKS(MmapHook, mmap_hooks_, (result, start, size, protection, flags,
526 fd, offset));
527}
528
529bool MallocHook::InvokeMmapReplacementSlow(const void* start,
530 size_t size,
531 int protection,
532 int flags,
533 int fd,
534 off_t offset,
535 void** result) {
536 INVOKE_REPLACEMENT(MmapReplacement, mmap_replacement_,
537 (start, size, protection, flags, fd, offset, result));
538}
539
540void MallocHook::InvokeMunmapHookSlow(const void* p, size_t s) {
541 INVOKE_HOOKS(MunmapHook, munmap_hooks_, (p, s));
542}
543
544bool MallocHook::InvokeMunmapReplacementSlow(const void* p,
545 size_t s,
546 int* result) {
547 INVOKE_REPLACEMENT(MunmapReplacement, munmap_replacement_, (p, s, result));
548}
549
550void MallocHook::InvokeMremapHookSlow(const void* result,
551 const void* old_addr,
552 size_t old_size,
553 size_t new_size,
554 int flags,
555 const void* new_addr) {
556 INVOKE_HOOKS(MremapHook, mremap_hooks_, (result, old_addr, old_size, new_size,
557 flags, new_addr));
558}
559
560void MallocHook::InvokePreSbrkHookSlow(ptrdiff_t increment) {
561 INVOKE_HOOKS(PreSbrkHook, presbrk_hooks_, (increment));
562}
563
564void MallocHook::InvokeSbrkHookSlow(const void* result, ptrdiff_t increment) {
565 INVOKE_HOOKS(SbrkHook, sbrk_hooks_, (result, increment));
566}
567
568#undef INVOKE_HOOKS
569
Brian Silverman20350ac2021-11-17 18:19:55 -0800570#ifndef NO_TCMALLOC_SAMPLES
571
Austin Schuh745610d2015-09-06 18:19:50 -0700572DEFINE_ATTRIBUTE_SECTION_VARS(google_malloc);
573DECLARE_ATTRIBUTE_SECTION_VARS(google_malloc);
574 // actual functions are in debugallocation.cc or tcmalloc.cc
575DEFINE_ATTRIBUTE_SECTION_VARS(malloc_hook);
576DECLARE_ATTRIBUTE_SECTION_VARS(malloc_hook);
577 // actual functions are in this file, malloc_hook.cc, and low_level_alloc.cc
578
579#define ADDR_IN_ATTRIBUTE_SECTION(addr, name) \
580 (reinterpret_cast<uintptr_t>(ATTRIBUTE_SECTION_START(name)) <= \
581 reinterpret_cast<uintptr_t>(addr) && \
582 reinterpret_cast<uintptr_t>(addr) < \
583 reinterpret_cast<uintptr_t>(ATTRIBUTE_SECTION_STOP(name)))
584
585// Return true iff 'caller' is a return address within a function
586// that calls one of our hooks via MallocHook:Invoke*.
587// A helper for GetCallerStackTrace.
588static inline bool InHookCaller(const void* caller) {
589 return ADDR_IN_ATTRIBUTE_SECTION(caller, google_malloc) ||
590 ADDR_IN_ATTRIBUTE_SECTION(caller, malloc_hook);
591 // We can use one section for everything except tcmalloc_or_debug
592 // due to its special linkage mode, which prevents merging of the sections.
593}
594
595#undef ADDR_IN_ATTRIBUTE_SECTION
596
597static bool checked_sections = false;
598
599static inline void CheckInHookCaller() {
600 if (!checked_sections) {
601 INIT_ATTRIBUTE_SECTION_VARS(google_malloc);
602 if (ATTRIBUTE_SECTION_START(google_malloc) ==
603 ATTRIBUTE_SECTION_STOP(google_malloc)) {
604 RAW_LOG(ERROR, "google_malloc section is missing, "
605 "thus InHookCaller is broken!");
606 }
607 INIT_ATTRIBUTE_SECTION_VARS(malloc_hook);
608 if (ATTRIBUTE_SECTION_START(malloc_hook) ==
609 ATTRIBUTE_SECTION_STOP(malloc_hook)) {
610 RAW_LOG(ERROR, "malloc_hook section is missing, "
611 "thus InHookCaller is broken!");
612 }
613 checked_sections = true;
614 }
615}
616
Brian Silverman20350ac2021-11-17 18:19:55 -0800617#endif // !NO_TCMALLOC_SAMPLES
618
Austin Schuh745610d2015-09-06 18:19:50 -0700619// We can improve behavior/compactness of this function
620// if we pass a generic test function (with a generic arg)
621// into the implementations for GetStackTrace instead of the skip_count.
622extern "C" int MallocHook_GetCallerStackTrace(void** result, int max_depth,
623 int skip_count) {
624#if defined(NO_TCMALLOC_SAMPLES)
625 return 0;
626#elif !defined(HAVE_ATTRIBUTE_SECTION_START)
627 // Fall back to GetStackTrace and good old but fragile frame skip counts.
628 // Note: this path is inaccurate when a hook is not called directly by an
629 // allocation function but is daisy-chained through another hook,
630 // search for MallocHook::(Get|Set|Invoke)* to find such cases.
631 return GetStackTrace(result, max_depth, skip_count + int(DEBUG_MODE));
632 // due to -foptimize-sibling-calls in opt mode
633 // there's no need for extra frame skip here then
634#else
635 CheckInHookCaller();
636 // MallocHook caller determination via InHookCaller works, use it:
637 static const int kMaxSkip = 32 + 6 + 3;
638 // Constant tuned to do just one GetStackTrace call below in practice
639 // and not get many frames that we don't actually need:
640 // currently max passsed max_depth is 32,
641 // max passed/needed skip_count is 6
642 // and 3 is to account for some hook daisy chaining.
643 static const int kStackSize = kMaxSkip + 1;
644 void* stack[kStackSize];
645 int depth = GetStackTrace(stack, kStackSize, 1); // skip this function frame
646 if (depth == 0) // silenty propagate cases when GetStackTrace does not work
647 return 0;
648 for (int i = 0; i < depth; ++i) { // stack[0] is our immediate caller
649 if (InHookCaller(stack[i])) {
Brian Silverman20350ac2021-11-17 18:19:55 -0800650 // fast-path to slow-path calls may be implemented by compiler
651 // as non-tail calls. Causing two functions on stack trace to be
652 // inside google_malloc. In such case we're skipping to
653 // outermost such frame since this is where malloc stack frames
654 // really start.
655 while (i + 1 < depth && InHookCaller(stack[i+1])) {
656 i++;
657 }
Austin Schuh745610d2015-09-06 18:19:50 -0700658 RAW_VLOG(10, "Found hooked allocator at %d: %p <- %p",
659 i, stack[i], stack[i+1]);
660 i += 1; // skip hook caller frame
661 depth -= i; // correct depth
662 if (depth > max_depth) depth = max_depth;
663 copy(stack + i, stack + i + depth, result);
664 if (depth < max_depth && depth + i == kStackSize) {
665 // get frames for the missing depth
666 depth +=
667 GetStackTrace(result + depth, max_depth - depth, 1 + kStackSize);
668 }
669 return depth;
670 }
671 }
672 RAW_LOG(WARNING, "Hooked allocator frame not found, returning empty trace");
673 // If this happens try increasing kMaxSkip
674 // or else something must be wrong with InHookCaller,
675 // e.g. for every section used in InHookCaller
676 // all functions in that section must be inside the same library.
677 return 0;
678#endif
679}
680
681// On systems where we know how, we override mmap/munmap/mremap/sbrk
682// to provide support for calling the related hooks (in addition,
683// of course, to doing what these functions normally do).
684
685#if defined(__linux)
686# include "malloc_hook_mmap_linux.h"
687
688#elif defined(__FreeBSD__)
689# include "malloc_hook_mmap_freebsd.h"
690
691#else
692
693/*static*/void* MallocHook::UnhookedMMap(void *start, size_t length, int prot,
694 int flags, int fd, off_t offset) {
695 void* result;
696 if (!MallocHook::InvokeMmapReplacement(
697 start, length, prot, flags, fd, offset, &result)) {
698 result = mmap(start, length, prot, flags, fd, offset);
699 }
700 return result;
701}
702
703/*static*/int MallocHook::UnhookedMUnmap(void *start, size_t length) {
704 int result;
705 if (!MallocHook::InvokeMunmapReplacement(start, length, &result)) {
706 result = munmap(start, length);
707 }
708 return result;
709}
710
711#endif