332 lines
8.4 KiB
C
332 lines
8.4 KiB
C
/* mmap.c -- Memory allocation with mmap.
|
|
Copyright (C) 2012-2021 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;
|
|
}
|