1 //===-- heap_find.c ---------------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file compiles into a dylib and can be used on darwin to find data that
10 // is contained in active malloc blocks. To use this make the project, then
11 // load the shared library in a debug session while you are stopped:
12 //
13 // (lldb) process load /path/to/libheap.dylib
14 //
15 // Now you can use the "find_pointer_in_heap" and "find_cstring_in_heap"
16 // functions in the expression parser.
17 //
18 // This will grep everything in all active allocation blocks and print and
19 // malloc blocks that contain the pointer 0x112233000000:
20 //
21 // (lldb) expression find_pointer_in_heap (0x112233000000)
22 //
23 // This will grep everything in all active allocation blocks and print and
24 // malloc blocks that contain the C string "hello" (as a substring, no
25 // NULL termination included):
26 //
27 // (lldb) expression find_cstring_in_heap ("hello")
28 //
29 // The results will be printed to the STDOUT of the inferior program. The
30 // return value of the "find_pointer_in_heap" function is the number of
31 // pointer references that were found. A quick example shows
32 //
33 // (lldb) expr find_pointer_in_heap(0x0000000104000410)
34 // (uint32_t) $5 = 0x00000002
35 // 0x104000740: 0x0000000104000410 found in malloc block 0x104000730 + 16
36 // (malloc_size = 48)
37 // 0x100820060: 0x0000000104000410 found in malloc block 0x100820000 + 96
38 // (malloc_size = 4096)
39 //
40 // From the above output we see that 0x104000410 was found in the malloc block
41 // at 0x104000730 and 0x100820000. If we want to see what these blocks are, we
42 // can display the memory for this block using the "address" ("A" for short)
43 // format. The address format shows pointers, and if those pointers point to
44 // objects that have symbols or know data contents, it will display information
45 // about the pointers:
46 //
47 // (lldb) memory read --format address --count 1 0x104000730
48 // 0x104000730: 0x0000000100002460 (void *)0x0000000100002488: MyString
49 //
50 // We can see that the first block is a "MyString" object that contains our
51 // pointer value at offset 16.
52 //
53 // Looking at the next pointers, are a bit more tricky:
54 // (lldb) memory read -fA 0x100820000 -c1
55 // 0x100820000: 0x4f545541a1a1a1a1
56 // (lldb) memory read 0x100820000
57 // 0x100820000: a1 a1 a1 a1 41 55 54 4f 52 45 4c 45 41 53 45 21 ....AUTORELEASE!
58 // 0x100820010: 78 00 82 00 01 00 00 00 60 f9 e8 75 ff 7f 00 00 x.......`..u....
59 //
60 // This is an objective C auto release pool object that contains our pointer.
61 // C++ classes will show up if they are virtual as something like:
62 // (lldb) memory read --format address --count 1 0x104008000
63 // 0x104008000: 0x109008000 vtable for lldb_private::Process
64 //
65 // This is a clue that the 0x104008000 is a "lldb_private::Process *".
66 //===----------------------------------------------------------------------===//
67 // C includes
68 #include <assert.h>
69 #include <ctype.h>
70 #include <dlfcn.h>
71 #include <mach/mach.h>
72 #include <mach/mach_vm.h>
73 #include <malloc/malloc.h>
74 #include <objc/objc-runtime.h>
75 #include <stdio.h>
76 #include <stdlib.h>
77 #include <unistd.h>
78 
79 // C++ includes
80 #include <vector>
81 
82 // Redefine private types from "/usr/local/include/stack_logging.h"
83 typedef struct {
84   uint32_t type_flags;
85   uint64_t stack_identifier;
86   uint64_t argument;
87   mach_vm_address_t address;
88 } mach_stack_logging_record_t;
89 
90 // Redefine private defines from "/usr/local/include/stack_logging.h"
91 #define stack_logging_type_free 0
92 #define stack_logging_type_generic 1
93 #define stack_logging_type_alloc 2
94 #define stack_logging_type_dealloc 4
95 // This bit is made up by this code
96 #define stack_logging_type_vm_region 8
97 
98 // Redefine private function prototypes from
99 // "/usr/local/include/stack_logging.h"
100 extern "C" kern_return_t __mach_stack_logging_set_file_path(task_t task,
101                                                             char *file_path);
102 
103 extern "C" kern_return_t
104 __mach_stack_logging_get_frames(task_t task, mach_vm_address_t address,
105                                 mach_vm_address_t *stack_frames_buffer,
106                                 uint32_t max_stack_frames, uint32_t *count);
107 
108 extern "C" kern_return_t __mach_stack_logging_enumerate_records(
109     task_t task, mach_vm_address_t address,
110     void enumerator(mach_stack_logging_record_t, void *), void *context);
111 
112 extern "C" kern_return_t __mach_stack_logging_frames_for_uniqued_stack(
113     task_t task, uint64_t stack_identifier,
114     mach_vm_address_t *stack_frames_buffer, uint32_t max_stack_frames,
115     uint32_t *count);
116 
117 extern "C" void *gdb_class_getClass(void *objc_class);
118 
119 static void range_info_callback(task_t task, void *baton, unsigned type,
120                                 uint64_t ptr_addr, uint64_t ptr_size);
121 
122 // Redefine private global variables prototypes from
123 // "/usr/local/include/stack_logging.h"
124 
125 extern "C" int stack_logging_enable_logging;
126 
127 // Local defines
128 #define MAX_FRAMES 1024
129 
130 // Local Typedefs and Types
131 typedef void range_callback_t(task_t task, void *baton, unsigned type,
132                               uint64_t ptr_addr, uint64_t ptr_size);
133 typedef void zone_callback_t(void *info, const malloc_zone_t *zone);
134 typedef int (*comare_function_t)(const void *, const void *);
135 struct range_callback_info_t {
136   zone_callback_t *zone_callback;
137   range_callback_t *range_callback;
138   void *baton;
139   int check_vm_regions;
140 };
141 
142 enum data_type_t {
143   eDataTypeAddress,
144   eDataTypeContainsData,
145   eDataTypeObjC,
146   eDataTypeHeapInfo
147 };
148 
149 struct aligned_data_t {
150   const uint8_t *buffer;
151   uint32_t size;
152   uint32_t align;
153 };
154 
155 struct objc_data_t {
156   void *match_isa; // Set to NULL for all objective C objects
157   bool match_superclasses;
158 };
159 
160 struct range_contains_data_callback_info_t {
161   data_type_t type;
162   const void *lookup_addr;
163   union {
164     uintptr_t addr;
165     aligned_data_t data;
166     objc_data_t objc;
167   };
168   uint32_t match_count;
169   bool done;
170   bool unique;
171 };
172 
173 struct malloc_match {
174   void *addr;
175   intptr_t size;
176   intptr_t offset;
177   uintptr_t type;
178 };
179 
180 struct malloc_stack_entry {
181   const void *address;
182   uint64_t argument;
183   uint32_t type_flags;
184   uint32_t num_frames;
185   mach_vm_address_t frames[MAX_FRAMES];
186 };
187 
188 struct malloc_block_contents {
189   union {
190     Class isa;
191     void *pointers[2];
192   };
193 };
194 
compare_void_ptr(const void * a,const void * b)195 static int compare_void_ptr(const void *a, const void *b) {
196   Class a_ptr = *(Class *)a;
197   Class b_ptr = *(Class *)b;
198   if (a_ptr < b_ptr)
199     return -1;
200   if (a_ptr > b_ptr)
201     return +1;
202   return 0;
203 }
204 
205 class MatchResults {
206   enum { k_max_entries = 8 * 1024 };
207 
208 public:
MatchResults()209   MatchResults() : m_size(0) {}
210 
clear()211   void clear() {
212     m_size = 0;
213     bzero(&m_entries, sizeof(m_entries));
214   }
215 
empty() const216   bool empty() const { return m_size == 0; }
217 
push_back(const malloc_match & m,bool unique=false)218   void push_back(const malloc_match &m, bool unique = false) {
219     if (unique) {
220       // Don't add the entry if there is already a match for this address
221       for (uint32_t i = 0; i < m_size; ++i) {
222         if (((uint8_t *)m_entries[i].addr + m_entries[i].offset) ==
223             ((uint8_t *)m.addr + m.offset))
224           return; // Duplicate entry
225       }
226     }
227     if (m_size < k_max_entries - 1) {
228       m_entries[m_size] = m;
229       m_size++;
230     }
231   }
232 
data()233   malloc_match *data() {
234     // If empty, return NULL
235     if (empty())
236       return NULL;
237     // In not empty, terminate and return the result
238     malloc_match terminator_entry = {NULL, 0, 0, 0};
239     // We always leave room for an empty entry at the end
240     m_entries[m_size] = terminator_entry;
241     return m_entries;
242   }
243 
244 protected:
245   malloc_match m_entries[k_max_entries];
246   uint32_t m_size;
247 };
248 
249 class MallocStackLoggingEntries {
250   enum { k_max_entries = 128 };
251 
252 public:
MallocStackLoggingEntries()253   MallocStackLoggingEntries() : m_size(0) {}
254 
clear()255   void clear() { m_size = 0; }
256 
empty() const257   bool empty() const { return m_size == 0; }
258 
next()259   malloc_stack_entry *next() {
260     if (m_size < k_max_entries - 1) {
261       malloc_stack_entry *result = m_entries + m_size;
262       ++m_size;
263       return result;
264     }
265     return NULL; // Out of entries...
266   }
267 
data()268   malloc_stack_entry *data() {
269     // If empty, return NULL
270     if (empty())
271       return NULL;
272     // In not empty, terminate and return the result
273     m_entries[m_size].address = NULL;
274     m_entries[m_size].argument = 0;
275     m_entries[m_size].type_flags = 0;
276     m_entries[m_size].num_frames = 0;
277     return m_entries;
278   }
279 
280 protected:
281   malloc_stack_entry m_entries[k_max_entries];
282   uint32_t m_size;
283 };
284 
285 // A safe way to allocate memory and keep it from interfering with the
286 // malloc enumerators.
safe_malloc(size_t n_bytes)287 void *safe_malloc(size_t n_bytes) {
288   if (n_bytes > 0) {
289     const int k_page_size = getpagesize();
290     const mach_vm_size_t vm_size =
291         ((n_bytes + k_page_size - 1) / k_page_size) * k_page_size;
292     vm_address_t address = 0;
293     kern_return_t kerr = vm_allocate(mach_task_self(), &address, vm_size, true);
294     if (kerr == KERN_SUCCESS)
295       return (void *)address;
296   }
297   return NULL;
298 }
299 
300 // ObjCClasses
301 class ObjCClasses {
302 public:
ObjCClasses()303   ObjCClasses() : m_objc_class_ptrs(NULL), m_size(0) {}
304 
Update()305   bool Update() {
306     // TODO: find out if class list has changed and update if needed
307     if (m_objc_class_ptrs == NULL) {
308       m_size = objc_getClassList(NULL, 0);
309       if (m_size > 0) {
310         // Allocate the class pointers
311         m_objc_class_ptrs = (Class *)safe_malloc(m_size * sizeof(Class));
312         m_size = objc_getClassList(m_objc_class_ptrs, m_size);
313         // Sort Class pointers for quick lookup
314         ::qsort(m_objc_class_ptrs, m_size, sizeof(Class), compare_void_ptr);
315       } else
316         return false;
317     }
318     return true;
319   }
320 
FindClassIndex(Class isa)321   uint32_t FindClassIndex(Class isa) {
322     Class *matching_class = (Class *)bsearch(&isa, m_objc_class_ptrs, m_size,
323                                              sizeof(Class), compare_void_ptr);
324     if (matching_class) {
325       uint32_t idx = matching_class - m_objc_class_ptrs;
326       return idx;
327     }
328     return UINT32_MAX;
329   }
330 
GetClassAtIndex(uint32_t idx) const331   Class GetClassAtIndex(uint32_t idx) const {
332     if (idx < m_size)
333       return m_objc_class_ptrs[idx];
334     return NULL;
335   }
GetSize() const336   uint32_t GetSize() const { return m_size; }
337 
338 private:
339   Class *m_objc_class_ptrs;
340   uint32_t m_size;
341 };
342 
343 // Local global variables
344 MatchResults g_matches;
345 MallocStackLoggingEntries g_malloc_stack_history;
346 ObjCClasses g_objc_classes;
347 
348 // ObjCClassInfo
349 
350 enum HeapInfoSortType { eSortTypeNone, eSortTypeBytes, eSortTypeCount };
351 
352 class ObjCClassInfo {
353 public:
ObjCClassInfo()354   ObjCClassInfo() : m_entries(NULL), m_size(0), m_sort_type(eSortTypeNone) {}
355 
Update(const ObjCClasses & objc_classes)356   void Update(const ObjCClasses &objc_classes) {
357     m_size = objc_classes.GetSize();
358     m_entries = (Entry *)safe_malloc(m_size * sizeof(Entry));
359     m_sort_type = eSortTypeNone;
360     Reset();
361   }
362 
AddInstance(uint32_t idx,uint64_t ptr_size)363   bool AddInstance(uint32_t idx, uint64_t ptr_size) {
364     if (m_size == 0)
365       Update(g_objc_classes);
366     // Update the totals for the classes
367     if (idx < m_size) {
368       m_entries[idx].bytes += ptr_size;
369       ++m_entries[idx].count;
370       return true;
371     }
372     return false;
373   }
374 
Reset()375   void Reset() {
376     m_sort_type = eSortTypeNone;
377     for (uint32_t i = 0; i < m_size; ++i) {
378       // In case we sort the entries after gathering the data, we will
379       // want to know the index into the m_objc_class_ptrs[] array.
380       m_entries[i].idx = i;
381       m_entries[i].bytes = 0;
382       m_entries[i].count = 0;
383     }
384   }
SortByTotalBytes(const ObjCClasses & objc_classes,bool print)385   void SortByTotalBytes(const ObjCClasses &objc_classes, bool print) {
386     if (m_sort_type != eSortTypeBytes && m_size > 0) {
387       ::qsort(m_entries, m_size, sizeof(Entry),
388               (comare_function_t)compare_bytes);
389       m_sort_type = eSortTypeBytes;
390     }
391     if (print && m_size > 0) {
392       puts("Objective-C objects by total bytes:");
393       puts("Total Bytes Class Name");
394       puts("----------- "
395            "-----------------------------------------------------------------");
396       for (uint32_t i = 0; i < m_size && m_entries[i].bytes > 0; ++i) {
397         printf("%11llu %s\n", m_entries[i].bytes,
398                class_getName(objc_classes.GetClassAtIndex(m_entries[i].idx)));
399       }
400     }
401   }
SortByTotalCount(const ObjCClasses & objc_classes,bool print)402   void SortByTotalCount(const ObjCClasses &objc_classes, bool print) {
403     if (m_sort_type != eSortTypeCount && m_size > 0) {
404       ::qsort(m_entries, m_size, sizeof(Entry),
405               (comare_function_t)compare_count);
406       m_sort_type = eSortTypeCount;
407     }
408     if (print && m_size > 0) {
409       puts("Objective-C objects by total count:");
410       puts("Count    Class Name");
411       puts("-------- "
412            "-----------------------------------------------------------------");
413       for (uint32_t i = 0; i < m_size && m_entries[i].count > 0; ++i) {
414         printf("%8u %s\n", m_entries[i].count,
415                class_getName(objc_classes.GetClassAtIndex(m_entries[i].idx)));
416       }
417     }
418   }
419 
420 private:
421   struct Entry {
422     uint32_t idx;   // Index into the m_objc_class_ptrs[] array
423     uint32_t count; // Number of object instances that were found
424     uint64_t bytes; // Total number of bytes for each objc class
425   };
426 
compare_bytes(const Entry * a,const Entry * b)427   static int compare_bytes(const Entry *a, const Entry *b) {
428     // Reverse the comparison to most bytes entries end up at top of list
429     if (a->bytes > b->bytes)
430       return -1;
431     if (a->bytes < b->bytes)
432       return +1;
433     return 0;
434   }
435 
compare_count(const Entry * a,const Entry * b)436   static int compare_count(const Entry *a, const Entry *b) {
437     // Reverse the comparison to most count entries end up at top of list
438     if (a->count > b->count)
439       return -1;
440     if (a->count < b->count)
441       return +1;
442     return 0;
443   }
444 
445   Entry *m_entries;
446   uint32_t m_size;
447   HeapInfoSortType m_sort_type;
448 };
449 
450 ObjCClassInfo g_objc_class_snapshot;
451 
452 // task_peek
453 //
454 // Reads memory from this tasks address space. This callback is needed
455 // by the code that iterates through all of the malloc blocks to read
456 // the memory in this process.
task_peek(task_t task,vm_address_t remote_address,vm_size_t size,void ** local_memory)457 static kern_return_t task_peek(task_t task, vm_address_t remote_address,
458                                vm_size_t size, void **local_memory) {
459   *local_memory = (void *)remote_address;
460   return KERN_SUCCESS;
461 }
462 
foreach_zone_in_this_process(range_callback_info_t * info)463 static const void foreach_zone_in_this_process(range_callback_info_t *info) {
464   if (info == NULL || info->zone_callback == NULL)
465     return;
466 
467   vm_address_t *zones = NULL;
468   unsigned int num_zones = 0;
469 
470   kern_return_t err = malloc_get_all_zones(0, task_peek, &zones, &num_zones);
471   if (KERN_SUCCESS == err) {
472     for (unsigned int i = 0; i < num_zones; ++i) {
473       info->zone_callback(info, (const malloc_zone_t *)zones[i]);
474     }
475   }
476 
477   if (info->check_vm_regions) {
478 #if defined(VM_REGION_SUBMAP_SHORT_INFO_COUNT_64)
479     typedef vm_region_submap_short_info_data_64_t RegionInfo;
480     enum { kRegionInfoSize = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 };
481 #else
482     typedef vm_region_submap_info_data_64_t RegionInfo;
483     enum { kRegionInfoSize = VM_REGION_SUBMAP_INFO_COUNT_64 };
484 #endif
485     task_t task = mach_task_self();
486     mach_vm_address_t vm_region_base_addr;
487     mach_vm_size_t vm_region_size;
488     natural_t vm_region_depth;
489     RegionInfo vm_region_info;
490 
491     ((range_contains_data_callback_info_t *)info->baton)->unique = true;
492 
493     for (vm_region_base_addr = 0, vm_region_size = 1; vm_region_size != 0;
494          vm_region_base_addr += vm_region_size) {
495       mach_msg_type_number_t vm_region_info_size = kRegionInfoSize;
496       const kern_return_t err = mach_vm_region_recurse(
497           task, &vm_region_base_addr, &vm_region_size, &vm_region_depth,
498           (vm_region_recurse_info_t)&vm_region_info, &vm_region_info_size);
499       if (err)
500         break;
501       // Check all read + write regions. This will cover the thread stacks
502       // and any regions of memory that aren't covered by the heap
503       if (vm_region_info.protection & VM_PROT_WRITE &&
504           vm_region_info.protection & VM_PROT_READ) {
505         // printf ("checking vm_region: [0x%16.16llx - 0x%16.16llx)\n",
506         // (uint64_t)vm_region_base_addr, (uint64_t)vm_region_base_addr +
507         // vm_region_size);
508         range_info_callback(task, info->baton, stack_logging_type_vm_region,
509                             vm_region_base_addr, vm_region_size);
510       }
511     }
512   }
513 }
514 
515 // dump_malloc_block_callback
516 //
517 // A simple callback that will dump each malloc block and all available
518 // info from the enumeration callback perspective.
dump_malloc_block_callback(task_t task,void * baton,unsigned type,uint64_t ptr_addr,uint64_t ptr_size)519 static void dump_malloc_block_callback(task_t task, void *baton, unsigned type,
520                                        uint64_t ptr_addr, uint64_t ptr_size) {
521   printf("task = 0x%4.4x: baton = %p, type = %u, ptr_addr = 0x%llx + 0x%llu\n",
522          task, baton, type, ptr_addr, ptr_size);
523 }
524 
ranges_callback(task_t task,void * baton,unsigned type,vm_range_t * ptrs,unsigned count)525 static void ranges_callback(task_t task, void *baton, unsigned type,
526                             vm_range_t *ptrs, unsigned count) {
527   range_callback_info_t *info = (range_callback_info_t *)baton;
528   while (count--) {
529     info->range_callback(task, info->baton, type, ptrs->address, ptrs->size);
530     ptrs++;
531   }
532 }
533 
enumerate_range_in_zone(void * baton,const malloc_zone_t * zone)534 static void enumerate_range_in_zone(void *baton, const malloc_zone_t *zone) {
535   range_callback_info_t *info = (range_callback_info_t *)baton;
536 
537   if (zone && zone->introspect)
538     zone->introspect->enumerator(
539         mach_task_self(), info, MALLOC_PTR_IN_USE_RANGE_TYPE,
540         (vm_address_t)zone, task_peek, ranges_callback);
541 }
542 
range_info_callback(task_t task,void * baton,unsigned type,uint64_t ptr_addr,uint64_t ptr_size)543 static void range_info_callback(task_t task, void *baton, unsigned type,
544                                 uint64_t ptr_addr, uint64_t ptr_size) {
545   const uint64_t end_addr = ptr_addr + ptr_size;
546 
547   range_contains_data_callback_info_t *info =
548       (range_contains_data_callback_info_t *)baton;
549   switch (info->type) {
550   case eDataTypeAddress:
551     // Check if the current malloc block contains an address specified by
552     // "info->addr"
553     if (ptr_addr <= info->addr && info->addr < end_addr) {
554       ++info->match_count;
555       malloc_match match = {(void *)ptr_addr, ptr_size, info->addr - ptr_addr,
556                             type};
557       g_matches.push_back(match, info->unique);
558     }
559     break;
560 
561   case eDataTypeContainsData:
562     // Check if the current malloc block contains data specified in "info->data"
563     {
564       const uint32_t size = info->data.size;
565       if (size < ptr_size) // Make sure this block can contain this data
566       {
567         uint8_t *ptr_data = NULL;
568         if (task_peek(task, ptr_addr, ptr_size, (void **)&ptr_data) ==
569             KERN_SUCCESS) {
570           const void *buffer = info->data.buffer;
571           assert(ptr_data);
572           const uint32_t align = info->data.align;
573           for (uint64_t addr = ptr_addr;
574                addr < end_addr && ((end_addr - addr) >= size);
575                addr += align, ptr_data += align) {
576             if (memcmp(buffer, ptr_data, size) == 0) {
577               ++info->match_count;
578               malloc_match match = {(void *)ptr_addr, ptr_size, addr - ptr_addr,
579                                     type};
580               g_matches.push_back(match, info->unique);
581             }
582           }
583         } else {
584           printf("0x%llx: error: couldn't read %llu bytes\n", ptr_addr,
585                  ptr_size);
586         }
587       }
588     }
589     break;
590 
591   case eDataTypeObjC:
592     // Check if the current malloc block contains an objective C object
593     // of any sort where the first pointer in the object is an OBJC class
594     // pointer (an isa)
595     {
596       malloc_block_contents *block_contents = NULL;
597       if (task_peek(task, ptr_addr, sizeof(void *), (void **)&block_contents) ==
598           KERN_SUCCESS) {
599         // We assume that g_objc_classes is up to date
600         // that the class list was verified to have some classes in it
601         // before calling this function
602         const uint32_t objc_class_idx =
603             g_objc_classes.FindClassIndex(block_contents->isa);
604         if (objc_class_idx != UINT32_MAX) {
605           bool match = false;
606           if (info->objc.match_isa == 0) {
607             // Match any objective C object
608             match = true;
609           } else {
610             // Only match exact isa values in the current class or
611             // optionally in the super classes
612             if (info->objc.match_isa == block_contents->isa)
613               match = true;
614             else if (info->objc.match_superclasses) {
615               Class super = class_getSuperclass(block_contents->isa);
616               while (super) {
617                 match = super == info->objc.match_isa;
618                 if (match)
619                   break;
620                 super = class_getSuperclass(super);
621               }
622             }
623           }
624           if (match) {
625             // printf (" success\n");
626             ++info->match_count;
627             malloc_match match = {(void *)ptr_addr, ptr_size, 0, type};
628             g_matches.push_back(match, info->unique);
629           } else {
630             // printf (" error: wrong class: %s\n", dl_info.dli_sname);
631           }
632         } else {
633           // printf ("\terror: symbol not objc class: %s\n", dl_info.dli_sname);
634           return;
635         }
636       }
637     }
638     break;
639 
640   case eDataTypeHeapInfo:
641     // Check if the current malloc block contains an objective C object
642     // of any sort where the first pointer in the object is an OBJC class
643     // pointer (an isa)
644     {
645       malloc_block_contents *block_contents = NULL;
646       if (task_peek(task, ptr_addr, sizeof(void *), (void **)&block_contents) ==
647           KERN_SUCCESS) {
648         // We assume that g_objc_classes is up to date
649         // that the class list was verified to have some classes in it
650         // before calling this function
651         const uint32_t objc_class_idx =
652             g_objc_classes.FindClassIndex(block_contents->isa);
653         if (objc_class_idx != UINT32_MAX) {
654           // This is an objective C object
655           g_objc_class_snapshot.AddInstance(objc_class_idx, ptr_size);
656         } else {
657           // Classify other heap info
658         }
659       }
660     }
661     break;
662   }
663 }
664 
665 static void
get_stack_for_address_enumerator(mach_stack_logging_record_t stack_record,void * task_ptr)666 get_stack_for_address_enumerator(mach_stack_logging_record_t stack_record,
667                                  void *task_ptr) {
668   malloc_stack_entry *stack_entry = g_malloc_stack_history.next();
669   if (stack_entry) {
670     stack_entry->address = (void *)stack_record.address;
671     stack_entry->type_flags = stack_record.type_flags;
672     stack_entry->argument = stack_record.argument;
673     stack_entry->num_frames = 0;
674     stack_entry->frames[0] = 0;
675     kern_return_t err = __mach_stack_logging_frames_for_uniqued_stack(
676         *(task_t *)task_ptr, stack_record.stack_identifier, stack_entry->frames,
677         MAX_FRAMES, &stack_entry->num_frames);
678     // Terminate the frames with zero if there is room
679     if (stack_entry->num_frames < MAX_FRAMES)
680       stack_entry->frames[stack_entry->num_frames] = 0;
681   }
682 }
683 
get_stack_history_for_address(const void * addr,int history)684 malloc_stack_entry *get_stack_history_for_address(const void *addr,
685                                                   int history) {
686   if (!stack_logging_enable_logging)
687     return NULL;
688   g_malloc_stack_history.clear();
689   kern_return_t err;
690   task_t task = mach_task_self();
691   if (history) {
692     err = __mach_stack_logging_enumerate_records(
693         task, (mach_vm_address_t)addr, get_stack_for_address_enumerator, &task);
694   } else {
695     malloc_stack_entry *stack_entry = g_malloc_stack_history.next();
696     if (stack_entry) {
697       stack_entry->address = addr;
698       stack_entry->type_flags = stack_logging_type_alloc;
699       stack_entry->argument = 0;
700       stack_entry->num_frames = 0;
701       stack_entry->frames[0] = 0;
702       err = __mach_stack_logging_get_frames(task, (mach_vm_address_t)addr,
703                                             stack_entry->frames, MAX_FRAMES,
704                                             &stack_entry->num_frames);
705       if (err == 0 && stack_entry->num_frames > 0) {
706         // Terminate the frames with zero if there is room
707         if (stack_entry->num_frames < MAX_FRAMES)
708           stack_entry->frames[stack_entry->num_frames] = 0;
709       } else {
710         g_malloc_stack_history.clear();
711       }
712     }
713   }
714   // Return data if there is any
715   return g_malloc_stack_history.data();
716 }
717 
718 // find_pointer_in_heap
719 //
720 // Finds a pointer value inside one or more currently valid malloc
721 // blocks.
find_pointer_in_heap(const void * addr,int check_vm_regions)722 malloc_match *find_pointer_in_heap(const void *addr, int check_vm_regions) {
723   g_matches.clear();
724   // Setup "info" to look for a malloc block that contains data
725   // that is the pointer
726   if (addr) {
727     range_contains_data_callback_info_t data_info;
728     data_info.type = eDataTypeContainsData; // Check each block for data
729     data_info.data.buffer =
730         (uint8_t *)&addr; // What data? The pointer value passed in
731     data_info.data.size =
732         sizeof(addr); // How many bytes? The byte size of a pointer
733     data_info.data.align = sizeof(addr); // Align to a pointer byte size
734     data_info.match_count = 0;           // Initialize the match count to zero
735     data_info.done = false;   // Set done to false so searching doesn't stop
736     data_info.unique = false; // Set to true when iterating on the vm_regions
737     range_callback_info_t info = {enumerate_range_in_zone, range_info_callback,
738                                   &data_info, check_vm_regions};
739     foreach_zone_in_this_process(&info);
740   }
741   return g_matches.data();
742 }
743 
744 // find_pointer_in_memory
745 //
746 // Finds a pointer value inside one or more currently valid malloc
747 // blocks.
find_pointer_in_memory(uint64_t memory_addr,uint64_t memory_size,const void * addr)748 malloc_match *find_pointer_in_memory(uint64_t memory_addr, uint64_t memory_size,
749                                      const void *addr) {
750   g_matches.clear();
751   // Setup "info" to look for a malloc block that contains data
752   // that is the pointer
753   range_contains_data_callback_info_t data_info;
754   data_info.type = eDataTypeContainsData; // Check each block for data
755   data_info.data.buffer =
756       (uint8_t *)&addr; // What data? The pointer value passed in
757   data_info.data.size =
758       sizeof(addr); // How many bytes? The byte size of a pointer
759   data_info.data.align = sizeof(addr); // Align to a pointer byte size
760   data_info.match_count = 0;           // Initialize the match count to zero
761   data_info.done = false;   // Set done to false so searching doesn't stop
762   data_info.unique = false; // Set to true when iterating on the vm_regions
763   range_info_callback(mach_task_self(), &data_info, stack_logging_type_generic,
764                       memory_addr, memory_size);
765   return g_matches.data();
766 }
767 
768 // find_objc_objects_in_memory
769 //
770 // Find all instances of ObjC classes 'c', or all ObjC classes if 'c' is
771 // NULL. If 'c' is non NULL, then also check objects to see if they
772 // inherit from 'c'
find_objc_objects_in_memory(void * isa,int check_vm_regions)773 malloc_match *find_objc_objects_in_memory(void *isa, int check_vm_regions) {
774   g_matches.clear();
775   if (g_objc_classes.Update()) {
776     // Setup "info" to look for a malloc block that contains data
777     // that is the pointer
778     range_contains_data_callback_info_t data_info;
779     data_info.type = eDataTypeObjC; // Check each block for data
780     data_info.objc.match_isa = isa;
781     data_info.objc.match_superclasses = true;
782     data_info.match_count = 0; // Initialize the match count to zero
783     data_info.done = false;    // Set done to false so searching doesn't stop
784     data_info.unique = false;  // Set to true when iterating on the vm_regions
785     range_callback_info_t info = {enumerate_range_in_zone, range_info_callback,
786                                   &data_info, check_vm_regions};
787     foreach_zone_in_this_process(&info);
788   }
789   return g_matches.data();
790 }
791 
792 // get_heap_info
793 //
794 // Gather information for all allocations on the heap and report
795 // statistics.
796 
get_heap_info(int sort_type)797 void get_heap_info(int sort_type) {
798   if (g_objc_classes.Update()) {
799     // Reset all stats
800     g_objc_class_snapshot.Reset();
801     // Setup "info" to look for a malloc block that contains data
802     // that is the pointer
803     range_contains_data_callback_info_t data_info;
804     data_info.type = eDataTypeHeapInfo; // Check each block for data
805     data_info.match_count = 0;          // Initialize the match count to zero
806     data_info.done = false;   // Set done to false so searching doesn't stop
807     data_info.unique = false; // Set to true when iterating on the vm_regions
808     const int check_vm_regions = false;
809     range_callback_info_t info = {enumerate_range_in_zone, range_info_callback,
810                                   &data_info, check_vm_regions};
811     foreach_zone_in_this_process(&info);
812 
813     // Sort and print byte total bytes
814     switch (sort_type) {
815     case eSortTypeNone:
816     default:
817     case eSortTypeBytes:
818       g_objc_class_snapshot.SortByTotalBytes(g_objc_classes, true);
819       break;
820 
821     case eSortTypeCount:
822       g_objc_class_snapshot.SortByTotalCount(g_objc_classes, true);
823       break;
824     }
825   } else {
826     printf("error: no objective C classes\n");
827   }
828 }
829 
830 // find_cstring_in_heap
831 //
832 // Finds a C string inside one or more currently valid malloc blocks.
find_cstring_in_heap(const char * s,int check_vm_regions)833 malloc_match *find_cstring_in_heap(const char *s, int check_vm_regions) {
834   g_matches.clear();
835   if (s == NULL || s[0] == '\0') {
836     printf("error: invalid argument (empty cstring)\n");
837     return NULL;
838   }
839   // Setup "info" to look for a malloc block that contains data
840   // that is the C string passed in aligned on a 1 byte boundary
841   range_contains_data_callback_info_t data_info;
842   data_info.type = eDataTypeContainsData; // Check each block for data
843   data_info.data.buffer = (uint8_t *)s;   // What data? The C string passed in
844   data_info.data.size = strlen(s); // How many bytes? The length of the C string
845   data_info.data.align =
846       1; // Data doesn't need to be aligned, so set the alignment to 1
847   data_info.match_count = 0; // Initialize the match count to zero
848   data_info.done = false;    // Set done to false so searching doesn't stop
849   data_info.unique = false;  // Set to true when iterating on the vm_regions
850   range_callback_info_t info = {enumerate_range_in_zone, range_info_callback,
851                                 &data_info, check_vm_regions};
852   foreach_zone_in_this_process(&info);
853   return g_matches.data();
854 }
855 
856 // find_block_for_address
857 //
858 // Find the malloc block that whose address range contains "addr".
find_block_for_address(const void * addr,int check_vm_regions)859 malloc_match *find_block_for_address(const void *addr, int check_vm_regions) {
860   g_matches.clear();
861   // Setup "info" to look for a malloc block that contains data
862   // that is the C string passed in aligned on a 1 byte boundary
863   range_contains_data_callback_info_t data_info;
864   data_info.type = eDataTypeAddress; // Check each block to see if the block
865                                      // contains the address passed in
866   data_info.addr = (uintptr_t)addr;  // What data? The C string passed in
867   data_info.match_count = 0;         // Initialize the match count to zero
868   data_info.done = false;   // Set done to false so searching doesn't stop
869   data_info.unique = false; // Set to true when iterating on the vm_regions
870   range_callback_info_t info = {enumerate_range_in_zone, range_info_callback,
871                                 &data_info, check_vm_regions};
872   foreach_zone_in_this_process(&info);
873   return g_matches.data();
874 }
875