| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "relocation.h" |
| |
| #include <lib/static-pie/static-pie.h> |
| |
| #include <atomic> |
| #include <climits> |
| #include <cstdint> |
| |
| #include <fbl/span.h> |
| |
| #include "elf-types.h" |
| |
| namespace static_pie { |
| |
| // Apply a fixup function to the word at `addr`. |
| // |
| // We assume that callers only want to convert LinkTimeAddr's in the |
| // program to RunTimeAddr's: hence, `fixup` is given a LinkTimeAddr and |
| // should return a RunTimeAddr. |
| template <typename F> |
| void ApplyFixup(const Program& program, LinkTimeAddr addr, F&& fixup) { |
| LinkTimeAddr orig_word = LinkTimeAddr(program.ReadWord(addr)); |
| RunTimeAddr fixed_word = fixup(orig_word); |
| program.WriteWord(addr, fixed_word.value()); |
| } |
| |
| void ApplyRelaRelocs(const Program& program, fbl::Span<const Elf64RelaEntry> table) { |
| // We require that all entries in the table are `R_RELATIVE` entries. |
| for (const Elf64RelaEntry& entry : table) { |
| ZX_DEBUG_ASSERT(entry.info.type() == ElfRelocType::kRelative); |
| |
| // `entry.addend` contains a link-time address. We simply convert it |
| // to a run-time address and write it into the program. |
| ApplyFixup(program, entry.offset, [&](LinkTimeAddr /*ignored*/) { |
| return program.ToRunTimeAddr(LinkTimeAddr{entry.addend}); |
| }); |
| } |
| } |
| |
| void ApplyRelRelocs(const Program& program, fbl::Span<const Elf64RelEntry> table) { |
| // We require that all entries in the table are `R_RELATIVE` entries. |
| for (const Elf64RelEntry& entry : table) { |
| ZX_DEBUG_ASSERT(entry.info.type() == ElfRelocType::kRelative); |
| |
| // `entry.offset` points to a link-time address. We convert it to |
| // a run-time address. |
| ApplyFixup(program, entry.offset, |
| [&](LinkTimeAddr addr) { return program.ToRunTimeAddr(addr); }); |
| } |
| } |
| |
| void ApplyRelrRelocs(const Program& program, fbl::Span<const uint64_t> table) { |
| LinkTimeAddr address = LinkTimeAddr(0); |
| |
| for (uint64_t value : table) { |
| // If the value is an address (low bit is 0), simply patch it in. |
| if ((value & 1) == 0) { |
| ZX_DEBUG_ASSERT(value != 0); |
| address = LinkTimeAddr(value); |
| |
| ApplyFixup(program, address, |
| [&](LinkTimeAddr input) { return program.ToRunTimeAddr(input); }); |
| address += sizeof(uint64_t); |
| |
| continue; |
| } |
| |
| // Otherwise, the value is a bitmap, indicating which of the next 63 words |
| // should be updated. |
| uint64_t bitmap = value >> 1; |
| LinkTimeAddr bitmap_address = address; |
| while (bitmap != 0) { |
| // Skip over `skip` words that need not be patched. |
| uint64_t skip = __builtin_ctzll(bitmap); |
| bitmap_address += skip * sizeof(uint64_t); |
| bitmap >>= (skip + 1); |
| |
| // Patch this word. |
| ApplyFixup(program, bitmap_address, |
| [&](LinkTimeAddr input) { return program.ToRunTimeAddr(input); }); |
| bitmap_address += sizeof(uint64_t); |
| } |
| |
| // Move `address` ahead 63 words. |
| constexpr uint64_t bits_per_bitmap = (sizeof(uint64_t) * CHAR_BIT) - 1; |
| address += sizeof(uint64_t) * bits_per_bitmap; |
| } |
| } |
| |
| void ApplyDynamicRelocs(Program& program, fbl::Span<const Elf64DynamicEntry> table) { |
| // Locations and sizes of the rel, rela, and relr tables. |
| struct RelocationTable { |
| LinkTimeAddr start = LinkTimeAddr(0); // Address of the table. |
| size_t size_bytes = 0; // Size of the table, in bytes. |
| |
| // Number of R_RELATIVE entries in the table. |
| // |
| // These are required to be ordered first in the `.rel` and `.rela` table. |
| uint64_t num_relative_relocs = 0; |
| }; |
| RelocationTable rel_table{}; |
| RelocationTable rela_table{}; |
| RelocationTable relr_table{}; |
| |
| // Process entries in the ".dynamic" table. |
| for (size_t i = 0; i < table.size() && table[i].tag != DynamicArrayTag::kNull; i++) { |
| switch (table[i].tag) { |
| // Rela table. |
| case DynamicArrayTag::kRela: |
| rela_table.start = LinkTimeAddr(table[i].value); |
| break; |
| case DynamicArrayTag::kRelaSize: |
| rela_table.size_bytes = table[i].value; |
| break; |
| case DynamicArrayTag::kRelaCount: |
| rela_table.num_relative_relocs = table[i].value; |
| break; |
| case DynamicArrayTag::kRelaEntrySize: |
| ZX_ASSERT(table[i].value == sizeof(Elf64RelaEntry)); |
| break; |
| |
| // Rel table. |
| case DynamicArrayTag::kRel: |
| rel_table.start = LinkTimeAddr(table[i].value); |
| break; |
| case DynamicArrayTag::kRelSize: |
| rel_table.size_bytes = table[i].value; |
| break; |
| case DynamicArrayTag::kRelCount: |
| rel_table.num_relative_relocs = table[i].value; |
| break; |
| case DynamicArrayTag::kRelEntrySize: |
| ZX_ASSERT(table[i].value != sizeof(Elf64RelEntry)); |
| break; |
| |
| // Relr table. |
| case DynamicArrayTag::kRelr: |
| relr_table.start = LinkTimeAddr(table[i].value); |
| break; |
| case DynamicArrayTag::kRelrSize: |
| relr_table.size_bytes = table[i].value; |
| break; |
| case DynamicArrayTag::kRelrEntrySize: |
| ZX_ASSERT(table[i].value == sizeof(uint64_t)); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| // Apply any relocations. |
| { |
| fbl::Span<const uint64_t> table = |
| program.MapRegion<const uint64_t>(relr_table.start, relr_table.size_bytes); |
| ApplyRelrRelocs(program, table); |
| } |
| { |
| fbl::Span<const Elf64RelaEntry> table = |
| program.MapRegion<const Elf64RelaEntry>(rela_table.start, rela_table.size_bytes); |
| // Only the first `num_relative_relocs` will be R_RELATIVE entries. |
| ApplyRelaRelocs(program, table.subspan(0, rela_table.num_relative_relocs)); |
| } |
| { |
| fbl::Span<const Elf64RelEntry> table = |
| program.MapRegion<const Elf64RelEntry>(rel_table.start, rel_table.size_bytes); |
| // Only the first `num_relative_relocs` will be R_RELATIVE entries. |
| ApplyRelRelocs(program, table.subspan(0, rel_table.num_relative_relocs)); |
| } |
| } |
| |
| } // namespace static_pie |