From: Andreas Ziegler Date: Mon, 14 Feb 2022 13:44:27 +0000 (+0100) Subject: Add support for DT_RELR/SHT_RELR compressed relocation sections (#395) X-Git-Tag: v0.29~28 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=c2cfef0557a7297dc945fc29ff4f5d44ca6b1f51;p=pyelftools.git Add support for DT_RELR/SHT_RELR compressed relocation sections (#395) As more and more tools now support DT_RELR compressed relocations (most notably, the just released GNU binutils 2.38 [0]), let's add support for reading these relocations as well. The original discussion about advantages of packe RELATIVE relocations can be found at [1]. In a nutshell, the format exploits the fact that RELATIVE relocations are often placed next to each other and (for x86_64) stores up to 64 relocations in two 8-byte words. In a regular .rela.dyn table, these would take up 24 * 64 = 1536 bytes. The compressed relocations work as follows: The first word in the section describes a base address and contains an offset for a relocation. This offset must always lie at an even address. Following this entry can be one or more bitmap(s) which have their least significant bit set to 1. All other bits describe (in increasing order of significance) if the following continuous offsets also contain a relocation. The addends for existing relocations are stored at the corresponding offsets in the file (that is, they work like REL relocations). A good description of the history of this feature and its current adoption is the following blog post [2]. [0]: https://lists.gnu.org/archive/html/info-gnu/2022-02/msg00009.html [1]: https://groups.google.com/g/generic-abi/c/bX460iggiKg?pli=1 [2]: https://maskray.me/blog/2021-10-31-relative-relocations-and-relr --- diff --git a/elftools/elf/descriptions.py b/elftools/elf/descriptions.py index b14ea3e..0ccc9a1 100644 --- a/elftools/elf/descriptions.py +++ b/elftools/elf/descriptions.py @@ -412,6 +412,7 @@ _DESCR_SH_TYPE = dict( SHT_GNU_HASH='GNU_HASH', SHT_GROUP='GROUP', SHT_SYMTAB_SHNDX='SYMTAB SECTION INDICIES', + SHT_RELR='RELR', SHT_GNU_verdef='VERDEF', SHT_GNU_verneed='VERNEED', SHT_GNU_versym='VERSYM', diff --git a/elftools/elf/elffile.py b/elftools/elf/elffile.py index e864a16..244841a 100644 --- a/elftools/elf/elffile.py +++ b/elftools/elf/elffile.py @@ -31,7 +31,8 @@ from .sections import ( SymbolTableIndexSection, SUNWSyminfoTableSection, NullSection, NoteSection, StabSection, ARMAttributesSection) from .dynamic import DynamicSection, DynamicSegment -from .relocation import RelocationSection, RelocationHandler +from .relocation import (RelocationSection, RelocationHandler, + RelrRelocationSection) from .gnuversions import ( GNUVerNeedSection, GNUVerDefSection, GNUVerSymSection) @@ -595,6 +596,8 @@ class ELFFile(object): return self._make_elf_hash_section(section_header, name) elif sectype == 'SHT_GNU_HASH': return self._make_gnu_hash_section(section_header, name) + elif sectype == 'SHT_RELR': + return RelrRelocationSection(section_header, name, self) else: return Section(section_header, name, self) diff --git a/elftools/elf/enums.py b/elftools/elf/enums.py index 61c3d42..8519f4e 100644 --- a/elftools/elf/enums.py +++ b/elftools/elf/enums.py @@ -295,7 +295,8 @@ ENUM_SH_TYPE_BASE = dict( SHT_PREINIT_ARRAY=16, SHT_GROUP=17, SHT_SYMTAB_SHNDX=18, - SHT_NUM=19, + SHT_RELR=19, + SHT_NUM=20, SHT_LOOS=0x60000000, SHT_GNU_ATTRIBUTES=0x6ffffff5, SHT_GNU_HASH=0x6ffffff6, @@ -513,7 +514,11 @@ ENUM_D_TAG_COMMON = dict( DT_ENCODING=32, DT_PREINIT_ARRAY=32, DT_PREINIT_ARRAYSZ=33, - DT_NUM=34, + DT_SYMTAB_SHNDX=34, + DT_RELRSZ=35, + DT_RELR=36, + DT_RELRENT=37, + DT_NUM=38, DT_LOOS=0x6000000d, DT_ANDROID_REL=0x6000000f, DT_ANDROID_RELSZ=0x60000010, diff --git a/elftools/elf/relocation.py b/elftools/elf/relocation.py index 8ca4ca1..4008e28 100644 --- a/elftools/elf/relocation.py +++ b/elftools/elf/relocation.py @@ -15,6 +15,7 @@ from .enums import ( ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, ENUM_RELOC_TYPE_MIPS, ENUM_RELOC_TYPE_ARM, ENUM_RELOC_TYPE_AARCH64, ENUM_RELOC_TYPE_PPC64, ENUM_D_TAG) +from ..construct import Container class Relocation(object): @@ -106,6 +107,80 @@ class RelocationSection(Section, RelocationTable): 'Expected sh_entsize of %s section to be %s' % ( header['sh_type'], self.entry_size)) +class RelrRelocationSection(Section): + """ RELR compressed relocation section. This stores relative relocations + in a compressed format. An entry with an even value serves as an + 'anchor' that defines a base address. Following this entry are one or + more bitmaps for consecutive addresses after the anchor which determine + if the corresponding relocation exists (if the bit is 1) or if it is + skipped. Addends are stored at the respective addresses (as in REL + relocations). + """ + def __init__(self, header, name, elffile): + Section.__init__(self, header, name, elffile) + self._offset = self['sh_offset'] + self._size = self['sh_size'] + self._relr_struct = self.elffile.structs.Elf_Relr + self._entrysize = self._relr_struct.sizeof() + self._cached_relocations = None + + def iter_relocations(self): + """ Yield all the relocations in the section + """ + limit = self._offset + self._size + relr = self._offset + # The addresses of relocations in a bitmap are calculated from a base + # value provided in an initial 'anchor' relocation. + base = None + while relr < limit: + entry = struct_parse(self._relr_struct, + self.elffile.stream, + stream_pos=relr) + entry_offset = entry['r_offset'] + if (entry_offset & 1) == 0: + # We found an anchor, take the current value as the base address + # for the following bitmaps and move the 'where' pointer to the + # beginning of the first bitmap. + base = entry_offset + base += self._entrysize + yield Relocation(entry, self.elffile) + else: + # We're processing a bitmap. + elf_assert(base is not None, 'RELR bitmap without base address') + i = 0 + while True: + # Iterate over all bits except the least significant one. + entry_offset = (entry_offset >> 1) + if entry_offset == 0: + break + # if the current LSB is set, we have a relocation at the + # corresponding address so generate a Relocation with the + # matching offset + if (entry_offset & 1) != 0: + calc_offset = base + i * self._entrysize + yield Relocation(Container(r_offset = calc_offset), + self.elffile) + i += 1 + # Advance 'base' past the current bitmap (8 == CHAR_BIT). There + # are 63 (or 31 for 32-bit ELFs) entries in each bitmap, and + # every bit corresponds to an ELF_addr-sized relocation. + base += (8 * self._entrysize - 1) * self.elffile.structs.Elf_addr('').sizeof() + # Advance to the next entry + relr += self._entrysize + + def num_relocations(self): + """ Number of relocations in the section + """ + if self._cached_relocations is None: + self._cached_relocations = list(self.iter_relocations()) + return len(self._cached_relocations) + + def get_relocation(self, n): + """ Get the relocation at index #n from the section (Relocation object) + """ + if self._cached_relocations is None: + self._cached_relocations = list(self.iter_relocations()) + return self._cached_relocations[n] class RelocationHandler(object): """ Handles the logic of relocations in ELF files. diff --git a/elftools/elf/structs.py b/elftools/elf/structs.py index 6a1d2aa..b437eec 100644 --- a/elftools/elf/structs.py +++ b/elftools/elf/structs.py @@ -270,6 +270,12 @@ class ELFStructs(object): *fields_and_addend ) + # Elf32_Relr is typedef'd as Elf32_Word, Elf64_Relr as Elf64_Xword + # (see the glibc patch, for example: + # https://sourceware.org/pipermail/libc-alpha/2021-October/132029.html) + # For us, this is the same as self.Elf_addr (or self.Elf_xword). + self.Elf_Relr = Struct('Elf_Relr', self.Elf_addr('r_offset')) + def _create_dyn(self): d_tag_dict = dict(ENUM_D_TAG_COMMON) if self.e_machine in ENUMMAP_EXTRA_D_TAG_MACHINE: diff --git a/test/test_relr.py b/test/test_relr.py new file mode 100644 index 0000000..69e39d6 --- /dev/null +++ b/test/test_relr.py @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------------- +# elftools tests +# +# Andreas Ziegler (andreas.ziegler@fau.de) +# This code is in the public domain +#------------------------------------------------------------------------------- +# The lib_relro.so.elf file was generated as follows (on Debian 11): +# $ cat lib_relro.c +# int retfunc(){ return 1; } +# int (*ptr1)() = retfunc; +# int (*ptr2)() = retfunc; +# <...> +# int (*ptr100)() = retfunc; +# $ clang-12 -c -o lib_relro.o -fPIC lib_relro.c +# $ ld.lld-12 -o lib_relro.so.elf --pack-dyn-relocs=relr --shared -Bsymbolic-functions lib_relro.o + +import unittest +import os + +from elftools.elf.elffile import ELFFile + +class TestRelr(unittest.TestCase): + + def test_num_relocations(self): + """ Verify we can get the number of relocations in a RELR relocation + section. + """ + path = os.path.join('test', 'testfiles_for_unittests', + 'lib_relro.so.elf') + with open(path, 'rb') as f: + elf = ELFFile(f) + relr_section = elf.get_section_by_name('.relr.dyn') + self.assertIsNotNone(relr_section) + self.assertEqual(relr_section.num_relocations(), 100) + + def test_get_relocation(self): + """ Verify we can get a specific relocation in a RELR relocation + section. + """ + path = os.path.join('test', 'testfiles_for_unittests', + 'lib_relro.so.elf') + with open(path, 'rb') as f: + elf = ELFFile(f) + relr_section = elf.get_section_by_name('.relr.dyn') + self.assertIsNotNone(relr_section) + reloc = relr_section.get_relocation(n=0) + self.assertEqual(reloc['r_offset'], 0x4540) + reloc = relr_section.get_relocation(n=65) + self.assertEqual(reloc['r_offset'], 0x4748) diff --git a/test/testfiles_for_unittests/lib_relro.so.elf b/test/testfiles_for_unittests/lib_relro.so.elf new file mode 100755 index 0000000..16b8587 Binary files /dev/null and b/test/testfiles_for_unittests/lib_relro.so.elf differ