xref: /llvm-project/lldb/examples/darwin/heap_find/heap.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1#!/usr/bin/env python3
2
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:
7#
8#   (lldb) script import lldb.macosx.heap
9# ----------------------------------------------------------------------
10
11import lldb
12import optparse
13import os
14import os.path
15import re
16import shlex
17import string
18import sys
19import tempfile
20import lldb.utils.symbolication
21
22g_libheap_dylib_dir = None
23g_libheap_dylib_dict = dict()
24
25
26def get_iterate_memory_expr(options, process, user_init_code, user_return_code):
27    expr = """
28typedef unsigned natural_t;
29typedef uintptr_t vm_size_t;
30typedef uintptr_t vm_address_t;
31typedef natural_t task_t;
32typedef int kern_return_t;
33#define KERN_SUCCESS 0
34typedef void (*range_callback_t)(task_t, void *, unsigned, uintptr_t, uintptr_t);
35"""
36    if options.search_vm_regions:
37        expr += """
38typedef int vm_prot_t;
39typedef unsigned int vm_inherit_t;
40typedef unsigned long long	memory_object_offset_t;
41typedef unsigned int boolean_t;
42typedef int vm_behavior_t;
43typedef uint32_t vm32_object_id_t;
44typedef natural_t mach_msg_type_number_t;
45typedef uint64_t mach_vm_address_t;
46typedef uint64_t mach_vm_offset_t;
47typedef uint64_t mach_vm_size_t;
48typedef uint64_t vm_map_offset_t;
49typedef uint64_t vm_map_address_t;
50typedef 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)
55typedef struct vm_region_submap_short_info_data_64_t {
56    vm_prot_t protection;
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)))"""
71        if user_init_code:
72            expr += user_init_code
73        expr += """
74task_t task = (task_t)mach_task_self();
75mach_vm_address_t vm_region_base_addr;
76mach_vm_size_t vm_region_size;
77natural_t vm_region_depth;
78vm_region_submap_short_info_data_64_t vm_region_info;
79kern_return_t err;
80for (vm_region_base_addr = 0, vm_region_size = 1; vm_region_size != 0; vm_region_base_addr += vm_region_size)
81{
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,
84                                                 &vm_region_base_addr,
85                                                 &vm_region_size,
86                                                 &vm_region_depth,
87                                                 &vm_region_info,
88                                                 &vm_region_info_size);
89    if (err)
90        break;
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)
96    {
97        baton.callback (task,
98                        &baton,
99                        64,
100                        vm_region_base_addr,
101                        vm_region_size);
102    }
103}"""
104    else:
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)
109        if user_init_code:
110            expr += user_init_code
111        if options.search_heap:
112            expr += """
113#define MALLOC_PTR_IN_USE_RANGE_TYPE 1
114typedef struct vm_range_t {
115    vm_address_t	address;
116    vm_size_t		size;
117} vm_range_t;
118typedef kern_return_t (*memory_reader_t)(task_t, vm_address_t, vm_size_t, void **);
119typedef void (*vm_range_recorder_t)(task_t, void *, unsigned, vm_range_t *, unsigned);
120typedef 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;
123typedef struct malloc_zone_t {
124    void *reserved1[12];
125    struct malloc_introspection_t	*introspect;
126} malloc_zone_t;
127kern_return_t malloc_get_all_zones(task_t, memory_reader_t, vm_address_t **, unsigned *);
128memory_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;
130    return KERN_SUCCESS;
131};
132vm_address_t *zones = 0;
133unsigned int num_zones = 0;task_t task = 0;
134kern_return_t err = (kern_return_t)malloc_get_all_zones (task, task_peek, &zones, &num_zones);
135if (KERN_SUCCESS == err)
136{
137    for (unsigned int i=0; i<num_zones; ++i)
138    {
139        const malloc_zone_t *zone = (const malloc_zone_t *)zones[i];
140        if (zone && zone->introspect)
141            zone->introspect->enumerator (task,
142                                          &baton,
143                                          MALLOC_PTR_IN_USE_RANGE_TYPE,
144                                          (vm_address_t)zone,
145                                          task_peek,
146                                          [] (task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned size) -> void
147                                          {
148                                              range_callback_t callback = ((callback_baton_t *)baton)->callback;
149                                              for (unsigned i=0; i<size; ++i)
150                                              {
151                                                  callback (task, baton, type, ranges[i].address, ranges[i].size);
152                                              }
153                                          });
154    }
155}"""
156
157        if options.search_stack:
158            expr += """
159#ifdef NUM_STACKS
160// Call the callback for the thread stack ranges
161for (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);
165    }
166}
167#endif"""
168
169        if options.search_segments:
170            expr += """
171#ifdef NUM_SEGMENTS
172// Call the callback for all segments
173for (uint32_t i=0; i<NUM_SEGMENTS; ++i)
174    range_callback(task, &baton, 32, segments[i].base, segments[i].size);
175#endif"""
176
177    if user_return_code:
178        expr += "\n%s" % (user_return_code,)
179
180    return expr
181
182
183def get_member_types_for_offset(value_type, offset, member_list):
184    member = value_type.GetFieldAtIndex(0)
185    search_bases = False
186    if member:
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
196                    )
197                    return
198        else:
199            search_bases = True
200    else:
201        search_bases = True
202    if search_bases:
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
211                )
212                return
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
221                )
222                return
223
224
225def append_regex_callback(option, opt, value, parser):
226    try:
227        ivar_regex = re.compile(value)
228        parser.values.ivar_regex_exclusions.append(ivar_regex)
229    except:
230        print(
231            'error: an exception was thrown when compiling the ivar regular expression for "%s"'
232            % value
233        )
234
235
236def add_common_options(parser):
237    parser.add_option(
238        "-v",
239        "--verbose",
240        action="store_true",
241        dest="verbose",
242        help="display verbose debug info",
243        default=False,
244    )
245    parser.add_option(
246        "-t",
247        "--type",
248        action="store_true",
249        dest="print_type",
250        help="print the full value of the type for each matching malloc block",
251        default=False,
252    )
253    parser.add_option(
254        "-o",
255        "--po",
256        action="store_true",
257        dest="print_object_description",
258        help="print the object descriptions for any matches",
259        default=False,
260    )
261    parser.add_option(
262        "-z",
263        "--size",
264        action="store_true",
265        dest="show_size",
266        help="print the allocation size in bytes",
267        default=False,
268    )
269    parser.add_option(
270        "-r",
271        "--range",
272        action="store_true",
273        dest="show_range",
274        help="print the allocation address range instead of just the allocation base address",
275        default=False,
276    )
277    parser.add_option(
278        "-m",
279        "--memory",
280        action="store_true",
281        dest="memory",
282        help="dump the memory for each matching block",
283        default=False,
284    )
285    parser.add_option(
286        "-f",
287        "--format",
288        type="string",
289        dest="format",
290        help="the format to use when dumping memory if --memory is specified",
291        default=None,
292    )
293    parser.add_option(
294        "-I",
295        "--omit-ivar-regex",
296        type="string",
297        action="callback",
298        callback=append_regex_callback,
299        dest="ivar_regex_exclusions",
300        default=[],
301        help="specify one or more regular expressions used to backlist any matches that are in ivars",
302    )
303    parser.add_option(
304        "-s",
305        "--stack",
306        action="store_true",
307        dest="stack",
308        help="gets the stack that allocated each malloc block if MallocStackLogging is enabled",
309        default=False,
310    )
311    parser.add_option(
312        "-S",
313        "--stack-history",
314        action="store_true",
315        dest="stack_history",
316        help="gets the stack history for all allocations whose start address matches each malloc block if MallocStackLogging is enabled",
317        default=False,
318    )
319    parser.add_option(
320        "-F",
321        "--max-frames",
322        type="int",
323        dest="max_frames",
324        help="the maximum number of stack frames to print when using the --stack or --stack-history options (default=128)",
325        default=128,
326    )
327    parser.add_option(
328        "-H",
329        "--max-history",
330        type="int",
331        dest="max_history",
332        help="the maximum number of stack history backtraces to print for each allocation when using the --stack-history option (default=16)",
333        default=16,
334    )
335    parser.add_option(
336        "-M",
337        "--max-matches",
338        type="int",
339        dest="max_matches",
340        help="the maximum number of matches to print",
341        default=32,
342    )
343    parser.add_option(
344        "-O",
345        "--offset",
346        type="int",
347        dest="offset",
348        help="the matching data must be at this offset",
349        default=-1,
350    )
351    parser.add_option(
352        "--ignore-stack",
353        action="store_false",
354        dest="search_stack",
355        help="Don't search the stack when enumerating memory",
356        default=True,
357    )
358    parser.add_option(
359        "--ignore-heap",
360        action="store_false",
361        dest="search_heap",
362        help="Don't search the heap allocations when enumerating memory",
363        default=True,
364    )
365    parser.add_option(
366        "--ignore-segments",
367        action="store_false",
368        dest="search_segments",
369        help="Don't search readable executable segments enumerating memory",
370        default=True,
371    )
372    parser.add_option(
373        "-V",
374        "--vm-regions",
375        action="store_true",
376        dest="search_vm_regions",
377        help="Check all VM regions instead of searching the heap, stack and segments",
378        default=False,
379    )
380
381
382def type_flags_to_string(type_flags):
383    if type_flags == 0:
384        type_str = "free"
385    elif type_flags & 2:
386        type_str = "malloc"
387    elif type_flags & 4:
388        type_str = "free"
389    elif type_flags & 1:
390        type_str = "generic"
391    elif type_flags & 8:
392        type_str = "stack"
393    elif type_flags & 16:
394        type_str = "stack (red zone)"
395    elif type_flags & 32:
396        type_str = "segment"
397    elif type_flags & 64:
398        type_str = "vm_region"
399    else:
400        type_str = hex(type_flags)
401    return type_str
402
403
404def find_variable_containing_address(verbose, frame, match_addr):
405    variables = frame.GetVariables(True, True, True, True)
406    matching_var = None
407    for var in variables:
408        var_addr = var.GetLoadAddress()
409        if var_addr != lldb.LLDB_INVALID_ADDRESS:
410            byte_size = var.GetType().GetByteSize()
411            if verbose:
412                print(
413                    "frame #%u: [%#x - %#x) %s"
414                    % (
415                        frame.GetFrameID(),
416                        var.load_addr,
417                        var.load_addr + byte_size,
418                        var.name,
419                    )
420                )
421            if var_addr == match_addr:
422                if verbose:
423                    print("match")
424                return var
425            else:
426                if (
427                    byte_size > 0
428                    and var_addr <= match_addr
429                    and match_addr < (var_addr + byte_size)
430                ):
431                    if verbose:
432                        print("match")
433                    return var
434    return None
435
436
437def find_frame_for_stack_address(process, addr):
438    closest_delta = sys.maxsize
439    closest_frame = None
440    # print 'find_frame_for_stack_address(%#x)' % (addr)
441    for thread in process:
442        prev_sp = lldb.LLDB_INVALID_ADDRESS
443        for frame in thread:
444            cfa = frame.GetCFA()
445            # print 'frame #%u: cfa = %#x' % (frame.GetFrameID(), cfa)
446            if addr < cfa:
447                delta = cfa - addr
448                # print '%#x < %#x, delta = %i' % (addr, cfa, delta)
449                if delta < closest_delta:
450                    # print 'closest'
451                    closest_delta = delta
452                    closest_frame = frame
453                # else:
454                #     print 'delta >= closest_delta'
455    return closest_frame
456
457
458def type_flags_to_description(
459    process, type_flags, ptr_addr, ptr_size, offset, match_addr
460):
461    show_offset = False
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)
466        show_offset = True
467    elif type_flags & 8:
468        type_str = "stack"
469        frame = find_frame_for_stack_address(process, match_addr)
470        if frame:
471            type_str += " in frame #%u of thread #%u: tid %#x" % (
472                frame.GetFrameID(),
473                frame.GetThread().GetIndexID(),
474                frame.GetThread().GetThreadID(),
475            )
476        variables = frame.GetVariables(True, True, True, True)
477        matching_var = None
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,
482                # match_addr)
483                if var_addr == match_addr:
484                    matching_var = var
485                    break
486                else:
487                    byte_size = var.GetType().GetByteSize()
488                    if (
489                        byte_size > 0
490                        and var_addr <= match_addr
491                        and match_addr < (var_addr + byte_size)
492                    ):
493                        matching_var = var
494                        break
495        if matching_var:
496            type_str += " in variable at %#x:\n    %s" % (
497                matching_var.GetLoadAddress(),
498                matching_var,
499            )
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" % (
505            ptr_addr,
506            ptr_addr + ptr_size,
507            sb_addr.section.name,
508            sb_addr.offset,
509            sb_addr,
510        )
511    elif type_flags & 64:
512        sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset)
513        type_str = "vm_region [%#x - %#x), %s + %u, %s" % (
514            ptr_addr,
515            ptr_addr + ptr_size,
516            sb_addr.section.name,
517            sb_addr.offset,
518            sb_addr,
519        )
520    else:
521        type_str = "%#x" % (ptr_addr,)
522        show_offset = True
523    if show_offset and offset != 0:
524        type_str += " + %-6u" % (offset,)
525    return type_str
526
527
528def dump_stack_history_entry(options, result, stack_history_entry, idx):
529    address = int(stack_history_entry.address)
530    if 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)
537        )
538        frame_idx = 0
539        idx = 0
540        pc = int(stack_history_entry.frames[idx])
541        while pc != 0:
542            if pc >= 0x1000:
543                frames = symbolicator.symbolicate(pc)
544                if frames:
545                    for frame in frames:
546                        result.AppendMessage("     [%u] %s" % (frame_idx, frame))
547                        frame_idx += 1
548                else:
549                    result.AppendMessage("     [%u] 0x%x" % (frame_idx, pc))
550                    frame_idx += 1
551                idx = idx + 1
552                pc = int(stack_history_entry.frames[idx])
553            else:
554                pc = 0
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)
559            )
560
561        result.AppendMessage("")
562
563
564def dump_stack_history_entries(options, result, addr, history):
565    # malloc_stack_entry *get_stack_history_for_address (const void * addr)
566    expr_prefix = """
567typedef int kern_return_t;
568typedef struct $malloc_stack_entry {
569    uint64_t address;
570    uint64_t argument;
571    uint32_t type_flags;
572    uint32_t num_frames;
573    uint64_t frames[512];
574    kern_return_t err;
575} $malloc_stack_entry;
576"""
577    single_expr = """
578#define MAX_FRAMES %u
579typedef unsigned task_t;
580$malloc_stack_entry stack;
581stack.address = 0x%x;
582stack.type_flags = 2;
583stack.num_frames = 0;
584stack.frames[0] = 0;
585uint32_t max_stack_frames = MAX_FRAMES;
586stack.err = (kern_return_t)__mach_stack_logging_get_frames (
587    (task_t)mach_task_self(),
588    stack.address,
589    &stack.frames[0],
590    max_stack_frames,
591    &stack.num_frames);
592if (stack.num_frames < MAX_FRAMES)
593    stack.frames[stack.num_frames] = 0;
594else
595    stack.frames[MAX_FRAMES-1] = 0;
596stack""" % (
597        options.max_frames,
598        addr,
599    )
600
601    history_expr = """
602typedef int kern_return_t;
603typedef unsigned task_t;
604#define MAX_FRAMES %u
605#define MAX_HISTORY %u
606typedef struct mach_stack_logging_record_t {
607	uint32_t type_flags;
608	uint64_t stack_identifier;
609	uint64_t argument;
610	uint64_t address;
611} mach_stack_logging_record_t;
612typedef void (*enumerate_callback_t)(mach_stack_logging_record_t, void *);
613typedef struct malloc_stack_entry {
614    uint64_t address;
615    uint64_t argument;
616    uint32_t type_flags;
617    uint32_t num_frames;
618    uint64_t frames[MAX_FRAMES];
619    kern_return_t frames_err;
620} malloc_stack_entry;
621typedef struct $malloc_stack_history {
622    task_t task;
623    unsigned idx;
624    malloc_stack_entry entries[MAX_HISTORY];
625} $malloc_stack_history;
626$malloc_stack_history lldb_info = { (task_t)mach_task_self(), 0 };
627uint32_t max_stack_frames = MAX_FRAMES;
628enumerate_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 (
638            lldb_info->task,
639            stack_record.stack_identifier,
640            stack_entry->frames,
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;
646    }
647    ++lldb_info->idx;
648};
649(kern_return_t)__mach_stack_logging_enumerate_records (lldb_info.task, (uint64_t)0x%x, callback, &lldb_info);
650lldb_info""" % (
651        options.max_frames,
652        options.max_history,
653        addr,
654    )
655
656    frame = (
657        lldb.debugger.GetSelectedTarget()
658        .GetProcess()
659        .GetSelectedThread()
660        .GetSelectedFrame()
661    )
662    if history:
663        expr = history_expr
664    else:
665        expr = single_expr
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)
673    if options.verbose:
674        print("expression:")
675        print(expr)
676        print("expression result:")
677        print(expr_sbvalue)
678    if expr_sbvalue.error.Success():
679        if history:
680            malloc_stack_history = lldb.value(expr_sbvalue)
681            num_stacks = int(malloc_stack_history.idx)
682            if num_stacks <= options.max_history:
683                i_max = num_stacks
684            else:
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)
693                )
694        else:
695            stack_history_entry = lldb.value(expr_sbvalue)
696            dump_stack_history_entry(options, result, stack_history_entry, 0)
697
698    else:
699        result.AppendMessage(
700            'error: expression failed "%s" => %s' % (expr, expr_sbvalue.error)
701        )
702
703
704def display_match_results(
705    process,
706    result,
707    options,
708    arg_str_description,
709    expr,
710    print_no_matches,
711    expr_prefix=None,
712):
713    frame = (
714        lldb.debugger.GetSelectedTarget()
715        .GetProcess()
716        .GetSelectedThread()
717        .GetSelectedFrame()
718    )
719    if not frame:
720        result.AppendMessage("error: invalid frame")
721        return 0
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)
728    if expr_prefix:
729        expr_options.SetPrefix(expr_prefix)
730    expr_sbvalue = frame.EvaluateExpression(expr, expr_options)
731    if options.verbose:
732        print("expression:")
733        print(expr)
734        print("expression result:")
735        print(expr_sbvalue)
736    if expr_sbvalue.error.Success():
737        match_value = lldb.value(expr_sbvalue)
738        i = 0
739        match_idx = 0
740        while True:
741            print_entry = True
742            match_entry = match_value[i]
743            i += 1
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)
748                )
749                break
750            malloc_addr = match_entry.addr.sbvalue.unsigned
751            if malloc_addr == 0:
752                break
753            malloc_size = int(match_entry.size)
754            offset = int(match_entry.offset)
755
756            if options.offset >= 0 and options.offset != offset:
757                print_entry = False
758            else:
759                match_addr = malloc_addr + offset
760                type_flags = int(match_entry.type)
761                # result.AppendMessage (hex(malloc_addr + offset))
762                if type_flags == 64:
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
771                    if malloc_info_impl(
772                        lldb.debugger, result, options, [hex(malloc_addr + offset)]
773                    ):
774                        print_entry = False
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
779                if print_entry:
780                    description = "%#16.16x: %s" % (
781                        match_addr,
782                        type_flags_to_description(
783                            process,
784                            type_flags,
785                            malloc_addr,
786                            malloc_size,
787                            offset,
788                            match_addr,
789                        ),
790                    )
791                    if options.show_size:
792                        description += " <%5u>" % (malloc_size)
793                    if options.show_range:
794                        description += " [%#x - %#x)" % (
795                            malloc_addr,
796                            malloc_addr + malloc_size,
797                        )
798                    derefed_dynamic_value = None
799                    dynamic_value = match_entry.addr.sbvalue.GetDynamicValue(
800                        lldb.eDynamicCanRunTarget
801                    )
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
812                                )
813                                #   4 bytes  0xa1a1a1a1
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" % (
822                                    thread
823                                )
824                        #     else:
825                        #         description += 'malloc(%u)' % (malloc_size)
826                        # else:
827                        #     description += 'malloc(%u)' % (malloc_size)
828                    else:
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
834                            description += " "
835                            description += derefed_dynamic_type_name
836                            if offset < derefed_dynamic_type_size:
837                                member_list = list()
838                                get_member_types_for_offset(
839                                    derefed_dynamic_type, offset, member_list
840                                )
841                                if member_list:
842                                    member_path = ""
843                                    for member in member_list:
844                                        member_name = member.name
845                                        if member_name:
846                                            if member_path:
847                                                member_path += "."
848                                            member_path += member_name
849                                    if member_path:
850                                        if options.ivar_regex_exclusions:
851                                            for (
852                                                ivar_regex
853                                            ) in options.ivar_regex_exclusions:
854                                                if ivar_regex.match(member_path):
855                                                    print_entry = False
856                                        description += ".%s" % (member_path)
857                            else:
858                                description += "%u bytes after %s" % (
859                                    offset - derefed_dynamic_type_size,
860                                    derefed_dynamic_type_name,
861                                )
862                        else:
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]
866            if print_entry:
867                match_idx += 1
868                result_output = ""
869                if description:
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()
875                        if desc:
876                            result_output += "\n%s" % (desc)
877                if result_output:
878                    result.AppendMessage(result_output)
879                if options.memory:
880                    cmd_result = lldb.SBCommandReturnObject()
881                    if options.format is None:
882                        memory_command = "memory read --force 0x%x 0x%x" % (
883                            malloc_addr,
884                            malloc_addr + malloc_size,
885                        )
886                    else:
887                        memory_command = "memory read --force -f %s 0x%x 0x%x" % (
888                            options.format,
889                            malloc_addr,
890                            malloc_addr + malloc_size,
891                        )
892                    if options.verbose:
893                        result.AppendMessage(memory_command)
894                    lldb.debugger.GetCommandInterpreter().HandleCommand(
895                        memory_command, cmd_result
896                    )
897                    result.AppendMessage(cmd_result.GetOutput())
898                if options.stack_history:
899                    dump_stack_history_entries(options, result, malloc_addr, 1)
900                elif options.stack:
901                    dump_stack_history_entries(options, result, malloc_addr, 0)
902        return i
903    else:
904        result.AppendMessage(str(expr_sbvalue.error))
905    return 0
906
907
908def get_ptr_refs_options():
909    usage = "usage: %prog [options] <EXPR> [EXPR ...]"
910    description = """Searches all allocations on the heap for pointer values on
911darwin user space programs. Any matches that were found will dump the malloc
912blocks that contain the pointers and might be able to print what kind of
913objects the pointers are contained in using dynamic type information in the
914program."""
915    parser = optparse.OptionParser(
916        description=description, prog="ptr_refs", usage=usage
917    )
918    add_common_options(parser)
919    return parser
920
921
922def find_variable(debugger, command, result, dict):
923    usage = "usage: %prog [options] <ADDR> [ADDR ...]"
924    description = (
925        """Searches for a local variable in all frames that contains a hex ADDR."""
926    )
927    command_args = shlex.split(command)
928    parser = optparse.OptionParser(
929        description=description, prog="find_variable", usage=usage
930    )
931    parser.add_option(
932        "-v",
933        "--verbose",
934        action="store_true",
935        dest="verbose",
936        help="display verbose debug info",
937        default=False,
938    )
939    try:
940        (options, args) = parser.parse_args(command_args)
941    except:
942        return
943
944    process = debugger.GetSelectedTarget().GetProcess()
945    if not process:
946        result.AppendMessage("error: invalid process")
947        return
948
949    for arg in args:
950        var_addr = int(arg, 16)
951        print("Finding a variable with address %#x..." % (var_addr), file=result)
952        done = False
953        for thread in process:
954            for frame in thread:
955                var = find_variable_containing_address(options.verbose, frame, var_addr)
956                if var:
957                    print(var)
958                    done = True
959                    break
960            if done:
961                break
962
963
964def ptr_refs(debugger, command, result, dict):
965    command_args = shlex.split(command)
966    parser = get_ptr_refs_options()
967    try:
968        (options, args) = parser.parse_args(command_args)
969    except:
970        return
971
972    process = debugger.GetSelectedTarget().GetProcess()
973    if not process:
974        result.AppendMessage("error: invalid process")
975        return
976    frame = process.GetSelectedThread().GetSelectedFrame()
977    if not frame:
978        result.AppendMessage("error: invalid frame")
979        return
980
981    options.type = "pointer"
982    if options.format is None:
983        options.format = "A"  # 'A' is "address" format
984
985    if args:
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
993        # each malloc range.
994        expr_prefix = """
995struct $malloc_match {
996    void *addr;
997    uintptr_t size;
998    uintptr_t offset;
999    uintptr_t type;
1000};
1001"""
1002        user_init_code_format = """
1003#define MAX_MATCHES %u
1004typedef struct callback_baton_t {
1005    range_callback_t callback;
1006    unsigned num_matches;
1007    $malloc_match matches[MAX_MATCHES];
1008    void *ptr;
1009} callback_baton_t;
1010range_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;
1012    typedef void* T;
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;
1023            }
1024        }
1025    }
1026};
1027callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
1028"""
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
1036baton.matches"""
1037        # Iterate through all of our pointer expressions and display the
1038        # results
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
1043            )
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
1047            )
1048    else:
1049        result.AppendMessage("error: no pointer arguments were given")
1050
1051
1052def get_cstr_refs_options():
1053    usage = "usage: %prog [options] <CSTR> [CSTR ...]"
1054    description = """Searches all allocations on the heap for C string values on
1055darwin user space programs. Any matches that were found will dump the malloc
1056blocks that contain the C strings and might be able to print what kind of
1057objects the pointers are contained in using dynamic type information in the
1058program."""
1059    parser = optparse.OptionParser(
1060        description=description, prog="cstr_refs", usage=usage
1061    )
1062    add_common_options(parser)
1063    return parser
1064
1065
1066def cstr_refs(debugger, command, result, dict):
1067    command_args = shlex.split(command)
1068    parser = get_cstr_refs_options()
1069    try:
1070        (options, args) = parser.parse_args(command_args)
1071    except:
1072        return
1073
1074    process = debugger.GetSelectedTarget().GetProcess()
1075    if not process:
1076        result.AppendMessage("error: invalid process")
1077        return
1078    frame = process.GetSelectedThread().GetSelectedFrame()
1079    if not frame:
1080        result.AppendMessage("error: invalid frame")
1081        return
1082
1083    options.type = "cstr"
1084    if options.format is None:
1085        options.format = "Y"  # 'Y' is "bytes with ASCII" format
1086
1087    if args:
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.
1096        expr_prefix = """
1097struct $malloc_match {
1098    void *addr;
1099    uintptr_t size;
1100    uintptr_t offset;
1101    uintptr_t type;
1102};
1103"""
1104        user_init_code_format = """
1105#define MAX_MATCHES %u
1106typedef struct callback_baton_t {
1107    range_callback_t callback;
1108    unsigned num_matches;
1109    $malloc_match matches[MAX_MATCHES];
1110    const char *cstr;
1111    unsigned cstr_len;
1112} callback_baton_t;
1113range_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;
1126                }
1127            }
1128        }
1129    }
1130};
1131const char *cstr = "%s";
1132callback_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
1140baton.matches"""
1141        # Iterate through all of our pointer expressions and display the
1142        # results
1143        for cstr in args:
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
1147            )
1148            arg_str_description = 'malloc block containing "%s"' % cstr
1149            display_match_results(
1150                process, result, options, arg_str_description, expr, True, expr_prefix
1151            )
1152    else:
1153        result.AppendMessage("error: command takes one or more C string arguments")
1154
1155
1156def get_malloc_info_options():
1157    usage = "usage: %prog [options] <EXPR> [EXPR ...]"
1158    description = """Searches the heap a malloc block that contains the addresses
1159specified as one or more address expressions. Any matches that were found will
1160dump the malloc blocks that match or contain the specified address. The matching
1161blocks might be able to show what kind of objects they are using dynamic type
1162information in the program."""
1163    parser = optparse.OptionParser(
1164        description=description, prog="malloc_info", usage=usage
1165    )
1166    add_common_options(parser)
1167    return parser
1168
1169
1170def malloc_info(debugger, command, result, dict):
1171    command_args = shlex.split(command)
1172    parser = get_malloc_info_options()
1173    try:
1174        (options, args) = parser.parse_args(command_args)
1175    except:
1176        return
1177    malloc_info_impl(debugger, result, options, args)
1178
1179
1180def malloc_info_impl(debugger, result, options, args):
1181    # We are specifically looking for something on the heap only
1182    options.type = "malloc_info"
1183
1184    process = debugger.GetSelectedTarget().GetProcess()
1185    if not process:
1186        result.AppendMessage("error: invalid process")
1187        return
1188    frame = process.GetSelectedThread().GetSelectedFrame()
1189    if not frame:
1190        result.AppendMessage("error: invalid frame")
1191        return
1192    expr_prefix = """
1193struct $malloc_match {
1194    void *addr;
1195    uintptr_t size;
1196    uintptr_t offset;
1197    uintptr_t type;
1198};
1199"""
1200
1201    user_init_code_format = """
1202typedef 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
1206    void *ptr;
1207} callback_baton_t;
1208range_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;
1220        }
1221    }
1222};
1223callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
1224baton.matches[0].addr = 0;
1225baton.matches[1].addr = 0;"""
1226    if args:
1227        total_matches = 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"
1232            )
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
1236            )
1237        return total_matches
1238    else:
1239        result.AppendMessage("error: command takes one or more pointer expressions")
1240        return 0
1241
1242
1243def 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()
1248    if process:
1249        i = 0
1250        for thread in process:
1251            min_sp = thread.frame[0].sp
1252            max_sp = min_sp
1253            for frame in thread.frames:
1254                sp = frame.sp
1255                if sp < min_sp:
1256                    min_sp = sp
1257                if sp > max_sp:
1258                    max_sp = sp
1259            if min_sp < max_sp:
1260                stack_dicts.append(
1261                    {
1262                        "tid": thread.GetThreadID(),
1263                        "base": min_sp,
1264                        "size": max_sp - min_sp,
1265                        "index": i,
1266                    }
1267                )
1268                i += 1
1269    stack_dicts_len = len(stack_dicts)
1270    if stack_dicts_len > 0:
1271        result = """
1272#define NUM_STACKS %u
1273#define STACK_RED_ZONE_SIZE %u
1274typedef struct thread_stack_t { uint64_t tid, base, size; } thread_stack_t;
1275thread_stack_t stacks[NUM_STACKS];""" % (
1276            stack_dicts_len,
1277            process.target.GetStackRedZoneSize(),
1278        )
1279        for stack_dict in stack_dicts:
1280            result += (
1281                """
1282stacks[%(index)u].tid  = 0x%(tid)x;
1283stacks[%(index)u].base = 0x%(base)x;
1284stacks[%(index)u].size = 0x%(size)x;"""
1285                % stack_dict
1286            )
1287        return result
1288    else:
1289        return ""
1290
1291
1292def 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)
1301            if not section:
1302                break
1303            name = section.name
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:
1311        result = """
1312#define NUM_SEGMENTS %u
1313typedef struct segment_range_t { uint64_t base; uint32_t size; } segment_range_t;
1314segment_range_t segments[NUM_SEGMENTS];""" % (
1315            segment_dicts_len,
1316        )
1317        for idx, segment_dict in enumerate(segment_dicts):
1318            segment_dict["index"] = idx
1319            result += (
1320                """
1321segments[%(index)u].base = 0x%(base)x;
1322segments[%(index)u].size = 0x%(size)x;"""
1323                % segment_dict
1324            )
1325        return result
1326    else:
1327        return ""
1328
1329
1330def 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
1336    )
1337    add_common_options(parser)
1338    parser.add_option(
1339        "--section",
1340        action="append",
1341        type="string",
1342        dest="section_names",
1343        help="section name to search",
1344        default=list(),
1345    )
1346    try:
1347        (options, args) = parser.parse_args(command_args)
1348    except:
1349        return
1350
1351    options.type = "pointer"
1352
1353    sections = list()
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"
1358        )
1359        return
1360
1361    target = debugger.GetSelectedTarget()
1362    for module in target.modules:
1363        for section_name in options.section_names:
1364            section = module.section[section_name]
1365            if section:
1366                sections.append(section)
1367                section_modules.append(module)
1368    if sections:
1369        dylid_load_err = load_dylib()
1370        if dylid_load_err:
1371            result.AppendMessage(dylid_load_err)
1372            return
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,
1378                    section.size,
1379                    expr_str,
1380                )
1381                arg_str_description = 'section %s.%s containing "%s"' % (
1382                    section_modules[idx].file.fullpath,
1383                    section.name,
1384                    expr_str,
1385                )
1386                num_matches = display_match_results(
1387                    target.GetProcess(),
1388                    result,
1389                    options,
1390                    arg_str_description,
1391                    expr,
1392                    False,
1393                )
1394                if num_matches:
1395                    if num_matches < options.max_matches:
1396                        options.max_matches = options.max_matches - num_matches
1397                    else:
1398                        options.max_matches = 0
1399                if options.max_matches == 0:
1400                    return
1401    else:
1402        result.AppendMessage(
1403            "error: no sections were found that match any of %s"
1404            % (", ".join(options.section_names))
1405        )
1406
1407
1408def get_objc_refs_options():
1409    usage = "usage: %prog [options] <CLASS> [CLASS ...]"
1410    description = """Searches all allocations on the heap for instances of
1411objective C classes, or any classes that inherit from the specified classes
1412in darwin user space programs. Any matches that were found will dump the malloc
1413blocks that contain the C strings and might be able to print what kind of
1414objects the pointers are contained in using dynamic type information in the
1415program."""
1416    parser = optparse.OptionParser(
1417        description=description, prog="objc_refs", usage=usage
1418    )
1419    add_common_options(parser)
1420    return parser
1421
1422
1423def objc_refs(debugger, command, result, dict):
1424    command_args = shlex.split(command)
1425    parser = get_objc_refs_options()
1426    try:
1427        (options, args) = parser.parse_args(command_args)
1428    except:
1429        return
1430
1431    process = debugger.GetSelectedTarget().GetProcess()
1432    if not process:
1433        result.AppendMessage("error: invalid process")
1434        return
1435    frame = process.GetSelectedThread().GetSelectedFrame()
1436    if not frame:
1437        result.AppendMessage("error: invalid frame")
1438        return
1439
1440    options.type = "isa"
1441    if options.format is None:
1442        options.format = "A"  # 'A' is "address" format
1443
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
1451    )
1452    if not num_objc_classes_value.error.Success():
1453        result.AppendMessage("error: %s" % num_objc_classes_value.error.GetCString())
1454        return
1455
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")
1459        return
1460
1461    if args:
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.
1470        expr_prefix = """
1471struct $malloc_match {
1472    void *addr;
1473    uintptr_t size;
1474    uintptr_t offset;
1475    uintptr_t type;
1476};
1477"""
1478
1479        user_init_code_format = """
1480#define MAX_MATCHES %u
1481typedef int (*compare_callback_t)(const void *a, const void *b);
1482typedef 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];
1487    void *isa;
1488    Class classes[%u];
1489} callback_baton_t;
1490compare_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;
1495     return 0;
1496};
1497typedef Class (*class_getSuperclass_type)(void *isa);
1498range_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),
1506                                                      sizeof(Class),
1507                                                      lldb_info->compare_callback);
1508        if (matching_class_ptr) {
1509            bool match = false;
1510            if (lldb_info->isa) {
1511                Class isa = *curr_class_ptr;
1512                if (lldb_info->isa == isa)
1513                    match = true;
1514                else { // if (lldb_info->objc.match_superclasses) {
1515                    Class super = class_getSuperclass_impl(isa);
1516                    while (super) {
1517                        if (super == lldb_info->isa) {
1518                            match = true;
1519                            break;
1520                        }
1521                        super = class_getSuperclass_impl(super);
1522                    }
1523                }
1524            }
1525            else
1526                match = true;
1527            if (match) {
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;
1534                }
1535            }
1536        }
1537    }
1538};
1539callback_baton_t baton = { range_callback, compare_callback, 0, {0}, (void *)0x%x, {0} };
1540int 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
1549        baton.matches"""
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
1561                if isa:
1562                    options.type = "isa"
1563                    result.AppendMessage(
1564                        'Searching for all instances of classes or subclasses of "%s" (isa=0x%x)'
1565                        % (class_name, isa)
1566                    )
1567                    user_init_code = user_init_code_format % (
1568                        options.max_matches,
1569                        num_objc_classes,
1570                        isa,
1571                    )
1572                    expr = get_iterate_memory_expr(
1573                        options, process, user_init_code, user_return_code
1574                    )
1575                    arg_str_description = "objective C classes with isa 0x%x" % isa
1576                    display_match_results(
1577                        process,
1578                        result,
1579                        options,
1580                        arg_str_description,
1581                        expr,
1582                        True,
1583                        expr_prefix,
1584                    )
1585                else:
1586                    result.AppendMessage(
1587                        'error: Can\'t find isa for an ObjC class named "%s"'
1588                        % (class_name)
1589                    )
1590            else:
1591                result.AppendMessage(
1592                    'error: expression error for "%s": %s'
1593                    % (addr_expr_str, expr_sbvalue.error)
1594                )
1595    else:
1596        result.AppendMessage("error: command takes one or more C string arguments")
1597
1598
1599if __name__ == "__main__":
1600    lldb.debugger = lldb.SBDebugger.Create()
1601
1602
1603def __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__
1616    )
1617    debugger.HandleCommand(
1618        "command script add -o -f %s.find_variable find_variable" % __name__
1619    )
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__)
1622    print(
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.'
1624    )
1625