mirror of
https://github.com/Gericom/teak-llvm.git
synced 2025-06-30 00:38:54 -04:00

*** to conform to clang-format’s LLVM style. This kind of mass change has *** two obvious implications: Firstly, merging this particular commit into a downstream fork may be a huge effort. Alternatively, it may be worth merging all changes up to this commit, performing the same reformatting operation locally, and then discarding the merge for this particular commit. The commands used to accomplish this reformatting were as follows (with current working directory as the root of the repository): find . \( -iname "*.c" -or -iname "*.cpp" -or -iname "*.h" -or -iname "*.mm" \) -exec clang-format -i {} + find . -iname "*.py" -exec autopep8 --in-place --aggressive --aggressive {} + ; The version of clang-format used was 3.9.0, and autopep8 was 1.2.4. Secondly, “blame” style tools will generally point to this commit instead of a meaningful prior commit. There are alternatives available that will attempt to look through this change and find the appropriate prior commit. YMMV. llvm-svn: 280751
912 lines
33 KiB
C++
912 lines
33 KiB
C++
//===-- heap_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 *".
|
|
//===----------------------------------------------------------------------===//
|
|
// C includes
|
|
#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 <unistd.h>
|
|
|
|
// C++ includes
|
|
#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 global 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 = 0;
|
|
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 comparison 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 comparison 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 perspective.
|
|
//----------------------------------------------------------------------
|
|
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 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 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 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 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();
|
|
}
|