From 8a6e44d289cbb2c89f7488dcfd9e14ed634c68a6 Mon Sep 17 00:00:00 2001 From: "erwan.viollet" Date: Tue, 23 Jun 2026 10:12:52 +0200 Subject: [PATCH] Add mremap and shmat/shmdt allocation tracking Extends allocation tracking to cover memory allocation APIs that were previously missing: 1. mremap() - Remap/resize existing mmap regions - Tracks old region as deallocation + new region as allocation - Commonly used by allocators to grow large allocations (e.g., df-executor) 2. shmat()/shmdt() - System V shared memory attach/detach - Queries segment size via shmctl(IPC_STAT) on attach - Commonly used by databases (PostgreSQL, Oracle) and legacy IPC These APIs can bypass malloc/mmap hooks when called directly, leading to under-reporting of memory usage in allocation profiles. brk()/sbrk() are explicitly not instrumented (with comment explaining why): they manipulate the program break (heap boundary), not individual allocations, and would double-count if malloc uses them internally. Tests added in allocation_tracker-ut.cc for both mremap and shmat/shmdt. --- src/lib/symbol_overrides.cc | 64 +++++++++++++++++++++++++++++++++++ test/allocation_tracker-ut.cc | 35 ++++++++++++++++++- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/lib/symbol_overrides.cc b/src/lib/symbol_overrides.cc index 135448d0..b5b83c76 100644 --- a/src/lib/symbol_overrides.cc +++ b/src/lib/symbol_overrides.cc @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include #if defined(__GNUC__) && !defined(__clang__) # define NOEXCEPT noexcept @@ -35,6 +37,14 @@ extern "C" { // Declaration of reallocarray is only available starting from glibc 2.28 DDPROF_WEAK void *reallocarray(void *ptr, size_t nmemb, size_t nmenb) NOEXCEPT; DDPROF_WEAK void *pvalloc(size_t size) NOEXCEPT; +DDPROF_WEAK void *mremap(void *old_address, size_t old_size, size_t new_size, + int flags, ...) NOEXCEPT; +DDPROF_WEAK void *shmat(int shmid, const void *shmaddr, int shmflg) NOEXCEPT; +DDPROF_WEAK int shmdt(const void *shmaddr) NOEXCEPT; +// brk()/sbrk() are not instrumented: +// - They grow/shrink the program break, not individual allocations +// - Would double-count if malloc uses them internally +// - Tracking would require maintaining state of what's actually allocated DDPROF_WEAK int __libc_allocate_rtsig(int high) NOEXCEPT; // NOLINTEND @@ -857,6 +867,57 @@ struct Munmap_Hook : HookBase { } }; +struct MremapHook : HookBase { + static constexpr auto name = "mremap"; + using FuncType = decltype(&::mremap); + static inline FuncType ref{}; + + static void *hook(void *old_address, size_t old_size, size_t new_size, + int flags) noexcept { + AllocTrackerHelperMmap helper; + if (likely(old_address) && helper) { + ddprof::AllocationTracker::track_deallocation_s( + reinterpret_cast(old_address), *helper.tl_state(), true); + } + auto *new_ptr = ref(old_address, old_size, new_size, flags); + if (likely(new_size && new_ptr != MAP_FAILED)) { + helper.track(new_ptr, new_size); + } + return new_ptr; + } +}; + +struct ShmatHook : HookBase { + static constexpr auto name = "shmat"; + using FuncType = decltype(&::shmat); + static inline FuncType ref{}; + + static void *hook(int shmid, const void *shmaddr, int shmflg) noexcept { + void *ptr = ref(shmid, shmaddr, shmflg); + if (ptr != reinterpret_cast(-1)) { + AllocTrackerHelperMmap helper; + // Query the segment size via shmctl + struct shmid_ds buf; + if (shmctl(shmid, IPC_STAT, &buf) == 0) { + helper.track(ptr, buf.shm_segsz); + } + } + return ptr; + } +}; + +struct ShmdtHook : HookBase { + static constexpr auto name = "shmdt"; + using FuncType = decltype(&::shmdt); + static inline FuncType ref{}; + + static int hook(const void *shmaddr) noexcept { + DeallocTrackerHelperMmap helper; + helper.track(const_cast(shmaddr)); + return ref(shmaddr); + } +}; + template void register_hook() { g_symbol_overrides->register_override(T::name, reinterpret_cast(&T::hook), @@ -922,6 +983,9 @@ void register_hooks() { register_hook(); register_hook(); register_hook(); + register_hook(); + register_hook(); + register_hook(); register_hook(); register_hook(); diff --git a/test/allocation_tracker-ut.cc b/test/allocation_tracker-ut.cc index 6918e440..cc8a00da 100644 --- a/test/allocation_tracker-ut.cc +++ b/test/allocation_tracker-ut.cc @@ -41,7 +41,9 @@ DDPROF_WEAK void sdallocx(void *ptr, size_t size, int flags); } #endif #include +#include #include +#include #include #if defined(__GNUC__) && !defined(__clang__) @@ -61,6 +63,10 @@ DDPROF_WEAK void *pvalloc(size_t size) NOEXCEPT; DDPROF_WEAK void *__mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); DDPROF_WEAK int __munmap(void *addr, size_t length); +DDPROF_WEAK void *mremap(void *old_address, size_t old_size, size_t new_size, + int flags, ...) NOEXCEPT; +DDPROF_WEAK void *shmat(int shmid, const void *shmaddr, int shmflg) NOEXCEPT; +DDPROF_WEAK int shmdt(const void *shmaddr) NOEXCEPT; } namespace ddprof { @@ -505,7 +511,34 @@ DDPROF_NOINLINE void test_allocation_functions(RingBuffer &ring_buffer) { SCOPED_TRACE("__mmap/__munmap"); checker.test_alloc(mmap_wrapper(&::__mmap), &::__munmap); } - + if (mremap) { + SCOPED_TRACE("mremap"); + checker.test_realloc( + mmap_wrapper(&::mmap), + [](void *ptr, size_t new_sz) { + auto old_sz = alloc_size; + auto *new_ptr = ::mremap(ptr, old_sz, new_sz, MREMAP_MAYMOVE); + return std::make_pair(new_ptr, new_sz); + }, + [](void *ptr, size_t sz) { ::munmap(ptr, sz); }); + } + if (shmat && shmdt) { + SCOPED_TRACE("shmat/shmdt"); + // Create a System V shared memory segment + int shmid = shmget(IPC_PRIVATE, alloc_size, IPC_CREAT | 0600); + if (shmid != -1) { + checker.empty_ring_buffer(); + void *ptr = ::shmat(shmid, nullptr, 0); + if (ptr != reinterpret_cast(-1)) { + checker.check_alloc(ptr, alloc_size); + checker.check_empty(); + ::shmdt(ptr); + checker.check_dealloc(ptr); + checker.check_empty(); + } + shmctl(shmid, IPC_RMID, nullptr); + } + } static constexpr size_t big_align = alignof(std::max_align_t) * 2; static constexpr size_t array_size = 16; static_assert((alloc_size / array_size) % big_align == 0);