diff options
Diffstat (limited to 'malloc.c')
-rw-r--r-- | malloc.c | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/malloc.c b/malloc.c new file mode 100644 index 0000000..8b69bde --- /dev/null +++ b/malloc.c @@ -0,0 +1,416 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "@(#)malloc.c 1.5 05/06/08 SMI" + +#include "config.h" +#include <unistd.h> + +#include <errno.h> + +#include <string.h> + +#include <sys/sysmacros.h> + +#include "umem_base.h" + +#include "misc.h" + +/* + * malloc_data_t is an 8-byte structure which is located "before" the pointer + * returned from {m,c,re}alloc and memalign. The first four bytes give + * information about the buffer, and the second four bytes are a status byte. + * + * See umem_impl.h for the various magic numbers used, and the size + * encode/decode macros. + * + * The 'size' of the buffer includes the tags. That is, we encode the + * argument to umem_alloc(), not the argument to malloc(). + */ + +typedef struct malloc_data { + uint32_t malloc_size; + uint32_t malloc_stat; /* = UMEM_MALLOC_ENCODE(state, malloc_size) */ +} malloc_data_t; + +void * +malloc(size_t size_arg) +{ +#ifdef _LP64 + uint32_t high_size = 0; +#endif + size_t size; + + malloc_data_t *ret; + size = size_arg + sizeof (malloc_data_t); + +#ifdef _LP64 + if (size > UMEM_SECOND_ALIGN) { + size += sizeof (malloc_data_t); + high_size = (size >> 32); + } +#endif + if (size < size_arg) { + errno = ENOMEM; /* overflow */ + return (NULL); + } + ret = (malloc_data_t *)_umem_alloc(size, UMEM_DEFAULT); + if (ret == NULL) { + if (size <= UMEM_MAXBUF) + errno = EAGAIN; + else + errno = ENOMEM; + return (NULL); +#ifdef _LP64 + } else if (high_size > 0) { + uint32_t low_size = (uint32_t)size; + + /* + * uses different magic numbers to make it harder to + * undetectably corrupt + */ + ret->malloc_size = high_size; + ret->malloc_stat = UMEM_MALLOC_ENCODE(MALLOC_MAGIC, high_size); + ret++; + + ret->malloc_size = low_size; + ret->malloc_stat = UMEM_MALLOC_ENCODE(MALLOC_OVERSIZE_MAGIC, + low_size); + ret++; + } else if (size > UMEM_SECOND_ALIGN) { + uint32_t low_size = (uint32_t)size; + + ret++; /* leave the first 8 bytes alone */ + + ret->malloc_size = low_size; + ret->malloc_stat = UMEM_MALLOC_ENCODE(MALLOC_SECOND_MAGIC, + low_size); + ret++; +#endif + } else { + ret->malloc_size = size; + ret->malloc_stat = UMEM_MALLOC_ENCODE(MALLOC_MAGIC, size); + ret++; + } + return ((void *)ret); +} + +void * +calloc(size_t nelem, size_t elsize) +{ + size_t size = nelem * elsize; + void *retval; + + if (nelem > 0 && elsize > 0 && size/nelem != elsize) { + errno = ENOMEM; /* overflow */ + return (NULL); + } + + retval = malloc(size); + if (retval == NULL) + return (NULL); + + (void) memset(retval, 0, size); + return (retval); +} + +/* + * memalign uses vmem_xalloc to do its work. + * + * in 64-bit, the memaligned buffer always has two tags. This simplifies the + * code. + */ + +void * +memalign(size_t align, size_t size_arg) +{ + size_t size; + uintptr_t phase; + + void *buf; + malloc_data_t *ret; + + size_t overhead; + + if (size_arg == 0 || align == 0 || (align & (align - 1)) != 0) { + errno = EINVAL; + return (NULL); + } + + /* + * if malloc provides the required alignment, use it. + */ + if (align <= UMEM_ALIGN || + (align <= UMEM_SECOND_ALIGN && size_arg >= UMEM_SECOND_ALIGN)) + return (malloc(size_arg)); + +#ifdef _LP64 + overhead = 2 * sizeof (malloc_data_t); +#else + overhead = sizeof (malloc_data_t); +#endif + + ASSERT(overhead <= align); + + size = size_arg + overhead; + phase = align - overhead; + + if (umem_memalign_arena == NULL && umem_init() == 0) { + errno = ENOMEM; + return (NULL); + } + + if (size < size_arg) { + errno = ENOMEM; /* overflow */ + return (NULL); + } + + buf = vmem_xalloc(umem_memalign_arena, size, align, phase, + 0, NULL, NULL, VM_NOSLEEP); + + if (buf == NULL) { + if ((size_arg + align) <= UMEM_MAXBUF) + errno = EAGAIN; + else + errno = ENOMEM; + + return (NULL); + } + + ret = (malloc_data_t *)buf; + { + uint32_t low_size = (uint32_t)size; + +#ifdef _LP64 + uint32_t high_size = (uint32_t)(size >> 32); + + ret->malloc_size = high_size; + ret->malloc_stat = UMEM_MALLOC_ENCODE(MEMALIGN_MAGIC, + high_size); + ret++; +#endif + + ret->malloc_size = low_size; + ret->malloc_stat = UMEM_MALLOC_ENCODE(MEMALIGN_MAGIC, low_size); + ret++; + } + + ASSERT(P2PHASE((uintptr_t)ret, align) == 0); + ASSERT((void *)((uintptr_t)ret - overhead) == buf); + + return ((void *)ret); +} + +void * +valloc(size_t size) +{ + return (memalign(pagesize, size)); +} + +/* + * process_free: + * + * Pulls information out of a buffer pointer, and optionally free it. + * This is used by free() and realloc() to process buffers. + * + * On failure, calls umem_err_recoverable() with an appropriate message + * On success, returns the data size through *data_size_arg, if (!is_free). + * + * Preserves errno, since free()'s semantics require it. + */ + +static int +process_free(void *buf_arg, + int do_free, /* free the buffer, or just get its size? */ + size_t *data_size_arg) /* output: bytes of data in buf_arg */ +{ + malloc_data_t *buf; + + void *base; + size_t size; + size_t data_size; + + const char *message; + int old_errno = errno; + + buf = (malloc_data_t *)buf_arg; + + buf--; + size = buf->malloc_size; + + switch (UMEM_MALLOC_DECODE(buf->malloc_stat, size)) { + + case MALLOC_MAGIC: + base = (void *)buf; + data_size = size - sizeof (malloc_data_t); + + if (do_free) + buf->malloc_stat = UMEM_FREE_PATTERN_32; + + goto process_malloc; + +#ifdef _LP64 + case MALLOC_SECOND_MAGIC: + base = (void *)(buf - 1); + data_size = size - 2 * sizeof (malloc_data_t); + + if (do_free) + buf->malloc_stat = UMEM_FREE_PATTERN_32; + + goto process_malloc; + + case MALLOC_OVERSIZE_MAGIC: { + size_t high_size; + + buf--; + high_size = buf->malloc_size; + + if (UMEM_MALLOC_DECODE(buf->malloc_stat, high_size) != + MALLOC_MAGIC) { + message = "invalid or corrupted buffer"; + break; + } + + size += high_size << 32; + + base = (void *)buf; + data_size = size - 2 * sizeof (malloc_data_t); + + if (do_free) { + buf->malloc_stat = UMEM_FREE_PATTERN_32; + (buf + 1)->malloc_stat = UMEM_FREE_PATTERN_32; + } + + goto process_malloc; + } +#endif + + case MEMALIGN_MAGIC: { + size_t overhead = sizeof (malloc_data_t); + +#ifdef _LP64 + size_t high_size; + + overhead += sizeof (malloc_data_t); + + buf--; + high_size = buf->malloc_size; + + if (UMEM_MALLOC_DECODE(buf->malloc_stat, high_size) != + MEMALIGN_MAGIC) { + message = "invalid or corrupted buffer"; + break; + } + size += high_size << 32; + + /* + * destroy the main tag's malloc_stat + */ + if (do_free) + (buf + 1)->malloc_stat = UMEM_FREE_PATTERN_32; +#endif + + base = (void *)buf; + data_size = size - overhead; + + if (do_free) + buf->malloc_stat = UMEM_FREE_PATTERN_32; + + goto process_memalign; + } + default: + if (buf->malloc_stat == UMEM_FREE_PATTERN_32) + message = "double-free or invalid buffer"; + else + message = "invalid or corrupted buffer"; + break; + } + + umem_err_recoverable("%s(%p): %s\n", + do_free? "free" : "realloc", buf_arg, message); + + errno = old_errno; + return (0); + +process_malloc: + if (do_free) + _umem_free(base, size); + else + *data_size_arg = data_size; + + errno = old_errno; + return (1); + +process_memalign: + if (do_free) + vmem_xfree(umem_memalign_arena, base, size); + else + *data_size_arg = data_size; + + errno = old_errno; + return (1); +} + +void +free(void *buf) +{ + if (buf == NULL) + return; + + /* + * Process buf, freeing it if it is not corrupt. + */ + (void) process_free(buf, 1, NULL); +} + +void * +realloc(void *buf_arg, size_t newsize) +{ + size_t oldsize; + void *buf; + + if (buf_arg == NULL) + return (malloc(newsize)); + + /* + * get the old data size without freeing the buffer + */ + if (process_free(buf_arg, 0, &oldsize) == 0) { + errno = EINVAL; + return (NULL); + } + + if (newsize == oldsize) /* size didn't change */ + return (buf_arg); + + buf = malloc(newsize); + if (buf == NULL) + return (NULL); + + (void) memcpy(buf, buf_arg, MIN(newsize, oldsize)); + free(buf_arg); + return (buf); +} |