asan_malloc_mac.cc revision 09672caefb5694f1981a1712fdefa44840a95e67
1//===-- asan_rtl.cc ---------------------------------------------*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This file is a part of AddressSanitizer, an address sanity checker.
11//
12// Mac-specific malloc interception.
13//===----------------------------------------------------------------------===//
14
15#ifdef __APPLE__
16
17#include <AvailabilityMacros.h>
18#include <CoreFoundation/CFBase.h>
19#include <malloc/malloc.h>
20#include <setjmp.h>
21
22#include "asan_allocator.h"
23#include "asan_interceptors.h"
24#include "asan_internal.h"
25#include "asan_stack.h"
26
27// Similar code is used in Google Perftools,
28// http://code.google.com/p/google-perftools.
29
30// ---------------------- Replacement functions ---------------- {{{1
31using namespace __asan;  // NOLINT
32
33// The free() implementation provided by OS X calls malloc_zone_from_ptr()
34// to find the owner of |ptr|. If the result is NULL, an invalid free() is
35// reported. Our implementation falls back to asan_free() in this case
36// in order to print an ASan-style report.
37extern "C"
38void free(void *ptr) {
39  malloc_zone_t *zone = malloc_zone_from_ptr(ptr);
40  if (zone) {
41#if defined(MAC_OS_X_VERSION_10_6) && \
42    MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
43    if ((zone->version >= 6) && (zone->free_definite_size)) {
44      zone->free_definite_size(zone, ptr, malloc_size(ptr));
45    } else {
46      malloc_zone_free(zone, ptr);
47    }
48#else
49    malloc_zone_free(zone, ptr);
50#endif
51  } else {
52    GET_STACK_TRACE_HERE_FOR_FREE(ptr);
53    asan_free(ptr, &stack);
54  }
55}
56
57// TODO(glider): do we need both zones?
58static malloc_zone_t *system_malloc_zone = NULL;
59static malloc_zone_t *system_purgeable_zone = NULL;
60
61// We need to provide wrappers around all the libc functions.
62namespace {
63// TODO(glider): the mz_* functions should be united with the Linux wrappers,
64// as they are basically copied from there.
65size_t mz_size(malloc_zone_t* zone, const void* ptr) {
66  // Fast path: check whether this pointer belongs to the original malloc zone.
67  // We cannot just call malloc_zone_from_ptr(), because it in turn
68  // calls our mz_size().
69  if (system_malloc_zone) {
70    if ((system_malloc_zone->size)(system_malloc_zone, ptr)) return 0;
71  }
72  return asan_mz_size(ptr);
73}
74
75void *mz_malloc(malloc_zone_t *zone, size_t size) {
76  if (!asan_inited) {
77    CHECK(system_malloc_zone);
78    return malloc_zone_malloc(system_malloc_zone, size);
79  }
80  GET_STACK_TRACE_HERE_FOR_MALLOC;
81  return asan_malloc(size, &stack);
82}
83
84void *cf_malloc(CFIndex size, CFOptionFlags hint, void *info) {
85  if (!asan_inited) {
86    CHECK(system_malloc_zone);
87    return malloc_zone_malloc(system_malloc_zone, size);
88  }
89  GET_STACK_TRACE_HERE_FOR_MALLOC;
90  return asan_malloc(size, &stack);
91}
92
93void *mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) {
94  if (!asan_inited) {
95    // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym.
96    const size_t kCallocPoolSize = 1024;
97    static uintptr_t calloc_memory_for_dlsym[kCallocPoolSize];
98    static size_t allocated;
99    size_t size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize;
100    void *mem = (void*)&calloc_memory_for_dlsym[allocated];
101    allocated += size_in_words;
102    CHECK(allocated < kCallocPoolSize);
103    return mem;
104  }
105  GET_STACK_TRACE_HERE_FOR_MALLOC;
106  return asan_calloc(nmemb, size, &stack);
107}
108
109void *mz_valloc(malloc_zone_t *zone, size_t size) {
110  if (!asan_inited) {
111    CHECK(system_malloc_zone);
112    return malloc_zone_valloc(system_malloc_zone, size);
113  }
114  GET_STACK_TRACE_HERE_FOR_MALLOC;
115  return asan_memalign(kPageSize, size, &stack);
116}
117
118void print_zone_for_ptr(void *ptr) {
119  malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr);
120  if (orig_zone) {
121    if (orig_zone->zone_name) {
122      Printf("malloc_zone_from_ptr(%p) = %p, which is %s\n",
123             ptr, orig_zone, orig_zone->zone_name);
124    } else {
125      Printf("malloc_zone_from_ptr(%p) = %p, which doesn't have a name\n",
126             ptr, orig_zone);
127    }
128  } else {
129    Printf("malloc_zone_from_ptr(%p) = NULL\n", ptr);
130  }
131}
132
133// TODO(glider): the allocation callbacks need to be refactored.
134void mz_free(malloc_zone_t *zone, void *ptr) {
135  if (!ptr) return;
136  malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr);
137  // For some reason Chromium calls mz_free() for pointers that belong to
138  // DefaultPurgeableMallocZone instead of asan_zone. We might want to
139  // fix this someday.
140  if (orig_zone == system_purgeable_zone) {
141    system_purgeable_zone->free(system_purgeable_zone, ptr);
142    return;
143  }
144  if (asan_mz_size(ptr)) {
145    GET_STACK_TRACE_HERE_FOR_FREE(ptr);
146    asan_free(ptr, &stack);
147  } else {
148    // Let us just leak this memory for now.
149    Printf("mz_free(%p) -- attempting to free unallocated memory.\n"
150           "AddressSanitizer is ignoring this error on Mac OS now.\n", ptr);
151    print_zone_for_ptr(ptr);
152    GET_STACK_TRACE_HERE_FOR_FREE(ptr);
153    stack.PrintStack();
154    return;
155  }
156}
157
158void cf_free(void *ptr, void *info) {
159  if (!ptr) return;
160  malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr);
161  // For some reason Chromium calls mz_free() for pointers that belong to
162  // DefaultPurgeableMallocZone instead of asan_zone. We might want to
163  // fix this someday.
164  if (orig_zone == system_purgeable_zone) {
165    system_purgeable_zone->free(system_purgeable_zone, ptr);
166    return;
167  }
168  if (asan_mz_size(ptr)) {
169    GET_STACK_TRACE_HERE_FOR_FREE(ptr);
170    asan_free(ptr, &stack);
171  } else {
172    // Let us just leak this memory for now.
173    Printf("cf_free(%p) -- attempting to free unallocated memory.\n"
174           "AddressSanitizer is ignoring this error on Mac OS now.\n", ptr);
175    print_zone_for_ptr(ptr);
176    GET_STACK_TRACE_HERE_FOR_FREE(ptr);
177    stack.PrintStack();
178    return;
179  }
180}
181
182void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) {
183  if (!ptr) {
184    GET_STACK_TRACE_HERE_FOR_MALLOC;
185    return asan_malloc(size, &stack);
186  } else {
187    if (asan_mz_size(ptr)) {
188      GET_STACK_TRACE_HERE_FOR_MALLOC;
189      return asan_realloc(ptr, size, &stack);
190    } else {
191      // We can't recover from reallocating an unknown address, because
192      // this would require reading at most |size| bytes from
193      // potentially unaccessible memory.
194      Printf("mz_realloc(%p) -- attempting to realloc unallocated memory.\n"
195             "This is an unrecoverable problem, exiting now.\n", ptr);
196      print_zone_for_ptr(ptr);
197      GET_STACK_TRACE_HERE_FOR_FREE(ptr);
198      stack.PrintStack();
199      ShowStatsAndAbort();
200      return NULL;  // unreachable
201    }
202  }
203}
204
205void *cf_realloc(void *ptr, CFIndex size, CFOptionFlags hint, void *info) {
206  if (!ptr) {
207    GET_STACK_TRACE_HERE_FOR_MALLOC;
208    return asan_malloc(size, &stack);
209  } else {
210    if (asan_mz_size(ptr)) {
211      GET_STACK_TRACE_HERE_FOR_MALLOC;
212      return asan_realloc(ptr, size, &stack);
213    } else {
214      // We can't recover from reallocating an unknown address, because
215      // this would require reading at most |size| bytes from
216      // potentially unaccessible memory.
217      Printf("cf_realloc(%p) -- attempting to realloc unallocated memory.\n"
218             "This is an unrecoverable problem, exiting now.\n", ptr);
219      print_zone_for_ptr(ptr);
220      GET_STACK_TRACE_HERE_FOR_FREE(ptr);
221      stack.PrintStack();
222      ShowStatsAndAbort();
223      return NULL;  // unreachable
224    }
225  }
226}
227
228void mz_destroy(malloc_zone_t* zone) {
229  // A no-op -- we will not be destroyed!
230  Printf("mz_destroy() called -- ignoring\n");
231}
232  // from AvailabilityMacros.h
233#if defined(MAC_OS_X_VERSION_10_6) && \
234    MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
235void *mz_memalign(malloc_zone_t *zone, size_t align, size_t size) {
236  if (!asan_inited) {
237    CHECK(system_malloc_zone);
238    return malloc_zone_memalign(system_malloc_zone, align, size);
239  }
240  GET_STACK_TRACE_HERE_FOR_MALLOC;
241  return asan_memalign(align, size, &stack);
242}
243
244// This function is currently unused, and we build with -Werror.
245#if 0
246void mz_free_definite_size(malloc_zone_t* zone, void *ptr, size_t size) {
247  // TODO(glider): check that |size| is valid.
248  UNIMPLEMENTED();
249}
250#endif
251#endif
252
253// malloc_introspection callbacks.  I'm not clear on what all of these do.
254kern_return_t mi_enumerator(task_t task, void *,
255                            unsigned type_mask, vm_address_t zone_address,
256                            memory_reader_t reader,
257                            vm_range_recorder_t recorder) {
258  // Should enumerate all the pointers we have.  Seems like a lot of work.
259  return KERN_FAILURE;
260}
261
262size_t mi_good_size(malloc_zone_t *zone, size_t size) {
263  // I think it's always safe to return size, but we maybe could do better.
264  return size;
265}
266
267boolean_t mi_check(malloc_zone_t *zone) {
268  UNIMPLEMENTED();
269  return true;
270}
271
272void mi_print(malloc_zone_t *zone, boolean_t verbose) {
273  UNIMPLEMENTED();
274  return;
275}
276
277void mi_log(malloc_zone_t *zone, void *address) {
278  // I don't think we support anything like this
279}
280
281void mi_force_lock(malloc_zone_t *zone) {
282  asan_mz_force_lock();
283}
284
285void mi_force_unlock(malloc_zone_t *zone) {
286  asan_mz_force_unlock();
287}
288
289// This function is currently unused, and we build with -Werror.
290#if 0
291void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) {
292  // TODO(csilvers): figure out how to fill these out
293  // TODO(glider): port this from tcmalloc when ready.
294  stats->blocks_in_use = 0;
295  stats->size_in_use = 0;
296  stats->max_size_in_use = 0;
297  stats->size_allocated = 0;
298}
299#endif
300
301boolean_t mi_zone_locked(malloc_zone_t *zone) {
302  // UNIMPLEMENTED();
303  return false;
304}
305
306}  // unnamed namespace
307
308extern bool kCFUseCollectableAllocator;  // is GC on?
309
310namespace __asan {
311void ReplaceSystemMalloc() {
312  static malloc_introspection_t asan_introspection;
313  __asan::REAL(memset)(&asan_introspection, 0, sizeof(asan_introspection));
314
315  asan_introspection.enumerator = &mi_enumerator;
316  asan_introspection.good_size = &mi_good_size;
317  asan_introspection.check = &mi_check;
318  asan_introspection.print = &mi_print;
319  asan_introspection.log = &mi_log;
320  asan_introspection.force_lock = &mi_force_lock;
321  asan_introspection.force_unlock = &mi_force_unlock;
322
323  static malloc_zone_t asan_zone;
324  __asan::REAL(memset)(&asan_zone, 0, sizeof(malloc_zone_t));
325
326  // Start with a version 4 zone which is used for OS X 10.4 and 10.5.
327  asan_zone.version = 4;
328  asan_zone.zone_name = "asan";
329  asan_zone.size = &mz_size;
330  asan_zone.malloc = &mz_malloc;
331  asan_zone.calloc = &mz_calloc;
332  asan_zone.valloc = &mz_valloc;
333  asan_zone.free = &mz_free;
334  asan_zone.realloc = &mz_realloc;
335  asan_zone.destroy = &mz_destroy;
336  asan_zone.batch_malloc = NULL;
337  asan_zone.batch_free = NULL;
338  asan_zone.introspect = &asan_introspection;
339
340  // from AvailabilityMacros.h
341#if defined(MAC_OS_X_VERSION_10_6) && \
342    MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
343  // Switch to version 6 on OSX 10.6 to support memalign.
344  asan_zone.version = 6;
345  asan_zone.free_definite_size = 0;
346  asan_zone.memalign = &mz_memalign;
347  asan_introspection.zone_locked = &mi_zone_locked;
348
349  // Request the default purgable zone to force its creation. The
350  // current default zone is registered with the purgable zone for
351  // doing tiny and small allocs.  Sadly, it assumes that the default
352  // zone is the szone implementation from OS X and will crash if it
353  // isn't.  By creating the zone now, this will be true and changing
354  // the default zone won't cause a problem.  (OS X 10.6 and higher.)
355  system_purgeable_zone = malloc_default_purgeable_zone();
356#endif
357
358  // Register the ASan zone. At this point, it will not be the
359  // default zone.
360  malloc_zone_register(&asan_zone);
361
362  // Unregister and reregister the default zone.  Unregistering swaps
363  // the specified zone with the last one registered which for the
364  // default zone makes the more recently registered zone the default
365  // zone.  The default zone is then re-registered to ensure that
366  // allocations made from it earlier will be handled correctly.
367  // Things are not guaranteed to work that way, but it's how they work now.
368  system_malloc_zone = malloc_default_zone();
369  malloc_zone_unregister(system_malloc_zone);
370  malloc_zone_register(system_malloc_zone);
371  // Make sure the default allocator was replaced.
372  CHECK(malloc_default_zone() == &asan_zone);
373
374  if (FLAG_replace_cfallocator) {
375    static CFAllocatorContext asan_context =
376        { /*version*/ 0, /*info*/ &asan_zone,
377          /*retain*/ NULL, /*release*/ NULL,
378          /*copyDescription*/NULL,
379          /*allocate*/ &cf_malloc,
380          /*reallocate*/ &cf_realloc,
381          /*deallocate*/ &cf_free,
382          /*preferredSize*/ NULL };
383    CFAllocatorRef cf_asan =
384        CFAllocatorCreate(kCFAllocatorUseContext, &asan_context);
385    CFAllocatorSetDefault(cf_asan);
386  }
387}
388}  // namespace __asan
389
390#endif  // __APPLE__
391