1522 lines
		
	
	
		
			60 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			1522 lines
		
	
	
		
			60 KiB
		
	
	
	
		
			Python
		
	
	
	
#!/usr/bin/env python3
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# 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 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, void *, unsigned, uintptr_t, uintptr_t);
 | 
						|
'''
 | 
						|
    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, vm_address_t, vm_size_t, void **);
 | 
						|
typedef void (*vm_range_recorder_t)(task_t, void *, unsigned, vm_range_t *, unsigned);
 | 
						|
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;
 | 
						|
kern_return_t malloc_get_all_zones(task_t, memory_reader_t, vm_address_t **, unsigned *);
 | 
						|
memory_reader_t task_peek = [](task_t, vm_address_t remote_address, vm_size_t, 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_exclusions.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_exclusions',
 | 
						|
        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.maxsize
 | 
						|
    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 lldb_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 *lldb_info = ($malloc_stack_history *)baton;
 | 
						|
    if (lldb_info->idx < MAX_HISTORY) {
 | 
						|
        malloc_stack_entry *stack_entry = &(lldb_info->entries[lldb_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 (
 | 
						|
            lldb_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;
 | 
						|
    }
 | 
						|
    ++lldb_info->idx;
 | 
						|
};
 | 
						|
(kern_return_t)__mach_stack_logging_enumerate_records (lldb_info.task, (uint64_t)0x%x, callback, &lldb_info);
 | 
						|
lldb_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 True:
 | 
						|
            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_exclusions:
 | 
						|
                                            for ivar_regex in options.ivar_regex_exclusions:
 | 
						|
                                                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 is 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("Finding a variable with address %#x..." % (var_addr), file=result)
 | 
						|
        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 is 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 *lldb_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] == lldb_info->ptr) {
 | 
						|
            if (lldb_info->num_matches < MAX_MATCHES) {
 | 
						|
                lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
 | 
						|
                lldb_info->matches[lldb_info->num_matches].size = ptr_size;
 | 
						|
                lldb_info->matches[lldb_info->num_matches].offset = idx*sizeof(T);
 | 
						|
                lldb_info->matches[lldb_info->num_matches].type = type;
 | 
						|
                ++lldb_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 is 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 *lldb_info = (callback_baton_t *)baton;
 | 
						|
    if (lldb_info->cstr_len < ptr_size) {
 | 
						|
        const char *begin = (const char *)ptr_addr;
 | 
						|
        const char *end = begin + ptr_size - lldb_info->cstr_len;
 | 
						|
        for (const char *s = begin; s < end; ++s) {
 | 
						|
            if ((int)memcmp(s, lldb_info->cstr, lldb_info->cstr_len) == 0) {
 | 
						|
                if (lldb_info->num_matches < MAX_MATCHES) {
 | 
						|
                    lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
 | 
						|
                    lldb_info->matches[lldb_info->num_matches].size = ptr_size;
 | 
						|
                    lldb_info->matches[lldb_info->num_matches].offset = s - begin;
 | 
						|
                    lldb_info->matches[lldb_info->num_matches].type = type;
 | 
						|
                    ++lldb_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 *lldb_info = (callback_baton_t *)baton;
 | 
						|
    if (lldb_info->num_matches == 0) {
 | 
						|
        uint8_t *p = (uint8_t *)lldb_info->ptr;
 | 
						|
        uint8_t *lo = (uint8_t *)ptr_addr;
 | 
						|
        uint8_t *hi = lo + ptr_size;
 | 
						|
        if (lo <= p && p < hi) {
 | 
						|
            lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
 | 
						|
            lldb_info->matches[lldb_info->num_matches].size = ptr_size;
 | 
						|
            lldb_info->matches[lldb_info->num_matches].offset = p - lo;
 | 
						|
            lldb_info->matches[lldb_info->num_matches].type = type;
 | 
						|
            lldb_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 is 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 *lldb_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 *)lldb_info->classes,
 | 
						|
                                                      sizeof(lldb_info->classes)/sizeof(Class),
 | 
						|
                                                      sizeof(Class),
 | 
						|
                                                      lldb_info->compare_callback);
 | 
						|
        if (matching_class_ptr) {
 | 
						|
            bool match = false;
 | 
						|
            if (lldb_info->isa) {
 | 
						|
                Class isa = *curr_class_ptr;
 | 
						|
                if (lldb_info->isa == isa)
 | 
						|
                    match = true;
 | 
						|
                else { // if (lldb_info->objc.match_superclasses) {
 | 
						|
                    Class super = class_getSuperclass_impl(isa);
 | 
						|
                    while (super) {
 | 
						|
                        if (super == lldb_info->isa) {
 | 
						|
                            match = true;
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                        super = class_getSuperclass_impl(super);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else
 | 
						|
                match = true;
 | 
						|
            if (match) {
 | 
						|
                if (lldb_info->num_matches < MAX_MATCHES) {
 | 
						|
                    lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
 | 
						|
                    lldb_info->matches[lldb_info->num_matches].size = ptr_size;
 | 
						|
                    lldb_info->matches[lldb_info->num_matches].offset = 0;
 | 
						|
                    lldb_info->matches[lldb_info->num_matches].type = type;
 | 
						|
                    ++lldb_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()
 | 
						|
 | 
						|
def __lldb_init_module(debugger, internal_dict):
 | 
						|
    # 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()
 | 
						|
    debugger.HandleCommand(
 | 
						|
        'command script add -f %s.ptr_refs ptr_refs' %
 | 
						|
        __name__)
 | 
						|
    debugger.HandleCommand(
 | 
						|
        'command script add -f %s.cstr_refs cstr_refs' %
 | 
						|
        __name__)
 | 
						|
    debugger.HandleCommand(
 | 
						|
        'command script add -f %s.malloc_info malloc_info' %
 | 
						|
        __name__)
 | 
						|
    debugger.HandleCommand(
 | 
						|
        'command script add -f %s.find_variable find_variable' %
 | 
						|
        __name__)
 | 
						|
    # debugger.HandleCommand('command script add -f %s.section_ptr_refs section_ptr_refs' % package_name)
 | 
						|
    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.')
 |