[RFC] Add scripts/run-tool.py

Message ID 87woaqvv9h.fsf@oldenburg2.str.redhat.com
State New, archived
Headers

Commit Message

Florian Weimer Dec. 21, 2019, 10:16 a.m. UTC
  This is a wrapper script for the toolchain built by
build-many-glibcs.py.  It attempts to guess the right toolchain
based on ELF properties, and runs that tool.

I'd like to receive feedback if something like this is useful, and
perhaps how to improve the MIPS selector (which is rather hackish at
present).

Thanks,
Florian

-----
 scripts/run-tool.py | 278 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 278 insertions(+)
  

Patch

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:])