Add hooking functionality
This allows us to hook chosen functions and do interesting things there (in particular: reentrancy checking).
This commit is contained in:
parent
36bd90b962
commit
0a0fcd3e6a
@ -98,6 +98,7 @@ C_SRCS := $(srcroot)src/jemalloc.c \
|
|||||||
$(srcroot)src/extent_dss.c \
|
$(srcroot)src/extent_dss.c \
|
||||||
$(srcroot)src/extent_mmap.c \
|
$(srcroot)src/extent_mmap.c \
|
||||||
$(srcroot)src/hash.c \
|
$(srcroot)src/hash.c \
|
||||||
|
$(srcroot)src/hooks.c \
|
||||||
$(srcroot)src/large.c \
|
$(srcroot)src/large.c \
|
||||||
$(srcroot)src/malloc_io.c \
|
$(srcroot)src/malloc_io.c \
|
||||||
$(srcroot)src/mutex.c \
|
$(srcroot)src/mutex.c \
|
||||||
@ -161,6 +162,7 @@ TESTS_UNIT := \
|
|||||||
$(srcroot)test/unit/extent_quantize.c \
|
$(srcroot)test/unit/extent_quantize.c \
|
||||||
$(srcroot)test/unit/fork.c \
|
$(srcroot)test/unit/fork.c \
|
||||||
$(srcroot)test/unit/hash.c \
|
$(srcroot)test/unit/hash.c \
|
||||||
|
$(srcroot)test/unit/hooks.c \
|
||||||
$(srcroot)test/unit/junk.c \
|
$(srcroot)test/unit/junk.c \
|
||||||
$(srcroot)test/unit/junk_alloc.c \
|
$(srcroot)test/unit/junk_alloc.c \
|
||||||
$(srcroot)test/unit/junk_free.c \
|
$(srcroot)test/unit/junk_free.c \
|
||||||
|
19
include/jemalloc/internal/hooks.h
Normal file
19
include/jemalloc/internal/hooks.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef JEMALLOC_INTERNAL_HOOKS_H
|
||||||
|
#define JEMALLOC_INTERNAL_HOOKS_H
|
||||||
|
|
||||||
|
extern void (*hooks_arena_new_hook)();
|
||||||
|
extern void (*hooks_libc_hook)();
|
||||||
|
|
||||||
|
#define JEMALLOC_HOOK(fn, hook) ((void)(hook != NULL && (hook(), 0)), fn)
|
||||||
|
|
||||||
|
#define open JEMALLOC_HOOK(open, hooks_libc_hook)
|
||||||
|
#define read JEMALLOC_HOOK(read, hooks_libc_hook)
|
||||||
|
#define write JEMALLOC_HOOK(write, hooks_libc_hook)
|
||||||
|
#define readlink JEMALLOC_HOOK(readlink, hooks_libc_hook)
|
||||||
|
#define close JEMALLOC_HOOK(close, hooks_libc_hook)
|
||||||
|
#define creat JEMALLOC_HOOK(creat, hooks_libc_hook)
|
||||||
|
#define secure_getenv JEMALLOC_HOOK(secure_getenv, hooks_libc_hook)
|
||||||
|
/* Note that this is undef'd and re-define'd in src/prof.c. */
|
||||||
|
#define _Unwind_Backtrace JEMALLOC_HOOK(_Unwind_Backtrace, hooks_libc_hook)
|
||||||
|
|
||||||
|
#endif /* JEMALLOC_INTERNAL_HOOKS_H */
|
@ -23,7 +23,14 @@ extern "C" {
|
|||||||
# define JEMALLOC_N(n) @private_namespace@##n
|
# define JEMALLOC_N(n) @private_namespace@##n
|
||||||
# include "../jemalloc@install_suffix@.h"
|
# include "../jemalloc@install_suffix@.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that the ordering matters here; the hook itself is name-mangled. We
|
||||||
|
* want the inclusion of hooks to happen early, so that we hook as much as
|
||||||
|
* possible.
|
||||||
|
*/
|
||||||
#include "jemalloc/internal/private_namespace.h"
|
#include "jemalloc/internal/private_namespace.h"
|
||||||
|
#include "jemalloc/internal/hooks.h"
|
||||||
|
|
||||||
static const bool config_debug =
|
static const bool config_debug =
|
||||||
#ifdef JEMALLOC_DEBUG
|
#ifdef JEMALLOC_DEBUG
|
||||||
|
@ -56,7 +56,7 @@ size_t malloc_snprintf(char *str, size_t size, const char *format, ...)
|
|||||||
JEMALLOC_FORMAT_PRINTF(3, 4);
|
JEMALLOC_FORMAT_PRINTF(3, 4);
|
||||||
void malloc_vcprintf(void (*write_cb)(void *, const char *), void *cbopaque,
|
void malloc_vcprintf(void (*write_cb)(void *, const char *), void *cbopaque,
|
||||||
const char *format, va_list ap);
|
const char *format, va_list ap);
|
||||||
void malloc_cprintf(void (*write)(void *, const char *), void *cbopaque,
|
void malloc_cprintf(void (*write_cb)(void *, const char *), void *cbopaque,
|
||||||
const char *format, ...) JEMALLOC_FORMAT_PRINTF(3, 4);
|
const char *format, ...) JEMALLOC_FORMAT_PRINTF(3, 4);
|
||||||
void malloc_printf(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
|
void malloc_printf(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
|
||||||
|
|
||||||
|
@ -232,6 +232,7 @@ hash_rotl_64
|
|||||||
hash_x64_128
|
hash_x64_128
|
||||||
hash_x86_128
|
hash_x86_128
|
||||||
hash_x86_32
|
hash_x86_32
|
||||||
|
hooks_libc_hook
|
||||||
iaalloc
|
iaalloc
|
||||||
ialloc
|
ialloc
|
||||||
iallocztm
|
iallocztm
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
extern bool opt_stats_print;
|
extern bool opt_stats_print;
|
||||||
|
|
||||||
void stats_print(void (*write)(void *, const char *), void *cbopaque,
|
void stats_print(void (*write_cb)(void *, const char *), void *cbopaque,
|
||||||
const char *opts);
|
const char *opts);
|
||||||
|
|
||||||
#endif /* JEMALLOC_INTERNAL_STATS_EXTERNS_H */
|
#endif /* JEMALLOC_INTERNAL_STATS_EXTERNS_H */
|
||||||
|
12
src/hooks.c
Normal file
12
src/hooks.c
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include "jemalloc/internal/jemalloc_internal.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The hooks are a little bit screwy -- they're not genuinely exported in the
|
||||||
|
* sense that we want them available to end-users, but we do want them visible
|
||||||
|
* from outside the generated library, so that we can use them in test code.
|
||||||
|
*/
|
||||||
|
JEMALLOC_EXPORT
|
||||||
|
void (*hooks_arena_new_hook)() = NULL;
|
||||||
|
|
||||||
|
JEMALLOC_EXPORT
|
||||||
|
void (*hooks_libc_hook)() = NULL;
|
@ -8,7 +8,14 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef JEMALLOC_PROF_LIBGCC
|
#ifdef JEMALLOC_PROF_LIBGCC
|
||||||
|
/*
|
||||||
|
* We have a circular dependency -- jemalloc_internal.h tells us if we should
|
||||||
|
* use libgcc's unwinding functionality, but after we've included that, we've
|
||||||
|
* already hooked _Unwind_Backtrace. We'll temporarily disable hooking.
|
||||||
|
*/
|
||||||
|
#undef _Unwind_Backtrace
|
||||||
#include <unwind.h>
|
#include <unwind.h>
|
||||||
|
#define _Unwind_Backtrace JEMALLOC_HOOK(_Unwind_Backtrace, hooks_libc_hook)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -149,6 +149,15 @@ _tls_callback(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to be able to say "read" here (in the "pragma section"), but have
|
||||||
|
* hooked "read". We won't read for the rest of the file, so we can get away
|
||||||
|
* with unhooking.
|
||||||
|
*/
|
||||||
|
#ifdef read
|
||||||
|
# undef read
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# ifdef _M_IX86
|
# ifdef _M_IX86
|
||||||
# pragma comment(linker, "/INCLUDE:__tls_used")
|
# pragma comment(linker, "/INCLUDE:__tls_used")
|
||||||
|
@ -45,6 +45,7 @@ extern "C" {
|
|||||||
# define JEMALLOC_MANGLE
|
# define JEMALLOC_MANGLE
|
||||||
# include "jemalloc/internal/jemalloc_internal.h"
|
# include "jemalloc/internal/jemalloc_internal.h"
|
||||||
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/*
|
/*
|
||||||
* For integration tests, expose the public jemalloc interfaces, but only
|
* For integration tests, expose the public jemalloc interfaces, but only
|
||||||
@ -68,6 +69,7 @@ static const bool config_debug =
|
|||||||
|
|
||||||
# define JEMALLOC_N(n) @private_namespace@##n
|
# define JEMALLOC_N(n) @private_namespace@##n
|
||||||
# include "jemalloc/internal/private_namespace.h"
|
# include "jemalloc/internal/private_namespace.h"
|
||||||
|
# include "jemalloc/internal/hooks.h"
|
||||||
|
|
||||||
/* Hermetic headers. */
|
/* Hermetic headers. */
|
||||||
# include "jemalloc/internal/assert.h"
|
# include "jemalloc/internal/assert.h"
|
||||||
|
@ -310,6 +310,9 @@ label_test_end: \
|
|||||||
#define test(...) \
|
#define test(...) \
|
||||||
p_test(__VA_ARGS__, NULL)
|
p_test(__VA_ARGS__, NULL)
|
||||||
|
|
||||||
|
#define test_no_reentrancy(...) \
|
||||||
|
p_test_no_reentrancy(__VA_ARGS__, NULL)
|
||||||
|
|
||||||
#define test_no_malloc_init(...) \
|
#define test_no_malloc_init(...) \
|
||||||
p_test_no_malloc_init(__VA_ARGS__, NULL)
|
p_test_no_malloc_init(__VA_ARGS__, NULL)
|
||||||
|
|
||||||
@ -321,11 +324,14 @@ label_test_end: \
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
bool test_is_reentrant();
|
||||||
|
|
||||||
void test_skip(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
|
void test_skip(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
|
||||||
void test_fail(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
|
void test_fail(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
|
||||||
|
|
||||||
/* For private use by macros. */
|
/* For private use by macros. */
|
||||||
test_status_t p_test(test_t *t, ...);
|
test_status_t p_test(test_t *t, ...);
|
||||||
|
test_status_t p_test_no_reentrancy(test_t *t, ...);
|
||||||
test_status_t p_test_no_malloc_init(test_t *t, ...);
|
test_status_t p_test_no_malloc_init(test_t *t, ...);
|
||||||
void p_test_init(const char *name);
|
void p_test_init(const char *name);
|
||||||
void p_test_fini(void);
|
void p_test_fini(void);
|
||||||
|
@ -1,10 +1,42 @@
|
|||||||
#include "test/jemalloc_test.h"
|
#include "test/jemalloc_test.h"
|
||||||
|
|
||||||
|
/* Test status state. */
|
||||||
|
|
||||||
static unsigned test_count = 0;
|
static unsigned test_count = 0;
|
||||||
static test_status_t test_counts[test_status_count] = {0, 0, 0};
|
static test_status_t test_counts[test_status_count] = {0, 0, 0};
|
||||||
static test_status_t test_status = test_status_pass;
|
static test_status_t test_status = test_status_pass;
|
||||||
static const char * test_name = "";
|
static const char * test_name = "";
|
||||||
|
|
||||||
|
/* Reentrancy testing helpers. */
|
||||||
|
|
||||||
|
#define NUM_REENTRANT_ALLOCS 20
|
||||||
|
static bool reentrant = false;
|
||||||
|
static bool hook_ran = false;
|
||||||
|
static void *to_free[NUM_REENTRANT_ALLOCS];
|
||||||
|
|
||||||
|
static void
|
||||||
|
reentrancy_hook() {
|
||||||
|
hook_ran = true;
|
||||||
|
hooks_libc_hook = NULL;
|
||||||
|
|
||||||
|
void *to_free_local[NUM_REENTRANT_ALLOCS];
|
||||||
|
size_t alloc_size = 1;
|
||||||
|
for (int i = 0; i < NUM_REENTRANT_ALLOCS; i++) {
|
||||||
|
to_free[i] = malloc(alloc_size);
|
||||||
|
to_free_local[i] = malloc(alloc_size);
|
||||||
|
alloc_size *= 2;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NUM_REENTRANT_ALLOCS; i++) {
|
||||||
|
free(to_free_local[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual test infrastructure. */
|
||||||
|
bool
|
||||||
|
test_is_reentrant() {
|
||||||
|
return reentrant;
|
||||||
|
}
|
||||||
|
|
||||||
JEMALLOC_FORMAT_PRINTF(1, 2)
|
JEMALLOC_FORMAT_PRINTF(1, 2)
|
||||||
void
|
void
|
||||||
test_skip(const char *format, ...) {
|
test_skip(const char *format, ...) {
|
||||||
@ -49,11 +81,13 @@ p_test_init(const char *name) {
|
|||||||
void
|
void
|
||||||
p_test_fini(void) {
|
p_test_fini(void) {
|
||||||
test_counts[test_status]++;
|
test_counts[test_status]++;
|
||||||
malloc_printf("%s: %s\n", test_name, test_status_string(test_status));
|
malloc_printf("%s: %s (%s)\n", test_name,
|
||||||
|
test_status_string(test_status),
|
||||||
|
reentrant ? "reentrant" : "non-reentrant");
|
||||||
}
|
}
|
||||||
|
|
||||||
static test_status_t
|
static test_status_t
|
||||||
p_test_impl(bool do_malloc_init, test_t *t, va_list ap) {
|
p_test_impl(bool do_malloc_init, bool do_reentrant, test_t *t, va_list ap) {
|
||||||
test_status_t ret;
|
test_status_t ret;
|
||||||
|
|
||||||
if (do_malloc_init) {
|
if (do_malloc_init) {
|
||||||
@ -71,10 +105,27 @@ p_test_impl(bool do_malloc_init, test_t *t, va_list ap) {
|
|||||||
|
|
||||||
ret = test_status_pass;
|
ret = test_status_pass;
|
||||||
for (; t != NULL; t = va_arg(ap, test_t *)) {
|
for (; t != NULL; t = va_arg(ap, test_t *)) {
|
||||||
|
/* Non-reentrant run. */
|
||||||
|
reentrant = false;
|
||||||
t();
|
t();
|
||||||
if (test_status > ret) {
|
if (test_status > ret) {
|
||||||
ret = test_status;
|
ret = test_status;
|
||||||
}
|
}
|
||||||
|
/* Reentrant run. */
|
||||||
|
if (do_reentrant) {
|
||||||
|
reentrant = true;
|
||||||
|
hooks_libc_hook = &reentrancy_hook;
|
||||||
|
t();
|
||||||
|
if (test_status > ret) {
|
||||||
|
ret = test_status;
|
||||||
|
}
|
||||||
|
if (hook_ran) {
|
||||||
|
hook_ran = false;
|
||||||
|
for (int i = 0; i < NUM_REENTRANT_ALLOCS; i++) {
|
||||||
|
free(to_free[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
malloc_printf("--- %s: %u/%u, %s: %u/%u, %s: %u/%u ---\n",
|
malloc_printf("--- %s: %u/%u, %s: %u/%u, %s: %u/%u ---\n",
|
||||||
@ -95,7 +146,20 @@ p_test(test_t *t, ...) {
|
|||||||
|
|
||||||
ret = test_status_pass;
|
ret = test_status_pass;
|
||||||
va_start(ap, t);
|
va_start(ap, t);
|
||||||
ret = p_test_impl(true, t, ap);
|
ret = p_test_impl(true, true, t, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_status_t
|
||||||
|
p_test_no_reentrancy(test_t *t, ...) {
|
||||||
|
test_status_t ret;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
ret = test_status_pass;
|
||||||
|
va_start(ap, t);
|
||||||
|
ret = p_test_impl(true, false, t, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@ -108,7 +172,11 @@ p_test_no_malloc_init(test_t *t, ...) {
|
|||||||
|
|
||||||
ret = test_status_pass;
|
ret = test_status_pass;
|
||||||
va_start(ap, t);
|
va_start(ap, t);
|
||||||
ret = p_test_impl(false, t, ap);
|
/*
|
||||||
|
* We also omit reentrancy from bootstrapping tests, since we don't
|
||||||
|
* (yet) care about general reentrancy during bootstrapping.
|
||||||
|
*/
|
||||||
|
ret = p_test_impl(false, false, t, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
38
test/unit/hooks.c
Normal file
38
test/unit/hooks.c
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#include "test/jemalloc_test.h"
|
||||||
|
|
||||||
|
static bool hook_called = false;
|
||||||
|
|
||||||
|
static void
|
||||||
|
hook() {
|
||||||
|
hook_called = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
func_to_hook(int arg1, int arg2) {
|
||||||
|
return arg1 + arg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define func_to_hook JEMALLOC_HOOK(func_to_hook, hooks_libc_hook)
|
||||||
|
|
||||||
|
TEST_BEGIN(unhooked_call) {
|
||||||
|
hooks_libc_hook = NULL;
|
||||||
|
hook_called = false;
|
||||||
|
assert_d_eq(3, func_to_hook(1, 2), "Hooking changed return value.");
|
||||||
|
assert_false(hook_called, "Nulling out hook didn't take.");
|
||||||
|
}
|
||||||
|
TEST_END
|
||||||
|
|
||||||
|
TEST_BEGIN(hooked_call) {
|
||||||
|
hooks_libc_hook = &hook;
|
||||||
|
hook_called = false;
|
||||||
|
assert_d_eq(3, func_to_hook(1, 2), "Hooking changed return value.");
|
||||||
|
assert_true(hook_called, "Hook should have executed.");
|
||||||
|
}
|
||||||
|
TEST_END
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void) {
|
||||||
|
return test(
|
||||||
|
unhooked_call,
|
||||||
|
hooked_call);
|
||||||
|
}
|
@ -76,6 +76,6 @@ TEST_END
|
|||||||
|
|
||||||
int
|
int
|
||||||
main(void) {
|
main(void) {
|
||||||
return test(
|
return test_no_reentrancy(
|
||||||
test_idump);
|
test_idump);
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,6 @@ TEST_END
|
|||||||
|
|
||||||
int
|
int
|
||||||
main(void) {
|
main(void) {
|
||||||
return test(
|
return test_no_reentrancy(
|
||||||
test_prof_active);
|
test_prof_active);
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,6 @@ TEST_END
|
|||||||
|
|
||||||
int
|
int
|
||||||
main(void) {
|
main(void) {
|
||||||
return test(
|
return test_no_reentrancy(
|
||||||
test_gdump);
|
test_gdump);
|
||||||
}
|
}
|
||||||
|
@ -278,7 +278,7 @@ main(void) {
|
|||||||
/* Intercept dumping prior to running any tests. */
|
/* Intercept dumping prior to running any tests. */
|
||||||
prof_dump_open = prof_dump_open_intercept;
|
prof_dump_open = prof_dump_open_intercept;
|
||||||
|
|
||||||
return test(
|
return test_no_reentrancy(
|
||||||
test_prof_reset_basic,
|
test_prof_reset_basic,
|
||||||
test_prof_reset_cleanup,
|
test_prof_reset_cleanup,
|
||||||
test_prof_reset,
|
test_prof_reset,
|
||||||
|
@ -41,6 +41,6 @@ TEST_END
|
|||||||
|
|
||||||
int
|
int
|
||||||
main(void) {
|
main(void) {
|
||||||
return test(
|
return test_no_reentrancy(
|
||||||
test_prof_realloc);
|
test_prof_realloc);
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ thd_start(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_BEGIN(test_tsd_main_thread) {
|
TEST_BEGIN(test_tsd_main_thread) {
|
||||||
|
test_skip_if(test_is_reentrant());
|
||||||
thd_start((void *)(uintptr_t)0xa5f3e329);
|
thd_start((void *)(uintptr_t)0xa5f3e329);
|
||||||
}
|
}
|
||||||
TEST_END
|
TEST_END
|
||||||
|
Loading…
Reference in New Issue
Block a user