new file mode 100644
@@ -0,0 +1,278 @@
+#!/usr/bin/python3
+
+"""Run the appropriate tool from a build-many-glibcs.py compilers tree.
+
+Automatically pick the right tool path based on ELF input files. By
+default, the first argument on the command line that can be opened as
+an ELF file determines the architecture, but this can be overriden by
+the --ref argument.
+
+"""
+
+import argparse
+import os
+import os.path
+import struct
+import sys
+
+
+def is_elf(blob):
+ return blob[:4] == b'\177ELF'
+
+
+class ElfHeader(object):
+ def __init__(self, blob):
+ if not is_elf(blob):
+ raise ValueError('Invalid ELF magic: {!r}'.format(blob[:4]))
+ self.blob = blob
+
+ elfclass = blob[4]
+ if elfclass == 1:
+ self.wordsize = 32
+ flags_offset = 36
+ elif elfclass == 2:
+ self.wordsize = 64
+ flags_offset = 48
+ else:
+ raise ValueError('Invalid ELF class: {!r}'.format(elfclass))
+
+ endian = blob[5]
+ if endian == 1:
+ self.endian = 'little'
+ self.__uint16 = '<H'
+ self.__uint32 = '<I'
+ elif endian == 2:
+ self.endian = 'big'
+ self.__uint16 = '>H'
+ self.__uint32 = '>I'
+ else:
+ raise ValueError('Invalid ELF data: {!r}'.format(endian))
+
+ self.machine = self.__get16(18)
+ self.flags = self.__get32(flags_offset)
+
+ def __get16(self, offset):
+ """Return a 16-bit unsigned integer at the blob offset."""
+ return struct.unpack_from(self.__uint16, self.blob, offset)[0]
+
+ def __get32(self, offset):
+ """Return a 32-bit unsigned integer at the blob offset."""
+ return struct.unpack_from(self.__uint32, self.blob, offset)[0]
+
+
+class Machine(object):
+ """Machine descriptors for file system paths and program names."""
+
+ def __init__(self, for_path, for_tool=None):
+ self.for_path = for_path
+ if for_tool is None:
+ self.for_tool = for_path
+ else:
+ self.for_tool = for_tool
+
+ def program(self, opts):
+ """Determines the full path of the program to invoke."""
+ topdir = os.path.abspath(opts.topdir)
+ return os.path.join(topdir, 'install', 'compilers',
+ self.for_path, 'bin',
+ self.__triple() + '-' + opts.tool)
+
+ def __triple(self):
+ """Inserts -glibc- at the second position to form a regular triple."""
+ machine = self.for_tool.split('-')
+ return '-'.join([machine[0], 'glibc', *machine[1:]])
+
+ def __str__(self):
+ if self.for_path == self.for_tool:
+ return self.for_path
+ else:
+ return self.for_path + '/' + self.for_tool
+
+
+# Lookup table for _elf_to_mips_tuple.
+elf_to_mips_tuple_dict = {
+ (32, 'big', 0x1007): 'mips64-linux-gnu',
+ (32, 'big', 0x20000026): 'mips64-linux-gnu', # ET_REL.
+ (32, 'big', 0x20000027): 'mips64-linux-gnu',
+ (32, 'big', 0x70001407): 'mips64-linux-gnu-nan2008',
+ (32, 'big', 0x80000426): 'mips64-linux-gnu-nan2008', # ET_REL.
+ (32, 'big', 0x80000427): 'mips64-linux-gnu-nan2008',
+ (32, 'little', 0x1007): 'mips64el-linux-gnu',
+ (32, 'little', 0x20000026): 'mips64el-linux-gnu', # ET_REL.
+ (32, 'little', 0x20000027): 'mips64el-linux-gnu',
+ (32, 'little', 0x70001407): 'mips64el-linux-gnu-nan2008',
+ (32, 'little', 0x80000426): 'mips64el-linux-gnu-nan2008', # ET_REL.
+ (32, 'little', 0x80000427): 'mips64el-linux-gnu-nan2008',
+ (32, 'little', 0x90001406): 'mipsisa64r6el-linux-gnu', # ET_REL.
+ (32, 'little', 0x90001407): 'mipsisa64r6el-linux-gnu',
+ (32, 'little', 0xa0000426): 'mipsisa64r6el-linux-gnu', # ET_REL.
+ (32, 'little', 0xa0000427): 'mipsisa64r6el-linux-gnu',
+ (64, 'big', 0x20000006): 'mips64-linux-gnu', # ET_REL.
+ (64, 'big', 0x20000007): 'mips64-linux-gnu',
+ (64, 'big', 0x80000406): 'mips64-linux-gnu-nan2008', # ET_REL.
+ (64, 'big', 0x80000407): 'mips64-linux-gnu-nan2008',
+ (64, 'little', 0x20000006): 'mips64el-linux-gnu', # ET_REL.
+ (64, 'little', 0x20000007): 'mips64el-linux-gnu',
+ (64, 'little', 0x80000406): 'mips64el-linux-gnu-nan2008', # ET_REL.
+ (64, 'little', 0x80000407): 'mips64el-linux-gnu-nan2008',
+ (64, 'little', 0xa0000406): 'mipsisa64r6el-linux-gnu', # ET_REL.
+ (64, 'little', 0xa0000407): 'mipsisa64r6el-linux-gnu',
+}
+
+
+def elf_to_mips_tuple(elf):
+ """Determine the machine for from an ELF header for MIPS."""
+ name = elf_to_mips_tuple_dict[(elf.wordsize, elf.endian, elf.flags)]
+ special_suffix = '-nan2008'
+ if name.endswith(special_suffix):
+ return Machine(name, name[:-len(special_suffix)])
+ else:
+ return Machine(name)
+
+
+def elf_to_riscv_tuple(elf):
+ """Determine the machine for from an ELF header for RISC-V."""
+ for_path = {
+ 1: 'riscv64-linux-gnu-rv64imafdc-lp64',
+ 5: 'riscv64-linux-gnu-rv64imafdc-lp64d',
+ }[elf.flags]
+ return Machine(for_path, 'riscv64-linux-gnu')
+
+
+elf_to_sh_tuple_dict = {
+ ('big', 0x01): 'sh4-linux-gnu', # ET_REL file.
+ ('big', 0x02): 'sh4eb-linux-gnu', # ET_REL file.
+ ('big', 0x10): 'sh4eb-linux-gnu-soft',
+ ('big', 0x16): 'sh3eb-linux-gnu',
+ ('big', 0x18): 'sh4eb-linux-gnu',
+ ('little', 0x01): 'sh4-linux-gnu', # ET_REL file.
+ ('little', 0x02): 'sh4-linux-gnu', # ET_REL file.
+ ('little', 0x10): 'sh4-linux-gnu-soft',
+ ('little', 0x16): 'sh3-linux-gnu',
+ ('little', 0x18): 'sh4-linux-gnu',
+}
+
+
+def elf_to_sh_tuple(elf):
+ """Determine the machine for from an ELF header for SH."""
+ name = elf_to_sh_tuple_dict[(elf.endian, elf.flags)]
+ special_suffix = '-soft'
+ if name.endswith(special_suffix):
+ return Machine(name, name[:-len(special_suffix)])
+ else:
+ return Machine(name)
+
+
+# Lookup table to find the correct classifer. String values are
+# directly passed to the Machine constructor. For tuple values, the
+# first element is use for a little-endian ELF object, and the second
+# value for a big-endian ELF object. Otherwise, the value is called
+# as a function, passing the ELF header.
+elf_machine_to_tuple_dict = {
+ 183: ('aarch64-linux-gnu', 'aarch64_be-linux-gnu'),
+ 0x9026: 'alpha-linux-gnu',
+ 40: ('arm-linux-gnueabihf', 'armeb-linux-gnueabihf'),
+ 252: 'csky-linux-gnuabiv2',
+ 15: 'hppa-linux-gnu',
+ 3: 'x86_64-linux-gnu',
+ 50: 'ia64-linux-gnu',
+ 4: 'm68k-linux-gnu',
+ 189: ('microblazeel-linux-gnu', 'microblaze-linux-gnu'),
+ 8: elf_to_mips_tuple,
+ 113: 'nios2-linux-gnu',
+ 20: 'powerpc-linux-gnu',
+ 21: ('powerpc64le-linux-gnu', 'powerpc64-linux-gnu'),
+ 243: elf_to_riscv_tuple,
+ 22: 's390x-linux-gnu',
+ 42: elf_to_sh_tuple,
+ 2: 'sparc64-linux-gnu',
+ 18: 'sparc64-linux-gnu',
+ 43: 'sparc64-linux-gnu',
+ 62: 'x86_64-linux-gnu',
+}
+
+
+def elf_to_machine(elf):
+ """Determine the Machine object for the ELF header."""
+ classifier = elf_machine_to_tuple_dict[elf.machine]
+ if type(classifier) == str:
+ return Machine(classifier)
+ elif type(classifier) == tuple:
+ return Machine(classifier[{'little': 0, 'big': 1}[elf.endian]])
+ else:
+ return classifier(elf)
+
+
+def elf_path_to_machine(path):
+ """Determine the Machine object for the file system path."""
+ with open(path, 'rb') as inp:
+ blob = inp.read(512)
+ return elf_to_machine(ElfHeader(blob))
+
+
+def guess_machine_from_args(opts):
+ """Tries to guess the Machine object from the argument list."""
+ for arg in opts.args:
+ if os.access(arg, 0):
+ with open(arg, 'rb') as inp:
+ blob = inp.read(512)
+ if is_elf(blob):
+ result = elf_to_machine(ElfHeader(blob))
+ if opts.verbose:
+ sys.stderr.write('run-tool: machine type {} from: {}\n'
+ .format(result, arg))
+ return result
+ sys.stderr.write('error: cannot guess machine architecture (use --ref)\n')
+ sys.exit(1)
+
+
+def get_parser():
+ """Return an argument parser for this module."""
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--ref', metavar='PATH',
+ help='ELF file which determines the architecture')
+ parser.add_argument('--verbose', action='store_true',
+ help='Print diagnostic output')
+ parser.add_argument('topdir',
+ help='Toplevel working directory')
+ parser.add_argument('tool',
+ help='Name of the program (e.g., objdump)')
+ parser.add_argument('args', help='Arguments passed to the tool',
+ nargs='*')
+ return parser
+
+
+def disable_option_parsing(argv):
+ """Insert -- into argv to disable option parsing."""
+ for i in range(len(argv)):
+ if not argv[i].startswith('-'):
+ return [*argv[:i], '--', *argv[i:]]
+ return argv
+
+
+def main(argv):
+ """The main entry point."""
+ parser = get_parser()
+ opts = parser.parse_args(disable_option_parsing(argv))
+
+ if not os.path.isdir(opts.topdir):
+ sys.stderr.write('error: directory expected: {!r}\n'.format(
+ opts.topdir))
+ sys.exit(1)
+ if not os.path.isdir(os.path.join(opts.topdir, 'install', 'compilers')):
+ sys.stderr.write('error: not a tree with compilers: {!r}\n'.format(
+ opts.topdir))
+ sys.exit(1)
+
+ if opts.ref is None:
+ machine = guess_machine_from_args(opts)
+ else:
+ machine = elf_path_to_machine(opts.ref)
+ sys.stderr.write('run-tool: machine type: {}\n'.format(machine))
+ program = machine.program(opts)
+ sys.stderr.write('run-tool: tool path: {!r}'.format(program))
+ os.execv(program, [program, *opts.args])
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])