summaryrefslogtreecommitdiffstats
path: root/src/core/loader/linker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/loader/linker.cpp')
-rw-r--r--src/core/loader/linker.cpp151
1 files changed, 151 insertions, 0 deletions
diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp
new file mode 100644
index 000000000..a265b9315
--- /dev/null
+++ b/src/core/loader/linker.cpp
@@ -0,0 +1,151 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/logging/log.h"
+#include "common/swap.h"
+#include "core/loader/linker.h"
+#include "core/memory.h"
+
+namespace Loader {
+
+enum class RelocationType : u32 { ABS64 = 257, GLOB_DAT = 1025, JUMP_SLOT = 1026, RELATIVE = 1027 };
+
+enum DynamicType : u32 {
+ DT_NULL = 0,
+ DT_PLTRELSZ = 2,
+ DT_STRTAB = 5,
+ DT_SYMTAB = 6,
+ DT_RELA = 7,
+ DT_RELASZ = 8,
+ DT_STRSZ = 10,
+ DT_JMPREL = 23,
+};
+
+struct Elf64_Rela {
+ u64_le offset;
+ RelocationType type;
+ u32_le symbol;
+ s64_le addend;
+};
+static_assert(sizeof(Elf64_Rela) == 0x18, "Elf64_Rela has incorrect size.");
+
+struct Elf64_Dyn {
+ u64_le tag;
+ u64_le value;
+};
+static_assert(sizeof(Elf64_Dyn) == 0x10, "Elf64_Dyn has incorrect size.");
+
+struct Elf64_Sym {
+ u32_le name;
+ INSERT_PADDING_BYTES(0x2);
+ u16_le shndx;
+ u64_le value;
+ u64_le size;
+};
+static_assert(sizeof(Elf64_Sym) == 0x18, "Elf64_Sym has incorrect size.");
+
+void Linker::WriteRelocations(std::vector<u8>& program_image,
+ const std::vector<Symbol>& symbols, u64 relocation_offset,
+ u64 size, bool is_jump_relocation, VAddr load_base) {
+ for (u64 i = 0; i < size; i += sizeof(Elf64_Rela)) {
+ Elf64_Rela rela;
+ std::memcpy(&rela, &program_image[relocation_offset + i], sizeof(Elf64_Rela));
+
+ const Symbol& symbol = symbols[rela.symbol];
+ switch (rela.type) {
+ case RelocationType::RELATIVE: {
+ const u64 value = load_base + rela.addend;
+ if (!symbol.name.empty()) {
+ exports[symbol.name] = value;
+ }
+ std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
+ break;
+ }
+ case RelocationType::JUMP_SLOT:
+ case RelocationType::GLOB_DAT:
+ if (!symbol.value) {
+ imports[symbol.name] = {rela.offset + load_base, 0};
+ } else {
+ exports[symbol.name] = symbol.value;
+ std::memcpy(&program_image[rela.offset], &symbol.value, sizeof(u64));
+ }
+ break;
+ case RelocationType::ABS64:
+ if (!symbol.value) {
+ imports[symbol.name] = {rela.offset + load_base, rela.addend};
+ } else {
+ const u64 value = symbol.value + rela.addend;
+ exports[symbol.name] = value;
+ std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
+ }
+ break;
+ default:
+ LOG_CRITICAL(Loader, "Unknown relocation type: %d", rela.type);
+ break;
+ }
+ }
+}
+
+void Linker::Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset,
+ VAddr load_base) {
+ std::map<u64, u64> dynamic;
+ while (dynamic_section_offset < program_image.size()) {
+ Elf64_Dyn dyn;
+ std::memcpy(&dyn, &program_image[dynamic_section_offset], sizeof(Elf64_Dyn));
+ dynamic_section_offset += sizeof(Elf64_Dyn);
+
+ if (dyn.tag == DT_NULL) {
+ break;
+ }
+ dynamic[dyn.tag] = dyn.value;
+ }
+
+ u64 offset = dynamic[DT_SYMTAB];
+ std::vector<Symbol> symbols;
+ while (offset < program_image.size()) {
+ Elf64_Sym sym;
+ std::memcpy(&sym, &program_image[offset], sizeof(Elf64_Sym));
+ offset += sizeof(Elf64_Sym);
+
+ if (sym.name >= dynamic[DT_STRSZ]) {
+ break;
+ }
+
+ std::string name = reinterpret_cast<char*>(&program_image[dynamic[DT_STRTAB] + sym.name]);
+ if (sym.value) {
+ exports[name] = load_base + sym.value;
+ symbols.emplace_back(std::move(name), load_base + sym.value);
+ } else {
+ symbols.emplace_back(std::move(name), 0);
+ }
+ }
+
+ if (dynamic.find(DT_RELA) != dynamic.end()) {
+ WriteRelocations(program_image, symbols, dynamic[DT_RELA], dynamic[DT_RELASZ], false,
+ load_base);
+ }
+
+ if (dynamic.find(DT_JMPREL) != dynamic.end()) {
+ WriteRelocations(program_image, symbols, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ], true,
+ load_base);
+ }
+}
+
+void Linker::ResolveImports() {
+ // Resolve imports
+ for (const auto& import : imports) {
+ const auto& search = exports.find(import.first);
+ if (search != exports.end()) {
+ Memory::Write64(import.second.ea, search->second + import.second.addend);
+ }
+ else {
+ LOG_ERROR(Loader, "Unresolved import: %s", import.first.c_str());
+ }
+ }
+}
+
+} // namespace Loader