diff options
author | Daniel Bertalan <dani@danielbertalan.dev> | 2021-10-28 09:31:51 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-02-11 18:07:53 +0100 |
commit | 3974cac148ff12f04fd98fa3a1c4578767988897 (patch) | |
tree | d3c262e97055e52a1aba8061402153382c7e2899 /Userland | |
parent | 32b8795091681de7296074fb9a7b4f63a17e475a (diff) | |
download | serenity-3974cac148ff12f04fd98fa3a1c4578767988897.zip |
LibELF: Implement support for DT_RELR relative relocations
The DT_RELR relocation is a relatively new relocation encoding designed
to achieve space-efficient relative relocations in PIE programs.
The description of the format is available here:
https://groups.google.com/g/generic-abi/c/bX460iggiKg/m/Pi9aSwwABgAJ
It works by using a bitmap to store the offsets which need to be
relocated. Even entries are *address* entries: they contain an address
(relative to the base of the executable) which needs to be relocated.
Subsequent even entries are *bitmap* entries: "1" bits encode offsets
(in word size increments) relative to the last address entry which need
to be relocated.
This is in contrast to the REL/RELA format, where each entry takes up
2/3 machine words. Certain kinds of relocations store useful data in
that space (like the name of the referenced symbol), so not everything
can be encoded in this format. But as position-independent executables
and shared libraries tend to have a lot of relative relocations, a
specialized encoding for them absolutely makes sense.
The authors of the format suggest an overall 5-20% reduction in the file
size of various programs. Due to our extensive use of dynamic linking
and us not stripping debug info, relative relocations don't make up such
a large portion of the binary's size, so the measurements will tend to
skew to the lower side of the spectrum.
The following measurements were made with the x86-64 Clang toolchain:
- The kernel contains 290989 relocations. Enabling RELR decreased its
size from 30 MiB to 23 MiB.
- LibUnicodeData contains 190262 relocations, almost all of them
relative. Its file size changed from 17 MiB to 13 MiB.
- /bin/WebContent contains 1300 relocations, 66% of which are relative
relocations. With RELR, its size changed from 832 KiB to 812 KiB.
This change was inspired by the following blog post:
https://maskray.me/blog/2021-10-31-relative-relocations-and-relr
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibC/elf.h | 9 | ||||
-rw-r--r-- | Userland/Libraries/LibELF/DynamicLoader.cpp | 10 | ||||
-rw-r--r-- | Userland/Libraries/LibELF/DynamicLoader.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibELF/DynamicObject.cpp | 20 | ||||
-rw-r--r-- | Userland/Libraries/LibELF/DynamicObject.h | 36 | ||||
-rw-r--r-- | Userland/Libraries/LibELF/Relocation.cpp | 39 |
6 files changed, 113 insertions, 2 deletions
diff --git a/Userland/Libraries/LibC/elf.h b/Userland/Libraries/LibC/elf.h index abb21e4536..01c497259e 100644 --- a/Userland/Libraries/LibC/elf.h +++ b/Userland/Libraries/LibC/elf.h @@ -277,6 +277,7 @@ typedef struct { #define SHT_PREINIT_ARRAY 16 /* ptrs to funcs called before init */ #define SHT_GROUP 17 /* defines a section group */ #define SHT_SYMTAB_SHNDX 18 /* Section indices (see SHN_XINDEX). */ +#define SHT_RELR 19 /* relative-only relocation section */ #define SHT_LOOS 0x60000000 /* reserved range for OS specific */ #define SHT_SUNW_dof 0x6ffffff4 /* used by dtrace */ #define SHT_GNU_LIBLIST 0x6ffffff7 /* libraries to be prelinked */ @@ -440,6 +441,9 @@ typedef struct { # define ELF64_R_INFO(s, t) (((uint64_t)swap32(t) << 32) + (uint32_t)(s)) #endif /* __mips64__ && __MIPSEL__ */ +typedef Elf32_Word Elf32_Relr; +typedef Elf64_Xword Elf64_Relr; + /* Program Header */ typedef struct { Elf32_Word p_type; /* segment type */ @@ -545,6 +549,9 @@ typedef struct { #define DT_ENCODING 31 /* further DT_* follow encoding rules */ #define DT_PREINIT_ARRAY 32 /* address of array of preinit func */ #define DT_PREINIT_ARRAYSZ 33 /* size of array of preinit func */ +#define DT_RELRSZ 35 /* size of DT_RELR relocation table */ +#define DT_RELR 36 /* addr of DT_RELR relocation table */ +#define DT_RELRENT 37 /* size of DT_RELR relocation entry */ #define DT_LOOS 0x6000000d /* reserved range for OS */ #define DT_HIOS 0x6ffff000 /* specific dynamic array tags */ #define DT_LOPROC 0x70000000 /* reserved range for processor */ @@ -740,6 +747,7 @@ struct elf_args { # define Elf_Sym Elf32_Sym # define Elf_Rel Elf32_Rel # define Elf_RelA Elf32_Rela +# define Elf_Relr Elf32_Relr # define Elf_Dyn Elf32_Dyn # define Elf_Half Elf32_Half # define Elf_Word Elf32_Word @@ -767,6 +775,7 @@ struct elf_args { # define Elf_Sym Elf64_Sym # define Elf_Rel Elf64_Rel # define Elf_RelA Elf64_Rela +# define Elf_Relr Elf64_Relr # define Elf_Dyn Elf64_Dyn # define Elf_Half Elf64_Half # define Elf_Word Elf64_Word diff --git a/Userland/Libraries/LibELF/DynamicLoader.cpp b/Userland/Libraries/LibELF/DynamicLoader.cpp index 2e678dc5a1..793ffb4e4d 100644 --- a/Userland/Libraries/LibELF/DynamicLoader.cpp +++ b/Userland/Libraries/LibELF/DynamicLoader.cpp @@ -198,8 +198,10 @@ void DynamicLoader::do_main_relocations() break; } }; + m_dynamic_object->relocation_section().for_each_relocation(do_single_relocation); m_dynamic_object->plt_relocation_section().for_each_relocation(do_single_relocation); + do_relr_relocations(); } Result<NonnullRefPtr<DynamicObject>, DlErrorMessage> DynamicLoader::load_stage_3(unsigned flags) @@ -537,6 +539,14 @@ DynamicLoader::RelocationResult DynamicLoader::do_relocation(const ELF::DynamicO return RelocationResult::Success; } +void DynamicLoader::do_relr_relocations() +{ + auto base_address = m_dynamic_object->base_address().get(); + m_dynamic_object->for_each_relr_relocation([base_address](FlatPtr address) { + *(FlatPtr*)address += base_address; + }); +} + ssize_t DynamicLoader::negative_offset_from_tls_block_end(ssize_t tls_offset, size_t value_of_symbol) const { ssize_t offset = static_cast<ssize_t>(tls_offset + value_of_symbol); diff --git a/Userland/Libraries/LibELF/DynamicLoader.h b/Userland/Libraries/LibELF/DynamicLoader.h index 093d555c23..d7c0c71e75 100644 --- a/Userland/Libraries/LibELF/DynamicLoader.h +++ b/Userland/Libraries/LibELF/DynamicLoader.h @@ -130,6 +130,7 @@ private: ResolveLater = 2, }; RelocationResult do_relocation(const DynamicObject::Relocation&, ShouldInitializeWeak should_initialize_weak); + void do_relr_relocations(); size_t calculate_tls_size() const; ssize_t negative_offset_from_tls_block_end(ssize_t tls_offset, size_t value_of_symbol) const; diff --git a/Userland/Libraries/LibELF/DynamicObject.cpp b/Userland/Libraries/LibELF/DynamicObject.cpp index a9e26fc0d7..f56b1b6aea 100644 --- a/Userland/Libraries/LibELF/DynamicObject.cpp +++ b/Userland/Libraries/LibELF/DynamicObject.cpp @@ -135,6 +135,15 @@ void DynamicObject::parse() case DT_RELCOUNT: m_number_of_relocations = entry.val(); break; + case DT_RELR: + m_relr_relocation_table_offset = entry.ptr() - m_elf_base_address.get(); + break; + case DT_RELRSZ: + m_size_of_relr_relocation_table = entry.val(); + break; + case DT_RELRENT: + m_size_of_relr_relocations_entry = entry.val(); + break; case DT_FLAGS: m_dt_flags = entry.val(); break; @@ -236,6 +245,11 @@ DynamicObject::RelocationSection DynamicObject::plt_relocation_section() const return RelocationSection(Section(*this, m_plt_relocation_offset_location, m_size_of_plt_relocation_entry_list, m_size_of_relocation_entry, "DT_JMPREL"sv), false); } +DynamicObject::Section DynamicObject::relr_relocation_section() const +{ + return Section(*this, m_relr_relocation_table_offset, m_size_of_relr_relocation_table, m_size_of_relr_relocations_entry, "DT_RELR"sv); +} + ElfW(Half) DynamicObject::program_header_count() const { auto* header = (const ElfW(Ehdr)*)m_base_address.as_ptr(); @@ -431,6 +445,12 @@ const char* DynamicObject::name_for_dtag(ElfW(Sword) d_tag) return "VERNEEDED"; case DT_VERNEEDEDNUM: return "VERNEEDEDNUM"; + case DT_RELR: + return "DT_RELR"; + case DT_RELRSZ: + return "DT_RELRSZ"; + case DT_RELRENT: + return "DT_RELRENT"; default: return "??"; } diff --git a/Userland/Libraries/LibELF/DynamicObject.h b/Userland/Libraries/LibELF/DynamicObject.h index bc1b3a7a57..24fb27d785 100644 --- a/Userland/Libraries/LibELF/DynamicObject.h +++ b/Userland/Libraries/LibELF/DynamicObject.h @@ -272,6 +272,7 @@ public: RelocationSection relocation_section() const; RelocationSection plt_relocation_section() const; + Section relr_relocation_section() const; bool should_process_origin() const { return m_dt_flags & DF_ORIGIN; } bool requires_symbolic_symbol_resolution() const { return m_dt_flags & DF_SYMBOLIC; } @@ -312,6 +313,9 @@ public: template<VoidFunction<Symbol&> F> void for_each_symbol(F) const; + template<typename F> + void for_each_relr_relocation(F) const; + struct SymbolLookupResult { FlatPtr value { 0 }; size_t size { 0 }; @@ -374,6 +378,9 @@ private: size_t m_size_of_relocation_table { 0 }; bool m_addend_used { false }; FlatPtr m_relocation_table_offset { 0 }; + size_t m_size_of_relr_relocations_entry { 0 }; + size_t m_size_of_relr_relocation_table { 0 }; + FlatPtr m_relr_relocation_table_offset { 0 }; bool m_is_elf_dynamic { false }; // DT_FLAGS @@ -412,6 +419,35 @@ inline void DynamicObject::RelocationSection::for_each_relocation(F func) const }); } +template<typename F> +inline void DynamicObject::for_each_relr_relocation(F f) const +{ + auto section = relr_relocation_section(); + if (section.entry_count() == 0) + return; + + VERIFY(section.entry_size() == sizeof(FlatPtr)); + VERIFY(section.size() >= section.entry_size() * section.entry_count()); + + auto* entries = reinterpret_cast<ElfW(Relr)*>(section.address().get()); + auto base = base_address().get(); + FlatPtr patch_addr = 0; + for (unsigned i = 0; i < section.entry_count(); ++i) { + if ((entries[i] & 1u) == 0) { + patch_addr = base + entries[i]; + f(patch_addr); + patch_addr += sizeof(FlatPtr); + } else { + unsigned j = 0; + for (auto bitmap = entries[i]; (bitmap >>= 1u) != 0; ++j) + if (bitmap & 1u) + f(patch_addr + j * sizeof(FlatPtr)); + + patch_addr += (8 * sizeof(FlatPtr) - 1) * sizeof(FlatPtr); + } + } +} + template<VoidFunction<DynamicObject::Symbol&> F> inline void DynamicObject::for_each_symbol(F func) const { diff --git a/Userland/Libraries/LibELF/Relocation.cpp b/Userland/Libraries/LibELF/Relocation.cpp index b3c84e4cbf..30bac88c5f 100644 --- a/Userland/Libraries/LibELF/Relocation.cpp +++ b/Userland/Libraries/LibELF/Relocation.cpp @@ -26,6 +26,9 @@ bool perform_relative_relocations(FlatPtr base_address) FlatPtr relocation_section_addr = 0; size_t relocation_table_size = 0; size_t relocation_count = 0; + size_t relocation_entry_size = 0; + FlatPtr relr_relocation_section_addr = 0; + size_t relr_relocation_table_size = 0; bool use_addend = false; auto* dyns = reinterpret_cast<const ElfW(Dyn)*>(dynamic_section_addr); for (unsigned i = 0;; ++i) { @@ -40,11 +43,19 @@ bool perform_relative_relocations(FlatPtr base_address) relocation_count = dyn.d_un.d_val; else if (dyn.d_tag == DT_RELSZ || dyn.d_tag == DT_RELASZ) relocation_table_size = dyn.d_un.d_val; + else if (dyn.d_tag == DT_RELENT || dyn.d_tag == DT_RELAENT) + relocation_entry_size = dyn.d_un.d_val; + else if (dyn.d_tag == DT_RELR) + relr_relocation_section_addr = base_address + dyn.d_un.d_ptr; + else if (dyn.d_tag == DT_RELRSZ) + relr_relocation_table_size = dyn.d_un.d_val; + else if (dyn.d_tag == DT_RELRENT) + VERIFY(dyn.d_un.d_val == sizeof(FlatPtr)); } - if (!relocation_section_addr || !relocation_table_size || !relocation_count) + + if ((!relocation_section_addr || !relocation_table_size || !relocation_count) && (!relr_relocation_section_addr || !relr_relocation_table_size)) return false; - auto relocation_entry_size = relocation_table_size / relocation_count; for (unsigned i = 0; i < relocation_count; ++i) { size_t offset_in_section = i * relocation_entry_size; auto* relocation = (ElfW(Rela)*)(relocation_section_addr + offset_in_section); @@ -64,6 +75,30 @@ bool perform_relative_relocations(FlatPtr base_address) __builtin_memcpy(patch_address, &relocated_address, sizeof(relocated_address)); } + auto patch_relr = [base_address](FlatPtr* patch_ptr) { + FlatPtr relocated_address; + __builtin_memcpy(&relocated_address, patch_ptr, sizeof(FlatPtr)); + relocated_address += base_address; + __builtin_memcpy(patch_ptr, &relocated_address, sizeof(FlatPtr)); + }; + + auto* entries = reinterpret_cast<ElfW(Relr)*>(relr_relocation_section_addr); + FlatPtr* patch_ptr = nullptr; + + for (unsigned i = 0; i < relr_relocation_table_size / sizeof(FlatPtr); ++i) { + if ((entries[i] & 1u) == 0) { + patch_ptr = reinterpret_cast<FlatPtr*>(base_address + entries[i]); + patch_relr(patch_ptr); + ++patch_ptr; + } else { + unsigned j = 0; + for (auto bitmap = entries[i]; (bitmap >>= 1u) != 0; ++j) + if (bitmap & 1u) + patch_relr(patch_ptr + j); + + patch_ptr += 8 * sizeof(FlatPtr) - 1; + } + } return true; } } |