3 #----------------------------------------------------------------------
4 # This module is designed to live inside the "lldb" python package
5 # in the "lldb.macosx" package. To use this in the embedded python
6 # interpreter using "lldb" just import it:
8 # (lldb) script import lldb.macosx.heap
9 #----------------------------------------------------------------------
21 import lldb.utils.symbolication
23 g_libheap_dylib_dir = None
24 g_libheap_dylib_dict = dict()
26 def get_iterate_memory_expr(options, process, user_init_code, user_return_code):
28 typedef unsigned natural_t;
29 typedef uintptr_t vm_size_t;
30 typedef uintptr_t vm_address_t;
31 typedef natural_t task_t;
32 typedef int kern_return_t;
33 #define KERN_SUCCESS 0
34 typedef void (*range_callback_t)(task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size);
36 if options.search_vm_regions:
38 typedef int vm_prot_t;
39 typedef unsigned int vm_inherit_t;
40 typedef unsigned long long memory_object_offset_t;
41 typedef unsigned int boolean_t;
42 typedef int vm_behavior_t;
43 typedef uint32_t vm32_object_id_t;
44 typedef natural_t mach_msg_type_number_t;
45 typedef uint64_t mach_vm_address_t;
46 typedef uint64_t mach_vm_offset_t;
47 typedef uint64_t mach_vm_size_t;
48 typedef uint64_t vm_map_offset_t;
49 typedef uint64_t vm_map_address_t;
50 typedef uint64_t vm_map_size_t;
51 #define VM_PROT_NONE ((vm_prot_t) 0x00)
52 #define VM_PROT_READ ((vm_prot_t) 0x01)
53 #define VM_PROT_WRITE ((vm_prot_t) 0x02)
54 #define VM_PROT_EXECUTE ((vm_prot_t) 0x04)
55 typedef struct vm_region_submap_short_info_data_64_t {
57 vm_prot_t max_protection;
58 vm_inherit_t inheritance;
59 memory_object_offset_t offset; // offset into object/map
60 unsigned int user_tag; // user tag on map entry
61 unsigned int ref_count; // obj/map mappers, etc
62 unsigned short shadow_depth; // only for obj
63 unsigned char external_pager; // only for obj
64 unsigned char share_mode; // see enumeration
65 boolean_t is_submap; // submap vs obj
66 vm_behavior_t behavior; // access behavior hint
67 vm32_object_id_t object_id; // obj/map name, not a handle
68 unsigned short user_wired_count;
69 } vm_region_submap_short_info_data_64_t;
70 #define VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 ((mach_msg_type_number_t)(sizeof(vm_region_submap_short_info_data_64_t)/sizeof(int)))''';
72 expr += user_init_code;
74 task_t task = (task_t)mach_task_self();
75 mach_vm_address_t vm_region_base_addr;
76 mach_vm_size_t vm_region_size;
77 natural_t vm_region_depth;
78 vm_region_submap_short_info_data_64_t vm_region_info;
80 for (vm_region_base_addr = 0, vm_region_size = 1; vm_region_size != 0; vm_region_base_addr += vm_region_size)
82 mach_msg_type_number_t vm_region_info_size = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
83 err = (kern_return_t)mach_vm_region_recurse (task,
88 &vm_region_info_size);
91 // Check all read + write regions. This will cover the thread stacks
92 // and any regions of memory like __DATA segments, that might contain
93 // data we are looking for
94 if (vm_region_info.protection & VM_PROT_WRITE &&
95 vm_region_info.protection & VM_PROT_READ)
105 if options.search_stack:
106 expr += get_thread_stack_ranges_struct (process)
107 if options.search_segments:
108 expr += get_sections_ranges_struct (process)
110 expr += user_init_code
111 if options.search_heap:
113 #define MALLOC_PTR_IN_USE_RANGE_TYPE 1
114 typedef struct vm_range_t {
115 vm_address_t address;
118 typedef kern_return_t (*memory_reader_t)(task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory);
119 typedef void (*vm_range_recorder_t)(task_t task, void *baton, unsigned type, vm_range_t *range, unsigned size);
120 typedef struct malloc_introspection_t {
121 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 */
122 } malloc_introspection_t;
123 typedef struct malloc_zone_t {
125 struct malloc_introspection_t *introspect;
127 memory_reader_t task_peek = [](task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory) -> kern_return_t {
128 *local_memory = (void*) remote_address;
131 vm_address_t *zones = 0;
132 unsigned int num_zones = 0;task_t task = 0;
133 kern_return_t err = (kern_return_t)malloc_get_all_zones (task, task_peek, &zones, &num_zones);
134 if (KERN_SUCCESS == err)
136 for (unsigned int i=0; i<num_zones; ++i)
138 const malloc_zone_t *zone = (const malloc_zone_t *)zones[i];
139 if (zone && zone->introspect)
140 zone->introspect->enumerator (task,
142 MALLOC_PTR_IN_USE_RANGE_TYPE,
145 [] (task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned size) -> void
147 range_callback_t callback = ((callback_baton_t *)baton)->callback;
148 for (unsigned i=0; i<size; ++i)
150 callback (task, baton, type, ranges[i].address, ranges[i].size);
156 if options.search_stack:
159 // Call the callback for the thread stack ranges
160 for (uint32_t i=0; i<NUM_STACKS; ++i) {
161 range_callback(task, &baton, 8, stacks[i].base, stacks[i].size);
162 if (STACK_RED_ZONE_SIZE > 0) {
163 range_callback(task, &baton, 16, stacks[i].base - STACK_RED_ZONE_SIZE, STACK_RED_ZONE_SIZE);
168 if options.search_segments:
171 // Call the callback for all segments
172 for (uint32_t i=0; i<NUM_SEGMENTS; ++i)
173 range_callback(task, &baton, 32, segments[i].base, segments[i].size);
177 expr += "\n%s" % (user_return_code,)
181 def get_member_types_for_offset(value_type, offset, member_list):
182 member = value_type.GetFieldAtIndex(0)
185 if member.GetOffsetInBytes() <= offset:
186 for field_idx in range (value_type.GetNumberOfFields()):
187 member = value_type.GetFieldAtIndex(field_idx)
188 member_byte_offset = member.GetOffsetInBytes()
189 member_end_byte_offset = member_byte_offset + member.type.size
190 if member_byte_offset <= offset and offset < member_end_byte_offset:
191 member_list.append(member)
192 get_member_types_for_offset (member.type, offset - member_byte_offset, member_list)
199 for field_idx in range (value_type.GetNumberOfDirectBaseClasses()):
200 member = value_type.GetDirectBaseClassAtIndex(field_idx)
201 member_byte_offset = member.GetOffsetInBytes()
202 member_end_byte_offset = member_byte_offset + member.type.size
203 if member_byte_offset <= offset and offset < member_end_byte_offset:
204 member_list.append(member)
205 get_member_types_for_offset (member.type, offset - member_byte_offset, member_list)
207 for field_idx in range (value_type.GetNumberOfVirtualBaseClasses()):
208 member = value_type.GetVirtualBaseClassAtIndex(field_idx)
209 member_byte_offset = member.GetOffsetInBytes()
210 member_end_byte_offset = member_byte_offset + member.type.size
211 if member_byte_offset <= offset and offset < member_end_byte_offset:
212 member_list.append(member)
213 get_member_types_for_offset (member.type, offset - member_byte_offset, member_list)
216 def append_regex_callback(option, opt, value, parser):
218 ivar_regex = re.compile(value)
219 parser.values.ivar_regex_blacklist.append(ivar_regex)
221 print 'error: an exception was thrown when compiling the ivar regular expression for "%s"' % value
223 def add_common_options(parser):
224 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
225 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)
226 parser.add_option('-o', '--po', action='store_true', dest='print_object_description', help='print the object descriptions for any matches', default=False)
227 parser.add_option('-z', '--size', action='store_true', dest='show_size', help='print the allocation size in bytes', default=False)
228 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)
229 parser.add_option('-m', '--memory', action='store_true', dest='memory', help='dump the memory for each matching block', default=False)
230 parser.add_option('-f', '--format', type='string', dest='format', help='the format to use when dumping memory if --memory is specified', default=None)
231 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')
232 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)
233 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)
234 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)
235 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)
236 parser.add_option('-M', '--max-matches', type='int', dest='max_matches', help='the maximum number of matches to print', default=32)
237 parser.add_option('-O', '--offset', type='int', dest='offset', help='the matching data must be at this offset', default=-1)
238 parser.add_option('--ignore-stack', action='store_false', dest='search_stack', help="Don't search the stack when enumerating memory", default=True)
239 parser.add_option('--ignore-heap', action='store_false', dest='search_heap', help="Don't search the heap allocations when enumerating memory", default=True)
240 parser.add_option('--ignore-segments', action='store_false', dest='search_segments', help="Don't search readable executable segments enumerating memory", default=True)
241 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)
243 def type_flags_to_string(type_flags):
254 elif type_flags & 16:
255 type_str = 'stack (red zone)'
256 elif type_flags & 32:
258 elif type_flags & 64:
259 type_str = 'vm_region'
261 type_str = hex(type_flags)
264 def find_variable_containing_address(verbose, frame, match_addr):
265 variables = frame.GetVariables(True,True,True,True)
267 for var in variables:
268 var_addr = var.GetLoadAddress()
269 if var_addr != lldb.LLDB_INVALID_ADDRESS:
270 byte_size = var.GetType().GetByteSize()
272 print 'frame #%u: [%#x - %#x) %s' % (frame.GetFrameID(), var.load_addr, var.load_addr + byte_size, var.name)
273 if var_addr == match_addr:
278 if byte_size > 0 and var_addr <= match_addr and match_addr < (var_addr + byte_size):
284 def find_frame_for_stack_address(process, addr):
285 closest_delta = sys.maxint
287 #print 'find_frame_for_stack_address(%#x)' % (addr)
288 for thread in process:
289 prev_sp = lldb.LLDB_INVALID_ADDRESS
292 #print 'frame #%u: cfa = %#x' % (frame.GetFrameID(), cfa)
295 #print '%#x < %#x, delta = %i' % (addr, cfa, delta)
296 if delta < closest_delta:
298 closest_delta = delta
299 closest_frame = frame
301 # print 'delta >= closest_delta'
304 def type_flags_to_description(process, type_flags, ptr_addr, ptr_size, offset, match_addr):
306 if type_flags == 0 or type_flags & 4:
307 type_str = 'free(%#x)' % (ptr_addr,)
308 elif type_flags & 2 or type_flags & 1:
309 type_str = 'malloc(%6u) -> %#x' % (ptr_size, ptr_addr)
313 frame = find_frame_for_stack_address(process, match_addr)
315 type_str += ' in frame #%u of thread #%u: tid %#x' % (frame.GetFrameID(), frame.GetThread().GetIndexID(), frame.GetThread().GetThreadID())
316 variables = frame.GetVariables(True,True,True,True)
318 for var in variables:
319 var_addr = var.GetLoadAddress()
320 if var_addr != lldb.LLDB_INVALID_ADDRESS:
321 #print 'variable "%s" @ %#x (%#x)' % (var.name, var.load_addr, match_addr)
322 if var_addr == match_addr:
326 byte_size = var.GetType().GetByteSize()
327 if byte_size > 0 and var_addr <= match_addr and match_addr < (var_addr + byte_size):
331 type_str += ' in variable at %#x:\n %s' % (matching_var.GetLoadAddress(), matching_var)
332 elif type_flags & 16:
333 type_str = 'stack (red zone)'
334 elif type_flags & 32:
335 sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset)
336 type_str = 'segment [%#x - %#x), %s + %u, %s' % (ptr_addr, ptr_addr + ptr_size, sb_addr.section.name, sb_addr.offset, sb_addr)
337 elif type_flags & 64:
338 sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset)
339 type_str = 'vm_region [%#x - %#x), %s + %u, %s' % (ptr_addr, ptr_addr + ptr_size, sb_addr.section.name, sb_addr.offset, sb_addr)
341 type_str = '%#x' % (ptr_addr,)
343 if show_offset and offset != 0:
344 type_str += ' + %-6u' % (offset,)
347 def dump_stack_history_entry(options, result, stack_history_entry, idx):
348 address = int(stack_history_entry.address)
350 type_flags = int(stack_history_entry.type_flags)
351 symbolicator = lldb.utils.symbolication.Symbolicator()
352 symbolicator.target = lldb.debugger.GetSelectedTarget()
353 type_str = type_flags_to_string(type_flags)
354 result.AppendMessage('stack[%u]: addr = 0x%x, type=%s, frames:' % (idx, address, type_str))
357 pc = int(stack_history_entry.frames[idx])
360 frames = symbolicator.symbolicate(pc)
363 result.AppendMessage(' [%u] %s' % (frame_idx, frame))
366 result.AppendMessage(' [%u] 0x%x' % (frame_idx, pc))
369 pc = int(stack_history_entry.frames[idx])
372 if idx >= options.max_frames:
373 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))
375 result.AppendMessage('')
377 def dump_stack_history_entries(options, result, addr, history):
378 # malloc_stack_entry *get_stack_history_for_address (const void * addr)
380 typedef int kern_return_t;
381 typedef struct $malloc_stack_entry {
386 uint64_t frames[512];
388 } $malloc_stack_entry;
391 #define MAX_FRAMES %u
392 typedef unsigned task_t;
393 $malloc_stack_entry stack;
394 stack.address = 0x%x;
395 stack.type_flags = 2;
396 stack.num_frames = 0;
398 uint32_t max_stack_frames = MAX_FRAMES;
399 stack.err = (kern_return_t)__mach_stack_logging_get_frames (
400 (task_t)mach_task_self(),
405 if (stack.num_frames < MAX_FRAMES)
406 stack.frames[stack.num_frames] = 0;
408 stack.frames[MAX_FRAMES-1] = 0;
409 stack''' % (options.max_frames, addr);
412 typedef int kern_return_t;
413 typedef unsigned task_t;
414 #define MAX_FRAMES %u
415 #define MAX_HISTORY %u
416 typedef struct mach_stack_logging_record_t {
418 uint64_t stack_identifier;
421 } mach_stack_logging_record_t;
422 typedef void (*enumerate_callback_t)(mach_stack_logging_record_t, void *);
423 typedef struct malloc_stack_entry {
428 uint64_t frames[MAX_FRAMES];
429 kern_return_t frames_err;
430 } malloc_stack_entry;
431 typedef struct $malloc_stack_history {
434 malloc_stack_entry entries[MAX_HISTORY];
435 } $malloc_stack_history;
436 $malloc_stack_history info = { (task_t)mach_task_self(), 0 };
437 uint32_t max_stack_frames = MAX_FRAMES;
438 enumerate_callback_t callback = [] (mach_stack_logging_record_t stack_record, void *baton) -> void {
439 $malloc_stack_history *info = ($malloc_stack_history *)baton;
440 if (info->idx < MAX_HISTORY) {
441 malloc_stack_entry *stack_entry = &(info->entries[info->idx]);
442 stack_entry->address = stack_record.address;
443 stack_entry->type_flags = stack_record.type_flags;
444 stack_entry->argument = stack_record.argument;
445 stack_entry->num_frames = 0;
446 stack_entry->frames[0] = 0;
447 stack_entry->frames_err = (kern_return_t)__mach_stack_logging_frames_for_uniqued_stack (
449 stack_record.stack_identifier,
451 (uint32_t)MAX_FRAMES,
452 &stack_entry->num_frames);
453 // Terminate the frames with zero if there is room
454 if (stack_entry->num_frames < MAX_FRAMES)
455 stack_entry->frames[stack_entry->num_frames] = 0;
459 (kern_return_t)__mach_stack_logging_enumerate_records (info.task, (uint64_t)0x%x, callback, &info);
460 info''' % (options.max_frames, options.max_history, addr);
462 frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
467 expr_options = lldb.SBExpressionOptions()
468 expr_options.SetIgnoreBreakpoints(True);
469 expr_options.SetTimeoutInMicroSeconds (5*1000*1000) # 5 second timeout
470 expr_options.SetTryAllThreads (True)
471 expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
472 expr_options.SetPrefix(expr_prefix)
473 expr_sbvalue = frame.EvaluateExpression (expr, expr_options)
477 print "expression result:"
479 if expr_sbvalue.error.Success():
481 malloc_stack_history = lldb.value(expr_sbvalue)
482 num_stacks = int(malloc_stack_history.idx)
483 if num_stacks <= options.max_history:
486 i_max = options.max_history
487 for i in range(i_max):
488 stack_history_entry = malloc_stack_history.entries[i]
489 dump_stack_history_entry(options, result, stack_history_entry, i)
490 if num_stacks > options.max_history:
491 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))
493 stack_history_entry = lldb.value(expr_sbvalue)
494 dump_stack_history_entry(options, result, stack_history_entry, 0)
497 result.AppendMessage('error: expression failed "%s" => %s' % (expr, expr_sbvalue.error))
500 def display_match_results (process, result, options, arg_str_description, expr, print_no_matches, expr_prefix = None):
501 frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
503 result.AppendMessage('error: invalid frame')
505 expr_options = lldb.SBExpressionOptions()
506 expr_options.SetIgnoreBreakpoints(True);
507 expr_options.SetFetchDynamicValue(lldb.eNoDynamicValues);
508 expr_options.SetTimeoutInMicroSeconds (30*1000*1000) # 30 second timeout
509 expr_options.SetTryAllThreads (False)
510 expr_options.SetLanguage (lldb.eLanguageTypeObjC_plus_plus)
512 expr_options.SetPrefix (expr_prefix)
513 expr_sbvalue = frame.EvaluateExpression (expr, expr_options)
517 print "expression result:"
519 if expr_sbvalue.error.Success():
520 match_value = lldb.value(expr_sbvalue)
525 match_entry = match_value[i]; i += 1
526 if i > options.max_matches:
527 result.AppendMessage('warning: the max number of matches (%u) was reached, use the --max-matches option to get more results' % (options.max_matches))
529 malloc_addr = match_entry.addr.sbvalue.unsigned
532 malloc_size = int(match_entry.size)
533 offset = int(match_entry.offset)
535 if options.offset >= 0 and options.offset != offset:
538 match_addr = malloc_addr + offset
539 type_flags = int(match_entry.type)
540 #result.AppendMessage (hex(malloc_addr + offset))
542 search_stack_old = options.search_stack
543 search_segments_old = options.search_segments
544 search_heap_old = options.search_heap
545 search_vm_regions = options.search_vm_regions
546 options.search_stack = True
547 options.search_segments = True
548 options.search_heap = True
549 options.search_vm_regions = False
550 if malloc_info_impl (lldb.debugger, result, options, [hex(malloc_addr + offset)]):
552 options.search_stack = search_stack_old
553 options.search_segments = search_segments_old
554 options.search_heap = search_heap_old
555 options.search_vm_regions = search_vm_regions
557 description = '%#16.16x: %s' % (match_addr, type_flags_to_description(process, type_flags, malloc_addr, malloc_size, offset, match_addr))
558 if options.show_size:
559 description += ' <%5u>' % (malloc_size)
560 if options.show_range:
561 description += ' [%#x - %#x)' % (malloc_addr, malloc_addr + malloc_size)
562 derefed_dynamic_value = None
563 dynamic_value = match_entry.addr.sbvalue.GetDynamicValue(lldb.eDynamicCanRunTarget)
564 if dynamic_value.type.name == 'void *':
565 if options.type == 'pointer' and malloc_size == 4096:
566 error = lldb.SBError()
567 process = expr_sbvalue.GetProcess()
568 target = expr_sbvalue.GetTarget()
569 data = bytearray(process.ReadMemory(malloc_addr, 16, error))
570 if data == '\xa1\xa1\xa1\xa1AUTORELEASE!':
571 ptr_size = target.addr_size
572 thread = process.ReadUnsignedFromMemory (malloc_addr + 16 + ptr_size, ptr_size, error)
574 # 12 bytes 'AUTORELEASE!'
575 # ptr bytes autorelease insertion point
576 # ptr bytes pthread_t
577 # ptr bytes next colder page
578 # ptr bytes next hotter page
579 # 4 bytes this page's depth in the list
580 # 4 bytes high-water mark
581 description += ' AUTORELEASE! for pthread_t %#x' % (thread)
583 # description += 'malloc(%u)' % (malloc_size)
585 # description += 'malloc(%u)' % (malloc_size)
587 derefed_dynamic_value = dynamic_value.deref
588 if derefed_dynamic_value:
589 derefed_dynamic_type = derefed_dynamic_value.type
590 derefed_dynamic_type_size = derefed_dynamic_type.size
591 derefed_dynamic_type_name = derefed_dynamic_type.name
593 description += derefed_dynamic_type_name
594 if offset < derefed_dynamic_type_size:
595 member_list = list();
596 get_member_types_for_offset (derefed_dynamic_type, offset, member_list)
599 for member in member_list:
600 member_name = member.name
604 member_path += member_name
606 if options.ivar_regex_blacklist:
607 for ivar_regex in options.ivar_regex_blacklist:
608 if ivar_regex.match(member_path):
610 description += '.%s' % (member_path)
612 description += '%u bytes after %s' % (offset - derefed_dynamic_type_size, derefed_dynamic_type_name)
614 # strip the "*" from the end of the name since we were unable to dereference this
615 description += dynamic_value.type.name[0:-1]
620 result_output += description
621 if options.print_type and derefed_dynamic_value:
622 result_output += ' %s' % (derefed_dynamic_value)
623 if options.print_object_description and dynamic_value:
624 desc = dynamic_value.GetObjectDescription()
626 result_output += '\n%s' % (desc)
628 result.AppendMessage(result_output)
630 cmd_result = lldb.SBCommandReturnObject()
631 if options.format == None:
632 memory_command = "memory read --force 0x%x 0x%x" % (malloc_addr, malloc_addr + malloc_size)
634 memory_command = "memory read --force -f %s 0x%x 0x%x" % (options.format, malloc_addr, malloc_addr + malloc_size)
636 result.AppendMessage(memory_command)
637 lldb.debugger.GetCommandInterpreter().HandleCommand(memory_command, cmd_result)
638 result.AppendMessage(cmd_result.GetOutput())
639 if options.stack_history:
640 dump_stack_history_entries(options, result, malloc_addr, 1)
642 dump_stack_history_entries(options, result, malloc_addr, 0)
645 result.AppendMessage(str(expr_sbvalue.error))
648 def get_ptr_refs_options ():
649 usage = "usage: %prog [options] <EXPR> [EXPR ...]"
650 description='''Searches all allocations on the heap for pointer values on
651 darwin user space programs. Any matches that were found will dump the malloc
652 blocks that contain the pointers and might be able to print what kind of
653 objects the pointers are contained in using dynamic type information in the
655 parser = optparse.OptionParser(description=description, prog='ptr_refs',usage=usage)
656 add_common_options(parser)
659 def find_variable(debugger, command, result, dict):
660 usage = "usage: %prog [options] <ADDR> [ADDR ...]"
661 description='''Searches for a local variable in all frames that contains a hex ADDR.'''
662 command_args = shlex.split(command)
663 parser = optparse.OptionParser(description=description, prog='find_variable',usage=usage)
664 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
666 (options, args) = parser.parse_args(command_args)
670 process = debugger.GetSelectedTarget().GetProcess()
672 result.AppendMessage('error: invalid process')
676 var_addr = int(arg, 16)
677 print >>result, "Finding a variable with address %#x..." % (var_addr)
679 for thread in process:
681 var = find_variable_containing_address(options.verbose, frame, var_addr)
689 def ptr_refs(debugger, command, result, dict):
690 command_args = shlex.split(command)
691 parser = get_ptr_refs_options()
693 (options, args) = parser.parse_args(command_args)
697 process = debugger.GetSelectedTarget().GetProcess()
699 result.AppendMessage('error: invalid process')
701 frame = process.GetSelectedThread().GetSelectedFrame()
703 result.AppendMessage('error: invalid frame')
706 options.type = 'pointer'
707 if options.format == None:
708 options.format = "A" # 'A' is "address" format
711 # When we initialize the expression, we must define any types that
712 # we will need when looking at every allocation. We must also define
713 # a type named callback_baton_t and make an instance named "baton"
714 # and initialize it how ever we want to. The address of "baton" will
715 # be passed into our range callback. callback_baton_t must contain
716 # a member named "callback" whose type is "range_callback_t". This
717 # will be used by our zone callbacks to call the range callback for
720 struct $malloc_match {
727 user_init_code_format = '''
728 #define MAX_MATCHES %u
729 typedef struct callback_baton_t {
730 range_callback_t callback;
731 unsigned num_matches;
732 $malloc_match matches[MAX_MATCHES];
735 range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
736 callback_baton_t *info = (callback_baton_t *)baton;
738 const unsigned size = sizeof(T);
739 T *array = (T*)ptr_addr;
740 for (unsigned idx = 0; ((idx + 1) * sizeof(T)) <= ptr_size; ++idx) {
741 if (array[idx] == info->ptr) {
742 if (info->num_matches < MAX_MATCHES) {
743 info->matches[info->num_matches].addr = (void*)ptr_addr;
744 info->matches[info->num_matches].size = ptr_size;
745 info->matches[info->num_matches].offset = idx*sizeof(T);
746 info->matches[info->num_matches].type = type;
752 callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
754 # We must also define a snippet of code to be run that returns
755 # the result of the expression we run.
756 # Here we return NULL if our pointer was not found in any malloc blocks,
757 # and we return the address of the matches array so we can then access
758 # the matching results
759 user_return_code = '''if (baton.num_matches < MAX_MATCHES)
760 baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
762 # Iterate through all of our pointer expressions and display the results
763 for ptr_expr in args:
764 user_init_code = user_init_code_format % (options.max_matches, ptr_expr)
765 expr = get_iterate_memory_expr(options, process, user_init_code, user_return_code)
766 arg_str_description = 'malloc block containing pointer %s' % ptr_expr
767 display_match_results (process, result, options, arg_str_description, expr, True, expr_prefix)
769 result.AppendMessage('error: no pointer arguments were given')
771 def get_cstr_refs_options():
772 usage = "usage: %prog [options] <CSTR> [CSTR ...]"
773 description='''Searches all allocations on the heap for C string values on
774 darwin user space programs. Any matches that were found will dump the malloc
775 blocks that contain the C strings and might be able to print what kind of
776 objects the pointers are contained in using dynamic type information in the
778 parser = optparse.OptionParser(description=description, prog='cstr_refs',usage=usage)
779 add_common_options(parser)
782 def cstr_refs(debugger, command, result, dict):
783 command_args = shlex.split(command)
784 parser = get_cstr_refs_options();
786 (options, args) = parser.parse_args(command_args)
790 process = debugger.GetSelectedTarget().GetProcess()
792 result.AppendMessage('error: invalid process')
794 frame = process.GetSelectedThread().GetSelectedFrame()
796 result.AppendMessage('error: invalid frame')
800 options.type = 'cstr'
801 if options.format == None:
802 options.format = "Y" # 'Y' is "bytes with ASCII" format
805 # When we initialize the expression, we must define any types that
806 # we will need when looking at every allocation. We must also define
807 # a type named callback_baton_t and make an instance named "baton"
808 # and initialize it how ever we want to. The address of "baton" will
809 # be passed into our range callback. callback_baton_t must contain
810 # a member named "callback" whose type is "range_callback_t". This
811 # will be used by our zone callbacks to call the range callback for
814 struct $malloc_match {
821 user_init_code_format = '''
822 #define MAX_MATCHES %u
823 typedef struct callback_baton_t {
824 range_callback_t callback;
825 unsigned num_matches;
826 $malloc_match matches[MAX_MATCHES];
830 range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
831 callback_baton_t *info = (callback_baton_t *)baton;
832 if (info->cstr_len < ptr_size) {
833 const char *begin = (const char *)ptr_addr;
834 const char *end = begin + ptr_size - info->cstr_len;
835 for (const char *s = begin; s < end; ++s) {
836 if ((int)memcmp(s, info->cstr, info->cstr_len) == 0) {
837 if (info->num_matches < MAX_MATCHES) {
838 info->matches[info->num_matches].addr = (void*)ptr_addr;
839 info->matches[info->num_matches].size = ptr_size;
840 info->matches[info->num_matches].offset = s - begin;
841 info->matches[info->num_matches].type = type;
848 const char *cstr = "%s";
849 callback_baton_t baton = { range_callback, 0, {0}, cstr, (unsigned)strlen(cstr) };'''
850 # We must also define a snippet of code to be run that returns
851 # the result of the expression we run.
852 # Here we return NULL if our pointer was not found in any malloc blocks,
853 # and we return the address of the matches array so we can then access
854 # the matching results
855 user_return_code = '''if (baton.num_matches < MAX_MATCHES)
856 baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
858 # Iterate through all of our pointer expressions and display the results
860 user_init_code = user_init_code_format % (options.max_matches, cstr)
861 expr = get_iterate_memory_expr(options, process, user_init_code, user_return_code)
862 arg_str_description = 'malloc block containing "%s"' % cstr
863 display_match_results (process, result, options, arg_str_description, expr, True, expr_prefix)
865 result.AppendMessage('error: command takes one or more C string arguments')
868 def get_malloc_info_options():
869 usage = "usage: %prog [options] <EXPR> [EXPR ...]"
870 description='''Searches the heap a malloc block that contains the addresses
871 specified as one or more address expressions. Any matches that were found will
872 dump the malloc blocks that match or contain the specified address. The matching
873 blocks might be able to show what kind of objects they are using dynamic type
874 information in the program.'''
875 parser = optparse.OptionParser(description=description, prog='malloc_info',usage=usage)
876 add_common_options(parser)
879 def malloc_info(debugger, command, result, dict):
880 command_args = shlex.split(command)
881 parser = get_malloc_info_options()
883 (options, args) = parser.parse_args(command_args)
886 malloc_info_impl (debugger, result, options, args)
888 def malloc_info_impl (debugger, result, options, args):
889 # We are specifically looking for something on the heap only
890 options.type = 'malloc_info'
892 process = debugger.GetSelectedTarget().GetProcess()
894 result.AppendMessage('error: invalid process')
896 frame = process.GetSelectedThread().GetSelectedFrame()
898 result.AppendMessage('error: invalid frame')
901 struct $malloc_match {
909 user_init_code_format = '''
910 typedef struct callback_baton_t {
911 range_callback_t callback;
912 unsigned num_matches;
913 $malloc_match matches[2]; // Two items so they can be NULL terminated
916 range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
917 callback_baton_t *info = (callback_baton_t *)baton;
918 if (info->num_matches == 0) {
919 uint8_t *p = (uint8_t *)info->ptr;
920 uint8_t *lo = (uint8_t *)ptr_addr;
921 uint8_t *hi = lo + ptr_size;
922 if (lo <= p && p < hi) {
923 info->matches[info->num_matches].addr = (void*)ptr_addr;
924 info->matches[info->num_matches].size = ptr_size;
925 info->matches[info->num_matches].offset = p - lo;
926 info->matches[info->num_matches].type = type;
927 info->num_matches = 1;
931 callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
932 baton.matches[0].addr = 0;
933 baton.matches[1].addr = 0;'''
936 for ptr_expr in args:
937 user_init_code = user_init_code_format % (ptr_expr)
938 expr = get_iterate_memory_expr(options, process, user_init_code, 'baton.matches')
939 arg_str_description = 'malloc block that contains %s' % ptr_expr
940 total_matches += display_match_results (process, result, options, arg_str_description, expr, True, expr_prefix)
943 result.AppendMessage('error: command takes one or more pointer expressions')
946 def get_thread_stack_ranges_struct (process):
947 '''Create code that defines a structure that represents threads stack bounds
948 for all threads. It returns a static sized array initialized with all of
949 the tid, base, size structs for all the threads.'''
953 for thread in process:
954 min_sp = thread.frame[0].sp
956 for frame in thread.frames:
958 if sp < min_sp: min_sp = sp
959 if sp > max_sp: max_sp = sp
961 stack_dicts.append ({ 'tid' : thread.GetThreadID(), 'base' : min_sp , 'size' : max_sp-min_sp, 'index' : i })
963 stack_dicts_len = len(stack_dicts)
964 if stack_dicts_len > 0:
966 #define NUM_STACKS %u
967 #define STACK_RED_ZONE_SIZE %u
968 typedef struct thread_stack_t { uint64_t tid, base, size; } thread_stack_t;
969 thread_stack_t stacks[NUM_STACKS];''' % (stack_dicts_len, process.target.GetStackRedZoneSize())
970 for stack_dict in stack_dicts:
972 stacks[%(index)u].tid = 0x%(tid)x;
973 stacks[%(index)u].base = 0x%(base)x;
974 stacks[%(index)u].size = 0x%(size)x;''' % stack_dict
979 def get_sections_ranges_struct (process):
980 '''Create code that defines a structure that represents all segments that
981 can contain data for all images in "target". It returns a static sized
982 array initialized with all of base, size structs for all the threads.'''
983 target = process.target
984 segment_dicts = list()
985 for (module_idx, module) in enumerate(target.modules):
986 for sect_idx in range(module.GetNumSections()):
987 section = module.GetSectionAtIndex(sect_idx)
991 if name != '__TEXT' and name != '__LINKEDIT' and name != '__PAGEZERO':
992 base = section.GetLoadAddress(target)
993 size = section.GetByteSize()
994 if base != lldb.LLDB_INVALID_ADDRESS and size > 0:
995 segment_dicts.append ({ 'base' : base, 'size' : size })
996 segment_dicts_len = len(segment_dicts)
997 if segment_dicts_len > 0:
999 #define NUM_SEGMENTS %u
1000 typedef struct segment_range_t { uint64_t base; uint32_t size; } segment_range_t;
1001 segment_range_t segments[NUM_SEGMENTS];''' % (segment_dicts_len,)
1002 for (idx, segment_dict) in enumerate(segment_dicts):
1003 segment_dict['index'] = idx
1005 segments[%(index)u].base = 0x%(base)x;
1006 segments[%(index)u].size = 0x%(size)x;''' % segment_dict
1011 def section_ptr_refs(debugger, command, result, dict):
1012 command_args = shlex.split(command)
1013 usage = "usage: %prog [options] <EXPR> [EXPR ...]"
1014 description='''Searches section contents for pointer values in darwin user space programs.'''
1015 parser = optparse.OptionParser(description=description, prog='section_ptr_refs',usage=usage)
1016 add_common_options(parser)
1017 parser.add_option('--section', action='append', type='string', dest='section_names', help='section name to search', default=list())
1019 (options, args) = parser.parse_args(command_args)
1023 options.type = 'pointer'
1026 section_modules = list()
1027 if not options.section_names:
1028 result.AppendMessage('error: at least one section must be specified with the --section option')
1031 target = debugger.GetSelectedTarget()
1032 for module in target.modules:
1033 for section_name in options.section_names:
1034 section = module.section[section_name]
1036 sections.append (section)
1037 section_modules.append (module)
1039 dylid_load_err = load_dylib()
1041 result.AppendMessage(dylid_load_err)
1043 frame = target.GetProcess().GetSelectedThread().GetSelectedFrame()
1044 for expr_str in args:
1045 for (idx, section) in enumerate(sections):
1046 expr = 'find_pointer_in_memory(0x%xllu, %ullu, (void *)%s)' % (section.addr.load_addr, section.size, expr_str)
1047 arg_str_description = 'section %s.%s containing "%s"' % (section_modules[idx].file.fullpath, section.name, expr_str)
1048 num_matches = display_match_results (target.GetProcess(), result, options, arg_str_description, expr, False)
1050 if num_matches < options.max_matches:
1051 options.max_matches = options.max_matches - num_matches
1053 options.max_matches = 0
1054 if options.max_matches == 0:
1057 result.AppendMessage('error: no sections were found that match any of %s' % (', '.join(options.section_names)))
1059 def get_objc_refs_options():
1060 usage = "usage: %prog [options] <CLASS> [CLASS ...]"
1061 description='''Searches all allocations on the heap for instances of
1062 objective C classes, or any classes that inherit from the specified classes
1063 in darwin user space programs. Any matches that were found will dump the malloc
1064 blocks that contain the C strings and might be able to print what kind of
1065 objects the pointers are contained in using dynamic type information in the
1067 parser = optparse.OptionParser(description=description, prog='objc_refs',usage=usage)
1068 add_common_options(parser)
1071 def objc_refs(debugger, command, result, dict):
1072 command_args = shlex.split(command)
1073 parser = get_objc_refs_options()
1075 (options, args) = parser.parse_args(command_args)
1079 process = debugger.GetSelectedTarget().GetProcess()
1081 result.AppendMessage('error: invalid process')
1083 frame = process.GetSelectedThread().GetSelectedFrame()
1085 result.AppendMessage('error: invalid frame')
1088 options.type = 'isa'
1089 if options.format == None:
1090 options.format = "A" # 'A' is "address" format
1092 expr_options = lldb.SBExpressionOptions()
1093 expr_options.SetIgnoreBreakpoints(True);
1094 expr_options.SetTimeoutInMicroSeconds (3*1000*1000) # 3 second infinite timeout
1095 expr_options.SetTryAllThreads (True)
1096 expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
1097 num_objc_classes_value = frame.EvaluateExpression("(int)objc_getClassList((void *)0, (int)0)", expr_options)
1098 if not num_objc_classes_value.error.Success():
1099 result.AppendMessage('error: %s' % num_objc_classes_value.error.GetCString())
1102 num_objc_classes = num_objc_classes_value.GetValueAsUnsigned()
1103 if num_objc_classes == 0:
1104 result.AppendMessage('error: no objective C classes in program')
1108 # When we initialize the expression, we must define any types that
1109 # we will need when looking at every allocation. We must also define
1110 # a type named callback_baton_t and make an instance named "baton"
1111 # and initialize it how ever we want to. The address of "baton" will
1112 # be passed into our range callback. callback_baton_t must contain
1113 # a member named "callback" whose type is "range_callback_t". This
1114 # will be used by our zone callbacks to call the range callback for
1115 # each malloc range.
1117 struct $malloc_match {
1125 user_init_code_format = '''
1126 #define MAX_MATCHES %u
1127 typedef int (*compare_callback_t)(const void *a, const void *b);
1128 typedef struct callback_baton_t {
1129 range_callback_t callback;
1130 compare_callback_t compare_callback;
1131 unsigned num_matches;
1132 $malloc_match matches[MAX_MATCHES];
1136 compare_callback_t compare_callback = [](const void *a, const void *b) -> int {
1137 Class a_ptr = *(Class *)a;
1138 Class b_ptr = *(Class *)b;
1139 if (a_ptr < b_ptr) return -1;
1140 if (a_ptr > b_ptr) return +1;
1143 typedef Class (*class_getSuperclass_type)(void *isa);
1144 range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
1145 class_getSuperclass_type class_getSuperclass_impl = (class_getSuperclass_type)class_getSuperclass;
1146 callback_baton_t *info = (callback_baton_t *)baton;
1147 if (sizeof(Class) <= ptr_size) {
1148 Class *curr_class_ptr = (Class *)ptr_addr;
1149 Class *matching_class_ptr = (Class *)bsearch (curr_class_ptr,
1150 (const void *)info->classes,
1151 sizeof(info->classes)/sizeof(Class),
1153 info->compare_callback);
1154 if (matching_class_ptr) {
1157 Class isa = *curr_class_ptr;
1158 if (info->isa == isa)
1160 else { // if (info->objc.match_superclasses) {
1161 Class super = class_getSuperclass_impl(isa);
1163 if (super == info->isa) {
1167 super = class_getSuperclass_impl(super);
1174 if (info->num_matches < MAX_MATCHES) {
1175 info->matches[info->num_matches].addr = (void*)ptr_addr;
1176 info->matches[info->num_matches].size = ptr_size;
1177 info->matches[info->num_matches].offset = 0;
1178 info->matches[info->num_matches].type = type;
1179 ++info->num_matches;
1185 callback_baton_t baton = { range_callback, compare_callback, 0, {0}, (void *)0x%x, {0} };
1186 int nc = (int)objc_getClassList(baton.classes, sizeof(baton.classes)/sizeof(Class));
1187 (void)qsort (baton.classes, sizeof(baton.classes)/sizeof(Class), sizeof(Class), compare_callback);'''
1188 # We must also define a snippet of code to be run that returns
1189 # the result of the expression we run.
1190 # Here we return NULL if our pointer was not found in any malloc blocks,
1191 # and we return the address of the matches array so we can then access
1192 # the matching results
1193 user_return_code = '''if (baton.num_matches < MAX_MATCHES)
1194 baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
1196 # Iterate through all of our ObjC class name arguments
1197 for class_name in args:
1198 addr_expr_str = "(void *)[%s class]" % class_name
1199 expr_options = lldb.SBExpressionOptions()
1200 expr_options.SetIgnoreBreakpoints(True);
1201 expr_options.SetTimeoutInMicroSeconds (1*1000*1000) # 1 second timeout
1202 expr_options.SetTryAllThreads (True)
1203 expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
1204 expr_sbvalue = frame.EvaluateExpression (addr_expr_str, expr_options)
1205 if expr_sbvalue.error.Success():
1206 isa = expr_sbvalue.unsigned
1208 options.type = 'isa'
1209 result.AppendMessage('Searching for all instances of classes or subclasses of "%s" (isa=0x%x)' % (class_name, isa))
1210 user_init_code = user_init_code_format % (options.max_matches, num_objc_classes, isa)
1211 expr = get_iterate_memory_expr(options, process, user_init_code, user_return_code)
1212 arg_str_description = 'objective C classes with isa 0x%x' % isa
1213 display_match_results (process, result, options, arg_str_description, expr, True, expr_prefix)
1215 result.AppendMessage('error: Can\'t find isa for an ObjC class named "%s"' % (class_name))
1217 result.AppendMessage('error: expression error for "%s": %s' % (addr_expr_str, expr_sbvalue.error))
1219 result.AppendMessage('error: command takes one or more C string arguments');
1221 if __name__ == '__main__':
1222 lldb.debugger = lldb.SBDebugger.Create()
1224 # Make the options so we can generate the help text for the new LLDB
1225 # command line command prior to registering it with LLDB below. This way
1226 # if clients in LLDB type "help malloc_info", they will see the exact same
1227 # output as typing "malloc_info --help".
1228 ptr_refs.__doc__ = get_ptr_refs_options().format_help()
1229 cstr_refs.__doc__ = get_cstr_refs_options().format_help()
1230 malloc_info.__doc__ = get_malloc_info_options().format_help()
1231 objc_refs.__doc__ = get_objc_refs_options().format_help()
1232 lldb.debugger.HandleCommand('command script add -f %s.ptr_refs ptr_refs' % __name__)
1233 lldb.debugger.HandleCommand('command script add -f %s.cstr_refs cstr_refs' % __name__)
1234 lldb.debugger.HandleCommand('command script add -f %s.malloc_info malloc_info' % __name__)
1235 lldb.debugger.HandleCommand('command script add -f %s.find_variable find_variable' % __name__)
1236 # lldb.debugger.HandleCommand('command script add -f %s.heap heap' % package_name)
1237 # lldb.debugger.HandleCommand('command script add -f %s.section_ptr_refs section_ptr_refs' % package_name)
1238 # lldb.debugger.HandleCommand('command script add -f %s.stack_ptr_refs stack_ptr_refs' % package_name)
1239 lldb.debugger.HandleCommand('command script add -f %s.objc_refs objc_refs' % __name__)
1240 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.'