/* Copyright 2017 - 2026 R. Thomas
* Copyright 2017 - 2026 Quarkslab
*
* 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.
*/
#include "LIEF/ELF/hash.hpp"
#include "LIEF/ELF/Relocation.hpp"
#include "LIEF/ELF/EnumToString.hpp"
#include "LIEF/ELF/Symbol.hpp"
#include "LIEF/ELF/Binary.hpp"
#include "ELF/Structures.hpp"
#include "logging.hpp"
namespace LIEF::ELF {
int32_t get_reloc_size(Relocation::TYPE type);
template Relocation::Relocation(const details::Elf32_Rel&, PURPOSE, ENCODING,
ARCH);
template Relocation::Relocation(const details::Elf32_Rela&, PURPOSE, ENCODING,
ARCH);
template Relocation::Relocation(const details::Elf64_Rel&, PURPOSE, ENCODING,
ARCH);
template Relocation::Relocation(const details::Elf64_Rela&, PURPOSE, ENCODING,
ARCH);
Relocation::TYPE Relocation::type_from(uint32_t value, ARCH arch) {
static std::set<ARCH> ERR;
switch (arch) {
case ARCH::X86_64: return TYPE(value | R_X64);
case ARCH::AARCH64: return TYPE(value | R_AARCH64);
case ARCH::I386: return TYPE(value | R_X86);
case ARCH::ARM: return TYPE(value | R_ARM);
case ARCH::HEXAGON: return TYPE(value | R_HEXAGON);
case ARCH::LOONGARCH: return TYPE(value | R_LARCH);
case ARCH::MIPS: return TYPE(value | R_MIPS);
case ARCH::PPC: return TYPE(value | R_PPC);
case ARCH::PPC64: return TYPE(value | R_PPC64);
case ARCH::SPARC: return TYPE(value | R_SPARC);
case ARCH::RISCV: return TYPE(value | R_RISCV);
case ARCH::BPF: return TYPE(value | R_BPF);
case ARCH::SH: return TYPE(value | R_SH4);
case ARCH::S390: return TYPE(value | R_SYSZ);
default:
{
if (ERR.insert(arch).second) {
LIEF_ERR("Unsupported relocation architecture '{}'", to_string(arch));
}
return TYPE::UNKNOWN;
}
}
return TYPE::UNKNOWN;
}
template<class T>
Relocation::Relocation(const T& header, PURPOSE purpose, ENCODING enc, ARCH arch) :
LIEF::Relocation{header.r_offset, 0},
encoding_{enc},
architecture_{arch},
purpose_{purpose} {
if constexpr (std::is_same_v<T, details::Elf32_Rel> ||
std::is_same_v<T, details::Elf32_Rela>)
{
type_ = type_from(header.r_info & 0xff, arch);
info_ = static_cast<uint32_t>(header.r_info >> 8);
}
if constexpr (std::is_same_v<T, details::Elf64_Rel> ||
std::is_same_v<T, details::Elf64_Rela>)
{
type_ = type_from(header.r_info & 0xffffffff, arch);
info_ = static_cast<uint32_t>(header.r_info >> 32);
}
if constexpr (std::is_same_v<T, details::Elf32_Rela> ||
std::is_same_v<T, details::Elf64_Rela>)
{
addend_ = header.r_addend;
}
}
Relocation::Relocation(uint64_t address, TYPE type, ENCODING encoding) :
LIEF::Relocation(address, 0),
type_(type),
encoding_(encoding) {
if (type != TYPE::UNKNOWN) {
auto raw_type = static_cast<uint64_t>(type);
const uint64_t ID = (raw_type >> Relocation::R_BIT) << Relocation::R_BIT;
if (ID == Relocation::R_X64) {
architecture_ = ARCH::X86_64;
} else if (ID == Relocation::R_AARCH64) {
architecture_ = ARCH::AARCH64;
} else if (ID == Relocation::R_ARM) {
architecture_ = ARCH::ARM;
} else if (ID == Relocation::R_HEXAGON) {
architecture_ = ARCH::HEXAGON;
} else if (ID == Relocation::R_X86) {
architecture_ = ARCH::I386;
} else if (ID == Relocation::R_LARCH) {
architecture_ = ARCH::LOONGARCH;
} else if (ID == Relocation::R_MIPS) {
architecture_ = ARCH::MIPS;
} else if (ID == Relocation::R_PPC) {
architecture_ = ARCH::PPC;
} else if (ID == Relocation::R_PPC64) {
architecture_ = ARCH::PPC64;
} else if (ID == Relocation::R_SPARC) {
architecture_ = ARCH::SPARC;
} else if (ID == Relocation::R_RISCV) {
architecture_ = ARCH::RISCV;
} else if (ID == Relocation::R_BPF) {
architecture_ = ARCH::BPF;
} else if (ID == Relocation::R_SH4) {
architecture_ = ARCH::SH;
} else if (ID == Relocation::R_SYSZ) {
architecture_ = ARCH::S390;
}
}
}
result<uint64_t> Relocation::resolve(uint64_t base_address) const {
int64_t A = this->addend();
uint64_t S = 0;
uint64_t P = this->address();
const int32_t RSZ = size();
if (const Symbol* sym = symbol()) {
S = sym->value();
}
/* Memory based address */
const uint64_t B = base_address;
auto Q = [&]() -> int64_t {
if (RSZ < 0) {
LIEF_ERR("Missing size info for relocation {}", to_string(type()));
return 0;
}
switch (RSZ) {
case sizeof(uint8_t):
{
auto V = binary_->get_int_from_virtual_address<uint8_t>(P);
if (!V) {
LIEF_ERR(
"{}: Failed to access relocation data at offset {:#08x} (8-bit)",
to_string(type()), P
);
return 0;
}
return (int64_t)((int8_t)*V);
}
case sizeof(uint16_t):
{
auto V = binary_->get_int_from_virtual_address<uint16_t>(P);
if (!V) {
LIEF_ERR(
"{}: Failed to access relocation data at offset {:#08x} (16-bit)",
to_string(type()), P
);
return 0;
}
return (int64_t)((int16_t)*V);
}
case sizeof(uint32_t):
{
auto V = binary_->get_int_from_virtual_address<uint32_t>(P);
if (!V) {
LIEF_ERR(
"{}: Failed to access relocation data at offset {:#08x} (32-bit)",
to_string(type()), P
);
return 0;
}
return (int64_t)((int32_t)*V);
}
case sizeof(uint64_t):
{
auto V = binary_->get_int_from_virtual_address<uint64_t>(P);
if (!V) {
LIEF_ERR(
"{}: Failed to access relocation data at offset {:#08x} (64-bit)",
to_string(type()), P
);
return 0;
}
return (int64_t)*V;
}
default:
LIEF_ERR("Invalid relocation size {} (sz: {})", to_string(type()), RSZ);
return 0;
}
};
switch (type()) {
/* X86_64 { */
/* See ELF x86-64-ABI psABI[1] for the reference
*
* [1]: https://gitlab.com/x86-psABIs/x86-64-ABI
*/
case TYPE::X86_64_NONE: return Q();
case TYPE::X86_64_64:
case TYPE::X86_64_DTPOFF32:
case TYPE::X86_64_DTPOFF64: return S + A;
case TYPE::X86_64_PC32:
case TYPE::X86_64_PC64: return S + A - P;
case TYPE::X86_64_32:
case TYPE::X86_64_32S: return (S + A) & 0xFFFFFFFF;
case TYPE::X86_64_GLOB_DAT:
case TYPE::X86_64_JUMP_SLOT: return S;
case TYPE::X86_64_RELATIVE:
case TYPE::X86_64_RELATIVE64:
return B + A;
/* } */
/* X86 { */
case TYPE::X86_NONE: return Q();
case TYPE::X86_32: return S + Q();
case TYPE::X86_PC32: return S - P + Q();
case TYPE::X86_GLOB_DAT:
case TYPE::X86_JUMP_SLOT: return S;
case TYPE::X86_RELATIVE:
return B + Q();
/* } */
/* AArc64 { */
case TYPE::AARCH64_ABS32: return (S + A) & 0xFFFFFFFF;
case TYPE::AARCH64_ABS64: return S + A;
case TYPE::AARCH64_PREL16: return (S + A - P) & 0xFFFF;
case TYPE::AARCH64_PREL32: return (S + A - P) & 0xFFFFFFFF;
case TYPE::AARCH64_PREL64: return S + A - P;
case TYPE::AARCH64_GLOB_DAT:
case TYPE::AARCH64_JUMP_SLOT: return S;
case TYPE::AARCH64_RELATIVE:
return B + A;
/* } */
/* ARM { */
case TYPE::ARM_ABS32: return (S + Q() + A) & 0xFFFFFFFF;
case TYPE::ARM_REL32: return (S + Q() + A - P) & 0xFFFFFFFF;
case TYPE::ARM_GLOB_DAT:
case TYPE::ARM_JUMP_SLOT: return S;
case TYPE::ARM_RELATIVE:
return B + Q();
/* } */
/* eBPF { */
case TYPE::BPF_64_ABS32: return (S + Q()) & 0xFFFFFFFF;
case TYPE::BPF_64_ABS64:
return S + Q();
/* } */
/* MIPS { */
case TYPE::MIPS_32: return (S + A) & 0xFFFFFFFF;
case TYPE::MIPS_64: return S + A;
case TYPE::MIPS_TLS_DTPREL64: return S + A - 0x8000;
case TYPE::MIPS_PC32:
return S + A - P;
/* } */
/* PPC64 { */
case TYPE::PPC64_ADDR32: return (S + A) & 0xFFFFFFFF;
case TYPE::PPC64_ADDR64: return S + A;
case TYPE::PPC64_REL32: return (S + A - P) & 0xFFFFFFFF;
case TYPE::PPC64_REL64:
return S + A - P;
/* } */
/* PPC32 { */
case TYPE::PPC_ADDR32: return (S + A) & 0xFFFFFFFF;
case TYPE::PPC_REL32:
return (S + A - P) & 0xFFFFFFFF;
/* } */
/* RISCV { */
case TYPE::RISCV_NONE: return Q();
case TYPE::RISCV_32: return (S + A) & 0xFFFFFFFF;
case TYPE::RISCV_32_PCREL: return (S + A - P) & 0xFFFFFFFF;
case TYPE::RISCV_64: return S + A;
case TYPE::RISCV_SET6: return (A & 0xC0) | ((S + A) & 0x3F);
case TYPE::RISCV_SUB6: return (A & 0xC0) | (((A & 0x3F) - (S + A)) & 0x3F);
case TYPE::RISCV_SET8: return (S + A) & 0xFF;
case TYPE::RISCV_ADD8: return (A + (S + A)) & 0xFF;
case TYPE::RISCV_SUB8: return (A - (S + 1)) & 0xFF;
case TYPE::RISCV_SET16: return (S + A) & 0xFFFF;
case TYPE::RISCV_ADD16: return (A + (S + A)) & 0xFFFF;
case TYPE::RISCV_SUB16: return (A - (S + A)) & 0xFFFF;
case TYPE::RISCV_SET32: return (S + A) & 0xFFFFFFFF;
case TYPE::RISCV_ADD32: return (A + (S + A)) & 0xFFFFFFFF;
case TYPE::RISCV_SUB32: return (A - (S + A)) & 0xFFFFFFFF;
case TYPE::RISCV_ADD64: return (A + (S + A));
case TYPE::RISCV_SUB64:
return (A - (S + A));
/* } */
/* LoongArch { */
case TYPE::LARCH_NONE: return Q();
case TYPE::LARCH_32: return (S + A) & 0xFFFFFFFF;
case TYPE::LARCH_32_PCREL: return (S + A - P) & 0xFFFFFFFF;
case TYPE::LARCH_64: return S + A;
case TYPE::LARCH_ADD6: return (Q() & 0xC0) | ((Q() + S + A) & 0x3F);
case TYPE::LARCH_SUB6: return (Q() & 0xC0) | ((Q() - (S + A)) & 0x3F);
case TYPE::LARCH_ADD8: return (Q() + (S + A)) & 0xFF;
case TYPE::LARCH_SUB8: return (Q() - (S + A)) & 0xFF;
case TYPE::LARCH_ADD16: return (Q() + (S + A)) & 0xFFFF;
case TYPE::LARCH_SUB16: return (Q() - (S + A)) & 0xFFFF;
case TYPE::LARCH_ADD32: return (Q() + (S + A)) & 0xFFFFFFFF;
case TYPE::LARCH_SUB32: return (Q() - (S + A)) & 0xFFFFFFFF;
case TYPE::LARCH_ADD64: return (Q() + (S + A));
case TYPE::LARCH_SUB64: return (Q() - (S + A));
/* } */
default: break;
}
LIEF_DEBUG("Unsupported relocation type: {}", to_string(type()));
return make_error_code(lief_errors::not_supported);
}
size_t Relocation::size() const {
return get_reloc_size(type_);
}
void Relocation::accept(Visitor& visitor) const {
visitor.visit(*this);
}
std::ostream& operator<<(std::ostream& os, const Relocation& entry) {
std::string symbol_name;
if (const Symbol* symbol = entry.symbol()) {
symbol_name = symbol->demangled_name();
if (symbol_name.empty()) {
symbol_name = symbol->name();
}
}
if (entry.size() == uint64_t(-1)) {
os << fmt::format("{:#08x} {} {:#06x} {:#04x} {}", entry.address(),
to_string(entry.type()), entry.addend(), entry.info(),
symbol_name);
return os;
}
os << fmt::format("{:#08x} {} ({}) {:#06x} {:#04x} {}", entry.address(),
to_string(entry.type()), entry.size(), entry.addend(),
entry.info(), symbol_name);
return os;
}
}