blob: 653dc06edb94a23b2e3090534b2282309490dc96 [file] [log] [blame]
/*--------------------------------------------------------------------*/
/*--- An implementation of malloc/free for the client. ---*/
/*--- vg_clientmalloc.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of Valgrind, an extensible x86 protected-mode
emulator for monitoring program execution on x86-Unixes.
Copyright (C) 2000-2002 Julian Seward
jseward@acm.org
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307, USA.
The GNU General Public License is contained in the file COPYING.
*/
#include "vg_include.h"
/*------------------------------------------------------------*/
/*--- Defns ---*/
/*------------------------------------------------------------*/
/* #define DEBUG_CLIENTMALLOC */
/* Holds malloc'd but not freed blocks. Static, so zero-inited by default. */
#define VG_MALLOCLIST_NO(aa) (((UInt)(aa)) % VG_N_MALLOCLISTS)
static ShadowChunk* vg_malloclist[VG_N_MALLOCLISTS];
/* Stats ... */
static UInt vg_cmalloc_n_mallocs = 0;
static UInt vg_cmalloc_n_frees = 0;
static UInt vg_cmalloc_bs_mallocd = 0;
static UInt vg_mlist_frees = 0;
static UInt vg_mlist_tries = 0;
/*------------------------------------------------------------*/
/*--- Fns ---*/
/*------------------------------------------------------------*/
static __inline__
Bool needs_shadow_chunks ( void )
{
return VG_(needs).core_errors ||
VG_(needs).alternative_free ||
VG_(needs).sizeof_shadow_block > 0 ||
VG_(track_events).bad_free ||
VG_(track_events).mismatched_free ||
VG_(track_events).copy_mem_heap ||
VG_(track_events).die_mem_heap;
}
#ifdef DEBUG_CLIENTMALLOC
static
Int count_malloclists ( void )
{
ShadowChunk* sc;
UInt ml_no;
Int n = 0;
for (ml_no = 0; ml_no < VG_N_MALLOCLISTS; ml_no++)
for (sc = vg_malloclist[ml_no]; sc != NULL; sc = sc->next)
n++;
return n;
}
#endif
/*------------------------------------------------------------*/
/*--- Shadow chunks, etc ---*/
/*------------------------------------------------------------*/
/* Allocate a user-chunk of size bytes. Also allocate its shadow
block, make the shadow block point at the user block. Put the
shadow chunk on the appropriate list, and set all memory
protections correctly. */
static void addShadowChunk ( ThreadState* tst,
Addr p, UInt size, VgAllocKind kind )
{
ShadowChunk* sc;
UInt ml_no = VG_MALLOCLIST_NO(p);
# ifdef DEBUG_CLIENTMALLOC
VG_(printf)("[m %d, f %d (%d)] addShadowChunk "
"( sz %d, addr %p, list %d )\n",
count_malloclists(),
0/*count_freelist()*/, 0/*vg_freed_list_volume*/,
size, p, ml_no );
# endif
sc = VG_(arena_malloc)(VG_AR_CORE,
sizeof(ShadowChunk)
+ VG_(needs).sizeof_shadow_block);
sc->size = size;
sc->allockind = kind;
sc->data = p;
/* Fill in any skin-specific shadow chunk stuff */
if (VG_(needs).sizeof_shadow_block > 0)
SK_(complete_shadow_chunk) ( sc, tst );
sc->next = vg_malloclist[ml_no];
vg_malloclist[ml_no] = sc;
}
/* Get the sc, and return the address of the previous node's next pointer
which allows sc to be removed from the list later without having to look
it up again. */
static ShadowChunk* getShadowChunk ( Addr a, /*OUT*/ShadowChunk*** next_ptr )
{
ShadowChunk *prev, *curr;
Int ml_no;
ml_no = VG_MALLOCLIST_NO(a);
prev = NULL;
curr = vg_malloclist[ml_no];
while (True) {
if (curr == NULL)
break;
if (a == curr->data)
break;
prev = curr;
curr = curr->next;
}
if (NULL == prev)
*next_ptr = &vg_malloclist[ml_no];
else
*next_ptr = &prev->next;
return curr;
}
void VG_(free_ShadowChunk) ( ShadowChunk* sc )
{
VG_(arena_free) ( VG_AR_CLIENT, (void*)sc->data );
VG_(arena_free) ( VG_AR_CORE, sc );
}
/* Allocate a suitably-sized array, copy all the malloc-d block
shadows into it, and return both the array and the size of it.
This is used by the memory-leak detector.
*/
ShadowChunk** VG_(get_malloc_shadows) ( /*OUT*/ UInt* n_shadows )
{
UInt i, scn;
ShadowChunk** arr;
ShadowChunk* sc;
*n_shadows = 0;
for (scn = 0; scn < VG_N_MALLOCLISTS; scn++) {
for (sc = vg_malloclist[scn]; sc != NULL; sc = sc->next) {
(*n_shadows)++;
}
}
if (*n_shadows == 0) return NULL;
arr = VG_(malloc)( *n_shadows * sizeof(ShadowChunk*) );
i = 0;
for (scn = 0; scn < VG_N_MALLOCLISTS; scn++) {
for (sc = vg_malloclist[scn]; sc != NULL; sc = sc->next) {
arr[i++] = sc;
}
}
vg_assert(i == *n_shadows);
return arr;
}
Bool VG_(addr_is_in_block)( Addr a, Addr start, UInt size )
{
return (start - VG_AR_CLIENT_REDZONE_SZB <= a
&& a < start + size + VG_AR_CLIENT_REDZONE_SZB);
}
/* Return the first shadow chunk satisfying the predicate p. */
ShadowChunk* VG_(any_matching_mallocd_ShadowChunks)
( Bool (*p) ( ShadowChunk* ))
{
UInt ml_no;
ShadowChunk* sc;
for (ml_no = 0; ml_no < VG_N_MALLOCLISTS; ml_no++)
for (sc = vg_malloclist[ml_no]; sc != NULL; sc = sc->next)
if (p(sc))
return sc;
return NULL;
}
/*------------------------------------------------------------*/
/*--- client_malloc(), etc ---*/
/*------------------------------------------------------------*/
/* Allocate memory, noticing whether or not we are doing the full
instrumentation thing. */
static __inline__
void* alloc_and_new_mem ( ThreadState* tst, UInt size, UInt alignment,
Bool is_zeroed, VgAllocKind kind )
{
Addr p;
VGP_PUSHCC(VgpCliMalloc);
vg_cmalloc_n_mallocs ++;
vg_cmalloc_bs_mallocd += size;
vg_assert(alignment >= 4);
if (alignment == 4)
p = (Addr)VG_(arena_malloc)(VG_AR_CLIENT, size);
else
p = (Addr)VG_(arena_malloc_aligned)(VG_AR_CLIENT, alignment, size);
if (needs_shadow_chunks())
addShadowChunk ( tst, p, size, kind );
VG_TRACK( ban_mem_heap, p-VG_AR_CLIENT_REDZONE_SZB,
VG_AR_CLIENT_REDZONE_SZB );
VG_TRACK( new_mem_heap, p, size, is_zeroed );
VG_TRACK( ban_mem_heap, p+size, VG_AR_CLIENT_REDZONE_SZB );
VGP_POPCC(VgpCliMalloc);
return (void*)p;
}
void* VG_(client_malloc) ( ThreadState* tst, UInt size, VgAllocKind kind )
{
void* p = alloc_and_new_mem ( tst, size, VG_(clo_alignment),
/*is_zeroed*/False, kind );
# ifdef DEBUG_CLIENTMALLOC
VG_(printf)("[m %d, f %d (%d)] client_malloc ( %d, %x ) = %p\n",
count_malloclists(),
0/*count_freelist()*/, 0/*vg_freed_list_volume*/,
size, kind, p );
# endif
return p;
}
void* VG_(client_memalign) ( ThreadState* tst, UInt align, UInt size )
{
void* p = alloc_and_new_mem ( tst, size, align,
/*is_zeroed*/False, Vg_AllocMalloc );
# ifdef DEBUG_CLIENTMALLOC
VG_(printf)("[m %d, f %d (%d)] client_memalign ( al %d, sz %d ) = %p\n",
count_malloclists(),
0/*count_freelist()*/, 0/*vg_freed_list_volume*/,
align, size, p );
# endif
return p;
}
void* VG_(client_calloc) ( ThreadState* tst, UInt nmemb, UInt size1 )
{
void* p;
UInt size, i;
size = nmemb * size1;
p = alloc_and_new_mem ( tst, size, VG_(clo_alignment),
/*is_zeroed*/True, Vg_AllocMalloc );
/* Must zero block for calloc! */
for (i = 0; i < size; i++) ((UChar*)p)[i] = 0;
# ifdef DEBUG_CLIENTMALLOC
VG_(printf)("[m %d, f %d (%d)] client_calloc ( %d, %d ) = %p\n",
count_malloclists(),
0/*count_freelist()*/, 0/*vg_freed_list_volume*/,
nmemb, size1, p );
# endif
return p;
}
static
void die_and_free_mem ( ThreadState* tst, ShadowChunk* sc,
ShadowChunk** prev_chunks_next_ptr )
{
/* Note: ban redzones again -- just in case user de-banned them
with a client request... */
VG_TRACK( ban_mem_heap, sc->data-VG_AR_CLIENT_REDZONE_SZB,
VG_AR_CLIENT_REDZONE_SZB );
VG_TRACK( die_mem_heap, sc->data, sc->size );
VG_TRACK( ban_mem_heap, sc->data+sc->size, VG_AR_CLIENT_REDZONE_SZB );
/* Remove sc from the malloclist using prev_chunks_next_ptr to
avoid repeating the hash table lookup. Can't remove until at least
after free and free_mismatch errors are done because they use
describe_addr() which looks for it in malloclist. */
*prev_chunks_next_ptr = sc->next;
if (VG_(needs).alternative_free)
SK_(alt_free) ( sc, tst );
else
VG_(free_ShadowChunk) ( sc );
}
void VG_(client_free) ( ThreadState* tst, void* p, VgAllocKind kind )
{
ShadowChunk* sc;
ShadowChunk** prev_chunks_next_ptr;
VGP_PUSHCC(VgpCliMalloc);
# ifdef DEBUG_CLIENTMALLOC
VG_(printf)("[m %d, f %d (%d)] client_free ( %p, %x )\n",
count_malloclists(),
0/*count_freelist()*/, 0/*vg_freed_list_volume*/,
p, kind );
# endif
vg_cmalloc_n_frees ++;
if (! needs_shadow_chunks()) {
VG_(arena_free) ( VG_AR_CLIENT, p );
} else {
sc = getShadowChunk ( (Addr)p, &prev_chunks_next_ptr );
if (sc == NULL) {
VG_TRACK( bad_free, tst, (Addr)p );
VGP_POPCC(VgpCliMalloc);
return;
}
/* check if its a matching free() / delete / delete [] */
if (kind != sc->allockind)
VG_TRACK( mismatched_free, tst, (Addr)p );
die_and_free_mem ( tst, sc, prev_chunks_next_ptr );
}
VGP_POPCC(VgpCliMalloc);
}
void* VG_(client_realloc) ( ThreadState* tst, void* p, UInt new_size )
{
ShadowChunk *sc;
ShadowChunk **prev_chunks_next_ptr;
UInt i;
VGP_PUSHCC(VgpCliMalloc);
vg_cmalloc_n_frees ++;
vg_cmalloc_n_mallocs ++;
vg_cmalloc_bs_mallocd += new_size;
if (! needs_shadow_chunks()) {
vg_assert(p != NULL && new_size != 0);
p = VG_(arena_realloc) ( VG_AR_CLIENT, p, VG_(clo_alignment),
new_size );
VGP_POPCC(VgpCliMalloc);
return p;
} else {
/* First try and find the block. */
sc = getShadowChunk ( (Addr)p, &prev_chunks_next_ptr );
if (sc == NULL) {
VG_TRACK( bad_free, tst, (Addr)p );
/* Perhaps we should return to the program regardless. */
VGP_POPCC(VgpCliMalloc);
return NULL;
}
/* check if its a matching free() / delete / delete [] */
if (Vg_AllocMalloc != sc->allockind) {
/* can not realloc a range that was allocated with new or new [] */
VG_TRACK( mismatched_free, tst, (Addr)p );
/* but keep going anyway */
}
if (sc->size == new_size) {
/* size unchanged */
VGP_POPCC(VgpCliMalloc);
return p;
} else if (sc->size > new_size) {
/* new size is smaller */
VG_TRACK( die_mem_heap, sc->data+new_size, sc->size-new_size );
sc->size = new_size;
VGP_POPCC(VgpCliMalloc);
# ifdef DEBUG_CLIENTMALLOC
VG_(printf)("[m %d, f %d (%d)] client_realloc_smaller ( %p, %d ) = %p\n",
count_malloclists(),
0/*count_freelist()*/, 0/*vg_freed_list_volume*/,
p, new_size, p );
# endif
return p;
} else {
/* new size is bigger */
Addr p_new;
/* Get new memory */
vg_assert(VG_(clo_alignment) >= 4);
if (VG_(clo_alignment) == 4)
p_new = (Addr)VG_(arena_malloc)(VG_AR_CLIENT, new_size);
else
p_new = (Addr)VG_(arena_malloc_aligned)(VG_AR_CLIENT,
VG_(clo_alignment), new_size);
/* First half kept and copied, second half new,
red zones as normal */
VG_TRACK( ban_mem_heap, p_new-VG_AR_CLIENT_REDZONE_SZB,
VG_AR_CLIENT_REDZONE_SZB );
VG_TRACK( copy_mem_heap, (Addr)p, p_new, sc->size );
VG_TRACK( new_mem_heap, p_new+sc->size, new_size-sc->size,
/*inited=*/False );
VG_TRACK( ban_mem_heap, p_new+new_size, VG_AR_CLIENT_REDZONE_SZB );
/* Copy from old to new */
for (i = 0; i < sc->size; i++)
((UChar*)p_new)[i] = ((UChar*)p)[i];
/* Free old memory */
die_and_free_mem ( tst, sc, prev_chunks_next_ptr );
/* this has to be after die_and_free_mem, otherwise the
former succeeds in shorting out the new block, not the
old, in the case when both are on the same list. */
addShadowChunk ( tst, p_new, new_size, Vg_AllocMalloc );
VGP_POPCC(VgpCliMalloc);
# ifdef DEBUG_CLIENTMALLOC
VG_(printf)("[m %d, f %d (%d)] client_realloc_bigger ( %p, %d ) = %p\n",
count_malloclists(),
0/*count_freelist()*/, 0/*vg_freed_list_volume*/,
p, new_size, (void*)p_new );
# endif
return (void*)p_new;
}
}
}
void VG_(print_malloc_stats) ( void )
{
UInt nblocks, nbytes, ml_no;
ShadowChunk* sc;
if (VG_(clo_verbosity) == 0)
return;
vg_assert(needs_shadow_chunks());
nblocks = nbytes = 0;
for (ml_no = 0; ml_no < VG_N_MALLOCLISTS; ml_no++) {
for (sc = vg_malloclist[ml_no]; sc != NULL; sc = sc->next) {
nblocks ++;
nbytes += sc->size;
}
}
VG_(message)(Vg_UserMsg,
"malloc/free: in use at exit: %d bytes in %d blocks.",
nbytes, nblocks);
VG_(message)(Vg_UserMsg,
"malloc/free: %d allocs, %d frees, %u bytes allocated.",
vg_cmalloc_n_mallocs,
vg_cmalloc_n_frees, vg_cmalloc_bs_mallocd);
if (0)
VG_(message)(Vg_DebugMsg,
"free search: %d tries, %d frees",
vg_mlist_tries,
vg_mlist_frees );
if (VG_(clo_verbosity) > 1)
VG_(message)(Vg_UserMsg, "");
}
/*--------------------------------------------------------------------*/
/*--- end vg_clientmalloc.c ---*/
/*--------------------------------------------------------------------*/