#include <assert.h>
#include <ctype.h>
#include <dlfcn.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <malloc/malloc.h>
#include <objc/objc-runtime.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
typedef struct {
uint32_t type_flags;
uint64_t stack_identifier;
uint64_t argument;
mach_vm_address_t address;
} mach_stack_logging_record_t;
#define stack_logging_type_free 0
#define stack_logging_type_generic 1
#define stack_logging_type_alloc 2
#define stack_logging_type_dealloc 4
#define stack_logging_type_vm_region 8
extern "C" kern_return_t
__mach_stack_logging_set_file_path (
task_t task,
char* file_path
);
extern "C" kern_return_t
__mach_stack_logging_get_frames (
task_t task,
mach_vm_address_t address,
mach_vm_address_t *stack_frames_buffer,
uint32_t max_stack_frames,
uint32_t *count
);
extern "C" kern_return_t
__mach_stack_logging_enumerate_records (
task_t task,
mach_vm_address_t address,
void enumerator(mach_stack_logging_record_t, void *),
void *context
);
extern "C" kern_return_t
__mach_stack_logging_frames_for_uniqued_stack (
task_t task,
uint64_t stack_identifier,
mach_vm_address_t *stack_frames_buffer,
uint32_t max_stack_frames,
uint32_t *count
);
extern "C" void *gdb_class_getClass (void *objc_class);
static void
range_info_callback (task_t task,
void *baton,
unsigned type,
uint64_t ptr_addr,
uint64_t ptr_size);
extern "C" int stack_logging_enable_logging;
#define MAX_FRAMES 1024
typedef void range_callback_t (task_t task, void *baton, unsigned type, uint64_t ptr_addr, uint64_t ptr_size);
typedef void zone_callback_t (void *info, const malloc_zone_t *zone);
typedef int (*comare_function_t)(const void *, const void *);
struct range_callback_info_t
{
zone_callback_t *zone_callback;
range_callback_t *range_callback;
void *baton;
int check_vm_regions;
};
enum data_type_t
{
eDataTypeAddress,
eDataTypeContainsData,
eDataTypeObjC,
eDataTypeHeapInfo
};
struct aligned_data_t
{
const uint8_t *buffer;
uint32_t size;
uint32_t align;
};
struct objc_data_t
{
void *match_isa; bool match_superclasses;
};
struct range_contains_data_callback_info_t
{
data_type_t type;
const void *lookup_addr;
union
{
uintptr_t addr;
aligned_data_t data;
objc_data_t objc;
};
uint32_t match_count;
bool done;
bool unique;
};
struct malloc_match
{
void *addr;
intptr_t size;
intptr_t offset;
uintptr_t type;
};
struct malloc_stack_entry
{
const void *address;
uint64_t argument;
uint32_t type_flags;
uint32_t num_frames;
mach_vm_address_t frames[MAX_FRAMES];
};
struct malloc_block_contents
{
union {
Class isa;
void *pointers[2];
};
};
static int
compare_void_ptr (const void *a, const void *b)
{
Class a_ptr = *(Class *)a;
Class b_ptr = *(Class *)b;
if (a_ptr < b_ptr) return -1;
if (a_ptr > b_ptr) return +1;
return 0;
}
class MatchResults
{
enum {
k_max_entries = 8 * 1024
};
public:
MatchResults () :
m_size(0)
{
}
void
clear()
{
m_size = 0;
bzero (&m_entries, sizeof(m_entries));
}
bool
empty() const
{
return m_size == 0;
}
void
push_back (const malloc_match& m, bool unique = false)
{
if (unique)
{
for (uint32_t i=0; i<m_size; ++i)
{
if (((uint8_t *)m_entries[i].addr + m_entries[i].offset) == ((uint8_t *)m.addr + m.offset))
return; }
}
if (m_size < k_max_entries - 1)
{
m_entries[m_size] = m;
m_size++;
}
}
malloc_match *
data ()
{
if (empty())
return NULL;
malloc_match terminator_entry = { NULL, 0, 0, 0 };
m_entries[m_size] = terminator_entry;
return m_entries;
}
protected:
malloc_match m_entries[k_max_entries];
uint32_t m_size;
};
class MallocStackLoggingEntries
{
enum { k_max_entries = 128 };
public:
MallocStackLoggingEntries () :
m_size(0)
{
}
void
clear()
{
m_size = 0;
}
bool
empty() const
{
return m_size == 0;
}
malloc_stack_entry *
next ()
{
if (m_size < k_max_entries - 1)
{
malloc_stack_entry * result = m_entries + m_size;
++m_size;
return result;
}
return NULL; }
malloc_stack_entry *
data ()
{
if (empty())
return NULL;
m_entries[m_size].address = NULL;
m_entries[m_size].argument = 0;
m_entries[m_size].type_flags = 0;
m_entries[m_size].num_frames = 0;
return m_entries;
}
protected:
malloc_stack_entry m_entries[k_max_entries];
uint32_t m_size;
};
void *
safe_malloc(size_t n_bytes)
{
if (n_bytes > 0)
{
const int k_page_size = getpagesize();
const mach_vm_size_t vm_size = ((n_bytes + k_page_size - 1)/k_page_size) * k_page_size;
vm_address_t address = NULL;
kern_return_t kerr = vm_allocate (mach_task_self(), &address, vm_size, true);
if (kerr == KERN_SUCCESS)
return (void *)address;
}
return NULL;
}
class ObjCClasses
{
public:
ObjCClasses() :
m_objc_class_ptrs (NULL),
m_size (0)
{
}
bool
Update()
{
if (m_objc_class_ptrs == NULL)
{
m_size = objc_getClassList(NULL, 0);
if (m_size > 0)
{
m_objc_class_ptrs = (Class *)safe_malloc (m_size * sizeof(Class));
m_size = objc_getClassList(m_objc_class_ptrs, m_size);
::qsort (m_objc_class_ptrs, m_size, sizeof(Class), compare_void_ptr);
}
else
return false;
}
return true;
}
uint32_t
FindClassIndex (Class isa)
{
Class *matching_class = (Class *)bsearch (&isa,
m_objc_class_ptrs,
m_size,
sizeof(Class),
compare_void_ptr);
if (matching_class)
{
uint32_t idx = matching_class - m_objc_class_ptrs;
return idx;
}
return UINT32_MAX;
}
Class
GetClassAtIndex (uint32_t idx) const
{
if (idx < m_size)
return m_objc_class_ptrs[idx];
return NULL;
}
uint32_t
GetSize() const
{
return m_size;
}
private:
Class *m_objc_class_ptrs;
uint32_t m_size;
};
MatchResults g_matches;
MallocStackLoggingEntries g_malloc_stack_history;
ObjCClasses g_objc_classes;
enum HeapInfoSortType
{
eSortTypeNone,
eSortTypeBytes,
eSortTypeCount
};
class ObjCClassInfo
{
public:
ObjCClassInfo() :
m_entries (NULL),
m_size (0),
m_sort_type (eSortTypeNone)
{
}
void
Update (const ObjCClasses &objc_classes)
{
m_size = objc_classes.GetSize();
m_entries = (Entry *)safe_malloc (m_size * sizeof(Entry));
m_sort_type = eSortTypeNone;
Reset ();
}
bool
AddInstance (uint32_t idx, uint64_t ptr_size)
{
if (m_size == 0)
Update (g_objc_classes);
if (idx < m_size)
{
m_entries[idx].bytes += ptr_size;
++m_entries[idx].count;
return true;
}
return false;
}
void
Reset ()
{
m_sort_type = eSortTypeNone;
for (uint32_t i=0; i<m_size; ++i)
{
m_entries[i].idx = i;
m_entries[i].bytes = 0;
m_entries[i].count = 0;
}
}
void
SortByTotalBytes (const ObjCClasses &objc_classes, bool print)
{
if (m_sort_type != eSortTypeBytes && m_size > 0)
{
::qsort (m_entries, m_size, sizeof(Entry), (comare_function_t)compare_bytes);
m_sort_type = eSortTypeBytes;
}
if (print && m_size > 0)
{
puts("Objective C objects by total bytes:");
puts("Total Bytes Class Name");
puts("----------- -----------------------------------------------------------------");
for (uint32_t i=0; i<m_size && m_entries[i].bytes > 0; ++i)
{
printf ("%11llu %s\n", m_entries[i].bytes, class_getName (objc_classes.GetClassAtIndex(m_entries[i].idx)));
}
}
}
void
SortByTotalCount (const ObjCClasses &objc_classes, bool print)
{
if (m_sort_type != eSortTypeCount && m_size > 0)
{
::qsort (m_entries, m_size, sizeof(Entry), (comare_function_t)compare_count);
m_sort_type = eSortTypeCount;
}
if (print && m_size > 0)
{
puts("Objective C objects by total count:");
puts("Count Class Name");
puts("-------- -----------------------------------------------------------------");
for (uint32_t i=0; i<m_size && m_entries[i].count > 0; ++i)
{
printf ("%8u %s\n", m_entries[i].count, class_getName (objc_classes.GetClassAtIndex(m_entries[i].idx)));
}
}
}
private:
struct Entry
{
uint32_t idx; uint32_t count; uint64_t bytes; };
static int
compare_bytes (const Entry *a, const Entry *b)
{
if (a->bytes > b->bytes) return -1;
if (a->bytes < b->bytes) return +1;
return 0;
}
static int
compare_count (const Entry *a, const Entry *b)
{
if (a->count > b->count) return -1;
if (a->count < b->count) return +1;
return 0;
}
Entry *m_entries;
uint32_t m_size;
HeapInfoSortType m_sort_type;
};
ObjCClassInfo g_objc_class_snapshot;
static kern_return_t
task_peek (task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory)
{
*local_memory = (void*) remote_address;
return KERN_SUCCESS;
}
static const void
foreach_zone_in_this_process (range_callback_info_t *info)
{
if (info == NULL || info->zone_callback == NULL)
return;
vm_address_t *zones = NULL;
unsigned int num_zones = 0;
kern_return_t err = malloc_get_all_zones (0, task_peek, &zones, &num_zones);
if (KERN_SUCCESS == err)
{
for (unsigned int i=0; i<num_zones; ++i)
{
info->zone_callback (info, (const malloc_zone_t *)zones[i]);
}
}
if (info->check_vm_regions)
{
#if defined (VM_REGION_SUBMAP_SHORT_INFO_COUNT_64)
typedef vm_region_submap_short_info_data_64_t RegionInfo;
enum { kRegionInfoSize = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 };
#else
typedef vm_region_submap_info_data_64_t RegionInfo;
enum { kRegionInfoSize = VM_REGION_SUBMAP_INFO_COUNT_64 };
#endif
task_t task = mach_task_self();
mach_vm_address_t vm_region_base_addr;
mach_vm_size_t vm_region_size;
natural_t vm_region_depth;
RegionInfo vm_region_info;
((range_contains_data_callback_info_t *)info->baton)->unique = true;
for (vm_region_base_addr = 0, vm_region_size = 1; vm_region_size != 0; vm_region_base_addr += vm_region_size)
{
mach_msg_type_number_t vm_region_info_size = kRegionInfoSize;
const kern_return_t err = mach_vm_region_recurse (task,
&vm_region_base_addr,
&vm_region_size,
&vm_region_depth,
(vm_region_recurse_info_t)&vm_region_info,
&vm_region_info_size);
if (err)
break;
if (vm_region_info.protection & VM_PROT_WRITE &&
vm_region_info.protection & VM_PROT_READ)
{
range_info_callback (task,
info->baton,
stack_logging_type_vm_region,
vm_region_base_addr,
vm_region_size);
}
}
}
}
static void
dump_malloc_block_callback (task_t task, void *baton, unsigned type, uint64_t ptr_addr, uint64_t ptr_size)
{
printf ("task = 0x%4.4x: baton = %p, type = %u, ptr_addr = 0x%llx + 0x%llu\n", task, baton, type, ptr_addr, ptr_size);
}
static void
ranges_callback (task_t task, void *baton, unsigned type, vm_range_t *ptrs, unsigned count)
{
range_callback_info_t *info = (range_callback_info_t *)baton;
while(count--) {
info->range_callback (task, info->baton, type, ptrs->address, ptrs->size);
ptrs++;
}
}
static void
enumerate_range_in_zone (void *baton, const malloc_zone_t *zone)
{
range_callback_info_t *info = (range_callback_info_t *)baton;
if (zone && zone->introspect)
zone->introspect->enumerator (mach_task_self(),
info,
MALLOC_PTR_IN_USE_RANGE_TYPE,
(vm_address_t)zone,
task_peek,
ranges_callback);
}
static void
range_info_callback (task_t task, void *baton, unsigned type, uint64_t ptr_addr, uint64_t ptr_size)
{
const uint64_t end_addr = ptr_addr + ptr_size;
range_contains_data_callback_info_t *info = (range_contains_data_callback_info_t *)baton;
switch (info->type)
{
case eDataTypeAddress:
if (ptr_addr <= info->addr && info->addr < end_addr)
{
++info->match_count;
malloc_match match = { (void *)ptr_addr, ptr_size, info->addr - ptr_addr, type };
g_matches.push_back(match, info->unique);
}
break;
case eDataTypeContainsData:
{
const uint32_t size = info->data.size;
if (size < ptr_size) {
uint8_t *ptr_data = NULL;
if (task_peek (task, ptr_addr, ptr_size, (void **)&ptr_data) == KERN_SUCCESS)
{
const void *buffer = info->data.buffer;
assert (ptr_data);
const uint32_t align = info->data.align;
for (uint64_t addr = ptr_addr;
addr < end_addr && ((end_addr - addr) >= size);
addr += align, ptr_data += align)
{
if (memcmp (buffer, ptr_data, size) == 0)
{
++info->match_count;
malloc_match match = { (void *)ptr_addr, ptr_size, addr - ptr_addr, type };
g_matches.push_back(match, info->unique);
}
}
}
else
{
printf ("0x%llx: error: couldn't read %llu bytes\n", ptr_addr, ptr_size);
}
}
}
break;
case eDataTypeObjC:
{
malloc_block_contents *block_contents = NULL;
if (task_peek (task, ptr_addr, sizeof(void *), (void **)&block_contents) == KERN_SUCCESS)
{
const uint32_t objc_class_idx = g_objc_classes.FindClassIndex (block_contents->isa);
if (objc_class_idx != UINT32_MAX)
{
bool match = false;
if (info->objc.match_isa == 0)
{
match = true;
}
else
{
if (info->objc.match_isa == block_contents->isa)
match = true;
else if (info->objc.match_superclasses)
{
Class super = class_getSuperclass(block_contents->isa);
while (super)
{
match = super == info->objc.match_isa;
if (match)
break;
super = class_getSuperclass(super);
}
}
}
if (match)
{
++info->match_count;
malloc_match match = { (void *)ptr_addr, ptr_size, 0, type };
g_matches.push_back(match, info->unique);
}
else
{
}
}
else
{
return;
}
}
}
break;
case eDataTypeHeapInfo:
{
malloc_block_contents *block_contents = NULL;
if (task_peek (task, ptr_addr, sizeof(void *), (void **)&block_contents) == KERN_SUCCESS)
{
const uint32_t objc_class_idx = g_objc_classes.FindClassIndex (block_contents->isa);
if (objc_class_idx != UINT32_MAX)
{
g_objc_class_snapshot.AddInstance (objc_class_idx, ptr_size);
}
else
{
}
}
}
break;
}
}
static void
get_stack_for_address_enumerator(mach_stack_logging_record_t stack_record, void *task_ptr)
{
malloc_stack_entry *stack_entry = g_malloc_stack_history.next();
if (stack_entry)
{
stack_entry->address = (void *)stack_record.address;
stack_entry->type_flags = stack_record.type_flags;
stack_entry->argument = stack_record.argument;
stack_entry->num_frames = 0;
stack_entry->frames[0] = 0;
kern_return_t err = __mach_stack_logging_frames_for_uniqued_stack (*(task_t *)task_ptr,
stack_record.stack_identifier,
stack_entry->frames,
MAX_FRAMES,
&stack_entry->num_frames);
if (stack_entry->num_frames < MAX_FRAMES)
stack_entry->frames[stack_entry->num_frames] = 0;
}
}
malloc_stack_entry *
get_stack_history_for_address (const void * addr, int history)
{
if (!stack_logging_enable_logging)
return NULL;
g_malloc_stack_history.clear();
kern_return_t err;
task_t task = mach_task_self();
if (history)
{
err = __mach_stack_logging_enumerate_records (task,
(mach_vm_address_t)addr,
get_stack_for_address_enumerator,
&task);
}
else
{
malloc_stack_entry *stack_entry = g_malloc_stack_history.next();
if (stack_entry)
{
stack_entry->address = addr;
stack_entry->type_flags = stack_logging_type_alloc;
stack_entry->argument = 0;
stack_entry->num_frames = 0;
stack_entry->frames[0] = 0;
err = __mach_stack_logging_get_frames(task, (mach_vm_address_t)addr, stack_entry->frames, MAX_FRAMES, &stack_entry->num_frames);
if (err == 0 && stack_entry->num_frames > 0)
{
if (stack_entry->num_frames < MAX_FRAMES)
stack_entry->frames[stack_entry->num_frames] = 0;
}
else
{
g_malloc_stack_history.clear();
}
}
}
return g_malloc_stack_history.data();
}
malloc_match *
find_pointer_in_heap (const void * addr, int check_vm_regions)
{
g_matches.clear();
if (addr)
{
range_contains_data_callback_info_t data_info;
data_info.type = eDataTypeContainsData; data_info.data.buffer = (uint8_t *)&addr; data_info.data.size = sizeof(addr); data_info.data.align = sizeof(addr); data_info.match_count = 0; data_info.done = false; data_info.unique = false; range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
foreach_zone_in_this_process (&info);
}
return g_matches.data();
}
malloc_match *
find_pointer_in_memory (uint64_t memory_addr, uint64_t memory_size, const void * addr)
{
g_matches.clear();
range_contains_data_callback_info_t data_info;
data_info.type = eDataTypeContainsData; data_info.data.buffer = (uint8_t *)&addr; data_info.data.size = sizeof(addr); data_info.data.align = sizeof(addr); data_info.match_count = 0; data_info.done = false; data_info.unique = false; range_info_callback (mach_task_self(), &data_info, stack_logging_type_generic, memory_addr, memory_size);
return g_matches.data();
}
malloc_match *
find_objc_objects_in_memory (void *isa, int check_vm_regions)
{
g_matches.clear();
if (g_objc_classes.Update())
{
range_contains_data_callback_info_t data_info;
data_info.type = eDataTypeObjC; data_info.objc.match_isa = isa;
data_info.objc.match_superclasses = true;
data_info.match_count = 0; data_info.done = false; data_info.unique = false; range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
foreach_zone_in_this_process (&info);
}
return g_matches.data();
}
void
get_heap_info (int sort_type)
{
if (g_objc_classes.Update())
{
g_objc_class_snapshot.Reset ();
range_contains_data_callback_info_t data_info;
data_info.type = eDataTypeHeapInfo; data_info.match_count = 0; data_info.done = false; data_info.unique = false; const int check_vm_regions = false;
range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
foreach_zone_in_this_process (&info);
switch (sort_type)
{
case eSortTypeNone:
default:
case eSortTypeBytes:
g_objc_class_snapshot.SortByTotalBytes(g_objc_classes, true);
break;
case eSortTypeCount:
g_objc_class_snapshot.SortByTotalCount(g_objc_classes, true);
break;
}
}
else
{
printf ("error: no objective C classes\n");
}
}
malloc_match *
find_cstring_in_heap (const char *s, int check_vm_regions)
{
g_matches.clear();
if (s == NULL || s[0] == '\0')
{
printf ("error: invalid argument (empty cstring)\n");
return NULL;
}
range_contains_data_callback_info_t data_info;
data_info.type = eDataTypeContainsData; data_info.data.buffer = (uint8_t *)s; data_info.data.size = strlen(s); data_info.data.align = 1; data_info.match_count = 0; data_info.done = false; data_info.unique = false; range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
foreach_zone_in_this_process (&info);
return g_matches.data();
}
malloc_match *
find_block_for_address (const void *addr, int check_vm_regions)
{
g_matches.clear();
range_contains_data_callback_info_t data_info;
data_info.type = eDataTypeAddress; data_info.addr = (uintptr_t)addr; data_info.match_count = 0; data_info.done = false; data_info.unique = false; range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
foreach_zone_in_this_process (&info);
return g_matches.data();
}