/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Memory management: malloc(), realloc(), free(), memalign(). * * The following #-parameters may be redefined: * GETCORE: a function to get more core memory. * GETCORE(0) is assumed to return the next available * address. Default is 'sbrk'. * ERRCORE: the error code as returned by GETCORE. * Default is ((char *)(-1)). * CORESIZE: a desired unit (measured in bytes) to be used * with GETCORE. Default is (1024*ALIGN). * * This algorithm is based on a best fit strategy with lists of * free elts maintained in a self-adjusting binary tree. Each list * contains all elts of the same size. The tree is ordered by size. * For results on self-adjusting trees, see the paper: * Self-Adjusting Binary Trees, * DD Sleator & RE Tarjan, JACM 1985. * * The header of a block contains the size of the data part in bytes. * Since the size of a block is 0%4, the low two bits of the header * are free and used as follows: * * BIT0: 1 for busy (block is in use), 0 for free. * BIT1: if the block is busy, this bit is 1 if the * preceding block in contiguous memory is free. * Otherwise, it is always 0. */ #include "mallint.h" static mutex_t __watch_malloc_lock = DEFAULTMUTEX; static TREE *Root; /* root of the free tree */ static TREE *Bottom; /* the last free chunk in the arena */ static char *Baddr; /* current high address of the arena */ static void t_delete(TREE *); static void t_splay(TREE *); static void realfree(void *); static void *malloc_unlocked(size_t); static void free_unlocked(void *); static TREE *morecore(size_t); static void protect(TREE *); static void unprotect(TREE *); #define FREEPAT 0 #define LIVEPAT 1 /* * Patterns to be copied into freed blocks and allocated blocks. * 0xfeedbeef and 0xfeedface are invalid pointer values in all programs. */ static uint64_t patterns[2] = { 0xdeadbeefdeadbeefULL, /* pattern in a freed block */ 0xbaddcafebaddcafeULL /* pattern in an allocated block */ }; static void copy_pattern(int pat, TREE *tp) { uint64_t pattern = patterns[pat]; size_t sz = SIZE(tp) / sizeof (uint64_t); /* LINTED improper alignment */ uint64_t *datap = (uint64_t *)DATA(tp); while (sz--) *datap++ = pattern; } /* * Keep lists of small blocks, LIFO order. */ static TREE *List[MINSIZE/WORDSIZE-1]; static TREE *Last[MINSIZE/WORDSIZE-1]; /* number of blocks to get at one time */ #define NPS (WORDSIZE*8) static void * smalloc(size_t size) { TREE *tp; size_t i; ASSERT(size % WORDSIZE == 0); /* want to return a unique pointer on malloc(0) */ if (size == 0) size = WORDSIZE; /* list to use */ i = size / WORDSIZE - 1; if (List[i] == NULL) { TREE *np; int n; ASSERT((size + WORDSIZE) * NPS >= MINSIZE); /* get NPS of these block types */ if ((np = malloc_unlocked((size + WORDSIZE)*NPS)) == NULL) return (NULL); /* make them into a link list */ for (n = 0, List[i] = np; n < NPS; ++n) { tp = np; SIZE(tp) = size; copy_pattern(FREEPAT, tp); if (n == NPS - 1) { Last[i] = tp; np = NULL; } else { /* LINTED improper alignment */ np = NEXT(tp); } AFTER(tp) = np; protect(tp); } } /* allocate from the head of the queue */ tp = List[i]; unprotect(tp); if ((List[i] = AFTER(tp)) == NULL) Last[i] = NULL; copy_pattern(LIVEPAT, tp); SETBIT0(SIZE(tp)); protect(tp); return (DATA(tp)); } void * malloc(size_t size) { void *ret; _mutex_lock(&__watch_malloc_lock); ret = malloc_unlocked(size); _mutex_unlock(&__watch_malloc_lock); return (ret); } static void * malloc_unlocked(size_t size) { size_t n; TREE *tp, *sp, *tmp; COUNT(nmalloc); ASSERT(WORDSIZE == ALIGN); /* check for size that could overflow calculations */ if (size > MAX_MALLOC) { errno = ENOMEM; return (NULL); } /* make sure that size is 0 mod ALIGN */ ROUND(size); /* small blocks */ if (size < MINSIZE) return (smalloc(size)); /* search for an elt of the right size */ sp = NULL; n = 0; if (Root) { tp = Root; for (;;) { unprotect(tp); if (SIZE(tp) >= size) { /* branch left */ if (n == 0 || n >= SIZE(tp)) { sp = tp; n = SIZE(tp); } if ((tmp = LEFT(tp)) != NULL) { protect(tp); tp = tmp; } else { protect(tp); break; } } else { /* branch right */ if ((tmp = RIGHT(tp)) != NULL) { protect(tp); tp = tmp; } else { protect(tp); break; } } } if (sp) { unprotect(sp); t_delete(sp); } else if (tp != Root) { /* make the searched-to element the root */ unprotect(tp); t_splay(tp); protect(tp); Root = tp; } } /* if found none fitted in the tree */ if (sp == NULL) { if (Bottom) { unprotect(Bottom); if (size <= SIZE(Bottom)) { sp = Bottom; CLRBITS01(SIZE(sp)); } else { protect(Bottom); if ((sp = morecore(size)) == NULL) return (NULL); } } else { if ((sp = morecore(size)) == NULL) return (NULL); } } /* tell the forward neighbor that we're busy */ /* LINTED improper alignment */ tmp = NEXT(sp); unprotect(tmp); CLRBIT1(SIZE(tmp)); ASSERT(ISBIT0(SIZE(tmp))); protect(tmp); leftover: /* if the leftover is enough for a new free piece */ if ((n = (SIZE(sp) - size)) >= MINSIZE + WORDSIZE) { n -= WORDSIZE; SIZE(sp) = size; /* LINTED improper alignment */ tp = NEXT(sp); SIZE(tp) = n | BIT0; realfree(DATA(tp)); } else if (BOTTOM(sp)) Bottom = NULL; /* return the allocated space */ copy_pattern(LIVEPAT, sp); SIZE(sp) |= BIT0; protect(sp); return (DATA(sp)); } /* * realloc(). * If the block size is increasing, we try forward merging first. * This is not best-fit but it avoids some data recopying. */ void * realloc(void *old, size_t size) { TREE *tp, *np; size_t ts; char *new; COUNT(nrealloc); /* check for size that could overflow calculations */ if (size > MAX_MALLOC) { errno = ENOMEM; return (NULL); } /* pointer to the block */ _mutex_lock(&__watch_malloc_lock); if (old == NULL) { new = malloc_unlocked(size); _mutex_unlock(&__watch_malloc_lock); return (new); } /* make sure that size is 0 mod ALIGN */ ROUND(size); /* LINTED improper alignment */ tp = BLOCK(old); unprotect(tp); ts = SIZE(tp); /* if the block was freed, data has been destroyed. */ if (!ISBIT0(ts)) { /* XXX; complain here! */ protect(tp); _mutex_unlock(&__watch_malloc_lock); errno = EINVAL; return (NULL); } CLRBITS01(SIZE(tp)); if (size == SIZE(tp)) { /* nothing to do */ SIZE(tp) = ts; protect(tp); _mutex_unlock(&__watch_malloc_lock); return (old); } /* special cases involving small blocks */ if (size < MINSIZE || SIZE(tp) < MINSIZE) { if (size == 0) { SETOLD01(SIZE(tp), ts); free_unlocked(old); _mutex_unlock(&__watch_malloc_lock); return (NULL); } goto call_malloc; } /* block is increasing in size, try merging the next block */ if (size > SIZE(tp)) { /* LINTED improper alignment */ np = NEXT(tp); unprotect(np); if (ISBIT0(SIZE(np))) protect(np); else { TREE *tmp; ASSERT(SIZE(np) >= MINSIZE); ASSERT(!ISBIT1(SIZE(np))); SIZE(tp) += SIZE(np) + WORDSIZE; if (np != Bottom) t_delete(np); else Bottom = NULL; /* LINTED improper alignment */ tmp = NEXT(np); unprotect(tmp); CLRBIT1(SIZE(tmp)); protect(tmp); } /* not enough & at TRUE end of memory, try extending core */ if (size > SIZE(tp) && BOTTOM(tp) && GETCORE(0) == Baddr) { Bottom = tp; protect(Bottom); if ((tp = morecore(size)) == NULL) { tp = Bottom; Bottom = NULL; unprotect(tp); } } } /* got enough space to use */ if (size <= SIZE(tp)) { size_t n; chop_big: if ((n = (SIZE(tp) - size)) >= MINSIZE + WORDSIZE) { n -= WORDSIZE; SIZE(tp) = size; /* LINTED improper alignment */ np = NEXT(tp); SIZE(np) = n | BIT0; realfree(DATA(np)); } else if (BOTTOM(tp)) Bottom = NULL; /* the previous block may be free */ SETOLD01(SIZE(tp), ts); protect(tp); _mutex_unlock(&__watch_malloc_lock); return (old); } call_malloc: /* call malloc to get a new block */ SETOLD01(SIZE(tp), ts); if ((new = malloc_unlocked(size)) != NULL) { CLRBITS01(ts); if (ts > size) ts = size; (void) memcpy(new, old, ts); free_unlocked(old); _mutex_unlock(&__watch_malloc_lock); return (new); } /* * Attempt special case recovery allocations since malloc() failed: * * 1. size <= SIZE(tp) < MINSIZE * Simply return the existing block * 2. SIZE(tp) < size < MINSIZE * malloc() may have failed to allocate the chunk of * small blocks. Try asking for MINSIZE bytes. * 3. size < MINSIZE <= SIZE(tp) * malloc() may have failed as with 2. Change to * MINSIZE allocation which is taken from the beginning * of the current block. * 4. MINSIZE <= SIZE(tp) < size * If the previous block is free and the combination of * these two blocks has at least size bytes, then merge * the two blocks copying the existing contents backwards. */ CLRBITS01(SIZE(tp)); if (SIZE(tp) < MINSIZE) { if (size < SIZE(tp)) /* case 1. */ { SETOLD01(SIZE(tp), ts); protect(tp); _mutex_unlock(&__watch_malloc_lock); return (old); } else if (size < MINSIZE) /* case 2. */ { size = MINSIZE; goto call_malloc; } } else if (size < MINSIZE) /* case 3. */ { size = MINSIZE; goto chop_big; } else if (ISBIT1(ts)) { np = LAST(tp); unprotect(np); if ((SIZE(np) + SIZE(tp) + WORDSIZE) >= size) { ASSERT(!ISBIT0(SIZE(np))); t_delete(np); SIZE(np) += SIZE(tp) + WORDSIZE; /* * Since the copy may overlap, use memmove(). */ (void) memmove(DATA(np), old, SIZE(tp)); old = DATA(np); tp = np; CLRBIT1(ts); goto chop_big; } protect(np); } SETOLD01(SIZE(tp), ts); protect(tp); _mutex_unlock(&__watch_malloc_lock); /* malloc() sets errno */ return (NULL); } /* * realfree(). * Coalescing of adjacent free blocks is done first. * Then, the new free block is leaf-inserted into the free tree * without splaying. This strategy does not guarantee the amortized * O(nlogn) behaviour for the insert/delete/find set of operations * on the tree. In practice, however, free is much more infrequent * than malloc/realloc and the tree searches performed by these * functions adequately keep the tree in balance. */ static void realfree(void *old) { TREE *tp, *sp, *np, *tmp; size_t ts, size; COUNT(nfree); /* pointer to the block */ /* LINTED improper alignment */ tp = BLOCK(old); unprotect(tp); ts = SIZE(tp); if (!ISBIT0(ts)) { /* block is not busy; previously freed? */ protect(tp); /* force a watchpoint trap */ CLRBIT0(SIZE(tp)); return; } CLRBITS01(SIZE(tp)); copy_pattern(FREEPAT, tp); /* small block, return it to the tail of its queue */ if (SIZE(tp) < MINSIZE) { ASSERT(SIZE(tp) / WORDSIZE >= 1); ts = SIZE(tp) / WORDSIZE - 1; AFTER(tp) = NULL; protect(tp); if (List[ts] == NULL) { List[ts] = tp; Last[ts] = tp; } else { sp = Last[ts]; unprotect(sp); AFTER(sp) = tp; protect(sp); Last[ts] = tp; } return; } /* see if coalescing with next block is warranted */ /* LINTED improper alignment */ np = NEXT(tp); unprotect(np); if (ISBIT0(SIZE(np))) protect(np); else { if (np != Bottom) t_delete(np); SIZE(tp) += SIZE(np) + WORDSIZE; } /* the same with the preceding block */ if (ISBIT1(ts)) { np = LAST(tp); unprotect(np); ASSERT(!ISBIT0(SIZE(np))); ASSERT(np != Bottom); t_delete(np); SIZE(np) += SIZE(tp) + WORDSIZE; tp = np; } /* initialize tree info */ PARENT(tp) = LEFT(tp) = RIGHT(tp) = LINKFOR(tp) = NULL; /* set bottom block, or insert in the free tree */ if (BOTTOM(tp)) Bottom = tp; else { /* search for the place to insert */ if (Root) { size = SIZE(tp); np = Root; for (;;) { unprotect(np); if (SIZE(np) > size) { if ((tmp = LEFT(np)) != NULL) { protect(np); np = tmp; } else { LEFT(np) = tp; PARENT(tp) = np; protect(np); break; } } else if (SIZE(np) < size) { if ((tmp = RIGHT(np)) != NULL) { protect(np); np = tmp; } else { RIGHT(np) = tp; PARENT(tp) = np; protect(np); break; } } else { if ((sp = PARENT(np)) != NULL) { unprotect(sp); if (np == LEFT(sp)) LEFT(sp) = tp; else RIGHT(sp) = tp; PARENT(tp) = sp; protect(sp); } else Root = tp; /* insert to head of list */ if ((sp = LEFT(np)) != NULL) { unprotect(sp); PARENT(sp) = tp; protect(sp); } LEFT(tp) = sp; if ((sp = RIGHT(np)) != NULL) { unprotect(sp); PARENT(sp) = tp; protect(sp); } RIGHT(tp) = sp; /* doubly link list */ LINKFOR(tp) = np; LINKBAK(np) = tp; SETNOTREE(np); protect(np); break; } } } else { Root = tp; } } /* * Tell next block that this one is free. * The first WORD of the next block contains self's address. */ /* LINTED improper alignment */ tmp = NEXT(tp); unprotect(tmp); /* LINTED improper alignment */ *(SELFP(tp)) = tp; SETBIT1(SIZE(tmp)); ASSERT(ISBIT0(SIZE(tmp))); protect(tmp); protect(tp); } /* * Get more core. Gaps in memory are noted as busy blocks. */ static TREE * morecore(size_t size) { TREE *tp; size_t n, offset, requestsize; char *addr; /* compute new amount of memory to get */ tp = Bottom; n = size + 2 * WORDSIZE; addr = GETCORE(0); if (addr == ERRCORE) /* errno set by GETCORE sbrk */ return (NULL); /* need to pad size out so that addr is aligned */ if ((((size_t)addr) % ALIGN) != 0) offset = ALIGN - (size_t)addr % ALIGN; else offset = 0; if (tp) unprotect(tp); /* if not segmented memory, what we need may be smaller */ if (addr == Baddr) { n -= WORDSIZE; if (tp != NULL) n -= SIZE(tp); } /* get a multiple of CORESIZE */ n = ((n - 1) / CORESIZE + 1) * CORESIZE; requestsize = n + offset; /* check if nsize request could overflow in GETCORE */ if (requestsize > MAX_MALLOC - (size_t)addr) { if (tp) protect(tp); errno = ENOMEM; return (NULL); } if (requestsize > MAX_GETCORE) { intptr_t delta; /* * the value required is too big for GETCORE() to deal with * in one go, so use GETCORE() at most 2 times instead. * Argument to GETCORE() must be multiple of ALIGN. * If not, GETCORE(-MAX_GETCORE) will not return brk point * to previous value, but will be ALIGN more. * This would leave a small hole. */ delta = MAX_GETCORE; while (delta > 0) { if (GETCORE(delta) == ERRCORE) { if (tp) protect(tp); if (addr != GETCORE(0)) (void) GETCORE(-MAX_GETCORE); return (NULL); } requestsize -= MAX_GETCORE; delta = requestsize; } } else if (GETCORE(requestsize) == ERRCORE) { if (tp) protect(tp); return (NULL); } /* contiguous memory */ if (addr == Baddr) { ASSERT(offset == 0); if (tp) { addr = ((char *)tp); n += SIZE(tp) + 2 * WORDSIZE; } else { addr = Baddr - WORDSIZE; n += WORDSIZE; } } else { addr += offset; } /* new bottom address */ Baddr = addr + n; /* new bottom block */ /* LINTED improper alignment */ tp = ((TREE *)addr); SIZE(tp) = n - 2 * WORDSIZE; ASSERT((SIZE(tp) % ALIGN) == 0); /* reserved the last word to head any noncontiguous memory */ /* LINTED improper alignment */ SETBIT0(SIZE(NEXT(tp))); /* non-contiguous memory, free old bottom block */ if (Bottom && Bottom != tp) { SETBIT0(SIZE(Bottom)); realfree(DATA(Bottom)); } return (tp); } /* * Utility function to avoid protecting a tree node twice. * Return true if tp is in the NULL-terminated array of tree nodes. */ static int in_list(TREE *tp, TREE **npp) { TREE *sp; while ((sp = *npp++) != NULL) if (tp == sp) return (1); return (0); } /* * Tree rotation functions (BU: bottom-up, TD: top-down). * All functions are entered with the arguments unprotected. * They must return in the same condition, with all other elements * that have been unprotected during the operation re-protected. */ static void LEFT1(TREE *x, TREE *y) { TREE *node[3]; TREE **npp = node; TREE *tp; if ((RIGHT(x) = LEFT(y)) != NULL) { unprotect(*npp++ = RIGHT(x)); PARENT(RIGHT(x)) = x; } if ((PARENT(y) = PARENT(x)) != NULL) { unprotect(*npp++ = PARENT(x)); if (LEFT(PARENT(x)) == x) LEFT(PARENT(y)) = y; else RIGHT(PARENT(y)) = y; } LEFT(y) = x; PARENT(x) = y; *npp = NULL; npp = node; while ((tp = *npp++) != NULL) if (tp != x && tp != y && !in_list(tp, npp)) protect(tp); } static void RIGHT1(TREE *x, TREE *y) { TREE *node[3]; TREE **npp = node; TREE *tp; if ((LEFT(x) = RIGHT(y)) != NULL) { unprotect(*npp++ = LEFT(x)); PARENT(LEFT(x)) = x; } if ((PARENT(y) = PARENT(x)) != NULL) { unprotect(*npp++ = PARENT(x)); if (LEFT(PARENT(x)) == x) LEFT(PARENT(y)) = y; else RIGHT(PARENT(y)) = y; } RIGHT(y) = x; PARENT(x) = y; *npp = NULL; npp = node; while ((tp = *npp++) != NULL) if (tp != x && tp != y && !in_list(tp, npp)) protect(tp); } static void BULEFT2(TREE *x, TREE *y, TREE *z) { TREE *node[4]; TREE **npp = node; TREE *tp; if ((RIGHT(x) = LEFT(y)) != NULL) { unprotect(*npp++ = RIGHT(x)); PARENT(RIGHT(x)) = x; } if ((RIGHT(y) = LEFT(z)) != NULL) { unprotect(*npp++ = RIGHT(y)); PARENT(RIGHT(y)) = y; } if ((PARENT(z) = PARENT(x)) != NULL) { unprotect(*npp++ = PARENT(x)); if (LEFT(PARENT(x)) == x) LEFT(PARENT(z)) = z; else RIGHT(PARENT(z)) = z; } LEFT(z) = y; PARENT(y) = z; LEFT(y) = x; PARENT(x) = y; *npp = NULL; npp = node; while ((tp = *npp++) != NULL) if (tp != x && tp != y && tp != z && !in_list(tp, npp)) protect(tp); } static void BURIGHT2(TREE *x, TREE *y, TREE *z) { TREE *node[4]; TREE **npp = node; TREE *tp; if ((LEFT(x) = RIGHT(y)) != NULL) { unprotect(*npp++ = LEFT(x)); PARENT(LEFT(x)) = x; } if ((LEFT(y) = RIGHT(z)) != NULL) { unprotect(*npp++ = LEFT(y)); PARENT(LEFT(y)) = y; } if ((PARENT(z) = PARENT(x)) != NULL) { unprotect(*npp++ = PARENT(x)); if (LEFT(PARENT(x)) == x) LEFT(PARENT(z)) = z; else RIGHT(PARENT(z)) = z; } RIGHT(z) = y; PARENT(y) = z; RIGHT(y) = x; PARENT(x) = y; *npp = NULL; npp = node; while ((tp = *npp++) != NULL) if (tp != x && tp != y && tp != z && !in_list(tp, npp)) protect(tp); } static void TDLEFT2(TREE *x, TREE *y, TREE *z) { TREE *node[3]; TREE **npp = node; TREE *tp; if ((RIGHT(y) = LEFT(z)) != NULL) { unprotect(*npp++ = RIGHT(y)); PARENT(RIGHT(y)) = y; } if ((PARENT(z) = PARENT(x)) != NULL) { unprotect(*npp++ = PARENT(x)); if (LEFT(PARENT(x)) == x) LEFT(PARENT(z)) = z; else RIGHT(PARENT(z)) = z; } PARENT(x) = z; LEFT(z) = x; *npp = NULL; npp = node; while ((tp = *npp++) != NULL) if (tp != x && tp != y && tp != z && !in_list(tp, npp)) protect(tp); } #if 0 /* Not used, for now */ static void TDRIGHT2(TREE *x, TREE *y, TREE *z) { TREE *node[3]; TREE **npp = node; TREE *tp; if ((LEFT(y) = RIGHT(z)) != NULL) { unprotect(*npp++ = LEFT(y)); PARENT(LEFT(y)) = y; } if ((PARENT(z) = PARENT(x)) != NULL) { unprotect(*npp++ = PARENT(x)); if (LEFT(PARENT(x)) == x) LEFT(PARENT(z)) = z; else RIGHT(PARENT(z)) = z; } PARENT(x) = z; RIGHT(z) = x; *npp = NULL; npp = node; while ((tp = *npp++) != NULL) if (tp != x && tp != y && tp != z && !in_list(tp, npp)) protect(tp); } #endif /* * Delete a tree element */ static void t_delete(TREE *op) { TREE *tp, *sp, *gp; /* if this is a non-tree node */ if (ISNOTREE(op)) { tp = LINKBAK(op); unprotect(tp); if ((sp = LINKFOR(op)) != NULL) { unprotect(sp); LINKBAK(sp) = tp; protect(sp); } LINKFOR(tp) = sp; protect(tp); return; } /* make op the root of the tree */ if (PARENT(op)) t_splay(op); /* if this is the start of a list */ if ((tp = LINKFOR(op)) != NULL) { unprotect(tp); PARENT(tp) = NULL; if ((sp = LEFT(op)) != NULL) { unprotect(sp); PARENT(sp) = tp; protect(sp); } LEFT(tp) = sp; if ((sp = RIGHT(op)) != NULL) { unprotect(sp); PARENT(sp) = tp; protect(sp); } RIGHT(tp) = sp; Root = tp; protect(tp); return; } /* if op has a non-null left subtree */ if ((tp = LEFT(op)) != NULL) { unprotect(tp); PARENT(tp) = NULL; if (RIGHT(op)) { /* make the right-end of the left subtree its root */ while ((sp = RIGHT(tp)) != NULL) { unprotect(sp); if ((gp = RIGHT(sp)) != NULL) { unprotect(gp); TDLEFT2(tp, sp, gp); protect(sp); protect(tp); tp = gp; } else { LEFT1(tp, sp); protect(tp); tp = sp; } } /* hook the right subtree of op to the above elt */ RIGHT(tp) = sp = RIGHT(op); unprotect(sp); PARENT(sp) = tp; protect(sp); } protect(tp); } else if ((tp = RIGHT(op)) != NULL) { /* no left subtree */ unprotect(tp); PARENT(tp) = NULL; protect(tp); } Root = tp; } /* * Bottom up splaying (simple version). * The basic idea is to roughly cut in half the * path from Root to tp and make tp the new root. */ static void t_splay(TREE *tp) { TREE *pp, *gp; /* iterate until tp is the root */ while ((pp = PARENT(tp)) != NULL) { unprotect(pp); /* grandparent of tp */ gp = PARENT(pp); if (gp) unprotect(gp); /* x is a left child */ if (LEFT(pp) == tp) { if (gp && LEFT(gp) == pp) { BURIGHT2(gp, pp, tp); protect(gp); } else { if (gp) protect(gp); RIGHT1(pp, tp); } } else { ASSERT(RIGHT(pp) == tp); if (gp && RIGHT(gp) == pp) { BULEFT2(gp, pp, tp); protect(gp); } else { if (gp) protect(gp); LEFT1(pp, tp); } } protect(pp); unprotect(tp); /* just in case */ } } void free(void *old) { _mutex_lock(&__watch_malloc_lock); free_unlocked(old); _mutex_unlock(&__watch_malloc_lock); } static void free_unlocked(void *old) { if (old != NULL) realfree(old); } /* * memalign(align,nbytes) * * Description: * Returns a block of specified size on a specified alignment boundary. * * Algorithm: * Malloc enough to ensure that a block can be aligned correctly. * Find the alignment point and return the fragments * before and after the block. * * Errors: * Returns NULL and sets errno as follows: * [EINVAL] * if nbytes = 0, * or if alignment is misaligned, * or if the heap has been detectably corrupted. * [ENOMEM] * if the requested memory could not be allocated. */ #define misaligned(p) ((unsigned)(p) & 3) /* 4-byte "word" alignment is considered ok in LP64 */ #define nextblk(p, size) ((TREE *)((char *)(p) + (size))) void * memalign(size_t align, size_t nbytes) { size_t reqsize; /* Num of bytes to get from malloc() */ TREE *p; /* Ptr returned from malloc() */ TREE *blk; /* For addressing fragment blocks */ size_t blksize; /* Current (shrinking) block size */ TREE *alignedp; /* Ptr to properly aligned boundary */ TREE *aligned_blk; /* The block to be returned */ size_t frag_size; /* size of fragments fore and aft */ size_t x; /* * check for valid size and alignment parameters * MAX_ALIGN check prevents overflow in later calculation. */ if (nbytes == 0 || misaligned(align) || align == 0 || align > MAX_ALIGN) { errno = EINVAL; return (NULL); } /* * Malloc enough memory to guarantee that the result can be * aligned correctly. The worst case is when malloc returns * a block so close to the next alignment boundary that a * fragment of minimum size cannot be created. In order to * make sure we can handle this, we need to force the * alignment to be at least as large as the minimum frag size * (MINSIZE + WORDSIZE). */ /* check for size that could overflow ROUND calculation */ if (nbytes > MAX_MALLOC) { errno = ENOMEM; return (NULL); } ROUND(nbytes); if (nbytes < MINSIZE) nbytes = MINSIZE; ROUND(align); while (align < MINSIZE + WORDSIZE) align <<= 1; reqsize = nbytes + align + (MINSIZE + WORDSIZE); /* check for overflow */ if (reqsize < nbytes) { errno = ENOMEM; return (NULL); } p = (TREE *) malloc(reqsize); if (p == (TREE *) NULL) { /* malloc sets errno */ return (NULL); } _mutex_lock(&__watch_malloc_lock); /* * get size of the entire block (overhead and all) */ /* LINTED improper alignment */ blk = BLOCK(p); /* back up to get length word */ unprotect(blk); blksize = SIZE(blk); CLRBITS01(blksize); /* * locate the proper alignment boundary within the block. */ x = (size_t)p; if (x % align != 0) x += align - (x % align); alignedp = (TREE *)x; /* LINTED improper alignment */ aligned_blk = BLOCK(alignedp); /* * Check out the space to the left of the alignment * boundary, and split off a fragment if necessary. */ frag_size = (size_t)aligned_blk - (size_t)blk; if (frag_size != 0) { /* * Create a fragment to the left of the aligned block. */ if (frag_size < MINSIZE + WORDSIZE) { /* * Not enough space. So make the split * at the other end of the alignment unit. * We know this yields enough space, because * we forced align >= MINSIZE + WORDSIZE above. */ frag_size += align; /* LINTED improper alignment */ aligned_blk = nextblk(aligned_blk, align); } blksize -= frag_size; SIZE(aligned_blk) = blksize | BIT0; frag_size -= WORDSIZE; SIZE(blk) = frag_size | BIT0 | ISBIT1(SIZE(blk)); free_unlocked(DATA(blk)); /* * free_unlocked(DATA(blk)) has the side-effect of calling * protect() on the block following blk, that is, aligned_blk. * We recover from this by unprotect()ing it here. */ unprotect(aligned_blk); } /* * Is there a (sufficiently large) fragment to the * right of the aligned block? */ frag_size = blksize - nbytes; if (frag_size >= MINSIZE + WORDSIZE) { /* * split and free a fragment on the right */ blksize = SIZE(aligned_blk); SIZE(aligned_blk) = nbytes; /* LINTED improper alignment */ blk = NEXT(aligned_blk); SETOLD01(SIZE(aligned_blk), blksize); frag_size -= WORDSIZE; SIZE(blk) = frag_size | BIT0; free_unlocked(DATA(blk)); } copy_pattern(LIVEPAT, aligned_blk); protect(aligned_blk); _mutex_unlock(&__watch_malloc_lock); return (DATA(aligned_blk)); } void * valloc(size_t size) { static unsigned pagesize; if (!pagesize) pagesize = _sysconf(_SC_PAGESIZE); return (memalign(pagesize, size)); } void * calloc(size_t num, size_t size) { void *mp; size_t total; total = num * size; /* check for overflow */ if (num != 0 && total / num != size) { errno = ENOMEM; return (NULL); } if ((mp = malloc(total)) != NULL) (void) memset(mp, 0, total); return (mp); } /* ARGSUSED1 */ void cfree(void *p, size_t num, size_t size) { free(p); } typedef struct { long cmd; prwatch_t prwatch; } ctl_t; static pid_t my_pid = 0; /* to check for whether we fork()d */ static int dont_watch = 0; static int do_stop = 0; static int ctlfd = -1; struct stat ctlstatb; static int wflags = WA_WRITE; static void init_watch() { char str[80]; char *s; my_pid = getpid(); dont_watch = 1; if ((s = getenv("MALLOC_DEBUG")) == NULL) return; s = strncpy(str, s, sizeof (str)); while (s != NULL) { char *e = strchr(s, ','); if (e) *e++ = '\0'; if (strcmp(s, "STOP") == 0) do_stop = 1; else if (strcmp(s, "WATCH") == 0) dont_watch = 0; else if (strcmp(s, "RW") == 0) { dont_watch = 0; wflags = WA_READ|WA_WRITE; } s = e; } if (dont_watch) return; if ((ctlfd = open("/proc/self/ctl", O_WRONLY)) < 0 || fstat(ctlfd, &ctlstatb) != 0) { if (ctlfd >= 0) (void) close(ctlfd); ctlfd = -1; dont_watch = 1; return; } /* close-on-exec */ (void) fcntl(ctlfd, F_SETFD, 1); if (do_stop) { int pfd; pstatus_t pstatus; struct { long cmd; fltset_t fltset; } ctl; /* * Play together with some /proc controller * that has set other stop-on-fault flags. */ premptyset(&ctl.fltset); if ((pfd = open("/proc/self/status", O_RDONLY)) >= 0) { if (read(pfd, &pstatus, sizeof (pstatus)) == sizeof (pstatus)) ctl.fltset = pstatus.pr_flttrace; (void) close(pfd); } praddset(&ctl.fltset, FLTWATCH); ctl.cmd = PCSFAULT; (void) write(ctlfd, &ctl, sizeof (ctl)); } } static int nowatch() { struct stat statb; if (dont_watch) return (1); if (ctlfd < 0) /* first time */ init_watch(); else if (fstat(ctlfd, &statb) != 0 || statb.st_dev != ctlstatb.st_dev || statb.st_ino != ctlstatb.st_ino) { /* * Someone closed our file descriptor. * Just open another one. */ if ((ctlfd = open("/proc/self/ctl", O_WRONLY)) < 0 || fstat(ctlfd, &ctlstatb) != 0) { if (ctlfd >= 0) (void) close(ctlfd); ctlfd = -1; dont_watch = 1; return (1); } /* close-on-exec */ (void) fcntl(ctlfd, F_SETFD, 1); } if (my_pid != getpid()) { /* * We fork()d since the last call to the allocator. * watchpoints are not inherited across fork(). * XXX: how to recover from this ??? */ dont_watch = 1; (void) close(ctlfd); ctlfd = -1; } return (dont_watch); } static void protect(TREE *tp) { ctl_t ctl; size_t size, sz; if (nowatch()) return; if (tp == NULL || DATA(tp) == Baddr) return; sz = size = SIZE(tp); CLRBITS01(size); if (size == 0) return; if (ISBIT0(sz)) /* block is busy, protect only the head */ size = 0; ctl.cmd = PCWATCH; ctl.prwatch.pr_vaddr = (uintptr_t)tp; ctl.prwatch.pr_size = size + WORDSIZE; ctl.prwatch.pr_wflags = wflags; (void) write(ctlfd, &ctl, sizeof (ctl)); } static void unprotect(TREE *tp) { ctl_t ctl; if (nowatch()) return; if (tp == NULL || DATA(tp) == Baddr) return; ctl.cmd = PCWATCH; ctl.prwatch.pr_vaddr = (uintptr_t)tp; ctl.prwatch.pr_size = WORDSIZE; /* size is arbitrary */ ctl.prwatch.pr_wflags = 0; /* clear the watched area */ (void) write(ctlfd, &ctl, sizeof (ctl)); }