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