blob: e423946dfa54818c2153ec753a941f36eb21c1b3 [file] [log] [blame]
#!/usr/bin/env python2.7
# Copyright 2017 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from collections import namedtuple
import elfinfo
import glob
import os
# Copied from //zircon/system/public/zircon/driver/binding.h, which see.
ZIRCON_NOTE_DRIVER = 0x31565244 # DRV1
LIBCXX_SONAMES = ['', '', '']
def binary_info(filename):
return elfinfo.get_elf_info(filename, [ZIRCON_DRIVER_IDENT])
def is_driver(info):
return bool(info.notes)
class variant(namedtuple(
'shared_toolchain', # GN toolchain (and thus build dir subdirectory).
'libprefix', # Prefix on DT_SONAME string.
'runtime', # SONAME of runtime, does not use libprefix.
'aux', # List of (file, group) required if this is used.
'has_libcxx', # True iff toolchain libraries have this variant.
def matches(self, info, assume=False):
if self.libprefix and info.interp:
return info.interp.startswith(self.libprefix)
if not self.has_libcxx and info.soname in LIBCXX_SONAMES:
# Variants without their own toolchain libraries wind up placing
# vanilla toolchain libraries in the variant target lib directory
# so they should be accepted even though they don't really match.
return True
if self.runtime:
return self.runtime in info.needed or info.soname == self.runtime
return assume
def soname_target(self, soname):
return 'lib/' + self.libprefix + soname
def make_variant(name, info):
libprefix = ''
runtime = None
# All drivers need devhost; it must be in /boot (group 0).
aux = []
has_libcxx = False
if name is None:
tc = '%s-shared' %
tc = '%s-%s-shared' % (, name)
libprefix = name + '/'
if 'asan' in name:
runtime = ''
# ASan drivers need devhost.asan.
aux = [(file + '.asan', group) for file, group in aux]
has_libcxx = True
if name == 'asan-ubsan':
libprefix = 'asan-ubsan/'
runtime = ''
# ASan drivers need devhost.asan.
aux = [(file + '.asan', group) for file, group in aux]
has_libcxx = True
elif name == 'profile' or name.startswith('fuzzer.'):
libprefix = name + '/'
if name.find('asan') != -1:
runtime = ''
elif name.find('ubsan') != -1:
runtime = ''
elif 'ubsan' in name or 'sancov' in name:
runtime = ''
return variant(tc, libprefix, runtime, aux, has_libcxx)
def deduce_aux_variant(info, install_path):
if info.interp is not None:
deduce_from = 'lib/' + info.interp
elif info.soname is not None:
deduce_from = install_path
elif '' in info.needed:
return make_variant('asan', info)
elif '' in info.needed:
return make_variant('ubsan', info)
return None
pathelts = deduce_from.split('/')
assert pathelts[0] == 'lib', (
"Library expected in lib/, not %r: %r for %r" %
(deduce_from, info, install_path))
if len(pathelts) == 2:
return None
assert len(pathelts) == 3, (
"Library expected to be lib/variant/, not %r: %r for %r" %
(deduce_from, info, install_path))
return make_variant(pathelts[1], info)
def find_variant(info, install_path, build_dir=os.path.curdir):
variant = None
variant_file = None
abs_build_dir = os.path.abspath(build_dir)
abs_filename = os.path.abspath(info.filename)
if abs_filename.startswith(os.path.join(abs_build_dir, '')):
# It's in the build directory. If it's a variant, it's a hard link
# into the variant toolchain root_out_dir.
file_stat = os.stat(info.filename)
if file_stat.st_nlink > 1:
# Figure out which variant it's linked to. Non-variant drivers
# are linked to the -shared toolchain. We match those as well
# as actual variants so we'll replace the unadorned filename
# with its -shared/ version, which is where the lib.unstripped/
# subdirectory is found. Below, we'll change the name but not
# call it a variant.
rel_filename = os.path.relpath(abs_filename, abs_build_dir)
variant_prefix = + '-'
subdirs = [
subdir for subdir in os.listdir(build_dir) if (
subdir.startswith(variant_prefix) and
os.path.exists(os.path.join(subdir, rel_filename)))
files = [os.path.join(subdir, rel_filename) for subdir in subdirs]
# Rust binaries have multiple links but are not variants.
# So just ignore a multiply-linked file with no matches.
if files:
# A variant loadable_module (or driver_module) is actually
# built in the variant's -shared toolchain but is also
# linked to other variant toolchains.
if (len(subdirs) > 1 and sum(
subdir.endswith('-shared') for subdir in subdirs) == 1):
[subdir] = [
subdir for subdir in subdirs
if subdir.endswith('-shared')
assert len(files) == 1, (
"Multiple hard links to %r: %r" % (info, files))
[subdir] = subdirs
name = subdir[len(variant_prefix):]
file = os.path.relpath(
os.path.join(subdir, rel_filename), build_dir)
if os.path.samestat(os.stat(file), file_stat):
# Ensure that this is actually the same file.
variant_file = file
if name != 'shared':
# loadable_module and driver_module targets are linked
# to the variant-shared toolchain.
if name[-7:] == '-shared':
name = name[:-7]
variant = make_variant(name, info)
# It's from an auxiliary.
variant = deduce_aux_variant(info, install_path)
if variant:
assert variant.matches(info, True), "%r vs %r" % (variant, info)
return variant, variant_file
return make_variant(None, info), variant_file
# Module public API.
__all__ = ['binary_info', 'find_variant', 'variant']
def test_main(build_dir, filenames):
for filename in filenames:
info = binary_info(filename)
print info
print ' Driver: %r' % is_driver(info)
print ' %r' % (find_variant(info, None, build_dir),)
# For manual testing.
if __name__ == "__main__":
import sys
test_main(sys.argv[1], sys.argv[2:])