0

I acknowledge that this question - in many different forms - has been asked for at least a decade. I've read no less that 25 posts and articles (here and elsewhere) addressing this topic. I still cannot find a consistent answer that actually works.

Environment:

  • Ubuntu 22.04, GCC 11.4, glib 2.35
  • Debian 12, GCC 12.2, glib 2.36
  • Targeting C11

I am working on several backend system services for which I would like to perform some minimal tracking of heap use. To that end, I wrote a very simple "allocator suite" to wrap the following standard calls: calloc(), malloc(), realloc(), valloc(), and free(). This suite was originally developed under macOS and worked without issue. I now have need to port it to Linux. I'm using --wrap=symbol and __wrap_*()/__real_*()

Everything compiles cleanly. Everything runs cleanly. However, my wrapping routines are never called, i.e. the core library versions are still used. I've attached all build files and source code files.

The wrapper library memtrace.c:

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/file.h>
#include <execinfo.h>

#include "memtrace.h"

#define EXPORT __attribute__((visibility("default")))
#define PRIVATE __attribute__((visibility("hidden")))

#define MAX_STACK_DEPTH 32
#define SNAP(T,S,P) \
        if( ! internal ) { \
                void* ary[MAX_STACK_DEPTH]; \
                int cnt = backtrace( ary, MAX_STACK_DEPTH ); \
                assert( cnt > 0 ); \
                writebt( (T), ary, cnt, (S), (P), (T)=='x'?'-':((P)!=NULL?'y':'n') ); \
        }

static char dname[MAXPATHLEN];
static int fd = -1;

static bool internal = false;

static int cnt_calloc = 0;
static int cnt_free = 0;
static int cnt_malloc = 0;
static int cnt_realloc = 0;
static int cnt_valloc = 0;

static int cnt_gain = 0;
static int cnt_loss = 0;

static void writebt ( char type, void** ary, int cnt, size_t size, void* ptr, char ok );

void* __real_calloc  ( size_t count, size_t size );
void  __real_free    ( void *ptr );
void* __real_malloc  ( size_t size );
void* __real_realloc ( void *ptr, size_t size );
void* __real_valloc  ( size_t size );

__attribute__((constructor))
static void initializer ( void )
{
        printf( "CONSTRUCT: memtrace (%s)\n", program_invocation_name );
        fflush( stdout );

        snprintf( dname, MAXPATHLEN, "%s.memuse", program_invocation_name );
        fd = open( dname, O_WRONLY | O_CREAT | O_TRUNC, 0644 );
        assert( fd >= 0 );
}

__attribute__((destructor))
static void finalizer ( void )
{
        close( fd );
        mem_report();
        printf( "DESTRUCT: memtrace\n" );
        fflush( stdout );
}

EXPORT
void* __wrap_calloc ( size_t count, size_t size )
{
        ++cnt_calloc; ++cnt_gain;
        void* ptr = __real_calloc( count, size );
        SNAP('c',size*count,ptr)
        return ptr;
}

EXPORT
void __wrap_free ( void *ptr )
{
        ++cnt_free; ++cnt_loss;
        SNAP('x',0,ptr)
        __real_free( ptr );
}

EXPORT
void* __wrap_malloc ( size_t size )
{
printf("malloc\n");fflush(stdout);
        ++cnt_malloc; ++cnt_gain;
        void* ptr = __real_malloc( size );
        SNAP('m',size,ptr)
        return ptr;
}

EXPORT
void* __wrap_realloc ( void *ptr, size_t size )
{
        ++cnt_realloc;
        ptr = __real_realloc( ptr, size );
        SNAP('r',size,ptr)
        return ptr;
}

EXPORT
void* __wrap_valloc ( size_t size )
{
        ++cnt_valloc; ++cnt_gain;
        void* ptr = __real_valloc( size );
        SNAP('v',size,ptr)
        return ptr;
}

static void writebt ( char type, void** ary, int cnt, size_t size, void* ptr, char ok )
{
        static char buf[256];
        static size_t len;

        flock( fd, LOCK_EX );
        internal = true;
        char** names = backtrace_symbols( ary, cnt );
        len = snprintf( buf, sizeof(buf), "<%c %ld %p : %d [%c]\n", type, size, ptr, cnt, ok );
        write( fd, buf, len );
        for( int i = 0; i < cnt; ++i ) {
                len = snprintf( buf, sizeof(buf), "%2d %18p %s\n", i, ary[i], names[i] );
                write( fd, buf, len );
        }
        free( names );
        write( fd, ">\n", 2 );
        internal = false;
        flock( fd, LOCK_UN );
}

EXPORT
void mem_report ( void )
{
        printf( "== MEMORY REPORT ==\n" );
        printf( "cnt_calloc   = %d\n", cnt_calloc   );
        printf( "cnt_free     = %d\n", cnt_free     );
        printf( "cnt_malloc   = %d\n", cnt_malloc   );
        printf( "cnt_realloc  = %d\n", cnt_realloc  );
        printf( "cnt_valloc   = %d\n", cnt_valloc   );
        printf( "+cnt_gain    = %d\n", cnt_gain     );
        printf( "+cnt_loss    = %d\n", cnt_loss     );
        printf( "== EOD\n" );
}

The library include file memtrace.h:

#ifndef _hdr_memtrace_h_
#define _hdr_memtrace_h_ 1
void mem_report ( void );
#endif // _hdr_memtrace_h_

The test harness test-memtrace.c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main ( int argc, char** argv ) {
        (void)argv;
        printf( "There are %d arg(s)\n", argc );
        char* str = strdup( "Hello, world." );
        char* pv = malloc( 27 );
        strcpy( pv, "John Doe says ..." );
        printf( "%s %s\n", pv, str );
        return 0;
}

Library build:

#!/usr/bin/env bash
cc \
        -o memtrace.o \
        -c memtrace.c \
        -DNDEBUG -D_GNU_SOURCE -O3 -fPIC -std=c11 -fvisibility=hidden \
        -Wall -Werror -Wextra -pedantic-errors \
        -I. -I/opt/local/include -I/usr/local/include \
        -Wl,--wrap=calloc -Wl,--wrap=free -Wl,--wrap=malloc -Wl,--wrap=realloc -Wl,--wrap=valloc
cc \
        -o libmemtrace.so \
        -shared \
        -L. -L/opt/local/lib -L/usr/local/lib \
        memtrace.o

Harness build:

#!/usr/bin/env bash
cc \
        -o test-memtrace.o \
        -c test-memtrace.c \
        -DNDEBUG -D_GNU_SOURCE -O3 -std=c11 \
        -Wall -Werror -Wextra -pedantic-errors \
        -I. -I/opt/local/include -I/usr/local/include
cc \
        -o test-memtrace \
        test-memtrace.o \
        -L. -L/opt/local/lib -L/usr/local/lib

Evocation: LD_PRELOAD=$PWD/libmemtrace.so test-memtrace

Output:

CONSTRUCT: memtrace (test-memtrace)
There are 1 arg(s)
John Doe says ... Hello, world.
== MEMORY REPORT ==
cnt_calloc   = 0
cnt_free     = 0
cnt_malloc   = 0
cnt_realloc  = 0
cnt_valloc   = 0
+cnt_gain    = 0
+cnt_loss    = 0
== EOD
DESTRUCT: memtrace

You can see that the library is being loaded (CONSTRUCT and DESTRUCT) but nothing is being counted. Similarly, the outputfile (test-memtrace.memuse) is empty.

EDIT This is not the same as How to wrap functions with the --wrap option correctly?; I'm trying to wrap with a shared library and that question does not ask about SO's nor do the comments address SO's. Everything in my code compiling and linking successfully and cleanly; there are no reference warnings or errors of any kind. Similarly, How do I reimplement (or wrap) a syscall function on Linux? is also about static linking. Additionally, What is the LD_PRELOAD trick? doesn't address my issue as it alludes to reimplementing the routines you wish to wrap where as what I need to do is wrap and call the original.

LiamF
  • 523
  • 3
  • 13
  • You're missing the `--wrap` arguments in the link of `test-memtrace` (Harness build in your description), so calls from there do not go to the wrapper routines. The `LD_PRELOAD` is irrelevant (and unneeded as the Harness build already has `-lmemtrace` so will link explicitly with the shared library) – Chris Dodd Aug 15 '23 at 22:09
  • For what you're trying to do, you're probably better off not using `--wrap` AT ALL, and instead using GNU libc's built-in wrapping support. Just define your own `malloc` (and `free` etc) that calls `__libc_malloc`. Do that in an .so you build and you can inject it (with `LD_PRELOAD`) on *any* executable that uses libc and it will "just work" on any system that uses GNU libc as the default system libc. – Chris Dodd Aug 15 '23 at 22:25
  • @ChrisDodd You caught that, too. I fixed it in my script but didn't update the question. The Q is now updated. Unfortunately, it didn't resolve any issues. (And fortunately it didn't create any new ones.) – LiamF Aug 15 '23 at 22:36
  • The question does not appear to have been updated -- you still have no `--wrap` arguments in the link command for `test-memtrace`. They're also missing in the link command for `libmemtrace.so`, so aren't used at all (they'll be ignored in the compile command for `memtrace.o` as that is a *compile* not a *link*, and they are linker options.) – Chris Dodd Aug 15 '23 at 23:10
  • `test-memtrace` does not get the `--wrap` arguments. `test-memtrace` is "any old application" and is unaware that its heap routines have been rerouted, hence the use of LD_PRELOAD before execution of `test-memtrace` Any other scenario requires `test-memtrace` to be recompiled/relinked thereby making it aware of and a participant in the wrapping process. Think of valgrind (valgrind-dot-org) ... – LiamF Aug 16 '23 at 08:47
  • The `--wrap` linker arguent is useless for what you are trying to do then -- it can only be used when linkning a program, and not when loading it. – Chris Dodd Aug 16 '23 at 09:27

0 Answers0