blob: b87b72bdec1a5b1b518bc8edf9990b13668ac3cd [file] [log] [blame]
// 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