heap_find.cpp   [plain text]


//===-- head_find.c ---------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file compiles into a dylib and can be used on darwin to find data that
// is contained in active malloc blocks. To use this make the project, then
// load the shared library in a debug session while you are stopped:
//
// (lldb) process load /path/to/libheap.dylib
//
// Now you can use the "find_pointer_in_heap" and "find_cstring_in_heap" 
// functions in the expression parser.
//
// This will grep everything in all active allocation blocks and print and 
// malloc blocks that contain the pointer 0x112233000000:
//
// (lldb) expression find_pointer_in_heap (0x112233000000)
//
// This will grep everything in all active allocation blocks and print and 
// malloc blocks that contain the C string "hello" (as a substring, no
// NULL termination included):
//
// (lldb) expression find_cstring_in_heap ("hello")
//
// The results will be printed to the STDOUT of the inferior program. The 
// return value of the "find_pointer_in_heap" function is the number of 
// pointer references that were found. A quick example shows
//
// (lldb) expr find_pointer_in_heap(0x0000000104000410)
// (uint32_t) $5 = 0x00000002
// 0x104000740: 0x0000000104000410 found in malloc block 0x104000730 + 16 (malloc_size = 48)
// 0x100820060: 0x0000000104000410 found in malloc block 0x100820000 + 96 (malloc_size = 4096)
//
// From the above output we see that 0x104000410 was found in the malloc block
// at 0x104000730 and 0x100820000. If we want to see what these blocks are, we
// can display the memory for this block using the "address" ("A" for short) 
// format. The address format shows pointers, and if those pointers point to
// objects that have symbols or know data contents, it will display information
// about the pointers:
//
// (lldb) memory read --format address --count 1 0x104000730 
// 0x104000730: 0x0000000100002460 (void *)0x0000000100002488: MyString
// 
// We can see that the first block is a "MyString" object that contains our
// pointer value at offset 16.
//
// Looking at the next pointers, are a bit more tricky:
// (lldb) memory read -fA 0x100820000 -c1
// 0x100820000: 0x4f545541a1a1a1a1
// (lldb) memory read 0x100820000
// 0x100820000: a1 a1 a1 a1 41 55 54 4f 52 45 4c 45 41 53 45 21  ....AUTORELEASE!
// 0x100820010: 78 00 82 00 01 00 00 00 60 f9 e8 75 ff 7f 00 00  x.......`..u....
// 
// This is an objective C auto release pool object that contains our pointer.
// C++ classes will show up if they are virtual as something like:
// (lldb) memory read --format address --count 1 0x104008000
// 0x104008000: 0x109008000 vtable for lldb_private::Process
//
// This is a clue that the 0x104008000 is a "lldb_private::Process *".
//===----------------------------------------------------------------------===//

#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>

//----------------------------------------------------------------------
// Redefine private types from "/usr/local/include/stack_logging.h"
//----------------------------------------------------------------------
typedef struct {
	uint32_t		type_flags;
	uint64_t		stack_identifier;
	uint64_t		argument;
	mach_vm_address_t	address;
} mach_stack_logging_record_t;

//----------------------------------------------------------------------
// Redefine private defines from "/usr/local/include/stack_logging.h"
//----------------------------------------------------------------------
#define stack_logging_type_free		0
#define stack_logging_type_generic	1
#define stack_logging_type_alloc	2
#define stack_logging_type_dealloc	4
// This bit is made up by this code
#define stack_logging_type_vm_region 8 

//----------------------------------------------------------------------
// Redefine private function prototypes from 
// "/usr/local/include/stack_logging.h"
//----------------------------------------------------------------------
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);

//----------------------------------------------------------------------
// Redefine private gloval variables prototypes from 
// "/usr/local/include/stack_logging.h"
//----------------------------------------------------------------------

extern "C" int stack_logging_enable_logging;

//----------------------------------------------------------------------
// Local defines
//----------------------------------------------------------------------
#define MAX_FRAMES 1024

//----------------------------------------------------------------------
// Local Typedefs and Types
//----------------------------------------------------------------------
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; // Set to NULL for all objective C objects
    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)
        {
            // Don't add the entry if there is already a match for this address
            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; // Duplicate entry
            }
        }
        if (m_size < k_max_entries - 1)
        {
            m_entries[m_size] = m;
            m_size++;
        }
    }

    malloc_match *
    data ()
    {
        // If empty, return NULL
        if (empty())
            return NULL;
        // In not empty, terminate and return the result
        malloc_match terminator_entry = { NULL, 0, 0, 0 };
        // We always leave room for an empty entry at the end
        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; // Out of entries...
    }

    malloc_stack_entry *
    data ()
    {
        // If empty, return NULL
        if (empty())
            return NULL;
        // In not empty, terminate and return the result
        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;
};

//----------------------------------------------------------------------
// A safe way to allocate memory and keep it from interfering with the
// malloc enumerators.
//----------------------------------------------------------------------
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;
}


//----------------------------------------------------------------------
// ObjCClasses
//----------------------------------------------------------------------
class ObjCClasses
{
public:
    ObjCClasses() :
        m_objc_class_ptrs (NULL),
        m_size (0)
    {
    }

    bool
    Update()
    {
        // TODO: find out if class list has changed and update if needed
        if (m_objc_class_ptrs == NULL)
        {
            m_size = objc_getClassList(NULL, 0);
            if (m_size > 0)
            {
                // Allocate the class pointers
                m_objc_class_ptrs = (Class *)safe_malloc (m_size * sizeof(Class));
                m_size = objc_getClassList(m_objc_class_ptrs, m_size);
                // Sort Class pointers for quick lookup
                ::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;    
};



//----------------------------------------------------------------------
// Local global variables
//----------------------------------------------------------------------
MatchResults g_matches;
MallocStackLoggingEntries g_malloc_stack_history;
ObjCClasses g_objc_classes;

//----------------------------------------------------------------------
// ObjCClassInfo
//----------------------------------------------------------------------

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);
        // Update the totals for the 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)
        {
             // In case we sort the entries after gathering the data, we will
             // want to know the index into the m_objc_class_ptrs[] array.
            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;   // Index into the m_objc_class_ptrs[] array
        uint32_t count; // Number of object instances that were found
        uint64_t bytes; // Total number of bytes for each objc class
    };
    
    static int
    compare_bytes (const Entry *a, const Entry *b)
    {
        // Reverse the comparisong to most bytes entries end up at top of list
        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)
    {
        // Reverse the comparisong to most count entries end up at top of list
        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;

//----------------------------------------------------------------------
// task_peek
//
// Reads memory from this tasks address space. This callback is needed
// by the code that iterates through all of the malloc blocks to read
// the memory in this process.
//----------------------------------------------------------------------
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;
            // Check all read + write regions. This will cover the thread stacks 
            // and any regions of memory that aren't covered by the heap
            if (vm_region_info.protection & VM_PROT_WRITE && 
                vm_region_info.protection & VM_PROT_READ)
            {
                //printf ("checking vm_region: [0x%16.16llx - 0x%16.16llx)\n", (uint64_t)vm_region_base_addr, (uint64_t)vm_region_base_addr + vm_region_size);
                range_info_callback (task, 
                                     info->baton, 
                                     stack_logging_type_vm_region, 
                                     vm_region_base_addr, 
                                     vm_region_size);
            }
        }
    }
}

//----------------------------------------------------------------------
// dump_malloc_block_callback
//
// A simple callback that will dump each malloc block and all available
// info from the enumeration callback perpective.
//----------------------------------------------------------------------
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:
        // Check if the current malloc block contains an address specified by "info->addr"
        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:
        // Check if the current malloc block contains data specified in "info->data"
        {
            const uint32_t size = info->data.size;
            if (size < ptr_size) // Make sure this block can contain this data
            {
                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:
        // Check if the current malloc block contains an objective C object
        // of any sort where the first pointer in the object is an OBJC class
        // pointer (an isa)
        {
            malloc_block_contents *block_contents = NULL;
            if (task_peek (task, ptr_addr, sizeof(void *), (void **)&block_contents) == KERN_SUCCESS)
            {
                // We assume that g_objc_classes is up to date
                // that the class list was verified to have some classes in it
                // before calling this function
                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 any objective C object
                        match = true;
                    }
                    else 
                    {
                        // Only match exact isa values in the current class or
                        // optionally in the super classes
                        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)
                    {
                        //printf (" success\n");
                        ++info->match_count;
                        malloc_match match = { (void *)ptr_addr, ptr_size, 0, type };
                        g_matches.push_back(match, info->unique);
                    }
                    else
                    {
                        //printf (" error: wrong class: %s\n", dl_info.dli_sname);                        
                    }
                }
                else
                {
                    //printf ("\terror: symbol not objc class: %s\n", dl_info.dli_sname);
                    return;
                }
            }
        }
        break;

    case eDataTypeHeapInfo:
        // Check if the current malloc block contains an objective C object
        // of any sort where the first pointer in the object is an OBJC class
        // pointer (an isa)
        {
            malloc_block_contents *block_contents = NULL;
            if (task_peek (task, ptr_addr, sizeof(void *), (void **)&block_contents) == KERN_SUCCESS)
            {
                // We assume that g_objc_classes is up to date
                // that the class list was verified to have some classes in it
                // before calling this function
                const uint32_t objc_class_idx = g_objc_classes.FindClassIndex (block_contents->isa);
                if (objc_class_idx != UINT32_MAX)
                {
                    // This is an objective C object
                    g_objc_class_snapshot.AddInstance (objc_class_idx, ptr_size);
                }
                else
                {
                    // Classify other heap info
                }
            }
        }
        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);    
        // Terminate the frames with zero if there is room
        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)
            {
                // Terminate the frames with zero if there is room
                if (stack_entry->num_frames < MAX_FRAMES)
                    stack_entry->frames[stack_entry->num_frames] = 0;
            }
            else
            {
                g_malloc_stack_history.clear();                
            }
        }
    }
    // Return data if there is any
    return g_malloc_stack_history.data();
}

//----------------------------------------------------------------------
// find_pointer_in_heap
//
// Finds a pointer value inside one or more currently valid malloc
// blocks.
//----------------------------------------------------------------------
malloc_match *
find_pointer_in_heap (const void * addr, int check_vm_regions)
{
    g_matches.clear();
    // Setup "info" to look for a malloc block that contains data
    // that is the a pointer 
    if (addr)
    {
        range_contains_data_callback_info_t data_info;
        data_info.type = eDataTypeContainsData;      // Check each block for data
        data_info.data.buffer = (uint8_t *)&addr;    // What data? The pointer value passed in
        data_info.data.size = sizeof(addr);          // How many bytes? The byte size of a pointer
        data_info.data.align = sizeof(addr);         // Align to a pointer byte size
        data_info.match_count = 0;                   // Initialize the match count to zero
        data_info.done = false;                      // Set done to false so searching doesn't stop
        data_info.unique = false;                    // Set to true when iterating on the vm_regions
        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();
}

//----------------------------------------------------------------------
// find_pointer_in_memory
//
// Finds a pointer value inside one or more currently valid malloc
// blocks.
//----------------------------------------------------------------------
malloc_match *
find_pointer_in_memory (uint64_t memory_addr, uint64_t memory_size, const void * addr)
{
    g_matches.clear();
    // Setup "info" to look for a malloc block that contains data
    // that is the a pointer 
    range_contains_data_callback_info_t data_info;
    data_info.type = eDataTypeContainsData;      // Check each block for data
    data_info.data.buffer = (uint8_t *)&addr;    // What data? The pointer value passed in
    data_info.data.size = sizeof(addr);          // How many bytes? The byte size of a pointer
    data_info.data.align = sizeof(addr);         // Align to a pointer byte size
    data_info.match_count = 0;                   // Initialize the match count to zero
    data_info.done = false;                      // Set done to false so searching doesn't stop
    data_info.unique = false;                    // Set to true when iterating on the vm_regions
    range_info_callback (mach_task_self(), &data_info, stack_logging_type_generic, memory_addr, memory_size);
    return g_matches.data();
}

//----------------------------------------------------------------------
// find_objc_objects_in_memory
//
// Find all instances of ObjC classes 'c', or all ObjC classes if 'c' is
// NULL. If 'c' is non NULL, then also check objects to see if they 
// inherit from 'c'
//----------------------------------------------------------------------
malloc_match *
find_objc_objects_in_memory (void *isa, int check_vm_regions)
{
    g_matches.clear();
    if (g_objc_classes.Update())
    {
        // Setup "info" to look for a malloc block that contains data
        // that is the a pointer 
        range_contains_data_callback_info_t data_info;
        data_info.type = eDataTypeObjC;      // Check each block for data
        data_info.objc.match_isa = isa;
        data_info.objc.match_superclasses = true;
        data_info.match_count = 0;                   // Initialize the match count to zero
        data_info.done = false;                      // Set done to false so searching doesn't stop
        data_info.unique = false;                    // Set to true when iterating on the vm_regions
        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();
}

//----------------------------------------------------------------------
// get_heap_info
//
// Gather information for all allocations on the heap and report 
// statistics.
//----------------------------------------------------------------------

void
get_heap_info (int sort_type)
{
    if (g_objc_classes.Update())
    {
        // Reset all stats
        g_objc_class_snapshot.Reset ();
        // Setup "info" to look for a malloc block that contains data
        // that is the a pointer 
        range_contains_data_callback_info_t data_info;
        data_info.type = eDataTypeHeapInfo; // Check each block for data
        data_info.match_count = 0;          // Initialize the match count to zero
        data_info.done = false;             // Set done to false so searching doesn't stop
        data_info.unique = false;           // Set to true when iterating on the vm_regions
        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);
        
        // Sort and print byte total bytes
        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");
    }
}

//----------------------------------------------------------------------
// find_cstring_in_heap
//
// Finds a C string inside one or more currently valid malloc blocks.
//----------------------------------------------------------------------
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;
    }
    // Setup "info" to look for a malloc block that contains data
    // that is the C string passed in aligned on a 1 byte boundary
    range_contains_data_callback_info_t data_info;
    data_info.type = eDataTypeContainsData;  // Check each block for data
    data_info.data.buffer = (uint8_t *)s;    // What data? The C string passed in
    data_info.data.size = strlen(s);         // How many bytes? The length of the C string
    data_info.data.align = 1;                // Data doesn't need to be aligned, so set the alignment to 1
    data_info.match_count = 0;               // Initialize the match count to zero
    data_info.done = false;                  // Set done to false so searching doesn't stop
    data_info.unique = false;                // Set to true when iterating on the vm_regions
    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();
}

//----------------------------------------------------------------------
// find_block_for_address
//
// Find the malloc block that whose address range contains "addr".
//----------------------------------------------------------------------
malloc_match *
find_block_for_address (const void *addr, int check_vm_regions)
{
    g_matches.clear();
    // Setup "info" to look for a malloc block that contains data
    // that is the C string passed in aligned on a 1 byte boundary
    range_contains_data_callback_info_t data_info;
    data_info.type = eDataTypeAddress;  // Check each block to see if the block contains the address passed in
    data_info.addr = (uintptr_t)addr;   // What data? The C string passed in
    data_info.match_count = 0;          // Initialize the match count to zero
    data_info.done = false;             // Set done to false so searching doesn't stop
    data_info.unique = false;           // Set to true when iterating on the vm_regions
    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();
}