/* $NetBSD: mem.c,v 1.1 2024/02/18 20:57:49 christos Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ /*! \file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBXML2 #include #define ISC_XMLCHAR (const xmlChar *) #endif /* HAVE_LIBXML2 */ #ifdef HAVE_JSON_C #include #endif /* HAVE_JSON_C */ #include "mem_p.h" #define MCTXLOCK(m) LOCK(&m->lock) #define MCTXUNLOCK(m) UNLOCK(&m->lock) #ifndef ISC_MEM_DEBUGGING #define ISC_MEM_DEBUGGING 0 #endif /* ifndef ISC_MEM_DEBUGGING */ LIBISC_EXTERNAL_DATA unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING; LIBISC_EXTERNAL_DATA unsigned int isc_mem_defaultflags = ISC_MEMFLAG_DEFAULT; /* * Constants. */ #define DEF_MAX_SIZE 1100 #define DEF_MEM_TARGET 4096 #define ALIGNMENT_SIZE \ 8U /*%< must be a power of 2, also update lib/dns/rbt.c */ #define NUM_BASIC_BLOCKS 64 /*%< must be > 1 */ #define TABLE_INCREMENT 1024 #define DEBUG_TABLE_COUNT 512U /* * Types. */ typedef struct isc__mem isc__mem_t; typedef struct isc__mempool isc__mempool_t; #if ISC_MEM_TRACKLINES typedef struct debuglink debuglink_t; struct debuglink { ISC_LINK(debuglink_t) link; const void *ptr; size_t size; const char *file; unsigned int line; }; typedef ISC_LIST(debuglink_t) debuglist_t; #define FLARG_PASS , file, line #define FLARG , const char *file, unsigned int line #else /* if ISC_MEM_TRACKLINES */ #define FLARG_PASS #define FLARG #endif /* if ISC_MEM_TRACKLINES */ typedef struct element element; struct element { element *next; }; typedef struct { /*! * This structure must be ALIGNMENT_SIZE bytes. */ union { size_t size; isc__mem_t *ctx; char bytes[ALIGNMENT_SIZE]; } u; } size_info; struct stats { unsigned long gets; unsigned long totalgets; unsigned long blocks; unsigned long freefrags; }; #define MEM_MAGIC ISC_MAGIC('M', 'e', 'm', 'C') #define VALID_CONTEXT(c) ISC_MAGIC_VALID(c, MEM_MAGIC) /* List of all active memory contexts. */ static ISC_LIST(isc__mem_t) contexts; static isc_once_t init_once = ISC_ONCE_INIT; static isc_once_t shut_once = ISC_ONCE_INIT; static isc_mutex_t contextslock; /*% * Total size of lost memory due to a bug of external library. * Locked by the global lock. */ static uint64_t totallost; /*% * Memory allocation and free function definitions. * isc__memalloc_t must deal with memory allocation failure * and must never return NULL. */ typedef void *(*isc__memalloc_t)(size_t); typedef void (*isc__memfree_t)(void *); struct isc__mem { isc_mem_t common; unsigned int flags; isc_mutex_t lock; isc__memalloc_t memalloc; isc__memfree_t memfree; size_t max_size; bool checkfree; struct stats *stats; isc_refcount_t references; char name[16]; void *tag; size_t total; size_t inuse; size_t maxinuse; size_t malloced; size_t maxmalloced; size_t hi_water; size_t lo_water; bool hi_called; bool is_overmem; isc_mem_water_t water; void *water_arg; ISC_LIST(isc__mempool_t) pools; unsigned int poolcnt; /* ISC_MEMFLAG_INTERNAL */ size_t mem_target; element **freelists; element *basic_blocks; unsigned char **basic_table; unsigned int basic_table_count; unsigned int basic_table_size; unsigned char *lowest; unsigned char *highest; #if ISC_MEM_TRACKLINES debuglist_t *debuglist; size_t debuglistcnt; #endif /* if ISC_MEM_TRACKLINES */ ISC_LINK(isc__mem_t) link; }; #define MEMPOOL_MAGIC ISC_MAGIC('M', 'E', 'M', 'p') #define VALID_MEMPOOL(c) ISC_MAGIC_VALID(c, MEMPOOL_MAGIC) struct isc__mempool { /* always unlocked */ isc_mempool_t common; /*%< common header of mempool's */ isc__mem_t *mctx; /*%< our memory context */ ISC_LINK(isc__mempool_t) link; /*%< next pool in this mem context */ element *items; /*%< low water item list */ size_t size; /*%< size of each item on this pool */ unsigned int maxalloc; /*%< max number of items allowed */ unsigned int allocated; /*%< # of items currently given out */ unsigned int freecount; /*%< # of items on reserved list */ unsigned int freemax; /*%< # of items allowed on free list */ unsigned int fillcount; /*%< # of items to fetch on each fill */ /*%< Stats only. */ unsigned int gets; /*%< # of requests to this pool */ /*%< Debugging only. */ #if ISC_MEMPOOL_NAMES char name[16]; /*%< printed name in stats reports */ #endif /* if ISC_MEMPOOL_NAMES */ }; /* * Private Inline-able. */ #if !ISC_MEM_TRACKLINES #define ADD_TRACE(a, b, c, d, e) #define DELETE_TRACE(a, b, c, d, e) #define ISC_MEMFUNC_SCOPE #else /* if !ISC_MEM_TRACKLINES */ #define TRACE_OR_RECORD (ISC_MEM_DEBUGTRACE | ISC_MEM_DEBUGRECORD) #define ADD_TRACE(a, b, c, d, e) \ do { \ if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0 && \ b != NULL)) \ add_trace_entry(a, b, c, d, e); \ } while (0) #define DELETE_TRACE(a, b, c, d, e) \ do { \ if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0 && \ b != NULL)) \ delete_trace_entry(a, b, c, d, e); \ } while (0) static void print_active(isc__mem_t *ctx, FILE *out); #endif /* ISC_MEM_TRACKLINES */ static void * isc___mem_get(isc_mem_t *ctx, size_t size FLARG); static void isc___mem_put(isc_mem_t *ctx, void *ptr, size_t size FLARG); static void isc___mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size FLARG); static void * isc___mem_allocate(isc_mem_t *ctx, size_t size FLARG); static void * isc___mem_reallocate(isc_mem_t *ctx, void *ptr, size_t size FLARG); static char * isc___mem_strdup(isc_mem_t *mctx, const char *s FLARG); static char * isc___mem_strndup(isc_mem_t *mctx0, const char *s, size_t size FLARG); static void isc___mem_free(isc_mem_t *ctx, void *ptr FLARG); static isc_memmethods_t memmethods = { isc___mem_get, isc___mem_put, isc___mem_putanddetach, isc___mem_allocate, isc___mem_reallocate, isc___mem_strdup, isc___mem_strndup, isc___mem_free, }; #if ISC_MEM_TRACKLINES /*! * mctx must be locked. */ static void add_trace_entry(isc__mem_t *mctx, const void *ptr, size_t size FLARG) { debuglink_t *dl; uint32_t hash; uint32_t idx; if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { fprintf(stderr, "add %p size %zu file %s line %u mctx %p\n", ptr, size, file, line, mctx); } if (mctx->debuglist == NULL) { return; } #ifdef __COVERITY__ /* * Use simple conversion from pointer to hash to avoid * tainting 'ptr' due to byte swap in isc_hash_function. */ hash = (uintptr_t)ptr >> 3; #else hash = isc_hash_function(&ptr, sizeof(ptr), true); #endif idx = hash % DEBUG_TABLE_COUNT; dl = malloc(sizeof(debuglink_t)); INSIST(dl != NULL); mctx->malloced += sizeof(debuglink_t); if (mctx->malloced > mctx->maxmalloced) { mctx->maxmalloced = mctx->malloced; } ISC_LINK_INIT(dl, link); dl->ptr = ptr; dl->size = size; dl->file = file; dl->line = line; ISC_LIST_PREPEND(mctx->debuglist[idx], dl, link); mctx->debuglistcnt++; } static void delete_trace_entry(isc__mem_t *mctx, const void *ptr, size_t size, const char *file, unsigned int line) { debuglink_t *dl; uint32_t hash; uint32_t idx; if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { fprintf(stderr, "del %p size %zu file %s line %u mctx %p\n", ptr, size, file, line, mctx); } if (mctx->debuglist == NULL) { return; } #ifdef __COVERITY__ /* * Use simple conversion from pointer to hash to avoid * tainting 'ptr' due to byte swap in isc_hash_function. */ hash = (uintptr_t)ptr >> 3; #else hash = isc_hash_function(&ptr, sizeof(ptr), true); #endif idx = hash % DEBUG_TABLE_COUNT; dl = ISC_LIST_HEAD(mctx->debuglist[idx]); while (ISC_LIKELY(dl != NULL)) { if (ISC_UNLIKELY(dl->ptr == ptr)) { ISC_LIST_UNLINK(mctx->debuglist[idx], dl, link); mctx->malloced -= sizeof(*dl); free(dl); return; } dl = ISC_LIST_NEXT(dl, link); } /* * If we get here, we didn't find the item on the list. We're * screwed. */ UNREACHABLE(); } #endif /* ISC_MEM_TRACKLINES */ static size_t rmsize(size_t size) { /* * round down to ALIGNMENT_SIZE */ return (size & (~(ALIGNMENT_SIZE - 1))); } static size_t quantize(size_t size) { /*! * Round up the result in order to get a size big * enough to satisfy the request and be aligned on ALIGNMENT_SIZE * byte boundaries. */ if (size == 0U) { return (ALIGNMENT_SIZE); } return ((size + ALIGNMENT_SIZE - 1) & (~(ALIGNMENT_SIZE - 1))); } static void more_basic_blocks(isc__mem_t *ctx) { void *tmp; unsigned char *curr, *next; unsigned char *first, *last; unsigned char **table; unsigned int table_size; /* Require: we hold the context lock. */ INSIST(ctx->basic_table_count <= ctx->basic_table_size); if (ctx->basic_table_count == ctx->basic_table_size) { table_size = ctx->basic_table_size + TABLE_INCREMENT; table = (ctx->memalloc)(table_size * sizeof(unsigned char *)); ctx->malloced += table_size * sizeof(unsigned char *); if (ctx->malloced > ctx->maxmalloced) { ctx->maxmalloced = ctx->malloced; } if (ctx->basic_table_size != 0) { memmove(table, ctx->basic_table, ctx->basic_table_size * sizeof(unsigned char *)); (ctx->memfree)(ctx->basic_table); ctx->malloced -= ctx->basic_table_size * sizeof(unsigned char *); } ctx->basic_table = table; ctx->basic_table_size = table_size; } tmp = (ctx->memalloc)(NUM_BASIC_BLOCKS * ctx->mem_target); ctx->total += NUM_BASIC_BLOCKS * ctx->mem_target; ctx->basic_table[ctx->basic_table_count] = tmp; ctx->basic_table_count++; ctx->malloced += NUM_BASIC_BLOCKS * ctx->mem_target; if (ctx->malloced > ctx->maxmalloced) { ctx->maxmalloced = ctx->malloced; } curr = tmp; next = curr + ctx->mem_target; for (int i = 0; i < (NUM_BASIC_BLOCKS - 1); i++) { ((element *)curr)->next = (element *)next; curr = next; next += ctx->mem_target; } /* * curr is now pointing at the last block in the * array. */ ((element *)curr)->next = NULL; first = tmp; last = first + NUM_BASIC_BLOCKS * ctx->mem_target - 1; if (first < ctx->lowest || ctx->lowest == NULL) { ctx->lowest = first; } if (last > ctx->highest) { ctx->highest = last; } ctx->basic_blocks = tmp; } static void more_frags(isc__mem_t *ctx, size_t new_size) { int frags; size_t total_size; void *tmp; unsigned char *curr, *next; /*! * Try to get more fragments by chopping up a basic block. */ if (ctx->basic_blocks == NULL) { more_basic_blocks(ctx); } INSIST(ctx->basic_blocks != NULL); total_size = ctx->mem_target; tmp = ctx->basic_blocks; ctx->basic_blocks = ctx->basic_blocks->next; frags = (int)(total_size / new_size); ctx->stats[new_size].blocks++; ctx->stats[new_size].freefrags += frags; /* * Set up a linked-list of blocks of size * "new_size". */ curr = tmp; next = curr + new_size; total_size -= new_size; for (int i = 0; i < (frags - 1); i++) { ((element *)curr)->next = (element *)next; curr = next; next += new_size; total_size -= new_size; } /* * Add the remaining fragment of the basic block to a free list. */ total_size = rmsize(total_size); if (total_size > 0U) { ((element *)next)->next = ctx->freelists[total_size]; ctx->freelists[total_size] = (element *)next; ctx->stats[total_size].freefrags++; } /* * curr is now pointing at the last block in the * array. */ ((element *)curr)->next = NULL; ctx->freelists[new_size] = tmp; } static void * mem_getunlocked(isc__mem_t *ctx, size_t size) { size_t new_size = quantize(size); void *ret; if (new_size >= ctx->max_size) { /* * memget() was called on something beyond our upper limit. */ ret = (ctx->memalloc)(size); ctx->total += size; ctx->inuse += size; ctx->stats[ctx->max_size].gets++; ctx->stats[ctx->max_size].totalgets++; ctx->malloced += size; if (ctx->malloced > ctx->maxmalloced) { ctx->maxmalloced = ctx->malloced; } /* * If we don't set new_size to size, then the * ISC_MEMFLAG_FILL code might write over bytes we don't * own. */ new_size = size; goto done; } /* * If there are no blocks in the free list for this size, get a chunk * of memory and then break it up into "new_size"-sized blocks, adding * them to the free list. */ if (ctx->freelists[new_size] == NULL) { more_frags(ctx, new_size); } INSIST(ctx->freelists[new_size] != NULL); /* * The free list uses the "rounded-up" size "new_size". */ ret = ctx->freelists[new_size]; ctx->freelists[new_size] = ctx->freelists[new_size]->next; /* * The stats[] uses the _actual_ "size" requested by the * caller, with the caveat (in the code above) that "size" >= the * max. size (max_size) ends up getting recorded as a call to * max_size. */ ctx->stats[size].gets++; ctx->stats[size].totalgets++; ctx->stats[new_size].freefrags--; ctx->inuse += new_size; done: if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0) && ISC_LIKELY(ret != NULL)) { memset(ret, 0xbe, new_size); /* Mnemonic for "beef". */ } return (ret); } #if ISC_MEM_CHECKOVERRUN static void check_overrun(void *mem, size_t size, size_t new_size) { unsigned char *cp; cp = (unsigned char *)mem; cp += size; while (size < new_size) { INSIST(*cp == 0xbe); cp++; size++; } } #endif /* if ISC_MEM_CHECKOVERRUN */ /* coverity[+free : arg-1] */ static void mem_putunlocked(isc__mem_t *ctx, void *mem, size_t size) { size_t new_size = quantize(size); if (new_size >= ctx->max_size) { /* * memput() called on something beyond our upper limit. */ if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) { memset(mem, 0xde, size); /* Mnemonic for "dead". */ } (ctx->memfree)(mem); INSIST(ctx->stats[ctx->max_size].gets != 0U); ctx->stats[ctx->max_size].gets--; INSIST(size <= ctx->inuse); ctx->inuse -= size; ctx->malloced -= size; return; } if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) { #if ISC_MEM_CHECKOVERRUN check_overrun(mem, size, new_size); #endif /* if ISC_MEM_CHECKOVERRUN */ memset(mem, 0xde, new_size); /* Mnemonic for "dead". */ } /* * The free list uses the "rounded-up" size "new_size". */ ((element *)mem)->next = ctx->freelists[new_size]; ctx->freelists[new_size] = (element *)mem; /* * The stats[] uses the _actual_ "size" requested by the * caller, with the caveat (in the code above) that "size" >= the * max. size (max_size) ends up getting recorded as a call to * max_size. */ INSIST(ctx->stats[size].gets != 0U); ctx->stats[size].gets--; ctx->stats[new_size].freefrags++; ctx->inuse -= new_size; } /*! * Perform a malloc, doing memory filling and overrun detection as necessary. */ static void * mem_get(isc__mem_t *ctx, size_t size) { char *ret; #if ISC_MEM_CHECKOVERRUN size += 1; #endif /* if ISC_MEM_CHECKOVERRUN */ ret = (ctx->memalloc)(size); if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) { if (ISC_LIKELY(ret != NULL)) { memset(ret, 0xbe, size); /* Mnemonic for "beef". */ } } #if ISC_MEM_CHECKOVERRUN else { if (ISC_LIKELY(ret != NULL)) { ret[size - 1] = 0xbe; } } #endif /* if ISC_MEM_CHECKOVERRUN */ return (ret); } /*! * Perform a free, doing memory filling and overrun detection as necessary. */ /* coverity[+free : arg-1] */ static void mem_put(isc__mem_t *ctx, void *mem, size_t size) { #if ISC_MEM_CHECKOVERRUN INSIST(((unsigned char *)mem)[size] == 0xbe); size += 1; #endif /* if ISC_MEM_CHECKOVERRUN */ if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) { memset(mem, 0xde, size); /* Mnemonic for "dead". */ } (ctx->memfree)(mem); } /*! * Update internal counters after a memory get. */ static void mem_getstats(isc__mem_t *ctx, size_t size) { ctx->total += size; ctx->inuse += size; if (size > ctx->max_size) { ctx->stats[ctx->max_size].gets++; ctx->stats[ctx->max_size].totalgets++; } else { ctx->stats[size].gets++; ctx->stats[size].totalgets++; } #if ISC_MEM_CHECKOVERRUN size += 1; #endif /* if ISC_MEM_CHECKOVERRUN */ ctx->malloced += size; if (ctx->malloced > ctx->maxmalloced) { ctx->maxmalloced = ctx->malloced; } } /*! * Update internal counters after a memory put. */ static void mem_putstats(isc__mem_t *ctx, void *ptr, size_t size) { UNUSED(ptr); INSIST(ctx->inuse >= size); ctx->inuse -= size; if (size > ctx->max_size) { INSIST(ctx->stats[ctx->max_size].gets > 0U); ctx->stats[ctx->max_size].gets--; } else { INSIST(ctx->stats[size].gets > 0U); ctx->stats[size].gets--; } #if ISC_MEM_CHECKOVERRUN size += 1; #endif /* if ISC_MEM_CHECKOVERRUN */ ctx->malloced -= size; } /* * Private. */ static void * default_memalloc(size_t size) { void *ptr; ptr = malloc(size); /* * If the space cannot be allocated, a null pointer is returned. If the * size of the space requested is zero, the behavior is * implementation-defined: either a null pointer is returned, or the * behavior is as if the size were some nonzero value, except that the * returned pointer shall not be used to access an object. * [ISO9899 ยง 7.22.3] * * [ISO9899] * ISO/IEC WG 9899:2011: Programming languages - C. * International Organization for Standardization, Geneva, * Switzerland. * http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf */ if (ptr == NULL && size != 0) { char strbuf[ISC_STRERRORSIZE]; strerror_r(errno, strbuf, sizeof(strbuf)); isc_error_fatal(__FILE__, __LINE__, "malloc failed: %s", strbuf); } return (ptr); } static void default_memfree(void *ptr) { free(ptr); } static void mem_initialize(void) { isc_mutex_init(&contextslock); ISC_LIST_INIT(contexts); totallost = 0; } void isc__mem_initialize(void) { RUNTIME_CHECK(isc_once_do(&init_once, mem_initialize) == ISC_R_SUCCESS); } static void mem_shutdown(void) { isc__mem_checkdestroyed(); isc_mutex_destroy(&contextslock); } void isc__mem_shutdown(void) { RUNTIME_CHECK(isc_once_do(&shut_once, mem_shutdown) == ISC_R_SUCCESS); } static void mem_create(isc_mem_t **ctxp, unsigned int flags) { REQUIRE(ctxp != NULL && *ctxp == NULL); #if __SANITIZE_ADDRESS__ REQUIRE((flags & ISC_MEMFLAG_INTERNAL) == 0); #endif isc__mem_t *ctx; isc_enable_constructors(); STATIC_ASSERT((ALIGNMENT_SIZE & (ALIGNMENT_SIZE - 1)) == 0, "wrong alignment size"); ctx = (default_memalloc)(sizeof(*ctx)); isc_mutex_init(&ctx->lock); ctx->max_size = DEF_MAX_SIZE; ctx->flags = flags; isc_refcount_init(&ctx->references, 1); memset(ctx->name, 0, sizeof(ctx->name)); ctx->tag = NULL; ctx->total = 0; ctx->inuse = 0; ctx->maxinuse = 0; ctx->malloced = sizeof(*ctx); ctx->maxmalloced = sizeof(*ctx); ctx->hi_water = 0; ctx->lo_water = 0; ctx->hi_called = false; ctx->is_overmem = false; ctx->water = NULL; ctx->water_arg = NULL; ctx->common.impmagic = MEM_MAGIC; ctx->common.magic = ISCAPI_MCTX_MAGIC; ctx->common.methods = (isc_memmethods_t *)&memmethods; ctx->memalloc = default_memalloc; ctx->memfree = default_memfree; ctx->stats = NULL; ctx->checkfree = true; #if ISC_MEM_TRACKLINES ctx->debuglist = NULL; ctx->debuglistcnt = 0; #endif /* if ISC_MEM_TRACKLINES */ ISC_LIST_INIT(ctx->pools); ctx->poolcnt = 0; ctx->freelists = NULL; ctx->basic_blocks = NULL; ctx->basic_table = NULL; ctx->basic_table_count = 0; ctx->basic_table_size = 0; ctx->lowest = NULL; ctx->highest = NULL; ctx->stats = (ctx->memalloc)((ctx->max_size + 1) * sizeof(struct stats)); memset(ctx->stats, 0, (ctx->max_size + 1) * sizeof(struct stats)); ctx->malloced += (ctx->max_size + 1) * sizeof(struct stats); ctx->maxmalloced += (ctx->max_size + 1) * sizeof(struct stats); if ((flags & ISC_MEMFLAG_INTERNAL) != 0) { ctx->mem_target = DEF_MEM_TARGET; ctx->freelists = (ctx->memalloc)(ctx->max_size * sizeof(element *)); memset(ctx->freelists, 0, ctx->max_size * sizeof(element *)); ctx->malloced += ctx->max_size * sizeof(element *); ctx->maxmalloced += ctx->max_size * sizeof(element *); } #if ISC_MEM_TRACKLINES if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGRECORD) != 0)) { unsigned int i; ctx->debuglist = (ctx->memalloc)( (DEBUG_TABLE_COUNT * sizeof(debuglist_t))); for (i = 0; i < DEBUG_TABLE_COUNT; i++) { ISC_LIST_INIT(ctx->debuglist[i]); } ctx->malloced += DEBUG_TABLE_COUNT * sizeof(debuglist_t); ctx->maxmalloced += DEBUG_TABLE_COUNT * sizeof(debuglist_t); } #endif /* if ISC_MEM_TRACKLINES */ LOCK(&contextslock); ISC_LIST_INITANDAPPEND(contexts, ctx, link); UNLOCK(&contextslock); *ctxp = (isc_mem_t *)ctx; } /* * Public. */ static void destroy(isc__mem_t *ctx) { unsigned int i; LOCK(&contextslock); ISC_LIST_UNLINK(contexts, ctx, link); totallost += ctx->inuse; UNLOCK(&contextslock); ctx->common.impmagic = 0; ctx->common.magic = 0; INSIST(ISC_LIST_EMPTY(ctx->pools)); #if ISC_MEM_TRACKLINES if (ISC_UNLIKELY(ctx->debuglist != NULL)) { debuglink_t *dl; for (i = 0; i < DEBUG_TABLE_COUNT; i++) { for (dl = ISC_LIST_HEAD(ctx->debuglist[i]); dl != NULL; dl = ISC_LIST_HEAD(ctx->debuglist[i])) { if (ctx->checkfree && dl->ptr != NULL) { print_active(ctx, stderr); } INSIST(!ctx->checkfree || dl->ptr == NULL); ISC_LIST_UNLINK(ctx->debuglist[i], dl, link); free(dl); ctx->malloced -= sizeof(*dl); } } (ctx->memfree)(ctx->debuglist); ctx->malloced -= DEBUG_TABLE_COUNT * sizeof(debuglist_t); } #endif /* if ISC_MEM_TRACKLINES */ if (ctx->checkfree) { for (i = 0; i <= ctx->max_size; i++) { if (ctx->stats[i].gets != 0U) { fprintf(stderr, "Failing assertion due to probable " "leaked memory in context %p (\"%s\") " "(stats[%u].gets == %lu).\n", ctx, ctx->name, i, ctx->stats[i].gets); #if ISC_MEM_TRACKLINES print_active(ctx, stderr); #endif /* if ISC_MEM_TRACKLINES */ INSIST(ctx->stats[i].gets == 0U); } } } (ctx->memfree)(ctx->stats); ctx->malloced -= (ctx->max_size + 1) * sizeof(struct stats); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { for (i = 0; i < ctx->basic_table_count; i++) { (ctx->memfree)(ctx->basic_table[i]); ctx->malloced -= NUM_BASIC_BLOCKS * ctx->mem_target; } (ctx->memfree)(ctx->freelists); ctx->malloced -= ctx->max_size * sizeof(element *); if (ctx->basic_table != NULL) { (ctx->memfree)(ctx->basic_table); ctx->malloced -= ctx->basic_table_size * sizeof(unsigned char *); } } isc_mutex_destroy(&ctx->lock); ctx->malloced -= sizeof(*ctx); if (ctx->checkfree) { INSIST(ctx->malloced == 0); } (ctx->memfree)(ctx); } void isc_mem_attach(isc_mem_t *source0, isc_mem_t **targetp) { REQUIRE(VALID_CONTEXT(source0)); REQUIRE(targetp != NULL && *targetp == NULL); isc__mem_t *source = (isc__mem_t *)source0; isc_refcount_increment(&source->references); *targetp = (isc_mem_t *)source; } void isc_mem_detach(isc_mem_t **ctxp) { REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); isc__mem_t *ctx = (isc__mem_t *)*ctxp; *ctxp = NULL; if (isc_refcount_decrement(&ctx->references) == 1) { isc_refcount_destroy(&ctx->references); destroy(ctx); } } /* * isc_mem_putanddetach() is the equivalent of: * * mctx = NULL; * isc_mem_attach(ptr->mctx, &mctx); * isc_mem_detach(&ptr->mctx); * isc_mem_put(mctx, ptr, sizeof(*ptr); * isc_mem_detach(&mctx); */ void isc___mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size FLARG) { REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); REQUIRE(ptr != NULL); isc__mem_t *ctx = (isc__mem_t *)*ctxp; *ctxp = NULL; if (ISC_UNLIKELY((isc_mem_debugging & (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0)) { if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) { size_info *si = &(((size_info *)ptr)[-1]); size_t oldsize = si->u.size - ALIGNMENT_SIZE; if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { oldsize -= ALIGNMENT_SIZE; } INSIST(oldsize == size); } isc__mem_free((isc_mem_t *)ctx, ptr FLARG_PASS); goto destroy; } MCTXLOCK(ctx); DELETE_TRACE(ctx, ptr, size, file, line); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { mem_putunlocked(ctx, ptr, size); } else { mem_putstats(ctx, ptr, size); mem_put(ctx, ptr, size); } MCTXUNLOCK(ctx); destroy: if (isc_refcount_decrement(&ctx->references) == 1) { isc_refcount_destroy(&ctx->references); destroy(ctx); } } void isc_mem_destroy(isc_mem_t **ctxp) { /* * This routine provides legacy support for callers who use mctxs * without attaching/detaching. */ REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); isc__mem_t *ctx = (isc__mem_t *)*ctxp; #if ISC_MEM_TRACKLINES if (isc_refcount_decrement(&ctx->references) > 1) { print_active(ctx, stderr); } #else /* if ISC_MEM_TRACKLINES */ isc_refcount_decrementz(&ctx->references); #endif /* if ISC_MEM_TRACKLINES */ isc_refcount_destroy(&ctx->references); destroy(ctx); *ctxp = NULL; } void * isc___mem_get(isc_mem_t *ctx0, size_t size FLARG) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; void *ptr; bool call_water = false; if (ISC_UNLIKELY((isc_mem_debugging & (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0)) { return (isc__mem_allocate(ctx0, size FLARG_PASS)); } if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { MCTXLOCK(ctx); ptr = mem_getunlocked(ctx, size); } else { ptr = mem_get(ctx, size); MCTXLOCK(ctx); if (ptr != NULL) { mem_getstats(ctx, size); } } ADD_TRACE(ctx, ptr, size, file, line); if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water) { ctx->is_overmem = true; if (!ctx->hi_called) { call_water = true; } } if (ctx->inuse > ctx->maxinuse) { ctx->maxinuse = ctx->inuse; if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) { fprintf(stderr, "maxinuse = %lu\n", (unsigned long)ctx->inuse); } } MCTXUNLOCK(ctx); if (call_water && (ctx->water != NULL)) { (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); } return (ptr); } void isc___mem_put(isc_mem_t *ctx0, void *ptr, size_t size FLARG) { REQUIRE(VALID_CONTEXT(ctx0)); REQUIRE(ptr != NULL); isc__mem_t *ctx = (isc__mem_t *)ctx0; bool call_water = false; size_info *si; size_t oldsize; if (ISC_UNLIKELY((isc_mem_debugging & (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0)) { if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) { si = &(((size_info *)ptr)[-1]); oldsize = si->u.size - ALIGNMENT_SIZE; if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { oldsize -= ALIGNMENT_SIZE; } INSIST(oldsize == size); } isc__mem_free((isc_mem_t *)ctx, ptr FLARG_PASS); return; } MCTXLOCK(ctx); DELETE_TRACE(ctx, ptr, size, file, line); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { mem_putunlocked(ctx, ptr, size); } else { mem_putstats(ctx, ptr, size); mem_put(ctx, ptr, size); } /* * The check against ctx->lo_water == 0 is for the condition * when the context was pushed over hi_water but then had * isc_mem_setwater() called with 0 for hi_water and lo_water. */ if ((ctx->inuse < ctx->lo_water) || (ctx->lo_water == 0U)) { ctx->is_overmem = false; if (ctx->hi_called) { call_water = true; } } MCTXUNLOCK(ctx); if (call_water && (ctx->water != NULL)) { (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); } } void isc_mem_waterack(isc_mem_t *ctx0, int flag) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; MCTXLOCK(ctx); if (flag == ISC_MEM_LOWATER) { ctx->hi_called = false; } else if (flag == ISC_MEM_HIWATER) { ctx->hi_called = true; } MCTXUNLOCK(ctx); } #if ISC_MEM_TRACKLINES static void print_active(isc__mem_t *mctx, FILE *out) { if (mctx->debuglist != NULL) { debuglink_t *dl; unsigned int i; bool found; fputs("Dump of all outstanding memory allocations:\n", out); found = false; for (i = 0; i < DEBUG_TABLE_COUNT; i++) { dl = ISC_LIST_HEAD(mctx->debuglist[i]); if (dl != NULL) { found = true; } while (dl != NULL) { if (dl->ptr != NULL) { fprintf(out, "\tptr %p size %zu file %s " "line %u\n", dl->ptr, dl->size, dl->file, dl->line); } dl = ISC_LIST_NEXT(dl, link); } } if (!found) { fputs("\tNone.\n", out); } } } #endif /* if ISC_MEM_TRACKLINES */ /* * Print the stats[] on the stream "out" with suitable formatting. */ void isc_mem_stats(isc_mem_t *ctx0, FILE *out) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; size_t i; const struct stats *s; const isc__mempool_t *pool; MCTXLOCK(ctx); for (i = 0; i <= ctx->max_size; i++) { s = &ctx->stats[i]; if (s->totalgets == 0U && s->gets == 0U) { continue; } fprintf(out, "%s%5lu: %11lu gets, %11lu rem", (i == ctx->max_size) ? ">=" : " ", (unsigned long)i, s->totalgets, s->gets); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0 && (s->blocks != 0U || s->freefrags != 0U)) { fprintf(out, " (%lu bl, %lu ff)", s->blocks, s->freefrags); } fputc('\n', out); } /* * Note that since a pool can be locked now, these stats might be * somewhat off if the pool is in active use at the time the stats * are dumped. The link fields are protected by the isc_mem_t's * lock, however, so walking this list and extracting integers from * stats fields is always safe. */ pool = ISC_LIST_HEAD(ctx->pools); if (pool != NULL) { fputs("[Pool statistics]\n", out); fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %10s %1s\n", "name", "size", "maxalloc", "allocated", "freecount", "freemax", "fillcount", "gets", "L"); } while (pool != NULL) { fprintf(out, "%15s %10lu %10u %10u %10u %10u %10u %10u %s\n", #if ISC_MEMPOOL_NAMES pool->name, #else /* if ISC_MEMPOOL_NAMES */ "(not tracked)", #endif /* if ISC_MEMPOOL_NAMES */ (unsigned long)pool->size, pool->maxalloc, pool->allocated, pool->freecount, pool->freemax, pool->fillcount, pool->gets, "N"); pool = ISC_LIST_NEXT(pool, link); } #if ISC_MEM_TRACKLINES print_active(ctx, out); #endif /* if ISC_MEM_TRACKLINES */ MCTXUNLOCK(ctx); } /* * Replacements for malloc() and free() -- they implicitly remember the * size of the object allocated (with some additional overhead). */ static void * mem_allocateunlocked(isc_mem_t *ctx0, size_t size) { isc__mem_t *ctx = (isc__mem_t *)ctx0; size_info *si; size += ALIGNMENT_SIZE; if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) { size += ALIGNMENT_SIZE; } if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { si = mem_getunlocked(ctx, size); } else { si = mem_get(ctx, size); } if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) { si->u.ctx = ctx; si++; } si->u.size = size; return (&si[1]); } void * isc___mem_allocate(isc_mem_t *ctx0, size_t size FLARG) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; size_info *si; bool call_water = false; MCTXLOCK(ctx); si = mem_allocateunlocked((isc_mem_t *)ctx, size); if (((ctx->flags & ISC_MEMFLAG_INTERNAL) == 0)) { mem_getstats(ctx, si[-1].u.size); } ADD_TRACE(ctx, si, si[-1].u.size, file, line); if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && !ctx->is_overmem) { ctx->is_overmem = true; } if (ctx->hi_water != 0U && !ctx->hi_called && ctx->inuse > ctx->hi_water) { ctx->hi_called = true; call_water = true; } if (ctx->inuse > ctx->maxinuse) { ctx->maxinuse = ctx->inuse; if (ISC_UNLIKELY(ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0)) { fprintf(stderr, "maxinuse = %lu\n", (unsigned long)ctx->inuse); } } MCTXUNLOCK(ctx); if (call_water) { (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); } return (si); } void * isc___mem_reallocate(isc_mem_t *ctx0, void *ptr, size_t size FLARG) { REQUIRE(VALID_CONTEXT(ctx0)); void *new_ptr = NULL; size_t oldsize, copysize; /* * This function emulates the realloc(3) standard library function: * - if size > 0, allocate new memory; and if ptr is non NULL, copy * as much of the old contents to the new buffer and free the old one. * Note that when allocation fails the original pointer is intact; * the caller must free it. * - if size is 0 and ptr is non NULL, simply free the given ptr. * - this function returns: * pointer to the newly allocated memory, or * NULL if allocation fails or doesn't happen. */ if (size > 0U) { new_ptr = isc__mem_allocate(ctx0, size FLARG_PASS); if (new_ptr != NULL && ptr != NULL) { oldsize = (((size_info *)ptr)[-1]).u.size; INSIST(oldsize >= ALIGNMENT_SIZE); oldsize -= ALIGNMENT_SIZE; if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) { INSIST(oldsize >= ALIGNMENT_SIZE); oldsize -= ALIGNMENT_SIZE; } copysize = (oldsize > size) ? size : oldsize; memmove(new_ptr, ptr, copysize); isc__mem_free(ctx0, ptr FLARG_PASS); } } else if (ptr != NULL) { isc__mem_free(ctx0, ptr FLARG_PASS); } return (new_ptr); } void isc___mem_free(isc_mem_t *ctx0, void *ptr FLARG) { REQUIRE(VALID_CONTEXT(ctx0)); REQUIRE(ptr != NULL); isc__mem_t *ctx = (isc__mem_t *)ctx0; size_info *si; size_t size; bool call_water = false; if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) { si = &(((size_info *)ptr)[-2]); REQUIRE(si->u.ctx == ctx); size = si[1].u.size; } else { si = &(((size_info *)ptr)[-1]); size = si->u.size; } MCTXLOCK(ctx); DELETE_TRACE(ctx, ptr, size, file, line); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { mem_putunlocked(ctx, si, size); } else { mem_putstats(ctx, si, size); mem_put(ctx, si, size); } /* * The check against ctx->lo_water == 0 is for the condition * when the context was pushed over hi_water but then had * isc_mem_setwater() called with 0 for hi_water and lo_water. */ if (ctx->is_overmem && (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U)) { ctx->is_overmem = false; } if (ctx->hi_called && (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U)) { ctx->hi_called = false; if (ctx->water != NULL) { call_water = true; } } MCTXUNLOCK(ctx); if (call_water) { (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); } } /* * Other useful things. */ char * isc___mem_strdup(isc_mem_t *mctx0, const char *s FLARG) { REQUIRE(VALID_CONTEXT(mctx0)); REQUIRE(s != NULL); isc__mem_t *mctx = (isc__mem_t *)mctx0; size_t len; char *ns; len = strlen(s) + 1; ns = isc__mem_allocate((isc_mem_t *)mctx, len FLARG_PASS); if (ns != NULL) { strlcpy(ns, s, len); } return (ns); } char * isc___mem_strndup(isc_mem_t *mctx0, const char *s, size_t size FLARG) { REQUIRE(VALID_CONTEXT(mctx0)); REQUIRE(s != NULL); isc__mem_t *mctx = (isc__mem_t *)mctx0; size_t len; char *ns; len = strlen(s) + 1; if (len > size) { len = size; } ns = isc__mem_allocate((isc_mem_t *)mctx, len FLARG_PASS); if (ns != NULL) { strlcpy(ns, s, len); } return (ns); } void isc_mem_setdestroycheck(isc_mem_t *ctx0, bool flag) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; MCTXLOCK(ctx); ctx->checkfree = flag; MCTXUNLOCK(ctx); } size_t isc_mem_inuse(isc_mem_t *ctx0) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; size_t inuse; MCTXLOCK(ctx); inuse = ctx->inuse; MCTXUNLOCK(ctx); return (inuse); } size_t isc_mem_maxinuse(isc_mem_t *ctx0) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; size_t maxinuse; MCTXLOCK(ctx); maxinuse = ctx->maxinuse; MCTXUNLOCK(ctx); return (maxinuse); } size_t isc_mem_total(isc_mem_t *ctx0) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; size_t total; MCTXLOCK(ctx); total = ctx->total; MCTXUNLOCK(ctx); return (total); } void isc_mem_setwater(isc_mem_t *ctx0, isc_mem_water_t water, void *water_arg, size_t hiwater, size_t lowater) { REQUIRE(VALID_CONTEXT(ctx0)); REQUIRE(hiwater >= lowater); isc__mem_t *ctx = (isc__mem_t *)ctx0; bool callwater = false; isc_mem_water_t oldwater; void *oldwater_arg; MCTXLOCK(ctx); oldwater = ctx->water; oldwater_arg = ctx->water_arg; if (water == NULL) { callwater = ctx->hi_called; ctx->water = NULL; ctx->water_arg = NULL; ctx->hi_water = 0; ctx->lo_water = 0; } else { if (ctx->hi_called && (ctx->water != water || ctx->water_arg != water_arg || ctx->inuse < lowater || lowater == 0U)) { callwater = true; } ctx->water = water; ctx->water_arg = water_arg; ctx->hi_water = hiwater; ctx->lo_water = lowater; } MCTXUNLOCK(ctx); if (callwater && oldwater != NULL) { (oldwater)(oldwater_arg, ISC_MEM_LOWATER); } } ISC_NO_SANITIZE_THREAD bool isc_mem_isovermem(isc_mem_t *ctx0) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; /* * We don't bother to lock the context because 100% accuracy isn't * necessary (and even if we locked the context the returned value * could be different from the actual state when it's used anyway) */ return (ctx->is_overmem); } void isc_mem_setname(isc_mem_t *ctx0, const char *name, void *tag) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; LOCK(&ctx->lock); strlcpy(ctx->name, name, sizeof(ctx->name)); ctx->tag = tag; UNLOCK(&ctx->lock); } const char * isc_mem_getname(isc_mem_t *ctx0) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; if (ctx->name[0] == 0) { return (""); } return (ctx->name); } void * isc_mem_gettag(isc_mem_t *ctx0) { REQUIRE(VALID_CONTEXT(ctx0)); isc__mem_t *ctx = (isc__mem_t *)ctx0; return (ctx->tag); } /* * Memory pool stuff */ void isc_mempool_create(isc_mem_t *mctx0, size_t size, isc_mempool_t **mpctxp) { REQUIRE(VALID_CONTEXT(mctx0)); REQUIRE(size > 0U); REQUIRE(mpctxp != NULL && *mpctxp == NULL); isc__mem_t *mctx = (isc__mem_t *)mctx0; isc__mempool_t *mpctx; /* * Allocate space for this pool, initialize values, and if all works * well, attach to the memory context. */ mpctx = isc_mem_get((isc_mem_t *)mctx, sizeof(isc__mempool_t)); mpctx->common.impmagic = MEMPOOL_MAGIC; mpctx->common.magic = ISCAPI_MPOOL_MAGIC; mpctx->mctx = NULL; isc_mem_attach((isc_mem_t *)mctx, (isc_mem_t **)&mpctx->mctx); /* * Mempools are stored as a linked list of element. */ if (size < sizeof(element)) { size = sizeof(element); } mpctx->size = size; mpctx->maxalloc = UINT_MAX; mpctx->allocated = 0; mpctx->freecount = 0; mpctx->freemax = 1; mpctx->fillcount = 1; mpctx->gets = 0; #if ISC_MEMPOOL_NAMES mpctx->name[0] = 0; #endif /* if ISC_MEMPOOL_NAMES */ mpctx->items = NULL; *mpctxp = (isc_mempool_t *)mpctx; MCTXLOCK(mctx); ISC_LIST_INITANDAPPEND(mctx->pools, mpctx, link); mctx->poolcnt++; MCTXUNLOCK(mctx); } void isc_mempool_setname(isc_mempool_t *mpctx0, const char *name) { REQUIRE(VALID_MEMPOOL(mpctx0)); REQUIRE(name != NULL); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; #if ISC_MEMPOOL_NAMES strlcpy(mpctx->name, name, sizeof(mpctx->name)); #else /* if ISC_MEMPOOL_NAMES */ UNUSED(mpctx); UNUSED(name); #endif /* if ISC_MEMPOOL_NAMES */ } void isc_mempool_destroy(isc_mempool_t **mpctxp) { REQUIRE(mpctxp != NULL); REQUIRE(VALID_MEMPOOL(*mpctxp)); isc__mempool_t *mpctx; isc__mem_t *mctx; element *item; mpctx = (isc__mempool_t *)*mpctxp; #if ISC_MEMPOOL_NAMES if (mpctx->allocated > 0) { UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_mempool_destroy(): mempool %s " "leaked memory", mpctx->name); } #endif /* if ISC_MEMPOOL_NAMES */ REQUIRE(mpctx->allocated == 0); mctx = mpctx->mctx; /* * Return any items on the free list */ MCTXLOCK(mctx); while (mpctx->items != NULL) { INSIST(mpctx->freecount > 0); mpctx->freecount--; item = mpctx->items; mpctx->items = item->next; mem_putstats(mctx, item, mpctx->size); mem_put(mctx, item, mpctx->size); } MCTXUNLOCK(mctx); /* * Remove our linked list entry from the memory context. */ MCTXLOCK(mctx); ISC_LIST_UNLINK(mctx->pools, mpctx, link); mctx->poolcnt--; MCTXUNLOCK(mctx); mpctx->common.impmagic = 0; mpctx->common.magic = 0; isc_mem_putanddetach((isc_mem_t **)&mpctx->mctx, mpctx, sizeof(isc__mempool_t)); *mpctxp = NULL; } #if __SANITIZE_ADDRESS__ void * isc__mempool_get(isc_mempool_t *mpctx0 FLARG) { void *item = NULL; REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; isc_mem_t *mctx = (isc_mem_t *)mpctx->mctx; /* * Don't let the caller go over quota */ if (ISC_UNLIKELY(mpctx->allocated >= mpctx->maxalloc)) { goto out; } item = isc__mem_get(mctx, mpctx->size FLARG_PASS); mpctx->gets++; mpctx->allocated++; out: return (item); } void isc__mempool_put(isc_mempool_t *mpctx0, void *mem FLARG) { REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; isc_mem_t *mctx = (isc_mem_t *)mpctx->mctx; REQUIRE(mem != NULL); INSIST(mpctx->allocated > 0); mpctx->allocated--; isc__mem_put(mctx, mem, mpctx->size FLARG_PASS); } #else /* __SANITIZE_ADDRESS__ */ void * isc__mempool_get(isc_mempool_t *mpctx0 FLARG) { REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; element *item; isc__mem_t *mctx; unsigned int i; mctx = mpctx->mctx; /* * Don't let the caller go over quota */ if (ISC_UNLIKELY(mpctx->allocated >= mpctx->maxalloc)) { item = NULL; goto out; } if (ISC_UNLIKELY(mpctx->items == NULL)) { /* * We need to dip into the well. Lock the memory context * here and fill up our free list. */ MCTXLOCK(mctx); for (i = 0; i < mpctx->fillcount; i++) { item = mem_get(mctx, mpctx->size); mem_getstats(mctx, mpctx->size); item->next = mpctx->items; mpctx->items = item; mpctx->freecount++; } MCTXUNLOCK(mctx); } /* * If we didn't get any items, return NULL. */ item = mpctx->items; if (ISC_UNLIKELY(item == NULL)) { goto out; } mpctx->items = item->next; INSIST(mpctx->freecount > 0); mpctx->freecount--; mpctx->gets++; mpctx->allocated++; out: #if ISC_MEM_TRACKLINES if (ISC_UNLIKELY(((isc_mem_debugging & TRACE_OR_RECORD) != 0) && item != NULL)) { MCTXLOCK(mctx); ADD_TRACE(mctx, item, mpctx->size, file, line); MCTXUNLOCK(mctx); } #endif /* ISC_MEM_TRACKLINES */ return (item); } /* coverity[+free : arg-1] */ void isc__mempool_put(isc_mempool_t *mpctx0, void *mem FLARG) { REQUIRE(VALID_MEMPOOL(mpctx0)); REQUIRE(mem != NULL); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; isc__mem_t *mctx = mpctx->mctx; element *item; INSIST(mpctx->allocated > 0); mpctx->allocated--; #if ISC_MEM_TRACKLINES if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0)) { MCTXLOCK(mctx); DELETE_TRACE(mctx, mem, mpctx->size, file, line); MCTXUNLOCK(mctx); } #endif /* ISC_MEM_TRACKLINES */ /* * If our free list is full, return this to the mctx directly. */ if (mpctx->freecount >= mpctx->freemax) { MCTXLOCK(mctx); mem_putstats(mctx, mem, mpctx->size); mem_put(mctx, mem, mpctx->size); MCTXUNLOCK(mctx); return; } /* * Otherwise, attach it to our free list and bump the counter. */ mpctx->freecount++; item = (element *)mem; item->next = mpctx->items; mpctx->items = item; } #endif /* __SANITIZE_ADDRESS__ */ /* * Quotas */ void isc_mempool_setfreemax(isc_mempool_t *mpctx0, unsigned int limit) { REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; mpctx->freemax = limit; } unsigned int isc_mempool_getfreemax(isc_mempool_t *mpctx0) { REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; return (mpctx->freemax); } unsigned int isc_mempool_getfreecount(isc_mempool_t *mpctx0) { REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; return (mpctx->freecount); } void isc_mempool_setmaxalloc(isc_mempool_t *mpctx0, unsigned int limit) { REQUIRE(VALID_MEMPOOL(mpctx0)); REQUIRE(limit > 0); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; mpctx->maxalloc = limit; } unsigned int isc_mempool_getmaxalloc(isc_mempool_t *mpctx0) { REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; return (mpctx->maxalloc); } unsigned int isc_mempool_getallocated(isc_mempool_t *mpctx0) { REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; return (mpctx->allocated); } void isc_mempool_setfillcount(isc_mempool_t *mpctx0, unsigned int limit) { REQUIRE(VALID_MEMPOOL(mpctx0)); REQUIRE(limit > 0); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; mpctx->fillcount = limit; } unsigned int isc_mempool_getfillcount(isc_mempool_t *mpctx0) { REQUIRE(VALID_MEMPOOL(mpctx0)); isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; return (mpctx->fillcount); } /* * Requires contextslock to be held by caller. */ static void print_contexts(FILE *file) { isc__mem_t *ctx; for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; ctx = ISC_LIST_NEXT(ctx, link)) { fprintf(file, "context: %p (%s): %" PRIuFAST32 " references\n", ctx, ctx->name[0] == 0 ? "" : ctx->name, isc_refcount_current(&ctx->references)); print_active(ctx, file); } fflush(file); } static atomic_uintptr_t checkdestroyed = 0; void isc_mem_checkdestroyed(FILE *file) { atomic_store_release(&checkdestroyed, (uintptr_t)file); } void isc__mem_checkdestroyed(void) { FILE *file = (FILE *)atomic_load_acquire(&checkdestroyed); if (file == NULL) { return; } LOCK(&contextslock); if (!ISC_LIST_EMPTY(contexts)) { #if ISC_MEM_TRACKLINES if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0)) { print_contexts(file); } #endif /* if ISC_MEM_TRACKLINES */ UNREACHABLE(); } UNLOCK(&contextslock); } unsigned int isc_mem_references(isc_mem_t *ctx0) { isc__mem_t *ctx = (isc__mem_t *)ctx0; return (isc_refcount_current(&ctx->references)); } typedef struct summarystat { uint64_t total; uint64_t inuse; uint64_t malloced; uint64_t blocksize; uint64_t contextsize; } summarystat_t; #ifdef HAVE_LIBXML2 #define TRY0(a) \ do { \ xmlrc = (a); \ if (xmlrc < 0) \ goto error; \ } while (0) static int xml_renderctx(isc__mem_t *ctx, summarystat_t *summary, xmlTextWriterPtr writer) { REQUIRE(VALID_CONTEXT(ctx)); int xmlrc; MCTXLOCK(ctx); TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "context")); TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id")); TRY0(xmlTextWriterWriteFormatString(writer, "%p", ctx)); TRY0(xmlTextWriterEndElement(writer)); /* id */ if (ctx->name[0] != 0) { TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "name")); TRY0(xmlTextWriterWriteFormatString(writer, "%s", ctx->name)); TRY0(xmlTextWriterEndElement(writer)); /* name */ } summary->contextsize += sizeof(*ctx) + (ctx->max_size + 1) * sizeof(struct stats) + ctx->max_size * sizeof(element *) + ctx->basic_table_count * sizeof(char *); #if ISC_MEM_TRACKLINES if (ctx->debuglist != NULL) { summary->contextsize += DEBUG_TABLE_COUNT * sizeof(debuglist_t) + ctx->debuglistcnt * sizeof(debuglink_t); } #endif /* if ISC_MEM_TRACKLINES */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "references")); TRY0(xmlTextWriterWriteFormatString( writer, "%" PRIuFAST32, isc_refcount_current(&ctx->references))); TRY0(xmlTextWriterEndElement(writer)); /* references */ summary->total += ctx->total; TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "total")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", (uint64_t)ctx->total)); TRY0(xmlTextWriterEndElement(writer)); /* total */ summary->inuse += ctx->inuse; TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", (uint64_t)ctx->inuse)); TRY0(xmlTextWriterEndElement(writer)); /* inuse */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxinuse")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", (uint64_t)ctx->maxinuse)); TRY0(xmlTextWriterEndElement(writer)); /* maxinuse */ summary->malloced += ctx->malloced; TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "malloced")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", (uint64_t)ctx->malloced)); TRY0(xmlTextWriterEndElement(writer)); /* malloced */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxmalloced")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", (uint64_t)ctx->maxmalloced)); TRY0(xmlTextWriterEndElement(writer)); /* maxmalloced */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "blocksize")); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { summary->blocksize += ctx->basic_table_count * NUM_BASIC_BLOCKS * ctx->mem_target; TRY0(xmlTextWriterWriteFormatString( writer, "%" PRIu64 "", (uint64_t)ctx->basic_table_count * NUM_BASIC_BLOCKS * ctx->mem_target)); } else { TRY0(xmlTextWriterWriteFormatString(writer, "%s", "-")); } TRY0(xmlTextWriterEndElement(writer)); /* blocksize */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "pools")); TRY0(xmlTextWriterWriteFormatString(writer, "%u", ctx->poolcnt)); TRY0(xmlTextWriterEndElement(writer)); /* pools */ summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t); TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "hiwater")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", (uint64_t)ctx->hi_water)); TRY0(xmlTextWriterEndElement(writer)); /* hiwater */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", (uint64_t)ctx->lo_water)); TRY0(xmlTextWriterEndElement(writer)); /* lowater */ TRY0(xmlTextWriterEndElement(writer)); /* context */ error: MCTXUNLOCK(ctx); return (xmlrc); } int isc_mem_renderxml(void *writer0) { isc__mem_t *ctx; summarystat_t summary; uint64_t lost; int xmlrc; xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0; memset(&summary, 0, sizeof(summary)); TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "contexts")); LOCK(&contextslock); lost = totallost; for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; ctx = ISC_LIST_NEXT(ctx, link)) { xmlrc = xml_renderctx(ctx, &summary, writer); if (xmlrc < 0) { UNLOCK(&contextslock); goto error; } } UNLOCK(&contextslock); TRY0(xmlTextWriterEndElement(writer)); /* contexts */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "summary")); TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "TotalUse")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", summary.total)); TRY0(xmlTextWriterEndElement(writer)); /* TotalUse */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "InUse")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", summary.inuse)); TRY0(xmlTextWriterEndElement(writer)); /* InUse */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Malloced")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", summary.malloced)); TRY0(xmlTextWriterEndElement(writer)); /* InUse */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "BlockSize")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", summary.blocksize)); TRY0(xmlTextWriterEndElement(writer)); /* BlockSize */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "ContextSize")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", summary.contextsize)); TRY0(xmlTextWriterEndElement(writer)); /* ContextSize */ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Lost")); TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", lost)); TRY0(xmlTextWriterEndElement(writer)); /* Lost */ TRY0(xmlTextWriterEndElement(writer)); /* summary */ error: return (xmlrc); } #endif /* HAVE_LIBXML2 */ #ifdef HAVE_JSON_C #define CHECKMEM(m) RUNTIME_CHECK(m != NULL) static isc_result_t json_renderctx(isc__mem_t *ctx, summarystat_t *summary, json_object *array) { REQUIRE(VALID_CONTEXT(ctx)); REQUIRE(summary != NULL); REQUIRE(array != NULL); json_object *ctxobj, *obj; char buf[1024]; MCTXLOCK(ctx); summary->contextsize += sizeof(*ctx) + (ctx->max_size + 1) * sizeof(struct stats) + ctx->max_size * sizeof(element *) + ctx->basic_table_count * sizeof(char *); summary->total += ctx->total; summary->inuse += ctx->inuse; summary->malloced += ctx->malloced; if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { summary->blocksize += ctx->basic_table_count * NUM_BASIC_BLOCKS * ctx->mem_target; } #if ISC_MEM_TRACKLINES if (ctx->debuglist != NULL) { summary->contextsize += DEBUG_TABLE_COUNT * sizeof(debuglist_t) + ctx->debuglistcnt * sizeof(debuglink_t); } #endif /* if ISC_MEM_TRACKLINES */ ctxobj = json_object_new_object(); CHECKMEM(ctxobj); snprintf(buf, sizeof(buf), "%p", ctx); obj = json_object_new_string(buf); CHECKMEM(obj); json_object_object_add(ctxobj, "id", obj); if (ctx->name[0] != 0) { obj = json_object_new_string(ctx->name); CHECKMEM(obj); json_object_object_add(ctxobj, "name", obj); } obj = json_object_new_int64(isc_refcount_current(&ctx->references)); CHECKMEM(obj); json_object_object_add(ctxobj, "references", obj); obj = json_object_new_int64(ctx->total); CHECKMEM(obj); json_object_object_add(ctxobj, "total", obj); obj = json_object_new_int64(ctx->inuse); CHECKMEM(obj); json_object_object_add(ctxobj, "inuse", obj); obj = json_object_new_int64(ctx->maxinuse); CHECKMEM(obj); json_object_object_add(ctxobj, "maxinuse", obj); obj = json_object_new_int64(ctx->malloced); CHECKMEM(obj); json_object_object_add(ctxobj, "malloced", obj); obj = json_object_new_int64(ctx->maxmalloced); CHECKMEM(obj); json_object_object_add(ctxobj, "maxmalloced", obj); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { uint64_t blocksize; blocksize = ctx->basic_table_count * NUM_BASIC_BLOCKS * ctx->mem_target; obj = json_object_new_int64(blocksize); CHECKMEM(obj); json_object_object_add(ctxobj, "blocksize", obj); } obj = json_object_new_int64(ctx->poolcnt); CHECKMEM(obj); json_object_object_add(ctxobj, "pools", obj); summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t); obj = json_object_new_int64(ctx->hi_water); CHECKMEM(obj); json_object_object_add(ctxobj, "hiwater", obj); obj = json_object_new_int64(ctx->lo_water); CHECKMEM(obj); json_object_object_add(ctxobj, "lowater", obj); MCTXUNLOCK(ctx); json_object_array_add(array, ctxobj); return (ISC_R_SUCCESS); } isc_result_t isc_mem_renderjson(void *memobj0) { isc_result_t result = ISC_R_SUCCESS; isc__mem_t *ctx; summarystat_t summary; uint64_t lost; json_object *ctxarray, *obj; json_object *memobj = (json_object *)memobj0; memset(&summary, 0, sizeof(summary)); ctxarray = json_object_new_array(); CHECKMEM(ctxarray); LOCK(&contextslock); lost = totallost; for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; ctx = ISC_LIST_NEXT(ctx, link)) { result = json_renderctx(ctx, &summary, ctxarray); if (result != ISC_R_SUCCESS) { UNLOCK(&contextslock); goto error; } } UNLOCK(&contextslock); obj = json_object_new_int64(summary.total); CHECKMEM(obj); json_object_object_add(memobj, "TotalUse", obj); obj = json_object_new_int64(summary.inuse); CHECKMEM(obj); json_object_object_add(memobj, "InUse", obj); obj = json_object_new_int64(summary.malloced); CHECKMEM(obj); json_object_object_add(memobj, "Malloced", obj); obj = json_object_new_int64(summary.blocksize); CHECKMEM(obj); json_object_object_add(memobj, "BlockSize", obj); obj = json_object_new_int64(summary.contextsize); CHECKMEM(obj); json_object_object_add(memobj, "ContextSize", obj); obj = json_object_new_int64(lost); CHECKMEM(obj); json_object_object_add(memobj, "Lost", obj); json_object_object_add(memobj, "contexts", ctxarray); return (ISC_R_SUCCESS); error: if (ctxarray != NULL) { json_object_put(ctxarray); } return (result); } #endif /* HAVE_JSON_C */ void isc_mem_create(isc_mem_t **mctxp) { mem_create(mctxp, isc_mem_defaultflags); } void * isc__mem_get(isc_mem_t *mctx, size_t size FLARG) { REQUIRE(ISCAPI_MCTX_VALID(mctx)); return (mctx->methods->memget(mctx, size FLARG_PASS)); } void isc__mem_put(isc_mem_t *mctx, void *ptr, size_t size FLARG) { REQUIRE(ISCAPI_MCTX_VALID(mctx)); mctx->methods->memput(mctx, ptr, size FLARG_PASS); } void isc__mem_putanddetach(isc_mem_t **mctxp, void *ptr, size_t size FLARG) { REQUIRE(mctxp != NULL && ISCAPI_MCTX_VALID(*mctxp)); (*mctxp)->methods->memputanddetach(mctxp, ptr, size FLARG_PASS); } void * isc__mem_allocate(isc_mem_t *mctx, size_t size FLARG) { REQUIRE(ISCAPI_MCTX_VALID(mctx)); return (mctx->methods->memallocate(mctx, size FLARG_PASS)); } void * isc__mem_reallocate(isc_mem_t *mctx, void *ptr, size_t size FLARG) { REQUIRE(ISCAPI_MCTX_VALID(mctx)); return (mctx->methods->memreallocate(mctx, ptr, size FLARG_PASS)); } char * isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) { REQUIRE(ISCAPI_MCTX_VALID(mctx)); return (mctx->methods->memstrdup(mctx, s FLARG_PASS)); } char * isc__mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG) { REQUIRE(ISCAPI_MCTX_VALID(mctx)); return (mctx->methods->memstrndup(mctx, s, size FLARG_PASS)); } void isc__mem_free(isc_mem_t *mctx, void *ptr FLARG) { REQUIRE(ISCAPI_MCTX_VALID(mctx)); mctx->methods->memfree(mctx, ptr FLARG_PASS); } void isc__mem_printactive(isc_mem_t *ctx0, FILE *file) { #if ISC_MEM_TRACKLINES REQUIRE(VALID_CONTEXT(ctx0)); REQUIRE(file != NULL); isc__mem_t *ctx = (isc__mem_t *)ctx0; print_active(ctx, file); #else /* if ISC_MEM_TRACKLINES */ UNUSED(ctx0); UNUSED(file); #endif /* if ISC_MEM_TRACKLINES */ }