diff --git a/scripts/run-tool.py b/scripts/run-tool.py
new file mode 100644
index 0000000000..64336ce8f1
--- /dev/null
+++ b/scripts/run-tool.py
@@ -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:])
