| #!/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)) |