diff options
author | Greg Clayton <gclayton@apple.com> | 2013-01-30 22:57:34 +0000 |
---|---|---|
committer | Greg Clayton <gclayton@apple.com> | 2013-01-30 22:57:34 +0000 |
commit | 85e62eca660491a5b1a31bfe1e4b0efe7a5b93a5 (patch) | |
tree | fdf4190fc16c21e2c71915e59268ca26cd4e894e | |
parent | 9449ec956ffde44c12a496276a2eb7e464b35b6d (diff) | |
download | bcm5719-llvm-85e62eca660491a5b1a31bfe1e4b0efe7a5b93a5.tar.gz bcm5719-llvm-85e62eca660491a5b1a31bfe1e4b0efe7a5b93a5.zip |
expressions + C++11 lambdas == cooooool!!!
C++11 lambdas that don't capture anything can be used as static callback functions!
Heavily modified this python module to be able to not require a dylib in order to traverse the heap allocations.
Re-implemented the ptr_refs, objc_refs, malloc_info and cstr_refs to use complex expressions that use lambdas to do all static callback function work.
llvm-svn: 173989
-rw-r--r-- | lldb/examples/darwin/heap_find/heap.py | 753 |
1 files changed, 641 insertions, 112 deletions
diff --git a/lldb/examples/darwin/heap_find/heap.py b/lldb/examples/darwin/heap_find/heap.py index 8bd8cd9653b..92dbf67c419 100644 --- a/lldb/examples/darwin/heap_find/heap.py +++ b/lldb/examples/darwin/heap_find/heap.py @@ -21,13 +21,66 @@ import lldb.utils.symbolication g_libheap_dylib_dir = None g_libheap_dylib_dict = dict() -g_verbose = False +g_iterate_malloc_blocks_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 +#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 void (*range_callback_t)(task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_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; +%s +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); + } + }); + } +} +%s +''' def load_dylib(): - if lldb.target: + target = lldb.debugger.GetSelectedTarget() + if target: global g_libheap_dylib_dir global g_libheap_dylib_dict - triple = lldb.target.triple + triple = target.triple if triple in g_libheap_dylib_dict: libheap_dylib_path = g_libheap_dylib_dict[triple] else: @@ -49,14 +102,15 @@ def load_dylib(): return 'error: make failed: %s' % (make_output) if os.path.exists(libheap_dylib_path): libheap_dylib_spec = lldb.SBFileSpec(libheap_dylib_path) - if lldb.target.FindModule(libheap_dylib_spec): + if target.FindModule(libheap_dylib_spec): return None # success, 'libheap.dylib' already loaded - if lldb.process: - state = lldb.process.state + process = target.GetProcess() + if process: + state = process.state if state == lldb.eStateStopped: (libheap_dylib_path) error = lldb.SBError() - image_idx = lldb.process.LoadImage(libheap_dylib_spec, error) + image_idx = process.LoadImage(libheap_dylib_spec, error) if error.Success(): return None else: @@ -74,7 +128,7 @@ def load_dylib(): return 'error: invalid target' debugger.HandleCommand('process load "%s"' % libheap_dylib_path) - if lldb.target.FindModule(libheap_dylib_spec): + if target.FindModule(libheap_dylib_spec): return None # success, 'libheap.dylib' already loaded return 'error: failed to load "%s"' % libheap_dylib_path @@ -131,28 +185,45 @@ def add_common_options(parser): 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=256) parser.add_option('-O', '--offset', type='int', dest='offset', help='the matching data must be at this offset', default=-1) parser.add_option('-V', '--vm-regions', action='store_true', dest='check_vm_regions', help='Also check the VM regions', default=False) -def dump_stack_history_entry(result, stack_history_entry, idx): +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' + else: + type_str = hex(type_flags) + return type_str + +def type_flags_to_description(type_flags, addr, ptr_addr, ptr_size): + 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(%5u) -> %#x' % (ptr_size, ptr_addr) + elif type_flags & 8: + type_str = 'stack @ %#x' % (addr,) + else: + type_str = hex(type_flags) + 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.target - type_str = '' - if type_flags == 0: - type_str = 'free' - else: - if type_flags & 2: - type_str = 'alloc' - elif type_flags & 4: - type_str = 'free' - elif type_flags & 1: - type_str = 'generic' - else: - type_str = hex(type_flags) + 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 @@ -171,28 +242,154 @@ def dump_stack_history_entry(result, stack_history_entry, idx): 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(result, addr, history): +def dump_stack_history_entries(options, result, addr, history): # malloc_stack_entry *get_stack_history_for_address (const void * addr) expr = 'get_stack_history_for_address((void *)0x%x, %u)' % (addr, history) - expr_sbvalue = lldb.frame.EvaluateExpression (expr) + frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame() + expr_sbvalue = frame.EvaluateExpression (expr) if expr_sbvalue.error.Success(): if expr_sbvalue.unsigned: expr_value = lldb.value(expr_sbvalue) idx = 0; stack_history_entry = expr_value[idx] while int(stack_history_entry.address) != 0: - dump_stack_history_entry(result, stack_history_entry, idx) + dump_stack_history_entry(options, result, stack_history_entry, idx) idx = idx + 1 stack_history_entry = expr_value[idx] else: result.AppendMessage('"%s" returned zero' % (expr)) else: result.AppendMessage('error: expression failed "%s" => %s' % (expr, expr_sbvalue.error)) - -def display_match_results (result, options, arg_str_description, expr_sbvalue, print_no_matches = True): +def dump_stack_history_entries2(options, result, addr, history): + # malloc_stack_entry *get_stack_history_for_address (const void * addr) + single_expr = '''typedef int kern_return_t; +#define MAX_FRAMES %u +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; +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_sbvalue = frame.EvaluateExpression (expr) + 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 (result, options, arg_str_description, expr, print_no_matches = True): + frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame() + if not frame: + result.AppendMessage('error: invalid frame') + return 0 + expr_sbvalue = frame.EvaluateExpression (expr) + if options.verbose: + print "expression:" + print expr + print "expression result:" + print expr_sbvalue if expr_sbvalue.error.Success(): if expr_sbvalue.unsigned: match_value = lldb.value(expr_sbvalue) @@ -201,8 +398,8 @@ def display_match_results (result, options, arg_str_description, expr_sbvalue, p while 1: print_entry = True match_entry = match_value[i]; i += 1 - if i >= options.max_matches: - result.AppendMessage('error: the max number of matches (%u) was reached, use the --max-matches option to get more results' % (options.max_matches)) + 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: @@ -215,7 +412,8 @@ def display_match_results (result, options, arg_str_description, expr_sbvalue, p else: match_addr = malloc_addr + offset dynamic_value = match_entry.addr.sbvalue.GetDynamicValue(lldb.eDynamicCanRunTarget) - description = '%#x: ' % (match_addr) + type_flags = int(match_entry.type) + description = '%#16.16x: %s ' % (match_addr, type_flags_to_description(type_flags, match_addr, malloc_addr, malloc_size)) if options.show_size: description += '<%5u> ' % (malloc_size) if options.show_range: @@ -225,15 +423,17 @@ def display_match_results (result, options, arg_str_description, expr_sbvalue, p description += '[%#x - %#x)' % (malloc_addr, malloc_addr + malloc_size) else: if options.type != 'isa': - description += '%#x + %-6u ' % (malloc_addr, offset) + description += ' + %-6u ' % (offset,) derefed_dynamic_value = None if dynamic_value.type.name == 'void *': if options.type == 'pointer' and malloc_size == 4096: error = lldb.SBError() - data = bytearray(lldb.process.ReadMemory(malloc_addr, 16, error)) + process = expr_sbvalue.GetProcess() + target = expr_sbvalue.GetTarget() + data = bytearray(process.ReadMemory(malloc_addr, 16, error)) if data == '\xa1\xa1\xa1\xa1AUTORELEASE!': - ptr_size = lldb.target.addr_size - thread = lldb.process.ReadUnsignedFromMemory (malloc_addr + 16 + ptr_size, ptr_size, error) + 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 @@ -291,13 +491,18 @@ def display_match_results (result, options, arg_str_description, expr_sbvalue, p result.AppendMessage(result_output) if options.memory: cmd_result = lldb.SBCommandReturnObject() - memory_command = "memory read -f %s 0x%x 0x%x" % (options.format, malloc_addr, malloc_addr + malloc_size) + 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(result, malloc_addr, 1) + dump_stack_history_entries2(options, result, malloc_addr, 1) elif options.stack: - dump_stack_history_entries(result, malloc_addr, 0) + dump_stack_history_entries2(options, result, malloc_addr, 0) return i elif print_no_matches: result.AppendMessage('no matches found for %s' % (arg_str_description)) @@ -341,76 +546,249 @@ def heap_search(result, options, arg_str): if options.format == None: options.format = "Y" # 'Y' is "bytes with ASCII" format - display_match_results (result, options, arg_str_description, lldb.frame.EvaluateExpression (expr)) - -def ptr_refs(debugger, command, result, dict): - command_args = shlex.split(command) + display_match_results (result, options, arg_str_description, expr) + +def get_ptr_refs_options (): usage = "usage: %prog [options] <EXPR> [EXPR ...]" - description='''Searches the heap for pointer references 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.''' + 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 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 = lldb.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: - - for data in args: - heap_search (result, options, data) + # 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. + init_expr_format = ''' +struct $malloc_match { + void *addr; + uintptr_t size; + uintptr_t offset; + uintptr_t type; +}; +typedef struct callback_baton_t { + range_callback_t callback; + unsigned num_matches; + $malloc_match matches[%u]; + 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 < sizeof(info->matches)/sizeof($malloc_match)) { + 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 + return_expr = ''' +for (uint32_t i=0; i<NUM_STACKS; ++i) + range_callback (task, &baton, 8, stacks[i].base, stacks[i].size); +baton.matches''' + # Iterate through all of our pointer expressions and display the results + for ptr_expr in args: + init_expr = init_expr_format % (options.max_matches, ptr_expr) + stack_info = get_thread_stack_ranges_struct (process) + if stack_info: + init_expr += stack_info + expr = g_iterate_malloc_blocks_expr % (init_expr, return_expr) + arg_str_description = 'malloc block containing pointer %s' % ptr_expr + display_match_results (result, options, arg_str_description, expr) else: - resultresult.AppendMessage('error: no pointer arguments were given') + result.AppendMessage('error: no pointer arguments were given') -def cstr_refs(debugger, command, result, dict): - command_args = shlex.split(command) +def get_cstr_refs_options(): usage = "usage: %prog [options] <CSTR> [CSTR ...]" - description='''Searches the heap for C string references 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.''' + 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 + frame = lldb.debugger.GetSelectedTarget().GetProcess().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: - - for data in args: - heap_search (result, options, data) + # 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. + init_expr_format = '''struct $malloc_match { + void *addr; + uintptr_t size; + uintptr_t offset; + uintptr_t type; +}; +typedef struct callback_baton_t { + range_callback_t callback; + unsigned num_matches; + $malloc_match matches[%u]; + 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 < sizeof(info->matches)/sizeof($malloc_match)) { + 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 + return_expr = '$malloc_match *result = baton.num_matches ? baton.matches : ($malloc_match *)0; result' + # Iterate through all of our pointer expressions and display the results + for cstr in args: + init_expr = init_expr_format % (options.max_matches, cstr) + expr = g_iterate_malloc_blocks_expr % (init_expr, return_expr) + arg_str_description = 'malloc block containing "%s"' % cstr + display_match_results (result, options, arg_str_description, expr) else: - result.AppendMessage('error: no c string arguments were given to search for'); + result.AppendMessage('error: command takes one or more C string arguments') -def malloc_info(debugger, command, result, dict): - command_args = shlex.split(command) - usage = "usage: %prog [options] <EXPR> [EXPR ...]" - description='''Searches the heap a malloc block that contains the addresses specified as arguments. - 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='cstr_refs',usage=usage) +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 options.type = 'addr' + + init_expr_format = '''struct $malloc_match { + void *addr; + uintptr_t size; + uintptr_t offset; + uintptr_t type; +}; +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[1].addr = 0;''' if args: - for data in args: - heap_search (result, options, data) + frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame() + if frame: + for ptr_expr in args: + init_expr = init_expr_format % (ptr_expr) + expr = g_iterate_malloc_blocks_expr % (init_expr, 'baton.matches') + arg_str_description = 'malloc block that contains %s' % ptr_expr + display_match_results (result, options, arg_str_description, expr) + else: + result.AppendMessage('error: invalid frame') else: - result.AppendMessage('error: no c string arguments were given to search for') + result.AppendMessage('error: command takes one or more pointer expressions') def heap(debugger, command, result, dict): command_args = shlex.split(command) @@ -419,7 +797,7 @@ def heap(debugger, command, result, dict): If programs set the MallocStackLogging=1 in the environment, then stack history is available for any allocations. ''' - parser = optparse.OptionParser(description=description, prog='cstr_refs',usage=usage) + parser = optparse.OptionParser(description=description, prog='heap',usage=usage) add_common_options(parser) try: (options, args) = parser.parse_args(command_args) @@ -431,11 +809,40 @@ def heap(debugger, command, result, dict): else: heap_search (result, options, None) +def get_thread_stack_ranges_struct (process): + 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 +typedef struct thread_stack_t { uint64_t tid, base, size; } thread_stack_t; +thread_stack_t stacks[NUM_STACKS]; +''' % (stack_dicts_len,) + 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 None + def stack_ptr_refs(debugger, command, result, dict): command_args = shlex.split(command) usage = "usage: %prog [options] <EXPR> [EXPR ...]" description='''Searches thread stack contents for pointer values in darwin user space programs.''' - parser = optparse.OptionParser(description=description, prog='section_ptr_refs',usage=usage) + parser = optparse.OptionParser(description=description, prog='stack_ptr_refs',usage=usage) add_common_options(parser) try: (options, args) = parser.parse_args(command_args) @@ -447,7 +854,8 @@ def stack_ptr_refs(debugger, command, result, dict): stack_threads = list() stack_bases = list() stack_sizes = list() - for thread in lldb.process: + process = lldb.debugger.GetSelectedTarget().GetProcess() + for thread in process: min_sp = thread.frame[0].sp max_sp = min_sp for frame in thread.frames: @@ -465,12 +873,13 @@ def stack_ptr_refs(debugger, command, result, dict): if dylid_load_err: result.AppendMessage(dylid_load_err) return + frame = process.GetSelectedThread().GetSelectedFrame() for expr_str in args: for (idx, stack_base) in enumerate(stack_bases): stack_size = stack_sizes[idx] expr = 'find_pointer_in_memory(0x%xllu, %ullu, (void *)%s)' % (stack_base, stack_size, expr_str) arg_str_description = 'thead %s stack containing "%s"' % (stack_threads[idx], expr_str) - num_matches = display_match_results (result, options, arg_str_description, lldb.frame.EvaluateExpression (expr), False) + num_matches = display_match_results (result, options, arg_str_description, expr, False) if num_matches: if num_matches < options.max_matches: options.max_matches = options.max_matches - num_matches @@ -501,7 +910,8 @@ def section_ptr_refs(debugger, command, result, dict): result.AppendMessage('error: at least one section must be specified with the --section option') return - for module in lldb.target.modules: + target = lldb.debugger.GetSelectedTarget() + for module in target.modules: for section_name in options.section_names: section = module.section[section_name] if section: @@ -512,11 +922,12 @@ def section_ptr_refs(debugger, command, result, dict): 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 (result, options, arg_str_description, lldb.frame.EvaluateExpression (expr), False) + num_matches = display_match_results (result, options, arg_str_description, expr, False) if num_matches: if num_matches < options.max_matches: options.max_matches = options.max_matches - num_matches @@ -527,53 +938,171 @@ def section_ptr_refs(debugger, command, result, dict): 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) - usage = "usage: %prog [options] <EXPR> [EXPR ...]" - description='''Find all heap allocations given one or more objective C class names.''' - parser = optparse.OptionParser(description=description, prog='object_refs',usage=usage) - add_common_options(parser) + parser = get_objc_refs_options() try: (options, args) = parser.parse_args(command_args) except: return - dylid_load_err = load_dylib() - if dylid_load_err: - result.AppendMessage(dylid_load_err) - else: - if args: - for class_name in args: - addr_expr_str = "(void *)[%s class]" % class_name - expr_sbvalue = lldb.frame.EvaluateExpression (addr_expr_str) - 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)) - heap_search (result, options, '0x%x' % isa) - else: - result.AppendMessage('error: Can\'t find isa for an ObjC class named "%s"' % (class_name)) + frame = lldb.debugger.GetSelectedTarget().GetProcess().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 + + num_objc_classes_value = frame.EvaluateExpression("(int)objc_getClassList((void *)0, (int)0)") + 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. + init_expr_format = '''struct $malloc_match { + void *addr; + uintptr_t size; + uintptr_t offset; + uintptr_t type; +}; +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[%u]; + 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; +}; +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 (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)class_getSuperclass(isa); + while (super) { + if (super == info->isa) { + match = true; + break; + } + super = (Class)class_getSuperclass(super); + } + } + } + else + match = true; + if (match) { + if (info->num_matches < sizeof(info->matches)/sizeof($malloc_match)) { + 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 + return_expr = '$malloc_match *result = baton.num_matches ? baton.matches : ($malloc_match *)0; result' + # Iterate through all of our ObjC class name arguments + for class_name in args: + addr_expr_str = "(void *)[%s class]" % class_name + expr_sbvalue = frame.EvaluateExpression (addr_expr_str) + 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)) + init_expr = init_expr_format % (options.max_matches, num_objc_classes, isa) + expr = g_iterate_malloc_blocks_expr % (init_expr, return_expr) + arg_str_description = 'objective C classes with isa 0x%x' % isa + display_match_results (result, options, arg_str_description, expr) else: - result.AppendMessage('error: expression error for "%s": %s' % (addr_expr_str, expr_sbvalue.error)) - else: - # Find all objective C objects by not specifying an isa - options.type = 'isa' - heap_search (result, options, '0x0') + 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() # This initializer is being run from LLDB in the embedded command interpreter # Add any commands contained in this module to LLDB -lldb.debugger.HandleCommand('command script add -f lldb.macosx.heap.ptr_refs ptr_refs') -lldb.debugger.HandleCommand('command script add -f lldb.macosx.heap.cstr_refs cstr_refs') -lldb.debugger.HandleCommand('command script add -f lldb.macosx.heap.malloc_info malloc_info') -lldb.debugger.HandleCommand('command script add -f lldb.macosx.heap.heap heap') -lldb.debugger.HandleCommand('command script add -f lldb.macosx.heap.section_ptr_refs section_ptr_refs') -lldb.debugger.HandleCommand('command script add -f lldb.macosx.heap.stack_ptr_refs stack_ptr_refs') -lldb.debugger.HandleCommand('command script add -f lldb.macosx.heap.objc_refs objc_refs') -print '"ptr_refs", "cstr_refs", "malloc_info", "heap", "section_ptr_refs" and "stack_ptr_refs" commands have been installed, use the "--help" options on these commands for detailed help.' +if __package__: + package_name = __package__ + '.' + __name__ +else: + package_name = __name__ + +# 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' % package_name) +lldb.debugger.HandleCommand('command script add -f %s.cstr_refs cstr_refs' % package_name) +lldb.debugger.HandleCommand('command script add -f %s.malloc_info malloc_info' % package_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' % package_name) +print '"malloc_info", "ptr_refs", "cstr_refs", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.' |