diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2018-07-28 11:09:23 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2018-07-28 11:09:23 +0000 |
commit | f73363f1dd94996356cefbf24388f561891acf0b (patch) | |
tree | e3c31248bdb36eaec5fd833490d4278162dba2a0 /examples | |
parent | 160ee69dd7ae18978f4068116777639ea98dc951 (diff) |
Vendor import of lldb trunk r338150:vendor/lldb/lldb-trunk-r338150
Notes
Notes:
svn path=/vendor/lldb/dist/; revision=336823
svn path=/vendor/lldb/lldb-trunk-r338150/; revision=336824; tag=vendor/lldb/lldb-trunk-r338150
Diffstat (limited to 'examples')
-rw-r--r-- | examples/darwin/heap_find/heap/heap_find.cpp | 4 | ||||
-rwxr-xr-x | examples/python/bsd.py | 481 | ||||
-rw-r--r-- | examples/python/cmdtemplate.py | 131 |
3 files changed, 561 insertions, 55 deletions
diff --git a/examples/darwin/heap_find/heap/heap_find.cpp b/examples/darwin/heap_find/heap/heap_find.cpp index 3567e559a6a7..b63d18b712bd 100644 --- a/examples/darwin/heap_find/heap/heap_find.cpp +++ b/examples/darwin/heap_find/heap/heap_find.cpp @@ -410,7 +410,7 @@ public: m_sort_type = eSortTypeBytes; } if (print && m_size > 0) { - puts("Objective C objects by total bytes:"); + puts("Objective-C objects by total bytes:"); puts("Total Bytes Class Name"); puts("----------- " "-----------------------------------------------------------------"); @@ -427,7 +427,7 @@ public: m_sort_type = eSortTypeCount; } if (print && m_size > 0) { - puts("Objective C objects by total count:"); + puts("Objective-C objects by total count:"); puts("Count Class Name"); puts("-------- " "-----------------------------------------------------------------"); diff --git a/examples/python/bsd.py b/examples/python/bsd.py new file mode 100755 index 000000000000..8218f4ae6323 --- /dev/null +++ b/examples/python/bsd.py @@ -0,0 +1,481 @@ +#!/usr/bin/python + +import optparse +import os +import shlex +import struct +import sys + +ARMAG = "!<arch>\n" +SARMAG = 8 +ARFMAG = "`\n" +AR_EFMT1 = "#1/" + + +def memdump(src, bytes_per_line=16, address=0): + FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' + for x in range(256)]) + for i in range(0, len(src), bytes_per_line): + s = src[i:i+bytes_per_line] + hex_bytes = ' '.join(["%02x" % (ord(x)) for x in s]) + ascii = s.translate(FILTER) + print("%#08.8x: %-*s %s" % (address+i, bytes_per_line*3, hex_bytes, + ascii)) + + +class Object(object): + def __init__(self, file): + def read_str(file, str_len): + return file.read(str_len).rstrip('\0 ') + + def read_int(file, str_len, base): + return int(read_str(file, str_len), base) + + self.offset = file.tell() + self.file = file + self.name = read_str(file, 16) + self.date = read_int(file, 12, 10) + self.uid = read_int(file, 6, 10) + self.gid = read_int(file, 6, 10) + self.mode = read_int(file, 8, 8) + self.size = read_int(file, 10, 10) + if file.read(2) != ARFMAG: + raise ValueError('invalid BSD object at offset %#08.8x' % ( + self.offset)) + # If we have an extended name read it. Extended names start with + name_len = 0 + if self.name.startswith(AR_EFMT1): + name_len = int(self.name[len(AR_EFMT1):], 10) + self.name = read_str(file, name_len) + self.obj_offset = file.tell() + self.obj_size = self.size - name_len + file.seek(self.obj_size, 1) + + def dump(self, f=sys.stdout, flat=True): + if flat: + f.write('%#08.8x: %#08.8x %5u %5u %6o %#08.8x %s\n' % (self.offset, + self.date, self.uid, self.gid, self.mode, self.size, + self.name)) + else: + f.write('%#08.8x: \n' % self.offset) + f.write(' name = "%s"\n' % self.name) + f.write(' date = %#08.8x\n' % self.date) + f.write(' uid = %i\n' % self.uid) + f.write(' gid = %i\n' % self.gid) + f.write(' mode = %o\n' % self.mode) + f.write(' size = %#08.8x\n' % (self.size)) + self.file.seek(self.obj_offset, 0) + first_bytes = self.file.read(4) + f.write('bytes = ') + memdump(first_bytes) + + def get_bytes(self): + saved_pos = self.file.tell() + self.file.seek(self.obj_offset, 0) + bytes = self.file.read(self.obj_size) + self.file.seek(saved_pos, 0) + return bytes + + +class StringTable(object): + def __init__(self, bytes): + self.bytes = bytes + + def get_string(self, offset): + length = len(self.bytes) + if offset >= length: + return None + return self.bytes[offset:self.bytes.find('\0', offset)] + + +class Archive(object): + def __init__(self, path): + self.path = path + self.file = open(path, 'r') + self.objects = [] + self.offset_to_object = {} + if self.file.read(SARMAG) != ARMAG: + print("error: file isn't a BSD archive") + while True: + try: + self.objects.append(Object(self.file)) + except ValueError: + break + + def get_object_at_offset(self, offset): + if offset in self.offset_to_object: + return self.offset_to_object[offset] + for obj in self.objects: + if obj.offset == offset: + self.offset_to_object[offset] = obj + return obj + return None + + def find(self, name, mtime=None, f=sys.stdout): + ''' + Find an object(s) by name with optional modification time. There + can be multple objects with the same name inside and possibly with + the same modification time within a BSD archive so clients must be + prepared to get multiple results. + ''' + matches = [] + for obj in self.objects: + if obj.name == name and (mtime is None or mtime == obj.date): + matches.append(obj) + return matches + + @classmethod + def dump_header(self, f=sys.stdout): + f.write(' DATE UID GID MODE SIZE NAME\n') + f.write(' ---------- ----- ----- ------ ---------- ' + '--------------\n') + + def get_symdef(self): + def get_uint32(file): + '''Extract a uint32_t from the current file position.''' + v, = struct.unpack('=I', file.read(4)) + return v + + for obj in self.objects: + symdef = [] + if obj.name.startswith("__.SYMDEF"): + self.file.seek(obj.obj_offset, 0) + ranlib_byte_size = get_uint32(self.file) + num_ranlib_structs = ranlib_byte_size/8 + str_offset_pairs = [] + for _ in range(num_ranlib_structs): + strx = get_uint32(self.file) + offset = get_uint32(self.file) + str_offset_pairs.append((strx, offset)) + strtab_len = get_uint32(self.file) + strtab = StringTable(self.file.read(strtab_len)) + for s in str_offset_pairs: + symdef.append((strtab.get_string(s[0]), s[1])) + return symdef + + def get_object_dicts(self): + ''' + Returns an array of object dictionaries that contain they following + keys: + 'object': the actual bsd.Object instance + 'symdefs': an array of symbol names that the object contains + as found in the "__.SYMDEF" item in the archive + ''' + symdefs = self.get_symdef() + symdef_dict = {} + if symdefs: + for (name, offset) in symdefs: + if offset in symdef_dict: + object_dict = symdef_dict[offset] + else: + object_dict = { + 'object': self.get_object_at_offset(offset), + 'symdefs': [] + } + symdef_dict[offset] = object_dict + object_dict['symdefs'].append(name) + object_dicts = [] + for offset in sorted(symdef_dict): + object_dicts.append(symdef_dict[offset]) + return object_dicts + + def dump(self, f=sys.stdout, flat=True): + f.write('%s:\n' % self.path) + if flat: + self.dump_header(f=f) + for obj in self.objects: + obj.dump(f=f, flat=flat) + + +def main(): + parser = optparse.OptionParser( + prog='bsd', + description='Utility for BSD archives') + parser.add_option( + '--object', + type='string', + dest='object_name', + default=None, + help=('Specify the name of a object within the BSD archive to get ' + 'information on')) + parser.add_option( + '-s', '--symbol', + type='string', + dest='find_symbol', + default=None, + help=('Specify the name of a symbol within the BSD archive to get ' + 'information on from SYMDEF')) + parser.add_option( + '--symdef', + action='store_true', + dest='symdef', + default=False, + help=('Dump the information in the SYMDEF.')) + parser.add_option( + '-v', '--verbose', + action='store_true', + dest='verbose', + default=False, + help='Enable verbose output') + parser.add_option( + '-e', '--extract', + action='store_true', + dest='extract', + default=False, + help=('Specify this to extract the object specified with the --object ' + 'option. There must be only one object with a matching name or ' + 'the --mtime option must be specified to uniquely identify a ' + 'single object.')) + parser.add_option( + '-m', '--mtime', + type='int', + dest='mtime', + default=None, + help=('Specify the modification time of the object an object. This ' + 'option is used with either the --object or --extract options.')) + parser.add_option( + '-o', '--outfile', + type='string', + dest='outfile', + default=None, + help=('Specify a different name or path for the file to extract when ' + 'using the --extract option. If this option isn\'t specified, ' + 'then the extracted object file will be extracted into the ' + 'current working directory if a file doesn\'t already exist ' + 'with that name.')) + + (options, args) = parser.parse_args(sys.argv[1:]) + + for path in args: + archive = Archive(path) + if options.object_name: + print('%s:\n' % (path)) + matches = archive.find(options.object_name, options.mtime) + if matches: + dump_all = True + if options.extract: + if len(matches) == 1: + dump_all = False + if options.outfile is None: + outfile_path = matches[0].name + else: + outfile_path = options.outfile + if os.path.exists(outfile_path): + print('error: outfile "%s" already exists' % ( + outfile_path)) + else: + print('Saving file to "%s"...' % (outfile_path)) + with open(outfile_path, 'w') as outfile: + outfile.write(matches[0].get_bytes()) + else: + print('error: multiple objects match "%s". Specify ' + 'the modification time using --mtime.' % ( + options.object_name)) + if dump_all: + for obj in matches: + obj.dump(flat=False) + else: + print('error: object "%s" not found in archive' % ( + options.object_name)) + elif options.find_symbol: + symdefs = archive.get_symdef() + if symdefs: + success = False + for (name, offset) in symdefs: + obj = archive.get_object_at_offset(offset) + if name == options.find_symbol: + print('Found "%s" in:' % (options.find_symbol)) + obj.dump(flat=False) + success = True + if not success: + print('Didn\'t find "%s" in any objects' % ( + options.find_symbol)) + else: + print("error: no __.SYMDEF was found") + elif options.symdef: + object_dicts = archive.get_object_dicts() + for object_dict in object_dicts: + object_dict['object'].dump(flat=False) + print("symbols:") + for name in object_dict['symdefs']: + print(" %s" % (name)) + else: + archive.dump(flat=not options.verbose) + + +if __name__ == '__main__': + main() + + +def print_mtime_error(result, dmap_mtime, actual_mtime): + print >>result, ("error: modification time in debug map (%#08.8x) doesn't " + "match the .o file modification time (%#08.8x)" % ( + dmap_mtime, actual_mtime)) + + +def print_file_missing_error(result, path): + print >>result, "error: file \"%s\" doesn't exist" % (path) + + +def print_multiple_object_matches(result, object_name, mtime, matches): + print >>result, ("error: multiple matches for object '%s' with with " + "modification time %#08.8x:" % (object_name, mtime)) + Archive.dump_header(f=result) + for match in matches: + match.dump(f=result, flat=True) + + +def print_archive_object_error(result, object_name, mtime, archive): + matches = archive.find(object_name, f=result) + if len(matches) > 0: + print >>result, ("error: no objects have a modification time that " + "matches %#08.8x for '%s'. Potential matches:" % ( + mtime, object_name)) + Archive.dump_header(f=result) + for match in matches: + match.dump(f=result, flat=True) + else: + print >>result, "error: no object named \"%s\" found in archive:" % ( + object_name) + Archive.dump_header(f=result) + for match in archive.objects: + match.dump(f=result, flat=True) + # archive.dump(f=result, flat=True) + + +class VerifyDebugMapCommand: + name = "verify-debug-map-objects" + + def create_options(self): + usage = "usage: %prog [options]" + description = '''This command reports any .o files that are missing +or whose modification times don't match in the debug map of an executable.''' + + self.parser = optparse.OptionParser( + description=description, + prog=self.name, + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-e', '--errors', + action='store_true', + dest='errors', + default=False, + help="Only show errors") + + def get_short_help(self): + return "Verify debug map object files." + + def get_long_help(self): + return self.help_string + + def __init__(self, debugger, unused): + self.create_options() + self.help_string = self.parser.format_help() + + def __call__(self, debugger, command, exe_ctx, result): + import lldb + # Use the Shell Lexer to properly parse up command options just like a + # shell would + command_args = shlex.split(command) + + try: + (options, args) = self.parser.parse_args(command_args) + except: + result.SetError("option parsing failed") + return + + # Always get program state from the SBExecutionContext passed in + target = exe_ctx.GetTarget() + if not target.IsValid(): + result.SetError("invalid target") + return + archives = {} + for module_spec in args: + module = target.module[module_spec] + if not (module and module.IsValid()): + result.SetError('error: invalid module specification: "%s". ' + 'Specify the full path, basename, or UUID of ' + 'a module ' % (module_spec)) + return + num_symbols = module.GetNumSymbols() + num_errors = 0 + for i in range(num_symbols): + symbol = module.GetSymbolAtIndex(i) + if symbol.GetType() != lldb.eSymbolTypeObjectFile: + continue + path = symbol.GetName() + if not path: + continue + # Extract the value of the symbol by dumping the + # symbol. The value is the mod time. + dmap_mtime = int(str(symbol).split('value = ') + [1].split(',')[0], 16) + if not options.errors: + print >>result, '%s' % (path) + if os.path.exists(path): + actual_mtime = int(os.stat(path).st_mtime) + if dmap_mtime != actual_mtime: + num_errors += 1 + if options.errors: + print >>result, '%s' % (path), + print_mtime_error(result, dmap_mtime, + actual_mtime) + elif path[-1] == ')': + (archive_path, object_name) = path[0:-1].split('(') + if not archive_path and not object_name: + num_errors += 1 + if options.errors: + print >>result, '%s' % (path), + print_file_missing_error(path) + continue + if not os.path.exists(archive_path): + num_errors += 1 + if options.errors: + print >>result, '%s' % (path), + print_file_missing_error(archive_path) + continue + if archive_path in archives: + archive = archives[archive_path] + else: + archive = Archive(archive_path) + archives[archive_path] = archive + matches = archive.find(object_name, dmap_mtime) + num_matches = len(matches) + if num_matches == 1: + print >>result, '1 match' + obj = matches[0] + if obj.date != dmap_mtime: + num_errors += 1 + if options.errors: + print >>result, '%s' % (path), + print_mtime_error(result, dmap_mtime, obj.date) + elif num_matches == 0: + num_errors += 1 + if options.errors: + print >>result, '%s' % (path), + print_archive_object_error(result, object_name, + dmap_mtime, archive) + elif num_matches > 1: + num_errors += 1 + if options.errors: + print >>result, '%s' % (path), + print_multiple_object_matches(result, + object_name, + dmap_mtime, matches) + if num_errors > 0: + print >>result, "%u errors found" % (num_errors) + else: + print >>result, "No errors detected in debug map" + + +def __lldb_init_module(debugger, dict): + # This initializer is being run from LLDB in the embedded command + # interpreter. + # Add any commands contained in this module to LLDB + debugger.HandleCommand( + 'command script add -c %s.VerifyDebugMapCommand %s' % ( + __name__, VerifyDebugMapCommand.name)) + print('The "%s" command has been installed, type "help %s" for detailed ' + 'help.' % (VerifyDebugMapCommand.name, VerifyDebugMapCommand.name)) diff --git a/examples/python/cmdtemplate.py b/examples/python/cmdtemplate.py index 4d506b91aaf8..0acb04ef4715 100644 --- a/examples/python/cmdtemplate.py +++ b/examples/python/cmdtemplate.py @@ -1,69 +1,91 @@ #!/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" just # import it with the full path using the "command script import" # command # (lldb) command script import /path/to/cmdtemplate.py -#---------------------------------------------------------------------- +# --------------------------------------------------------------------- +import inspect import lldb -import commands import optparse import shlex +import sys + class FrameStatCommand: - def create_options(self): + program = 'framestats' + + @classmethod + def register_lldb_command(cls, debugger, module_name): + parser = cls.create_options() + cls.__doc__ = parser.format_help() + # Add any commands contained in this module to LLDB + command = 'command script add -c %s.%s %s' % (module_name, + cls.__name__, + cls.program) + debugger.HandleCommand(command) + print('The "{0}" command has been installed, type "help {0}" or "{0} ' + '--help" for detailed help.'.format(cls.program)) + + @classmethod + def create_options(cls): usage = "usage: %prog [options]" - description = '''This command is meant to be an example of how to make an LLDB command that -does something useful, follows best practices, and exploits the SB API. -Specifically, this command computes the aggregate and average size of the variables in the current frame -and allows you to tweak exactly which variables are to be accounted in the computation. -''' - - # Pass add_help_option = False, since this keeps the command in line with lldb commands, - # and we wire up "help command" to work by providing the long & short help methods below. - self.parser = optparse.OptionParser( - description = description, - prog = 'framestats', - usage = usage, - add_help_option = False) - - self.parser.add_option( + description = ('This command is meant to be an example of how to make ' + 'an LLDB command that does something useful, follows ' + 'best practices, and exploits the SB API. ' + 'Specifically, this command computes the aggregate ' + 'and average size of the variables in the current ' + 'frame and allows you to tweak exactly which variables ' + 'are to be accounted in the computation.') + + # Pass add_help_option = False, since this keeps the command in line + # with lldb commands, and we wire up "help command" to work by + # providing the long & short help methods below. + parser = optparse.OptionParser( + description=description, + prog=cls.program, + usage=usage, + add_help_option=False) + + parser.add_option( '-i', '--in-scope', - action = 'store_true', - dest = 'inscope', - help = 'in_scope_only = True', - default = True) + action='store_true', + dest='inscope', + help='in_scope_only = True', + default=True) - self.parser.add_option( + parser.add_option( '-a', '--arguments', - action = 'store_true', - dest = 'arguments', - help = 'arguments = True', - default = True) + action='store_true', + dest='arguments', + help='arguments = True', + default=True) - self.parser.add_option( + parser.add_option( '-l', '--locals', - action = 'store_true', - dest = 'locals', - help = 'locals = True', - default = True) + action='store_true', + dest='locals', + help='locals = True', + default=True) - self.parser.add_option( + parser.add_option( '-s', '--statics', - action = 'store_true', - dest = 'statics', - help = 'statics = True', - default = True) - + action='store_true', + dest='statics', + help='statics = True', + default=True) + + return parser + def get_short_help(self): return "Example command for use in debugging" @@ -71,23 +93,25 @@ and allows you to tweak exactly which variables are to be accounted in the compu return self.help_string def __init__(self, debugger, unused): - self.create_options() + self.parser = self.create_options() self.help_string = self.parser.format_help() def __call__(self, debugger, command, exe_ctx, result): # Use the Shell Lexer to properly parse up command options just like a # shell would command_args = shlex.split(command) - + try: (options, args) = self.parser.parse_args(command_args) except: - # if you don't handle exceptions, passing an incorrect argument to the OptionParser will cause LLDB to exit - # (courtesy of OptParse dealing with argument errors by throwing SystemExit) + # if you don't handle exceptions, passing an incorrect argument to + # the OptionParser will cause LLDB to exit (courtesy of OptParse + # dealing with argument errors by throwing SystemExit) result.SetError("option parsing failed") return - # Always get program state from the SBExecutionContext passed in as exe_ctx + # Always get program state from the lldb.SBExecutionContext passed + # in as exe_ctx frame = exe_ctx.GetFrame() if not frame.IsValid(): result.SetError("invalid frame") @@ -108,15 +132,16 @@ and allows you to tweak exactly which variables are to be accounted in the compu variable_type = variable.GetType() total_size = total_size + variable_type.GetByteSize() average_size = float(total_size) / variables_count - print >>result, "Your frame has %d variables. Their total size is %d bytes. The average size is %f bytes" % ( - variables_count, total_size, average_size) - # not returning anything is akin to returning success + print >>result, ("Your frame has %d variables. Their total size " + "is %d bytes. The average size is %f bytes") % ( + variables_count, total_size, average_size) + # not returning anything is akin to returning success def __lldb_init_module(debugger, dict): - # This initializer is being run from LLDB in the embedded command interpreter - - # Add any commands contained in this module to LLDB - debugger.HandleCommand( - 'command script add -c cmdtemplate.FrameStatCommand framestats') - print 'The "framestats" command has been installed, type "help framestats" for detailed help.' + # Register all classes that have a register_lldb_command method + for _name, cls in inspect.getmembers(sys.modules[__name__]): + if inspect.isclass(cls) and callable(getattr(cls, + "register_lldb_command", + None)): + cls.register_lldb_command(debugger, __name__) |