about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--elf/Makefile7
-rw-r--r--elf/tst-glibcelf.py260
-rw-r--r--scripts/glibcelf.py1135
3 files changed, 1402 insertions, 0 deletions
diff --git a/elf/Makefile b/elf/Makefile
index d30d0ee917..3d79f40879 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -1115,6 +1115,13 @@ tests-special += $(objpfx)check-abi-ld.out
 update-abi: update-abi-ld
 update-all-abi: update-all-abi-ld
 
+tests-special += $(objpfx)tst-glibcelf.out
+$(objpfx)tst-glibcelf.out: tst-glibcelf.py elf.h $(..)/scripts/glibcelf.py \
+  $(..)/scripts/glibcextract.py
+	PYTHONPATH=$(..)scripts $(PYTHON) tst-glibcelf.py \
+          --cc="$(CC) $(patsubst -DMODULE_NAME=%,-DMODULE_NAME=testsuite,$(CPPFLAGS))" \
+	  < /dev/null > $@ 2>&1; $(evaluate-test)
+
 # The test requires shared _and_ PIE because the executable
 # unit test driver must be able to link with the shared object
 # that is going to eventually go into an installed DSO.
diff --git a/elf/tst-glibcelf.py b/elf/tst-glibcelf.py
new file mode 100644
index 0000000000..bf15a3bad4
--- /dev/null
+++ b/elf/tst-glibcelf.py
@@ -0,0 +1,260 @@
+#!/usr/bin/python3
+# Verify scripts/glibcelf.py contents against elf/elf.h.
+# Copyright (C) 2022 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+import argparse
+import enum
+import sys
+
+import glibcelf
+import glibcextract
+
+errors_encountered = 0
+
+def error(message):
+    global errors_encountered
+    sys.stdout.write('error: {}\n'.format(message))
+    errors_encountered += 1
+
+# The enum constants in glibcelf are expected to have exactly these
+# prefixes.
+expected_constant_prefixes = tuple(
+    'ELFCLASS ELFDATA EM_ ET_ DT_ PF_ PT_ SHF_ SHN_ SHT_ STB_ STT_'.split())
+
+def find_constant_prefix(name):
+    """Returns a matching prefix from expected_constant_prefixes or None."""
+    for prefix in expected_constant_prefixes:
+        if name.startswith(prefix):
+            return prefix
+    return None
+
+def find_enum_types():
+    """A generator for OpenIntEnum and IntFlag classes in glibcelf."""
+    for obj in vars(glibcelf).values():
+        if isinstance(obj, type) and obj.__bases__[0] in (
+                glibcelf._OpenIntEnum, enum.Enum, enum.IntFlag):
+            yield obj
+
+def check_duplicates():
+    """Verifies that enum types do not have duplicate values.
+
+    Different types must have different member names, too.
+
+    """
+    global_seen = {}
+    for typ in find_enum_types():
+        seen = {}
+        last = None
+        for (name, e) in typ.__members__.items():
+            if e.value in seen:
+                error('{} has {}={} and {}={}'.format(
+                    typ, seen[e.value], e.value, name, e.value))
+                last = e
+            else:
+                seen[e.value] = name
+                if last is not None and last.value > e.value:
+                    error('{} has {}={} after {}={}'.format(
+                        typ, name, e.value, last.name, last.value))
+                if name in global_seen:
+                    error('{} used in {} and {}'.format(
+                        name, global_seen[name], typ))
+                else:
+                    global_seen[name] = typ
+
+def check_constant_prefixes():
+    """Check that the constant prefixes match expected_constant_prefixes."""
+    seen = set()
+    for typ in find_enum_types():
+        typ_prefix = None
+        for val in typ:
+            prefix = find_constant_prefix(val.name)
+            if prefix is None:
+                error('constant {!r} for {} has unknown prefix'.format(
+                    val, typ))
+                break
+            elif typ_prefix is None:
+                typ_prefix = prefix
+                seen.add(typ_prefix)
+            elif prefix != typ_prefix:
+                error('prefix {!r} for constant {!r}, expected {!r}'.format(
+                    prefix, val, typ_prefix))
+        if typ_prefix is None:
+            error('empty enum type {}'.format(typ))
+
+    for prefix in sorted(set(expected_constant_prefixes) - seen):
+        error('missing constant prefix {!r}'.format(prefix))
+    # Reverse difference is already covered inside the loop.
+
+def find_elf_h_constants(cc):
+    """Returns a dictionary of relevant constants from <elf.h>."""
+    return glibcextract.compute_macro_consts(
+        source_text='#include <elf.h>',
+        cc=cc,
+        macro_re='|'.join(
+            prefix + '.*' for prefix in expected_constant_prefixes))
+
+# The first part of the pair is a name of an <elf.h> constant that is
+# dropped from glibcelf.  The second part is the constant as it is
+# used in <elf.h>.
+glibcelf_skipped_aliases = (
+    ('EM_ARC_A5', 'EM_ARC_COMPACT'),
+    ('PF_PARISC_SBP', 'PF_HP_SBP')
+)
+
+# Constants that provide little value and are not included in
+# glibcelf: *LO*/*HI* range constants, *NUM constants counting the
+# number of constants.  Also includes the alias names from
+# glibcelf_skipped_aliases.
+glibcelf_skipped_constants = frozenset(
+    [e[0] for e in glibcelf_skipped_aliases]) | frozenset("""
+DT_AARCH64_NUM
+DT_ADDRNUM
+DT_ADDRRNGHI
+DT_ADDRRNGLO
+DT_ALPHA_NUM
+DT_ENCODING
+DT_EXTRANUM
+DT_HIOS
+DT_HIPROC
+DT_IA_64_NUM
+DT_LOOS
+DT_LOPROC
+DT_MIPS_NUM
+DT_NUM
+DT_PPC64_NUM
+DT_PPC_NUM
+DT_PROCNUM
+DT_SPARC_NUM
+DT_VALNUM
+DT_VALRNGHI
+DT_VALRNGLO
+DT_VERSIONTAGNUM
+ELFCLASSNUM
+ELFDATANUM
+ET_HIOS
+ET_HIPROC
+ET_LOOS
+ET_LOPROC
+ET_NUM
+PF_MASKOS
+PF_MASKPROC
+PT_HIOS
+PT_HIPROC
+PT_HISUNW
+PT_LOOS
+PT_LOPROC
+PT_LOSUNW
+SHF_MASKOS
+SHF_MASKPROC
+SHN_HIOS
+SHN_HIPROC
+SHN_HIRESERVE
+SHN_LOOS
+SHN_LOPROC
+SHN_LORESERVE
+SHT_HIOS
+SHT_HIPROC
+SHT_HIPROC
+SHT_HISUNW
+SHT_HIUSER
+SHT_LOOS
+SHT_LOPROC
+SHT_LOSUNW
+SHT_LOUSER
+SHT_NUM
+STB_HIOS
+STB_HIPROC
+STB_LOOS
+STB_LOPROC
+STB_NUM
+STT_HIOS
+STT_HIPROC
+STT_LOOS
+STT_LOPROC
+STT_NUM
+""".strip().split())
+
+def check_constant_values(cc):
+    """Checks the values of <elf.h> constants against glibcelf."""
+
+    glibcelf_constants = {
+        e.name: e for typ in find_enum_types() for e in typ}
+    elf_h_constants = find_elf_h_constants(cc=cc)
+
+    missing_in_glibcelf = (set(elf_h_constants) - set(glibcelf_constants)
+                           - glibcelf_skipped_constants)
+    for name in sorted(missing_in_glibcelf):
+        error('constant {} is missing from glibcelf'.format(name))
+
+    unexpected_in_glibcelf = \
+        set(glibcelf_constants) & glibcelf_skipped_constants
+    for name in sorted(unexpected_in_glibcelf):
+        error('constant {} is supposed to be filtered from glibcelf'.format(
+            name))
+
+    missing_in_elf_h = set(glibcelf_constants) - set(elf_h_constants)
+    for name in sorted(missing_in_elf_h):
+        error('constant {} is missing from <elf.h>'.format(name))
+
+    expected_in_elf_h = glibcelf_skipped_constants - set(elf_h_constants)
+    for name in expected_in_elf_h:
+        error('filtered constant {} is missing from <elf.h>'.format(name))
+
+    for alias_name, name_in_glibcelf in glibcelf_skipped_aliases:
+        if name_in_glibcelf not in glibcelf_constants:
+            error('alias value {} for {} not in glibcelf'.format(
+                name_in_glibcelf, alias_name))
+        elif (int(elf_h_constants[alias_name])
+              != glibcelf_constants[name_in_glibcelf].value):
+            error('<elf.h> has {}={}, glibcelf has {}={}'.format(
+                alias_name, elf_h_constants[alias_name],
+                name_in_glibcelf, glibcelf_constants[name_in_glibcelf]))
+
+    # Check for value mismatches:
+    for name in sorted(set(glibcelf_constants) & set(elf_h_constants)):
+        glibcelf_value = glibcelf_constants[name].value
+        elf_h_value = int(elf_h_constants[name])
+        # On 32-bit architectures <elf.h> as some constants that are
+        # parsed as signed, while they are unsigned in glibcelf.  So
+        # far, this only affects some flag constants, so special-case
+        # them here.
+        if (glibcelf_value != elf_h_value
+            and not (isinstance(glibcelf_constants[name], enum.IntFlag)
+                     and glibcelf_value == 1 << 31
+                     and elf_h_value == -(1 << 31))):
+            error('{}: glibcelf has {!r}, <elf.h> has {!r}'.format(
+                name, glibcelf_value, elf_h_value))
+
+def main():
+    """The main entry point."""
+    parser = argparse.ArgumentParser(
+        description="Check glibcelf.py and elf.h against each other.")
+    parser.add_argument('--cc', metavar='CC',
+                        help='C compiler (including options) to use')
+    args = parser.parse_args()
+
+    check_duplicates()
+    check_constant_prefixes()
+    check_constant_values(cc=args.cc)
+
+    if errors_encountered > 0:
+        print("note: errors encountered:", errors_encountered)
+        sys.exit(1)
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/glibcelf.py b/scripts/glibcelf.py
new file mode 100644
index 0000000000..8f7d0ca184
--- /dev/null
+++ b/scripts/glibcelf.py
@@ -0,0 +1,1135 @@
+#!/usr/bin/python3
+# ELF support functionality for Python.
+# Copyright (C) 2022 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+"""Basic ELF parser.
+
+Use Image.readfile(path) to read an ELF file into memory and begin
+parsing it.
+
+"""
+
+import collections
+import enum
+import struct
+
+class _OpenIntEnum(enum.IntEnum):
+    """Integer enumeration that supports arbitrary int values."""
+    @classmethod
+    def _missing_(cls, value):
+        # See enum.IntFlag._create_pseudo_member_.  This allows
+        # creating of enum constants with arbitrary integer values.
+        pseudo_member = int.__new__(cls, value)
+        pseudo_member._name_ = None
+        pseudo_member._value_ = value
+        return pseudo_member
+
+    def __repr__(self):
+        name = self._name_
+        if name is not None:
+            # The names have prefixes like SHT_, implying their type.
+            return name
+        return '{}({})'.format(self.__class__.__name__, self._value_)
+
+    def __str__(self):
+        name = self._name_
+        if name is not None:
+            return name
+        return str(self._value_)
+
+class ElfClass(_OpenIntEnum):
+    """ELF word size.  Type of EI_CLASS values."""
+    ELFCLASSNONE = 0
+    ELFCLASS32 = 1
+    ELFCLASS64 = 2
+
+class ElfData(_OpenIntEnum):
+    """ELF endianess.  Type of EI_DATA values."""
+    ELFDATANONE = 0
+    ELFDATA2LSB = 1
+    ELFDATA2MSB = 2
+
+class Machine(_OpenIntEnum):
+    """ELF machine type.  Type of values in Ehdr.e_machine field."""
+    EM_NONE = 0
+    EM_M32 = 1
+    EM_SPARC = 2
+    EM_386 = 3
+    EM_68K = 4
+    EM_88K = 5
+    EM_IAMCU = 6
+    EM_860 = 7
+    EM_MIPS = 8
+    EM_S370 = 9
+    EM_MIPS_RS3_LE = 10
+    EM_PARISC = 15
+    EM_VPP500 = 17
+    EM_SPARC32PLUS = 18
+    EM_960 = 19
+    EM_PPC = 20
+    EM_PPC64 = 21
+    EM_S390 = 22
+    EM_SPU = 23
+    EM_V800 = 36
+    EM_FR20 = 37
+    EM_RH32 = 38
+    EM_RCE = 39
+    EM_ARM = 40
+    EM_FAKE_ALPHA = 41
+    EM_SH = 42
+    EM_SPARCV9 = 43
+    EM_TRICORE = 44
+    EM_ARC = 45
+    EM_H8_300 = 46
+    EM_H8_300H = 47
+    EM_H8S = 48
+    EM_H8_500 = 49
+    EM_IA_64 = 50
+    EM_MIPS_X = 51
+    EM_COLDFIRE = 52
+    EM_68HC12 = 53
+    EM_MMA = 54
+    EM_PCP = 55
+    EM_NCPU = 56
+    EM_NDR1 = 57
+    EM_STARCORE = 58
+    EM_ME16 = 59
+    EM_ST100 = 60
+    EM_TINYJ = 61
+    EM_X86_64 = 62
+    EM_PDSP = 63
+    EM_PDP10 = 64
+    EM_PDP11 = 65
+    EM_FX66 = 66
+    EM_ST9PLUS = 67
+    EM_ST7 = 68
+    EM_68HC16 = 69
+    EM_68HC11 = 70
+    EM_68HC08 = 71
+    EM_68HC05 = 72
+    EM_SVX = 73
+    EM_ST19 = 74
+    EM_VAX = 75
+    EM_CRIS = 76
+    EM_JAVELIN = 77
+    EM_FIREPATH = 78
+    EM_ZSP = 79
+    EM_MMIX = 80
+    EM_HUANY = 81
+    EM_PRISM = 82
+    EM_AVR = 83
+    EM_FR30 = 84
+    EM_D10V = 85
+    EM_D30V = 86
+    EM_V850 = 87
+    EM_M32R = 88
+    EM_MN10300 = 89
+    EM_MN10200 = 90
+    EM_PJ = 91
+    EM_OPENRISC = 92
+    EM_ARC_COMPACT = 93
+    EM_XTENSA = 94
+    EM_VIDEOCORE = 95
+    EM_TMM_GPP = 96
+    EM_NS32K = 97
+    EM_TPC = 98
+    EM_SNP1K = 99
+    EM_ST200 = 100
+    EM_IP2K = 101
+    EM_MAX = 102
+    EM_CR = 103
+    EM_F2MC16 = 104
+    EM_MSP430 = 105
+    EM_BLACKFIN = 106
+    EM_SE_C33 = 107
+    EM_SEP = 108
+    EM_ARCA = 109
+    EM_UNICORE = 110
+    EM_EXCESS = 111
+    EM_DXP = 112
+    EM_ALTERA_NIOS2 = 113
+    EM_CRX = 114
+    EM_XGATE = 115
+    EM_C166 = 116
+    EM_M16C = 117
+    EM_DSPIC30F = 118
+    EM_CE = 119
+    EM_M32C = 120
+    EM_TSK3000 = 131
+    EM_RS08 = 132
+    EM_SHARC = 133
+    EM_ECOG2 = 134
+    EM_SCORE7 = 135
+    EM_DSP24 = 136
+    EM_VIDEOCORE3 = 137
+    EM_LATTICEMICO32 = 138
+    EM_SE_C17 = 139
+    EM_TI_C6000 = 140
+    EM_TI_C2000 = 141
+    EM_TI_C5500 = 142
+    EM_TI_ARP32 = 143
+    EM_TI_PRU = 144
+    EM_MMDSP_PLUS = 160
+    EM_CYPRESS_M8C = 161
+    EM_R32C = 162
+    EM_TRIMEDIA = 163
+    EM_QDSP6 = 164
+    EM_8051 = 165
+    EM_STXP7X = 166
+    EM_NDS32 = 167
+    EM_ECOG1X = 168
+    EM_MAXQ30 = 169
+    EM_XIMO16 = 170
+    EM_MANIK = 171
+    EM_CRAYNV2 = 172
+    EM_RX = 173
+    EM_METAG = 174
+    EM_MCST_ELBRUS = 175
+    EM_ECOG16 = 176
+    EM_CR16 = 177
+    EM_ETPU = 178
+    EM_SLE9X = 179
+    EM_L10M = 180
+    EM_K10M = 181
+    EM_AARCH64 = 183
+    EM_AVR32 = 185
+    EM_STM8 = 186
+    EM_TILE64 = 187
+    EM_TILEPRO = 188
+    EM_MICROBLAZE = 189
+    EM_CUDA = 190
+    EM_TILEGX = 191
+    EM_CLOUDSHIELD = 192
+    EM_COREA_1ST = 193
+    EM_COREA_2ND = 194
+    EM_ARCV2 = 195
+    EM_OPEN8 = 196
+    EM_RL78 = 197
+    EM_VIDEOCORE5 = 198
+    EM_78KOR = 199
+    EM_56800EX = 200
+    EM_BA1 = 201
+    EM_BA2 = 202
+    EM_XCORE = 203
+    EM_MCHP_PIC = 204
+    EM_INTELGT = 205
+    EM_KM32 = 210
+    EM_KMX32 = 211
+    EM_EMX16 = 212
+    EM_EMX8 = 213
+    EM_KVARC = 214
+    EM_CDP = 215
+    EM_COGE = 216
+    EM_COOL = 217
+    EM_NORC = 218
+    EM_CSR_KALIMBA = 219
+    EM_Z80 = 220
+    EM_VISIUM = 221
+    EM_FT32 = 222
+    EM_MOXIE = 223
+    EM_AMDGPU = 224
+    EM_RISCV = 243
+    EM_BPF = 247
+    EM_CSKY = 252
+    EM_NUM = 253
+    EM_ALPHA = 0x9026
+
+class Et(_OpenIntEnum):
+    """ELF file type.  Type of ET_* values and the Ehdr.e_type field."""
+    ET_NONE = 0
+    ET_REL = 1
+    ET_EXEC = 2
+    ET_DYN = 3
+    ET_CORE = 4
+
+class Shn(_OpenIntEnum):
+    """ELF reserved section indices."""
+    SHN_UNDEF = 0
+    SHN_BEFORE = 0xff00
+    SHN_AFTER = 0xff01
+    SHN_ABS = 0xfff1
+    SHN_COMMON = 0xfff2
+    SHN_XINDEX = 0xffff
+
+class ShnMIPS(enum.Enum):
+    """Supplemental SHN_* constants for EM_MIPS."""
+    SHN_MIPS_ACOMMON = 0xff00
+    SHN_MIPS_TEXT = 0xff01
+    SHN_MIPS_DATA = 0xff02
+    SHN_MIPS_SCOMMON = 0xff03
+    SHN_MIPS_SUNDEFINED = 0xff04
+
+class ShnPARISC(enum.Enum):
+    """Supplemental SHN_* constants for EM_PARISC."""
+    SHN_PARISC_ANSI_COMMON = 0xff00
+    SHN_PARISC_HUGE_COMMON = 0xff01
+
+class Sht(_OpenIntEnum):
+    """ELF section types.  Type of SHT_* values."""
+    SHT_NULL = 0
+    SHT_PROGBITS = 1
+    SHT_SYMTAB = 2
+    SHT_STRTAB = 3
+    SHT_RELA = 4
+    SHT_HASH = 5
+    SHT_DYNAMIC = 6
+    SHT_NOTE = 7
+    SHT_NOBITS = 8
+    SHT_REL = 9
+    SHT_SHLIB = 10
+    SHT_DYNSYM = 11
+    SHT_INIT_ARRAY = 14
+    SHT_FINI_ARRAY = 15
+    SHT_PREINIT_ARRAY = 16
+    SHT_GROUP = 17
+    SHT_SYMTAB_SHNDX = 18
+    SHT_GNU_ATTRIBUTES = 0x6ffffff5
+    SHT_GNU_HASH = 0x6ffffff6
+    SHT_GNU_LIBLIST = 0x6ffffff7
+    SHT_CHECKSUM = 0x6ffffff8
+    SHT_SUNW_move = 0x6ffffffa
+    SHT_SUNW_COMDAT = 0x6ffffffb
+    SHT_SUNW_syminfo = 0x6ffffffc
+    SHT_GNU_verdef = 0x6ffffffd
+    SHT_GNU_verneed = 0x6ffffffe
+    SHT_GNU_versym = 0x6fffffff
+
+class ShtALPHA(enum.Enum):
+    """Supplemental SHT_* constants for EM_ALPHA."""
+    SHT_ALPHA_DEBUG = 0x70000001
+    SHT_ALPHA_REGINFO = 0x70000002
+
+class ShtARM(enum.Enum):
+    """Supplemental SHT_* constants for EM_ARM."""
+    SHT_ARM_EXIDX = 0x70000001
+    SHT_ARM_PREEMPTMAP = 0x70000002
+    SHT_ARM_ATTRIBUTES = 0x70000003
+
+class ShtCSKY(enum.Enum):
+    """Supplemental SHT_* constants for EM_CSKY."""
+    SHT_CSKY_ATTRIBUTES = 0x70000001
+
+class ShtIA_64(enum.Enum):
+    """Supplemental SHT_* constants for EM_IA_64."""
+    SHT_IA_64_EXT = 0x70000000
+    SHT_IA_64_UNWIND = 0x70000001
+
+class ShtMIPS(enum.Enum):
+    """Supplemental SHT_* constants for EM_MIPS."""
+    SHT_MIPS_LIBLIST = 0x70000000
+    SHT_MIPS_MSYM = 0x70000001
+    SHT_MIPS_CONFLICT = 0x70000002
+    SHT_MIPS_GPTAB = 0x70000003
+    SHT_MIPS_UCODE = 0x70000004
+    SHT_MIPS_DEBUG = 0x70000005
+    SHT_MIPS_REGINFO = 0x70000006
+    SHT_MIPS_PACKAGE = 0x70000007
+    SHT_MIPS_PACKSYM = 0x70000008
+    SHT_MIPS_RELD = 0x70000009
+    SHT_MIPS_IFACE = 0x7000000b
+    SHT_MIPS_CONTENT = 0x7000000c
+    SHT_MIPS_OPTIONS = 0x7000000d
+    SHT_MIPS_SHDR = 0x70000010
+    SHT_MIPS_FDESC = 0x70000011
+    SHT_MIPS_EXTSYM = 0x70000012
+    SHT_MIPS_DENSE = 0x70000013
+    SHT_MIPS_PDESC = 0x70000014
+    SHT_MIPS_LOCSYM = 0x70000015
+    SHT_MIPS_AUXSYM = 0x70000016
+    SHT_MIPS_OPTSYM = 0x70000017
+    SHT_MIPS_LOCSTR = 0x70000018
+    SHT_MIPS_LINE = 0x70000019
+    SHT_MIPS_RFDESC = 0x7000001a
+    SHT_MIPS_DELTASYM = 0x7000001b
+    SHT_MIPS_DELTAINST = 0x7000001c
+    SHT_MIPS_DELTACLASS = 0x7000001d
+    SHT_MIPS_DWARF = 0x7000001e
+    SHT_MIPS_DELTADECL = 0x7000001f
+    SHT_MIPS_SYMBOL_LIB = 0x70000020
+    SHT_MIPS_EVENTS = 0x70000021
+    SHT_MIPS_TRANSLATE = 0x70000022
+    SHT_MIPS_PIXIE = 0x70000023
+    SHT_MIPS_XLATE = 0x70000024
+    SHT_MIPS_XLATE_DEBUG = 0x70000025
+    SHT_MIPS_WHIRL = 0x70000026
+    SHT_MIPS_EH_REGION = 0x70000027
+    SHT_MIPS_XLATE_OLD = 0x70000028
+    SHT_MIPS_PDR_EXCEPTION = 0x70000029
+    SHT_MIPS_XHASH = 0x7000002b
+
+class ShtPARISC(enum.Enum):
+    """Supplemental SHT_* constants for EM_PARISC."""
+    SHT_PARISC_EXT = 0x70000000
+    SHT_PARISC_UNWIND = 0x70000001
+    SHT_PARISC_DOC = 0x70000002
+
+class Pf(enum.IntFlag):
+    """Program header flags.  Type of Phdr.p_flags values."""
+    PF_X = 1
+    PF_W = 2
+    PF_R = 4
+
+class PfARM(enum.IntFlag):
+    """Supplemental PF_* flags for EM_ARM."""
+    PF_ARM_SB = 0x10000000
+    PF_ARM_PI = 0x20000000
+    PF_ARM_ABS = 0x40000000
+
+class PfPARISC(enum.IntFlag):
+    """Supplemental PF_* flags for EM_PARISC."""
+    PF_HP_PAGE_SIZE = 0x00100000
+    PF_HP_FAR_SHARED = 0x00200000
+    PF_HP_NEAR_SHARED = 0x00400000
+    PF_HP_CODE = 0x01000000
+    PF_HP_MODIFY = 0x02000000
+    PF_HP_LAZYSWAP = 0x04000000
+    PF_HP_SBP = 0x08000000
+
+class PfIA_64(enum.IntFlag):
+    """Supplemental PF_* flags for EM_IA_64."""
+    PF_IA_64_NORECOV = 0x80000000
+
+class PfMIPS(enum.IntFlag):
+    """Supplemental PF_* flags for EM_MIPS."""
+    PF_MIPS_LOCAL = 0x10000000
+
+class Shf(enum.IntFlag):
+    """Section flags.  Type of Shdr.sh_type values."""
+    SHF_WRITE = 1 << 0
+    SHF_ALLOC = 1 << 1
+    SHF_EXECINSTR = 1 << 2
+    SHF_MERGE = 1 << 4
+    SHF_STRINGS = 1 << 5
+    SHF_INFO_LINK = 1 << 6
+    SHF_LINK_ORDER = 1 << 7
+    SHF_OS_NONCONFORMING = 256
+    SHF_GROUP = 1 << 9
+    SHF_TLS = 1 << 10
+    SHF_COMPRESSED = 1 << 11
+    SHF_GNU_RETAIN = 1 << 21
+    SHF_ORDERED = 1 << 30
+    SHF_EXCLUDE = 1 << 31
+
+class ShfALPHA(enum.IntFlag):
+    """Supplemental SHF_* constants for EM_ALPHA."""
+    SHF_ALPHA_GPREL = 0x10000000
+
+class ShfARM(enum.IntFlag):
+    """Supplemental SHF_* constants for EM_ARM."""
+    SHF_ARM_ENTRYSECT = 0x10000000
+    SHF_ARM_COMDEF = 0x80000000
+
+class ShfIA_64(enum.IntFlag):
+    """Supplemental SHF_* constants for EM_IA_64."""
+    SHF_IA_64_SHORT  = 0x10000000
+    SHF_IA_64_NORECOV = 0x20000000
+
+class ShfMIPS(enum.IntFlag):
+    """Supplemental SHF_* constants for EM_MIPS."""
+    SHF_MIPS_GPREL = 0x10000000
+    SHF_MIPS_MERGE = 0x20000000
+    SHF_MIPS_ADDR = 0x40000000
+    SHF_MIPS_STRINGS = 0x80000000
+    SHF_MIPS_NOSTRIP = 0x08000000
+    SHF_MIPS_LOCAL = 0x04000000
+    SHF_MIPS_NAMES = 0x02000000
+    SHF_MIPS_NODUPE = 0x01000000
+
+class ShfPARISC(enum.IntFlag):
+    """Supplemental SHF_* constants for EM_PARISC."""
+    SHF_PARISC_SHORT = 0x20000000
+    SHF_PARISC_HUGE = 0x40000000
+    SHF_PARISC_SBP = 0x80000000
+
+class Stb(_OpenIntEnum):
+    """ELF symbol binding type."""
+    STB_LOCAL = 0
+    STB_GLOBAL = 1
+    STB_WEAK = 2
+    STB_GNU_UNIQUE = 10
+    STB_MIPS_SPLIT_COMMON = 13
+
+class Stt(_OpenIntEnum):
+    """ELF symbol type."""
+    STT_NOTYPE = 0
+    STT_OBJECT = 1
+    STT_FUNC = 2
+    STT_SECTION = 3
+    STT_FILE = 4
+    STT_COMMON = 5
+    STT_TLS = 6
+    STT_GNU_IFUNC = 10
+
+class SttARM(enum.Enum):
+    """Supplemental STT_* constants for EM_ARM."""
+    STT_ARM_TFUNC = 13
+    STT_ARM_16BIT = 15
+
+class SttPARISC(enum.Enum):
+    """Supplemental STT_* constants for EM_PARISC."""
+    STT_HP_OPAQUE = 11
+    STT_HP_STUB = 12
+    STT_PARISC_MILLICODE = 13
+
+class SttSPARC(enum.Enum):
+    """Supplemental STT_* constants for EM_SPARC."""
+    STT_SPARC_REGISTER = 13
+
+class SttX86_64(enum.Enum):
+    """Supplemental STT_* constants for EM_X86_64."""
+    SHT_X86_64_UNWIND = 0x70000001
+
+class Pt(_OpenIntEnum):
+    """ELF program header types.  Type of Phdr.p_type."""
+    PT_NULL = 0
+    PT_LOAD = 1
+    PT_DYNAMIC = 2
+    PT_INTERP = 3
+    PT_NOTE = 4
+    PT_SHLIB = 5
+    PT_PHDR = 6
+    PT_TLS = 7
+    PT_NUM = 8
+    PT_GNU_EH_FRAME = 0x6474e550
+    PT_GNU_STACK = 0x6474e551
+    PT_GNU_RELRO = 0x6474e552
+    PT_GNU_PROPERTY = 0x6474e553
+    PT_SUNWBSS = 0x6ffffffa
+    PT_SUNWSTACK = 0x6ffffffb
+
+class PtARM(enum.Enum):
+    """Supplemental PT_* constants for EM_ARM."""
+    PT_ARM_EXIDX = 0x70000001
+
+class PtIA_64(enum.Enum):
+    """Supplemental PT_* constants for EM_IA_64."""
+    PT_IA_64_HP_OPT_ANOT = 0x60000012
+    PT_IA_64_HP_HSL_ANOT = 0x60000013
+    PT_IA_64_HP_STACK = 0x60000014
+    PT_IA_64_ARCHEXT = 0x70000000
+    PT_IA_64_UNWIND = 0x70000001
+
+class PtMIPS(enum.Enum):
+    """Supplemental PT_* constants for EM_MIPS."""
+    PT_MIPS_REGINFO = 0x70000000
+    PT_MIPS_RTPROC = 0x70000001
+    PT_MIPS_OPTIONS = 0x70000002
+    PT_MIPS_ABIFLAGS = 0x70000003
+
+class PtPARISC(enum.Enum):
+    """Supplemental PT_* constants for EM_PARISC."""
+    PT_HP_TLS = 0x60000000
+    PT_HP_CORE_NONE = 0x60000001
+    PT_HP_CORE_VERSION = 0x60000002
+    PT_HP_CORE_KERNEL = 0x60000003
+    PT_HP_CORE_COMM = 0x60000004
+    PT_HP_CORE_PROC = 0x60000005
+    PT_HP_CORE_LOADABLE = 0x60000006
+    PT_HP_CORE_STACK = 0x60000007
+    PT_HP_CORE_SHM = 0x60000008
+    PT_HP_CORE_MMF = 0x60000009
+    PT_HP_PARALLEL = 0x60000010
+    PT_HP_FASTBIND = 0x60000011
+    PT_HP_OPT_ANNOT = 0x60000012
+    PT_HP_HSL_ANNOT = 0x60000013
+    PT_HP_STACK = 0x60000014
+    PT_PARISC_ARCHEXT = 0x70000000
+    PT_PARISC_UNWIND = 0x70000001
+
+class Dt(_OpenIntEnum):
+    """ELF dynamic segment tags.  Type of Dyn.d_val."""
+    DT_NULL = 0
+    DT_NEEDED = 1
+    DT_PLTRELSZ = 2
+    DT_PLTGOT = 3
+    DT_HASH = 4
+    DT_STRTAB = 5
+    DT_SYMTAB = 6
+    DT_RELA = 7
+    DT_RELASZ = 8
+    DT_RELAENT = 9
+    DT_STRSZ = 10
+    DT_SYMENT = 11
+    DT_INIT = 12
+    DT_FINI = 13
+    DT_SONAME = 14
+    DT_RPATH = 15
+    DT_SYMBOLIC = 16
+    DT_REL = 17
+    DT_RELSZ = 18
+    DT_RELENT = 19
+    DT_PLTREL = 20
+    DT_DEBUG = 21
+    DT_TEXTREL = 22
+    DT_JMPREL = 23
+    DT_BIND_NOW = 24
+    DT_INIT_ARRAY = 25
+    DT_FINI_ARRAY = 26
+    DT_INIT_ARRAYSZ = 27
+    DT_FINI_ARRAYSZ = 28
+    DT_RUNPATH = 29
+    DT_FLAGS = 30
+    DT_PREINIT_ARRAY = 32
+    DT_PREINIT_ARRAYSZ = 33
+    DT_SYMTAB_SHNDX = 34
+    DT_GNU_PRELINKED = 0x6ffffdf5
+    DT_GNU_CONFLICTSZ = 0x6ffffdf6
+    DT_GNU_LIBLISTSZ = 0x6ffffdf7
+    DT_CHECKSUM = 0x6ffffdf8
+    DT_PLTPADSZ = 0x6ffffdf9
+    DT_MOVEENT = 0x6ffffdfa
+    DT_MOVESZ = 0x6ffffdfb
+    DT_FEATURE_1 = 0x6ffffdfc
+    DT_POSFLAG_1 = 0x6ffffdfd
+    DT_SYMINSZ = 0x6ffffdfe
+    DT_SYMINENT = 0x6ffffdff
+    DT_GNU_HASH = 0x6ffffef5
+    DT_TLSDESC_PLT = 0x6ffffef6
+    DT_TLSDESC_GOT = 0x6ffffef7
+    DT_GNU_CONFLICT = 0x6ffffef8
+    DT_GNU_LIBLIST = 0x6ffffef9
+    DT_CONFIG = 0x6ffffefa
+    DT_DEPAUDIT = 0x6ffffefb
+    DT_AUDIT = 0x6ffffefc
+    DT_PLTPAD = 0x6ffffefd
+    DT_MOVETAB = 0x6ffffefe
+    DT_SYMINFO = 0x6ffffeff
+    DT_VERSYM = 0x6ffffff0
+    DT_RELACOUNT = 0x6ffffff9
+    DT_RELCOUNT = 0x6ffffffa
+    DT_FLAGS_1 = 0x6ffffffb
+    DT_VERDEF = 0x6ffffffc
+    DT_VERDEFNUM = 0x6ffffffd
+    DT_VERNEED = 0x6ffffffe
+    DT_VERNEEDNUM = 0x6fffffff
+    DT_AUXILIARY = 0x7ffffffd
+    DT_FILTER = 0x7fffffff
+
+class DtAARCH64(enum.Enum):
+    """Supplemental DT_* constants for EM_AARCH64."""
+    DT_AARCH64_BTI_PLT = 0x70000001
+    DT_AARCH64_PAC_PLT = 0x70000003
+    DT_AARCH64_VARIANT_PCS = 0x70000005
+
+class DtALPHA(enum.Enum):
+    """Supplemental DT_* constants for EM_ALPHA."""
+    DT_ALPHA_PLTRO = 0x70000000
+
+class DtALTERA_NIOS2(enum.Enum):
+    """Supplemental DT_* constants for EM_ALTERA_NIOS2."""
+    DT_NIOS2_GP = 0x70000002
+
+class DtIA_64(enum.Enum):
+    """Supplemental DT_* constants for EM_IA_64."""
+    DT_IA_64_PLT_RESERVE = 0x70000000
+
+class DtMIPS(enum.Enum):
+    """Supplemental DT_* constants for EM_MIPS."""
+    DT_MIPS_RLD_VERSION = 0x70000001
+    DT_MIPS_TIME_STAMP = 0x70000002
+    DT_MIPS_ICHECKSUM = 0x70000003
+    DT_MIPS_IVERSION = 0x70000004
+    DT_MIPS_FLAGS = 0x70000005
+    DT_MIPS_BASE_ADDRESS = 0x70000006
+    DT_MIPS_MSYM = 0x70000007
+    DT_MIPS_CONFLICT = 0x70000008
+    DT_MIPS_LIBLIST = 0x70000009
+    DT_MIPS_LOCAL_GOTNO = 0x7000000a
+    DT_MIPS_CONFLICTNO = 0x7000000b
+    DT_MIPS_LIBLISTNO = 0x70000010
+    DT_MIPS_SYMTABNO = 0x70000011
+    DT_MIPS_UNREFEXTNO = 0x70000012
+    DT_MIPS_GOTSYM = 0x70000013
+    DT_MIPS_HIPAGENO = 0x70000014
+    DT_MIPS_RLD_MAP = 0x70000016
+    DT_MIPS_DELTA_CLASS = 0x70000017
+    DT_MIPS_DELTA_CLASS_NO = 0x70000018
+    DT_MIPS_DELTA_INSTANCE = 0x70000019
+    DT_MIPS_DELTA_INSTANCE_NO = 0x7000001a
+    DT_MIPS_DELTA_RELOC = 0x7000001b
+    DT_MIPS_DELTA_RELOC_NO = 0x7000001c
+    DT_MIPS_DELTA_SYM = 0x7000001d
+    DT_MIPS_DELTA_SYM_NO = 0x7000001e
+    DT_MIPS_DELTA_CLASSSYM = 0x70000020
+    DT_MIPS_DELTA_CLASSSYM_NO = 0x70000021
+    DT_MIPS_CXX_FLAGS = 0x70000022
+    DT_MIPS_PIXIE_INIT = 0x70000023
+    DT_MIPS_SYMBOL_LIB = 0x70000024
+    DT_MIPS_LOCALPAGE_GOTIDX = 0x70000025
+    DT_MIPS_LOCAL_GOTIDX = 0x70000026
+    DT_MIPS_HIDDEN_GOTIDX = 0x70000027
+    DT_MIPS_PROTECTED_GOTIDX = 0x70000028
+    DT_MIPS_OPTIONS = 0x70000029
+    DT_MIPS_INTERFACE = 0x7000002a
+    DT_MIPS_DYNSTR_ALIGN = 0x7000002b
+    DT_MIPS_INTERFACE_SIZE = 0x7000002c
+    DT_MIPS_RLD_TEXT_RESOLVE_ADDR = 0x7000002d
+    DT_MIPS_PERF_SUFFIX = 0x7000002e
+    DT_MIPS_COMPACT_SIZE = 0x7000002f
+    DT_MIPS_GP_VALUE = 0x70000030
+    DT_MIPS_AUX_DYNAMIC = 0x70000031
+    DT_MIPS_PLTGOT = 0x70000032
+    DT_MIPS_RWPLT = 0x70000034
+    DT_MIPS_RLD_MAP_REL = 0x70000035
+    DT_MIPS_XHASH = 0x70000036
+
+class DtPPC(enum.Enum):
+    """Supplemental DT_* constants for EM_PPC."""
+    DT_PPC_GOT = 0x70000000
+    DT_PPC_OPT = 0x70000001
+
+class DtPPC64(enum.Enum):
+    """Supplemental DT_* constants for EM_PPC64."""
+    DT_PPC64_GLINK = 0x70000000
+    DT_PPC64_OPD = 0x70000001
+    DT_PPC64_OPDSZ = 0x70000002
+    DT_PPC64_OPT = 0x70000003
+
+class DtSPARC(enum.Enum):
+    """Supplemental DT_* constants for EM_SPARC."""
+    DT_SPARC_REGISTER = 0x70000001
+
+class StInfo:
+    """ELF symbol binding and type.  Type of the Sym.st_info field."""
+    def __init__(self, arg0, arg1=None):
+        if isinstance(arg0, int) and arg1 is None:
+            self.bind = Stb(arg0 >> 4)
+            self.type = Stt(arg0 & 15)
+        else:
+            self.bind = Stb(arg0)
+            self.type = Stt(arg1)
+
+    def value(self):
+        """Returns the raw value for the bind/type combination."""
+        return (self.bind.value() << 4) | (self.type.value())
+
+# Type in an ELF file.  Used for deserialization.
+_Layout = collections.namedtuple('_Layout', 'unpack size')
+
+def _define_layouts(baseclass: type, layout32: str, layout64: str,
+                    types=None, fields32=None):
+    """Assign variants dict to baseclass.
+
+    The variants dict is indexed by (ElfClass, ElfData) pairs, and its
+    values are _Layout instances.
+
+    """
+    struct32 = struct.Struct(layout32)
+    struct64 = struct.Struct(layout64)
+
+    # Check that the struct formats yield the right number of components.
+    for s in (struct32, struct64):
+        example = s.unpack(b' ' * s.size)
+        if len(example) != len(baseclass._fields):
+            raise ValueError('{!r} yields wrong field count: {} != {}'.format(
+                s.format, len(example),  len(baseclass._fields)))
+
+    # Check that field names in types are correct.
+    if types is None:
+        types = ()
+    for n in types:
+        if n not in baseclass._fields:
+            raise ValueError('{} does not have field {!r}'.format(
+                baseclass.__name__, n))
+
+    if fields32 is not None \
+       and set(fields32) != set(baseclass._fields):
+        raise ValueError('{!r} is not a permutation of the fields {!r}'.format(
+            fields32, baseclass._fields))
+
+    def unique_name(name, used_names = (set((baseclass.__name__,))
+                                        | set(baseclass._fields)
+                                        | {n.__name__
+                                           for n in (types or {}).values()})):
+        """Find a name that is not used for a class or field name."""
+        candidate = name
+        n = 0
+        while candidate in used_names:
+            n += 1
+            candidate = '{}{}'.format(name, n)
+        used_names.add(candidate)
+        return candidate
+
+    blob_name = unique_name('blob')
+    struct_unpack_name = unique_name('struct_unpack')
+    comps_name = unique_name('comps')
+
+    layouts = {}
+    for (bits, elfclass, layout, fields) in (
+            (32, ElfClass.ELFCLASS32, layout32, fields32),
+            (64, ElfClass.ELFCLASS64, layout64, None),
+    ):
+        for (elfdata, structprefix, funcsuffix) in (
+                (ElfData.ELFDATA2LSB, '<', 'LE'),
+                (ElfData.ELFDATA2MSB, '>', 'BE'),
+        ):
+            env = {
+                baseclass.__name__: baseclass,
+                struct_unpack_name: struct.unpack,
+            }
+
+            # Add the type converters.
+            if types:
+                for cls in types.values():
+                    env[cls.__name__] = cls
+
+            funcname = ''.join(
+                ('unpack_', baseclass.__name__, str(bits), funcsuffix))
+
+            code = '''
+def {funcname}({blob_name}):
+'''.format(funcname=funcname, blob_name=blob_name)
+
+            indent = ' ' * 4
+            unpack_call = '{}({!r}, {})'.format(
+                struct_unpack_name, structprefix + layout, blob_name)
+            field_names = ', '.join(baseclass._fields)
+            if types is None and fields is None:
+                code += '{}return {}({})\n'.format(
+                    indent, baseclass.__name__, unpack_call)
+            else:
+                # Destructuring tuple assignment.
+                if fields is None:
+                    code += '{}{} = {}\n'.format(
+                        indent, field_names, unpack_call)
+                else:
+                    # Use custom field order.
+                    code += '{}{} = {}\n'.format(
+                        indent, ', '.join(fields), unpack_call)
+
+                # Perform the type conversions.
+                for n in baseclass._fields:
+                    if n in types:
+                        code += '{}{} = {}({})\n'.format(
+                            indent, n, types[n].__name__, n)
+                # Create the named tuple.
+                code += '{}return {}({})\n'.format(
+                    indent, baseclass.__name__, field_names)
+
+            exec(code, env)
+            layouts[(elfclass, elfdata)] = _Layout(
+                env[funcname], struct.calcsize(layout))
+    baseclass.layouts = layouts
+
+
+# Corresponds to EI_* indices into Elf*_Ehdr.e_indent.
+class Ident(collections.namedtuple('Ident',
+    'ei_mag ei_class ei_data ei_version ei_osabi ei_abiversion ei_pad')):
+
+    def __new__(cls, *args):
+        """Construct an object from a blob or its constituent fields."""
+        if len(args) == 1:
+            return cls.unpack(args[0])
+        return cls.__base__.__new__(cls, *args)
+
+    @staticmethod
+    def unpack(blob: memoryview) -> 'Ident':
+        """Parse raws data into a tuple."""
+        ei_mag, ei_class, ei_data, ei_version, ei_osabi, ei_abiversion, \
+            ei_pad = struct.unpack('4s5B7s', blob)
+        return Ident(ei_mag, ElfClass(ei_class), ElfData(ei_data),
+                     ei_version, ei_osabi, ei_abiversion, ei_pad)
+    size = 16
+
+# Corresponds to Elf32_Ehdr and Elf64_Ehdr.
+Ehdr = collections.namedtuple('Ehdr',
+   'e_ident e_type e_machine e_version e_entry e_phoff e_shoff e_flags'
+    + ' e_ehsize e_phentsize e_phnum e_shentsize e_shnum e_shstrndx')
+_define_layouts(Ehdr,
+                layout32='16s2H5I6H',
+                layout64='16s2HI3QI6H',
+                types=dict(e_ident=Ident,
+                           e_machine=Machine,
+                           e_type=Et,
+                           e_shstrndx=Shn))
+
+# Corresponds to Elf32_Phdr and Elf64_Pdhr.  Order follows the latter.
+Phdr = collections.namedtuple('Phdr',
+    'p_type p_flags p_offset p_vaddr p_paddr p_filesz p_memsz p_align')
+_define_layouts(Phdr,
+                layout32='8I',
+                fields32=('p_type', 'p_offset', 'p_vaddr', 'p_paddr',
+                          'p_filesz', 'p_memsz', 'p_flags', 'p_align'),
+                layout64='2I6Q',
+            types=dict(p_type=Pt, p_flags=Pf))
+
+
+# Corresponds to Elf32_Shdr and Elf64_Shdr.
+class Shdr(collections.namedtuple('Shdr',
+    'sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link sh_info'
+    + ' sh_addralign sh_entsize')):
+    def resolve(self, strtab: 'StringTable') -> 'Shdr':
+        """Resolve sh_name using a string table."""
+        return self.__class__(strtab.get(self[0]), *self[1:])
+_define_layouts(Shdr,
+                layout32='10I',
+                layout64='2I4Q2I2Q',
+                types=dict(sh_type=Sht,
+                           sh_flags=Shf,
+                           sh_link=Shn))
+
+# Corresponds to Elf32_Dyn and Elf64_Dyn.  The nesting through the
+# d_un union is skipped, and d_ptr is missing (its representation in
+# Python would be identical to d_val).
+Dyn = collections.namedtuple('Dyn', 'd_tag d_val')
+_define_layouts(Dyn,
+                layout32='2i',
+                layout64='2q',
+                types=dict(d_tag=Dt))
+
+# Corresponds to Elf32_Sym and Elf64_Sym.
+class Sym(collections.namedtuple('Sym',
+    'st_name st_info st_other st_shndx st_value st_size')):
+    def resolve(self, strtab: 'StringTable') -> 'Sym':
+        """Resolve st_name using a string table."""
+        return self.__class__(strtab.get(self[0]), *self[1:])
+_define_layouts(Sym,
+                layout32='3I2BH',
+                layout64='I2BH2Q',
+                fields32=('st_name', 'st_value', 'st_size', 'st_info',
+                          'st_other', 'st_shndx'),
+                types=dict(st_shndx=Shn,
+                           st_info=StInfo))
+
+# Corresponds to Elf32_Rel and Elf64_Rel.
+Rel = collections.namedtuple('Rel', 'r_offset r_info')
+_define_layouts(Rel,
+                layout32='2I',
+                layout64='2Q')
+
+# Corresponds to Elf32_Rel and Elf64_Rel.
+Rela = collections.namedtuple('Rela', 'r_offset r_info r_addend')
+_define_layouts(Rela,
+                layout32='3I',
+                layout64='3Q')
+
+class StringTable:
+    """ELF string table."""
+    def __init__(self, blob):
+        """Create a new string table backed by the data in the blob.
+
+        blob: a memoryview-like object
+
+        """
+        self.blob = blob
+
+    def get(self, index) -> bytes:
+        """Returns the null-terminated byte string at the index."""
+        blob = self.blob
+        endindex = index
+        while True:
+            if blob[endindex] == 0:
+                return bytes(blob[index:endindex])
+            endindex += 1
+
+class Image:
+    """ELF image parser."""
+    def __init__(self, image):
+        """Create an ELF image from binary image data.
+
+        image: a memoryview-like object that supports efficient range
+        subscripting.
+
+        """
+        self.image = image
+        ident = self.read(Ident, 0)
+        classdata = (ident.ei_class, ident.ei_data)
+        # Set self.Ehdr etc. to the subtypes with the right parsers.
+        for typ in (Ehdr, Phdr, Shdr, Dyn, Sym, Rel, Rela):
+            setattr(self, typ.__name__, typ.layouts.get(classdata, None))
+
+        if self.Ehdr is not None:
+            self.ehdr = self.read(self.Ehdr, 0)
+            self._shdr_num = self._compute_shdr_num()
+        else:
+            self.ehdr = None
+            self._shdr_num = 0
+
+        self._section = {}
+        self._stringtab = {}
+
+        if self._shdr_num > 0:
+            self._shdr_strtab = self._find_shdr_strtab()
+        else:
+            self._shdr_strtab = None
+
+    @staticmethod
+    def readfile(path: str) -> 'Image':
+        """Reads the ELF file at the specified path."""
+        with open(path, 'rb') as inp:
+            return Image(memoryview(inp.read()))
+
+    def _compute_shdr_num(self) -> int:
+        """Computes the actual number of section headers."""
+        shnum = self.ehdr.e_shnum
+        if shnum == 0:
+            if self.ehdr.e_shoff == 0 or self.ehdr.e_shentsize == 0:
+                # No section headers.
+                return 0
+            # Otherwise the extension mechanism is used (which may be
+            # needed because e_shnum is just 16 bits).
+            return self.read(self.Shdr, self.ehdr.e_shoff).sh_size
+        return shnum
+
+    def _find_shdr_strtab(self) -> StringTable:
+        """Finds the section header string table (maybe via extensions)."""
+        shstrndx = self.ehdr.e_shstrndx
+        if shstrndx == Shn.SHN_XINDEX:
+            shstrndx = self.read(self.Shdr, self.ehdr.e_shoff).sh_link
+        return self._find_stringtab(shstrndx)
+
+    def read(self, typ: type, offset:int ):
+        """Reads an object at a specific offset.
+
+        The type must have been enhanced using _define_variants.
+
+        """
+        return typ.unpack(self.image[offset: offset + typ.size])
+
+    def phdrs(self) -> Phdr:
+        """Generator iterating over the program headers."""
+        if self.ehdr is None:
+            return
+        size = self.ehdr.e_phentsize
+        if size != self.Phdr.size:
+            raise ValueError('Unexpected Phdr size in ELF header: {} != {}'
+                             .format(size, self.Phdr.size))
+
+        offset = self.ehdr.e_phoff
+        for _ in range(self.ehdr.e_phnum):
+            yield self.read(self.Phdr, offset)
+            offset += size
+
+    def shdrs(self, resolve: bool=True) -> Shdr:
+        """Generator iterating over the section headers.
+
+        If resolve, section names are automatically translated
+        using the section header string table.
+
+        """
+        if self._shdr_num == 0:
+            return
+
+        size = self.ehdr.e_shentsize
+        if size != self.Shdr.size:
+            raise ValueError('Unexpected Shdr size in ELF header: {} != {}'
+                             .format(size, self.Shdr.size))
+
+        offset = self.ehdr.e_shoff
+        for _ in range(self._shdr_num):
+            shdr = self.read(self.Shdr, offset)
+            if resolve:
+                shdr = shdr.resolve(self._shdr_strtab)
+            yield shdr
+            offset += size
+
+    def dynamic(self) -> Dyn:
+        """Generator iterating over the dynamic segment."""
+        for phdr in self.phdrs():
+            if phdr.p_type == Pt.PT_DYNAMIC:
+                # Pick the first dynamic segment, like the loader.
+                if phdr.p_filesz == 0:
+                    # Probably separated debuginfo.
+                    return
+                offset = phdr.p_offset
+                end = offset + phdr.p_memsz
+                size = self.Dyn.size
+                while True:
+                    next_offset = offset + size
+                    if next_offset > end:
+                        raise ValueError(
+                            'Dynamic segment size {} is not a multiple of Dyn size {}'.format(
+                                phdr.p_memsz, size))
+                    yield self.read(self.Dyn, offset)
+                    if next_offset == end:
+                        return
+                    offset = next_offset
+
+    def syms(self, shdr: Shdr, resolve: bool=True) -> Sym:
+        """A generator iterating over a symbol table.
+
+        If resolve, symbol names are automatically translated using
+        the string table for the symbol table.
+
+        """
+        assert shdr.sh_type == Sht.SHT_SYMTAB
+        size = shdr.sh_entsize
+        if size != self.Sym.size:
+            raise ValueError('Invalid symbol table entry size {}'.format(size))
+        offset = shdr.sh_offset
+        end = shdr.sh_offset + shdr.sh_size
+        if resolve:
+            strtab = self._find_stringtab(shdr.sh_link)
+        while offset < end:
+            sym = self.read(self.Sym, offset)
+            if resolve:
+                sym = sym.resolve(strtab)
+            yield sym
+            offset += size
+        if offset != end:
+            raise ValueError('Symbol table is not a multiple of entry size')
+
+    def lookup_string(self, strtab_index: int, strtab_offset: int) -> bytes:
+        """Looks up a string in a string table identified by its link index."""
+        try:
+            strtab = self._stringtab[strtab_index]
+        except KeyError:
+            strtab = self._find_stringtab(strtab_index)
+        return strtab.get(strtab_offset)
+
+    def find_section(self, shndx: Shn) -> Shdr:
+        """Returns the section header for the indexed section.
+
+        The section name is not resolved.
+        """
+        try:
+            return self._section[shndx]
+        except KeyError:
+            pass
+        if shndx in Shn:
+            raise ValueError('Reserved section index {}'.format(shndx))
+        idx = shndx.value
+        if idx < 0 or idx > self._shdr_num:
+            raise ValueError('Section index {} out of range [0, {})'.format(
+                idx, self._shdr_num))
+        shdr = self.read(
+            self.Shdr, self.ehdr.e_shoff + idx * self.Shdr.size)
+        self._section[shndx] = shdr
+        return shdr
+
+    def _find_stringtab(self, sh_link: int) -> StringTable:
+        if sh_link in self._stringtab:
+            return self._stringtab
+        if sh_link < 0 or sh_link >= self._shdr_num:
+            raise ValueError('Section index {} out of range [0, {})'.format(
+                sh_link, self._shdr_num))
+        shdr = self.read(
+            self.Shdr, self.ehdr.e_shoff + sh_link * self.Shdr.size)
+        if shdr.sh_type != Sht.SHT_STRTAB:
+            raise ValueError(
+                'Section {} is not a string table: {}'.format(
+                    sh_link, shdr.sh_type))
+        strtab = StringTable(
+            self.image[shdr.sh_offset:shdr.sh_offset + shdr.sh_size])
+        # This could retrain essentially arbitrary amounts of data,
+        # but caching string tables seems important for performance.
+        self._stringtab[sh_link] = strtab
+        return strtab
+
+
+__all__ = [name for name in dir() if name[0].isupper()]