|  | /* mmap.c -- Memory allocation with mmap. | 
|  | Copyright (C) 2012-2025 Free Software Foundation, Inc. | 
|  | Written by Ian Lance Taylor, Google. | 
|  |  | 
|  | Redistribution and use in source and binary forms, with or without | 
|  | modification, are permitted provided that the following conditions are | 
|  | met: | 
|  |  | 
|  | (1) Redistributions of source code must retain the above copyright | 
|  | notice, this list of conditions and the following disclaimer. | 
|  |  | 
|  | (2) Redistributions in binary form must reproduce the above copyright | 
|  | notice, this list of conditions and the following disclaimer in | 
|  | the documentation and/or other materials provided with the | 
|  | distribution. | 
|  |  | 
|  | (3) The name of the author may not be used to | 
|  | endorse or promote products derived from this software without | 
|  | specific prior written permission. | 
|  |  | 
|  | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | 
|  | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 
|  | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
|  | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, | 
|  | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 
|  | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
|  | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | 
|  | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | 
|  | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | 
|  | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
|  | POSSIBILITY OF SUCH DAMAGE.  */ | 
|  |  | 
|  | #include "config.h" | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <string.h> | 
|  | #include <stdlib.h> | 
|  | #include <unistd.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/mman.h> | 
|  |  | 
|  | #include "backtrace.h" | 
|  | #include "internal.h" | 
|  |  | 
|  | #ifndef HAVE_DECL_GETPAGESIZE | 
|  | extern int getpagesize (void); | 
|  | #endif | 
|  |  | 
|  | /* Memory allocation on systems that provide anonymous mmap.  This | 
|  | permits the backtrace functions to be invoked from a signal | 
|  | handler, assuming that mmap is async-signal safe.  */ | 
|  |  | 
|  | #ifndef MAP_ANONYMOUS | 
|  | #define MAP_ANONYMOUS MAP_ANON | 
|  | #endif | 
|  |  | 
|  | #ifndef MAP_FAILED | 
|  | #define MAP_FAILED ((void *)-1) | 
|  | #endif | 
|  |  | 
|  | /* A list of free memory blocks.  */ | 
|  |  | 
|  | struct backtrace_freelist_struct | 
|  | { | 
|  | /* Next on list.  */ | 
|  | struct backtrace_freelist_struct *next; | 
|  | /* Size of this block, including this structure.  */ | 
|  | size_t size; | 
|  | }; | 
|  |  | 
|  | /* Free memory allocated by backtrace_alloc.  */ | 
|  |  | 
|  | static void | 
|  | backtrace_free_locked (struct backtrace_state *state, void *addr, size_t size) | 
|  | { | 
|  | /* Just leak small blocks.  We don't have to be perfect.  Don't put | 
|  | more than 16 entries on the free list, to avoid wasting time | 
|  | searching when allocating a block.  If we have more than 16 | 
|  | entries, leak the smallest entry.  */ | 
|  |  | 
|  | if (size >= sizeof (struct backtrace_freelist_struct)) | 
|  | { | 
|  | size_t c; | 
|  | struct backtrace_freelist_struct **ppsmall; | 
|  | struct backtrace_freelist_struct **pp; | 
|  | struct backtrace_freelist_struct *p; | 
|  |  | 
|  | c = 0; | 
|  | ppsmall = NULL; | 
|  | for (pp = &state->freelist; *pp != NULL; pp = &(*pp)->next) | 
|  | { | 
|  | if (ppsmall == NULL || (*pp)->size < (*ppsmall)->size) | 
|  | ppsmall = pp; | 
|  | ++c; | 
|  | } | 
|  | if (c >= 16) | 
|  | { | 
|  | if (size <= (*ppsmall)->size) | 
|  | return; | 
|  | *ppsmall = (*ppsmall)->next; | 
|  | } | 
|  |  | 
|  | p = (struct backtrace_freelist_struct *) addr; | 
|  | p->next = state->freelist; | 
|  | p->size = size; | 
|  | state->freelist = p; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Allocate memory like malloc.  If ERROR_CALLBACK is NULL, don't | 
|  | report an error.  */ | 
|  |  | 
|  | void * | 
|  | backtrace_alloc (struct backtrace_state *state, | 
|  | size_t size, backtrace_error_callback error_callback, | 
|  | void *data) | 
|  | { | 
|  | void *ret; | 
|  | int locked; | 
|  | struct backtrace_freelist_struct **pp; | 
|  | size_t pagesize; | 
|  | size_t asksize; | 
|  | void *page; | 
|  |  | 
|  | ret = NULL; | 
|  |  | 
|  | /* If we can acquire the lock, then see if there is space on the | 
|  | free list.  If we can't acquire the lock, drop straight into | 
|  | using mmap.  __sync_lock_test_and_set returns the old state of | 
|  | the lock, so we have acquired it if it returns 0.  */ | 
|  |  | 
|  | if (!state->threaded) | 
|  | locked = 1; | 
|  | else | 
|  | locked = __sync_lock_test_and_set (&state->lock_alloc, 1) == 0; | 
|  |  | 
|  | if (locked) | 
|  | { | 
|  | for (pp = &state->freelist; *pp != NULL; pp = &(*pp)->next) | 
|  | { | 
|  | if ((*pp)->size >= size) | 
|  | { | 
|  | struct backtrace_freelist_struct *p; | 
|  |  | 
|  | p = *pp; | 
|  | *pp = p->next; | 
|  |  | 
|  | /* Round for alignment; we assume that no type we care about | 
|  | is more than 8 bytes.  */ | 
|  | size = (size + 7) & ~ (size_t) 7; | 
|  | if (size < p->size) | 
|  | backtrace_free_locked (state, (char *) p + size, | 
|  | p->size - size); | 
|  |  | 
|  | ret = (void *) p; | 
|  |  | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (state->threaded) | 
|  | __sync_lock_release (&state->lock_alloc); | 
|  | } | 
|  |  | 
|  | if (ret == NULL) | 
|  | { | 
|  | /* Allocate a new page.  */ | 
|  |  | 
|  | pagesize = getpagesize (); | 
|  | asksize = (size + pagesize - 1) & ~ (pagesize - 1); | 
|  | page = mmap (NULL, asksize, PROT_READ | PROT_WRITE, | 
|  | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | 
|  | if (page == MAP_FAILED) | 
|  | { | 
|  | if (error_callback) | 
|  | error_callback (data, "mmap", errno); | 
|  | } | 
|  | else | 
|  | { | 
|  | size = (size + 7) & ~ (size_t) 7; | 
|  | if (size < asksize) | 
|  | backtrace_free (state, (char *) page + size, asksize - size, | 
|  | error_callback, data); | 
|  |  | 
|  | ret = page; | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Free memory allocated by backtrace_alloc.  */ | 
|  |  | 
|  | void | 
|  | backtrace_free (struct backtrace_state *state, void *addr, size_t size, | 
|  | backtrace_error_callback error_callback ATTRIBUTE_UNUSED, | 
|  | void *data ATTRIBUTE_UNUSED) | 
|  | { | 
|  | int locked; | 
|  |  | 
|  | /* If we are freeing a large aligned block, just release it back to | 
|  | the system.  This case arises when growing a vector for a large | 
|  | binary with lots of debug info.  Calling munmap here may cause us | 
|  | to call mmap again if there is also a large shared library; we | 
|  | just live with that.  */ | 
|  | if (size >= 16 * 4096) | 
|  | { | 
|  | size_t pagesize; | 
|  |  | 
|  | pagesize = getpagesize (); | 
|  | if (((uintptr_t) addr & (pagesize - 1)) == 0 | 
|  | && (size & (pagesize - 1)) == 0) | 
|  | { | 
|  | /* If munmap fails for some reason, just add the block to | 
|  | the freelist.  */ | 
|  | if (munmap (addr, size) == 0) | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* If we can acquire the lock, add the new space to the free list. | 
|  | If we can't acquire the lock, just leak the memory. | 
|  | __sync_lock_test_and_set returns the old state of the lock, so we | 
|  | have acquired it if it returns 0.  */ | 
|  |  | 
|  | if (!state->threaded) | 
|  | locked = 1; | 
|  | else | 
|  | locked = __sync_lock_test_and_set (&state->lock_alloc, 1) == 0; | 
|  |  | 
|  | if (locked) | 
|  | { | 
|  | backtrace_free_locked (state, addr, size); | 
|  |  | 
|  | if (state->threaded) | 
|  | __sync_lock_release (&state->lock_alloc); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Grow VEC by SIZE bytes.  */ | 
|  |  | 
|  | void * | 
|  | backtrace_vector_grow (struct backtrace_state *state,size_t size, | 
|  | backtrace_error_callback error_callback, | 
|  | void *data, struct backtrace_vector *vec) | 
|  | { | 
|  | void *ret; | 
|  |  | 
|  | if (size > vec->alc) | 
|  | { | 
|  | size_t pagesize; | 
|  | size_t alc; | 
|  | void *base; | 
|  |  | 
|  | pagesize = getpagesize (); | 
|  | alc = vec->size + size; | 
|  | if (vec->size == 0) | 
|  | alc = 16 * size; | 
|  | else if (alc < pagesize) | 
|  | { | 
|  | alc *= 2; | 
|  | if (alc > pagesize) | 
|  | alc = pagesize; | 
|  | } | 
|  | else | 
|  | { | 
|  | alc *= 2; | 
|  | alc = (alc + pagesize - 1) & ~ (pagesize - 1); | 
|  | } | 
|  | base = backtrace_alloc (state, alc, error_callback, data); | 
|  | if (base == NULL) | 
|  | return NULL; | 
|  | if (vec->base != NULL) | 
|  | { | 
|  | memcpy (base, vec->base, vec->size); | 
|  | backtrace_free (state, vec->base, vec->size + vec->alc, | 
|  | error_callback, data); | 
|  | } | 
|  | vec->base = base; | 
|  | vec->alc = alc - vec->size; | 
|  | } | 
|  |  | 
|  | ret = (char *) vec->base + vec->size; | 
|  | vec->size += size; | 
|  | vec->alc -= size; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Finish the current allocation on VEC.  */ | 
|  |  | 
|  | void * | 
|  | backtrace_vector_finish ( | 
|  | struct backtrace_state *state ATTRIBUTE_UNUSED, | 
|  | struct backtrace_vector *vec, | 
|  | backtrace_error_callback error_callback ATTRIBUTE_UNUSED, | 
|  | void *data ATTRIBUTE_UNUSED) | 
|  | { | 
|  | void *ret; | 
|  |  | 
|  | ret = vec->base; | 
|  | vec->base = (char *) vec->base + vec->size; | 
|  | vec->size = 0; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Release any extra space allocated for VEC.  */ | 
|  |  | 
|  | int | 
|  | backtrace_vector_release (struct backtrace_state *state, | 
|  | struct backtrace_vector *vec, | 
|  | backtrace_error_callback error_callback, | 
|  | void *data) | 
|  | { | 
|  | size_t size; | 
|  | size_t alc; | 
|  | size_t aligned; | 
|  |  | 
|  | /* Make sure that the block that we free is aligned on an 8-byte | 
|  | boundary.  */ | 
|  | size = vec->size; | 
|  | alc = vec->alc; | 
|  | aligned = (size + 7) & ~ (size_t) 7; | 
|  | alc -= aligned - size; | 
|  |  | 
|  | backtrace_free (state, (char *) vec->base + aligned, alc, | 
|  | error_callback, data); | 
|  | vec->alc = 0; | 
|  | if (vec->size == 0) | 
|  | vec->base = NULL; | 
|  | return 1; | 
|  | } |