1010#include < stdlib.h>
1111#include < string.h>
1212#include " codeCache.h"
13+ #include " guards.h"
1314#include " libraries.h"
1415#include " mallocTracer.h"
1516#include " os.h"
1920#include " tsc.h"
2021#include " vmEntry.h"
2122
22- #ifdef __clang__
23- # define NO_OPTIMIZE __attribute__ ((optnone))
24- #else
25- # define NO_OPTIMIZE __attribute__ ((optimize(" -fno-omit-frame-pointer,-fno-optimize-sibling-calls" )))
26- #endif
27-
2823#define SAVE_IMPORT (FUNC ) \
2924 do { \
3025 void ** _entry = lib->findImport (im_##FUNC ); \
3126 if (_entry != NULL ) _orig_##FUNC = (decltype (_orig_##FUNC ))*_entry; \
3227 } while (0 )
3328
3429static void * (*_orig_malloc)(size_t );
35- static void (*_orig_free)(void *);
3630static void * (*_orig_calloc)(size_t , size_t );
3731static void * (*_orig_realloc)(void *, size_t );
3832static int (*_orig_posix_memalign)(void **, size_t , size_t );
3933static void * (*_orig_aligned_alloc)(size_t , size_t );
4034
4135// Inline helper to avoid repeating the running+ret+size guard in each hook.
36+ // CriticalSection prevents reentrancy: profiler-internal allocations triggered
37+ // inside recordMalloc (e.g. sample buffer allocation) re-enter these hooks via
38+ // the patched GOT; without the guard they would be double-counted.
39+ // Acquiring the CS here also blocks concurrent same-thread timer samples
40+ // (SIGPROF/SIGVTALRM) for the duration of recordMalloc; this is acceptable
41+ // because the window is short.
4242static inline void maybeRecord (void * ret, size_t size) {
4343 if (MallocTracer::running () && ret && size) {
44- MallocTracer::recordMalloc (ret, size);
44+ CriticalSection cs;
45+ if (cs.entered ()) {
46+ MallocTracer::recordMalloc (ret, size);
47+ }
4548 }
4649}
4750
@@ -53,41 +56,38 @@ extern "C" void* malloc_hook(size_t size) {
5356
5457extern " C" void * calloc_hook (size_t num, size_t size) {
5558 void * ret = _orig_calloc (num, size);
56- // ret != NULL guarantees no overflow per POSIX, so num * size is safe.
57- if (MallocTracer::running () && ret && num && size) {
58- MallocTracer::recordMalloc (ret, num * size);
59+ // num * size may wrap on size_t, but the wrapped value is only forwarded
60+ // to maybeRecord, which discards it when ret == NULL (the libc returns
61+ // NULL on overflow per POSIX).
62+ if (num && size) {
63+ maybeRecord (ret, num * size);
5964 }
6065 return ret;
6166}
6267
63- // Make sure this is not optimized away (function-scoped -fno-optimize-sibling-calls)
64- extern " C" NO_OPTIMIZE
65- void * calloc_hook_dummy (size_t num, size_t size) {
66- return _orig_calloc (num, size);
67- }
68-
6968extern " C" void * realloc_hook (void * addr, size_t size) {
7069 void * ret = _orig_realloc (addr, size);
71- if (MallocTracer::running () && ret != NULL && size > 0 ) {
72- MallocTracer::recordMalloc (ret, size);
73- }
70+ // Record every successful realloc, regardless of whether addr was NULL.
71+ // Without a free hook we cannot subtract the prior allocation, so a
72+ // realloc that grows an existing buffer is double-counted against the
73+ // original malloc when both were sampled. This is benign for the leak
74+ // detector: the prior sample (if any) ages out as the freed address is
75+ // never seen again, and missing the realloc entirely would leave a
76+ // phantom live allocation on the freed old address.
77+ maybeRecord (ret, size);
7478 return ret;
7579}
7680
7781extern " C" int posix_memalign_hook (void ** memptr, size_t alignment, size_t size) {
7882 int ret = _orig_posix_memalign (memptr, alignment, size);
79- if (MallocTracer::running () && ret == 0 && memptr && *memptr && size) {
80- MallocTracer::recordMalloc (*memptr, size);
83+ if (ret == 0 && memptr) {
84+ // POSIX guarantees *memptr is set to the allocated block when ret == 0;
85+ // maybeRecord is the sole NULL/size gate for non-conforming libc.
86+ maybeRecord (*memptr, size);
8187 }
8288 return ret;
8389}
8490
85- // Make sure this is not optimized away (function-scoped -fno-optimize-sibling-calls)
86- extern " C" NO_OPTIMIZE
87- int posix_memalign_hook_dummy (void ** memptr, size_t alignment, size_t size) {
88- return _orig_posix_memalign (memptr, alignment, size);
89- }
90-
9191extern " C" void * aligned_alloc_hook (size_t alignment, size_t size) {
9292 void * ret = _orig_aligned_alloc (alignment, size);
9393 maybeRecord (ret, size);
@@ -106,9 +106,6 @@ PidController MallocTracer::_pid(MallocTracer::TARGET_SAMPLES_PER_WINDOW,
106106Mutex MallocHooker::_patch_lock;
107107int MallocHooker::_patched_libs = 0 ;
108108bool MallocHooker::_initialized = false ;
109- void * MallocHooker::_calloc_hook_fn = nullptr ;
110- void * MallocHooker::_posix_memalign_hook_fn = nullptr ;
111-
112109// xoroshiro128+ PRNG state — shared, relaxed atomics.
113110// Benign races are acceptable: occasional duplicate output is harmless
114111// for a sampling PRNG and thread_local cannot be used on the malloc path.
@@ -165,7 +162,7 @@ void MallocHooker::detectNestedMalloc() {
165162 void * pm_probe = NULL ;
166163 _orig_posix_memalign (&pm_probe, sizeof (void *), sizeof (void *));
167164 _current_thread = pthread_t (0 );
168- if (pm_probe != NULL ) _orig_free (pm_probe);
165+ if (pm_probe != NULL ) free (pm_probe);
169166
170167 // Restore original aligned_alloc so libc doesn't carry the probe hook.
171168 libc->patchImport (im_aligned_alloc, (void *)_orig_aligned_alloc);
@@ -219,26 +216,19 @@ bool MallocHooker::initialize() {
219216 resolveMallocSymbols ();
220217
221218 SAVE_IMPORT (malloc);
222- SAVE_IMPORT (free);
223219 SAVE_IMPORT (calloc);
224220 SAVE_IMPORT (realloc);
225221 SAVE_IMPORT (posix_memalign);
226222 SAVE_IMPORT (aligned_alloc);
227223
228224 detectNestedMalloc ();
229225
230- // Pre-compute hook pointers so patchLibraries() avoids repeated conditionals.
231- _calloc_hook_fn = _nested_malloc ? (void *)calloc_hook_dummy : (void *)calloc_hook;
232- _posix_memalign_hook_fn = _nested_posix_memalign ? (void *)posix_memalign_hook_dummy : (void *)posix_memalign_hook;
233-
234226 lib->mark (
235227 [](const char * s) -> bool {
236228 return strcmp (s, " malloc_hook" ) == 0
237229 || strcmp (s, " calloc_hook" ) == 0
238- || strcmp (s, " calloc_hook_dummy" ) == 0
239230 || strcmp (s, " realloc_hook" ) == 0
240231 || strcmp (s, " posix_memalign_hook" ) == 0
241- || strcmp (s, " posix_memalign_hook_dummy" ) == 0
242232 || strcmp (s, " aligned_alloc_hook" ) == 0 ;
243233 },
244234 MARK_ASYNC_PROFILER );
@@ -248,15 +238,10 @@ bool MallocHooker::initialize() {
248238 return _orig_malloc != NULL ;
249239}
250240
251- // To avoid complexity in hooking and tracking reentrancy, a TLS-based approach is not used.
252- // Reentrant allocation calls would result in double-accounting. However, this does not impact
253- // the leak detector, as it correctly tracks memory as freed regardless of how many times
254- // recordMalloc is called with the same address.
255241void MallocHooker::patchLibraries () {
256- // If initialize() hasn't resolved _orig_malloc yet, advancing _patched_libs here
257- // would consume library slots without patching them, causing a later real call
258- // (from MallocTracer::start) to find _patched_libs == native_lib_count and skip
259- // all libraries. This happens when dlopen_hook fires during a non-nativemem session.
242+ // Defensive guard: _orig_malloc is set by initialize(), which runs in
243+ // MallocTracer::start() before _running is set and this path is reached.
244+ // Guards against stale or unexpected direct calls.
260245 if (_orig_malloc == NULL ) return ;
261246
262247 MutexLocker ml (_patch_lock);
@@ -283,8 +268,10 @@ void MallocHooker::patchLibraries() {
283268 if (_orig_malloc) cc->patchImport (im_malloc, (void *)malloc_hook);
284269 if (_orig_realloc) cc->patchImport (im_realloc, (void *)realloc_hook);
285270 if (_orig_aligned_alloc) cc->patchImport (im_aligned_alloc, (void *)aligned_alloc_hook);
286- if (_orig_calloc) cc->patchImport (im_calloc, _calloc_hook_fn);
287- if (_orig_posix_memalign) cc->patchImport (im_posix_memalign, _posix_memalign_hook_fn);
271+ // On musl, calloc/posix_memalign delegate to malloc/aligned_alloc internally;
272+ // hooking them too would double-count. Leave the GOT entry untouched instead.
273+ if (_orig_calloc && !_nested_malloc) cc->patchImport (im_calloc, (void *)calloc_hook);
274+ if (_orig_posix_memalign && !_nested_posix_memalign) cc->patchImport (im_posix_memalign, (void *)posix_memalign_hook);
288275 }
289276}
290277
0 commit comments