You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1524 lines
60 KiB
1524 lines
60 KiB
#!/usr/bin/env python
|
|
|
|
#----------------------------------------------------------------------
|
|
# This module is designed to live inside the "lldb" python package
|
|
# in the "lldb.macosx" package. To use this in the embedded python
|
|
# interpreter using "lldb" just import it:
|
|
#
|
|
# (lldb) script import lldb.macosx.heap
|
|
#----------------------------------------------------------------------
|
|
|
|
from __future__ import print_function
|
|
import lldb
|
|
import optparse
|
|
import os
|
|
import os.path
|
|
import re
|
|
import shlex
|
|
import string
|
|
import sys
|
|
import tempfile
|
|
import lldb.utils.symbolication
|
|
|
|
g_libheap_dylib_dir = None
|
|
g_libheap_dylib_dict = dict()
|
|
|
|
|
|
def get_iterate_memory_expr(
|
|
options,
|
|
process,
|
|
user_init_code,
|
|
user_return_code):
|
|
expr = '''
|
|
typedef unsigned natural_t;
|
|
typedef uintptr_t vm_size_t;
|
|
typedef uintptr_t vm_address_t;
|
|
typedef natural_t task_t;
|
|
typedef int kern_return_t;
|
|
#define KERN_SUCCESS 0
|
|
typedef void (*range_callback_t)(task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size);
|
|
'''
|
|
if options.search_vm_regions:
|
|
expr += '''
|
|
typedef int vm_prot_t;
|
|
typedef unsigned int vm_inherit_t;
|
|
typedef unsigned long long memory_object_offset_t;
|
|
typedef unsigned int boolean_t;
|
|
typedef int vm_behavior_t;
|
|
typedef uint32_t vm32_object_id_t;
|
|
typedef natural_t mach_msg_type_number_t;
|
|
typedef uint64_t mach_vm_address_t;
|
|
typedef uint64_t mach_vm_offset_t;
|
|
typedef uint64_t mach_vm_size_t;
|
|
typedef uint64_t vm_map_offset_t;
|
|
typedef uint64_t vm_map_address_t;
|
|
typedef uint64_t vm_map_size_t;
|
|
#define VM_PROT_NONE ((vm_prot_t) 0x00)
|
|
#define VM_PROT_READ ((vm_prot_t) 0x01)
|
|
#define VM_PROT_WRITE ((vm_prot_t) 0x02)
|
|
#define VM_PROT_EXECUTE ((vm_prot_t) 0x04)
|
|
typedef struct vm_region_submap_short_info_data_64_t {
|
|
vm_prot_t protection;
|
|
vm_prot_t max_protection;
|
|
vm_inherit_t inheritance;
|
|
memory_object_offset_t offset; // offset into object/map
|
|
unsigned int user_tag; // user tag on map entry
|
|
unsigned int ref_count; // obj/map mappers, etc
|
|
unsigned short shadow_depth; // only for obj
|
|
unsigned char external_pager; // only for obj
|
|
unsigned char share_mode; // see enumeration
|
|
boolean_t is_submap; // submap vs obj
|
|
vm_behavior_t behavior; // access behavior hint
|
|
vm32_object_id_t object_id; // obj/map name, not a handle
|
|
unsigned short user_wired_count;
|
|
} vm_region_submap_short_info_data_64_t;
|
|
#define VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 ((mach_msg_type_number_t)(sizeof(vm_region_submap_short_info_data_64_t)/sizeof(int)))'''
|
|
if user_init_code:
|
|
expr += user_init_code
|
|
expr += '''
|
|
task_t task = (task_t)mach_task_self();
|
|
mach_vm_address_t vm_region_base_addr;
|
|
mach_vm_size_t vm_region_size;
|
|
natural_t vm_region_depth;
|
|
vm_region_submap_short_info_data_64_t vm_region_info;
|
|
kern_return_t err;
|
|
for (vm_region_base_addr = 0, vm_region_size = 1; vm_region_size != 0; vm_region_base_addr += vm_region_size)
|
|
{
|
|
mach_msg_type_number_t vm_region_info_size = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
|
|
err = (kern_return_t)mach_vm_region_recurse (task,
|
|
&vm_region_base_addr,
|
|
&vm_region_size,
|
|
&vm_region_depth,
|
|
&vm_region_info,
|
|
&vm_region_info_size);
|
|
if (err)
|
|
break;
|
|
// Check all read + write regions. This will cover the thread stacks
|
|
// and any regions of memory like __DATA segments, that might contain
|
|
// data we are looking for
|
|
if (vm_region_info.protection & VM_PROT_WRITE &&
|
|
vm_region_info.protection & VM_PROT_READ)
|
|
{
|
|
baton.callback (task,
|
|
&baton,
|
|
64,
|
|
vm_region_base_addr,
|
|
vm_region_size);
|
|
}
|
|
}'''
|
|
else:
|
|
if options.search_stack:
|
|
expr += get_thread_stack_ranges_struct(process)
|
|
if options.search_segments:
|
|
expr += get_sections_ranges_struct(process)
|
|
if user_init_code:
|
|
expr += user_init_code
|
|
if options.search_heap:
|
|
expr += '''
|
|
#define MALLOC_PTR_IN_USE_RANGE_TYPE 1
|
|
typedef struct vm_range_t {
|
|
vm_address_t address;
|
|
vm_size_t size;
|
|
} vm_range_t;
|
|
typedef kern_return_t (*memory_reader_t)(task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory);
|
|
typedef void (*vm_range_recorder_t)(task_t task, void *baton, unsigned type, vm_range_t *range, unsigned size);
|
|
typedef struct malloc_introspection_t {
|
|
kern_return_t (*enumerator)(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder); /* enumerates all the malloc pointers in use */
|
|
} malloc_introspection_t;
|
|
typedef struct malloc_zone_t {
|
|
void *reserved1[12];
|
|
struct malloc_introspection_t *introspect;
|
|
} malloc_zone_t;
|
|
kern_return_t malloc_get_all_zones(task_t task, memory_reader_t reader, vm_address_t **addresses, unsigned *count);
|
|
memory_reader_t task_peek = [](task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory) -> kern_return_t {
|
|
*local_memory = (void*) remote_address;
|
|
return KERN_SUCCESS;
|
|
};
|
|
vm_address_t *zones = 0;
|
|
unsigned int num_zones = 0;task_t task = 0;
|
|
kern_return_t err = (kern_return_t)malloc_get_all_zones (task, task_peek, &zones, &num_zones);
|
|
if (KERN_SUCCESS == err)
|
|
{
|
|
for (unsigned int i=0; i<num_zones; ++i)
|
|
{
|
|
const malloc_zone_t *zone = (const malloc_zone_t *)zones[i];
|
|
if (zone && zone->introspect)
|
|
zone->introspect->enumerator (task,
|
|
&baton,
|
|
MALLOC_PTR_IN_USE_RANGE_TYPE,
|
|
(vm_address_t)zone,
|
|
task_peek,
|
|
[] (task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned size) -> void
|
|
{
|
|
range_callback_t callback = ((callback_baton_t *)baton)->callback;
|
|
for (unsigned i=0; i<size; ++i)
|
|
{
|
|
callback (task, baton, type, ranges[i].address, ranges[i].size);
|
|
}
|
|
});
|
|
}
|
|
}'''
|
|
|
|
if options.search_stack:
|
|
expr += '''
|
|
#ifdef NUM_STACKS
|
|
// Call the callback for the thread stack ranges
|
|
for (uint32_t i=0; i<NUM_STACKS; ++i) {
|
|
range_callback(task, &baton, 8, stacks[i].base, stacks[i].size);
|
|
if (STACK_RED_ZONE_SIZE > 0) {
|
|
range_callback(task, &baton, 16, stacks[i].base - STACK_RED_ZONE_SIZE, STACK_RED_ZONE_SIZE);
|
|
}
|
|
}
|
|
#endif'''
|
|
|
|
if options.search_segments:
|
|
expr += '''
|
|
#ifdef NUM_SEGMENTS
|
|
// Call the callback for all segments
|
|
for (uint32_t i=0; i<NUM_SEGMENTS; ++i)
|
|
range_callback(task, &baton, 32, segments[i].base, segments[i].size);
|
|
#endif'''
|
|
|
|
if user_return_code:
|
|
expr += "\n%s" % (user_return_code,)
|
|
|
|
return expr
|
|
|
|
|
|
def get_member_types_for_offset(value_type, offset, member_list):
|
|
member = value_type.GetFieldAtIndex(0)
|
|
search_bases = False
|
|
if member:
|
|
if member.GetOffsetInBytes() <= offset:
|
|
for field_idx in range(value_type.GetNumberOfFields()):
|
|
member = value_type.GetFieldAtIndex(field_idx)
|
|
member_byte_offset = member.GetOffsetInBytes()
|
|
member_end_byte_offset = member_byte_offset + member.type.size
|
|
if member_byte_offset <= offset and offset < member_end_byte_offset:
|
|
member_list.append(member)
|
|
get_member_types_for_offset(
|
|
member.type, offset - member_byte_offset, member_list)
|
|
return
|
|
else:
|
|
search_bases = True
|
|
else:
|
|
search_bases = True
|
|
if search_bases:
|
|
for field_idx in range(value_type.GetNumberOfDirectBaseClasses()):
|
|
member = value_type.GetDirectBaseClassAtIndex(field_idx)
|
|
member_byte_offset = member.GetOffsetInBytes()
|
|
member_end_byte_offset = member_byte_offset + member.type.size
|
|
if member_byte_offset <= offset and offset < member_end_byte_offset:
|
|
member_list.append(member)
|
|
get_member_types_for_offset(
|
|
member.type, offset - member_byte_offset, member_list)
|
|
return
|
|
for field_idx in range(value_type.GetNumberOfVirtualBaseClasses()):
|
|
member = value_type.GetVirtualBaseClassAtIndex(field_idx)
|
|
member_byte_offset = member.GetOffsetInBytes()
|
|
member_end_byte_offset = member_byte_offset + member.type.size
|
|
if member_byte_offset <= offset and offset < member_end_byte_offset:
|
|
member_list.append(member)
|
|
get_member_types_for_offset(
|
|
member.type, offset - member_byte_offset, member_list)
|
|
return
|
|
|
|
|
|
def append_regex_callback(option, opt, value, parser):
|
|
try:
|
|
ivar_regex = re.compile(value)
|
|
parser.values.ivar_regex_exclusions.append(ivar_regex)
|
|
except:
|
|
print('error: an exception was thrown when compiling the ivar regular expression for "%s"' % value)
|
|
|
|
|
|
def add_common_options(parser):
|
|
parser.add_option(
|
|
'-v',
|
|
'--verbose',
|
|
action='store_true',
|
|
dest='verbose',
|
|
help='display verbose debug info',
|
|
default=False)
|
|
parser.add_option(
|
|
'-t',
|
|
'--type',
|
|
action='store_true',
|
|
dest='print_type',
|
|
help='print the full value of the type for each matching malloc block',
|
|
default=False)
|
|
parser.add_option(
|
|
'-o',
|
|
'--po',
|
|
action='store_true',
|
|
dest='print_object_description',
|
|
help='print the object descriptions for any matches',
|
|
default=False)
|
|
parser.add_option(
|
|
'-z',
|
|
'--size',
|
|
action='store_true',
|
|
dest='show_size',
|
|
help='print the allocation size in bytes',
|
|
default=False)
|
|
parser.add_option(
|
|
'-r',
|
|
'--range',
|
|
action='store_true',
|
|
dest='show_range',
|
|
help='print the allocation address range instead of just the allocation base address',
|
|
default=False)
|
|
parser.add_option(
|
|
'-m',
|
|
'--memory',
|
|
action='store_true',
|
|
dest='memory',
|
|
help='dump the memory for each matching block',
|
|
default=False)
|
|
parser.add_option(
|
|
'-f',
|
|
'--format',
|
|
type='string',
|
|
dest='format',
|
|
help='the format to use when dumping memory if --memory is specified',
|
|
default=None)
|
|
parser.add_option(
|
|
'-I',
|
|
'--omit-ivar-regex',
|
|
type='string',
|
|
action='callback',
|
|
callback=append_regex_callback,
|
|
dest='ivar_regex_exclusions',
|
|
default=[],
|
|
help='specify one or more regular expressions used to backlist any matches that are in ivars')
|
|
parser.add_option(
|
|
'-s',
|
|
'--stack',
|
|
action='store_true',
|
|
dest='stack',
|
|
help='gets the stack that allocated each malloc block if MallocStackLogging is enabled',
|
|
default=False)
|
|
parser.add_option(
|
|
'-S',
|
|
'--stack-history',
|
|
action='store_true',
|
|
dest='stack_history',
|
|
help='gets the stack history for all allocations whose start address matches each malloc block if MallocStackLogging is enabled',
|
|
default=False)
|
|
parser.add_option(
|
|
'-F',
|
|
'--max-frames',
|
|
type='int',
|
|
dest='max_frames',
|
|
help='the maximum number of stack frames to print when using the --stack or --stack-history options (default=128)',
|
|
default=128)
|
|
parser.add_option(
|
|
'-H',
|
|
'--max-history',
|
|
type='int',
|
|
dest='max_history',
|
|
help='the maximum number of stack history backtraces to print for each allocation when using the --stack-history option (default=16)',
|
|
default=16)
|
|
parser.add_option(
|
|
'-M',
|
|
'--max-matches',
|
|
type='int',
|
|
dest='max_matches',
|
|
help='the maximum number of matches to print',
|
|
default=32)
|
|
parser.add_option(
|
|
'-O',
|
|
'--offset',
|
|
type='int',
|
|
dest='offset',
|
|
help='the matching data must be at this offset',
|
|
default=-1)
|
|
parser.add_option(
|
|
'--ignore-stack',
|
|
action='store_false',
|
|
dest='search_stack',
|
|
help="Don't search the stack when enumerating memory",
|
|
default=True)
|
|
parser.add_option(
|
|
'--ignore-heap',
|
|
action='store_false',
|
|
dest='search_heap',
|
|
help="Don't search the heap allocations when enumerating memory",
|
|
default=True)
|
|
parser.add_option(
|
|
'--ignore-segments',
|
|
action='store_false',
|
|
dest='search_segments',
|
|
help="Don't search readable executable segments enumerating memory",
|
|
default=True)
|
|
parser.add_option(
|
|
'-V',
|
|
'--vm-regions',
|
|
action='store_true',
|
|
dest='search_vm_regions',
|
|
help='Check all VM regions instead of searching the heap, stack and segments',
|
|
default=False)
|
|
|
|
|
|
def type_flags_to_string(type_flags):
|
|
if type_flags == 0:
|
|
type_str = 'free'
|
|
elif type_flags & 2:
|
|
type_str = 'malloc'
|
|
elif type_flags & 4:
|
|
type_str = 'free'
|
|
elif type_flags & 1:
|
|
type_str = 'generic'
|
|
elif type_flags & 8:
|
|
type_str = 'stack'
|
|
elif type_flags & 16:
|
|
type_str = 'stack (red zone)'
|
|
elif type_flags & 32:
|
|
type_str = 'segment'
|
|
elif type_flags & 64:
|
|
type_str = 'vm_region'
|
|
else:
|
|
type_str = hex(type_flags)
|
|
return type_str
|
|
|
|
|
|
def find_variable_containing_address(verbose, frame, match_addr):
|
|
variables = frame.GetVariables(True, True, True, True)
|
|
matching_var = None
|
|
for var in variables:
|
|
var_addr = var.GetLoadAddress()
|
|
if var_addr != lldb.LLDB_INVALID_ADDRESS:
|
|
byte_size = var.GetType().GetByteSize()
|
|
if verbose:
|
|
print('frame #%u: [%#x - %#x) %s' % (frame.GetFrameID(), var.load_addr, var.load_addr + byte_size, var.name))
|
|
if var_addr == match_addr:
|
|
if verbose:
|
|
print('match')
|
|
return var
|
|
else:
|
|
if byte_size > 0 and var_addr <= match_addr and match_addr < (
|
|
var_addr + byte_size):
|
|
if verbose:
|
|
print('match')
|
|
return var
|
|
return None
|
|
|
|
|
|
def find_frame_for_stack_address(process, addr):
|
|
closest_delta = sys.maxsize
|
|
closest_frame = None
|
|
# print 'find_frame_for_stack_address(%#x)' % (addr)
|
|
for thread in process:
|
|
prev_sp = lldb.LLDB_INVALID_ADDRESS
|
|
for frame in thread:
|
|
cfa = frame.GetCFA()
|
|
# print 'frame #%u: cfa = %#x' % (frame.GetFrameID(), cfa)
|
|
if addr < cfa:
|
|
delta = cfa - addr
|
|
# print '%#x < %#x, delta = %i' % (addr, cfa, delta)
|
|
if delta < closest_delta:
|
|
# print 'closest'
|
|
closest_delta = delta
|
|
closest_frame = frame
|
|
# else:
|
|
# print 'delta >= closest_delta'
|
|
return closest_frame
|
|
|
|
|
|
def type_flags_to_description(
|
|
process,
|
|
type_flags,
|
|
ptr_addr,
|
|
ptr_size,
|
|
offset,
|
|
match_addr):
|
|
show_offset = False
|
|
if type_flags == 0 or type_flags & 4:
|
|
type_str = 'free(%#x)' % (ptr_addr,)
|
|
elif type_flags & 2 or type_flags & 1:
|
|
type_str = 'malloc(%6u) -> %#x' % (ptr_size, ptr_addr)
|
|
show_offset = True
|
|
elif type_flags & 8:
|
|
type_str = 'stack'
|
|
frame = find_frame_for_stack_address(process, match_addr)
|
|
if frame:
|
|
type_str += ' in frame #%u of thread #%u: tid %#x' % (frame.GetFrameID(
|
|
), frame.GetThread().GetIndexID(), frame.GetThread().GetThreadID())
|
|
variables = frame.GetVariables(True, True, True, True)
|
|
matching_var = None
|
|
for var in variables:
|
|
var_addr = var.GetLoadAddress()
|
|
if var_addr != lldb.LLDB_INVALID_ADDRESS:
|
|
# print 'variable "%s" @ %#x (%#x)' % (var.name, var.load_addr,
|
|
# match_addr)
|
|
if var_addr == match_addr:
|
|
matching_var = var
|
|
break
|
|
else:
|
|
byte_size = var.GetType().GetByteSize()
|
|
if byte_size > 0 and var_addr <= match_addr and match_addr < (
|
|
var_addr + byte_size):
|
|
matching_var = var
|
|
break
|
|
if matching_var:
|
|
type_str += ' in variable at %#x:\n %s' % (
|
|
matching_var.GetLoadAddress(), matching_var)
|
|
elif type_flags & 16:
|
|
type_str = 'stack (red zone)'
|
|
elif type_flags & 32:
|
|
sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset)
|
|
type_str = 'segment [%#x - %#x), %s + %u, %s' % (
|
|
ptr_addr, ptr_addr + ptr_size, sb_addr.section.name, sb_addr.offset, sb_addr)
|
|
elif type_flags & 64:
|
|
sb_addr = process.GetTarget().ResolveLoadAddress(ptr_addr + offset)
|
|
type_str = 'vm_region [%#x - %#x), %s + %u, %s' % (
|
|
ptr_addr, ptr_addr + ptr_size, sb_addr.section.name, sb_addr.offset, sb_addr)
|
|
else:
|
|
type_str = '%#x' % (ptr_addr,)
|
|
show_offset = True
|
|
if show_offset and offset != 0:
|
|
type_str += ' + %-6u' % (offset,)
|
|
return type_str
|
|
|
|
|
|
def dump_stack_history_entry(options, result, stack_history_entry, idx):
|
|
address = int(stack_history_entry.address)
|
|
if address:
|
|
type_flags = int(stack_history_entry.type_flags)
|
|
symbolicator = lldb.utils.symbolication.Symbolicator()
|
|
symbolicator.target = lldb.debugger.GetSelectedTarget()
|
|
type_str = type_flags_to_string(type_flags)
|
|
result.AppendMessage(
|
|
'stack[%u]: addr = 0x%x, type=%s, frames:' %
|
|
(idx, address, type_str))
|
|
frame_idx = 0
|
|
idx = 0
|
|
pc = int(stack_history_entry.frames[idx])
|
|
while pc != 0:
|
|
if pc >= 0x1000:
|
|
frames = symbolicator.symbolicate(pc)
|
|
if frames:
|
|
for frame in frames:
|
|
result.AppendMessage(
|
|
' [%u] %s' %
|
|
(frame_idx, frame))
|
|
frame_idx += 1
|
|
else:
|
|
result.AppendMessage(' [%u] 0x%x' % (frame_idx, pc))
|
|
frame_idx += 1
|
|
idx = idx + 1
|
|
pc = int(stack_history_entry.frames[idx])
|
|
else:
|
|
pc = 0
|
|
if idx >= options.max_frames:
|
|
result.AppendMessage(
|
|
'warning: the max number of stack frames (%u) was reached, use the "--max-frames=<COUNT>" option to see more frames' %
|
|
(options.max_frames))
|
|
|
|
result.AppendMessage('')
|
|
|
|
|
|
def dump_stack_history_entries(options, result, addr, history):
|
|
# malloc_stack_entry *get_stack_history_for_address (const void * addr)
|
|
expr_prefix = '''
|
|
typedef int kern_return_t;
|
|
typedef struct $malloc_stack_entry {
|
|
uint64_t address;
|
|
uint64_t argument;
|
|
uint32_t type_flags;
|
|
uint32_t num_frames;
|
|
uint64_t frames[512];
|
|
kern_return_t err;
|
|
} $malloc_stack_entry;
|
|
'''
|
|
single_expr = '''
|
|
#define MAX_FRAMES %u
|
|
typedef unsigned task_t;
|
|
$malloc_stack_entry stack;
|
|
stack.address = 0x%x;
|
|
stack.type_flags = 2;
|
|
stack.num_frames = 0;
|
|
stack.frames[0] = 0;
|
|
uint32_t max_stack_frames = MAX_FRAMES;
|
|
stack.err = (kern_return_t)__mach_stack_logging_get_frames (
|
|
(task_t)mach_task_self(),
|
|
stack.address,
|
|
&stack.frames[0],
|
|
max_stack_frames,
|
|
&stack.num_frames);
|
|
if (stack.num_frames < MAX_FRAMES)
|
|
stack.frames[stack.num_frames] = 0;
|
|
else
|
|
stack.frames[MAX_FRAMES-1] = 0;
|
|
stack''' % (options.max_frames, addr)
|
|
|
|
history_expr = '''
|
|
typedef int kern_return_t;
|
|
typedef unsigned task_t;
|
|
#define MAX_FRAMES %u
|
|
#define MAX_HISTORY %u
|
|
typedef struct mach_stack_logging_record_t {
|
|
uint32_t type_flags;
|
|
uint64_t stack_identifier;
|
|
uint64_t argument;
|
|
uint64_t address;
|
|
} mach_stack_logging_record_t;
|
|
typedef void (*enumerate_callback_t)(mach_stack_logging_record_t, void *);
|
|
typedef struct malloc_stack_entry {
|
|
uint64_t address;
|
|
uint64_t argument;
|
|
uint32_t type_flags;
|
|
uint32_t num_frames;
|
|
uint64_t frames[MAX_FRAMES];
|
|
kern_return_t frames_err;
|
|
} malloc_stack_entry;
|
|
typedef struct $malloc_stack_history {
|
|
task_t task;
|
|
unsigned idx;
|
|
malloc_stack_entry entries[MAX_HISTORY];
|
|
} $malloc_stack_history;
|
|
$malloc_stack_history lldb_info = { (task_t)mach_task_self(), 0 };
|
|
uint32_t max_stack_frames = MAX_FRAMES;
|
|
enumerate_callback_t callback = [] (mach_stack_logging_record_t stack_record, void *baton) -> void {
|
|
$malloc_stack_history *lldb_info = ($malloc_stack_history *)baton;
|
|
if (lldb_info->idx < MAX_HISTORY) {
|
|
malloc_stack_entry *stack_entry = &(lldb_info->entries[lldb_info->idx]);
|
|
stack_entry->address = stack_record.address;
|
|
stack_entry->type_flags = stack_record.type_flags;
|
|
stack_entry->argument = stack_record.argument;
|
|
stack_entry->num_frames = 0;
|
|
stack_entry->frames[0] = 0;
|
|
stack_entry->frames_err = (kern_return_t)__mach_stack_logging_frames_for_uniqued_stack (
|
|
lldb_info->task,
|
|
stack_record.stack_identifier,
|
|
stack_entry->frames,
|
|
(uint32_t)MAX_FRAMES,
|
|
&stack_entry->num_frames);
|
|
// Terminate the frames with zero if there is room
|
|
if (stack_entry->num_frames < MAX_FRAMES)
|
|
stack_entry->frames[stack_entry->num_frames] = 0;
|
|
}
|
|
++lldb_info->idx;
|
|
};
|
|
(kern_return_t)__mach_stack_logging_enumerate_records (lldb_info.task, (uint64_t)0x%x, callback, &lldb_info);
|
|
lldb_info''' % (options.max_frames, options.max_history, addr)
|
|
|
|
frame = lldb.debugger.GetSelectedTarget().GetProcess(
|
|
).GetSelectedThread().GetSelectedFrame()
|
|
if history:
|
|
expr = history_expr
|
|
else:
|
|
expr = single_expr
|
|
expr_options = lldb.SBExpressionOptions()
|
|
expr_options.SetIgnoreBreakpoints(True)
|
|
expr_options.SetTimeoutInMicroSeconds(5 * 1000 * 1000) # 5 second timeout
|
|
expr_options.SetTryAllThreads(True)
|
|
expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
|
|
expr_options.SetPrefix(expr_prefix)
|
|
expr_sbvalue = frame.EvaluateExpression(expr, expr_options)
|
|
if options.verbose:
|
|
print("expression:")
|
|
print(expr)
|
|
print("expression result:")
|
|
print(expr_sbvalue)
|
|
if expr_sbvalue.error.Success():
|
|
if history:
|
|
malloc_stack_history = lldb.value(expr_sbvalue)
|
|
num_stacks = int(malloc_stack_history.idx)
|
|
if num_stacks <= options.max_history:
|
|
i_max = num_stacks
|
|
else:
|
|
i_max = options.max_history
|
|
for i in range(i_max):
|
|
stack_history_entry = malloc_stack_history.entries[i]
|
|
dump_stack_history_entry(
|
|
options, result, stack_history_entry, i)
|
|
if num_stacks > options.max_history:
|
|
result.AppendMessage(
|
|
'warning: the max number of stacks (%u) was reached, use the "--max-history=%u" option to see all of the stacks' %
|
|
(options.max_history, num_stacks))
|
|
else:
|
|
stack_history_entry = lldb.value(expr_sbvalue)
|
|
dump_stack_history_entry(options, result, stack_history_entry, 0)
|
|
|
|
else:
|
|
result.AppendMessage(
|
|
'error: expression failed "%s" => %s' %
|
|
(expr, expr_sbvalue.error))
|
|
|
|
|
|
def display_match_results(
|
|
process,
|
|
result,
|
|
options,
|
|
arg_str_description,
|
|
expr,
|
|
print_no_matches,
|
|
expr_prefix=None):
|
|
frame = lldb.debugger.GetSelectedTarget().GetProcess(
|
|
).GetSelectedThread().GetSelectedFrame()
|
|
if not frame:
|
|
result.AppendMessage('error: invalid frame')
|
|
return 0
|
|
expr_options = lldb.SBExpressionOptions()
|
|
expr_options.SetIgnoreBreakpoints(True)
|
|
expr_options.SetFetchDynamicValue(lldb.eNoDynamicValues)
|
|
expr_options.SetTimeoutInMicroSeconds(
|
|
30 * 1000 * 1000) # 30 second timeout
|
|
expr_options.SetTryAllThreads(False)
|
|
expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
|
|
if expr_prefix:
|
|
expr_options.SetPrefix(expr_prefix)
|
|
expr_sbvalue = frame.EvaluateExpression(expr, expr_options)
|
|
if options.verbose:
|
|
print("expression:")
|
|
print(expr)
|
|
print("expression result:")
|
|
print(expr_sbvalue)
|
|
if expr_sbvalue.error.Success():
|
|
match_value = lldb.value(expr_sbvalue)
|
|
i = 0
|
|
match_idx = 0
|
|
while True:
|
|
print_entry = True
|
|
match_entry = match_value[i]
|
|
i += 1
|
|
if i > options.max_matches:
|
|
result.AppendMessage(
|
|
'warning: the max number of matches (%u) was reached, use the --max-matches option to get more results' %
|
|
(options.max_matches))
|
|
break
|
|
malloc_addr = match_entry.addr.sbvalue.unsigned
|
|
if malloc_addr == 0:
|
|
break
|
|
malloc_size = int(match_entry.size)
|
|
offset = int(match_entry.offset)
|
|
|
|
if options.offset >= 0 and options.offset != offset:
|
|
print_entry = False
|
|
else:
|
|
match_addr = malloc_addr + offset
|
|
type_flags = int(match_entry.type)
|
|
#result.AppendMessage (hex(malloc_addr + offset))
|
|
if type_flags == 64:
|
|
search_stack_old = options.search_stack
|
|
search_segments_old = options.search_segments
|
|
search_heap_old = options.search_heap
|
|
search_vm_regions = options.search_vm_regions
|
|
options.search_stack = True
|
|
options.search_segments = True
|
|
options.search_heap = True
|
|
options.search_vm_regions = False
|
|
if malloc_info_impl(lldb.debugger, result, options, [
|
|
hex(malloc_addr + offset)]):
|
|
print_entry = False
|
|
options.search_stack = search_stack_old
|
|
options.search_segments = search_segments_old
|
|
options.search_heap = search_heap_old
|
|
options.search_vm_regions = search_vm_regions
|
|
if print_entry:
|
|
description = '%#16.16x: %s' % (match_addr, type_flags_to_description(
|
|
process, type_flags, malloc_addr, malloc_size, offset, match_addr))
|
|
if options.show_size:
|
|
description += ' <%5u>' % (malloc_size)
|
|
if options.show_range:
|
|
description += ' [%#x - %#x)' % (
|
|
malloc_addr, malloc_addr + malloc_size)
|
|
derefed_dynamic_value = None
|
|
dynamic_value = match_entry.addr.sbvalue.GetDynamicValue(
|
|
lldb.eDynamicCanRunTarget)
|
|
if dynamic_value.type.name == 'void *':
|
|
if options.type == 'pointer' and malloc_size == 4096:
|
|
error = lldb.SBError()
|
|
process = expr_sbvalue.GetProcess()
|
|
target = expr_sbvalue.GetTarget()
|
|
data = bytearray(
|
|
process.ReadMemory(
|
|
malloc_addr, 16, error))
|
|
if data == '\xa1\xa1\xa1\xa1AUTORELEASE!':
|
|
ptr_size = target.addr_size
|
|
thread = process.ReadUnsignedFromMemory(
|
|
malloc_addr + 16 + ptr_size, ptr_size, error)
|
|
# 4 bytes 0xa1a1a1a1
|
|
# 12 bytes 'AUTORELEASE!'
|
|
# ptr bytes autorelease insertion point
|
|
# ptr bytes pthread_t
|
|
# ptr bytes next colder page
|
|
# ptr bytes next hotter page
|
|
# 4 bytes this page's depth in the list
|
|
# 4 bytes high-water mark
|
|
description += ' AUTORELEASE! for pthread_t %#x' % (
|
|
thread)
|
|
# else:
|
|
# description += 'malloc(%u)' % (malloc_size)
|
|
# else:
|
|
# description += 'malloc(%u)' % (malloc_size)
|
|
else:
|
|
derefed_dynamic_value = dynamic_value.deref
|
|
if derefed_dynamic_value:
|
|
derefed_dynamic_type = derefed_dynamic_value.type
|
|
derefed_dynamic_type_size = derefed_dynamic_type.size
|
|
derefed_dynamic_type_name = derefed_dynamic_type.name
|
|
description += ' '
|
|
description += derefed_dynamic_type_name
|
|
if offset < derefed_dynamic_type_size:
|
|
member_list = list()
|
|
get_member_types_for_offset(
|
|
derefed_dynamic_type, offset, member_list)
|
|
if member_list:
|
|
member_path = ''
|
|
for member in member_list:
|
|
member_name = member.name
|
|
if member_name:
|
|
if member_path:
|
|
member_path += '.'
|
|
member_path += member_name
|
|
if member_path:
|
|
if options.ivar_regex_exclusions:
|
|
for ivar_regex in options.ivar_regex_exclusions:
|
|
if ivar_regex.match(
|
|
member_path):
|
|
print_entry = False
|
|
description += '.%s' % (member_path)
|
|
else:
|
|
description += '%u bytes after %s' % (
|
|
offset - derefed_dynamic_type_size, derefed_dynamic_type_name)
|
|
else:
|
|
# strip the "*" from the end of the name since we
|
|
# were unable to dereference this
|
|
description += dynamic_value.type.name[0:-1]
|
|
if print_entry:
|
|
match_idx += 1
|
|
result_output = ''
|
|
if description:
|
|
result_output += description
|
|
if options.print_type and derefed_dynamic_value:
|
|
result_output += ' %s' % (derefed_dynamic_value)
|
|
if options.print_object_description and dynamic_value:
|
|
desc = dynamic_value.GetObjectDescription()
|
|
if desc:
|
|
result_output += '\n%s' % (desc)
|
|
if result_output:
|
|
result.AppendMessage(result_output)
|
|
if options.memory:
|
|
cmd_result = lldb.SBCommandReturnObject()
|
|
if options.format is None:
|
|
memory_command = "memory read --force 0x%x 0x%x" % (
|
|
malloc_addr, malloc_addr + malloc_size)
|
|
else:
|
|
memory_command = "memory read --force -f %s 0x%x 0x%x" % (
|
|
options.format, malloc_addr, malloc_addr + malloc_size)
|
|
if options.verbose:
|
|
result.AppendMessage(memory_command)
|
|
lldb.debugger.GetCommandInterpreter().HandleCommand(memory_command, cmd_result)
|
|
result.AppendMessage(cmd_result.GetOutput())
|
|
if options.stack_history:
|
|
dump_stack_history_entries(options, result, malloc_addr, 1)
|
|
elif options.stack:
|
|
dump_stack_history_entries(options, result, malloc_addr, 0)
|
|
return i
|
|
else:
|
|
result.AppendMessage(str(expr_sbvalue.error))
|
|
return 0
|
|
|
|
|
|
def get_ptr_refs_options():
|
|
usage = "usage: %prog [options] <EXPR> [EXPR ...]"
|
|
description = '''Searches all allocations on the heap for pointer values on
|
|
darwin user space programs. Any matches that were found will dump the malloc
|
|
blocks that contain the pointers and might be able to print what kind of
|
|
objects the pointers are contained in using dynamic type information in the
|
|
program.'''
|
|
parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='ptr_refs',
|
|
usage=usage)
|
|
add_common_options(parser)
|
|
return parser
|
|
|
|
|
|
def find_variable(debugger, command, result, dict):
|
|
usage = "usage: %prog [options] <ADDR> [ADDR ...]"
|
|
description = '''Searches for a local variable in all frames that contains a hex ADDR.'''
|
|
command_args = shlex.split(command)
|
|
parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='find_variable',
|
|
usage=usage)
|
|
parser.add_option(
|
|
'-v',
|
|
'--verbose',
|
|
action='store_true',
|
|
dest='verbose',
|
|
help='display verbose debug info',
|
|
default=False)
|
|
try:
|
|
(options, args) = parser.parse_args(command_args)
|
|
except:
|
|
return
|
|
|
|
process = debugger.GetSelectedTarget().GetProcess()
|
|
if not process:
|
|
result.AppendMessage('error: invalid process')
|
|
return
|
|
|
|
for arg in args:
|
|
var_addr = int(arg, 16)
|
|
print("Finding a variable with address %#x..." % (var_addr), file=result)
|
|
done = False
|
|
for thread in process:
|
|
for frame in thread:
|
|
var = find_variable_containing_address(
|
|
options.verbose, frame, var_addr)
|
|
if var:
|
|
print(var)
|
|
done = True
|
|
break
|
|
if done:
|
|
break
|
|
|
|
|
|
def ptr_refs(debugger, command, result, dict):
|
|
command_args = shlex.split(command)
|
|
parser = get_ptr_refs_options()
|
|
try:
|
|
(options, args) = parser.parse_args(command_args)
|
|
except:
|
|
return
|
|
|
|
process = debugger.GetSelectedTarget().GetProcess()
|
|
if not process:
|
|
result.AppendMessage('error: invalid process')
|
|
return
|
|
frame = process.GetSelectedThread().GetSelectedFrame()
|
|
if not frame:
|
|
result.AppendMessage('error: invalid frame')
|
|
return
|
|
|
|
options.type = 'pointer'
|
|
if options.format is None:
|
|
options.format = "A" # 'A' is "address" format
|
|
|
|
if args:
|
|
# When we initialize the expression, we must define any types that
|
|
# we will need when looking at every allocation. We must also define
|
|
# a type named callback_baton_t and make an instance named "baton"
|
|
# and initialize it how ever we want to. The address of "baton" will
|
|
# be passed into our range callback. callback_baton_t must contain
|
|
# a member named "callback" whose type is "range_callback_t". This
|
|
# will be used by our zone callbacks to call the range callback for
|
|
# each malloc range.
|
|
expr_prefix = '''
|
|
struct $malloc_match {
|
|
void *addr;
|
|
uintptr_t size;
|
|
uintptr_t offset;
|
|
uintptr_t type;
|
|
};
|
|
'''
|
|
user_init_code_format = '''
|
|
#define MAX_MATCHES %u
|
|
typedef struct callback_baton_t {
|
|
range_callback_t callback;
|
|
unsigned num_matches;
|
|
$malloc_match matches[MAX_MATCHES];
|
|
void *ptr;
|
|
} callback_baton_t;
|
|
range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
|
|
callback_baton_t *lldb_info = (callback_baton_t *)baton;
|
|
typedef void* T;
|
|
const unsigned size = sizeof(T);
|
|
T *array = (T*)ptr_addr;
|
|
for (unsigned idx = 0; ((idx + 1) * sizeof(T)) <= ptr_size; ++idx) {
|
|
if (array[idx] == lldb_info->ptr) {
|
|
if (lldb_info->num_matches < MAX_MATCHES) {
|
|
lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
|
|
lldb_info->matches[lldb_info->num_matches].size = ptr_size;
|
|
lldb_info->matches[lldb_info->num_matches].offset = idx*sizeof(T);
|
|
lldb_info->matches[lldb_info->num_matches].type = type;
|
|
++lldb_info->num_matches;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
|
|
'''
|
|
# We must also define a snippet of code to be run that returns
|
|
# the result of the expression we run.
|
|
# Here we return NULL if our pointer was not found in any malloc blocks,
|
|
# and we return the address of the matches array so we can then access
|
|
# the matching results
|
|
user_return_code = '''if (baton.num_matches < MAX_MATCHES)
|
|
baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
|
|
baton.matches'''
|
|
# Iterate through all of our pointer expressions and display the
|
|
# results
|
|
for ptr_expr in args:
|
|
user_init_code = user_init_code_format % (
|
|
options.max_matches, ptr_expr)
|
|
expr = get_iterate_memory_expr(
|
|
options, process, user_init_code, user_return_code)
|
|
arg_str_description = 'malloc block containing pointer %s' % ptr_expr
|
|
display_match_results(
|
|
process,
|
|
result,
|
|
options,
|
|
arg_str_description,
|
|
expr,
|
|
True,
|
|
expr_prefix)
|
|
else:
|
|
result.AppendMessage('error: no pointer arguments were given')
|
|
|
|
|
|
def get_cstr_refs_options():
|
|
usage = "usage: %prog [options] <CSTR> [CSTR ...]"
|
|
description = '''Searches all allocations on the heap for C string values on
|
|
darwin user space programs. Any matches that were found will dump the malloc
|
|
blocks that contain the C strings and might be able to print what kind of
|
|
objects the pointers are contained in using dynamic type information in the
|
|
program.'''
|
|
parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='cstr_refs',
|
|
usage=usage)
|
|
add_common_options(parser)
|
|
return parser
|
|
|
|
|
|
def cstr_refs(debugger, command, result, dict):
|
|
command_args = shlex.split(command)
|
|
parser = get_cstr_refs_options()
|
|
try:
|
|
(options, args) = parser.parse_args(command_args)
|
|
except:
|
|
return
|
|
|
|
process = debugger.GetSelectedTarget().GetProcess()
|
|
if not process:
|
|
result.AppendMessage('error: invalid process')
|
|
return
|
|
frame = process.GetSelectedThread().GetSelectedFrame()
|
|
if not frame:
|
|
result.AppendMessage('error: invalid frame')
|
|
return
|
|
|
|
options.type = 'cstr'
|
|
if options.format is None:
|
|
options.format = "Y" # 'Y' is "bytes with ASCII" format
|
|
|
|
if args:
|
|
# When we initialize the expression, we must define any types that
|
|
# we will need when looking at every allocation. We must also define
|
|
# a type named callback_baton_t and make an instance named "baton"
|
|
# and initialize it how ever we want to. The address of "baton" will
|
|
# be passed into our range callback. callback_baton_t must contain
|
|
# a member named "callback" whose type is "range_callback_t". This
|
|
# will be used by our zone callbacks to call the range callback for
|
|
# each malloc range.
|
|
expr_prefix = '''
|
|
struct $malloc_match {
|
|
void *addr;
|
|
uintptr_t size;
|
|
uintptr_t offset;
|
|
uintptr_t type;
|
|
};
|
|
'''
|
|
user_init_code_format = '''
|
|
#define MAX_MATCHES %u
|
|
typedef struct callback_baton_t {
|
|
range_callback_t callback;
|
|
unsigned num_matches;
|
|
$malloc_match matches[MAX_MATCHES];
|
|
const char *cstr;
|
|
unsigned cstr_len;
|
|
} callback_baton_t;
|
|
range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
|
|
callback_baton_t *lldb_info = (callback_baton_t *)baton;
|
|
if (lldb_info->cstr_len < ptr_size) {
|
|
const char *begin = (const char *)ptr_addr;
|
|
const char *end = begin + ptr_size - lldb_info->cstr_len;
|
|
for (const char *s = begin; s < end; ++s) {
|
|
if ((int)memcmp(s, lldb_info->cstr, lldb_info->cstr_len) == 0) {
|
|
if (lldb_info->num_matches < MAX_MATCHES) {
|
|
lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
|
|
lldb_info->matches[lldb_info->num_matches].size = ptr_size;
|
|
lldb_info->matches[lldb_info->num_matches].offset = s - begin;
|
|
lldb_info->matches[lldb_info->num_matches].type = type;
|
|
++lldb_info->num_matches;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const char *cstr = "%s";
|
|
callback_baton_t baton = { range_callback, 0, {0}, cstr, (unsigned)strlen(cstr) };'''
|
|
# We must also define a snippet of code to be run that returns
|
|
# the result of the expression we run.
|
|
# Here we return NULL if our pointer was not found in any malloc blocks,
|
|
# and we return the address of the matches array so we can then access
|
|
# the matching results
|
|
user_return_code = '''if (baton.num_matches < MAX_MATCHES)
|
|
baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
|
|
baton.matches'''
|
|
# Iterate through all of our pointer expressions and display the
|
|
# results
|
|
for cstr in args:
|
|
user_init_code = user_init_code_format % (
|
|
options.max_matches, cstr)
|
|
expr = get_iterate_memory_expr(
|
|
options, process, user_init_code, user_return_code)
|
|
arg_str_description = 'malloc block containing "%s"' % cstr
|
|
display_match_results(
|
|
process,
|
|
result,
|
|
options,
|
|
arg_str_description,
|
|
expr,
|
|
True,
|
|
expr_prefix)
|
|
else:
|
|
result.AppendMessage(
|
|
'error: command takes one or more C string arguments')
|
|
|
|
|
|
def get_malloc_info_options():
|
|
usage = "usage: %prog [options] <EXPR> [EXPR ...]"
|
|
description = '''Searches the heap a malloc block that contains the addresses
|
|
specified as one or more address expressions. Any matches that were found will
|
|
dump the malloc blocks that match or contain the specified address. The matching
|
|
blocks might be able to show what kind of objects they are using dynamic type
|
|
information in the program.'''
|
|
parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='malloc_info',
|
|
usage=usage)
|
|
add_common_options(parser)
|
|
return parser
|
|
|
|
|
|
def malloc_info(debugger, command, result, dict):
|
|
command_args = shlex.split(command)
|
|
parser = get_malloc_info_options()
|
|
try:
|
|
(options, args) = parser.parse_args(command_args)
|
|
except:
|
|
return
|
|
malloc_info_impl(debugger, result, options, args)
|
|
|
|
|
|
def malloc_info_impl(debugger, result, options, args):
|
|
# We are specifically looking for something on the heap only
|
|
options.type = 'malloc_info'
|
|
|
|
process = debugger.GetSelectedTarget().GetProcess()
|
|
if not process:
|
|
result.AppendMessage('error: invalid process')
|
|
return
|
|
frame = process.GetSelectedThread().GetSelectedFrame()
|
|
if not frame:
|
|
result.AppendMessage('error: invalid frame')
|
|
return
|
|
expr_prefix = '''
|
|
struct $malloc_match {
|
|
void *addr;
|
|
uintptr_t size;
|
|
uintptr_t offset;
|
|
uintptr_t type;
|
|
};
|
|
'''
|
|
|
|
user_init_code_format = '''
|
|
typedef struct callback_baton_t {
|
|
range_callback_t callback;
|
|
unsigned num_matches;
|
|
$malloc_match matches[2]; // Two items so they can be NULL terminated
|
|
void *ptr;
|
|
} callback_baton_t;
|
|
range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
|
|
callback_baton_t *lldb_info = (callback_baton_t *)baton;
|
|
if (lldb_info->num_matches == 0) {
|
|
uint8_t *p = (uint8_t *)lldb_info->ptr;
|
|
uint8_t *lo = (uint8_t *)ptr_addr;
|
|
uint8_t *hi = lo + ptr_size;
|
|
if (lo <= p && p < hi) {
|
|
lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
|
|
lldb_info->matches[lldb_info->num_matches].size = ptr_size;
|
|
lldb_info->matches[lldb_info->num_matches].offset = p - lo;
|
|
lldb_info->matches[lldb_info->num_matches].type = type;
|
|
lldb_info->num_matches = 1;
|
|
}
|
|
}
|
|
};
|
|
callback_baton_t baton = { range_callback, 0, {0}, (void *)%s };
|
|
baton.matches[0].addr = 0;
|
|
baton.matches[1].addr = 0;'''
|
|
if args:
|
|
total_matches = 0
|
|
for ptr_expr in args:
|
|
user_init_code = user_init_code_format % (ptr_expr)
|
|
expr = get_iterate_memory_expr(
|
|
options, process, user_init_code, 'baton.matches')
|
|
arg_str_description = 'malloc block that contains %s' % ptr_expr
|
|
total_matches += display_match_results(
|
|
process, result, options, arg_str_description, expr, True, expr_prefix)
|
|
return total_matches
|
|
else:
|
|
result.AppendMessage(
|
|
'error: command takes one or more pointer expressions')
|
|
return 0
|
|
|
|
|
|
def get_thread_stack_ranges_struct(process):
|
|
'''Create code that defines a structure that represents threads stack bounds
|
|
for all threads. It returns a static sized array initialized with all of
|
|
the tid, base, size structs for all the threads.'''
|
|
stack_dicts = list()
|
|
if process:
|
|
i = 0
|
|
for thread in process:
|
|
min_sp = thread.frame[0].sp
|
|
max_sp = min_sp
|
|
for frame in thread.frames:
|
|
sp = frame.sp
|
|
if sp < min_sp:
|
|
min_sp = sp
|
|
if sp > max_sp:
|
|
max_sp = sp
|
|
if min_sp < max_sp:
|
|
stack_dicts.append({'tid': thread.GetThreadID(
|
|
), 'base': min_sp, 'size': max_sp - min_sp, 'index': i})
|
|
i += 1
|
|
stack_dicts_len = len(stack_dicts)
|
|
if stack_dicts_len > 0:
|
|
result = '''
|
|
#define NUM_STACKS %u
|
|
#define STACK_RED_ZONE_SIZE %u
|
|
typedef struct thread_stack_t { uint64_t tid, base, size; } thread_stack_t;
|
|
thread_stack_t stacks[NUM_STACKS];''' % (stack_dicts_len, process.target.GetStackRedZoneSize())
|
|
for stack_dict in stack_dicts:
|
|
result += '''
|
|
stacks[%(index)u].tid = 0x%(tid)x;
|
|
stacks[%(index)u].base = 0x%(base)x;
|
|
stacks[%(index)u].size = 0x%(size)x;''' % stack_dict
|
|
return result
|
|
else:
|
|
return ''
|
|
|
|
|
|
def get_sections_ranges_struct(process):
|
|
'''Create code that defines a structure that represents all segments that
|
|
can contain data for all images in "target". It returns a static sized
|
|
array initialized with all of base, size structs for all the threads.'''
|
|
target = process.target
|
|
segment_dicts = list()
|
|
for (module_idx, module) in enumerate(target.modules):
|
|
for sect_idx in range(module.GetNumSections()):
|
|
section = module.GetSectionAtIndex(sect_idx)
|
|
if not section:
|
|
break
|
|
name = section.name
|
|
if name != '__TEXT' and name != '__LINKEDIT' and name != '__PAGEZERO':
|
|
base = section.GetLoadAddress(target)
|
|
size = section.GetByteSize()
|
|
if base != lldb.LLDB_INVALID_ADDRESS and size > 0:
|
|
segment_dicts.append({'base': base, 'size': size})
|
|
segment_dicts_len = len(segment_dicts)
|
|
if segment_dicts_len > 0:
|
|
result = '''
|
|
#define NUM_SEGMENTS %u
|
|
typedef struct segment_range_t { uint64_t base; uint32_t size; } segment_range_t;
|
|
segment_range_t segments[NUM_SEGMENTS];''' % (segment_dicts_len,)
|
|
for (idx, segment_dict) in enumerate(segment_dicts):
|
|
segment_dict['index'] = idx
|
|
result += '''
|
|
segments[%(index)u].base = 0x%(base)x;
|
|
segments[%(index)u].size = 0x%(size)x;''' % segment_dict
|
|
return result
|
|
else:
|
|
return ''
|
|
|
|
|
|
def section_ptr_refs(debugger, command, result, dict):
|
|
command_args = shlex.split(command)
|
|
usage = "usage: %prog [options] <EXPR> [EXPR ...]"
|
|
description = '''Searches section contents for pointer values in darwin user space programs.'''
|
|
parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='section_ptr_refs',
|
|
usage=usage)
|
|
add_common_options(parser)
|
|
parser.add_option(
|
|
'--section',
|
|
action='append',
|
|
type='string',
|
|
dest='section_names',
|
|
help='section name to search',
|
|
default=list())
|
|
try:
|
|
(options, args) = parser.parse_args(command_args)
|
|
except:
|
|
return
|
|
|
|
options.type = 'pointer'
|
|
|
|
sections = list()
|
|
section_modules = list()
|
|
if not options.section_names:
|
|
result.AppendMessage(
|
|
'error: at least one section must be specified with the --section option')
|
|
return
|
|
|
|
target = debugger.GetSelectedTarget()
|
|
for module in target.modules:
|
|
for section_name in options.section_names:
|
|
section = module.section[section_name]
|
|
if section:
|
|
sections.append(section)
|
|
section_modules.append(module)
|
|
if sections:
|
|
dylid_load_err = load_dylib()
|
|
if dylid_load_err:
|
|
result.AppendMessage(dylid_load_err)
|
|
return
|
|
frame = target.GetProcess().GetSelectedThread().GetSelectedFrame()
|
|
for expr_str in args:
|
|
for (idx, section) in enumerate(sections):
|
|
expr = 'find_pointer_in_memory(0x%xllu, %ullu, (void *)%s)' % (
|
|
section.addr.load_addr, section.size, expr_str)
|
|
arg_str_description = 'section %s.%s containing "%s"' % (
|
|
section_modules[idx].file.fullpath, section.name, expr_str)
|
|
num_matches = display_match_results(
|
|
target.GetProcess(), result, options, arg_str_description, expr, False)
|
|
if num_matches:
|
|
if num_matches < options.max_matches:
|
|
options.max_matches = options.max_matches - num_matches
|
|
else:
|
|
options.max_matches = 0
|
|
if options.max_matches == 0:
|
|
return
|
|
else:
|
|
result.AppendMessage(
|
|
'error: no sections were found that match any of %s' %
|
|
(', '.join(
|
|
options.section_names)))
|
|
|
|
|
|
def get_objc_refs_options():
|
|
usage = "usage: %prog [options] <CLASS> [CLASS ...]"
|
|
description = '''Searches all allocations on the heap for instances of
|
|
objective C classes, or any classes that inherit from the specified classes
|
|
in darwin user space programs. Any matches that were found will dump the malloc
|
|
blocks that contain the C strings and might be able to print what kind of
|
|
objects the pointers are contained in using dynamic type information in the
|
|
program.'''
|
|
parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='objc_refs',
|
|
usage=usage)
|
|
add_common_options(parser)
|
|
return parser
|
|
|
|
|
|
def objc_refs(debugger, command, result, dict):
|
|
command_args = shlex.split(command)
|
|
parser = get_objc_refs_options()
|
|
try:
|
|
(options, args) = parser.parse_args(command_args)
|
|
except:
|
|
return
|
|
|
|
process = debugger.GetSelectedTarget().GetProcess()
|
|
if not process:
|
|
result.AppendMessage('error: invalid process')
|
|
return
|
|
frame = process.GetSelectedThread().GetSelectedFrame()
|
|
if not frame:
|
|
result.AppendMessage('error: invalid frame')
|
|
return
|
|
|
|
options.type = 'isa'
|
|
if options.format is None:
|
|
options.format = "A" # 'A' is "address" format
|
|
|
|
expr_options = lldb.SBExpressionOptions()
|
|
expr_options.SetIgnoreBreakpoints(True)
|
|
expr_options.SetTimeoutInMicroSeconds(
|
|
3 * 1000 * 1000) # 3 second infinite timeout
|
|
expr_options.SetTryAllThreads(True)
|
|
expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
|
|
num_objc_classes_value = frame.EvaluateExpression(
|
|
"(int)objc_getClassList((void *)0, (int)0)", expr_options)
|
|
if not num_objc_classes_value.error.Success():
|
|
result.AppendMessage('error: %s' %
|
|
num_objc_classes_value.error.GetCString())
|
|
return
|
|
|
|
num_objc_classes = num_objc_classes_value.GetValueAsUnsigned()
|
|
if num_objc_classes == 0:
|
|
result.AppendMessage('error: no objective C classes in program')
|
|
return
|
|
|
|
if args:
|
|
# When we initialize the expression, we must define any types that
|
|
# we will need when looking at every allocation. We must also define
|
|
# a type named callback_baton_t and make an instance named "baton"
|
|
# and initialize it how ever we want to. The address of "baton" will
|
|
# be passed into our range callback. callback_baton_t must contain
|
|
# a member named "callback" whose type is "range_callback_t". This
|
|
# will be used by our zone callbacks to call the range callback for
|
|
# each malloc range.
|
|
expr_prefix = '''
|
|
struct $malloc_match {
|
|
void *addr;
|
|
uintptr_t size;
|
|
uintptr_t offset;
|
|
uintptr_t type;
|
|
};
|
|
'''
|
|
|
|
user_init_code_format = '''
|
|
#define MAX_MATCHES %u
|
|
typedef int (*compare_callback_t)(const void *a, const void *b);
|
|
typedef struct callback_baton_t {
|
|
range_callback_t callback;
|
|
compare_callback_t compare_callback;
|
|
unsigned num_matches;
|
|
$malloc_match matches[MAX_MATCHES];
|
|
void *isa;
|
|
Class classes[%u];
|
|
} callback_baton_t;
|
|
compare_callback_t compare_callback = [](const void *a, const void *b) -> int {
|
|
Class a_ptr = *(Class *)a;
|
|
Class b_ptr = *(Class *)b;
|
|
if (a_ptr < b_ptr) return -1;
|
|
if (a_ptr > b_ptr) return +1;
|
|
return 0;
|
|
};
|
|
typedef Class (*class_getSuperclass_type)(void *isa);
|
|
range_callback_t range_callback = [](task_t task, void *baton, unsigned type, uintptr_t ptr_addr, uintptr_t ptr_size) -> void {
|
|
class_getSuperclass_type class_getSuperclass_impl = (class_getSuperclass_type)class_getSuperclass;
|
|
callback_baton_t *lldb_info = (callback_baton_t *)baton;
|
|
if (sizeof(Class) <= ptr_size) {
|
|
Class *curr_class_ptr = (Class *)ptr_addr;
|
|
Class *matching_class_ptr = (Class *)bsearch (curr_class_ptr,
|
|
(const void *)lldb_info->classes,
|
|
sizeof(lldb_info->classes)/sizeof(Class),
|
|
sizeof(Class),
|
|
lldb_info->compare_callback);
|
|
if (matching_class_ptr) {
|
|
bool match = false;
|
|
if (lldb_info->isa) {
|
|
Class isa = *curr_class_ptr;
|
|
if (lldb_info->isa == isa)
|
|
match = true;
|
|
else { // if (lldb_info->objc.match_superclasses) {
|
|
Class super = class_getSuperclass_impl(isa);
|
|
while (super) {
|
|
if (super == lldb_info->isa) {
|
|
match = true;
|
|
break;
|
|
}
|
|
super = class_getSuperclass_impl(super);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
match = true;
|
|
if (match) {
|
|
if (lldb_info->num_matches < MAX_MATCHES) {
|
|
lldb_info->matches[lldb_info->num_matches].addr = (void*)ptr_addr;
|
|
lldb_info->matches[lldb_info->num_matches].size = ptr_size;
|
|
lldb_info->matches[lldb_info->num_matches].offset = 0;
|
|
lldb_info->matches[lldb_info->num_matches].type = type;
|
|
++lldb_info->num_matches;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
callback_baton_t baton = { range_callback, compare_callback, 0, {0}, (void *)0x%x, {0} };
|
|
int nc = (int)objc_getClassList(baton.classes, sizeof(baton.classes)/sizeof(Class));
|
|
(void)qsort (baton.classes, sizeof(baton.classes)/sizeof(Class), sizeof(Class), compare_callback);'''
|
|
# We must also define a snippet of code to be run that returns
|
|
# the result of the expression we run.
|
|
# Here we return NULL if our pointer was not found in any malloc blocks,
|
|
# and we return the address of the matches array so we can then access
|
|
# the matching results
|
|
user_return_code = '''if (baton.num_matches < MAX_MATCHES)
|
|
baton.matches[baton.num_matches].addr = 0; // Terminate the matches array
|
|
baton.matches'''
|
|
# Iterate through all of our ObjC class name arguments
|
|
for class_name in args:
|
|
addr_expr_str = "(void *)[%s class]" % class_name
|
|
expr_options = lldb.SBExpressionOptions()
|
|
expr_options.SetIgnoreBreakpoints(True)
|
|
expr_options.SetTimeoutInMicroSeconds(
|
|
1 * 1000 * 1000) # 1 second timeout
|
|
expr_options.SetTryAllThreads(True)
|
|
expr_options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
|
|
expr_sbvalue = frame.EvaluateExpression(
|
|
addr_expr_str, expr_options)
|
|
if expr_sbvalue.error.Success():
|
|
isa = expr_sbvalue.unsigned
|
|
if isa:
|
|
options.type = 'isa'
|
|
result.AppendMessage(
|
|
'Searching for all instances of classes or subclasses of "%s" (isa=0x%x)' %
|
|
(class_name, isa))
|
|
user_init_code = user_init_code_format % (
|
|
options.max_matches, num_objc_classes, isa)
|
|
expr = get_iterate_memory_expr(
|
|
options, process, user_init_code, user_return_code)
|
|
arg_str_description = 'objective C classes with isa 0x%x' % isa
|
|
display_match_results(
|
|
process,
|
|
result,
|
|
options,
|
|
arg_str_description,
|
|
expr,
|
|
True,
|
|
expr_prefix)
|
|
else:
|
|
result.AppendMessage(
|
|
'error: Can\'t find isa for an ObjC class named "%s"' %
|
|
(class_name))
|
|
else:
|
|
result.AppendMessage(
|
|
'error: expression error for "%s": %s' %
|
|
(addr_expr_str, expr_sbvalue.error))
|
|
else:
|
|
result.AppendMessage(
|
|
'error: command takes one or more C string arguments')
|
|
|
|
if __name__ == '__main__':
|
|
lldb.debugger = lldb.SBDebugger.Create()
|
|
|
|
# Make the options so we can generate the help text for the new LLDB
|
|
# command line command prior to registering it with LLDB below. This way
|
|
# if clients in LLDB type "help malloc_info", they will see the exact same
|
|
# output as typing "malloc_info --help".
|
|
ptr_refs.__doc__ = get_ptr_refs_options().format_help()
|
|
cstr_refs.__doc__ = get_cstr_refs_options().format_help()
|
|
malloc_info.__doc__ = get_malloc_info_options().format_help()
|
|
objc_refs.__doc__ = get_objc_refs_options().format_help()
|
|
lldb.debugger.HandleCommand(
|
|
'command script add -f %s.ptr_refs ptr_refs' %
|
|
__name__)
|
|
lldb.debugger.HandleCommand(
|
|
'command script add -f %s.cstr_refs cstr_refs' %
|
|
__name__)
|
|
lldb.debugger.HandleCommand(
|
|
'command script add -f %s.malloc_info malloc_info' %
|
|
__name__)
|
|
lldb.debugger.HandleCommand(
|
|
'command script add -f %s.find_variable find_variable' %
|
|
__name__)
|
|
# lldb.debugger.HandleCommand('command script add -f %s.heap heap' % package_name)
|
|
# lldb.debugger.HandleCommand('command script add -f %s.section_ptr_refs section_ptr_refs' % package_name)
|
|
# lldb.debugger.HandleCommand('command script add -f %s.stack_ptr_refs stack_ptr_refs' % package_name)
|
|
lldb.debugger.HandleCommand(
|
|
'command script add -f %s.objc_refs objc_refs' %
|
|
__name__)
|
|
print('"malloc_info", "ptr_refs", "cstr_refs", "find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.')
|