alloc counter test: better recursive malloc (#263)

Motivation:

The allocation counter integration tests previously used a pretty crude
malloc implementation for the case that malloc is needed whilst we
resolve malloc from libc using dlsym. This implements a much better
version and also allows us to actually use free. Previously we just
leaked all the memory (which is okay as its just a test but in
constrained environments this wouldn't scale as we might actually run
out of memory). The reason we used to leak the memory is that given a
pointer we couldn't figure out if that pointer was alloated using libc's
malloc or our crude malloc implementation so we didn't know how to free.
The new version fixes that as all the pointers vended from
recursive_malloc live in one global block. That makes it trivial to
check if a given pointer is allocated by recursive_malloc (lives in that
block) or libc's malloc (doesn't live in the recursive_malloc block).

Modifications:

- implemented a better recursive_malloc function
- make the allocation counter test actually free memory that can be
  freed

Result:

Less memory use in the allocation counter test.
This commit is contained in:
Johannes Weiß 2018-04-03 13:29:23 +01:00 committed by Cory Benfield
parent 66e4fdfbba
commit 2839ef0a3c
2 changed files with 54 additions and 18 deletions

View File

@ -55,7 +55,7 @@ make_git_commit_all
cd ..
"$swift_bin" package edit --path "$nio_root" swift-nio
"$swift_bin" run -c release > "$tmp/output"
"$swift_bin" run -c release | tee "$tmp/output"
)
for test in 1000_reqs_1_conn 1_reqs_1000_conn; do

View File

@ -17,6 +17,8 @@
#include <dlfcn.h>
#include <fcntl.h>
#include <hooked-functions.h>
#include <inttypes.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
@ -25,30 +27,50 @@
#include <sys/types.h>
#include <unistd.h>
/* a big block of memory that we'll use for recursive mallocs */
static char g_recursive_malloc_mem[10 * 1024 * 1024] = {0};
/* the index of the first free byte */
static _Atomic ptrdiff_t g_recursive_malloc_next_free_ptr = ATOMIC_VAR_INIT(0);
#define DYLD_INTERPOSE(_replacement,_replacee) \
__attribute__((used)) static struct { const void *replacement; const void *replacee; } _interpose_##_replacee \
__attribute__ ((section("__DATA,__interpose"))) = { (const void *)(unsigned long)&_replacement, (const void *)(unsigned long)&_replacee };
#define LIBC_SYMBOL(_fun) "" # _fun
void replacement_free(void *ptr) {
if (ptr) {
inc_free_counter();
}
}
__thread bool g_in_malloc = false;
__thread bool g_in_realloc = false;
__thread void *(*g_libc_malloc)(size_t) = NULL;
__thread void *(*g_libc_realloc)(void *, size_t) = NULL;
static __thread bool g_in_malloc = false;
static __thread bool g_in_realloc = false;
static __thread bool g_in_free = false;
static __thread void *(*g_libc_malloc)(size_t) = NULL;
static __thread void *(*g_libc_realloc)(void *, size_t) = NULL;
static __thread void (*g_libc_free)(void *) = NULL;
// this is called if malloc is called whilst trying to resolve libc's realloc.
static void *recursive_malloc(size_t size) {
/* a very cheap (as in bad) and inefficient malloc implementation that we just use
if malloc calls itself. */
int fd = open("/dev/zero", O_RDWR);
void *ptr = mmap(0, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
close(fd);
return ptr;
// we just vend out pointers to a large block in the BSS (which we never free).
// This block should be large enough because it's only used when malloc is
// called from dlsym which should only happen once per thread.
static void *recursive_malloc(size_t size_in) {
size_t size = size_in;
if ((size & 0xf) != 0) {
// make size 16 byte aligned
size = (size + 0xf) & (~(size_t)0xf);
}
ptrdiff_t next = atomic_fetch_add_explicit(&g_recursive_malloc_next_free_ptr,
size,
memory_order_relaxed);
if ((size_t)next >= sizeof(g_recursive_malloc_mem)) {
// we ran out of memory
return NULL;
}
return (void *)((intptr_t)g_recursive_malloc_mem + next);
}
static bool is_recursive_malloc_block(void *ptr) {
uintptr_t block_begin = (uintptr_t)g_recursive_malloc_mem;
uintptr_t block_end = block_begin + sizeof(g_recursive_malloc_mem);
uintptr_t user_ptr = (uintptr_t)ptr;
return user_ptr >= block_begin && user_ptr < block_end;
}
// this is called if realloc is called whilst trying to resolve libc's realloc.
@ -57,6 +79,12 @@ static void *recursive_realloc(void *ptr, size_t size) {
abort();
}
// this is called if free is called whilst trying to resolve libc's free.
static void recursive_free(void *ptr) {
// not implemented yet...
abort();
}
#if __APPLE__
/* on Darwin calling the original function is super easy, just call it, done. */
@ -90,6 +118,14 @@ static void *recursive_realloc(void *ptr, size_t size) {
#endif
void replacement_free(void *ptr) {
if (ptr) {
inc_free_counter();
if (!is_recursive_malloc_block(ptr)) {
JUMP_INTO_LIBC_FUN(free, ptr);
}
}
}
void *replacement_malloc(size_t size) {
inc_malloc_counter();