diff options
Diffstat (limited to 'examples/python/bsd.py')
-rwxr-xr-x | examples/python/bsd.py | 481 |
1 files changed, 481 insertions, 0 deletions
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)) |