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.