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