[Lldb-commits] [lldb] r255697 - First pass at LLDBRPC.framework
Greg Clayton via lldb-commits
lldb-commits at lists.llvm.org
Tue Dec 15 15:03:24 PST 2015
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/crashlog.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/crashlog.py?rev=255697&view=auto
==============================================================================
--- lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/crashlog.py (added)
+++ lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/crashlog.py Tue Dec 15 17:03:22 2015
@@ -0,0 +1,829 @@
+#!/usr/bin/python
+
+#----------------------------------------------------------------------
+# Be sure to add the python path that points to the LLDB shared library.
+#
+# To use this in the embedded python interpreter using "lldb":
+#
+# cd /path/containing/crashlog.py
+# lldb
+# (lldb) script import crashlog
+# "crashlog" command installed, type "crashlog --help" for detailed help
+# (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash
+#
+# The benefit of running the crashlog command inside lldb in the
+# embedded python interpreter is when the command completes, there
+# will be a target with all of the files loaded at the locations
+# described in the crash log. Only the files that have stack frames
+# in the backtrace will be loaded unless the "--load-all" option
+# has been specified. This allows users to explore the program in the
+# state it was in right at crash time.
+#
+# On MacOSX csh, tcsh:
+# ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
+#
+# On MacOSX sh, bash:
+# PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
+#----------------------------------------------------------------------
+
+import commands
+import cmd
+import datetime
+import glob
+import optparse
+import os
+import platform
+import plistlib
+import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
+import re
+import shlex
+import string
+import sys
+import time
+import uuid
+
+try:
+ # Just try for LLDB in case PYTHONPATH is already correctly setup
+ import lldb
+except ImportError:
+ lldb_python_dirs = list()
+ # lldb is not in the PYTHONPATH, try some defaults for the current platform
+ platform_system = platform.system()
+ if platform_system == 'Darwin':
+ # On Darwin, try the currently selected Xcode directory
+ xcode_dir = commands.getoutput("xcode-select --print-path")
+ if xcode_dir:
+ lldb_python_dirs.append(os.path.realpath(xcode_dir + '/../SharedFrameworks/LLDB.framework/Resources/Python'))
+ lldb_python_dirs.append(xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
+ lldb_python_dirs.append('/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
+ success = False
+ for lldb_python_dir in lldb_python_dirs:
+ if os.path.exists(lldb_python_dir):
+ if not (sys.path.__contains__(lldb_python_dir)):
+ sys.path.append(lldb_python_dir)
+ try:
+ import lldb
+ except ImportError:
+ pass
+ else:
+ print 'imported lldb from: "%s"' % (lldb_python_dir)
+ success = True
+ break
+ if not success:
+ print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
+ sys.exit(1)
+
+from lldb.utils import symbolication
+
+PARSE_MODE_NORMAL = 0
+PARSE_MODE_THREAD = 1
+PARSE_MODE_IMAGES = 2
+PARSE_MODE_THREGS = 3
+PARSE_MODE_SYSTEM = 4
+
+class CrashLog(symbolication.Symbolicator):
+ """Class that does parses darwin crash logs"""
+ parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]');
+ thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
+ thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
+ app_backtrace_regex = re.compile('^Application Specific Backtrace ([0-9]+)([^:]*):(.*)')
+ frame_regex = re.compile('^([0-9]+)\s+([^ ]+)\s+(0x[0-9a-fA-F]+) +(.*)')
+ image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
+ image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
+ empty_line_regex = re.compile('^$')
+
+ class Thread:
+ """Class that represents a thread in a darwin crash log"""
+ def __init__(self, index, app_specific_backtrace):
+ self.index = index
+ self.frames = list()
+ self.idents = list()
+ self.registers = dict()
+ self.reason = None
+ self.queue = None
+ self.app_specific_backtrace = app_specific_backtrace
+
+ def dump(self, prefix):
+ if self.app_specific_backtrace:
+ print "%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason)
+ else:
+ print "%sThread[%u] %s" % (prefix, self.index, self.reason)
+ if self.frames:
+ print "%s Frames:" % (prefix)
+ for frame in self.frames:
+ frame.dump(prefix + ' ')
+ if self.registers:
+ print "%s Registers:" % (prefix)
+ for reg in self.registers.keys():
+ print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
+
+ def dump_symbolicated (self, crash_log, options):
+ this_thread_crashed = self.app_specific_backtrace
+ if not this_thread_crashed:
+ this_thread_crashed = self.did_crash()
+ if options.crashed_only and this_thread_crashed == False:
+ return
+
+ print "%s" % self
+ #prev_frame_index = -1
+ display_frame_idx = -1
+ for frame_idx, frame in enumerate(self.frames):
+ disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
+ if frame_idx == 0:
+ symbolicated_frame_addresses = crash_log.symbolicate (frame.pc & crash_log.addr_mask, options.verbose)
+ else:
+ # Any frame above frame zero and we have to subtract one to get the previous line entry
+ symbolicated_frame_addresses = crash_log.symbolicate ((frame.pc & crash_log.addr_mask) - 1, options.verbose)
+
+ if symbolicated_frame_addresses:
+ symbolicated_frame_address_idx = 0
+ for symbolicated_frame_address in symbolicated_frame_addresses:
+ display_frame_idx += 1
+ print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
+ if (options.source_all or self.did_crash()) and display_frame_idx < options.source_frames and options.source_context:
+ source_context = options.source_context
+ line_entry = symbolicated_frame_address.get_symbol_context().line_entry
+ if line_entry.IsValid():
+ strm = lldb.SBStream()
+ if line_entry:
+ lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm)
+ source_text = strm.GetData()
+ if source_text:
+ # Indent the source a bit
+ indent_str = ' '
+ join_str = '\n' + indent_str
+ print '%s%s' % (indent_str, join_str.join(source_text.split('\n')))
+ if symbolicated_frame_address_idx == 0:
+ if disassemble:
+ instructions = symbolicated_frame_address.get_instructions()
+ if instructions:
+ print
+ symbolication.disassemble_instructions (crash_log.get_target(),
+ instructions,
+ frame.pc,
+ options.disassemble_before,
+ options.disassemble_after, frame.index > 0)
+ print
+ symbolicated_frame_address_idx += 1
+ else:
+ print frame
+
+ def add_ident(self, ident):
+ if not ident in self.idents:
+ self.idents.append(ident)
+
+ def did_crash(self):
+ return self.reason != None
+
+ def __str__(self):
+ if self.app_specific_backtrace:
+ s = "Application Specific Backtrace[%u]" % self.index
+ else:
+ s = "Thread[%u]" % self.index
+ if self.reason:
+ s += ' %s' % self.reason
+ return s
+
+
+ class Frame:
+ """Class that represents a stack frame in a thread in a darwin crash log"""
+ def __init__(self, index, pc, description):
+ self.pc = pc
+ self.description = description
+ self.index = index
+
+ def __str__(self):
+ if self.description:
+ return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
+ else:
+ return "[%3u] 0x%16.16x" % (self.index, self.pc)
+
+ def dump(self, prefix):
+ print "%s%s" % (prefix, str(self))
+
+ class DarwinImage(symbolication.Image):
+ """Class that represents a binary images in a darwin crash log"""
+ dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
+ if not os.path.exists(dsymForUUIDBinary):
+ dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
+
+ dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
+
+ def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path):
+ symbolication.Image.__init__(self, path, uuid);
+ self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT"))
+ self.identifier = identifier
+ self.version = version
+
+ def locate_module_and_debug_symbols(self):
+ # Don't load a module twice...
+ if self.resolved:
+ return True
+ # Mark this as resolved so we don't keep trying
+ self.resolved = True
+ uuid_str = self.get_normalized_uuid_string()
+ print 'Getting symbols for %s %s...' % (uuid_str, self.path),
+ if os.path.exists(self.dsymForUUIDBinary):
+ dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
+ s = commands.getoutput(dsym_for_uuid_command)
+ if s:
+ plist_root = plistlib.readPlistFromString (s)
+ if plist_root:
+ plist = plist_root[uuid_str]
+ if plist:
+ if 'DBGArchitecture' in plist:
+ self.arch = plist['DBGArchitecture']
+ if 'DBGDSYMPath' in plist:
+ self.symfile = os.path.realpath(plist['DBGDSYMPath'])
+ if 'DBGSymbolRichExecutable' in plist:
+ self.path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
+ self.resolved_path = self.path
+ if not self.resolved_path and os.path.exists(self.path):
+ dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
+ self_uuid = self.get_uuid()
+ for line in dwarfdump_cmd_output.splitlines():
+ match = self.dwarfdump_uuid_regex.search (line)
+ if match:
+ dwarf_uuid_str = match.group(1)
+ dwarf_uuid = uuid.UUID(dwarf_uuid_str)
+ if self_uuid == dwarf_uuid:
+ self.resolved_path = self.path
+ self.arch = match.group(2)
+ break;
+ if not self.resolved_path:
+ self.unavailable = True
+ print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
+ return False
+ if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
+ print 'ok'
+ # if self.resolved_path:
+ # print ' exe = "%s"' % self.resolved_path
+ # if self.symfile:
+ # print ' dsym = "%s"' % self.symfile
+ return True
+ else:
+ self.unavailable = True
+ return False
+
+
+
+ def __init__(self, path):
+ """CrashLog constructor that take a path to a darwin crash log file"""
+ symbolication.Symbolicator.__init__(self);
+ self.path = os.path.expanduser(path);
+ self.info_lines = list()
+ self.system_profile = list()
+ self.threads = list()
+ self.backtraces = list() # For application specific backtraces
+ self.idents = list() # A list of the required identifiers for doing all stack backtraces
+ self.crashed_thread_idx = -1
+ self.version = -1
+ self.error = None
+ self.target = None
+ # With possible initial component of ~ or ~user replaced by that user's home directory.
+ try:
+ f = open(self.path)
+ except IOError:
+ self.error = 'error: cannot open "%s"' % self.path
+ return
+
+ self.file_lines = f.read().splitlines()
+ parse_mode = PARSE_MODE_NORMAL
+ thread = None
+ app_specific_backtrace = False
+ for line in self.file_lines:
+ # print line
+ line_len = len(line)
+ if line_len == 0:
+ if thread:
+ if parse_mode == PARSE_MODE_THREAD:
+ if thread.index == self.crashed_thread_idx:
+ thread.reason = ''
+ if self.thread_exception:
+ thread.reason += self.thread_exception
+ if self.thread_exception_data:
+ thread.reason += " (%s)" % self.thread_exception_data
+ if app_specific_backtrace:
+ self.backtraces.append(thread)
+ else:
+ self.threads.append(thread)
+ thread = None
+ else:
+ # only append an extra empty line if the previous line
+ # in the info_lines wasn't empty
+ if len(self.info_lines) > 0 and len(self.info_lines[-1]):
+ self.info_lines.append(line)
+ parse_mode = PARSE_MODE_NORMAL
+ # print 'PARSE_MODE_NORMAL'
+ elif parse_mode == PARSE_MODE_NORMAL:
+ if line.startswith ('Process:'):
+ (self.process_name, pid_with_brackets) = line[8:].strip().split(' [')
+ self.process_id = pid_with_brackets.strip('[]')
+ elif line.startswith ('Path:'):
+ self.process_path = line[5:].strip()
+ elif line.startswith ('Identifier:'):
+ self.process_identifier = line[11:].strip()
+ elif line.startswith ('Version:'):
+ version_string = line[8:].strip()
+ matched_pair = re.search("(.+)\((.+)\)", version_string)
+ if matched_pair:
+ self.process_version = matched_pair.group(1)
+ self.process_compatability_version = matched_pair.group(2)
+ else:
+ self.process = version_string
+ self.process_compatability_version = version_string
+ elif self.parent_process_regex.search(line):
+ parent_process_match = self.parent_process_regex.search(line)
+ self.parent_process_name = parent_process_match.group(1)
+ self.parent_process_id = parent_process_match.group(2)
+ elif line.startswith ('Exception Type:'):
+ self.thread_exception = line[15:].strip()
+ continue
+ elif line.startswith ('Exception Codes:'):
+ self.thread_exception_data = line[16:].strip()
+ continue
+ elif line.startswith ('Crashed Thread:'):
+ self.crashed_thread_idx = int(line[15:].strip().split()[0])
+ continue
+ elif line.startswith ('Report Version:'):
+ self.version = int(line[15:].strip())
+ continue
+ elif line.startswith ('System Profile:'):
+ parse_mode = PARSE_MODE_SYSTEM
+ continue
+ elif (line.startswith ('Interval Since Last Report:') or
+ line.startswith ('Crashes Since Last Report:') or
+ line.startswith ('Per-App Interval Since Last Report:') or
+ line.startswith ('Per-App Crashes Since Last Report:') or
+ line.startswith ('Sleep/Wake UUID:') or
+ line.startswith ('Anonymous UUID:')):
+ # ignore these
+ continue
+ elif line.startswith ('Thread'):
+ thread_state_match = self.thread_state_regex.search (line)
+ if thread_state_match:
+ app_specific_backtrace = False
+ thread_state_match = self.thread_regex.search (line)
+ thread_idx = int(thread_state_match.group(1))
+ parse_mode = PARSE_MODE_THREGS
+ thread = self.threads[thread_idx]
+ else:
+ thread_match = self.thread_regex.search (line)
+ if thread_match:
+ app_specific_backtrace = False
+ parse_mode = PARSE_MODE_THREAD
+ thread_idx = int(thread_match.group(1))
+ thread = CrashLog.Thread(thread_idx, False)
+ continue
+ elif line.startswith ('Binary Images:'):
+ parse_mode = PARSE_MODE_IMAGES
+ continue
+ elif line.startswith ('Application Specific Backtrace'):
+ app_backtrace_match = self.app_backtrace_regex.search (line)
+ if app_backtrace_match:
+ parse_mode = PARSE_MODE_THREAD
+ app_specific_backtrace = True
+ idx = int(app_backtrace_match.group(1))
+ thread = CrashLog.Thread(idx, True)
+ self.info_lines.append(line.strip())
+ elif parse_mode == PARSE_MODE_THREAD:
+ if line.startswith ('Thread'):
+ continue
+ frame_match = self.frame_regex.search(line)
+ if frame_match:
+ ident = frame_match.group(2)
+ thread.add_ident(ident)
+ if not ident in self.idents:
+ self.idents.append(ident)
+ thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
+ else:
+ print 'error: frame regex failed for line: "%s"' % line
+ elif parse_mode == PARSE_MODE_IMAGES:
+ image_match = self.image_regex_uuid.search (line)
+ if image_match:
+ image = CrashLog.DarwinImage (int(image_match.group(1),0),
+ int(image_match.group(2),0),
+ image_match.group(3).strip(),
+ image_match.group(4).strip(),
+ uuid.UUID(image_match.group(5)),
+ image_match.group(6))
+ self.images.append (image)
+ else:
+ image_match = self.image_regex_no_uuid.search (line)
+ if image_match:
+ image = CrashLog.DarwinImage (int(image_match.group(1),0),
+ int(image_match.group(2),0),
+ image_match.group(3).strip(),
+ image_match.group(4).strip(),
+ None,
+ image_match.group(5))
+ self.images.append (image)
+ else:
+ print "error: image regex failed for: %s" % line
+
+ elif parse_mode == PARSE_MODE_THREGS:
+ stripped_line = line.strip()
+ # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
+ reg_values = re.findall ('([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line);
+ for reg_value in reg_values:
+ #print 'reg_value = "%s"' % reg_value
+ (reg, value) = reg_value.split(': ')
+ #print 'reg = "%s"' % reg
+ #print 'value = "%s"' % value
+ thread.registers[reg.strip()] = int(value, 0)
+ elif parse_mode == PARSE_MODE_SYSTEM:
+ self.system_profile.append(line)
+ f.close()
+
+ def dump(self):
+ print "Crash Log File: %s" % (self.path)
+ if self.backtraces:
+ print "\nApplication Specific Backtraces:"
+ for thread in self.backtraces:
+ thread.dump(' ')
+ print "\nThreads:"
+ for thread in self.threads:
+ thread.dump(' ')
+ print "\nImages:"
+ for image in self.images:
+ image.dump(' ')
+
+ def find_image_with_identifier(self, identifier):
+ for image in self.images:
+ if image.identifier == identifier:
+ return image
+ regex_text = '^.*\.%s$' % (identifier)
+ regex = re.compile(regex_text)
+ for image in self.images:
+ if regex.match(image.identifier):
+ return image
+ return None
+
+ def create_target(self):
+ #print 'crashlog.create_target()...'
+ if self.target is None:
+ self.target = symbolication.Symbolicator.create_target(self)
+ if self.target:
+ return self.target
+ # We weren't able to open the main executable as, but we can still symbolicate
+ print 'crashlog.create_target()...2'
+ if self.idents:
+ for ident in self.idents:
+ image = self.find_image_with_identifier (ident)
+ if image:
+ self.target = image.create_target ()
+ if self.target:
+ return self.target # success
+ print 'crashlog.create_target()...3'
+ for image in self.images:
+ self.target = image.create_target ()
+ if self.target:
+ return self.target # success
+ print 'crashlog.create_target()...4'
+ print 'error: unable to locate any executables from the crash log'
+ return self.target
+
+ def get_target(self):
+ return self.target
+
+def usage():
+ print "Usage: lldb-symbolicate.py [-n name] executable-image"
+ sys.exit(0)
+
+class Interactive(cmd.Cmd):
+ '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
+ image_option_parser = None
+
+ def __init__(self, crash_logs):
+ cmd.Cmd.__init__(self)
+ self.use_rawinput = False
+ self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
+ self.crash_logs = crash_logs
+ self.prompt = '% '
+
+ def default(self, line):
+ '''Catch all for unknown command, which will exit the interpreter.'''
+ print "uknown command: %s" % line
+ return True
+
+ def do_q(self, line):
+ '''Quit command'''
+ return True
+
+ def do_quit(self, line):
+ '''Quit command'''
+ return True
+
+ def do_symbolicate(self, line):
+ description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
+ inlined stack frames back to the concrete functions, and disassemble the location of the crash
+ for the first frame of the crashed thread.'''
+ option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
+ command_args = shlex.split(line)
+ try:
+ (options, args) = option_parser.parse_args(command_args)
+ except:
+ return
+
+ if args:
+ # We have arguments, they must valid be crash log file indexes
+ for idx_str in args:
+ idx = int(idx_str)
+ if idx < len(self.crash_logs):
+ SymbolicateCrashLog (self.crash_logs[idx], options)
+ else:
+ print 'error: crash log index %u is out of range' % (idx)
+ else:
+ # No arguments, symbolicate all crash logs using the options provided
+ for idx in range(len(self.crash_logs)):
+ SymbolicateCrashLog (self.crash_logs[idx], options)
+
+ def do_list(self, line=None):
+ '''Dump a list of all crash logs that are currently loaded.
+
+ USAGE: list'''
+ print '%u crash logs are loaded:' % len(self.crash_logs)
+ for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
+ print '[%u] = %s' % (crash_log_idx, crash_log.path)
+
+ def do_image(self, line):
+ '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.'''
+ usage = "usage: %prog [options] <PATH> [PATH ...]"
+ description='''Dump information about one or more images in all crash logs. The <PATH> can be a full path, image basename, or partial path. Searches are done in this order.'''
+ command_args = shlex.split(line)
+ if not self.image_option_parser:
+ self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
+ self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
+ try:
+ (options, args) = self.image_option_parser.parse_args(command_args)
+ except:
+ return
+
+ if args:
+ for image_path in args:
+ fullpath_search = image_path[0] == '/'
+ for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
+ matches_found = 0
+ for (image_idx, image) in enumerate(crash_log.images):
+ if fullpath_search:
+ if image.get_resolved_path() == image_path:
+ matches_found += 1
+ print '[%u] ' % (crash_log_idx), image
+ else:
+ image_basename = image.get_resolved_path_basename()
+ if image_basename == image_path:
+ matches_found += 1
+ print '[%u] ' % (crash_log_idx), image
+ if matches_found == 0:
+ for (image_idx, image) in enumerate(crash_log.images):
+ resolved_image_path = image.get_resolved_path()
+ if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
+ print '[%u] ' % (crash_log_idx), image
+ else:
+ for crash_log in self.crash_logs:
+ for (image_idx, image) in enumerate(crash_log.images):
+ print '[%u] %s' % (image_idx, image)
+ return False
+
+
+def interactive_crashlogs(options, args):
+ crash_log_files = list()
+ for arg in args:
+ for resolved_path in glob.glob(arg):
+ crash_log_files.append(resolved_path)
+
+ crash_logs = list();
+ for crash_log_file in crash_log_files:
+ #print 'crash_log_file = "%s"' % crash_log_file
+ crash_log = CrashLog(crash_log_file)
+ if crash_log.error:
+ print crash_log.error
+ continue
+ if options.debug:
+ crash_log.dump()
+ if not crash_log.images:
+ print 'error: no images in crash log "%s"' % (crash_log)
+ continue
+ else:
+ crash_logs.append(crash_log)
+
+ interpreter = Interactive(crash_logs)
+ # List all crash logs that were imported
+ interpreter.do_list()
+ interpreter.cmdloop()
+
+
+def save_crashlog(debugger, command, result, dict):
+ usage = "usage: %prog [options] <output-path>"
+ description='''Export the state of current target into a crashlog file'''
+ parser = optparse.OptionParser(description=description, prog='save_crashlog',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(shlex.split(command))
+ except:
+ result.PutCString ("error: invalid options");
+ return
+ if len(args) != 1:
+ result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
+ return
+ out_file = open(args[0], 'w')
+ if not out_file:
+ result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
+ return
+ target = debugger.GetSelectedTarget()
+ if target:
+ identifier = target.executable.basename
+ if lldb.process:
+ pid = lldb.process.id
+ if pid != lldb.LLDB_INVALID_PROCESS_ID:
+ out_file.write('Process: %s [%u]\n' % (identifier, pid))
+ out_file.write('Path: %s\n' % (target.executable.fullpath))
+ out_file.write('Identifier: %s\n' % (identifier))
+ out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
+ out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
+ out_file.write('Report Version: 9\n')
+ for thread_idx in range(lldb.process.num_threads):
+ thread = lldb.process.thread[thread_idx]
+ out_file.write('\nThread %u:\n' % (thread_idx))
+ for (frame_idx, frame) in enumerate(thread.frames):
+ frame_pc = frame.pc
+ frame_offset = 0
+ if frame.function:
+ block = frame.GetFrameBlock()
+ block_range = block.range[frame.addr]
+ if block_range:
+ block_start_addr = block_range[0]
+ frame_offset = frame_pc - block_start_addr.load_addr
+ else:
+ frame_offset = frame_pc - frame.function.addr.load_addr
+ elif frame.symbol:
+ frame_offset = frame_pc - frame.symbol.addr.load_addr
+ out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
+ if frame_offset > 0:
+ out_file.write(' + %u' % (frame_offset))
+ line_entry = frame.line_entry
+ if line_entry:
+ if options.verbose:
+ # This will output the fullpath + line + column
+ out_file.write(' %s' % (line_entry))
+ else:
+ out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
+ column = line_entry.column
+ if column:
+ out_file.write(':%u' % (column))
+ out_file.write('\n')
+
+ out_file.write('\nBinary Images:\n')
+ for module in target.modules:
+ text_segment = module.section['__TEXT']
+ if text_segment:
+ text_segment_load_addr = text_segment.GetLoadAddress(target)
+ if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
+ text_segment_end_load_addr = text_segment_load_addr + text_segment.size
+ identifier = module.file.basename
+ module_version = '???'
+ module_version_array = module.GetVersion()
+ if module_version_array:
+ module_version = '.'.join(map(str,module_version_array))
+ out_file.write (' 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n' % (text_segment_load_addr, text_segment_end_load_addr, identifier, module_version, module.GetUUIDString(), module.file.fullpath))
+ out_file.close()
+ else:
+ result.PutCString ("error: invalid target");
+
+
+def Symbolicate(debugger, command, result, dict):
+ try:
+ SymbolicateCrashLogs (shlex.split(command))
+ except:
+ result.PutCString ("error: python exception %s" % sys.exc_info()[0])
+
+def SymbolicateCrashLog(crash_log, options):
+ if crash_log.error:
+ print crash_log.error
+ return
+ if options.debug:
+ crash_log.dump()
+ if not crash_log.images:
+ print 'error: no images in crash log'
+ return
+
+ if options.dump_image_list:
+ print "Binary Images:"
+ for image in crash_log.images:
+ if options.verbose:
+ print image.debug_dump()
+ else:
+ print image
+
+ target = crash_log.create_target ()
+ if not target:
+ return
+ exe_module = target.GetModuleAtIndex(0)
+ images_to_load = list()
+ loaded_images = list()
+ if options.load_all_images:
+ # --load-all option was specified, load everything up
+ for image in crash_log.images:
+ images_to_load.append(image)
+ else:
+ # Only load the images found in stack frames for the crashed threads
+ if options.crashed_only:
+ for thread in crash_log.threads:
+ if thread.did_crash():
+ for ident in thread.idents:
+ images = crash_log.find_images_with_identifier (ident)
+ if images:
+ for image in images:
+ images_to_load.append(image)
+ else:
+ print 'error: can\'t find image for identifier "%s"' % ident
+ else:
+ for ident in crash_log.idents:
+ images = crash_log.find_images_with_identifier (ident)
+ if images:
+ for image in images:
+ images_to_load.append(image)
+ else:
+ print 'error: can\'t find image for identifier "%s"' % ident
+
+ for image in images_to_load:
+ if not image in loaded_images:
+ err = image.add_module (target)
+ if err:
+ print err
+ else:
+ #print 'loaded %s' % image
+ loaded_images.append(image)
+
+ if crash_log.backtraces:
+ for thread in crash_log.backtraces:
+ thread.dump_symbolicated (crash_log, options)
+ print
+
+ for thread in crash_log.threads:
+ thread.dump_symbolicated (crash_log, options)
+ print
+
+
+def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
+ usage = "usage: %prog [options] <FILE> [FILE ...]"
+ option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
+ option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False)
+ option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False)
+ option_parser.add_option('--load-all' , '-a', action='store_true', dest='load_all_images', help='load all executable images, not just the images found in the crashed stack frames', default=False)
+ option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False)
+ option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
+ option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
+ option_parser.add_option('--disasm-depth' , '-d', type='int', dest='disassemble_depth', help='set the depth in stack frames that should be disassembled (default is 1)', default=1)
+ option_parser.add_option('--disasm-all' , '-D', action='store_true', dest='disassemble_all_threads', help='enabled disassembly of frames on all threads (not just the crashed thread)', default=False)
+ option_parser.add_option('--disasm-before' , '-B', type='int', dest='disassemble_before', help='the number of instructions to disassemble before the frame PC', default=4)
+ option_parser.add_option('--disasm-after' , '-A', type='int', dest='disassemble_after', help='the number of instructions to disassemble after the frame PC', default=4)
+ option_parser.add_option('--source-context', '-C', type='int', metavar='NLINES', dest='source_context', help='show NLINES source lines of source context (default = 4)', default=4)
+ option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4)
+ option_parser.add_option('--source-all' , action='store_true', dest='source_all', help='show source for all threads, not just the crashed thread', default=False)
+ if add_interactive_options:
+ option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
+ return option_parser
+
+def SymbolicateCrashLogs(command_args):
+ description='''Symbolicate one or more darwin crash log files to provide source file and line information,
+inlined stack frames back to the concrete functions, and disassemble the location of the crash
+for the first frame of the crashed thread.
+If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
+for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
+created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
+you to explore the program as if it were stopped at the locations described in the crash log and functions can
+be disassembled and lookups can be performed using the addresses found in the crash log.'''
+ option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
+ try:
+ (options, args) = option_parser.parse_args(command_args)
+ except:
+ return
+
+ if options.debug:
+ print 'command_args = %s' % command_args
+ print 'options', options
+ print 'args', args
+
+ if options.debug_delay > 0:
+ print "Waiting %u seconds for debugger to attach..." % options.debug_delay
+ time.sleep(options.debug_delay)
+ error = lldb.SBError()
+
+ if args:
+ if options.interactive:
+ interactive_crashlogs(options, args)
+ else:
+ for crash_log_file in args:
+ crash_log = CrashLog(crash_log_file)
+ SymbolicateCrashLog (crash_log, options)
+if __name__ == '__main__':
+ # Create a new debugger instance
+ lldb.debugger = lldb.SBDebugger.Create()
+ SymbolicateCrashLogs (sys.argv[1:])
+ lldb.SBDebugger.Destroy (lldb.debugger)
+elif getattr(lldb, 'debugger', None):
+ lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
+ lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
+ print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
+
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/crashlog.py
------------------------------------------------------------------------------
svn:executable = *
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap.py?rev=255697&view=auto
==============================================================================
--- lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap.py (added)
+++ lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap.py Tue Dec 15 17:03:22 2015
@@ -0,0 +1,1244 @@
+#!/usr/bin/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
+#----------------------------------------------------------------------
+
+import lldb
+import commands
+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;
+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_blacklist.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_blacklist', 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.maxint
+ 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 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 *info = ($malloc_stack_history *)baton;
+ if (info->idx < MAX_HISTORY) {
+ malloc_stack_entry *stack_entry = &(info->entries[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 (
+ 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;
+ }
+ ++info->idx;
+};
+(kern_return_t)__mach_stack_logging_enumerate_records (info.task, (uint64_t)0x%x, callback, &info);
+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 1:
+ 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_blacklist:
+ for ivar_regex in options.ivar_regex_blacklist:
+ 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 == 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 >>result, "Finding a variable with address %#x..." % (var_addr)
+ 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 == 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 *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] == info->ptr) {
+ if (info->num_matches < MAX_MATCHES) {
+ info->matches[info->num_matches].addr = (void*)ptr_addr;
+ info->matches[info->num_matches].size = ptr_size;
+ info->matches[info->num_matches].offset = idx*sizeof(T);
+ info->matches[info->num_matches].type = type;
+ ++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 == 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 *info = (callback_baton_t *)baton;
+ if (info->cstr_len < ptr_size) {
+ const char *begin = (const char *)ptr_addr;
+ const char *end = begin + ptr_size - info->cstr_len;
+ for (const char *s = begin; s < end; ++s) {
+ if ((int)memcmp(s, info->cstr, info->cstr_len) == 0) {
+ if (info->num_matches < MAX_MATCHES) {
+ info->matches[info->num_matches].addr = (void*)ptr_addr;
+ info->matches[info->num_matches].size = ptr_size;
+ info->matches[info->num_matches].offset = s - begin;
+ info->matches[info->num_matches].type = type;
+ ++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 *info = (callback_baton_t *)baton;
+ if (info->num_matches == 0) {
+ uint8_t *p = (uint8_t *)info->ptr;
+ uint8_t *lo = (uint8_t *)ptr_addr;
+ uint8_t *hi = lo + ptr_size;
+ if (lo <= p && p < hi) {
+ info->matches[info->num_matches].addr = (void*)ptr_addr;
+ info->matches[info->num_matches].size = ptr_size;
+ info->matches[info->num_matches].offset = p - lo;
+ info->matches[info->num_matches].type = type;
+ 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 == 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 *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 *)info->classes,
+ sizeof(info->classes)/sizeof(Class),
+ sizeof(Class),
+ info->compare_callback);
+ if (matching_class_ptr) {
+ bool match = false;
+ if (info->isa) {
+ Class isa = *curr_class_ptr;
+ if (info->isa == isa)
+ match = true;
+ else { // if (info->objc.match_superclasses) {
+ Class super = class_getSuperclass_impl(isa);
+ while (super) {
+ if (super == info->isa) {
+ match = true;
+ break;
+ }
+ super = class_getSuperclass_impl(super);
+ }
+ }
+ }
+ else
+ match = true;
+ if (match) {
+ if (info->num_matches < MAX_MATCHES) {
+ info->matches[info->num_matches].addr = (void*)ptr_addr;
+ info->matches[info->num_matches].size = ptr_size;
+ info->matches[info->num_matches].offset = 0;
+ info->matches[info->num_matches].type = type;
+ ++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.'
+
+
+
+
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap/Makefile
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap/Makefile?rev=255697&view=auto
==============================================================================
--- lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap/Makefile (added)
+++ lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap/Makefile Tue Dec 15 17:03:22 2015
@@ -0,0 +1,33 @@
+#----------------------------------------------------------------------
+# Fill in the source files to build
+#----------------------------------------------------------------------
+# Uncomment line below for debugging shell commands
+# SHELL = /bin/sh -x
+
+#----------------------------------------------------------------------
+# Change any build/tool options needed
+#----------------------------------------------------------------------
+ARCH ?= x86_64
+CFLAGS ?=-arch $(ARCH) -gdwarf-2 -O0
+CXX ?= $(shell xcrun -find clang++)
+EXE ?= libheap.dylib
+DSYM ?= $(EXE).dSYM
+
+#----------------------------------------------------------------------
+# Compile the executable from all the objects (default rule) with no
+# dsym file.
+#----------------------------------------------------------------------
+$(EXE) : heap_find.cpp
+ $(CXX) $(CFLAGS) -install_name "@executable_path/libheap.dylib" -dynamiclib -lobjc heap_find.cpp -o "$(EXE)"
+
+#----------------------------------------------------------------------
+# Include all of the makefiles for each source file so we don't have
+# to manually track all of the prerequisites for each source file.
+#----------------------------------------------------------------------
+.PHONY: clean
+all: $(EXE)
+clean:
+ rm -rf "$(EXE)" "$(DSYM)"
+
+
+
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap/heap_find.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap/heap_find.cpp?rev=255697&view=auto
==============================================================================
--- lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap/heap_find.cpp (added)
+++ lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap/heap_find.cpp Tue Dec 15 17:03:22 2015
@@ -0,0 +1,1071 @@
+//===-- heap_find.c ---------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file compiles into a dylib and can be used on darwin to find data that
+// is contained in active malloc blocks. To use this make the project, then
+// load the shared library in a debug session while you are stopped:
+//
+// (lldb) process load /path/to/libheap.dylib
+//
+// Now you can use the "find_pointer_in_heap" and "find_cstring_in_heap"
+// functions in the expression parser.
+//
+// This will grep everything in all active allocation blocks and print and
+// malloc blocks that contain the pointer 0x112233000000:
+//
+// (lldb) expression find_pointer_in_heap (0x112233000000)
+//
+// This will grep everything in all active allocation blocks and print and
+// malloc blocks that contain the C string "hello" (as a substring, no
+// NULL termination included):
+//
+// (lldb) expression find_cstring_in_heap ("hello")
+//
+// The results will be printed to the STDOUT of the inferior program. The
+// return value of the "find_pointer_in_heap" function is the number of
+// pointer references that were found. A quick example shows
+//
+// (lldb) expr find_pointer_in_heap(0x0000000104000410)
+// (uint32_t) $5 = 0x00000002
+// 0x104000740: 0x0000000104000410 found in malloc block 0x104000730 + 16 (malloc_size = 48)
+// 0x100820060: 0x0000000104000410 found in malloc block 0x100820000 + 96 (malloc_size = 4096)
+//
+// From the above output we see that 0x104000410 was found in the malloc block
+// at 0x104000730 and 0x100820000. If we want to see what these blocks are, we
+// can display the memory for this block using the "address" ("A" for short)
+// format. The address format shows pointers, and if those pointers point to
+// objects that have symbols or know data contents, it will display information
+// about the pointers:
+//
+// (lldb) memory read --format address --count 1 0x104000730
+// 0x104000730: 0x0000000100002460 (void *)0x0000000100002488: MyString
+//
+// We can see that the first block is a "MyString" object that contains our
+// pointer value at offset 16.
+//
+// Looking at the next pointers, are a bit more tricky:
+// (lldb) memory read -fA 0x100820000 -c1
+// 0x100820000: 0x4f545541a1a1a1a1
+// (lldb) memory read 0x100820000
+// 0x100820000: a1 a1 a1 a1 41 55 54 4f 52 45 4c 45 41 53 45 21 ....AUTORELEASE!
+// 0x100820010: 78 00 82 00 01 00 00 00 60 f9 e8 75 ff 7f 00 00 x.......`..u....
+//
+// This is an objective C auto release pool object that contains our pointer.
+// C++ classes will show up if they are virtual as something like:
+// (lldb) memory read --format address --count 1 0x104008000
+// 0x104008000: 0x109008000 vtable for lldb_private::Process
+//
+// This is a clue that the 0x104008000 is a "lldb_private::Process *".
+//===----------------------------------------------------------------------===//
+// C includes
+#include <assert.h>
+#include <ctype.h>
+#include <dlfcn.h>
+#include <mach/mach.h>
+#include <mach/mach_vm.h>
+#include <malloc/malloc.h>
+#include <objc/objc-runtime.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+// C++ includes
+#include <vector>
+
+//----------------------------------------------------------------------
+// Redefine private types from "/usr/local/include/stack_logging.h"
+//----------------------------------------------------------------------
+typedef struct {
+ uint32_t type_flags;
+ uint64_t stack_identifier;
+ uint64_t argument;
+ mach_vm_address_t address;
+} mach_stack_logging_record_t;
+
+//----------------------------------------------------------------------
+// Redefine private defines from "/usr/local/include/stack_logging.h"
+//----------------------------------------------------------------------
+#define stack_logging_type_free 0
+#define stack_logging_type_generic 1
+#define stack_logging_type_alloc 2
+#define stack_logging_type_dealloc 4
+// This bit is made up by this code
+#define stack_logging_type_vm_region 8
+
+//----------------------------------------------------------------------
+// Redefine private function prototypes from
+// "/usr/local/include/stack_logging.h"
+//----------------------------------------------------------------------
+extern "C" kern_return_t
+__mach_stack_logging_set_file_path (
+ task_t task,
+ char* file_path
+);
+
+extern "C" kern_return_t
+__mach_stack_logging_get_frames (
+ task_t task,
+ mach_vm_address_t address,
+ mach_vm_address_t *stack_frames_buffer,
+ uint32_t max_stack_frames,
+ uint32_t *count
+);
+
+extern "C" kern_return_t
+__mach_stack_logging_enumerate_records (
+ task_t task,
+ mach_vm_address_t address,
+ void enumerator(mach_stack_logging_record_t, void *),
+ void *context
+);
+
+extern "C" kern_return_t
+__mach_stack_logging_frames_for_uniqued_stack (
+ task_t task,
+ uint64_t stack_identifier,
+ mach_vm_address_t *stack_frames_buffer,
+ uint32_t max_stack_frames,
+ uint32_t *count
+);
+
+extern "C" void *gdb_class_getClass (void *objc_class);
+
+static void
+range_info_callback (task_t task,
+ void *baton,
+ unsigned type,
+ uint64_t ptr_addr,
+ uint64_t ptr_size);
+
+//----------------------------------------------------------------------
+// Redefine private global variables prototypes from
+// "/usr/local/include/stack_logging.h"
+//----------------------------------------------------------------------
+
+extern "C" int stack_logging_enable_logging;
+
+//----------------------------------------------------------------------
+// Local defines
+//----------------------------------------------------------------------
+#define MAX_FRAMES 1024
+
+//----------------------------------------------------------------------
+// Local Typedefs and Types
+//----------------------------------------------------------------------
+typedef void range_callback_t (task_t task, void *baton, unsigned type, uint64_t ptr_addr, uint64_t ptr_size);
+typedef void zone_callback_t (void *info, const malloc_zone_t *zone);
+typedef int (*comare_function_t)(const void *, const void *);
+struct range_callback_info_t
+{
+ zone_callback_t *zone_callback;
+ range_callback_t *range_callback;
+ void *baton;
+ int check_vm_regions;
+};
+
+enum data_type_t
+{
+ eDataTypeAddress,
+ eDataTypeContainsData,
+ eDataTypeObjC,
+ eDataTypeHeapInfo
+};
+
+struct aligned_data_t
+{
+ const uint8_t *buffer;
+ uint32_t size;
+ uint32_t align;
+};
+
+struct objc_data_t
+{
+ void *match_isa; // Set to NULL for all objective C objects
+ bool match_superclasses;
+};
+
+struct range_contains_data_callback_info_t
+{
+ data_type_t type;
+ const void *lookup_addr;
+ union
+ {
+ uintptr_t addr;
+ aligned_data_t data;
+ objc_data_t objc;
+ };
+ uint32_t match_count;
+ bool done;
+ bool unique;
+};
+
+struct malloc_match
+{
+ void *addr;
+ intptr_t size;
+ intptr_t offset;
+ uintptr_t type;
+};
+
+struct malloc_stack_entry
+{
+ const void *address;
+ uint64_t argument;
+ uint32_t type_flags;
+ uint32_t num_frames;
+ mach_vm_address_t frames[MAX_FRAMES];
+};
+
+struct malloc_block_contents
+{
+ union {
+ Class isa;
+ void *pointers[2];
+ };
+};
+
+static int
+compare_void_ptr (const void *a, const void *b)
+{
+ 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;
+}
+
+class MatchResults
+{
+ enum {
+ k_max_entries = 8 * 1024
+ };
+public:
+ MatchResults () :
+ m_size(0)
+ {
+ }
+
+ void
+ clear()
+ {
+ m_size = 0;
+ bzero (&m_entries, sizeof(m_entries));
+ }
+
+ bool
+ empty() const
+ {
+ return m_size == 0;
+ }
+
+ void
+ push_back (const malloc_match& m, bool unique = false)
+ {
+ if (unique)
+ {
+ // Don't add the entry if there is already a match for this address
+ for (uint32_t i=0; i<m_size; ++i)
+ {
+ if (((uint8_t *)m_entries[i].addr + m_entries[i].offset) == ((uint8_t *)m.addr + m.offset))
+ return; // Duplicate entry
+ }
+ }
+ if (m_size < k_max_entries - 1)
+ {
+ m_entries[m_size] = m;
+ m_size++;
+ }
+ }
+
+ malloc_match *
+ data ()
+ {
+ // If empty, return NULL
+ if (empty())
+ return NULL;
+ // In not empty, terminate and return the result
+ malloc_match terminator_entry = { NULL, 0, 0, 0 };
+ // We always leave room for an empty entry at the end
+ m_entries[m_size] = terminator_entry;
+ return m_entries;
+ }
+
+protected:
+ malloc_match m_entries[k_max_entries];
+ uint32_t m_size;
+};
+
+class MallocStackLoggingEntries
+{
+ enum { k_max_entries = 128 };
+public:
+ MallocStackLoggingEntries () :
+ m_size(0)
+ {
+ }
+
+ void
+ clear()
+ {
+ m_size = 0;
+ }
+
+ bool
+ empty() const
+ {
+ return m_size == 0;
+ }
+
+
+ malloc_stack_entry *
+ next ()
+ {
+ if (m_size < k_max_entries - 1)
+ {
+ malloc_stack_entry * result = m_entries + m_size;
+ ++m_size;
+ return result;
+ }
+ return NULL; // Out of entries...
+ }
+
+ malloc_stack_entry *
+ data ()
+ {
+ // If empty, return NULL
+ if (empty())
+ return NULL;
+ // In not empty, terminate and return the result
+ m_entries[m_size].address = NULL;
+ m_entries[m_size].argument = 0;
+ m_entries[m_size].type_flags = 0;
+ m_entries[m_size].num_frames = 0;
+ return m_entries;
+ }
+
+protected:
+ malloc_stack_entry m_entries[k_max_entries];
+ uint32_t m_size;
+};
+
+//----------------------------------------------------------------------
+// A safe way to allocate memory and keep it from interfering with the
+// malloc enumerators.
+//----------------------------------------------------------------------
+void *
+safe_malloc(size_t n_bytes)
+{
+ if (n_bytes > 0)
+ {
+ const int k_page_size = getpagesize();
+ const mach_vm_size_t vm_size = ((n_bytes + k_page_size - 1)/k_page_size) * k_page_size;
+ vm_address_t address = 0;
+ kern_return_t kerr = vm_allocate (mach_task_self(), &address, vm_size, true);
+ if (kerr == KERN_SUCCESS)
+ return (void *)address;
+ }
+ return NULL;
+}
+
+
+//----------------------------------------------------------------------
+// ObjCClasses
+//----------------------------------------------------------------------
+class ObjCClasses
+{
+public:
+ ObjCClasses() :
+ m_objc_class_ptrs (NULL),
+ m_size (0)
+ {
+ }
+
+ bool
+ Update()
+ {
+ // TODO: find out if class list has changed and update if needed
+ if (m_objc_class_ptrs == NULL)
+ {
+ m_size = objc_getClassList(NULL, 0);
+ if (m_size > 0)
+ {
+ // Allocate the class pointers
+ m_objc_class_ptrs = (Class *)safe_malloc (m_size * sizeof(Class));
+ m_size = objc_getClassList(m_objc_class_ptrs, m_size);
+ // Sort Class pointers for quick lookup
+ ::qsort (m_objc_class_ptrs, m_size, sizeof(Class), compare_void_ptr);
+ }
+ else
+ return false;
+ }
+ return true;
+ }
+
+ uint32_t
+ FindClassIndex (Class isa)
+ {
+ Class *matching_class = (Class *)bsearch (&isa,
+ m_objc_class_ptrs,
+ m_size,
+ sizeof(Class),
+ compare_void_ptr);
+ if (matching_class)
+ {
+ uint32_t idx = matching_class - m_objc_class_ptrs;
+ return idx;
+ }
+ return UINT32_MAX;
+ }
+
+ Class
+ GetClassAtIndex (uint32_t idx) const
+ {
+ if (idx < m_size)
+ return m_objc_class_ptrs[idx];
+ return NULL;
+ }
+ uint32_t
+ GetSize() const
+ {
+ return m_size;
+ }
+private:
+ Class *m_objc_class_ptrs;
+ uint32_t m_size;
+};
+
+
+
+//----------------------------------------------------------------------
+// Local global variables
+//----------------------------------------------------------------------
+MatchResults g_matches;
+MallocStackLoggingEntries g_malloc_stack_history;
+ObjCClasses g_objc_classes;
+
+//----------------------------------------------------------------------
+// ObjCClassInfo
+//----------------------------------------------------------------------
+
+enum HeapInfoSortType
+{
+ eSortTypeNone,
+ eSortTypeBytes,
+ eSortTypeCount
+};
+
+class ObjCClassInfo
+{
+public:
+ ObjCClassInfo() :
+ m_entries (NULL),
+ m_size (0),
+ m_sort_type (eSortTypeNone)
+ {
+ }
+
+ void
+ Update (const ObjCClasses &objc_classes)
+ {
+ m_size = objc_classes.GetSize();
+ m_entries = (Entry *)safe_malloc (m_size * sizeof(Entry));
+ m_sort_type = eSortTypeNone;
+ Reset ();
+ }
+
+ bool
+ AddInstance (uint32_t idx, uint64_t ptr_size)
+ {
+ if (m_size == 0)
+ Update (g_objc_classes);
+ // Update the totals for the classes
+ if (idx < m_size)
+ {
+ m_entries[idx].bytes += ptr_size;
+ ++m_entries[idx].count;
+ return true;
+ }
+ return false;
+ }
+
+ void
+ Reset ()
+ {
+ m_sort_type = eSortTypeNone;
+ for (uint32_t i=0; i<m_size; ++i)
+ {
+ // In case we sort the entries after gathering the data, we will
+ // want to know the index into the m_objc_class_ptrs[] array.
+ m_entries[i].idx = i;
+ m_entries[i].bytes = 0;
+ m_entries[i].count = 0;
+ }
+ }
+ void
+ SortByTotalBytes (const ObjCClasses &objc_classes, bool print)
+ {
+ if (m_sort_type != eSortTypeBytes && m_size > 0)
+ {
+ ::qsort (m_entries, m_size, sizeof(Entry), (comare_function_t)compare_bytes);
+ m_sort_type = eSortTypeBytes;
+ }
+ if (print && m_size > 0)
+ {
+ puts("Objective C objects by total bytes:");
+ puts("Total Bytes Class Name");
+ puts("----------- -----------------------------------------------------------------");
+ for (uint32_t i=0; i<m_size && m_entries[i].bytes > 0; ++i)
+ {
+ printf ("%11llu %s\n", m_entries[i].bytes, class_getName (objc_classes.GetClassAtIndex(m_entries[i].idx)));
+ }
+ }
+ }
+ void
+ SortByTotalCount (const ObjCClasses &objc_classes, bool print)
+ {
+ if (m_sort_type != eSortTypeCount && m_size > 0)
+ {
+ ::qsort (m_entries, m_size, sizeof(Entry), (comare_function_t)compare_count);
+ m_sort_type = eSortTypeCount;
+ }
+ if (print && m_size > 0)
+ {
+ puts("Objective C objects by total count:");
+ puts("Count Class Name");
+ puts("-------- -----------------------------------------------------------------");
+ for (uint32_t i=0; i<m_size && m_entries[i].count > 0; ++i)
+ {
+ printf ("%8u %s\n", m_entries[i].count, class_getName (objc_classes.GetClassAtIndex(m_entries[i].idx)));
+ }
+ }
+ }
+private:
+ struct Entry
+ {
+ uint32_t idx; // Index into the m_objc_class_ptrs[] array
+ uint32_t count; // Number of object instances that were found
+ uint64_t bytes; // Total number of bytes for each objc class
+ };
+
+ static int
+ compare_bytes (const Entry *a, const Entry *b)
+ {
+ // Reverse the comparison to most bytes entries end up at top of list
+ if (a->bytes > b->bytes) return -1;
+ if (a->bytes < b->bytes) return +1;
+ return 0;
+ }
+
+ static int
+ compare_count (const Entry *a, const Entry *b)
+ {
+ // Reverse the comparison to most count entries end up at top of list
+ if (a->count > b->count) return -1;
+ if (a->count < b->count) return +1;
+ return 0;
+ }
+
+ Entry *m_entries;
+ uint32_t m_size;
+ HeapInfoSortType m_sort_type;
+};
+
+ObjCClassInfo g_objc_class_snapshot;
+
+//----------------------------------------------------------------------
+// task_peek
+//
+// Reads memory from this tasks address space. This callback is needed
+// by the code that iterates through all of the malloc blocks to read
+// the memory in this process.
+//----------------------------------------------------------------------
+static kern_return_t
+task_peek (task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory)
+{
+ *local_memory = (void*) remote_address;
+ return KERN_SUCCESS;
+}
+
+
+static const void
+foreach_zone_in_this_process (range_callback_info_t *info)
+{
+ if (info == NULL || info->zone_callback == NULL)
+ return;
+
+ vm_address_t *zones = NULL;
+ unsigned int num_zones = 0;
+
+ kern_return_t err = malloc_get_all_zones (0, task_peek, &zones, &num_zones);
+ if (KERN_SUCCESS == err)
+ {
+ for (unsigned int i=0; i<num_zones; ++i)
+ {
+ info->zone_callback (info, (const malloc_zone_t *)zones[i]);
+ }
+ }
+
+ if (info->check_vm_regions)
+ {
+#if defined (VM_REGION_SUBMAP_SHORT_INFO_COUNT_64)
+ typedef vm_region_submap_short_info_data_64_t RegionInfo;
+ enum { kRegionInfoSize = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 };
+#else
+ typedef vm_region_submap_info_data_64_t RegionInfo;
+ enum { kRegionInfoSize = VM_REGION_SUBMAP_INFO_COUNT_64 };
+#endif
+ task_t task = mach_task_self();
+ mach_vm_address_t vm_region_base_addr;
+ mach_vm_size_t vm_region_size;
+ natural_t vm_region_depth;
+ RegionInfo vm_region_info;
+
+ ((range_contains_data_callback_info_t *)info->baton)->unique = true;
+
+ 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 = kRegionInfoSize;
+ const kern_return_t err = mach_vm_region_recurse (task,
+ &vm_region_base_addr,
+ &vm_region_size,
+ &vm_region_depth,
+ (vm_region_recurse_info_t)&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 that aren't covered by the heap
+ if (vm_region_info.protection & VM_PROT_WRITE &&
+ vm_region_info.protection & VM_PROT_READ)
+ {
+ //printf ("checking vm_region: [0x%16.16llx - 0x%16.16llx)\n", (uint64_t)vm_region_base_addr, (uint64_t)vm_region_base_addr + vm_region_size);
+ range_info_callback (task,
+ info->baton,
+ stack_logging_type_vm_region,
+ vm_region_base_addr,
+ vm_region_size);
+ }
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+// dump_malloc_block_callback
+//
+// A simple callback that will dump each malloc block and all available
+// info from the enumeration callback perspective.
+//----------------------------------------------------------------------
+static void
+dump_malloc_block_callback (task_t task, void *baton, unsigned type, uint64_t ptr_addr, uint64_t ptr_size)
+{
+ printf ("task = 0x%4.4x: baton = %p, type = %u, ptr_addr = 0x%llx + 0x%llu\n", task, baton, type, ptr_addr, ptr_size);
+}
+
+static void
+ranges_callback (task_t task, void *baton, unsigned type, vm_range_t *ptrs, unsigned count)
+{
+ range_callback_info_t *info = (range_callback_info_t *)baton;
+ while(count--) {
+ info->range_callback (task, info->baton, type, ptrs->address, ptrs->size);
+ ptrs++;
+ }
+}
+
+static void
+enumerate_range_in_zone (void *baton, const malloc_zone_t *zone)
+{
+ range_callback_info_t *info = (range_callback_info_t *)baton;
+
+ if (zone && zone->introspect)
+ zone->introspect->enumerator (mach_task_self(),
+ info,
+ MALLOC_PTR_IN_USE_RANGE_TYPE,
+ (vm_address_t)zone,
+ task_peek,
+ ranges_callback);
+}
+
+static void
+range_info_callback (task_t task, void *baton, unsigned type, uint64_t ptr_addr, uint64_t ptr_size)
+{
+ const uint64_t end_addr = ptr_addr + ptr_size;
+
+ range_contains_data_callback_info_t *info = (range_contains_data_callback_info_t *)baton;
+ switch (info->type)
+ {
+ case eDataTypeAddress:
+ // Check if the current malloc block contains an address specified by "info->addr"
+ if (ptr_addr <= info->addr && info->addr < end_addr)
+ {
+ ++info->match_count;
+ malloc_match match = { (void *)ptr_addr, ptr_size, info->addr - ptr_addr, type };
+ g_matches.push_back(match, info->unique);
+ }
+ break;
+
+ case eDataTypeContainsData:
+ // Check if the current malloc block contains data specified in "info->data"
+ {
+ const uint32_t size = info->data.size;
+ if (size < ptr_size) // Make sure this block can contain this data
+ {
+ uint8_t *ptr_data = NULL;
+ if (task_peek (task, ptr_addr, ptr_size, (void **)&ptr_data) == KERN_SUCCESS)
+ {
+ const void *buffer = info->data.buffer;
+ assert (ptr_data);
+ const uint32_t align = info->data.align;
+ for (uint64_t addr = ptr_addr;
+ addr < end_addr && ((end_addr - addr) >= size);
+ addr += align, ptr_data += align)
+ {
+ if (memcmp (buffer, ptr_data, size) == 0)
+ {
+ ++info->match_count;
+ malloc_match match = { (void *)ptr_addr, ptr_size, addr - ptr_addr, type };
+ g_matches.push_back(match, info->unique);
+ }
+ }
+ }
+ else
+ {
+ printf ("0x%llx: error: couldn't read %llu bytes\n", ptr_addr, ptr_size);
+ }
+ }
+ }
+ break;
+
+ case eDataTypeObjC:
+ // Check if the current malloc block contains an objective C object
+ // of any sort where the first pointer in the object is an OBJC class
+ // pointer (an isa)
+ {
+ malloc_block_contents *block_contents = NULL;
+ if (task_peek (task, ptr_addr, sizeof(void *), (void **)&block_contents) == KERN_SUCCESS)
+ {
+ // We assume that g_objc_classes is up to date
+ // that the class list was verified to have some classes in it
+ // before calling this function
+ const uint32_t objc_class_idx = g_objc_classes.FindClassIndex (block_contents->isa);
+ if (objc_class_idx != UINT32_MAX)
+ {
+ bool match = false;
+ if (info->objc.match_isa == 0)
+ {
+ // Match any objective C object
+ match = true;
+ }
+ else
+ {
+ // Only match exact isa values in the current class or
+ // optionally in the super classes
+ if (info->objc.match_isa == block_contents->isa)
+ match = true;
+ else if (info->objc.match_superclasses)
+ {
+ Class super = class_getSuperclass(block_contents->isa);
+ while (super)
+ {
+ match = super == info->objc.match_isa;
+ if (match)
+ break;
+ super = class_getSuperclass(super);
+ }
+ }
+ }
+ if (match)
+ {
+ //printf (" success\n");
+ ++info->match_count;
+ malloc_match match = { (void *)ptr_addr, ptr_size, 0, type };
+ g_matches.push_back(match, info->unique);
+ }
+ else
+ {
+ //printf (" error: wrong class: %s\n", dl_info.dli_sname);
+ }
+ }
+ else
+ {
+ //printf ("\terror: symbol not objc class: %s\n", dl_info.dli_sname);
+ return;
+ }
+ }
+ }
+ break;
+
+ case eDataTypeHeapInfo:
+ // Check if the current malloc block contains an objective C object
+ // of any sort where the first pointer in the object is an OBJC class
+ // pointer (an isa)
+ {
+ malloc_block_contents *block_contents = NULL;
+ if (task_peek (task, ptr_addr, sizeof(void *), (void **)&block_contents) == KERN_SUCCESS)
+ {
+ // We assume that g_objc_classes is up to date
+ // that the class list was verified to have some classes in it
+ // before calling this function
+ const uint32_t objc_class_idx = g_objc_classes.FindClassIndex (block_contents->isa);
+ if (objc_class_idx != UINT32_MAX)
+ {
+ // This is an objective C object
+ g_objc_class_snapshot.AddInstance (objc_class_idx, ptr_size);
+ }
+ else
+ {
+ // Classify other heap info
+ }
+ }
+ }
+ break;
+
+ }
+}
+
+static void
+get_stack_for_address_enumerator(mach_stack_logging_record_t stack_record, void *task_ptr)
+{
+ malloc_stack_entry *stack_entry = g_malloc_stack_history.next();
+ if (stack_entry)
+ {
+ stack_entry->address = (void *)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;
+ kern_return_t err = __mach_stack_logging_frames_for_uniqued_stack (*(task_t *)task_ptr,
+ stack_record.stack_identifier,
+ stack_entry->frames,
+ 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;
+ }
+}
+
+malloc_stack_entry *
+get_stack_history_for_address (const void * addr, int history)
+{
+ if (!stack_logging_enable_logging)
+ return NULL;
+ g_malloc_stack_history.clear();
+ kern_return_t err;
+ task_t task = mach_task_self();
+ if (history)
+ {
+ err = __mach_stack_logging_enumerate_records (task,
+ (mach_vm_address_t)addr,
+ get_stack_for_address_enumerator,
+ &task);
+ }
+ else
+ {
+ malloc_stack_entry *stack_entry = g_malloc_stack_history.next();
+ if (stack_entry)
+ {
+ stack_entry->address = addr;
+ stack_entry->type_flags = stack_logging_type_alloc;
+ stack_entry->argument = 0;
+ stack_entry->num_frames = 0;
+ stack_entry->frames[0] = 0;
+ err = __mach_stack_logging_get_frames(task, (mach_vm_address_t)addr, stack_entry->frames, MAX_FRAMES, &stack_entry->num_frames);
+ if (err == 0 && stack_entry->num_frames > 0)
+ {
+ // Terminate the frames with zero if there is room
+ if (stack_entry->num_frames < MAX_FRAMES)
+ stack_entry->frames[stack_entry->num_frames] = 0;
+ }
+ else
+ {
+ g_malloc_stack_history.clear();
+ }
+ }
+ }
+ // Return data if there is any
+ return g_malloc_stack_history.data();
+}
+
+//----------------------------------------------------------------------
+// find_pointer_in_heap
+//
+// Finds a pointer value inside one or more currently valid malloc
+// blocks.
+//----------------------------------------------------------------------
+malloc_match *
+find_pointer_in_heap (const void * addr, int check_vm_regions)
+{
+ g_matches.clear();
+ // Setup "info" to look for a malloc block that contains data
+ // that is the pointer
+ if (addr)
+ {
+ range_contains_data_callback_info_t data_info;
+ data_info.type = eDataTypeContainsData; // Check each block for data
+ data_info.data.buffer = (uint8_t *)&addr; // What data? The pointer value passed in
+ data_info.data.size = sizeof(addr); // How many bytes? The byte size of a pointer
+ data_info.data.align = sizeof(addr); // Align to a pointer byte size
+ data_info.match_count = 0; // Initialize the match count to zero
+ data_info.done = false; // Set done to false so searching doesn't stop
+ data_info.unique = false; // Set to true when iterating on the vm_regions
+ range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
+ foreach_zone_in_this_process (&info);
+
+
+ }
+ return g_matches.data();
+}
+
+//----------------------------------------------------------------------
+// find_pointer_in_memory
+//
+// Finds a pointer value inside one or more currently valid malloc
+// blocks.
+//----------------------------------------------------------------------
+malloc_match *
+find_pointer_in_memory (uint64_t memory_addr, uint64_t memory_size, const void * addr)
+{
+ g_matches.clear();
+ // Setup "info" to look for a malloc block that contains data
+ // that is the pointer
+ range_contains_data_callback_info_t data_info;
+ data_info.type = eDataTypeContainsData; // Check each block for data
+ data_info.data.buffer = (uint8_t *)&addr; // What data? The pointer value passed in
+ data_info.data.size = sizeof(addr); // How many bytes? The byte size of a pointer
+ data_info.data.align = sizeof(addr); // Align to a pointer byte size
+ data_info.match_count = 0; // Initialize the match count to zero
+ data_info.done = false; // Set done to false so searching doesn't stop
+ data_info.unique = false; // Set to true when iterating on the vm_regions
+ range_info_callback (mach_task_self(), &data_info, stack_logging_type_generic, memory_addr, memory_size);
+ return g_matches.data();
+}
+
+//----------------------------------------------------------------------
+// find_objc_objects_in_memory
+//
+// Find all instances of ObjC classes 'c', or all ObjC classes if 'c' is
+// NULL. If 'c' is non NULL, then also check objects to see if they
+// inherit from 'c'
+//----------------------------------------------------------------------
+malloc_match *
+find_objc_objects_in_memory (void *isa, int check_vm_regions)
+{
+ g_matches.clear();
+ if (g_objc_classes.Update())
+ {
+ // Setup "info" to look for a malloc block that contains data
+ // that is the pointer
+ range_contains_data_callback_info_t data_info;
+ data_info.type = eDataTypeObjC; // Check each block for data
+ data_info.objc.match_isa = isa;
+ data_info.objc.match_superclasses = true;
+ data_info.match_count = 0; // Initialize the match count to zero
+ data_info.done = false; // Set done to false so searching doesn't stop
+ data_info.unique = false; // Set to true when iterating on the vm_regions
+ range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
+ foreach_zone_in_this_process (&info);
+ }
+ return g_matches.data();
+}
+
+//----------------------------------------------------------------------
+// get_heap_info
+//
+// Gather information for all allocations on the heap and report
+// statistics.
+//----------------------------------------------------------------------
+
+void
+get_heap_info (int sort_type)
+{
+ if (g_objc_classes.Update())
+ {
+ // Reset all stats
+ g_objc_class_snapshot.Reset ();
+ // Setup "info" to look for a malloc block that contains data
+ // that is the pointer
+ range_contains_data_callback_info_t data_info;
+ data_info.type = eDataTypeHeapInfo; // Check each block for data
+ data_info.match_count = 0; // Initialize the match count to zero
+ data_info.done = false; // Set done to false so searching doesn't stop
+ data_info.unique = false; // Set to true when iterating on the vm_regions
+ const int check_vm_regions = false;
+ range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
+ foreach_zone_in_this_process (&info);
+
+ // Sort and print byte total bytes
+ switch (sort_type)
+ {
+ case eSortTypeNone:
+ default:
+ case eSortTypeBytes:
+ g_objc_class_snapshot.SortByTotalBytes(g_objc_classes, true);
+ break;
+
+ case eSortTypeCount:
+ g_objc_class_snapshot.SortByTotalCount(g_objc_classes, true);
+ break;
+ }
+ }
+ else
+ {
+ printf ("error: no objective C classes\n");
+ }
+}
+
+//----------------------------------------------------------------------
+// find_cstring_in_heap
+//
+// Finds a C string inside one or more currently valid malloc blocks.
+//----------------------------------------------------------------------
+malloc_match *
+find_cstring_in_heap (const char *s, int check_vm_regions)
+{
+ g_matches.clear();
+ if (s == NULL || s[0] == '\0')
+ {
+ printf ("error: invalid argument (empty cstring)\n");
+ return NULL;
+ }
+ // Setup "info" to look for a malloc block that contains data
+ // that is the C string passed in aligned on a 1 byte boundary
+ range_contains_data_callback_info_t data_info;
+ data_info.type = eDataTypeContainsData; // Check each block for data
+ data_info.data.buffer = (uint8_t *)s; // What data? The C string passed in
+ data_info.data.size = strlen(s); // How many bytes? The length of the C string
+ data_info.data.align = 1; // Data doesn't need to be aligned, so set the alignment to 1
+ data_info.match_count = 0; // Initialize the match count to zero
+ data_info.done = false; // Set done to false so searching doesn't stop
+ data_info.unique = false; // Set to true when iterating on the vm_regions
+ range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
+ foreach_zone_in_this_process (&info);
+ return g_matches.data();
+}
+
+//----------------------------------------------------------------------
+// find_block_for_address
+//
+// Find the malloc block that whose address range contains "addr".
+//----------------------------------------------------------------------
+malloc_match *
+find_block_for_address (const void *addr, int check_vm_regions)
+{
+ g_matches.clear();
+ // Setup "info" to look for a malloc block that contains data
+ // that is the C string passed in aligned on a 1 byte boundary
+ range_contains_data_callback_info_t data_info;
+ data_info.type = eDataTypeAddress; // Check each block to see if the block contains the address passed in
+ data_info.addr = (uintptr_t)addr; // What data? The C string passed in
+ data_info.match_count = 0; // Initialize the match count to zero
+ data_info.done = false; // Set done to false so searching doesn't stop
+ data_info.unique = false; // Set to true when iterating on the vm_regions
+ range_callback_info_t info = { enumerate_range_in_zone, range_info_callback, &data_info, check_vm_regions };
+ foreach_zone_in_this_process (&info);
+ return g_matches.data();
+}
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/runtime/__init__.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/runtime/__init__.py?rev=255697&view=auto
==============================================================================
--- lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/runtime/__init__.py (added)
+++ lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/runtime/__init__.py Tue Dec 15 17:03:22 2015
@@ -0,0 +1,3 @@
+__all__ = []
+for x in __all__:
+ __import__('lldb.runtime.'+x)
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/__init__.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/__init__.py?rev=255697&view=auto
==============================================================================
--- lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/__init__.py (added)
+++ lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/__init__.py Tue Dec 15 17:03:22 2015
@@ -0,0 +1,3 @@
+__all__ = ["symbolication"]
+for x in __all__:
+ __import__('lldb.utils.'+x)
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/symbolication.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/symbolication.py?rev=255697&view=auto
==============================================================================
--- lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/symbolication.py (added)
+++ lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/symbolication.py Tue Dec 15 17:03:22 2015
@@ -0,0 +1,640 @@
+#!/usr/bin/python
+
+#----------------------------------------------------------------------
+# Be sure to add the python path that points to the LLDB shared library.
+#
+# To use this in the embedded python interpreter using "lldb":
+#
+# cd /path/containing/crashlog.py
+# lldb
+# (lldb) script import crashlog
+# "crashlog" command installed, type "crashlog --help" for detailed help
+# (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash
+#
+# The benefit of running the crashlog command inside lldb in the
+# embedded python interpreter is when the command completes, there
+# will be a target with all of the files loaded at the locations
+# described in the crash log. Only the files that have stack frames
+# in the backtrace will be loaded unless the "--load-all" option
+# has been specified. This allows users to explore the program in the
+# state it was in right at crash time.
+#
+# On MacOSX csh, tcsh:
+# ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
+#
+# On MacOSX sh, bash:
+# PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
+#----------------------------------------------------------------------
+
+import lldb
+import commands
+import optparse
+import os
+import plistlib
+import re
+import shlex
+import sys
+import time
+import uuid
+
+class Address:
+ """Class that represents an address that will be symbolicated"""
+ def __init__(self, target, load_addr):
+ self.target = target
+ self.load_addr = load_addr # The load address that this object represents
+ self.so_addr = None # the resolved lldb.SBAddress (if any), named so_addr for section/offset address
+ self.sym_ctx = None # The cached symbol context for this address
+ self.description = None # Any original textual description of this address to be used as a backup in case symbolication fails
+ self.symbolication = None # The cached symbolicated string that describes this address
+ self.inlined = False
+ def __str__(self):
+ s = "%#16.16x" % (self.load_addr)
+ if self.symbolication:
+ s += " %s" % (self.symbolication)
+ elif self.description:
+ s += " %s" % (self.description)
+ elif self.so_addr:
+ s += " %s" % (self.so_addr)
+ return s
+
+ def resolve_addr(self):
+ if self.so_addr == None:
+ self.so_addr = self.target.ResolveLoadAddress (self.load_addr)
+ return self.so_addr
+
+ def is_inlined(self):
+ return self.inlined
+
+ def get_symbol_context(self):
+ if self.sym_ctx == None:
+ sb_addr = self.resolve_addr()
+ if sb_addr:
+ self.sym_ctx = self.target.ResolveSymbolContextForAddress (sb_addr, lldb.eSymbolContextEverything)
+ else:
+ self.sym_ctx = lldb.SBSymbolContext()
+ return self.sym_ctx
+
+ def get_instructions(self):
+ sym_ctx = self.get_symbol_context()
+ if sym_ctx:
+ function = sym_ctx.GetFunction()
+ if function:
+ return function.GetInstructions(self.target)
+ return sym_ctx.GetSymbol().GetInstructions(self.target)
+ return None
+
+ def symbolicate(self, verbose = False):
+ if self.symbolication == None:
+ self.symbolication = ''
+ self.inlined = False
+ sym_ctx = self.get_symbol_context()
+ if sym_ctx:
+ module = sym_ctx.GetModule()
+ if module:
+ # Print full source file path in verbose mode
+ if verbose:
+ self.symbolication += str(module.GetFileSpec()) + '`'
+ else:
+ self.symbolication += module.GetFileSpec().GetFilename() + '`'
+ function_start_load_addr = -1
+ function = sym_ctx.GetFunction()
+ block = sym_ctx.GetBlock()
+ line_entry = sym_ctx.GetLineEntry()
+ symbol = sym_ctx.GetSymbol()
+ inlined_block = block.GetContainingInlinedBlock();
+ if function:
+ self.symbolication += function.GetName()
+
+ if inlined_block:
+ self.inlined = True
+ self.symbolication += ' [inlined] ' + inlined_block.GetInlinedName();
+ block_range_idx = inlined_block.GetRangeIndexForBlockAddress (self.so_addr)
+ if block_range_idx < lldb.UINT32_MAX:
+ block_range_start_addr = inlined_block.GetRangeStartAddress (block_range_idx)
+ function_start_load_addr = block_range_start_addr.GetLoadAddress (self.target)
+ if function_start_load_addr == -1:
+ function_start_load_addr = function.GetStartAddress().GetLoadAddress (self.target)
+ elif symbol:
+ self.symbolication += symbol.GetName()
+ function_start_load_addr = symbol.GetStartAddress().GetLoadAddress (self.target)
+ else:
+ self.symbolication = ''
+ return False
+
+ # Dump the offset from the current function or symbol if it is non zero
+ function_offset = self.load_addr - function_start_load_addr
+ if function_offset > 0:
+ self.symbolication += " + %u" % (function_offset)
+ elif function_offset < 0:
+ self.symbolication += " %i (invalid negative offset, file a bug) " % function_offset
+
+ # Print out any line information if any is available
+ if line_entry.GetFileSpec():
+ # Print full source file path in verbose mode
+ if verbose:
+ self.symbolication += ' at %s' % line_entry.GetFileSpec()
+ else:
+ self.symbolication += ' at %s' % line_entry.GetFileSpec().GetFilename()
+ self.symbolication += ':%u' % line_entry.GetLine ()
+ column = line_entry.GetColumn()
+ if column > 0:
+ self.symbolication += ':%u' % column
+ return True
+ return False
+
+class Section:
+ """Class that represents an load address range"""
+ sect_info_regex = re.compile('(?P<name>[^=]+)=(?P<range>.*)')
+ addr_regex = re.compile('^\s*(?P<start>0x[0-9A-Fa-f]+)\s*$')
+ range_regex = re.compile('^\s*(?P<start>0x[0-9A-Fa-f]+)\s*(?P<op>[-+])\s*(?P<end>0x[0-9A-Fa-f]+)\s*$')
+
+ def __init__(self, start_addr = None, end_addr = None, name = None):
+ self.start_addr = start_addr
+ self.end_addr = end_addr
+ self.name = name
+
+ @classmethod
+ def InitWithSBTargetAndSBSection(cls, target, section):
+ sect_load_addr = section.GetLoadAddress(target)
+ if sect_load_addr != lldb.LLDB_INVALID_ADDRESS:
+ obj = cls(sect_load_addr, sect_load_addr + section.size, section.name)
+ return obj
+ else:
+ return None
+
+ def contains(self, addr):
+ return self.start_addr <= addr and addr < self.end_addr;
+
+ def set_from_string(self, s):
+ match = self.sect_info_regex.match (s)
+ if match:
+ self.name = match.group('name')
+ range_str = match.group('range')
+ addr_match = self.addr_regex.match(range_str)
+ if addr_match:
+ self.start_addr = int(addr_match.group('start'), 16)
+ self.end_addr = None
+ return True
+
+ range_match = self.range_regex.match(range_str)
+ if range_match:
+ self.start_addr = int(range_match.group('start'), 16)
+ self.end_addr = int(range_match.group('end'), 16)
+ op = range_match.group('op')
+ if op == '+':
+ self.end_addr += self.start_addr
+ return True
+ print 'error: invalid section info string "%s"' % s
+ print 'Valid section info formats are:'
+ print 'Format Example Description'
+ print '--------------------- -----------------------------------------------'
+ print '<name>=<base> __TEXT=0x123000 Section from base address only'
+ print '<name>=<base>-<end> __TEXT=0x123000-0x124000 Section from base address and end address'
+ print '<name>=<base>+<size> __TEXT=0x123000+0x1000 Section from base address and size'
+ return False
+
+ def __str__(self):
+ if self.name:
+ if self.end_addr != None:
+ if self.start_addr != None:
+ return "%s=[0x%16.16x - 0x%16.16x)" % (self.name, self.start_addr, self.end_addr)
+ else:
+ if self.start_addr != None:
+ return "%s=0x%16.16x" % (self.name, self.start_addr)
+ return self.name
+ return "<invalid>"
+
+class Image:
+ """A class that represents an executable image and any associated data"""
+
+ def __init__(self, path, uuid = None):
+ self.path = path
+ self.resolved_path = None
+ self.resolved = False
+ self.unavailable = False
+ self.uuid = uuid
+ self.section_infos = list()
+ self.identifier = None
+ self.version = None
+ self.arch = None
+ self.module = None
+ self.symfile = None
+ self.slide = None
+
+ @classmethod
+ def InitWithSBTargetAndSBModule(cls, target, module):
+ '''Initialize this Image object with a module from a target.'''
+ obj = cls(module.file.fullpath, module.uuid)
+ obj.resolved_path = module.platform_file.fullpath
+ obj.resolved = True
+ obj.arch = module.triple
+ for section in module.sections:
+ symb_section = Section.InitWithSBTargetAndSBSection(target, section)
+ if symb_section:
+ obj.section_infos.append (symb_section)
+ obj.arch = module.triple
+ obj.module = module
+ obj.symfile = None
+ obj.slide = None
+ return obj
+
+ def dump(self, prefix):
+ print "%s%s" % (prefix, self)
+
+ def debug_dump(self):
+ print 'path = "%s"' % (self.path)
+ print 'resolved_path = "%s"' % (self.resolved_path)
+ print 'resolved = %i' % (self.resolved)
+ print 'unavailable = %i' % (self.unavailable)
+ print 'uuid = %s' % (self.uuid)
+ print 'section_infos = %s' % (self.section_infos)
+ print 'identifier = "%s"' % (self.identifier)
+ print 'version = %s' % (self.version)
+ print 'arch = %s' % (self.arch)
+ print 'module = %s' % (self.module)
+ print 'symfile = "%s"' % (self.symfile)
+ print 'slide = %i (0x%x)' % (self.slide, self.slide)
+
+ def __str__(self):
+ s = ''
+ if self.uuid:
+ s += "%s " % (self.get_uuid())
+ if self.arch:
+ s += "%s " % (self.arch)
+ if self.version:
+ s += "%s " % (self.version)
+ resolved_path = self.get_resolved_path()
+ if resolved_path:
+ s += "%s " % (resolved_path)
+ for section_info in self.section_infos:
+ s += ", %s" % (section_info)
+ if self.slide != None:
+ s += ', slide = 0x%16.16x' % self.slide
+ return s
+
+ def add_section(self, section):
+ #print "added '%s' to '%s'" % (section, self.path)
+ self.section_infos.append (section)
+
+ def get_section_containing_load_addr (self, load_addr):
+ for section_info in self.section_infos:
+ if section_info.contains(load_addr):
+ return section_info
+ return None
+
+ def get_resolved_path(self):
+ if self.resolved_path:
+ return self.resolved_path
+ elif self.path:
+ return self.path
+ return None
+
+ def get_resolved_path_basename(self):
+ path = self.get_resolved_path()
+ if path:
+ return os.path.basename(path)
+ return None
+
+ def symfile_basename(self):
+ if self.symfile:
+ return os.path.basename(self.symfile)
+ return None
+
+ def has_section_load_info(self):
+ return self.section_infos or self.slide != None
+
+ def load_module(self, target):
+ if self.unavailable:
+ return None # We already warned that we couldn't find this module, so don't return an error string
+ # Load this module into "target" using the section infos to
+ # set the section load addresses
+ if self.has_section_load_info():
+ if target:
+ if self.module:
+ if self.section_infos:
+ num_sections_loaded = 0
+ for section_info in self.section_infos:
+ if section_info.name:
+ section = self.module.FindSection (section_info.name)
+ if section:
+ error = target.SetSectionLoadAddress (section, section_info.start_addr)
+ if error.Success():
+ num_sections_loaded += 1
+ else:
+ return 'error: %s' % error.GetCString()
+ else:
+ return 'error: unable to find the section named "%s"' % section_info.name
+ else:
+ return 'error: unable to find "%s" section in "%s"' % (range.name, self.get_resolved_path())
+ if num_sections_loaded == 0:
+ return 'error: no sections were successfully loaded'
+ else:
+ err = target.SetModuleLoadAddress(self.module, self.slide)
+ if err.Fail():
+ return err.GetCString()
+ return None
+ else:
+ return 'error: invalid module'
+ else:
+ return 'error: invalid target'
+ else:
+ return 'error: no section infos'
+
+ def add_module(self, target):
+ '''Add the Image described in this object to "target" and load the sections if "load" is True.'''
+ if target:
+ # Try and find using UUID only first so that paths need not match up
+ uuid_str = self.get_normalized_uuid_string()
+ if uuid_str:
+ self.module = target.AddModule (None, None, uuid_str)
+ if not self.module:
+ self.locate_module_and_debug_symbols ()
+ if self.unavailable:
+ return None
+ resolved_path = self.get_resolved_path()
+ self.module = target.AddModule (resolved_path, self.arch, uuid_str, self.symfile)
+ if not self.module:
+ return 'error: unable to get module for (%s) "%s"' % (self.arch, self.get_resolved_path())
+ if self.has_section_load_info():
+ return self.load_module(target)
+ else:
+ return None # No sections, the module was added to the target, so success
+ else:
+ return 'error: invalid target'
+
+ def locate_module_and_debug_symbols (self):
+ # By default, just use the paths that were supplied in:
+ # self.path
+ # self.resolved_path
+ # self.module
+ # self.symfile
+ # Subclasses can inherit from this class and override this function
+ self.resolved = True
+ return True
+
+ def get_uuid(self):
+ if not self.uuid and self.module:
+ self.uuid = uuid.UUID(self.module.GetUUIDString())
+ return self.uuid
+
+ def get_normalized_uuid_string(self):
+ if self.uuid:
+ return str(self.uuid).upper()
+ return None
+
+ def create_target(self):
+ '''Create a target using the information in this Image object.'''
+ if self.unavailable:
+ return None
+
+ if self.locate_module_and_debug_symbols ():
+ resolved_path = self.get_resolved_path();
+ path_spec = lldb.SBFileSpec (resolved_path)
+ #result.PutCString ('plist[%s] = %s' % (uuid, self.plist))
+ error = lldb.SBError()
+ target = lldb.debugger.CreateTarget (resolved_path, self.arch, None, False, error);
+ if target:
+ self.module = target.FindModule(path_spec)
+ if self.has_section_load_info():
+ err = self.load_module(target)
+ if err:
+ print 'ERROR: ', err
+ return target
+ else:
+ print 'error: unable to create a valid target for (%s) "%s"' % (self.arch, self.path)
+ else:
+ print 'error: unable to locate main executable (%s) "%s"' % (self.arch, self.path)
+ return None
+
+class Symbolicator:
+
+ def __init__(self):
+ """A class the represents the information needed to symbolicate addresses in a program"""
+ self.target = None
+ self.images = list() # a list of images to be used when symbolicating
+ self.addr_mask = 0xffffffffffffffff
+
+ @classmethod
+ def InitWithSBTarget(cls, target):
+ obj = cls()
+ obj.target = target
+ obj.images = list();
+ triple = target.triple
+ if triple:
+ arch = triple.split('-')[0]
+ if "arm" in arch:
+ obj.addr_mask = 0xfffffffffffffffe
+
+ for module in target.modules:
+ image = Image.InitWithSBTargetAndSBModule(target, module)
+ obj.images.append(image)
+ return obj
+
+ def __str__(self):
+ s = "Symbolicator:\n"
+ if self.target:
+ s += "Target = '%s'\n" % (self.target)
+ s += "Target modules:\n"
+ for m in self.target.modules:
+ s += str(m) + "\n"
+ s += "Images:\n"
+ for image in self.images:
+ s += ' %s\n' % (image)
+ return s
+
+ def find_images_with_identifier(self, identifier):
+ images = list()
+ for image in self.images:
+ if image.identifier == identifier:
+ images.append(image)
+ if len(images) == 0:
+ regex_text = '^.*\.%s$' % (identifier)
+ regex = re.compile(regex_text)
+ for image in self.images:
+ if regex.match(image.identifier):
+ images.append(image)
+ return images
+
+ def find_image_containing_load_addr(self, load_addr):
+ for image in self.images:
+ if image.get_section_containing_load_addr (load_addr):
+ return image
+ return None
+
+ def create_target(self):
+ if self.target:
+ return self.target
+
+ if self.images:
+ for image in self.images:
+ self.target = image.create_target ()
+ if self.target:
+ if self.target.GetAddressByteSize() == 4:
+ triple = self.target.triple
+ if triple:
+ arch = triple.split('-')[0]
+ if "arm" in arch:
+ self.addr_mask = 0xfffffffffffffffe
+ return self.target
+ return None
+
+
+ def symbolicate(self, load_addr, verbose = False):
+ if not self.target:
+ self.create_target()
+ if self.target:
+ live_process = False
+ process = self.target.process
+ if process:
+ state = process.state
+ if state > lldb.eStateUnloaded and state < lldb.eStateDetached:
+ live_process = True
+ # If we don't have a live process, we can attempt to find the image
+ # that a load address belongs to and lazily load its module in the
+ # target, but we shouldn't do any of this if we have a live process
+ if not live_process:
+ image = self.find_image_containing_load_addr (load_addr)
+ if image:
+ image.add_module (self.target)
+ symbolicated_address = Address(self.target, load_addr)
+ if symbolicated_address.symbolicate (verbose):
+ if symbolicated_address.so_addr:
+ symbolicated_addresses = list()
+ symbolicated_addresses.append(symbolicated_address)
+ # See if we were able to reconstruct anything?
+ while 1:
+ inlined_parent_so_addr = lldb.SBAddress()
+ inlined_parent_sym_ctx = symbolicated_address.sym_ctx.GetParentOfInlinedScope (symbolicated_address.so_addr, inlined_parent_so_addr)
+ if not inlined_parent_sym_ctx:
+ break
+ if not inlined_parent_so_addr:
+ break
+
+ symbolicated_address = Address(self.target, inlined_parent_so_addr.GetLoadAddress(self.target))
+ symbolicated_address.sym_ctx = inlined_parent_sym_ctx
+ symbolicated_address.so_addr = inlined_parent_so_addr
+ symbolicated_address.symbolicate (verbose)
+
+ # push the new frame onto the new frame stack
+ symbolicated_addresses.append (symbolicated_address)
+
+ if symbolicated_addresses:
+ return symbolicated_addresses
+ else:
+ print 'error: no target in Symbolicator'
+ return None
+
+
+def disassemble_instructions (target, instructions, pc, insts_before_pc, insts_after_pc, non_zeroeth_frame):
+ lines = list()
+ pc_index = -1
+ comment_column = 50
+ for inst_idx, inst in enumerate(instructions):
+ inst_pc = inst.GetAddress().GetLoadAddress(target);
+ if pc == inst_pc:
+ pc_index = inst_idx
+ mnemonic = inst.GetMnemonic (target)
+ operands = inst.GetOperands (target)
+ comment = inst.GetComment (target)
+ #data = inst.GetData (target)
+ lines.append ("%#16.16x: %8s %s" % (inst_pc, mnemonic, operands))
+ if comment:
+ line_len = len(lines[-1])
+ if line_len < comment_column:
+ lines[-1] += ' ' * (comment_column - line_len)
+ lines[-1] += "; %s" % comment
+
+ if pc_index >= 0:
+ # If we are disassembling the non-zeroeth frame, we need to backup the PC by 1
+ if non_zeroeth_frame and pc_index > 0:
+ pc_index = pc_index - 1
+ if insts_before_pc == -1:
+ start_idx = 0
+ else:
+ start_idx = pc_index - insts_before_pc
+ if start_idx < 0:
+ start_idx = 0
+ if insts_before_pc == -1:
+ end_idx = inst_idx
+ else:
+ end_idx = pc_index + insts_after_pc
+ if end_idx > inst_idx:
+ end_idx = inst_idx
+ for i in range(start_idx, end_idx+1):
+ if i == pc_index:
+ print ' -> ', lines[i]
+ else:
+ print ' ', lines[i]
+
+def print_module_section_data (section):
+ print section
+ section_data = section.GetSectionData()
+ if section_data:
+ ostream = lldb.SBStream()
+ section_data.GetDescription (ostream, section.GetFileAddress())
+ print ostream.GetData()
+
+def print_module_section (section, depth):
+ print section
+ if depth > 0:
+ num_sub_sections = section.GetNumSubSections()
+ for sect_idx in range(num_sub_sections):
+ print_module_section (section.GetSubSectionAtIndex(sect_idx), depth - 1)
+
+def print_module_sections (module, depth):
+ for sect in module.section_iter():
+ print_module_section (sect, depth)
+
+def print_module_symbols (module):
+ for sym in module:
+ print sym
+
+def Symbolicate(command_args):
+
+ usage = "usage: %prog [options] <addr1> [addr2 ...]"
+ description='''Symbolicate one or more addresses using LLDB's python scripting API..'''
+ parser = optparse.OptionParser(description=description, prog='crashlog.py',usage=usage)
+ parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
+ parser.add_option('-p', '--platform', type='string', metavar='platform', dest='platform', help='Specify the platform to use when creating the debug target. Valid values include "localhost", "darwin-kernel", "ios-simulator", "remote-freebsd", "remote-macosx", "remote-ios", "remote-linux".')
+ parser.add_option('-f', '--file', type='string', metavar='file', dest='file', help='Specify a file to use when symbolicating')
+ parser.add_option('-a', '--arch', type='string', metavar='arch', dest='arch', help='Specify a architecture to use when symbolicating')
+ parser.add_option('-s', '--slide', type='int', metavar='slide', dest='slide', help='Specify the slide to use on the file specified with the --file option', default=None)
+ parser.add_option('--section', type='string', action='append', dest='section_strings', help='specify <sect-name>=<start-addr> or <sect-name>=<start-addr>-<end-addr>')
+ try:
+ (options, args) = parser.parse_args(command_args)
+ except:
+ return
+ symbolicator = Symbolicator()
+ images = list();
+ if options.file:
+ image = Image(options.file);
+ image.arch = options.arch
+ # Add any sections that were specified with one or more --section options
+ if options.section_strings:
+ for section_str in options.section_strings:
+ section = Section()
+ if section.set_from_string (section_str):
+ image.add_section (section)
+ else:
+ sys.exit(1)
+ if options.slide != None:
+ image.slide = options.slide
+ symbolicator.images.append(image)
+
+ target = symbolicator.create_target()
+ if options.verbose:
+ print symbolicator
+ if target:
+ for addr_str in args:
+ addr = int(addr_str, 0)
+ symbolicated_addrs = symbolicator.symbolicate(addr, options.verbose)
+ for symbolicated_addr in symbolicated_addrs:
+ print symbolicated_addr
+ print
+ else:
+ print 'error: no target for %s' % (symbolicator)
+
+if __name__ == '__main__':
+ # Create a new debugger instance
+ lldb.debugger = lldb.SBDebugger.Create()
+ Symbolicate (sys.argv[1:])
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/Python/lldb/utils/symbolication.py
------------------------------------------------------------------------------
svn:executable = *
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/darwin-debug
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/darwin-debug?rev=255697&view=auto
==============================================================================
Binary file - no diff available.
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/darwin-debug
------------------------------------------------------------------------------
svn:executable = *
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/darwin-debug
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/debugserver
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/debugserver?rev=255697&view=auto
==============================================================================
Binary file - no diff available.
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/debugserver
------------------------------------------------------------------------------
svn:executable = *
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/debugserver
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/lldb-argdumper
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/lldb-argdumper?rev=255697&view=auto
==============================================================================
Binary file - no diff available.
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/lldb-argdumper
------------------------------------------------------------------------------
svn:executable = *
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/lldb-argdumper
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/lldb-server
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/lldb-server?rev=255697&view=auto
==============================================================================
Binary file - no diff available.
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/lldb-server
------------------------------------------------------------------------------
svn:executable = *
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/A/Resources/lldb-server
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: lldb/trunk/build/Debug/LLDB.framework/Versions/Current
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/build/Debug/LLDB.framework/Versions/Current?rev=255697&view=auto
==============================================================================
--- lldb/trunk/build/Debug/LLDB.framework/Versions/Current (added)
+++ lldb/trunk/build/Debug/LLDB.framework/Versions/Current Tue Dec 15 17:03:22 2015
@@ -0,0 +1 @@
+link A
\ No newline at end of file
Propchange: lldb/trunk/build/Debug/LLDB.framework/Versions/Current
------------------------------------------------------------------------------
svn:special = *
Modified: lldb/trunk/include/lldb/API/LLDB.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/API/LLDB.h?rev=255697&r1=255696&r2=255697&view=diff
==============================================================================
--- lldb/trunk/include/lldb/API/LLDB.h (original)
+++ lldb/trunk/include/lldb/API/LLDB.h Tue Dec 15 17:03:22 2015
@@ -16,6 +16,7 @@
// Project includes
#include "lldb/API/SBDefines.h"
#include "lldb/API/SBAddress.h"
+#include "lldb/API/SBAttachInfo.h"
#include "lldb/API/SBBlock.h"
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBBreakpointLocation.h"
@@ -29,31 +30,48 @@
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBError.h"
#include "lldb/API/SBEvent.h"
-#include "lldb/API/SBExpressionOptions.h"
#include "lldb/API/SBExecutionContext.h"
+#include "lldb/API/SBExpressionOptions.h"
#include "lldb/API/SBFileSpec.h"
+#include "lldb/API/SBFileSpecList.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBFunction.h"
#include "lldb/API/SBHostOS.h"
#include "lldb/API/SBInstruction.h"
#include "lldb/API/SBInstructionList.h"
+#include "lldb/API/SBLanguageRuntime.h"
+#include "lldb/API/SBLaunchInfo.h"
#include "lldb/API/SBLineEntry.h"
#include "lldb/API/SBListener.h"
#include "lldb/API/SBModule.h"
+#include "lldb/API/SBModuleSpec.h"
+#include "lldb/API/SBPlatform.h"
#include "lldb/API/SBProcess.h"
#include "lldb/API/SBQueue.h"
#include "lldb/API/SBQueueItem.h"
+#include "lldb/API/SBSection.h"
#include "lldb/API/SBSourceManager.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStringList.h"
#include "lldb/API/SBSymbol.h"
#include "lldb/API/SBSymbolContext.h"
+#include "lldb/API/SBSymbolContextList.h"
#include "lldb/API/SBTarget.h"
#include "lldb/API/SBThread.h"
+#include "lldb/API/SBThreadCollection.h"
+#include "lldb/API/SBThreadPlan.h"
#include "lldb/API/SBType.h"
+#include "lldb/API/SBTypeCategory.h"
+#include "lldb/API/SBTypeEnumMember.h"
+#include "lldb/API/SBTypeFilter.h"
+#include "lldb/API/SBTypeFormat.h"
+#include "lldb/API/SBTypeNameSpecifier.h"
+#include "lldb/API/SBTypeSummary.h"
+#include "lldb/API/SBTypeSynthetic.h"
#include "lldb/API/SBUnixSignals.h"
#include "lldb/API/SBValue.h"
#include "lldb/API/SBValueList.h"
#include "lldb/API/SBVariablesOptions.h"
+#include "lldb/API/SBWatchpoint.h"
#endif // LLDB_LLDB_h_
Modified: lldb/trunk/include/lldb/API/SBStream.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/API/SBStream.h?rev=255697&r1=255696&r2=255697&view=diff
==============================================================================
--- lldb/trunk/include/lldb/API/SBStream.h (original)
+++ lldb/trunk/include/lldb/API/SBStream.h Tue Dec 15 17:03:22 2015
@@ -21,7 +21,9 @@ class LLDB_API SBStream
public:
SBStream ();
-
+
+ SBStream (SBStream &&rhs);
+
~SBStream ();
bool
Modified: lldb/trunk/source/API/SBStream.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/API/SBStream.cpp?rev=255697&r1=255696&r2=255697&view=diff
==============================================================================
--- lldb/trunk/source/API/SBStream.cpp (original)
+++ lldb/trunk/source/API/SBStream.cpp Tue Dec 15 17:03:22 2015
@@ -23,6 +23,13 @@ SBStream::SBStream () :
{
}
+SBStream::SBStream (SBStream &&rhs) :
+ m_opaque_ap (std::move(rhs.m_opaque_ap)),
+ m_is_file (rhs.m_is_file)
+{
+}
+
+
SBStream::~SBStream ()
{
}
More information about the lldb-commits
mailing list