Skip to content

Commit 0f996e8

Browse files
authored
Native malloc fixes and cleanup (#550)
1 parent 40be0dc commit 0f996e8

3 files changed

Lines changed: 39 additions & 52 deletions

File tree

ddprof-lib/src/main/cpp/libraries.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ void Libraries::refresh() {
6060
_dirty.store(false, std::memory_order_release);
6161
updateSymbols(false);
6262
LibraryPatcher::patch_sigaction();
63-
MallocTracer::installHooks();
63+
if (MallocTracer::running()) {
64+
MallocTracer::installHooks();
65+
}
6466
if (_remote_symbolication) {
6567
updateBuildIds();
6668
}

ddprof-lib/src/main/cpp/mallocTracer.cpp

Lines changed: 36 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
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"
@@ -19,29 +20,31 @@
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

3429
static void* (*_orig_malloc)(size_t);
35-
static void (*_orig_free)(void*);
3630
static void* (*_orig_calloc)(size_t, size_t);
3731
static void* (*_orig_realloc)(void*, size_t);
3832
static int (*_orig_posix_memalign)(void**, size_t, size_t);
3933
static 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.
4242
static 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

5457
extern "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-
6968
extern "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

7781
extern "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-
9191
extern "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,
106106
Mutex MallocHooker::_patch_lock;
107107
int MallocHooker::_patched_libs = 0;
108108
bool 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.
255241
void 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

ddprof-lib/src/main/cpp/mallocTracer.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ class MallocHooker {
1919
static Mutex _patch_lock;
2020
static int _patched_libs;
2121
static bool _initialized;
22-
static void* _calloc_hook_fn;
23-
static void* _posix_memalign_hook_fn;
2422

2523
static void detectNestedMalloc();
2624

0 commit comments

Comments
 (0)