lib/elf: Add RELR support
diff --git a/common/lib/elf.c b/common/lib/elf.c
index 1fba9e4f..76deb96b 100644
--- a/common/lib/elf.c
+++ b/common/lib/elf.c
@@ -25,6 +25,9 @@
#define DT_RELA 0x00000007
#define DT_RELASZ 0x00000008
#define DT_RELAENT 0x00000009
+#define DT_RELR 0x00000024
+#define DT_RELRSZ 0x00000023
+#define DT_RELRENT 0x00000025
#define DT_SYMTAB 0x00000006
#define DT_SYMENT 0x0000000b
#define DT_PLTRELSZ 0x00000002
@@ -56,6 +59,8 @@
#define R_RISCV_64 0x00000002
#define R_AARCH64_ABS64 0x00000101
+#define R_INTERNAL_RELR 0xfffffff0
+
/* Indices into identification array */
#define EI_CLASS 4
#define EI_DATA 5
@@ -248,6 +253,20 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b
panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)");
}
+ uint64_t symtab_offset = 0;
+ uint64_t symtab_ent = 0;
+
+ uint64_t dt_pltrelsz = 0;
+ uint64_t dt_jmprel = 0;
+
+ uint64_t relr_offset = 0;
+ uint64_t relr_size = 0;
+ uint64_t relr_ent = 0;
+
+ uint64_t rela_offset = 0;
+ uint64_t rela_size = 0;
+ uint64_t rela_ent = 0;
+
// Find DYN segment
for (uint16_t i = 0; i < hdr->ph_num; i++) {
struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
@@ -255,15 +274,6 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b
if (phdr->p_type != PT_DYNAMIC)
continue;
- uint64_t symtab_offset = 0;
- uint64_t symtab_ent = 0;
-
- uint64_t dt_pltrelsz = 0;
- uint64_t dt_jmprel = 0;
-
- uint64_t rela_offset = 0;
- uint64_t rela_size = 0;
- uint64_t rela_ent = 0;
for (uint16_t j = 0; j < phdr->p_filesz / sizeof(struct elf64_dyn); j++) {
struct elf64_dyn *dyn = (void *)elf + (phdr->p_offset + j * sizeof(struct elf64_dyn));
@@ -277,6 +287,15 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b
case DT_RELASZ:
rela_size = dyn->d_un;
break;
+ case DT_RELR:
+ relr_offset = dyn->d_un;
+ break;
+ case DT_RELRENT:
+ relr_ent = dyn->d_un;
+ break;
+ case DT_RELRSZ:
+ relr_size = dyn->d_un;
+ break;
case DT_SYMTAB:
symtab_offset = dyn->d_un;
break;
@@ -292,16 +311,16 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b
}
}
- if (rela_offset == 0) {
- break;
- }
+ break;
+ }
+ if (rela_offset != 0) {
if (rela_ent < sizeof(struct elf64_rela)) {
- panic(true, "elf: sh_entsize < sizeof(struct elf64_rela)");
+ panic(true, "elf: rela_ent < sizeof(struct elf64_rela)");
}
- for (uint16_t j = 0; j < hdr->ph_num; j++) {
- struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + j * hdr->phdr_size);
+ for (uint16_t i = 0; i < hdr->ph_num; i++) {
+ struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
if (_phdr->p_vaddr <= rela_offset && _phdr->p_vaddr + _phdr->p_filesz > rela_offset) {
rela_offset -= _phdr->p_vaddr;
@@ -309,133 +328,196 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b
break;
}
}
+ }
+ if (relr_offset != 0) {
+ if (relr_ent != 8) {
+ panic(true, "elf: relr_ent != 8");
+ }
+
+ for (uint16_t i = 0; i < hdr->ph_num; i++) {
+ struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
+
+ if (_phdr->p_vaddr <= relr_offset && _phdr->p_vaddr + _phdr->p_filesz > relr_offset) {
+ relr_offset -= _phdr->p_vaddr;
+ relr_offset += _phdr->p_offset;
+ break;
+ }
+ }
+ }
+
+ if (symtab_offset != 0) {
if (symtab_ent < sizeof(struct elf64_sym)) {
- symtab_offset = 0;
+ panic(true, "elf: symtab_ent < sizeof(struct elf64_sym)");
}
- if (symtab_offset != 0) {
- for (uint16_t j = 0; j < hdr->ph_num; j++) {
- struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + j * hdr->phdr_size);
+ for (uint16_t i = 0; i < hdr->ph_num; i++) {
+ struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
- if (_phdr->p_vaddr <= symtab_offset && _phdr->p_vaddr + _phdr->p_filesz > symtab_offset) {
- symtab_offset -= _phdr->p_vaddr;
- symtab_offset += _phdr->p_offset;
- break;
- }
+ if (_phdr->p_vaddr <= symtab_offset && _phdr->p_vaddr + _phdr->p_filesz > symtab_offset) {
+ symtab_offset -= _phdr->p_vaddr;
+ symtab_offset += _phdr->p_offset;
+ break;
}
}
+ }
- if (dt_pltrelsz == 0) {
- dt_jmprel = 0;
+ if (dt_jmprel != 0) {
+ if (rela_ent < sizeof(struct elf64_rela)) {
+ panic(true, "elf: rela_ent < sizeof(struct elf64_rela)");
}
- if (dt_jmprel != 0) {
- for (uint16_t j = 0; j < hdr->ph_num; j++) {
- struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + j * hdr->phdr_size);
+ for (uint16_t i = 0; i < hdr->ph_num; i++) {
+ struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
- if (_phdr->p_vaddr <= dt_jmprel && _phdr->p_vaddr + _phdr->p_filesz > dt_jmprel) {
- dt_jmprel -= _phdr->p_vaddr;
- dt_jmprel += _phdr->p_offset;
- break;
- }
+ if (_phdr->p_vaddr <= dt_jmprel && _phdr->p_vaddr + _phdr->p_filesz > dt_jmprel) {
+ dt_jmprel -= _phdr->p_vaddr;
+ dt_jmprel += _phdr->p_offset;
+ break;
}
}
+ }
+
+ size_t relocs_i = 0;
+ if (relr_offset != 0) {
+ for (size_t i = 0; i < relr_size / relr_ent; i++) {
+ uint64_t entry = *((uint64_t *)(elf + relr_offset + i * relr_ent));
- size_t relocs_i = rela_size / rela_ent;
- if (dt_jmprel != 0) {
- relocs_i += dt_pltrelsz / rela_ent;
+ if ((entry & 1) == 0) {
+ relocs_i++;
+ } else {
+ relocs_i += __builtin_popcount(entry) - 1;
+ }
}
- struct elf64_rela **relocs = ext_mem_alloc(relocs_i * sizeof(struct elf64_rela *));
+ }
+ size_t relr_count = relocs_i;
+ if (rela_offset != 0) {
+ relocs_i += rela_size / rela_ent;
+ }
+ if (dt_jmprel != 0) {
+ relocs_i += dt_pltrelsz / rela_ent;
+ }
+ struct elf64_rela **relocs = ext_mem_alloc(relocs_i * sizeof(struct elf64_rela *));
- for (uint64_t j = 0, offset = 0; offset < rela_size; offset += rela_ent) {
- relocs[j++] = (void *)elf + (rela_offset + offset);
+ if (relr_offset != 0) {
+ size_t relr_i;
+ for (relr_i = 0; relr_i < relr_count; relr_i++) {
+ relocs[relr_i] = ext_mem_alloc(sizeof(struct elf64_rela));
+ relocs[relr_i]->r_info = R_INTERNAL_RELR;
}
- if (dt_jmprel != 0) {
- for (uint64_t j = rela_size / rela_ent, offset = 0; offset < dt_pltrelsz; offset += rela_ent) {
- relocs[j++] = (void *)elf + (dt_jmprel + offset);
+ // This logic is partially lifted from https://maskray.me/blog/2021-10-31-relative-relocations-and-relr
+ uint64_t where;
+ relr_i = 0;
+ for (size_t i = 0; i < relr_size / relr_ent; i++) {
+ uint64_t entry = *((uint64_t *)(elf + relr_offset + i * relr_ent));
+
+ if ((entry & 1) == 0) {
+ where = entry;
+ relocs[relr_i++]->r_addr = where;
+ where += 8;
+ } else {
+ for (size_t j = 0; (entry >>= 1) != 0; j++) {
+ if ((entry & 1) != 0) {
+ relocs[relr_i++]->r_addr = where + j * 8;
+ }
+ }
+ where += 63 * 8;
}
}
+ }
- for (size_t j = 0; j < relocs_i; j++) {
- struct elf64_rela *relocation = relocs[j];
+ if (rela_offset != 0) {
+ for (uint64_t i = relr_count, offset = 0; offset < rela_size; offset += rela_ent) {
+ relocs[i++] = (void *)elf + (rela_offset + offset);
+ }
+ }
- // Relocation is before buffer
- if (relocation->r_addr < vaddr)
- continue;
+ if (dt_jmprel != 0) {
+ for (uint64_t i = relr_count + rela_size / rela_ent, offset = 0; offset < dt_pltrelsz; offset += rela_ent) {
+ relocs[i++] = (void *)elf + (dt_jmprel + offset);
+ }
+ }
- // Relocation is after buffer
- if (vaddr + size < relocation->r_addr + 8)
- continue;
+ for (size_t i = 0; i < relocs_i; i++) {
+ struct elf64_rela *relocation = relocs[i];
- // It's inside it, calculate where it is
- uint64_t *ptr = (uint64_t *)((buffer - vaddr) + relocation->r_addr);
+ // Relocation is before buffer
+ if (relocation->r_addr < vaddr)
+ continue;
+
+ // Relocation is after buffer
+ if (vaddr + size < relocation->r_addr + 8)
+ continue;
- switch (relocation->r_info) {
+ // It's inside it, calculate where it is
+ uint64_t *ptr = (uint64_t *)((buffer - vaddr) + relocation->r_addr);
+
+ switch (relocation->r_info) {
#if defined (__x86_64__) || defined (__i386__)
- case R_X86_64_NONE:
+ case R_X86_64_NONE:
#elif defined (__aarch64__)
- case R_AARCH64_NONE:
+ case R_AARCH64_NONE:
#elif defined (__riscv64)
- case R_RISCV_NONE:
+ case R_RISCV_NONE:
#endif
- {
- break;
- }
+ {
+ break;
+ }
#if defined (__x86_64__) || defined (__i386__)
- case R_X86_64_RELATIVE:
+ case R_X86_64_RELATIVE:
#elif defined (__aarch64__)
- case R_AARCH64_RELATIVE:
+ case R_AARCH64_RELATIVE:
#elif defined (__riscv64)
- case R_RISCV_RELATIVE:
+ case R_RISCV_RELATIVE:
#endif
- {
- *ptr = slide + relocation->r_addend;
- break;
- }
+ {
+ *ptr = slide + relocation->r_addend;
+ break;
+ }
+ case R_INTERNAL_RELR:
+ {
+ *ptr += slide;
+ break;
+ }
#if defined (__x86_64__) || defined (__i386__)
- case R_X86_64_GLOB_DAT:
- case R_X86_64_JUMP_SLOT:
+ case R_X86_64_GLOB_DAT:
+ case R_X86_64_JUMP_SLOT:
#elif defined (__aarch64__)
- case R_AARCH64_GLOB_DAT:
- case R_AARCH64_JUMP_SLOT:
+ case R_AARCH64_GLOB_DAT:
+ case R_AARCH64_JUMP_SLOT:
#elif defined (__riscv64)
- case R_RISCV_JUMP_SLOT:
+ case R_RISCV_JUMP_SLOT:
#endif
- {
- struct elf64_sym *s = (void *)elf + symtab_offset + symtab_ent * relocation->r_symbol;
- *ptr = slide + s->st_value
+ {
+ struct elf64_sym *s = (void *)elf + symtab_offset + symtab_ent * relocation->r_symbol;
+ *ptr = slide + s->st_value
#if defined (__aarch64__)
- + relocation->r_addend
+ + relocation->r_addend
#endif
- ;
- break;
- }
+ ;
+ break;
+ }
#if defined (__x86_64__) || defined (__i386__)
- case R_X86_64_64:
+ case R_X86_64_64:
#elif defined (__aarch64__)
- case R_AARCH64_ABS64:
+ case R_AARCH64_ABS64:
#elif defined (__riscv64)
- case R_RISCV_64:
+ case R_RISCV_64:
#endif
- {
- struct elf64_sym *s = (void *)elf + symtab_offset + symtab_ent * relocation->r_symbol;
- *ptr = slide + s->st_value + relocation->r_addend;
- break;
- }
- default: {
- print("elf: Unknown RELA type: %x\n", relocation->r_info);
- return false;
- }
+ {
+ struct elf64_sym *s = (void *)elf + symtab_offset + symtab_ent * relocation->r_symbol;
+ *ptr = slide + s->st_value + relocation->r_addend;
+ break;
+ }
+ default: {
+ panic(true, "elf: Unknown relocation type: %x", relocation->r_info);
}
}
-
- pmm_free(relocs, relocs_i * sizeof(struct elf64_rela *));
-
- break;
}
+ pmm_free(relocs, relocs_i * sizeof(struct elf64_rela *));
+
return true;
}
