blob: 8c7e8d6e3ee86cbc3b559d4af89ac1633a846709 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// The code in this file is responsible for applying dynamic relocation
// for position independent ELF. It itself has to work without any relocation.
// That means all dependencies of this file need to be statically linked. There
// shall not be any reference to any symbol from dynamically linked library
// before relocation is done.
//
// It would be worth to investigate if this can be implemented in rust.
#include <stddef.h>
#include <stdint.h>
#include "libelf/elf.h"
#include "elf/relocation.h"
#ifdef HOST_TOOLING
#include <stdio.h>
#include <stdlib.h>
#define RELOCATION_PRINTF(...) fprintf(stderr, __VA_ARGS__)
#else
#define RELOCATION_PRINTF(...)
#endif
namespace {
struct DynamicSectionInfo {
const Elf64_Dyn* rel;
const Elf64_Dyn* rel_size;
const Elf64_Dyn* rela;
const Elf64_Dyn* rela_size;
const Elf64_Sym* symtab;
};
Elf64_Sxword GetRelxAddend(const Elf64_Rela& entry, uint64_t*) {
return entry.r_addend;
}
Elf64_Sxword GetRelxAddend(const Elf64_Rel&, uint64_t* addr) {
return *addr;
}
// Perform relocation fixup for the given RELA/REL relocation table.
//
// For more information about REL/RELA relocation, see the Relocation and Dynamic linking
// related sections in System V ABI (https://www.sco.com/developers/gabi/latest/contents.html)
template <typename T>
bool FixUpRelxTable(uintptr_t program_base, [[maybe_unused]] const Elf64_Sym* symtab,
uintptr_t table_addr, size_t size) {
size_t num_entries = size / sizeof(T);
const T* table = reinterpret_cast<const T*>(table_addr);
for (size_t i = 0; i < num_entries; i++) {
const T& entry = table[i];
// Type of the relocation. It determines the calculation.
uint64_t reloc_type = entry.r_info & 0xffffffff;
// Address for storing the new calculated address.
// TODO(294059825): Add support for overflow checking.
uint64_t* addr = reinterpret_cast<uint64_t*>(program_base + entry.r_offset);
// Addend value. For RELA, it comes from the entry. For REL, it's the current
// value at address `addr`.
Elf64_Sxword addend = GetRelxAddend(entry, addr);
// Index of the corresponding symbol in the symbol table.
// Certain type of relocation requires to modify symbol table. Keep this here
// for reference.
[[maybe_unused]] size_t symbol_index = entry.r_info >> 32;
// Now process the various type of relocation.
//
// For documents about AARCH64 relocation, see
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst#5712dynamic-relocations
//
// For document about RISC-V relocation, see
// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#relocations
//
// GBL's usage of ELF is restricted. We only implement relocations that we expect to see.
if (reloc_type == R_RISCV_RELATIVE) {
// This is the most common type of relocation. For position independent executable,
// symbol address is typically the relative offset to program start. Relocated absolute
// address is obtained by adding the address the program is loaded to.
*addr = program_base + addend;
} else {
RELOCATION_PRINTF("Unhandled relocation type: %lx\n", reloc_type);
return false;
}
}
return true;
}
// Search for RELA type relocation table from the .dynamic section and apply relocation fix up if
// there is one.
bool ApplyRelaRelocation(uintptr_t program_base, const DynamicSectionInfo& info) {
if ((info.rela == NULL) != (info.rela_size == NULL)) {
return false;
} else if (!info.rela) {
return true;
}
return FixUpRelxTable<Elf64_Rela>(program_base, info.symtab, info.rela->d_un.d_ptr + program_base,
info.rela_size->d_un.d_val);
}
// Search for REL type relocation table from the .dynamic section and apply relocation fix up if
// there is one.
bool ApplyRelRelocation(uintptr_t program_base, const DynamicSectionInfo& info) {
if ((info.rel == NULL) != (info.rel_size == NULL)) {
return false;
} else if (!info.rel) {
return true;
}
return FixUpRelxTable<Elf64_Rel>(program_base, info.symtab, info.rel->d_un.d_ptr + program_base,
info.rel_size->d_un.d_val);
}
} // namespace
extern "C" bool ApplyRelocation(uintptr_t program_base, uintptr_t dynamic_section) {
DynamicSectionInfo dynamic_section_info = {
.rel = NULL,
.rel_size = NULL,
.rela = NULL,
.rela_size = NULL,
.symtab = NULL,
};
const Elf64_Dyn* dynamic_section_table = reinterpret_cast<const Elf64_Dyn*>(dynamic_section);
for (size_t i = 0; dynamic_section_table[i].d_tag != DT_NULL; i++) {
switch (dynamic_section_table[i].d_tag) {
// RELA type relocation entries
case DT_RELA:
dynamic_section_info.rela = &dynamic_section_table[i];
break;
// RELA type relocation entries total size
case DT_RELASZ:
dynamic_section_info.rela_size = &dynamic_section_table[i];
break;
// REL type relocation entries
case DT_REL:
dynamic_section_info.rel = &dynamic_section_table[i];
break;
// RELA type relocation entries total size
case DT_RELSZ:
dynamic_section_info.rel_size = &dynamic_section_table[i];
break;
// Symbol table
case DT_SYMTAB:
dynamic_section_info.symtab =
(const Elf64_Sym*)(dynamic_section_table[i].d_un.d_ptr + program_base);
break;
// We shouldn't see RELR relocation unless `-Wl,--pack-dyn-relocs=relr` is used.
case DT_RELR:
// We shouldn't see PLT relocation unless we are loading a shared library.
case DT_JMPREL:
return false;
default:
break;
}
}
if (!dynamic_section_info.symtab) {
return false;
}
return ApplyRelRelocation(program_base, dynamic_section_info) &&
ApplyRelaRelocation(program_base, dynamic_section_info);
}