1/*
2  This file is part of drd, a thread error detector.
3
4  Copyright (C) 2006-2013 Bart Van Assche <bvanassche@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 */
47typedef 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
57static StartUsingMem s_start_using_mem_callback;
58static StopUsingMem  s_stop_using_mem_callback;
59/* Statistics. */
60static SizeT s_cmalloc_n_mallocs  = 0;
61static SizeT s_cmalloc_n_frees    = 0;
62static SizeT s_cmalloc_bs_mallocd = 0;
63/* Record malloc'd blocks. */
64static VgHashTable s_malloc_list = NULL;
65
66
67/* Function definitions. */
68
69/** Allocate client memory memory and update the hash map. */
70static 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 */
89void 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
109static 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 */
122Bool 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(). */
146static 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(). */
152static 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(). */
158static 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(). */
165static 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*/
178static 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(). */
253static 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(). */
259static void drd___builtin_delete(ThreadId tid, void* p)
260{
261   handle_free(tid, p);
262}
263
264/** Wrapper for __builtin_vec_new(). */
265static 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(). */
271static 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 */
282static 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
291void 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(s_malloc_list);
297   tl_assert(start_callback);
298   tl_assert(stop_callback);
299
300   s_start_using_mem_callback = start_callback;
301   s_stop_using_mem_callback  = stop_callback;
302
303   VG_(needs_malloc_replacement)(drd_malloc,
304                                 drd___builtin_new,
305                                 drd___builtin_vec_new,
306                                 drd_memalign,
307                                 drd_calloc,
308                                 drd_free,
309                                 drd___builtin_delete,
310                                 drd___builtin_vec_delete,
311                                 drd_realloc,
312                                 drd_malloc_usable_size,
313                                 0);
314}
315
316Bool DRD_(heap_addrinfo)(Addr const a,
317                         Addr* const data,
318                         SizeT* const size,
319                         ExeContext** const where)
320{
321   DRD_Chunk* mc;
322
323   tl_assert(data);
324   tl_assert(size);
325   tl_assert(where);
326
327   VG_(HT_ResetIter)(s_malloc_list);
328   while ((mc = VG_(HT_Next)(s_malloc_list)))
329   {
330      if (mc->data <= a && a < mc->data + mc->size)
331      {
332         *data  = mc->data;
333         *size  = mc->size;
334         *where = mc->where;
335         return True;
336      }
337   }
338   return False;
339}
340
341/*------------------------------------------------------------*/
342/*--- Statistics printing                                  ---*/
343/*------------------------------------------------------------*/
344
345void DRD_(print_malloc_stats)(void)
346{
347   DRD_Chunk* mc;
348   SizeT     nblocks = 0;
349   SizeT     nbytes  = 0;
350
351   if (VG_(clo_verbosity) == 0)
352      return;
353   if (VG_(clo_xml))
354      return;
355
356   /* Count memory still in use. */
357   VG_(HT_ResetIter)(s_malloc_list);
358   while ((mc = VG_(HT_Next)(s_malloc_list)))
359   {
360      nblocks++;
361      nbytes += mc->size;
362   }
363
364   VG_(message)(Vg_DebugMsg,
365                "malloc/free: in use at exit: %lu bytes in %lu blocks.\n",
366                nbytes, nblocks);
367   VG_(message)(Vg_DebugMsg,
368                "malloc/free: %lu allocs, %lu frees, %lu bytes allocated.\n",
369                s_cmalloc_n_mallocs,
370                s_cmalloc_n_frees, s_cmalloc_bs_mallocd);
371   if (VG_(clo_verbosity) > 1)
372      VG_(message)(Vg_DebugMsg, " \n");
373}
374
375/*--------------------------------------------------------------------*/
376/*--- end                                                          ---*/
377/*--------------------------------------------------------------------*/
378