I have a created shared library which interposes malloc() and related calls. The works well but for some caveats. There is one thing that does not work. I am expecting to be able to chain interposers such that I can run something like
LD_PRELOAD="/path/to/mymalloc.so /usr/lib64/jemalloc.so" some_app
The intention is that instead of forwarding to libc malloc() my library should now forward to jemalloc via RTLD_NEXT.
However it segfaults generating stack trace showing my malloc wrapper calling itself ad infinitum. Though it does not allocate any memory itself when jemalloc is not in use:
#224364 0x00007facb1aef46a in Memory::HybridAllocator<Memory::LibCAllocator, Memory::StaticAllocator>::malloc (this=0x7facb1d0be60 <Memory::getHybridAllocator()::hybrid>, size=72704) at /home/brucea/work/git/libbede/src/main/cpp/memory/Memory/HybridAllocator.h:109
#224365 0x00007facb1aefa8a in malloc (size=72704) at /home/brucea/work/git/libbede/src/main/cpp/memory/Memory/mallocwrap.cpp:11
#224366 0x00007facb1aeeca2 in Memory::LibCAllocator::malloc (this=0x7facb1cf3720 <Memory::getBootstrapAllocator()::bootstrap>, requestSize=72704) at /home/brucea/work/git/libbede/src/main/cpp/memory/Memory/LibCAllocator.h:77
#224367 0x00007facb1aef46a in Memory::HybridAllocator<Memory::LibCAllocator, Memory::StaticAllocator>::malloc (this=0x7facb1d0be60 <Memory::getHybridAllocator()::hybrid>, size=72704) at /home/brucea/work/git/libbede/src/main/cpp/memory/Memory/HybridAllocator.h:109
#224368 0x00007facb1aefa8a in malloc (size=72704) at /home/brucea/work/git/libbede/src/main/cpp/memory/Memory/mallocwrap.cpp:11
#224369 0x00007facb133fc1a in (anonymous namespace)::pool::pool (this=0x7facb163e200 <(anonymous namespace)::emergency_pool>) at ../../../../libstdc++-v3/libsupc++/eh_alloc.cc:123
#224370 __static_initialization_and_destruction_0 (__priority=65535, __initialize_p=1) at ../../../../libstdc++-v3/libsupc++/eh_alloc.cc:262
#224371 _GLOBAL__sub_I_eh_alloc.cc(void) () at ../../../../libstdc++-v3/libsupc++/eh_alloc.cc:338
#224372 0x00007facb1d1b8ba in call_init (l=<optimized out>, argc=argc@entry=4, argv=argv@entry=0x7ffe3ba440e8, env=env@entry=0x7ffe3ba44110) at dl-init.c:72
#224373 0x00007facb1d1b9ba in call_init (env=0x7ffe3ba44110, argv=0x7ffe3ba440e8, argc=4, l=<optimized out>) at dl-init.c:30
#224374 _dl_init (main_map=0x7facb1f3a1d0, argc=4, argv=0x7ffe3ba440e8, env=0x7ffe3ba44110) at dl-init.c:119
#224375 0x00007facb1d0cfda in _dl_start_user () from /lib64/ld-linux-x86-64.so.2
#224376 0x0000000000000004 in ?? ()
#224377 0x00007ffe3ba45d7f in ?? ()
#224378 0x00007ffe3ba45ddd in ?? ()
#224379 0x00007ffe3ba45de0 in ?? ()
#224380 0x00007ffe3ba45de4 in ?? ()
#224381 0x0000000000000000 in ?? ()
Debugging in gdb the cause seems to be that malloc_hook inside __libc_malloc() is somehow set to point at my implementation of malloc resulting in an infinite recursion. But it must be jemalloc doing this somehow.
__GI___libc_malloc (bytes=16) at malloc.c:3037
3037 {
(gdb) s
3042 = atomic_forced_read (__malloc_hook);
(gdb) s
3043 if (__builtin_expect (hook != NULL, 0))
(gdb) s
3044 return (*hook)(bytes, RETURN_ADDRESS (0));
(gdb) s
malloc (size=140737488345424) at /home/brucea/work/git/libbede/src/main/cpp/memory/Memory/mallocwrap.cpp:12
The basic outline is my code (in C++ except for the low-level parts so apologies for any offence caused to C purists):
extern "C" void* malloc(const size_t size) __THROW
{
return getMyAllocator().malloc(size);
}
// etc. for free() et al
// elsewhere
auto wrap(const char* sym)
{
static void* libchandle = nullptr;
auto f = dlsym(RTLD_NEXT,sym);
if (f == nullptr)
{
std::fprintf(stderr, "error: unable to find symbol via dlsym(RTLD_NEXT,%s):\n",sym);
std::fprintf(stderr, "%s\n",dlerror());
f = dlsym(RTLD_DEFAULT, sym);
}
if (f == nullptr)
{
std::fprintf(stderr, "error: unable to find symbol via dlsym(RTLD_DEFAULT,%s):\n",sym);
std::fprintf(stderr, "%s\n",dlerror());
if (libchandle == nullptr)
{
libchandle = dlopen("libc.so", RTLD_LAZY);
if (libchandle == nullptr)
{ \
std::fprintf(stderr, "unable to open libc.so:\n");
std::fprintf(stderr, "%s\n",dlerror());
}
if (libchandle != nullptr)
{
f = dlsym(libchandle, sym);
}
}
if (f == nullptr)
{
std::fprintf(stderr, "error: unable to find symbol via dlsym(\"libc\",%s):\n",sym);
std::fprintf(stderr, "%s\n",dlerror());
std::exit(1);
}
}
return f;
}
#define WRAP(X) \
{ \
static constexpr const char* const symName = #X; \
auto f = reinterpret_cast<decltype(&::X)>(wrap(#X)); \
this->X##Func = f; \
}
// Note: until ForwardingAllocator is setup
// malloc() etc are forwarded to __libc_malloc() etc
ForwardingAllocator::ForwardingAllocator()
{
WRAP(malloc)
WRAP(free)
WRAP(calloc)
WRAP(realloc)
WRAP(malloc_usable_size)
}
Lots of stuff omitted for brevity.
Are there any suggestions as to what I might be doing wrong or how I can better diagnose the issue?
It seems that jemalloc itself defines __libc_malloc
>nm /usr/lib/debug/usr/lib64/libjemalloc.so.2-5.2.1-2.el8.x86_64.debug | grep __libc_malloc
000000000000d4f0 t __libc_malloc
Some further information.
- malloc_hooks are deprecated so I don't use them.
Complications I have handled with some success:
dlsym() uses malloc() - I use a simple bootstrap allocator during startup before switching to the main one which forwards to libc's malloc()
I originally used a naive allocator as a booststrap allocator
My wrapper to free() delegates to the appropriate free() depending on which malloc() was in use
I have now moved to using __libc_malloc as a the bootstrap allocator but allowing it to be replaced via dlsym as soon as possible.
This is a useful answer - https://stackoverflow.com/a/17850402/1569204