/* This file is part of drd, a data race detector. Copyright (C) 2006-2008 Bart Van Assche bart.vanassche@gmail.com 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 "drd_malloc_wrappers.h" #include "drd_thread.h" #include "pub_tool_basics.h" #include "pub_tool_execontext.h" #include "pub_tool_hashtable.h" #include "pub_tool_libcassert.h" #include "pub_tool_libcbase.h" #include "pub_tool_libcprint.h" #include "pub_tool_mallocfree.h" #include "pub_tool_options.h" #include "pub_tool_replacemalloc.h" #include "pub_tool_threadstate.h" #include "pub_tool_tooliface.h" /*------------------------------------------------------------*/ /*--- Definitions ---*/ /*------------------------------------------------------------*/ typedef struct _DRD_Chunk { struct _DRD_Chunk* next; Addr data; // ptr to actual block SizeT size : (sizeof(UWord)*8)-2; //size requested; 30 or 62 bits ExeContext* where; // where it was allocated } DRD_Chunk; static StartUsingMem s_start_using_mem_callback; static StopUsingMem s_stop_using_mem_callback; /* Stats ... */ static SizeT cmalloc_n_mallocs = 0; static SizeT cmalloc_n_frees = 0; static SizeT cmalloc_bs_mallocd = 0; /*------------------------------------------------------------*/ /*--- Tracking malloc'd and free'd blocks ---*/ /*------------------------------------------------------------*/ /* Record malloc'd blocks. */ static VgHashTable drd_malloc_list = NULL; /* Allocate its shadow chunk, put it on the appropriate list. */ static DRD_Chunk* create_DRD_Chunk(ThreadId tid, Addr p, SizeT size) { DRD_Chunk* mc = VG_(malloc)(sizeof(DRD_Chunk)); mc->data = p; mc->size = size; mc->where = VG_(record_ExeContext)(tid, 0); return mc; } /*------------------------------------------------------------*/ /*--- client_malloc(), etc ---*/ /*------------------------------------------------------------*/ /* Allocate memory and note change in memory available */ static __inline__ void* drd_new_block(ThreadId tid, SizeT size, SizeT align, Bool is_zeroed) { Addr p; cmalloc_n_mallocs ++; // Allocate and zero p = (Addr)VG_(cli_malloc)(align, size); if (!p) { return NULL; } if (is_zeroed) VG_(memset)((void*)p, 0, size); s_start_using_mem_callback(p, p + size); // Only update this stat if allocation succeeded. cmalloc_bs_mallocd += size; VG_(HT_add_node)(drd_malloc_list, create_DRD_Chunk(tid, p, size)); return (void*)p; } static void* drd_malloc(ThreadId tid, SizeT n) { return drd_new_block(tid, n, VG_(clo_alignment), /*is_zeroed*/False); } static void* drd_memalign(ThreadId tid, SizeT align, SizeT n) { return drd_new_block(tid, n, align, /*is_zeroed*/False); } static void* drd_calloc(ThreadId tid, SizeT nmemb, SizeT size1) { return drd_new_block(tid, nmemb*size1, VG_(clo_alignment), /*is_zeroed*/True); } static __inline__ void drd_handle_free(ThreadId tid, Addr p) { DRD_Chunk* mc; cmalloc_n_frees++; mc = VG_(HT_remove)(drd_malloc_list, (UWord)p); if (mc == NULL) { tl_assert(0); } else { tl_assert(p == mc->data); if (mc->size > 0) s_stop_using_mem_callback(mc->data, mc->size); VG_(cli_free)((void*)p); VG_(free)(mc); } } static void drd_free(ThreadId tid, void* p) { drd_handle_free(tid, (Addr)p); } static void* drd_realloc(ThreadId tid, void* p_old, SizeT new_size) { DRD_Chunk* mc; void* p_new; SizeT old_size; cmalloc_n_frees ++; cmalloc_n_mallocs ++; cmalloc_bs_mallocd += new_size; /* Remove the old block */ mc = VG_(HT_remove)(drd_malloc_list, (UWord)p_old); if (mc == NULL) { tl_assert(0); return NULL; } old_size = mc->size; if (old_size == new_size) { /* size unchanged */ mc->where = VG_(record_ExeContext)(tid, 0); p_new = p_old; } else if (old_size > new_size) { /* new size is smaller */ s_stop_using_mem_callback(mc->data + new_size, old_size); mc->size = new_size; mc->where = VG_(record_ExeContext)(tid, 0); p_new = p_old; } else { /* new size is bigger */ /* Get new memory */ const Addr a_new = (Addr)VG_(cli_malloc)(VG_(clo_alignment), new_size); if (a_new) { /* Copy from old to new */ VG_(memcpy)((void*)a_new, p_old, mc->size); /* Free old memory */ s_stop_using_mem_callback(mc->data, mc->size); VG_(free)(mc); // Allocate a new chunk. mc = create_DRD_Chunk(tid, a_new, new_size); s_start_using_mem_callback(a_new, a_new + new_size); } else { /* Allocation failed -- leave original block untouched. */ } p_new = (void*)a_new; } // Now insert the new mc (with a possibly new 'data' field) into // malloc_list. If this realloc() did not increase the memory size, we // will have removed and then re-added mc unnecessarily. But that's ok // because shrinking a block with realloc() is (presumably) much rarer // than growing it, and this way simplifies the growing case. VG_(HT_add_node)(drd_malloc_list, mc); return p_new; } static void* drd___builtin_new(ThreadId tid, SizeT n) { void* const result = drd_new_block(tid, n, VG_(clo_alignment), /*is_zeroed*/False); //VG_(message)(Vg_DebugMsg, "__builtin_new(%d, %d) = %p", tid, n, result); return result; } static void drd___builtin_delete(ThreadId tid, void* p) { //VG_(message)(Vg_DebugMsg, "__builtin_delete(%d, %p)", tid, p); drd_handle_free(tid, (Addr)p); } static void* drd___builtin_vec_new(ThreadId tid, SizeT n) { return drd_new_block(tid, n, VG_(clo_alignment), /*is_zeroed*/False); } static void drd___builtin_vec_delete(ThreadId tid, void* p) { drd_handle_free(tid, (Addr)p); } void drd_register_malloc_wrappers(const StartUsingMem start_using_mem_callback, const StopUsingMem stop_using_mem_callback) { tl_assert(drd_malloc_list == 0); drd_malloc_list = VG_(HT_construct)("drd_malloc_list"); // a big prime tl_assert(drd_malloc_list != 0); tl_assert(stop_using_mem_callback); s_start_using_mem_callback = start_using_mem_callback; s_stop_using_mem_callback = stop_using_mem_callback; VG_(needs_malloc_replacement)(drd_malloc, drd___builtin_new, drd___builtin_vec_new, drd_memalign, drd_calloc, drd_free, drd___builtin_delete, drd___builtin_vec_delete, drd_realloc, 0); } Bool drd_heap_addrinfo(Addr const a, Addr* const data, SizeT* const size, ExeContext** const where) { DRD_Chunk* mc; tl_assert(data); tl_assert(size); tl_assert(where); VG_(HT_ResetIter)(drd_malloc_list); while ((mc = VG_(HT_Next)(drd_malloc_list))) { if (mc->data <= a && a < mc->data + mc->size) { *data = mc->data; *size = mc->size; *where = mc->where; return True; } } return False; } /*------------------------------------------------------------*/ /*--- Statistics printing ---*/ /*------------------------------------------------------------*/ void drd_print_malloc_stats(void) { DRD_Chunk* mc; SizeT nblocks = 0; SizeT nbytes = 0; if (VG_(clo_verbosity) == 0) return; if (VG_(clo_xml)) return; /* Count memory still in use. */ VG_(HT_ResetIter)(drd_malloc_list); while ((mc = VG_(HT_Next)(drd_malloc_list))) { nblocks++; nbytes += mc->size; } VG_(message)(Vg_DebugMsg, "malloc/free: in use at exit: %lu bytes in %lu blocks.", nbytes, nblocks); VG_(message)(Vg_DebugMsg, "malloc/free: %lu allocs, %lu frees, %lu bytes allocated.", cmalloc_n_mallocs, cmalloc_n_frees, cmalloc_bs_mallocd); if (VG_(clo_verbosity) > 1) VG_(message)(Vg_DebugMsg, " "); } /*--------------------------------------------------------------------*/ /*--- end ---*/ /*--------------------------------------------------------------------*/