17bdf38e5Schristos #include "jemalloc/internal/jemalloc_preamble.h" 27bdf38e5Schristos 37bdf38e5Schristos #include "jemalloc/internal/hook.h" 47bdf38e5Schristos 57bdf38e5Schristos #include "jemalloc/internal/atomic.h" 67bdf38e5Schristos #include "jemalloc/internal/mutex.h" 77bdf38e5Schristos #include "jemalloc/internal/seq.h" 87bdf38e5Schristos 97bdf38e5Schristos typedef struct hooks_internal_s hooks_internal_t; 107bdf38e5Schristos struct hooks_internal_s { 117bdf38e5Schristos hooks_t hooks; 127bdf38e5Schristos bool in_use; 137bdf38e5Schristos }; 147bdf38e5Schristos 157bdf38e5Schristos seq_define(hooks_internal_t, hooks) 167bdf38e5Schristos 177bdf38e5Schristos static atomic_u_t nhooks = ATOMIC_INIT(0); 187bdf38e5Schristos static seq_hooks_t hooks[HOOK_MAX]; 197bdf38e5Schristos static malloc_mutex_t hooks_mu; 207bdf38e5Schristos 217bdf38e5Schristos bool 22*05ba6793Schristos hook_boot(void) { 237bdf38e5Schristos return malloc_mutex_init(&hooks_mu, "hooks", WITNESS_RANK_HOOK, 247bdf38e5Schristos malloc_mutex_rank_exclusive); 257bdf38e5Schristos } 267bdf38e5Schristos 277bdf38e5Schristos static void * 287bdf38e5Schristos hook_install_locked(hooks_t *to_install) { 297bdf38e5Schristos hooks_internal_t hooks_internal; 307bdf38e5Schristos for (int i = 0; i < HOOK_MAX; i++) { 317bdf38e5Schristos bool success = seq_try_load_hooks(&hooks_internal, &hooks[i]); 327bdf38e5Schristos /* We hold mu; no concurrent access. */ 337bdf38e5Schristos assert(success); 347bdf38e5Schristos if (!hooks_internal.in_use) { 357bdf38e5Schristos hooks_internal.hooks = *to_install; 367bdf38e5Schristos hooks_internal.in_use = true; 377bdf38e5Schristos seq_store_hooks(&hooks[i], &hooks_internal); 387bdf38e5Schristos atomic_store_u(&nhooks, 397bdf38e5Schristos atomic_load_u(&nhooks, ATOMIC_RELAXED) + 1, 407bdf38e5Schristos ATOMIC_RELAXED); 417bdf38e5Schristos return &hooks[i]; 427bdf38e5Schristos } 437bdf38e5Schristos } 447bdf38e5Schristos return NULL; 457bdf38e5Schristos } 467bdf38e5Schristos 477bdf38e5Schristos void * 487bdf38e5Schristos hook_install(tsdn_t *tsdn, hooks_t *to_install) { 497bdf38e5Schristos malloc_mutex_lock(tsdn, &hooks_mu); 507bdf38e5Schristos void *ret = hook_install_locked(to_install); 517bdf38e5Schristos if (ret != NULL) { 527bdf38e5Schristos tsd_global_slow_inc(tsdn); 537bdf38e5Schristos } 547bdf38e5Schristos malloc_mutex_unlock(tsdn, &hooks_mu); 557bdf38e5Schristos return ret; 567bdf38e5Schristos } 577bdf38e5Schristos 587bdf38e5Schristos static void 597bdf38e5Schristos hook_remove_locked(seq_hooks_t *to_remove) { 607bdf38e5Schristos hooks_internal_t hooks_internal; 617bdf38e5Schristos bool success = seq_try_load_hooks(&hooks_internal, to_remove); 627bdf38e5Schristos /* We hold mu; no concurrent access. */ 637bdf38e5Schristos assert(success); 647bdf38e5Schristos /* Should only remove hooks that were added. */ 657bdf38e5Schristos assert(hooks_internal.in_use); 667bdf38e5Schristos hooks_internal.in_use = false; 677bdf38e5Schristos seq_store_hooks(to_remove, &hooks_internal); 687bdf38e5Schristos atomic_store_u(&nhooks, atomic_load_u(&nhooks, ATOMIC_RELAXED) - 1, 697bdf38e5Schristos ATOMIC_RELAXED); 707bdf38e5Schristos } 717bdf38e5Schristos 727bdf38e5Schristos void 737bdf38e5Schristos hook_remove(tsdn_t *tsdn, void *opaque) { 747bdf38e5Schristos if (config_debug) { 757bdf38e5Schristos char *hooks_begin = (char *)&hooks[0]; 767bdf38e5Schristos char *hooks_end = (char *)&hooks[HOOK_MAX]; 777bdf38e5Schristos char *hook = (char *)opaque; 787bdf38e5Schristos assert(hooks_begin <= hook && hook < hooks_end 797bdf38e5Schristos && (hook - hooks_begin) % sizeof(seq_hooks_t) == 0); 807bdf38e5Schristos } 817bdf38e5Schristos malloc_mutex_lock(tsdn, &hooks_mu); 827bdf38e5Schristos hook_remove_locked((seq_hooks_t *)opaque); 837bdf38e5Schristos tsd_global_slow_dec(tsdn); 847bdf38e5Schristos malloc_mutex_unlock(tsdn, &hooks_mu); 857bdf38e5Schristos } 867bdf38e5Schristos 877bdf38e5Schristos #define FOR_EACH_HOOK_BEGIN(hooks_internal_ptr) \ 887bdf38e5Schristos for (int for_each_hook_counter = 0; \ 897bdf38e5Schristos for_each_hook_counter < HOOK_MAX; \ 907bdf38e5Schristos for_each_hook_counter++) { \ 917bdf38e5Schristos bool for_each_hook_success = seq_try_load_hooks( \ 927bdf38e5Schristos (hooks_internal_ptr), &hooks[for_each_hook_counter]); \ 937bdf38e5Schristos if (!for_each_hook_success) { \ 947bdf38e5Schristos continue; \ 957bdf38e5Schristos } \ 967bdf38e5Schristos if (!(hooks_internal_ptr)->in_use) { \ 977bdf38e5Schristos continue; \ 987bdf38e5Schristos } 997bdf38e5Schristos #define FOR_EACH_HOOK_END \ 1007bdf38e5Schristos } 1017bdf38e5Schristos 1027bdf38e5Schristos static bool * 103*05ba6793Schristos hook_reentrantp(void) { 1047bdf38e5Schristos /* 1057bdf38e5Schristos * We prevent user reentrancy within hooks. This is basically just a 1067bdf38e5Schristos * thread-local bool that triggers an early-exit. 1077bdf38e5Schristos * 1087bdf38e5Schristos * We don't fold in_hook into reentrancy. There are two reasons for 1097bdf38e5Schristos * this: 1107bdf38e5Schristos * - Right now, we turn on reentrancy during things like extent hook 1117bdf38e5Schristos * execution. Allocating during extent hooks is not officially 1127bdf38e5Schristos * supported, but we don't want to break it for the time being. These 1137bdf38e5Schristos * sorts of allocations should probably still be hooked, though. 1147bdf38e5Schristos * - If a hook allocates, we may want it to be relatively fast (after 1157bdf38e5Schristos * all, it executes on every allocator operation). Turning on 1167bdf38e5Schristos * reentrancy is a fairly heavyweight mode (disabling tcache, 1177bdf38e5Schristos * redirecting to arena 0, etc.). It's possible we may one day want 1187bdf38e5Schristos * to turn on reentrant mode here, if it proves too difficult to keep 1197bdf38e5Schristos * this working. But that's fairly easy for us to see; OTOH, people 1207bdf38e5Schristos * not using hooks because they're too slow is easy for us to miss. 1217bdf38e5Schristos * 1227bdf38e5Schristos * The tricky part is 1237bdf38e5Schristos * that this code might get invoked even if we don't have access to tsd. 1247bdf38e5Schristos * This function mimics getting a pointer to thread-local data, except 1257bdf38e5Schristos * that it might secretly return a pointer to some global data if we 1267bdf38e5Schristos * know that the caller will take the early-exit path. 1277bdf38e5Schristos * If we return a bool that indicates that we are reentrant, then the 1287bdf38e5Schristos * caller will go down the early exit path, leaving the global 1297bdf38e5Schristos * untouched. 1307bdf38e5Schristos */ 1317bdf38e5Schristos static bool in_hook_global = true; 1327bdf38e5Schristos tsdn_t *tsdn = tsdn_fetch(); 1337bdf38e5Schristos bool *in_hook = tsdn_in_hookp_get(tsdn); 1347bdf38e5Schristos if (in_hook!= NULL) { 1357bdf38e5Schristos return in_hook; 1367bdf38e5Schristos } 1377bdf38e5Schristos return &in_hook_global; 1387bdf38e5Schristos } 1397bdf38e5Schristos 1407bdf38e5Schristos #define HOOK_PROLOGUE \ 1417bdf38e5Schristos if (likely(atomic_load_u(&nhooks, ATOMIC_RELAXED) == 0)) { \ 1427bdf38e5Schristos return; \ 1437bdf38e5Schristos } \ 1447bdf38e5Schristos bool *in_hook = hook_reentrantp(); \ 1457bdf38e5Schristos if (*in_hook) { \ 1467bdf38e5Schristos return; \ 1477bdf38e5Schristos } \ 1487bdf38e5Schristos *in_hook = true; 1497bdf38e5Schristos 1507bdf38e5Schristos #define HOOK_EPILOGUE \ 1517bdf38e5Schristos *in_hook = false; 1527bdf38e5Schristos 1537bdf38e5Schristos void 1547bdf38e5Schristos hook_invoke_alloc(hook_alloc_t type, void *result, uintptr_t result_raw, 1557bdf38e5Schristos uintptr_t args_raw[3]) { 1567bdf38e5Schristos HOOK_PROLOGUE 1577bdf38e5Schristos 1587bdf38e5Schristos hooks_internal_t hook; 1597bdf38e5Schristos FOR_EACH_HOOK_BEGIN(&hook) 1607bdf38e5Schristos hook_alloc h = hook.hooks.alloc_hook; 1617bdf38e5Schristos if (h != NULL) { 1627bdf38e5Schristos h(hook.hooks.extra, type, result, result_raw, args_raw); 1637bdf38e5Schristos } 1647bdf38e5Schristos FOR_EACH_HOOK_END 1657bdf38e5Schristos 1667bdf38e5Schristos HOOK_EPILOGUE 1677bdf38e5Schristos } 1687bdf38e5Schristos 1697bdf38e5Schristos void 1707bdf38e5Schristos hook_invoke_dalloc(hook_dalloc_t type, void *address, uintptr_t args_raw[3]) { 1717bdf38e5Schristos HOOK_PROLOGUE 1727bdf38e5Schristos hooks_internal_t hook; 1737bdf38e5Schristos FOR_EACH_HOOK_BEGIN(&hook) 1747bdf38e5Schristos hook_dalloc h = hook.hooks.dalloc_hook; 1757bdf38e5Schristos if (h != NULL) { 1767bdf38e5Schristos h(hook.hooks.extra, type, address, args_raw); 1777bdf38e5Schristos } 1787bdf38e5Schristos FOR_EACH_HOOK_END 1797bdf38e5Schristos HOOK_EPILOGUE 1807bdf38e5Schristos } 1817bdf38e5Schristos 1827bdf38e5Schristos void 1837bdf38e5Schristos hook_invoke_expand(hook_expand_t type, void *address, size_t old_usize, 1847bdf38e5Schristos size_t new_usize, uintptr_t result_raw, uintptr_t args_raw[4]) { 1857bdf38e5Schristos HOOK_PROLOGUE 1867bdf38e5Schristos hooks_internal_t hook; 1877bdf38e5Schristos FOR_EACH_HOOK_BEGIN(&hook) 1887bdf38e5Schristos hook_expand h = hook.hooks.expand_hook; 1897bdf38e5Schristos if (h != NULL) { 1907bdf38e5Schristos h(hook.hooks.extra, type, address, old_usize, new_usize, 1917bdf38e5Schristos result_raw, args_raw); 1927bdf38e5Schristos } 1937bdf38e5Schristos FOR_EACH_HOOK_END 1947bdf38e5Schristos HOOK_EPILOGUE 1957bdf38e5Schristos } 196