// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2020 Martin Whitaker. // // Derived from memtest86+ reloc.c: // // reloc.c - MemTest-86 Version 3.3 // // Released under version 2 of the Gnu Public License. // By Eric Biederman #include #include //------------------------------------------------------------------------------ // Constants //------------------------------------------------------------------------------ // Dynamic section tag values #define DT_NULL 0 // End of dynamic section #define DT_PLTRELSZ 2 // Size in bytes of PLT relocs #define DT_RELA 7 // Address of Rel relocs #define DT_RELASZ 8 // Total size of Rel relocs #define DT_RELAENT 9 #define DT_PLTREL 20 // Type of reloc in PLT #define DT_JMPREL 23 // Address of PLT relocs #define DT_NUM 34 // Number of tag values // Relocation types #define R_X86_64_NONE 0 #define R_X86_64_RELATIVE 8 //------------------------------------------------------------------------------ // Types //------------------------------------------------------------------------------ typedef uint64_t Elf64_Addr; typedef int64_t Elf64_Sxword; typedef uint64_t Elf64_Xword; typedef struct { Elf64_Sxword d_tag; union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; } Elf64_Dyn; typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; } Elf64_Rela; //------------------------------------------------------------------------------ // Private Functions //------------------------------------------------------------------------------ #define ELF64_R_TYPE(r_info) ((r_info) & 0xffffffff) static inline void assert(int expr) { if (!expr) { __asm__ __volatile__ ("int $3"); } } /* * Return the run-time load address of the shared object. */ static inline Elf64_Addr __attribute__ ((unused)) get_load_address(void) { Elf64_Addr addr; __asm__ __volatile__ ( "leaq _start(%%rip), %0" : "=r" (addr) : : "cc" ); return addr; } /* * Return the link-time address of _DYNAMIC. Conveniently, this is the first * element of the GOT. */ static inline Elf64_Addr __attribute__ ((unused)) get_dynamic_section_offset(void) { Elf64_Addr offs; __asm__ __volatile__ ( "movq _GLOBAL_OFFSET_TABLE_(%%rip), %0" : "=r" (offs) : : "cc" ); return offs; } static void get_dynamic_info(Elf64_Dyn *dyn_section, Elf64_Addr load_offs, Elf64_Dyn *dyn_info[DT_NUM]) { Elf64_Dyn *dyn = dyn_section; while (dyn->d_tag != DT_NULL) { if (dyn->d_tag < DT_NUM) { dyn_info[dyn->d_tag] = dyn; } dyn++; } if (dyn_info[DT_RELA] != NULL) { assert(dyn_info[DT_RELAENT]->d_un.d_val == sizeof(Elf64_Rela)); dyn_info[DT_RELA]->d_un.d_ptr += load_offs; } if (dyn_info[DT_PLTREL] != NULL) { assert(dyn_info[DT_PLTREL]->d_un.d_val == DT_RELA); } if (dyn_info[DT_JMPREL] != NULL) { dyn_info[DT_JMPREL]->d_un.d_ptr += load_offs; } } static void do_relocation(Elf64_Addr load_addr, Elf64_Addr load_offs, const Elf64_Rela *rel) { Elf64_Addr *target_addr = (Elf64_Addr *)(load_addr + rel->r_offset); if (ELF64_R_TYPE(rel->r_info) == R_X86_64_RELATIVE) { if (load_offs == load_addr) { *target_addr = load_addr + rel->r_addend; } else { *target_addr += load_offs; } return; } if (ELF64_R_TYPE(rel->r_info) == R_X86_64_NONE) { return; } assert(! "unexpected dynamic reloc type"); } static void do_relocations(Elf64_Addr load_addr, Elf64_Addr load_offs, Elf64_Addr rel_addr, Elf64_Addr rel_size) { const Elf64_Rela *rel_start = (const Elf64_Rela *)(rel_addr); const Elf64_Rela *rel_end = (const Elf64_Rela *)(rel_addr + rel_size); for (const Elf64_Rela *rel = rel_start; rel < rel_end; rel++) { do_relocation(load_addr, load_offs, rel); } } //------------------------------------------------------------------------------ // Public Functions //------------------------------------------------------------------------------ void reloc(void) { static volatile Elf64_Addr last_load_addr = 0; Elf64_Dyn *dyn_info[DT_NUM]; for (int i = 0; i < DT_NUM; i++) { dyn_info[i] = NULL; } Elf64_Addr load_addr = get_load_address(); Elf64_Addr load_offs = load_addr - last_load_addr; if (load_addr == last_load_addr) { return; } last_load_addr = load_addr; Elf64_Dyn *dyn_section = (Elf64_Dyn *)(load_addr + get_dynamic_section_offset()); get_dynamic_info(dyn_section, load_offs, dyn_info); do_relocations(load_addr, load_offs, dyn_info[DT_RELA]->d_un.d_ptr, dyn_info[DT_RELASZ]->d_un.d_val); if (dyn_info[DT_PLTREL]->d_un.d_val == DT_RELA) { do_relocations(load_addr, load_offs, dyn_info[DT_JMPREL]->d_un.d_ptr, dyn_info[DT_PLTRELSZ]->d_un.d_val); } }