diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 |
commit | 9e6d35490a6542f9c97607f93c2ef8ca8e03cbcc (patch) | |
tree | dd2a1ddf0476664c2b823409c36cbccd52662ca7 /examples/darwin/heap_find | |
parent | 3bd2e91faeb9eeec1aae82c64a3253afff551cfd (diff) |
Notes
Diffstat (limited to 'examples/darwin/heap_find')
-rw-r--r-- | examples/darwin/heap_find/heap.py | 1244 | ||||
-rw-r--r-- | examples/darwin/heap_find/heap/Makefile | 33 | ||||
-rw-r--r-- | examples/darwin/heap_find/heap/heap_find.cpp | 1071 |
3 files changed, 2348 insertions, 0 deletions
diff --git a/examples/darwin/heap_find/heap.py b/examples/darwin/heap_find/heap.py new file mode 100644 index 0000000000000..fb3394323890e --- /dev/null +++ b/examples/darwin/heap_find/heap.py @@ -0,0 +1,1244 @@ +#!/usr/bin/python + +#---------------------------------------------------------------------- +# This module is designed to live inside the "lldb" python package +# in the "lldb.macosx" package. To use this in the embedded python +# interpreter using "lldb" just import it: +# +# (lldb) script import lldb.macosx.heap +#---------------------------------------------------------------------- + +import lldb +import commands +import optparse +import os +import os.path +import re +import shlex +import string +import sys +import tempfile +import lldb.utils.symbolication + +g_libheap_dylib_dir = None +g_libheap_dylib_dict = dict() + +def get_iterate_memory_expr(options, process, user_init_code, user_return_code): + expr = ''' +typedef unsigned natural_t; +typedef uintptr_t vm_size_t; +typedef uintptr_t vm_address_t; +typedef natural_t task_t; +typedef int kern_return_t; +#define KERN_SUCCESS 0 +typedef void (*range_callback_t)(task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size); +'''; + if options.search_vm_regions: + expr += ''' +typedef int vm_prot_t; +typedef unsigned int vm_inherit_t; +typedef unsigned long long memory_object_offset_t; +typedef unsigned int boolean_t; +typedef int vm_behavior_t; +typedef uint32_t vm32_object_id_t; +typedef natural_t mach_msg_type_number_t; +typedef uint64_t mach_vm_address_t; +typedef uint64_t mach_vm_offset_t; +typedef uint64_t mach_vm_size_t; +typedef uint64_t vm_map_offset_t; +typedef uint64_t vm_map_address_t; +typedef uint64_t vm_map_size_t; +#define VM_PROT_NONE ((vm_prot_t) 0x00) +#define VM_PROT_READ ((vm_prot_t) 0x01) +#define VM_PROT_WRITE ((vm_prot_t) 0x02) +#define VM_PROT_EXECUTE ((vm_prot_t) 0x04) +typedef struct vm_region_submap_short_info_data_64_t { + vm_prot_t protection; + vm_prot_t max_protection; + vm_inherit_t inheritance; + memory_object_offset_t offset; // offset into object/map + unsigned int user_tag; // user tag on map entry + unsigned int ref_count; // obj/map mappers, etc + unsigned short shadow_depth; // only for obj + unsigned char external_pager; // only for obj + unsigned char share_mode; // see enumeration + boolean_t is_submap; // submap vs obj + vm_behavior_t behavior; // access behavior hint + vm32_object_id_t object_id; // obj/map name, not a handle + unsigned short user_wired_count; +} vm_region_submap_short_info_data_64_t; +#define VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 ((mach_msg_type_number_t)(sizeof(vm_region_submap_short_info_data_64_t)/sizeof(int)))'''; + if user_init_code: + expr += user_init_code; + expr += ''' +task_t task = (task_t)mach_task_self(); +mach_vm_address_t vm_region_base_addr; +mach_vm_size_t vm_region_size; +natural_t vm_region_depth; +vm_region_submap_short_info_data_64_t vm_region_info; +kern_return_t err; +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 = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; + err = (kern_return_t)mach_vm_region_recurse (task, + &vm_region_base_addr, + &vm_region_size, + &vm_region_depth, + &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 like __DATA segments, that might contain + // data we are looking for + if (vm_region_info.protection & VM_PROT_WRITE && + vm_region_info.protection & VM_PROT_READ) + { + baton.callback (task, + &baton, + 64, + vm_region_base_addr, + vm_region_size); + } +}''' + else: + if options.search_stack: + expr += get_thread_stack_ranges_struct (process) + if options.search_segments: + expr += get_sections_ranges_struct (process) + if user_init_code: + expr += user_init_code + if options.search_heap: + expr += ''' +#define MALLOC_PTR_IN_USE_RANGE_TYPE 1 +typedef struct vm_range_t { + vm_address_t address; + vm_size_t size; +} vm_range_t; +typedef kern_return_t (*memory_reader_t)(task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory); +typedef void (*vm_range_recorder_t)(task_t task, void *baton, unsigned type, vm_range_t *range, unsigned size); +typedef struct malloc_introspection_t { + kern_return_t (*enumerator)(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder); /* enumerates all the malloc pointers in use */ +} malloc_introspection_t; +typedef struct malloc_zone_t { + void *reserved1[12]; + struct malloc_introspection_t *introspect; +} malloc_zone_t; +memory_reader_t task_peek = [](task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory) -> kern_return_t { + *local_memory = (void*) remote_address; + return KERN_SUCCESS; +}; +vm_address_t *zones = 0; +unsigned int num_zones = 0;task_t task = 0; +kern_return_t err = (kern_return_t)malloc_get_all_zones (task, task_peek, &zones, &num_zones); +if (KERN_SUCCESS == err) +{ + for (unsigned int i=0; i<num_zones; ++i) + { + const malloc_zone_t *zone = (const malloc_zone_t *)zones[i]; + if (zone && zone->introspect) + zone->introspect->enumerator (task, + &baton, + MALLOC_PTR_IN_USE_RANGE_TYPE, + (vm_address_t)zone, + task_peek, + [] (task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned size) -> void + { + range_callback_t callback = ((callback_baton_t *)baton)->callback; + for (unsigned i=0; i<size; ++i) + { + callback (task, baton, type, ranges[i].address, ranges[i].size); + } + }); + } +}''' + + if options.search_stack: + expr += ''' +#ifdef NUM_STACKS +// Call the callback for the thread stack ranges +for (uint32_t i=0; i<NUM_STACKS; ++i) { + range_callback(task, &baton, 8, stacks[i].base, stacks[i].size); + if (STACK_RED_ZONE_SIZE > 0) { + range_callback(task, &baton, 16, stacks[i].base - STACK_RED_ZONE_SIZE, STACK_RED_ZONE_SIZE); + } +} +#endif''' + + if options.search_segments: + expr += ''' +#ifdef NUM_SEGMENTS +// Call the callback for all segments +for (uint32_t i=0; i<NUM_SEGMENTS; ++i) + range_callback(task, &baton, 32, segments[i].base, segments[i].size); +#endif''' + + if user_return_code: + expr += "\n%s" % (user_return_code,) + + return expr + +def get_member_types_for_offset(value_type, offset, member_list): + member = value_type.GetFieldAtIndex(0) + search_bases = False + if member: + if member.GetOffsetInBytes() <= offset: + for field_idx in range (value_type.GetNumberOfFields()): + member = value_type.GetFieldAtIndex(field_idx) + member_byte_offset = member.GetOffsetInBytes() + member_end_byte_offset = member_byte_offset + member.type.size + if member_byte_offset <= offset and offset < member_end_byte_offset: + member_list.append(member) + get_member_types_for_offset (member.type, offset - member_byte_offset, member_list) + return + else: + search_bases = True + else: + search_bases = True + if search_bases: + for field_idx in range (value_type.GetNumberOfDirectBaseClasses()): + member = value_type.GetDirectBaseClassAtIndex(field_idx) + member_byte_offset = member.GetOffsetInBytes() + member_end_byte_offset = member_byte_offset + member.type.size + if member_byte_offset <= offset and offset < member_end_byte_offset: + member_list.append(member) + get_member_types_for_offset (member.type, offset - member_byte_offset, member_list) + return + for field_idx in range (value_type.GetNumberOfVirtualBaseClasses()): + member = value_type.GetVirtualBaseClassAtIndex(field_idx) + member_byte_offset = member.GetOffsetInBytes() + member_end_byte_offset = member_byte_offset + member.type.size + if member_byte_offset <= offset and offset < member_end_byte_offset: + member_list.append(member) + get_member_types_for_offset (member.type, offset - member_byte_offset, member_list) + return + +def append_regex_callback(option, opt, value, parser): + try: + ivar_regex = re.compile(value) + parser.values.ivar_regex_blacklist.append(ivar_regex) + except: + print 'error: an exception was thrown when compiling the ivar regular expression for "%s"' % value + +def add_common_options(parser): + parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) + parser.add_option('-t', '--type', action='store_true', dest='print_type', help='print the full value of the type for each matching malloc block', default=False) + parser.add_option('-o', '--po', action='store_true', dest='print_object_description', help='print the object descriptions for any matches', default=False) + parser.add_option('-z', '--size', action='store_true', dest='show_size', help='print the allocation size in bytes', default=False) + parser.add_option('-r', '--range', action='store_true', dest='show_range', help='print the allocation address range instead of just the allocation base address', default=False) + parser.add_option('-m', '--memory', action='store_true', dest='memory', help='dump the memory for each matching block', default=False) + parser.add_option('-f', '--format', type='string', dest='format', help='the format to use when dumping memory if --memory is specified', default=None) + parser.add_option('-I', '--omit-ivar-regex', type='string', action='callback', callback=append_regex_callback, dest='ivar_regex_blacklist', default=[], help='specify one or more regular expressions used to backlist any matches that are in ivars') + parser.add_option('-s', '--stack', action='store_true', dest='stack', help='gets the stack that allocated each malloc block if MallocStackLogging is enabled', default=False) + parser.add_option('-S', '--stack-history', action='store_true', dest='stack_history', help='gets the stack history for all allocations whose start address matches each malloc block if MallocStackLogging is enabled', default=False) + parser.add_option('-F', '--max-frames', type='int', dest='max_frames', help='the maximum number of stack frames to print when using the --stack or --stack-history options (default=128)', default=128) + parser.add_option('-H', '--max-history', type='int', dest='max_history', help='the maximum number of stack history backtraces to print for each allocation when using the --stack-history option (default=16)', default=16) + parser.add_option('-M', '--max-matches', type='int', dest='max_matches', help='the maximum number of matches to print', default=32) + parser.add_option('-O', '--offset', type='int', dest='offset', help='the matching data must be at this offset', default=-1) + parser.add_option('--ignore-stack', action='store_false', dest='search_stack', help="Don't search the stack when enumerating memory", default=True) + parser.add_option('--ignore-heap', action='store_false', dest='search_heap', help="Don't search the heap allocations when enumerating memory", default=True) + parser.add_option('--ignore-segments', action='store_false', dest='search_segments', help="Don't search readable executable segments enumerating memory", default=True) + parser.add_option('-V', '--vm-regions', action='store_true', dest='search_vm_regions', help='Check all VM regions instead of searching the heap, stack and segments', default=False) + +def type_flags_to_string(type_flags): + if type_flags == 0: + type_str = 'free' + elif type_flags & 2: + type_str = 'malloc' + elif type_flags & 4: + type_str = 'free' + elif type_flags & 1: + type_str = 'generic' + elif type_flags & 8: + type_str = 'stack' + elif type_flags & 16: + type_str = 'stack (red zone)' + elif type_flags & 32: + type_str = 'segment' + elif type_flags & 64: + type_str = 'vm_region' + else: + type_str = hex(type_flags) + return type_str + +def find_variable_containing_address(verbose, frame, match_addr): + variables = frame.GetVariables(True,True,True,True) + matching_var = None + for var in variables: + var_addr = var.GetLoadAddress() + if var_addr != lldb.LLDB_INVALID_ADDRESS: + byte_size = var.GetType().GetByteSize() + if verbose: + print 'frame #%u: [%#x - %#x) %s' % (frame.GetFrameID(), var.load_addr, var.load_addr + byte_size, var.name) + if var_addr == match_addr: + if verbose: + print 'match' + return var + else: + if byte_size > 0 and var_addr <= match_addr and match_addr < (var_addr + byte_size): + if verbose: + print 'match' + return var + return None + +def find_frame_for_stack_address(process, addr): + closest_delta = sys.maxint + closest_frame = None + #print 'find_frame_for_stack_address(%#x)' % (addr) + for thread in process: + prev_sp = lldb.LLDB_INVALID_ADDRESS + for frame in thread: + cfa = frame.GetCFA() + #print 'frame #%u: cfa = %#x' % (frame.GetFrameID(), cfa) + if addr < cfa: + delta = cfa - addr + #print '%#x < %#x, delta = %i' % (addr, cfa, delta) + if delta < closest_delta: + #print 'closest' + closest_delta = delta + closest_frame = frame + # else: + # print 'delta >= closest_delta' + return closest_frame + +def type_flags_to_description(process, type_flags, ptr_addr, ptr_size, offset, match_addr): + show_offset = False + if type_flags == 0 or type_flags & 4: + type_str = 'free(%#x)' % (ptr_addr,) + elif type_flags & 2 or type_flags & 1: + type_str = 'malloc(%6u) -> %#x' % (ptr_size, ptr_addr) + show_offset = True + elif type_flags & 8: + type_str = 'stack' + frame = find_frame_for_stack_address(process, match_addr) + if frame: + type_str += ' in frame #%u of thread #%u: tid %#x' % (frame.GetFrameID(), frame.GetThread().GetIndexID(), frame.GetThread().GetThreadID()) + variables = frame.GetVariables(True,True,True,True) + matching_var = None + for var in variables: + var_addr = var.GetLoadAddress() + if var_addr != lldb.LLDB_INVALID_ADDRESS: + #print 'variable "%s" @ %#x (%#x)' % (var.name, var.load_addr, match_addr) + if var_addr == match_addr: + matching_var = var + break + else: + byte_size = var.GetType().GetByteSize() + if byte_size > 0 and var_addr <= match_addr and match_addr < (var_addr + byte_size): + matching_var = var + break + if matching_var: + type_str += ' in variable at %#x:\n %s' % (matching_var.GetLoadAddress(), matching_var) + elif type_flags & 16: + type_str = 'stack (red zone)' + elif type_flags & 32: + sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset) + type_str = 'segment [%#x - %#x), %s + %u, %s' % (ptr_addr, ptr_addr + ptr_size, sb_addr.section.name, sb_addr.offset, sb_addr) + elif type_flags & 64: + sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset) + type_str = 'vm_region [%#x - %#x), %s + %u, %s' % (ptr_addr, ptr_addr + ptr_size, sb_addr.section.name, sb_addr.offset, sb_addr) + else: + type_str = '%#x' % (ptr_addr,) + show_offset = True + if show_offset and offset != 0: + type_str += ' + %-6u' % (offset,) + return type_str + +def dump_stack_history_entry(options, result, stack_history_entry, idx): + address = int(stack_history_entry.address) + if address: + type_flags = int(stack_history_entry.type_flags) + symbolicator = lldb.utils.symbolication.Symbolicator() + symbolicator.target = lldb.debugger.GetSelectedTarget() + type_str = type_flags_to_string(type_flags) + result.AppendMessage('stack[%u]: addr = 0x%x, type=%s, frames:' % (idx, address, type_str)) + frame_idx = 0 + idx = 0 + pc = int(stack_history_entry.frames[idx]) + while pc != 0: + if pc >= 0x1000: + frames = symbolicator.symbolicate(pc) + if frames: + for frame in frames: + result.AppendMessage(' [%u] %s' % (frame_idx, frame)) + frame_idx += 1 + else: + result.AppendMessage(' [%u] 0x%x' % (frame_idx, pc)) + frame_idx += 1 + idx = idx + 1 + pc = int(stack_history_entry.frames[idx]) + else: + pc = 0 + if idx >= options.max_frames: + result.AppendMessage('warning: the max number of stack frames (%u) was reached, use the "--max-frames=<COUNT>" option to see more frames' % (options.max_frames)) + + result.AppendMessage('') + +def dump_stack_history_entries(options, result, addr, history): + # malloc_stack_entry *get_stack_history_for_address (const void * addr) + expr_prefix = ''' +typedef int kern_return_t; +typedef struct $malloc_stack_entry { + uint64_t address; + uint64_t argument; + uint32_t type_flags; + uint32_t num_frames; + uint64_t frames[512]; + kern_return_t err; +} $malloc_stack_entry; +''' + single_expr = ''' +#define MAX_FRAMES %u +typedef unsigned task_t; +$malloc_stack_entry stack; +stack.address = 0x%x; +stack.type_flags = 2; +stack.num_frames = 0; +stack.frames[0] = 0; +uint32_t max_stack_frames = MAX_FRAMES; +stack.err = (kern_return_t)__mach_stack_logging_get_frames ( + (task_t)mach_task_self(), + stack.address, + &stack.frames[0], + max_stack_frames, + &stack.num_frames); +if (stack.num_frames < MAX_FRAMES) + stack.frames[stack.num_frames] = 0; +else + stack.frames[MAX_FRAMES-1] = 0; +stack''' % (options.max_frames, addr); + + history_expr = ''' +typedef int kern_return_t; +typedef unsigned task_t; +#define MAX_FRAMES %u +#define MAX_HISTORY %u +typedef struct mach_stack_logging_record_t { + uint32_t type_flags; + uint64_t stack_identifier; + uint64_t argument; + uint64_t address; +} mach_stack_logging_record_t; +typedef void (*enumerate_callback_t)(mach_stack_logging_record_t, void *); +typedef struct malloc_stack_entry { + uint64_t address; + uint64_t argument; + uint32_t type_flags; + uint32_t num_frames; + uint64_t frames[MAX_FRAMES]; + kern_return_t frames_err; +} malloc_stack_entry; +typedef struct $malloc_stack_history { + task_t task; + unsigned idx; + malloc_stack_entry entries[MAX_HISTORY]; +} $malloc_stack_history; +$malloc_stack_history info = { (task_t)mach_task_self(), 0 }; +uint32_t max_stack_frames = MAX_FRAMES; +enumerate_callback_t callback = [] (mach_stack_logging_record_t stack_record, void *baton) -> void { + $malloc_stack_history *info = ($malloc_stack_history *)baton; + if (info->idx < MAX_HISTORY) { + malloc_stack_entry *stack_entry = &(info->entries[info->idx]); + stack_entry->address = 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; + stack_entry->frames_err = (kern_return_t)__mach_stack_logging_frames_for_uniqued_stack ( + info->task, + stack_record.stack_identifier, + stack_entry->frames, + (uint32_t)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; + } + ++info->idx; +}; +(kern_return_t)__mach_stack_logging_enumerate_records (info.task, (uint64_t)0x%x, callback, &info); +info''' % (options.max_frames, options.max_history, addr); + + frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame() + if history: + expr = history_expr + else: + expr = single_expr + expr_options = lldb.SBExpressionOptions() + expr_options.SetIgnoreBreakpoints(True); + expr_options.SetTimeoutInMicroSeconds (5*1000*1000) # 5 second timeout + expr_options.SetTryAllThreads (True) + expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus) + expr_options.SetPrefix(expr_prefix) + expr_sbvalue = frame.EvaluateExpression (expr, expr_options) + if options.verbose: + print "expression:" + print expr + print "expression result:" + print expr_sbvalue + if expr_sbvalue.error.Success(): + if history: + malloc_stack_history = lldb.value(expr_sbvalue) + num_stacks = int(malloc_stack_history.idx) + if num_stacks <= options.max_history: + i_max = num_stacks + else: + i_max = options.max_history + for i in range(i_max): + stack_history_entry = malloc_stack_history.entries[i] + dump_stack_history_entry(options, result, stack_history_entry, i) + if num_stacks > options.max_history: + result.AppendMessage('warning: the max number of stacks (%u) was reached, use the "--max-history=%u" option to see all of the stacks' % (options.max_history, num_stacks)) + else: + stack_history_entry = lldb.value(expr_sbvalue) + dump_stack_history_entry(options, result, stack_history_entry, 0) + + else: + result.AppendMessage('error: expression failed "%s" => %s' % (expr, expr_sbvalue.error)) + + +def display_match_results (process, result, options, arg_str_description, expr, print_no_matches, expr_prefix = None): + frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame() + if not frame: + result.AppendMessage('error: invalid frame') + return 0 + expr_options = lldb.SBExpressionOptions() + expr_options.SetIgnoreBreakpoints(True); + expr_options.SetFetchDynamicValue(lldb.eNoDynamicValues); + expr_options.SetTimeoutInMicroSeconds (30*1000*1000) # 30 second timeout + expr_options.SetTryAllThreads (False) + expr_options.SetLanguage (lldb.eLanguageTypeObjC_plus_plus) + if expr_prefix: + expr_options.SetPrefix (expr_prefix) + expr_sbvalue = frame.EvaluateExpression (expr, expr_options) + if options.verbose: + print "expression:" + print expr + print "expression result:" + print expr_sbvalue + if expr_sbvalue.error.Success(): + match_value = lldb.value(expr_sbvalue) + i = 0 + match_idx = 0 + while 1: + print_entry = True + match_entry = match_value[i]; i += 1 + if i > options.max_matches: + result.AppendMessage('warning: the max number of matches (%u) was reached, use the --max-matches option to get more results' % (options.max_matches)) + break + malloc_addr = match_entry.addr.sbvalue.unsigned + if malloc_addr == 0: + break + malloc_size = int(match_entry.size) + offset = int(match_entry.offset) + + if options.offset >= 0 and options.offset != offset: + print_entry = False + else: + match_addr = malloc_addr + offset + type_flags = int(match_entry.type) + #result.AppendMessage (hex(malloc_addr + offset)) + if type_flags == 64: + search_stack_old = options.search_stack + search_segments_old = options.search_segments + search_heap_old = options.search_heap + search_vm_regions = options.search_vm_regions + options.search_stack = True + options.search_segments = True + options.search_heap = True + options.search_vm_regions = False + if malloc_info_impl (lldb.debugger, result, options, [hex(malloc_addr + offset)]): + print_entry = False + options.search_stack = search_stack_old + options.search_segments = search_segments_old + options.search_heap = search_heap_old + options.search_vm_regions = search_vm_regions + if print_entry: + description = '%#16.16x: %s' % (match_addr, type_flags_to_description(process, type_flags, malloc_addr, malloc_size, offset, match_addr)) + if options.show_size: + description += ' <%5u>' % (malloc_size) + if options.show_range: + description += ' [%#x - %#x)' % (malloc_addr, malloc_addr + malloc_size) + derefed_dynamic_value = None + dynamic_value = match_entry.addr.sbvalue.GetDynamicValue(lldb.eDynamicCanRunTarget) + if dynamic_value.type.name == 'void *': + if options.type == 'pointer' and malloc_size == 4096: + error = lldb.SBError() + process = expr_sbvalue.GetProcess() + target = expr_sbvalue.GetTarget() + data = bytearray(process.ReadMemory(malloc_addr, 16, error)) + if data == '\xa1\xa1\xa1\xa1AUTORELEASE!': + ptr_size = target.addr_size + thread = process.ReadUnsignedFromMemory (malloc_addr + 16 + ptr_size, ptr_size, error) + # 4 bytes 0xa1a1a1a1 + # 12 bytes 'AUTORELEASE!' + # ptr bytes autorelease insertion point + # ptr bytes pthread_t + # ptr bytes next colder page + # ptr bytes next hotter page + # 4 bytes this page's depth in the list + # 4 bytes high-water mark + description += ' AUTORELEASE! for pthread_t %#x' % (thread) + # else: + # description += 'malloc(%u)' % (malloc_size) + # else: + # description += 'malloc(%u)' % (malloc_size) + else: + derefed_dynamic_value = dynamic_value.deref + if derefed_dynamic_value: + derefed_dynamic_type = derefed_dynamic_value.type + derefed_dynamic_type_size = derefed_dynamic_type.size + derefed_dynamic_type_name = derefed_dynamic_type.name + description += ' ' + description += derefed_dynamic_type_name + if offset < derefed_dynamic_type_size: + member_list = list(); + get_member_types_for_offset (derefed_dynamic_type, offset, member_list) + if member_list: + member_path = '' + for member in member_list: + member_name = member.name + if member_name: + if member_path: + member_path += '.' + member_path += member_name + if member_path: + if options.ivar_regex_blacklist: + for ivar_regex in options.ivar_regex_blacklist: + if ivar_regex.match(member_path): + print_entry = False + description += '.%s' % (member_path) + else: + description += '%u bytes after %s' % (offset - derefed_dynamic_type_size, derefed_dynamic_type_name) + else: + # strip the "*" from the end of the name since we were unable to dereference this + description += dynamic_value.type.name[0:-1] + if print_entry: + match_idx += 1 + result_output = '' + if description: + result_output += description + if options.print_type and derefed_dynamic_value: + result_output += ' %s' % (derefed_dynamic_value) + if options.print_object_description and dynamic_value: + desc = dynamic_value.GetObjectDescription() + if desc: + result_output += '\n%s' % (desc) + if result_output: + result.AppendMessage(result_output) + if options.memory: + cmd_result = lldb.SBCommandReturnObject() + if options.format == None: + memory_command = "memory read --force 0x%x 0x%x" % (malloc_addr, malloc_addr + malloc_size) + else: + memory_command = "memory read --force -f %s 0x%x 0x%x" % (options.format, malloc_addr, malloc_addr + malloc_size) + if options.verbose: + result.AppendMessage(memory_command) + lldb.debugger.GetCommandInterpreter().HandleCommand(memory_command, cmd_result) + result.AppendMessage(cmd_result.GetOutput()) + if options.stack_history: + dump_stack_history_entries(options, result, malloc_addr, 1) + elif options.stack: + dump_stack_history_entries(options, result, malloc_addr, 0) + return i + else: + result.AppendMessage(str(expr_sbvalue.error)) + return 0 + +def get_ptr_refs_options (): + usage = "usage: %prog [options] <EXPR> [EXPR ...]" + description='''Searches all allocations on the heap for pointer values on +darwin user space programs. Any matches that were found will dump the malloc +blocks that contain the pointers and might be able to print what kind of +objects the pointers are contained in using dynamic type information in the +program.''' + parser = optparse.OptionParser(description=description, prog='ptr_refs',usage=usage) + add_common_options(parser) + return parser + +def find_variable(debugger, command, result, dict): + usage = "usage: %prog [options] <ADDR> [ADDR ...]" + description='''Searches for a local variable in all frames that contains a hex ADDR.''' + command_args = shlex.split(command) + parser = optparse.OptionParser(description=description, prog='find_variable',usage=usage) + parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) + try: + (options, args) = parser.parse_args(command_args) + except: + return + + process = debugger.GetSelectedTarget().GetProcess() + if not process: + result.AppendMessage('error: invalid process') + return + + for arg in args: + var_addr = int(arg, 16) + print >>result, "Finding a variable with address %#x..." % (var_addr) + done = False + for thread in process: + for frame in thread: + var = find_variable_containing_address(options.verbose, frame, var_addr) + if var: + print var + done = True + break + if done: + break + +def ptr_refs(debugger, command, result, dict): + command_args = shlex.split(command) + parser = get_ptr_refs_options() + try: + (options, args) = parser.parse_args(command_args) + except: + return + + process = debugger.GetSelectedTarget().GetProcess() + if not process: + result.AppendMessage('error: invalid process') + return + frame = process.GetSelectedThread().GetSelectedFrame() + if not frame: + result.AppendMessage('error: invalid frame') + return + + options.type = 'pointer' + if options.format == None: + options.format = "A" # 'A' is "address" format + + if args: + # When we initialize the expression, we must define any types that + # we will need when looking at every allocation. We must also define + # a type named callback_baton_t and make an instance named "baton" + # and initialize it how ever we want to. The address of "baton" will + # be passed into our range callback. callback_baton_t must contain + # a member named "callback" whose type is "range_callback_t". This + # will be used by our zone callbacks to call the range callback for + # each malloc range. + expr_prefix = ''' +struct $malloc_match { + void *addr; + uintptr_t size; + uintptr_t offset; + uintptr_t type; +}; +''' + user_init_code_format = ''' +#define MAX_MATCHES %u +typedef struct callback_baton_t { + range_callback_t callback; + unsigned num_matches; + $malloc_match matches[MAX_MATCHES]; + void *ptr; +} callback_baton_t; +range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void { + callback_baton_t *info = (callback_baton_t *)baton; + typedef void* T; + const unsigned size = sizeof(T); + T *array = (T*)ptr_addr; + for (unsigned idx = 0; ((idx + 1) * sizeof(T)) <= ptr_size; ++idx) { + if (array[idx] == info->ptr) { + if (info->num_matches < MAX_MATCHES) { + info->matches[info->num_matches].addr = (void*)ptr_addr; + info->matches[info->num_matches].size = ptr_size; + info->matches[info->num_matches].offset = idx*sizeof(T); + info->matches[info->num_matches].type = type; + ++info->num_matches; + } + } + } +}; +callback_baton_t baton = { range_callback, 0, {0}, (void *)%s }; +''' + # We must also define a snippet of code to be run that returns + # the result of the expression we run. + # Here we return NULL if our pointer was not found in any malloc blocks, + # and we return the address of the matches array so we can then access + # the matching results + user_return_code = '''if (baton.num_matches < MAX_MATCHES) + baton.matches[baton.num_matches].addr = 0; // Terminate the matches array +baton.matches''' + # Iterate through all of our pointer expressions and display the results + for ptr_expr in args: + user_init_code = user_init_code_format % (options.max_matches, ptr_expr) + expr = get_iterate_memory_expr(options, process, user_init_code, user_return_code) + arg_str_description = 'malloc block containing pointer %s' % ptr_expr + display_match_results (process, result, options, arg_str_description, expr, True, expr_prefix) + else: + result.AppendMessage('error: no pointer arguments were given') + +def get_cstr_refs_options(): + usage = "usage: %prog [options] <CSTR> [CSTR ...]" + description='''Searches all allocations on the heap for C string values on +darwin user space programs. Any matches that were found will dump the malloc +blocks that contain the C strings and might be able to print what kind of +objects the pointers are contained in using dynamic type information in the +program.''' + parser = optparse.OptionParser(description=description, prog='cstr_refs',usage=usage) + add_common_options(parser) + return parser + +def cstr_refs(debugger, command, result, dict): + command_args = shlex.split(command) + parser = get_cstr_refs_options(); + try: + (options, args) = parser.parse_args(command_args) + except: + return + + process = debugger.GetSelectedTarget().GetProcess() + if not process: + result.AppendMessage('error: invalid process') + return + frame = process.GetSelectedThread().GetSelectedFrame() + if not frame: + result.AppendMessage('error: invalid frame') + return + + + options.type = 'cstr' + if options.format == None: + options.format = "Y" # 'Y' is "bytes with ASCII" format + + if args: + # When we initialize the expression, we must define any types that + # we will need when looking at every allocation. We must also define + # a type named callback_baton_t and make an instance named "baton" + # and initialize it how ever we want to. The address of "baton" will + # be passed into our range callback. callback_baton_t must contain + # a member named "callback" whose type is "range_callback_t". This + # will be used by our zone callbacks to call the range callback for + # each malloc range. + expr_prefix = ''' +struct $malloc_match { + void *addr; + uintptr_t size; + uintptr_t offset; + uintptr_t type; +}; +''' + user_init_code_format = ''' +#define MAX_MATCHES %u +typedef struct callback_baton_t { + range_callback_t callback; + unsigned num_matches; + $malloc_match matches[MAX_MATCHES]; + const char *cstr; + unsigned cstr_len; +} callback_baton_t; +range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void { + callback_baton_t *info = (callback_baton_t *)baton; + if (info->cstr_len < ptr_size) { + const char *begin = (const char *)ptr_addr; + const char *end = begin + ptr_size - info->cstr_len; + for (const char *s = begin; s < end; ++s) { + if ((int)memcmp(s, info->cstr, info->cstr_len) == 0) { + if (info->num_matches < MAX_MATCHES) { + info->matches[info->num_matches].addr = (void*)ptr_addr; + info->matches[info->num_matches].size = ptr_size; + info->matches[info->num_matches].offset = s - begin; + info->matches[info->num_matches].type = type; + ++info->num_matches; + } + } + } + } +}; +const char *cstr = "%s"; +callback_baton_t baton = { range_callback, 0, {0}, cstr, (unsigned)strlen(cstr) };''' + # We must also define a snippet of code to be run that returns + # the result of the expression we run. + # Here we return NULL if our pointer was not found in any malloc blocks, + # and we return the address of the matches array so we can then access + # the matching results + user_return_code = '''if (baton.num_matches < MAX_MATCHES) + baton.matches[baton.num_matches].addr = 0; // Terminate the matches array +baton.matches''' + # Iterate through all of our pointer expressions and display the results + for cstr in args: + user_init_code = user_init_code_format % (options.max_matches, cstr) + expr = get_iterate_memory_expr(options, process, user_init_code, user_return_code) + arg_str_description = 'malloc block containing "%s"' % cstr + display_match_results (process, result, options, arg_str_description, expr, True, expr_prefix) + else: + result.AppendMessage('error: command takes one or more C string arguments') + + +def get_malloc_info_options(): + usage = "usage: %prog [options] <EXPR> [EXPR ...]" + description='''Searches the heap a malloc block that contains the addresses +specified as one or more address expressions. Any matches that were found will +dump the malloc blocks that match or contain the specified address. The matching +blocks might be able to show what kind of objects they are using dynamic type +information in the program.''' + parser = optparse.OptionParser(description=description, prog='malloc_info',usage=usage) + add_common_options(parser) + return parser + +def malloc_info(debugger, command, result, dict): + command_args = shlex.split(command) + parser = get_malloc_info_options() + try: + (options, args) = parser.parse_args(command_args) + except: + return + malloc_info_impl (debugger, result, options, args) + +def malloc_info_impl (debugger, result, options, args): + # We are specifically looking for something on the heap only + options.type = 'malloc_info' + + process = debugger.GetSelectedTarget().GetProcess() + if not process: + result.AppendMessage('error: invalid process') + return + frame = process.GetSelectedThread().GetSelectedFrame() + if not frame: + result.AppendMessage('error: invalid frame') + return + expr_prefix = ''' +struct $malloc_match { + void *addr; + uintptr_t size; + uintptr_t offset; + uintptr_t type; +}; +''' + + user_init_code_format = ''' +typedef struct callback_baton_t { + range_callback_t callback; + unsigned num_matches; + $malloc_match matches[2]; // Two items so they can be NULL terminated + void *ptr; +} callback_baton_t; +range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void { + callback_baton_t *info = (callback_baton_t *)baton; + if (info->num_matches == 0) { + uint8_t *p = (uint8_t *)info->ptr; + uint8_t *lo = (uint8_t *)ptr_addr; + uint8_t *hi = lo + ptr_size; + if (lo <= p && p < hi) { + info->matches[info->num_matches].addr = (void*)ptr_addr; + info->matches[info->num_matches].size = ptr_size; + info->matches[info->num_matches].offset = p - lo; + info->matches[info->num_matches].type = type; + info->num_matches = 1; + } + } +}; +callback_baton_t baton = { range_callback, 0, {0}, (void *)%s }; +baton.matches[0].addr = 0; +baton.matches[1].addr = 0;''' + if args: + total_matches = 0 + for ptr_expr in args: + user_init_code = user_init_code_format % (ptr_expr) + expr = get_iterate_memory_expr(options, process, user_init_code, 'baton.matches') + arg_str_description = 'malloc block that contains %s' % ptr_expr + total_matches += display_match_results (process, result, options, arg_str_description, expr, True, expr_prefix) + return total_matches + else: + result.AppendMessage('error: command takes one or more pointer expressions') + return 0 + +def get_thread_stack_ranges_struct (process): + '''Create code that defines a structure that represents threads stack bounds + for all threads. It returns a static sized array initialized with all of + the tid, base, size structs for all the threads.''' + stack_dicts = list() + if process: + i = 0; + for thread in process: + min_sp = thread.frame[0].sp + max_sp = min_sp + for frame in thread.frames: + sp = frame.sp + if sp < min_sp: min_sp = sp + if sp > max_sp: max_sp = sp + if min_sp < max_sp: + stack_dicts.append ({ 'tid' : thread.GetThreadID(), 'base' : min_sp , 'size' : max_sp-min_sp, 'index' : i }) + i += 1 + stack_dicts_len = len(stack_dicts) + if stack_dicts_len > 0: + result = ''' +#define NUM_STACKS %u +#define STACK_RED_ZONE_SIZE %u +typedef struct thread_stack_t { uint64_t tid, base, size; } thread_stack_t; +thread_stack_t stacks[NUM_STACKS];''' % (stack_dicts_len, process.target.GetStackRedZoneSize()) + for stack_dict in stack_dicts: + result += ''' +stacks[%(index)u].tid = 0x%(tid)x; +stacks[%(index)u].base = 0x%(base)x; +stacks[%(index)u].size = 0x%(size)x;''' % stack_dict + return result + else: + return '' + +def get_sections_ranges_struct (process): + '''Create code that defines a structure that represents all segments that + can contain data for all images in "target". It returns a static sized + array initialized with all of base, size structs for all the threads.''' + target = process.target + segment_dicts = list() + for (module_idx, module) in enumerate(target.modules): + for sect_idx in range(module.GetNumSections()): + section = module.GetSectionAtIndex(sect_idx) + if not section: + break + name = section.name + if name != '__TEXT' and name != '__LINKEDIT' and name != '__PAGEZERO': + base = section.GetLoadAddress(target) + size = section.GetByteSize() + if base != lldb.LLDB_INVALID_ADDRESS and size > 0: + segment_dicts.append ({ 'base' : base, 'size' : size }) + segment_dicts_len = len(segment_dicts) + if segment_dicts_len > 0: + result = ''' +#define NUM_SEGMENTS %u +typedef struct segment_range_t { uint64_t base; uint32_t size; } segment_range_t; +segment_range_t segments[NUM_SEGMENTS];''' % (segment_dicts_len,) + for (idx, segment_dict) in enumerate(segment_dicts): + segment_dict['index'] = idx + result += ''' +segments[%(index)u].base = 0x%(base)x; +segments[%(index)u].size = 0x%(size)x;''' % segment_dict + return result + else: + return '' + +def section_ptr_refs(debugger, command, result, dict): + command_args = shlex.split(command) + usage = "usage: %prog [options] <EXPR> [EXPR ...]" + description='''Searches section contents for pointer values in darwin user space programs.''' + parser = optparse.OptionParser(description=description, prog='section_ptr_refs',usage=usage) + add_common_options(parser) + parser.add_option('--section', action='append', type='string', dest='section_names', help='section name to search', default=list()) + try: + (options, args) = parser.parse_args(command_args) + except: + return + + options.type = 'pointer' + + sections = list() + section_modules = list() + if not options.section_names: + result.AppendMessage('error: at least one section must be specified with the --section option') + return + + target = debugger.GetSelectedTarget() + for module in target.modules: + for section_name in options.section_names: + section = module.section[section_name] + if section: + sections.append (section) + section_modules.append (module) + if sections: + dylid_load_err = load_dylib() + if dylid_load_err: + result.AppendMessage(dylid_load_err) + return + frame = target.GetProcess().GetSelectedThread().GetSelectedFrame() + for expr_str in args: + for (idx, section) in enumerate(sections): + expr = 'find_pointer_in_memory(0x%xllu, %ullu, (void *)%s)' % (section.addr.load_addr, section.size, expr_str) + arg_str_description = 'section %s.%s containing "%s"' % (section_modules[idx].file.fullpath, section.name, expr_str) + num_matches = display_match_results (target.GetProcess(), result, options, arg_str_description, expr, False) + if num_matches: + if num_matches < options.max_matches: + options.max_matches = options.max_matches - num_matches + else: + options.max_matches = 0 + if options.max_matches == 0: + return + else: + result.AppendMessage('error: no sections were found that match any of %s' % (', '.join(options.section_names))) + +def get_objc_refs_options(): + usage = "usage: %prog [options] <CLASS> [CLASS ...]" + description='''Searches all allocations on the heap for instances of +objective C classes, or any classes that inherit from the specified classes +in darwin user space programs. Any matches that were found will dump the malloc +blocks that contain the C strings and might be able to print what kind of +objects the pointers are contained in using dynamic type information in the +program.''' + parser = optparse.OptionParser(description=description, prog='objc_refs',usage=usage) + add_common_options(parser) + return parser + +def objc_refs(debugger, command, result, dict): + command_args = shlex.split(command) + parser = get_objc_refs_options() + try: + (options, args) = parser.parse_args(command_args) + except: + return + + process = debugger.GetSelectedTarget().GetProcess() + if not process: + result.AppendMessage('error: invalid process') + return + frame = process.GetSelectedThread().GetSelectedFrame() + if not frame: + result.AppendMessage('error: invalid frame') + return + + options.type = 'isa' + if options.format == None: + options.format = "A" # 'A' is "address" format + + expr_options = lldb.SBExpressionOptions() + expr_options.SetIgnoreBreakpoints(True); + expr_options.SetTimeoutInMicroSeconds (3*1000*1000) # 3 second infinite timeout + expr_options.SetTryAllThreads (True) + expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus) + num_objc_classes_value = frame.EvaluateExpression("(int)objc_getClassList((void *)0, (int)0)", expr_options) + if not num_objc_classes_value.error.Success(): + result.AppendMessage('error: %s' % num_objc_classes_value.error.GetCString()) + return + + num_objc_classes = num_objc_classes_value.GetValueAsUnsigned() + if num_objc_classes == 0: + result.AppendMessage('error: no objective C classes in program') + return + + if args: + # When we initialize the expression, we must define any types that + # we will need when looking at every allocation. We must also define + # a type named callback_baton_t and make an instance named "baton" + # and initialize it how ever we want to. The address of "baton" will + # be passed into our range callback. callback_baton_t must contain + # a member named "callback" whose type is "range_callback_t". This + # will be used by our zone callbacks to call the range callback for + # each malloc range. + expr_prefix = ''' +struct $malloc_match { + void *addr; + uintptr_t size; + uintptr_t offset; + uintptr_t type; +}; +''' + + user_init_code_format = ''' +#define MAX_MATCHES %u +typedef int (*compare_callback_t)(const void *a, const void *b); +typedef struct callback_baton_t { + range_callback_t callback; + compare_callback_t compare_callback; + unsigned num_matches; + $malloc_match matches[MAX_MATCHES]; + void *isa; + Class classes[%u]; +} callback_baton_t; +compare_callback_t compare_callback = [](const void *a, const void *b) -> int { + 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; +}; +typedef Class (*class_getSuperclass_type)(void *isa); +range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void { + class_getSuperclass_type class_getSuperclass_impl = (class_getSuperclass_type)class_getSuperclass; + callback_baton_t *info = (callback_baton_t *)baton; + if (sizeof(Class) <= ptr_size) { + Class *curr_class_ptr = (Class *)ptr_addr; + Class *matching_class_ptr = (Class *)bsearch (curr_class_ptr, + (const void *)info->classes, + sizeof(info->classes)/sizeof(Class), + sizeof(Class), + info->compare_callback); + if (matching_class_ptr) { + bool match = false; + if (info->isa) { + Class isa = *curr_class_ptr; + if (info->isa == isa) + match = true; + else { // if (info->objc.match_superclasses) { + Class super = class_getSuperclass_impl(isa); + while (super) { + if (super == info->isa) { + match = true; + break; + } + super = class_getSuperclass_impl(super); + } + } + } + else + match = true; + if (match) { + if (info->num_matches < MAX_MATCHES) { + info->matches[info->num_matches].addr = (void*)ptr_addr; + info->matches[info->num_matches].size = ptr_size; + info->matches[info->num_matches].offset = 0; + info->matches[info->num_matches].type = type; + ++info->num_matches; + } + } + } + } +}; +callback_baton_t baton = { range_callback, compare_callback, 0, {0}, (void *)0x%x, {0} }; +int nc = (int)objc_getClassList(baton.classes, sizeof(baton.classes)/sizeof(Class)); +(void)qsort (baton.classes, sizeof(baton.classes)/sizeof(Class), sizeof(Class), compare_callback);''' + # We must also define a snippet of code to be run that returns + # the result of the expression we run. + # Here we return NULL if our pointer was not found in any malloc blocks, + # and we return the address of the matches array so we can then access + # the matching results + user_return_code = '''if (baton.num_matches < MAX_MATCHES) + baton.matches[baton.num_matches].addr = 0; // Terminate the matches array + baton.matches''' + # Iterate through all of our ObjC class name arguments + for class_name in args: + addr_expr_str = "(void *)[%s class]" % class_name + expr_options = lldb.SBExpressionOptions() + expr_options.SetIgnoreBreakpoints(True); + expr_options.SetTimeoutInMicroSeconds (1*1000*1000) # 1 second timeout + expr_options.SetTryAllThreads (True) + expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus) + expr_sbvalue = frame.EvaluateExpression (addr_expr_str, expr_options) + if expr_sbvalue.error.Success(): + isa = expr_sbvalue.unsigned + if isa: + options.type = 'isa' + result.AppendMessage('Searching for all instances of classes or subclasses of "%s" (isa=0x%x)' % (class_name, isa)) + user_init_code = user_init_code_format % (options.max_matches, num_objc_classes, isa) + expr = get_iterate_memory_expr(options, process, user_init_code, user_return_code) + arg_str_description = 'objective C classes with isa 0x%x' % isa + display_match_results (process, result, options, arg_str_description, expr, True, expr_prefix) + else: + result.AppendMessage('error: Can\'t find isa for an ObjC class named "%s"' % (class_name)) + else: + result.AppendMessage('error: expression error for "%s": %s' % (addr_expr_str, expr_sbvalue.error)) + else: + result.AppendMessage('error: command takes one or more C string arguments'); + +if __name__ == '__main__': + lldb.debugger = lldb.SBDebugger.Create() + +# Make the options so we can generate the help text for the new LLDB +# command line command prior to registering it with LLDB below. This way +# if clients in LLDB type "help malloc_info", they will see the exact same +# output as typing "malloc_info --help". +ptr_refs.__doc__ = get_ptr_refs_options().format_help() +cstr_refs.__doc__ = get_cstr_refs_options().format_help() +malloc_info.__doc__ = get_malloc_info_options().format_help() +objc_refs.__doc__ = get_objc_refs_options().format_help() +lldb.debugger.HandleCommand('command script add -f %s.ptr_refs ptr_refs' % __name__) +lldb.debugger.HandleCommand('command script add -f %s.cstr_refs cstr_refs' % __name__) +lldb.debugger.HandleCommand('command script add -f %s.malloc_info malloc_info' % __name__) +lldb.debugger.HandleCommand('command script add -f %s.find_variable find_variable' % __name__) +# lldb.debugger.HandleCommand('command script add -f %s.heap heap' % package_name) +# lldb.debugger.HandleCommand('command script add -f %s.section_ptr_refs section_ptr_refs' % package_name) +# lldb.debugger.HandleCommand('command script add -f %s.stack_ptr_refs stack_ptr_refs' % package_name) +lldb.debugger.HandleCommand('command script add -f %s.objc_refs objc_refs' % __name__) +print '"malloc_info", "ptr_refs", "cstr_refs", "find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.' + + + + diff --git a/examples/darwin/heap_find/heap/Makefile b/examples/darwin/heap_find/heap/Makefile new file mode 100644 index 0000000000000..0e33dc9f8936b --- /dev/null +++ b/examples/darwin/heap_find/heap/Makefile @@ -0,0 +1,33 @@ +#---------------------------------------------------------------------- +# Fill in the source files to build +#---------------------------------------------------------------------- +# Uncomment line below for debugging shell commands +# SHELL = /bin/sh -x + +#---------------------------------------------------------------------- +# Change any build/tool options needed +#---------------------------------------------------------------------- +ARCH ?= x86_64 +CFLAGS ?=-arch $(ARCH) -gdwarf-2 -O0 +CXX ?= $(shell xcrun -find clang++) +EXE ?= libheap.dylib +DSYM ?= $(EXE).dSYM + +#---------------------------------------------------------------------- +# Compile the executable from all the objects (default rule) with no +# dsym file. +#---------------------------------------------------------------------- +$(EXE) : heap_find.cpp + $(CXX) $(CFLAGS) -install_name "@executable_path/libheap.dylib" -dynamiclib -lobjc heap_find.cpp -o "$(EXE)" + +#---------------------------------------------------------------------- +# Include all of the makefiles for each source file so we don't have +# to manually track all of the prerequisites for each source file. +#---------------------------------------------------------------------- +.PHONY: clean +all: $(EXE) +clean: + rm -rf "$(EXE)" "$(DSYM)" + + + diff --git a/examples/darwin/heap_find/heap/heap_find.cpp b/examples/darwin/heap_find/heap/heap_find.cpp new file mode 100644 index 0000000000000..de896775e4026 --- /dev/null +++ b/examples/darwin/heap_find/heap/heap_find.cpp @@ -0,0 +1,1071 @@ +//===-- 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(); +} |