blob: 8218f4ae6323a50bb106235a80d00054542b2e2b [file] [log] [blame]
#!/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))