1 /* 2 This file is part of drd, a thread error detector. 3 4 Copyright (C) 2006-2017 Bart Van Assche <bvanassche (at) acm.org>. 5 6 This program is free software; you can redistribute it and/or 7 modify it under the terms of the GNU General Public License as 8 published by the Free Software Foundation; either version 2 of the 9 License, or (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software 18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 19 02111-1307, USA. 20 21 The GNU General Public License is contained in the file COPYING. 22 */ 23 24 25 #include "drd_malloc_wrappers.h" 26 #include "drd_thread.h" 27 #include "pub_tool_basics.h" 28 #include "pub_tool_execontext.h" 29 #include "pub_tool_hashtable.h" 30 #include "pub_tool_libcassert.h" 31 #include "pub_tool_libcbase.h" 32 #include "pub_tool_libcprint.h" 33 #include "pub_tool_mallocfree.h" 34 #include "pub_tool_options.h" 35 #include "pub_tool_replacemalloc.h" 36 #include "pub_tool_threadstate.h" 37 #include "pub_tool_tooliface.h" 38 39 40 /* Local type definitions. */ 41 42 /** 43 * Node with per-allocation information that will be stored in a hash map. 44 * As specified in <pub_tool_hashtable.h>, the first member must be a pointer 45 * and the second member must be an UWord. 46 */ 47 typedef struct _DRD_Chunk { 48 struct _DRD_Chunk* next; 49 UWord data; // pointer to actual block 50 SizeT size; // size requested 51 ExeContext* where; // where it was allocated 52 } DRD_Chunk; 53 54 55 /* Local variables. */ 56 57 static StartUsingMem s_start_using_mem_callback; 58 static StopUsingMem s_stop_using_mem_callback; 59 /* Statistics. */ 60 static SizeT s_cmalloc_n_mallocs = 0; 61 static SizeT s_cmalloc_n_frees = 0; 62 static SizeT s_cmalloc_bs_mallocd = 0; 63 /* Record malloc'd blocks. */ 64 static VgHashTable *s_malloc_list = NULL; 65 66 67 /* Function definitions. */ 68 69 /** Allocate client memory memory and update the hash map. */ 70 static void* new_block(ThreadId tid, SizeT size, SizeT align, Bool is_zeroed) 71 { 72 void* p; 73 74 p = VG_(cli_malloc)(align, size); 75 if (!p) 76 return NULL; 77 if (is_zeroed) 78 VG_(memset)(p, 0, size); 79 80 DRD_(malloclike_block)(tid, (Addr)p, size); 81 82 return p; 83 } 84 85 /** 86 * Store information about a memory block that has been allocated by 87 * malloc() or a malloc() replacement in the hash map. 88 */ 89 void DRD_(malloclike_block)(const ThreadId tid, const Addr p, const SizeT size) 90 { 91 DRD_Chunk* mc; 92 93 tl_assert(p); 94 95 if (size > 0) 96 s_start_using_mem_callback(p, size, 0/*ec_uniq*/); 97 98 s_cmalloc_n_mallocs++; 99 // Only update this stat if allocation succeeded. 100 s_cmalloc_bs_mallocd += size; 101 102 mc = VG_(malloc)("drd.malloc_wrappers.cDC.1", sizeof(DRD_Chunk)); 103 mc->data = p; 104 mc->size = size; 105 mc->where = VG_(record_ExeContext)(tid, 0); 106 VG_(HT_add_node)(s_malloc_list, mc); 107 } 108 109 static void handle_free(ThreadId tid, void* p) 110 { 111 Bool success; 112 113 tl_assert(p); 114 success = DRD_(freelike_block)(tid, (Addr)p, True); 115 tl_assert(success); 116 } 117 118 /** 119 * Remove the information that was stored by DRD_(malloclike_block)() about 120 * a memory block. 121 */ 122 Bool DRD_(freelike_block)(const ThreadId tid, const Addr p, const Bool dealloc) 123 { 124 DRD_Chunk* mc; 125 126 tl_assert(p); 127 128 s_cmalloc_n_frees++; 129 130 mc = VG_(HT_lookup)(s_malloc_list, (UWord)p); 131 if (mc) 132 { 133 tl_assert(p == mc->data); 134 if (mc->size > 0) 135 s_stop_using_mem_callback(mc->data, mc->size); 136 if (dealloc) 137 VG_(cli_free)((void*)p); 138 VG_(HT_remove)(s_malloc_list, (UWord)p); 139 VG_(free)(mc); 140 return True; 141 } 142 return False; 143 } 144 145 /** Wrapper for malloc(). */ 146 static void* drd_malloc(ThreadId tid, SizeT n) 147 { 148 return new_block(tid, n, VG_(clo_alignment), /*is_zeroed*/False); 149 } 150 151 /** Wrapper for memalign(). */ 152 static void* drd_memalign(ThreadId tid, SizeT align, SizeT n) 153 { 154 return new_block(tid, n, align, /*is_zeroed*/False); 155 } 156 157 /** Wrapper for calloc(). */ 158 static void* drd_calloc(ThreadId tid, SizeT nmemb, SizeT size1) 159 { 160 return new_block(tid, nmemb*size1, VG_(clo_alignment), 161 /*is_zeroed*/True); 162 } 163 164 /** Wrapper for free(). */ 165 static void drd_free(ThreadId tid, void* p) 166 { 167 handle_free(tid, p); 168 } 169 170 /** 171 * Wrapper for realloc(). Returns a pointer to the new block of memory, or 172 * NULL if no new block could not be allocated. Notes: 173 * - realloc(NULL, size) has the same effect as malloc(size). 174 * - realloc(p, 0) has the same effect as free(p). 175 * - success is not guaranteed even if the requested size is smaller than the 176 * allocated size. 177 */ 178 static void* drd_realloc(ThreadId tid, void* p_old, SizeT new_size) 179 { 180 DRD_Chunk* mc; 181 void* p_new; 182 SizeT old_size; 183 184 if (! p_old) 185 return drd_malloc(tid, new_size); 186 187 if (new_size == 0) 188 { 189 drd_free(tid, p_old); 190 return NULL; 191 } 192 193 s_cmalloc_n_mallocs++; 194 s_cmalloc_n_frees++; 195 s_cmalloc_bs_mallocd += new_size; 196 197 mc = VG_(HT_lookup)(s_malloc_list, (UWord)p_old); 198 if (mc == NULL) 199 { 200 tl_assert(0); 201 return NULL; 202 } 203 204 old_size = mc->size; 205 206 if (old_size == new_size) 207 { 208 /* size unchanged */ 209 mc->where = VG_(record_ExeContext)(tid, 0); 210 p_new = p_old; 211 } 212 else if (new_size < old_size) 213 { 214 /* new size is smaller but nonzero */ 215 s_stop_using_mem_callback(mc->data + new_size, old_size - new_size); 216 mc->size = new_size; 217 mc->where = VG_(record_ExeContext)(tid, 0); 218 p_new = p_old; 219 } 220 else 221 { 222 /* new size is bigger */ 223 p_new = VG_(cli_malloc)(VG_(clo_alignment), new_size); 224 225 if (p_new) 226 { 227 /* Copy from old to new. */ 228 VG_(memcpy)(p_new, p_old, mc->size); 229 230 /* Free old memory. */ 231 if (mc->size > 0) 232 s_stop_using_mem_callback(mc->data, mc->size); 233 VG_(cli_free)(p_old); 234 VG_(HT_remove)(s_malloc_list, (UWord)p_old); 235 236 /* Update state information. */ 237 mc->data = (Addr)p_new; 238 mc->size = new_size; 239 mc->where = VG_(record_ExeContext)(tid, 0); 240 VG_(HT_add_node)(s_malloc_list, mc); 241 s_start_using_mem_callback((Addr)p_new, new_size, 0/*ec_uniq*/); 242 } 243 else 244 { 245 /* Allocation failed -- leave original block untouched. */ 246 } 247 } 248 249 return p_new; 250 } 251 252 /** Wrapper for __builtin_new(). */ 253 static void* drd___builtin_new(ThreadId tid, SizeT n) 254 { 255 return new_block(tid, n, VG_(clo_alignment), /*is_zeroed*/False); 256 } 257 258 /** Wrapper for __builtin_delete(). */ 259 static void drd___builtin_delete(ThreadId tid, void* p) 260 { 261 handle_free(tid, p); 262 } 263 264 /** Wrapper for __builtin_vec_new(). */ 265 static void* drd___builtin_vec_new(ThreadId tid, SizeT n) 266 { 267 return new_block(tid, n, VG_(clo_alignment), /*is_zeroed*/False); 268 } 269 270 /** Wrapper for __builtin_vec_delete(). */ 271 static void drd___builtin_vec_delete(ThreadId tid, void* p) 272 { 273 handle_free(tid, p); 274 } 275 276 /** 277 * Wrapper for malloc_usable_size() / malloc_size(). This function takes 278 * a pointer to a block allocated by `malloc' and returns the amount of space 279 * that is available in the block. This may or may not be more than the size 280 * requested from `malloc', due to alignment or minimum size constraints. 281 */ 282 static SizeT drd_malloc_usable_size(ThreadId tid, void* p) 283 { 284 DRD_Chunk* mc; 285 286 mc = VG_(HT_lookup)(s_malloc_list, (UWord)p); 287 288 return mc ? mc->size : 0; 289 } 290 291 void DRD_(register_malloc_wrappers)(const StartUsingMem start_callback, 292 const StopUsingMem stop_callback) 293 { 294 tl_assert(s_malloc_list == 0); 295 s_malloc_list = VG_(HT_construct)("drd_malloc_list"); 296 tl_assert(start_callback); 297 tl_assert(stop_callback); 298 299 s_start_using_mem_callback = start_callback; 300 s_stop_using_mem_callback = stop_callback; 301 302 VG_(needs_malloc_replacement)(drd_malloc, 303 drd___builtin_new, 304 drd___builtin_vec_new, 305 drd_memalign, 306 drd_calloc, 307 drd_free, 308 drd___builtin_delete, 309 drd___builtin_vec_delete, 310 drd_realloc, 311 drd_malloc_usable_size, 312 0); 313 } 314 315 Bool DRD_(heap_addrinfo)(Addr const a, 316 Addr* const data, 317 SizeT* const size, 318 ExeContext** const where) 319 { 320 DRD_Chunk* mc; 321 322 tl_assert(data); 323 tl_assert(size); 324 tl_assert(where); 325 326 VG_(HT_ResetIter)(s_malloc_list); 327 while ((mc = VG_(HT_Next)(s_malloc_list))) 328 { 329 if (mc->data <= a && a < mc->data + mc->size) 330 { 331 *data = mc->data; 332 *size = mc->size; 333 *where = mc->where; 334 return True; 335 } 336 } 337 return False; 338 } 339 340 /*------------------------------------------------------------*/ 341 /*--- Statistics printing ---*/ 342 /*------------------------------------------------------------*/ 343 344 void DRD_(print_malloc_stats)(void) 345 { 346 DRD_Chunk* mc; 347 SizeT nblocks = 0; 348 SizeT nbytes = 0; 349 350 if (VG_(clo_verbosity) == 0) 351 return; 352 if (VG_(clo_xml)) 353 return; 354 355 /* Count memory still in use. */ 356 VG_(HT_ResetIter)(s_malloc_list); 357 while ((mc = VG_(HT_Next)(s_malloc_list))) 358 { 359 nblocks++; 360 nbytes += mc->size; 361 } 362 363 VG_(message)(Vg_DebugMsg, 364 "malloc/free: in use at exit: %lu bytes in %lu blocks.\n", 365 nbytes, nblocks); 366 VG_(message)(Vg_DebugMsg, 367 "malloc/free: %lu allocs, %lu frees, %lu bytes allocated.\n", 368 s_cmalloc_n_mallocs, 369 s_cmalloc_n_frees, s_cmalloc_bs_mallocd); 370 if (VG_(clo_verbosity) > 1) 371 VG_(message)(Vg_DebugMsg, " \n"); 372 } 373 374 /*--------------------------------------------------------------------*/ 375 /*--- end ---*/ 376 /*--------------------------------------------------------------------*/ 377