aboutsummaryrefslogtreecommitdiff
path: root/examples/python/bsd.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/python/bsd.py')
-rwxr-xr-xexamples/python/bsd.py481
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))