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 # ----------------------------------------------------------------------
20 import lldb
.utils
.symbolication
22 g_libheap_dylib_dir
= None
23 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, void *, unsigned, uintptr_t, uintptr_t);
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, vm_address_t, vm_size_t, void **);
119 typedef void (*vm_range_recorder_t)(task_t, void *, unsigned, vm_range_t *, unsigned);
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 kern_return_t malloc_get_all_zones(task_t, memory_reader_t, vm_address_t **, unsigned *);
128 memory_reader_t task_peek = [](task_t, vm_address_t remote_address, vm_size_t, void **local_memory) -> kern_return_t {
129 *local_memory = (void*) remote_address;
132 vm_address_t *zones = 0;
133 unsigned int num_zones = 0;task_t task = 0;
134 kern_return_t err = (kern_return_t)malloc_get_all_zones (task, task_peek, &zones, &num_zones);
135 if (KERN_SUCCESS == err)
137 for (unsigned int i=0; i<num_zones; ++i)
139 const malloc_zone_t *zone = (const malloc_zone_t *)zones[i];
140 if (zone && zone->introspect)
141 zone->introspect->enumerator (task,
143 MALLOC_PTR_IN_USE_RANGE_TYPE,
146 [] (task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned size) -> void
148 range_callback_t callback = ((callback_baton_t *)baton)->callback;
149 for (unsigned i=0; i<size; ++i)
151 callback (task, baton, type, ranges[i].address, ranges[i].size);
157 if options
.search_stack
:
160 // Call the callback for the thread stack ranges
161 for (uint32_t i=0; i<NUM_STACKS; ++i) {
162 range_callback(task, &baton, 8, stacks[i].base, stacks[i].size);
163 if (STACK_RED_ZONE_SIZE > 0) {
164 range_callback(task, &baton, 16, stacks[i].base - STACK_RED_ZONE_SIZE, STACK_RED_ZONE_SIZE);
169 if options
.search_segments
:
172 // Call the callback for all segments
173 for (uint32_t i=0; i<NUM_SEGMENTS; ++i)
174 range_callback(task, &baton, 32, segments[i].base, segments[i].size);
178 expr
+= "\n%s" % (user_return_code
,)
183 def get_member_types_for_offset(value_type
, offset
, member_list
):
184 member
= value_type
.GetFieldAtIndex(0)
187 if member
.GetOffsetInBytes() <= offset
:
188 for field_idx
in range(value_type
.GetNumberOfFields()):
189 member
= value_type
.GetFieldAtIndex(field_idx
)
190 member_byte_offset
= member
.GetOffsetInBytes()
191 member_end_byte_offset
= member_byte_offset
+ member
.type.size
192 if member_byte_offset
<= offset
and offset
< member_end_byte_offset
:
193 member_list
.append(member
)
194 get_member_types_for_offset(
195 member
.type, offset
- member_byte_offset
, member_list
203 for field_idx
in range(value_type
.GetNumberOfDirectBaseClasses()):
204 member
= value_type
.GetDirectBaseClassAtIndex(field_idx
)
205 member_byte_offset
= member
.GetOffsetInBytes()
206 member_end_byte_offset
= member_byte_offset
+ member
.type.size
207 if member_byte_offset
<= offset
and offset
< member_end_byte_offset
:
208 member_list
.append(member
)
209 get_member_types_for_offset(
210 member
.type, offset
- member_byte_offset
, member_list
213 for field_idx
in range(value_type
.GetNumberOfVirtualBaseClasses()):
214 member
= value_type
.GetVirtualBaseClassAtIndex(field_idx
)
215 member_byte_offset
= member
.GetOffsetInBytes()
216 member_end_byte_offset
= member_byte_offset
+ member
.type.size
217 if member_byte_offset
<= offset
and offset
< member_end_byte_offset
:
218 member_list
.append(member
)
219 get_member_types_for_offset(
220 member
.type, offset
- member_byte_offset
, member_list
225 def append_regex_callback(option
, opt
, value
, parser
):
227 ivar_regex
= re
.compile(value
)
228 parser
.values
.ivar_regex_exclusions
.append(ivar_regex
)
231 'error: an exception was thrown when compiling the ivar regular expression for "%s"'
236 def add_common_options(parser
):
242 help="display verbose debug info",
250 help="print the full value of the type for each matching malloc block",
257 dest
="print_object_description",
258 help="print the object descriptions for any matches",
266 help="print the allocation size in bytes",
274 help="print the allocation address range instead of just the allocation base address",
282 help="dump the memory for each matching block",
290 help="the format to use when dumping memory if --memory is specified",
298 callback
=append_regex_callback
,
299 dest
="ivar_regex_exclusions",
301 help="specify one or more regular expressions used to backlist any matches that are in ivars",
308 help="gets the stack that allocated each malloc block if MallocStackLogging is enabled",
315 dest
="stack_history",
316 help="gets the stack history for all allocations whose start address matches each malloc block if MallocStackLogging is enabled",
324 help="the maximum number of stack frames to print when using the --stack or --stack-history options (default=128)",
332 help="the maximum number of stack history backtraces to print for each allocation when using the --stack-history option (default=16)",
340 help="the maximum number of matches to print",
348 help="the matching data must be at this offset",
353 action
="store_false",
355 help="Don't search the stack when enumerating memory",
360 action
="store_false",
362 help="Don't search the heap allocations when enumerating memory",
367 action
="store_false",
368 dest
="search_segments",
369 help="Don't search readable executable segments enumerating memory",
376 dest
="search_vm_regions",
377 help="Check all VM regions instead of searching the heap, stack and segments",
382 def type_flags_to_string(type_flags
):
393 elif type_flags
& 16:
394 type_str
= "stack (red zone)"
395 elif type_flags
& 32:
397 elif type_flags
& 64:
398 type_str
= "vm_region"
400 type_str
= hex(type_flags
)
404 def find_variable_containing_address(verbose
, frame
, match_addr
):
405 variables
= frame
.GetVariables(True, True, True, True)
407 for var
in variables
:
408 var_addr
= var
.GetLoadAddress()
409 if var_addr
!= lldb
.LLDB_INVALID_ADDRESS
:
410 byte_size
= var
.GetType().GetByteSize()
413 "frame #%u: [%#x - %#x) %s"
417 var
.load_addr
+ byte_size
,
421 if var_addr
== match_addr
:
428 and var_addr
<= match_addr
429 and match_addr
< (var_addr
+ byte_size
)
437 def find_frame_for_stack_address(process
, addr
):
438 closest_delta
= sys
.maxsize
440 # print 'find_frame_for_stack_address(%#x)' % (addr)
441 for thread
in process
:
442 prev_sp
= lldb
.LLDB_INVALID_ADDRESS
445 # print 'frame #%u: cfa = %#x' % (frame.GetFrameID(), cfa)
448 # print '%#x < %#x, delta = %i' % (addr, cfa, delta)
449 if delta
< closest_delta
:
451 closest_delta
= delta
452 closest_frame
= frame
454 # print 'delta >= closest_delta'
458 def type_flags_to_description(
459 process
, type_flags
, ptr_addr
, ptr_size
, offset
, match_addr
462 if type_flags
== 0 or type_flags
& 4:
463 type_str
= "free(%#x)" % (ptr_addr
,)
464 elif type_flags
& 2 or type_flags
& 1:
465 type_str
= "malloc(%6u) -> %#x" % (ptr_size
, ptr_addr
)
469 frame
= find_frame_for_stack_address(process
, match_addr
)
471 type_str
+= " in frame #%u of thread #%u: tid %#x" % (
473 frame
.GetThread().GetIndexID(),
474 frame
.GetThread().GetThreadID(),
476 variables
= frame
.GetVariables(True, True, True, True)
478 for var
in variables
:
479 var_addr
= var
.GetLoadAddress()
480 if var_addr
!= lldb
.LLDB_INVALID_ADDRESS
:
481 # print 'variable "%s" @ %#x (%#x)' % (var.name, var.load_addr,
483 if var_addr
== match_addr
:
487 byte_size
= var
.GetType().GetByteSize()
490 and var_addr
<= match_addr
491 and match_addr
< (var_addr
+ byte_size
)
496 type_str
+= " in variable at %#x:\n %s" % (
497 matching_var
.GetLoadAddress(),
500 elif type_flags
& 16:
501 type_str
= "stack (red zone)"
502 elif type_flags
& 32:
503 sb_addr
= process
.GetTarget().ResolveLoadAddress(ptr_addr
+ offset
)
504 type_str
= "segment [%#x - %#x), %s + %u, %s" % (
507 sb_addr
.section
.name
,
511 elif type_flags
& 64:
512 sb_addr
= process
.GetTarget().ResolveLoadAddress(ptr_addr
+ offset
)
513 type_str
= "vm_region [%#x - %#x), %s + %u, %s" % (
516 sb_addr
.section
.name
,
521 type_str
= "%#x" % (ptr_addr
,)
523 if show_offset
and offset
!= 0:
524 type_str
+= " + %-6u" % (offset
,)
528 def dump_stack_history_entry(options
, result
, stack_history_entry
, idx
):
529 address
= int(stack_history_entry
.address
)
531 type_flags
= int(stack_history_entry
.type_flags
)
532 symbolicator
= lldb
.utils
.symbolication
.Symbolicator()
533 symbolicator
.target
= lldb
.debugger
.GetSelectedTarget()
534 type_str
= type_flags_to_string(type_flags
)
535 result
.AppendMessage(
536 "stack[%u]: addr = 0x%x, type=%s, frames:" % (idx
, address
, type_str
)
540 pc
= int(stack_history_entry
.frames
[idx
])
543 frames
= symbolicator
.symbolicate(pc
)
546 result
.AppendMessage(" [%u] %s" % (frame_idx
, frame
))
549 result
.AppendMessage(" [%u] 0x%x" % (frame_idx
, pc
))
552 pc
= int(stack_history_entry
.frames
[idx
])
555 if idx
>= options
.max_frames
:
556 result
.AppendMessage(
557 'warning: the max number of stack frames (%u) was reached, use the "--max-frames=<COUNT>" option to see more frames'
558 % (options
.max_frames
)
561 result
.AppendMessage("")
564 def dump_stack_history_entries(options
, result
, addr
, history
):
565 # malloc_stack_entry *get_stack_history_for_address (const void * addr)
567 typedef int kern_return_t;
568 typedef struct $malloc_stack_entry {
573 uint64_t frames[512];
575 } $malloc_stack_entry;
578 #define MAX_FRAMES %u
579 typedef unsigned task_t;
580 $malloc_stack_entry stack;
581 stack.address = 0x%x;
582 stack.type_flags = 2;
583 stack.num_frames = 0;
585 uint32_t max_stack_frames = MAX_FRAMES;
586 stack.err = (kern_return_t)__mach_stack_logging_get_frames (
587 (task_t)mach_task_self(),
592 if (stack.num_frames < MAX_FRAMES)
593 stack.frames[stack.num_frames] = 0;
595 stack.frames[MAX_FRAMES-1] = 0;
602 typedef int kern_return_t;
603 typedef unsigned task_t;
604 #define MAX_FRAMES %u
605 #define MAX_HISTORY %u
606 typedef struct mach_stack_logging_record_t {
608 uint64_t stack_identifier;
611 } mach_stack_logging_record_t;
612 typedef void (*enumerate_callback_t)(mach_stack_logging_record_t, void *);
613 typedef struct malloc_stack_entry {
618 uint64_t frames[MAX_FRAMES];
619 kern_return_t frames_err;
620 } malloc_stack_entry;
621 typedef struct $malloc_stack_history {
624 malloc_stack_entry entries[MAX_HISTORY];
625 } $malloc_stack_history;
626 $malloc_stack_history lldb_info = { (task_t)mach_task_self(), 0 };
627 uint32_t max_stack_frames = MAX_FRAMES;
628 enumerate_callback_t callback = [] (mach_stack_logging_record_t stack_record, void *baton) -> void {
629 $malloc_stack_history *lldb_info = ($malloc_stack_history *)baton;
630 if (lldb_info->idx < MAX_HISTORY) {
631 malloc_stack_entry *stack_entry = &(lldb_info->entries[lldb_info->idx]);
632 stack_entry->address = stack_record.address;
633 stack_entry->type_flags = stack_record.type_flags;
634 stack_entry->argument = stack_record.argument;
635 stack_entry->num_frames = 0;
636 stack_entry->frames[0] = 0;
637 stack_entry->frames_err = (kern_return_t)__mach_stack_logging_frames_for_uniqued_stack (
639 stack_record.stack_identifier,
641 (uint32_t)MAX_FRAMES,
642 &stack_entry->num_frames);
643 // Terminate the frames with zero if there is room
644 if (stack_entry->num_frames < MAX_FRAMES)
645 stack_entry->frames[stack_entry->num_frames] = 0;
649 (kern_return_t)__mach_stack_logging_enumerate_records (lldb_info.task, (uint64_t)0x%x, callback, &lldb_info);
657 lldb
.debugger
.GetSelectedTarget()
666 expr_options
= lldb
.SBExpressionOptions()
667 expr_options
.SetIgnoreBreakpoints(True)
668 expr_options
.SetTimeoutInMicroSeconds(5 * 1000 * 1000) # 5 second timeout
669 expr_options
.SetTryAllThreads(True)
670 expr_options
.SetLanguage(lldb
.eLanguageTypeObjC_plus_plus
)
671 expr_options
.SetPrefix(expr_prefix
)
672 expr_sbvalue
= frame
.EvaluateExpression(expr
, expr_options
)
676 print("expression result:")
678 if expr_sbvalue
.error
.Success():
680 malloc_stack_history
= lldb
.value(expr_sbvalue
)
681 num_stacks
= int(malloc_stack_history
.idx
)
682 if num_stacks
<= options
.max_history
:
685 i_max
= options
.max_history
686 for i
in range(i_max
):
687 stack_history_entry
= malloc_stack_history
.entries
[i
]
688 dump_stack_history_entry(options
, result
, stack_history_entry
, i
)
689 if num_stacks
> options
.max_history
:
690 result
.AppendMessage(
691 'warning: the max number of stacks (%u) was reached, use the "--max-history=%u" option to see all of the stacks'
692 % (options
.max_history
, num_stacks
)
695 stack_history_entry
= lldb
.value(expr_sbvalue
)
696 dump_stack_history_entry(options
, result
, stack_history_entry
, 0)
699 result
.AppendMessage(
700 'error: expression failed "%s" => %s' % (expr
, expr_sbvalue
.error
)
704 def display_match_results(
714 lldb
.debugger
.GetSelectedTarget()
720 result
.AppendMessage("error: invalid frame")
722 expr_options
= lldb
.SBExpressionOptions()
723 expr_options
.SetIgnoreBreakpoints(True)
724 expr_options
.SetFetchDynamicValue(lldb
.eNoDynamicValues
)
725 expr_options
.SetTimeoutInMicroSeconds(30 * 1000 * 1000) # 30 second timeout
726 expr_options
.SetTryAllThreads(False)
727 expr_options
.SetLanguage(lldb
.eLanguageTypeObjC_plus_plus
)
729 expr_options
.SetPrefix(expr_prefix
)
730 expr_sbvalue
= frame
.EvaluateExpression(expr
, expr_options
)
734 print("expression result:")
736 if expr_sbvalue
.error
.Success():
737 match_value
= lldb
.value(expr_sbvalue
)
742 match_entry
= match_value
[i
]
744 if i
> options
.max_matches
:
745 result
.AppendMessage(
746 "warning: the max number of matches (%u) was reached, use the --max-matches option to get more results"
747 % (options
.max_matches
)
750 malloc_addr
= match_entry
.addr
.sbvalue
.unsigned
753 malloc_size
= int(match_entry
.size
)
754 offset
= int(match_entry
.offset
)
756 if options
.offset
>= 0 and options
.offset
!= offset
:
759 match_addr
= malloc_addr
+ offset
760 type_flags
= int(match_entry
.type)
761 # result.AppendMessage (hex(malloc_addr + offset))
763 search_stack_old
= options
.search_stack
764 search_segments_old
= options
.search_segments
765 search_heap_old
= options
.search_heap
766 search_vm_regions
= options
.search_vm_regions
767 options
.search_stack
= True
768 options
.search_segments
= True
769 options
.search_heap
= True
770 options
.search_vm_regions
= False
772 lldb
.debugger
, result
, options
, [hex(malloc_addr
+ offset
)]
775 options
.search_stack
= search_stack_old
776 options
.search_segments
= search_segments_old
777 options
.search_heap
= search_heap_old
778 options
.search_vm_regions
= search_vm_regions
780 description
= "%#16.16x: %s" % (
782 type_flags_to_description(
791 if options
.show_size
:
792 description
+= " <%5u>" % (malloc_size
)
793 if options
.show_range
:
794 description
+= " [%#x - %#x)" % (
796 malloc_addr
+ malloc_size
,
798 derefed_dynamic_value
= None
799 dynamic_value
= match_entry
.addr
.sbvalue
.GetDynamicValue(
800 lldb
.eDynamicCanRunTarget
802 if dynamic_value
.type.name
== "void *":
803 if options
.type == "pointer" and malloc_size
== 4096:
804 error
= lldb
.SBError()
805 process
= expr_sbvalue
.GetProcess()
806 target
= expr_sbvalue
.GetTarget()
807 data
= bytearray(process
.ReadMemory(malloc_addr
, 16, error
))
808 if data
== "\xa1\xa1\xa1\xa1AUTORELEASE!":
809 ptr_size
= target
.addr_size
810 thread
= process
.ReadUnsignedFromMemory(
811 malloc_addr
+ 16 + ptr_size
, ptr_size
, error
814 # 12 bytes 'AUTORELEASE!'
815 # ptr bytes autorelease insertion point
816 # ptr bytes pthread_t
817 # ptr bytes next colder page
818 # ptr bytes next hotter page
819 # 4 bytes this page's depth in the list
820 # 4 bytes high-water mark
821 description
+= " AUTORELEASE! for pthread_t %#x" % (
825 # description += 'malloc(%u)' % (malloc_size)
827 # description += 'malloc(%u)' % (malloc_size)
829 derefed_dynamic_value
= dynamic_value
.deref
830 if derefed_dynamic_value
:
831 derefed_dynamic_type
= derefed_dynamic_value
.type
832 derefed_dynamic_type_size
= derefed_dynamic_type
.size
833 derefed_dynamic_type_name
= derefed_dynamic_type
.name
835 description
+= derefed_dynamic_type_name
836 if offset
< derefed_dynamic_type_size
:
838 get_member_types_for_offset(
839 derefed_dynamic_type
, offset
, member_list
843 for member
in member_list
:
844 member_name
= member
.name
848 member_path
+= member_name
850 if options
.ivar_regex_exclusions
:
853 ) in options
.ivar_regex_exclusions
:
854 if ivar_regex
.match(member_path
):
856 description
+= ".%s" % (member_path
)
858 description
+= "%u bytes after %s" % (
859 offset
- derefed_dynamic_type_size
,
860 derefed_dynamic_type_name
,
863 # strip the "*" from the end of the name since we
864 # were unable to dereference this
865 description
+= dynamic_value
.type.name
[0:-1]
870 result_output
+= description
871 if options
.print_type
and derefed_dynamic_value
:
872 result_output
+= " %s" % (derefed_dynamic_value
)
873 if options
.print_object_description
and dynamic_value
:
874 desc
= dynamic_value
.GetObjectDescription()
876 result_output
+= "\n%s" % (desc
)
878 result
.AppendMessage(result_output
)
880 cmd_result
= lldb
.SBCommandReturnObject()
881 if options
.format
is None:
882 memory_command
= "memory read --force 0x%x 0x%x" % (
884 malloc_addr
+ malloc_size
,
887 memory_command
= "memory read --force -f %s 0x%x 0x%x" % (
890 malloc_addr
+ malloc_size
,
893 result
.AppendMessage(memory_command
)
894 lldb
.debugger
.GetCommandInterpreter().HandleCommand(
895 memory_command
, cmd_result
897 result
.AppendMessage(cmd_result
.GetOutput())
898 if options
.stack_history
:
899 dump_stack_history_entries(options
, result
, malloc_addr
, 1)
901 dump_stack_history_entries(options
, result
, malloc_addr
, 0)
904 result
.AppendMessage(str(expr_sbvalue
.error
))
908 def get_ptr_refs_options():
909 usage
= "usage: %prog [options] <EXPR> [EXPR ...]"
910 description
= """Searches all allocations on the heap for pointer values on
911 darwin user space programs. Any matches that were found will dump the malloc
912 blocks that contain the pointers and might be able to print what kind of
913 objects the pointers are contained in using dynamic type information in the
915 parser
= optparse
.OptionParser(
916 description
=description
, prog
="ptr_refs", usage
=usage
918 add_common_options(parser
)
922 def find_variable(debugger
, command
, result
, dict):
923 usage
= "usage: %prog [options] <ADDR> [ADDR ...]"
925 """Searches for a local variable in all frames that contains a hex ADDR."""
927 command_args
= shlex
.split(command
)
928 parser
= optparse
.OptionParser(
929 description
=description
, prog
="find_variable", usage
=usage
936 help="display verbose debug info",
940 (options
, args
) = parser
.parse_args(command_args
)
944 process
= debugger
.GetSelectedTarget().GetProcess()
946 result
.AppendMessage("error: invalid process")
950 var_addr
= int(arg
, 16)
951 print("Finding a variable with address %#x..." % (var_addr
), file=result
)
953 for thread
in process
:
955 var
= find_variable_containing_address(options
.verbose
, frame
, var_addr
)
964 def ptr_refs(debugger
, command
, result
, dict):
965 command_args
= shlex
.split(command
)
966 parser
= get_ptr_refs_options()
968 (options
, args
) = parser
.parse_args(command_args
)
972 process
= debugger
.GetSelectedTarget().GetProcess()
974 result
.AppendMessage("error: invalid process")
976 frame
= process
.GetSelectedThread().GetSelectedFrame()
978 result
.AppendMessage("error: invalid frame")
981 options
.type = "pointer"
982 if options
.format
is None:
983 options
.format
= "A" # 'A' is "address" format
986 # When we initialize the expression, we must define any types that
987 # we will need when looking at every allocation. We must also define
988 # a type named callback_baton_t and make an instance named "baton"
989 # and initialize it how ever we want to. The address of "baton" will
990 # be passed into our range callback. callback_baton_t must contain
991 # a member named "callback" whose type is "range_callback_t". This
992 # will be used by our zone callbacks to call the range callback for
995 struct $malloc_match {
1002 user_init_code_format
= """
1003 #define MAX_MATCHES %u
1004 typedef struct callback_baton_t {
1005 range_callback_t callback;
1006 unsigned num_matches;
1007 $malloc_match matches[MAX_MATCHES];
1010 range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
1011 callback_baton_t *lldb_info = (callback_baton_t *)baton;
1013 const unsigned size = sizeof(T);
1014 T *array = (T*)ptr_addr;
1015 for (unsigned idx = 0; ((idx + 1) * sizeof(T)) <= ptr_size; ++idx) {
1016 if (array[idx] == lldb_info->ptr) {
1017 if (lldb_info->num_matches < MAX_MATCHES) {
1018 lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
1019 lldb_info->matches[lldb_info->num_matches].size = ptr_size;
1020 lldb_info->matches[lldb_info->num_matches].offset = idx*sizeof(T);
1021 lldb_info->matches[lldb_info->num_matches].type = type;
1022 ++lldb_info->num_matches;
1027 callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
1029 # We must also define a snippet of code to be run that returns
1030 # the result of the expression we run.
1031 # Here we return NULL if our pointer was not found in any malloc blocks,
1032 # and we return the address of the matches array so we can then access
1033 # the matching results
1034 user_return_code
= """if (baton.num_matches < MAX_MATCHES)
1035 baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
1037 # Iterate through all of our pointer expressions and display the
1039 for ptr_expr
in args
:
1040 user_init_code
= user_init_code_format
% (options
.max_matches
, ptr_expr
)
1041 expr
= get_iterate_memory_expr(
1042 options
, process
, user_init_code
, user_return_code
1044 arg_str_description
= "malloc block containing pointer %s" % ptr_expr
1045 display_match_results(
1046 process
, result
, options
, arg_str_description
, expr
, True, expr_prefix
1049 result
.AppendMessage("error: no pointer arguments were given")
1052 def get_cstr_refs_options():
1053 usage
= "usage: %prog [options] <CSTR> [CSTR ...]"
1054 description
= """Searches all allocations on the heap for C string values on
1055 darwin user space programs. Any matches that were found will dump the malloc
1056 blocks that contain the C strings and might be able to print what kind of
1057 objects the pointers are contained in using dynamic type information in the
1059 parser
= optparse
.OptionParser(
1060 description
=description
, prog
="cstr_refs", usage
=usage
1062 add_common_options(parser
)
1066 def cstr_refs(debugger
, command
, result
, dict):
1067 command_args
= shlex
.split(command
)
1068 parser
= get_cstr_refs_options()
1070 (options
, args
) = parser
.parse_args(command_args
)
1074 process
= debugger
.GetSelectedTarget().GetProcess()
1076 result
.AppendMessage("error: invalid process")
1078 frame
= process
.GetSelectedThread().GetSelectedFrame()
1080 result
.AppendMessage("error: invalid frame")
1083 options
.type = "cstr"
1084 if options
.format
is None:
1085 options
.format
= "Y" # 'Y' is "bytes with ASCII" format
1088 # When we initialize the expression, we must define any types that
1089 # we will need when looking at every allocation. We must also define
1090 # a type named callback_baton_t and make an instance named "baton"
1091 # and initialize it how ever we want to. The address of "baton" will
1092 # be passed into our range callback. callback_baton_t must contain
1093 # a member named "callback" whose type is "range_callback_t". This
1094 # will be used by our zone callbacks to call the range callback for
1095 # each malloc range.
1097 struct $malloc_match {
1104 user_init_code_format
= """
1105 #define MAX_MATCHES %u
1106 typedef struct callback_baton_t {
1107 range_callback_t callback;
1108 unsigned num_matches;
1109 $malloc_match matches[MAX_MATCHES];
1113 range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
1114 callback_baton_t *lldb_info = (callback_baton_t *)baton;
1115 if (lldb_info->cstr_len < ptr_size) {
1116 const char *begin = (const char *)ptr_addr;
1117 const char *end = begin + ptr_size - lldb_info->cstr_len;
1118 for (const char *s = begin; s < end; ++s) {
1119 if ((int)memcmp(s, lldb_info->cstr, lldb_info->cstr_len) == 0) {
1120 if (lldb_info->num_matches < MAX_MATCHES) {
1121 lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
1122 lldb_info->matches[lldb_info->num_matches].size = ptr_size;
1123 lldb_info->matches[lldb_info->num_matches].offset = s - begin;
1124 lldb_info->matches[lldb_info->num_matches].type = type;
1125 ++lldb_info->num_matches;
1131 const char *cstr = "%s";
1132 callback_baton_t baton = { range_callback, 0, {0}, cstr, (unsigned)strlen(cstr) };"""
1133 # We must also define a snippet of code to be run that returns
1134 # the result of the expression we run.
1135 # Here we return NULL if our pointer was not found in any malloc blocks,
1136 # and we return the address of the matches array so we can then access
1137 # the matching results
1138 user_return_code
= """if (baton.num_matches < MAX_MATCHES)
1139 baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
1141 # Iterate through all of our pointer expressions and display the
1144 user_init_code
= user_init_code_format
% (options
.max_matches
, cstr
)
1145 expr
= get_iterate_memory_expr(
1146 options
, process
, user_init_code
, user_return_code
1148 arg_str_description
= 'malloc block containing "%s"' % cstr
1149 display_match_results(
1150 process
, result
, options
, arg_str_description
, expr
, True, expr_prefix
1153 result
.AppendMessage("error: command takes one or more C string arguments")
1156 def get_malloc_info_options():
1157 usage
= "usage: %prog [options] <EXPR> [EXPR ...]"
1158 description
= """Searches the heap a malloc block that contains the addresses
1159 specified as one or more address expressions. Any matches that were found will
1160 dump the malloc blocks that match or contain the specified address. The matching
1161 blocks might be able to show what kind of objects they are using dynamic type
1162 information in the program."""
1163 parser
= optparse
.OptionParser(
1164 description
=description
, prog
="malloc_info", usage
=usage
1166 add_common_options(parser
)
1170 def malloc_info(debugger
, command
, result
, dict):
1171 command_args
= shlex
.split(command
)
1172 parser
= get_malloc_info_options()
1174 (options
, args
) = parser
.parse_args(command_args
)
1177 malloc_info_impl(debugger
, result
, options
, args
)
1180 def malloc_info_impl(debugger
, result
, options
, args
):
1181 # We are specifically looking for something on the heap only
1182 options
.type = "malloc_info"
1184 process
= debugger
.GetSelectedTarget().GetProcess()
1186 result
.AppendMessage("error: invalid process")
1188 frame
= process
.GetSelectedThread().GetSelectedFrame()
1190 result
.AppendMessage("error: invalid frame")
1193 struct $malloc_match {
1201 user_init_code_format
= """
1202 typedef struct callback_baton_t {
1203 range_callback_t callback;
1204 unsigned num_matches;
1205 $malloc_match matches[2]; // Two items so they can be NULL terminated
1208 range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
1209 callback_baton_t *lldb_info = (callback_baton_t *)baton;
1210 if (lldb_info->num_matches == 0) {
1211 uint8_t *p = (uint8_t *)lldb_info->ptr;
1212 uint8_t *lo = (uint8_t *)ptr_addr;
1213 uint8_t *hi = lo + ptr_size;
1214 if (lo <= p && p < hi) {
1215 lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
1216 lldb_info->matches[lldb_info->num_matches].size = ptr_size;
1217 lldb_info->matches[lldb_info->num_matches].offset = p - lo;
1218 lldb_info->matches[lldb_info->num_matches].type = type;
1219 lldb_info->num_matches = 1;
1223 callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
1224 baton.matches[0].addr = 0;
1225 baton.matches[1].addr = 0;"""
1228 for ptr_expr
in args
:
1229 user_init_code
= user_init_code_format
% (ptr_expr
)
1230 expr
= get_iterate_memory_expr(
1231 options
, process
, user_init_code
, "baton.matches"
1233 arg_str_description
= "malloc block that contains %s" % ptr_expr
1234 total_matches
+= display_match_results(
1235 process
, result
, options
, arg_str_description
, expr
, True, expr_prefix
1237 return total_matches
1239 result
.AppendMessage("error: command takes one or more pointer expressions")
1243 def get_thread_stack_ranges_struct(process
):
1244 """Create code that defines a structure that represents threads stack bounds
1245 for all threads. It returns a static sized array initialized with all of
1246 the tid, base, size structs for all the threads."""
1247 stack_dicts
= list()
1250 for thread
in process
:
1251 min_sp
= thread
.frame
[0].sp
1253 for frame
in thread
.frames
:
1262 "tid": thread
.GetThreadID(),
1264 "size": max_sp
- min_sp
,
1269 stack_dicts_len
= len(stack_dicts
)
1270 if stack_dicts_len
> 0:
1272 #define NUM_STACKS %u
1273 #define STACK_RED_ZONE_SIZE %u
1274 typedef struct thread_stack_t { uint64_t tid, base, size; } thread_stack_t;
1275 thread_stack_t stacks[NUM_STACKS];""" % (
1277 process
.target
.GetStackRedZoneSize(),
1279 for stack_dict
in stack_dicts
:
1282 stacks[%(index)u].tid = 0x%(tid)x;
1283 stacks[%(index)u].base = 0x%(base)x;
1284 stacks[%(index)u].size = 0x%(size)x;"""
1292 def get_sections_ranges_struct(process
):
1293 """Create code that defines a structure that represents all segments that
1294 can contain data for all images in "target". It returns a static sized
1295 array initialized with all of base, size structs for all the threads."""
1296 target
= process
.target
1297 segment_dicts
= list()
1298 for module_idx
, module
in enumerate(target
.modules
):
1299 for sect_idx
in range(module
.GetNumSections()):
1300 section
= module
.GetSectionAtIndex(sect_idx
)
1304 if name
!= "__TEXT" and name
!= "__LINKEDIT" and name
!= "__PAGEZERO":
1305 base
= section
.GetLoadAddress(target
)
1306 size
= section
.GetByteSize()
1307 if base
!= lldb
.LLDB_INVALID_ADDRESS
and size
> 0:
1308 segment_dicts
.append({"base": base
, "size": size
})
1309 segment_dicts_len
= len(segment_dicts
)
1310 if segment_dicts_len
> 0:
1312 #define NUM_SEGMENTS %u
1313 typedef struct segment_range_t { uint64_t base; uint32_t size; } segment_range_t;
1314 segment_range_t segments[NUM_SEGMENTS];""" % (
1317 for idx
, segment_dict
in enumerate(segment_dicts
):
1318 segment_dict
["index"] = idx
1321 segments[%(index)u].base = 0x%(base)x;
1322 segments[%(index)u].size = 0x%(size)x;"""
1330 def section_ptr_refs(debugger
, command
, result
, dict):
1331 command_args
= shlex
.split(command
)
1332 usage
= "usage: %prog [options] <EXPR> [EXPR ...]"
1333 description
= """Searches section contents for pointer values in darwin user space programs."""
1334 parser
= optparse
.OptionParser(
1335 description
=description
, prog
="section_ptr_refs", usage
=usage
1337 add_common_options(parser
)
1342 dest
="section_names",
1343 help="section name to search",
1347 (options
, args
) = parser
.parse_args(command_args
)
1351 options
.type = "pointer"
1354 section_modules
= list()
1355 if not options
.section_names
:
1356 result
.AppendMessage(
1357 "error: at least one section must be specified with the --section option"
1361 target
= debugger
.GetSelectedTarget()
1362 for module
in target
.modules
:
1363 for section_name
in options
.section_names
:
1364 section
= module
.section
[section_name
]
1366 sections
.append(section
)
1367 section_modules
.append(module
)
1369 dylid_load_err
= load_dylib()
1371 result
.AppendMessage(dylid_load_err
)
1373 frame
= target
.GetProcess().GetSelectedThread().GetSelectedFrame()
1374 for expr_str
in args
:
1375 for idx
, section
in enumerate(sections
):
1376 expr
= "find_pointer_in_memory(0x%xllu, %ullu, (void *)%s)" % (
1377 section
.addr
.load_addr
,
1381 arg_str_description
= 'section %s.%s containing "%s"' % (
1382 section_modules
[idx
].file.fullpath
,
1386 num_matches
= display_match_results(
1387 target
.GetProcess(),
1390 arg_str_description
,
1395 if num_matches
< options
.max_matches
:
1396 options
.max_matches
= options
.max_matches
- num_matches
1398 options
.max_matches
= 0
1399 if options
.max_matches
== 0:
1402 result
.AppendMessage(
1403 "error: no sections were found that match any of %s"
1404 % (", ".join(options
.section_names
))
1408 def get_objc_refs_options():
1409 usage
= "usage: %prog [options] <CLASS> [CLASS ...]"
1410 description
= """Searches all allocations on the heap for instances of
1411 objective C classes, or any classes that inherit from the specified classes
1412 in darwin user space programs. Any matches that were found will dump the malloc
1413 blocks that contain the C strings and might be able to print what kind of
1414 objects the pointers are contained in using dynamic type information in the
1416 parser
= optparse
.OptionParser(
1417 description
=description
, prog
="objc_refs", usage
=usage
1419 add_common_options(parser
)
1423 def objc_refs(debugger
, command
, result
, dict):
1424 command_args
= shlex
.split(command
)
1425 parser
= get_objc_refs_options()
1427 (options
, args
) = parser
.parse_args(command_args
)
1431 process
= debugger
.GetSelectedTarget().GetProcess()
1433 result
.AppendMessage("error: invalid process")
1435 frame
= process
.GetSelectedThread().GetSelectedFrame()
1437 result
.AppendMessage("error: invalid frame")
1440 options
.type = "isa"
1441 if options
.format
is None:
1442 options
.format
= "A" # 'A' is "address" format
1444 expr_options
= lldb
.SBExpressionOptions()
1445 expr_options
.SetIgnoreBreakpoints(True)
1446 expr_options
.SetTimeoutInMicroSeconds(3 * 1000 * 1000) # 3 second infinite timeout
1447 expr_options
.SetTryAllThreads(True)
1448 expr_options
.SetLanguage(lldb
.eLanguageTypeObjC_plus_plus
)
1449 num_objc_classes_value
= frame
.EvaluateExpression(
1450 "(int)objc_getClassList((void *)0, (int)0)", expr_options
1452 if not num_objc_classes_value
.error
.Success():
1453 result
.AppendMessage("error: %s" % num_objc_classes_value
.error
.GetCString())
1456 num_objc_classes
= num_objc_classes_value
.GetValueAsUnsigned()
1457 if num_objc_classes
== 0:
1458 result
.AppendMessage("error: no objective C classes in program")
1462 # When we initialize the expression, we must define any types that
1463 # we will need when looking at every allocation. We must also define
1464 # a type named callback_baton_t and make an instance named "baton"
1465 # and initialize it how ever we want to. The address of "baton" will
1466 # be passed into our range callback. callback_baton_t must contain
1467 # a member named "callback" whose type is "range_callback_t". This
1468 # will be used by our zone callbacks to call the range callback for
1469 # each malloc range.
1471 struct $malloc_match {
1479 user_init_code_format
= """
1480 #define MAX_MATCHES %u
1481 typedef int (*compare_callback_t)(const void *a, const void *b);
1482 typedef struct callback_baton_t {
1483 range_callback_t callback;
1484 compare_callback_t compare_callback;
1485 unsigned num_matches;
1486 $malloc_match matches[MAX_MATCHES];
1490 compare_callback_t compare_callback = [](const void *a, const void *b) -> int {
1491 Class a_ptr = *(Class *)a;
1492 Class b_ptr = *(Class *)b;
1493 if (a_ptr < b_ptr) return -1;
1494 if (a_ptr > b_ptr) return +1;
1497 typedef Class (*class_getSuperclass_type)(void *isa);
1498 range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
1499 class_getSuperclass_type class_getSuperclass_impl = (class_getSuperclass_type)class_getSuperclass;
1500 callback_baton_t *lldb_info = (callback_baton_t *)baton;
1501 if (sizeof(Class) <= ptr_size) {
1502 Class *curr_class_ptr = (Class *)ptr_addr;
1503 Class *matching_class_ptr = (Class *)bsearch (curr_class_ptr,
1504 (const void *)lldb_info->classes,
1505 sizeof(lldb_info->classes)/sizeof(Class),
1507 lldb_info->compare_callback);
1508 if (matching_class_ptr) {
1510 if (lldb_info->isa) {
1511 Class isa = *curr_class_ptr;
1512 if (lldb_info->isa == isa)
1514 else { // if (lldb_info->objc.match_superclasses) {
1515 Class super = class_getSuperclass_impl(isa);
1517 if (super == lldb_info->isa) {
1521 super = class_getSuperclass_impl(super);
1528 if (lldb_info->num_matches < MAX_MATCHES) {
1529 lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
1530 lldb_info->matches[lldb_info->num_matches].size = ptr_size;
1531 lldb_info->matches[lldb_info->num_matches].offset = 0;
1532 lldb_info->matches[lldb_info->num_matches].type = type;
1533 ++lldb_info->num_matches;
1539 callback_baton_t baton = { range_callback, compare_callback, 0, {0}, (void *)0x%x, {0} };
1540 int nc = (int)objc_getClassList(baton.classes, sizeof(baton.classes)/sizeof(Class));
1541 (void)qsort (baton.classes, sizeof(baton.classes)/sizeof(Class), sizeof(Class), compare_callback);"""
1542 # We must also define a snippet of code to be run that returns
1543 # the result of the expression we run.
1544 # Here we return NULL if our pointer was not found in any malloc blocks,
1545 # and we return the address of the matches array so we can then access
1546 # the matching results
1547 user_return_code
= """if (baton.num_matches < MAX_MATCHES)
1548 baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
1550 # Iterate through all of our ObjC class name arguments
1551 for class_name
in args
:
1552 addr_expr_str
= "(void *)[%s class]" % class_name
1553 expr_options
= lldb
.SBExpressionOptions()
1554 expr_options
.SetIgnoreBreakpoints(True)
1555 expr_options
.SetTimeoutInMicroSeconds(1 * 1000 * 1000) # 1 second timeout
1556 expr_options
.SetTryAllThreads(True)
1557 expr_options
.SetLanguage(lldb
.eLanguageTypeObjC_plus_plus
)
1558 expr_sbvalue
= frame
.EvaluateExpression(addr_expr_str
, expr_options
)
1559 if expr_sbvalue
.error
.Success():
1560 isa
= expr_sbvalue
.unsigned
1562 options
.type = "isa"
1563 result
.AppendMessage(
1564 'Searching for all instances of classes or subclasses of "%s" (isa=0x%x)'
1567 user_init_code
= user_init_code_format
% (
1568 options
.max_matches
,
1572 expr
= get_iterate_memory_expr(
1573 options
, process
, user_init_code
, user_return_code
1575 arg_str_description
= "objective C classes with isa 0x%x" % isa
1576 display_match_results(
1580 arg_str_description
,
1586 result
.AppendMessage(
1587 'error: Can\'t find isa for an ObjC class named "%s"'
1591 result
.AppendMessage(
1592 'error: expression error for "%s": %s'
1593 % (addr_expr_str
, expr_sbvalue
.error
)
1596 result
.AppendMessage("error: command takes one or more C string arguments")
1599 if __name__
== "__main__":
1600 lldb
.debugger
= lldb
.SBDebugger
.Create()
1603 def __lldb_init_module(debugger
, internal_dict
):
1604 # Make the options so we can generate the help text for the new LLDB
1605 # command line command prior to registering it with LLDB below. This way
1606 # if clients in LLDB type "help malloc_info", they will see the exact same
1607 # output as typing "malloc_info --help".
1608 ptr_refs
.__doc
__ = get_ptr_refs_options().format_help()
1609 cstr_refs
.__doc
__ = get_cstr_refs_options().format_help()
1610 malloc_info
.__doc
__ = get_malloc_info_options().format_help()
1611 objc_refs
.__doc
__ = get_objc_refs_options().format_help()
1612 debugger
.HandleCommand("command script add -o -f %s.ptr_refs ptr_refs" % __name__
)
1613 debugger
.HandleCommand("command script add -o -f %s.cstr_refs cstr_refs" % __name__
)
1614 debugger
.HandleCommand(
1615 "command script add -o -f %s.malloc_info malloc_info" % __name__
1617 debugger
.HandleCommand(
1618 "command script add -o -f %s.find_variable find_variable" % __name__
1620 # debugger.HandleCommand('command script add -o -f %s.section_ptr_refs section_ptr_refs' % package_name)
1621 debugger
.HandleCommand("command script add -o -f %s.objc_refs objc_refs" % __name__
)
1623 '"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.'