arm64: Add support for GOT and PCREL32 relocations.

This commit is contained in:
Vladimir Serbinenko 2017-02-01 21:46:19 +01:00
parent 377c121170
commit 34fe0b5901
9 changed files with 226 additions and 54 deletions

View file

@ -25,14 +25,9 @@
#include <grub/i18n.h>
#include <grub/cpu/reloc.h>
struct trampoline
{
#define LDR 0x58000050
#define BR 0xd61f0200
grub_uint32_t ldr; /* ldr x16, 8 */
grub_uint32_t br; /* br x16 */
grub_uint64_t addr;
};
/*
* Check if EHDR is a valid ELF header.
@ -53,42 +48,6 @@ grub_arch_dl_check_header (void *ehdr)
#pragma GCC diagnostic ignored "-Wcast-align"
grub_err_t
grub_arch_dl_get_tramp_got_size (const void *ehdr, grub_size_t *tramp,
grub_size_t *got)
{
const Elf_Ehdr *e = ehdr;
const Elf_Shdr *s;
unsigned i;
*tramp = 0;
*got = 0;
for (i = 0, s = (const Elf_Shdr *) ((grub_addr_t) e + e->e_shoff);
i < e->e_shnum;
i++, s = (const Elf_Shdr *) ((grub_addr_t) s + e->e_shentsize))
if (s->sh_type == SHT_REL || s->sh_type == SHT_RELA)
{
const Elf_Rel *rel, *max;
for (rel = (const Elf_Rel *) ((grub_addr_t) e + s->sh_offset),
max = rel + s->sh_size / s->sh_entsize;
rel < max;
rel = (const Elf_Rel *) ((grub_addr_t) rel + s->sh_entsize))
switch (ELF_R_TYPE (rel->r_info))
{
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
{
*tramp += sizeof (struct trampoline);
break;
}
}
}
return GRUB_ERR_NONE;
}
/*
* Unified function for both REL and RELA
*/
@ -97,6 +56,7 @@ grub_arch_dl_relocate_symbols (grub_dl_t mod, void *ehdr,
Elf_Shdr *s, grub_dl_segment_t seg)
{
Elf_Rel *rel, *max;
unsigned unmatched_adr_got_page = 0;
for (rel = (Elf_Rel *) ((char *) ehdr + s->sh_offset),
max = (Elf_Rel *) ((char *) rel + s->sh_size);
@ -145,7 +105,7 @@ grub_arch_dl_relocate_symbols (grub_dl_t mod, void *ehdr,
if (!grub_arm_64_check_xxxx26_offset (offset))
{
struct trampoline *tp = mod->trampptr;
struct grub_arm64_trampoline *tp = mod->trampptr;
mod->trampptr = tp + 1;
tp->ldr = LDR;
tp->br = BR;
@ -160,6 +120,56 @@ grub_arch_dl_relocate_symbols (grub_dl_t mod, void *ehdr,
grub_arm64_set_xxxx26_offset (place, offset);
}
break;
case R_AARCH64_PREL32:
{
grub_int64_t value;
Elf64_Word *addr32 = place;
value = ((grub_int32_t) *addr32) + sym_addr -
(Elf64_Xword) (grub_addr_t) seg->addr - rel->r_offset;
if (value != (grub_int32_t) value)
return grub_error (GRUB_ERR_BAD_MODULE, "relocation out of range");
grub_dprintf("dl", " reloc_prel32 %p => 0x%016llx\n",
place, (unsigned long long) sym_addr);
*addr32 = value;
}
break;
case R_AARCH64_ADR_GOT_PAGE:
{
grub_uint64_t *gp = mod->gotptr;
Elf_Rela *rel2;
grub_int64_t gpoffset = ((grub_uint64_t) gp & ~0xfffULL) - (((grub_uint64_t) place) & ~0xfffULL);
*gp = (grub_uint64_t) sym_addr;
mod->gotptr = gp + 1;
unmatched_adr_got_page++;
grub_dprintf("dl", " reloc_got %p => 0x%016llx (0x%016llx)\n",
place, (unsigned long long) sym_addr, (unsigned long long) gp);
if (!grub_arm64_check_hi21_signed (gpoffset))
return grub_error (GRUB_ERR_BAD_MODULE,
"HI21 out of range");
grub_arm64_set_hi21(place, gpoffset);
for (rel2 = (Elf_Rela *) ((char *) rel + s->sh_entsize);
rel2 < (Elf_Rela *) max;
rel2 = (Elf_Rela *) ((char *) rel2 + s->sh_entsize))
if (ELF_R_SYM (rel2->r_info)
== ELF_R_SYM (rel->r_info)
&& ((Elf_Rela *) rel)->r_addend == rel2->r_addend
&& ELF_R_TYPE (rel2->r_info) == R_AARCH64_LD64_GOT_LO12_NC)
{
grub_arm64_set_abs_lo12_ldst64 ((void *) ((grub_addr_t) seg->addr + rel2->r_offset),
(grub_uint64_t)gp);
break;
}
if (rel2 >= (Elf_Rela *) max)
return grub_error (GRUB_ERR_BAD_MODULE,
"ADR_GOT_PAGE without matching LD64_GOT_LO12_NC");
}
break;
case R_AARCH64_LD64_GOT_LO12_NC:
if (unmatched_adr_got_page == 0)
return grub_error (GRUB_ERR_BAD_MODULE,
"LD64_GOT_LO12_NC without matching ADR_GOT_PAGE");
unmatched_adr_got_page--;
break;
case R_AARCH64_ADR_PREL_PG_HI21:
{
grub_int64_t offset = (sym_addr & ~0xfffULL) - (((grub_uint64_t) place) & ~0xfffULL);

View file

@ -93,3 +93,42 @@ grub_arm64_set_abs_lo12_ldst64 (grub_uint32_t *place, grub_int64_t target)
*place &= insmask;
*place |= grub_cpu_to_le32 (target << 7) & ~insmask;
}
#pragma GCC diagnostic ignored "-Wcast-align"
grub_err_t
grub_arm64_dl_get_tramp_got_size (const void *ehdr, grub_size_t *tramp,
grub_size_t *got)
{
const Elf64_Ehdr *e = ehdr;
const Elf64_Shdr *s;
unsigned i;
*tramp = 0;
*got = 0;
for (i = 0, s = (Elf64_Shdr *) ((char *) e + grub_le_to_cpu64 (e->e_shoff));
i < grub_le_to_cpu16 (e->e_shnum);
i++, s = (Elf64_Shdr *) ((char *) s + grub_le_to_cpu16 (e->e_shentsize)))
if (s->sh_type == grub_cpu_to_le32_compile_time (SHT_REL)
|| s->sh_type == grub_cpu_to_le32_compile_time (SHT_RELA))
{
const Elf64_Rela *rel, *max;
for (rel = (Elf64_Rela *) ((char *) e + grub_le_to_cpu64 (s->sh_offset)),
max = (const Elf64_Rela *) ((grub_addr_t) rel + grub_le_to_cpu64 (s->sh_size));
rel < max; rel = (const Elf64_Rela *) ((grub_addr_t) rel + grub_le_to_cpu64 (s->sh_entsize)))
switch (ELF64_R_TYPE (rel->r_info))
{
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
*tramp += sizeof (struct grub_arm64_trampoline);
break;
case R_AARCH64_ADR_GOT_PAGE:
*got += 8;
break;
}
}
return GRUB_ERR_NONE;
}

View file

@ -19,6 +19,13 @@
#ifndef GRUB_ARM64_RELOC_H
#define GRUB_ARM64_RELOC_H 1
struct grub_arm64_trampoline
{
grub_uint32_t ldr; /* ldr x16, 8 */
grub_uint32_t br; /* br x16 */
grub_uint64_t addr;
};
int grub_arm_64_check_xxxx26_offset (grub_int64_t offset);
void
grub_arm64_set_xxxx26_offset (grub_uint32_t *place, grub_int64_t offset);

View file

@ -263,11 +263,16 @@ void grub_arch_dl_init_linker (void);
grub_err_t
grub_ia64_dl_get_tramp_got_size (const void *ehdr, grub_size_t *tramp,
grub_size_t *got);
grub_err_t
grub_arm64_dl_get_tramp_got_size (const void *ehdr, grub_size_t *tramp,
grub_size_t *got);
#if defined (__ia64__)
#define GRUB_ARCH_DL_TRAMP_ALIGN GRUB_IA64_DL_TRAMP_ALIGN
#define GRUB_ARCH_DL_GOT_ALIGN GRUB_IA64_DL_GOT_ALIGN
#define grub_arch_dl_get_tramp_got_size grub_ia64_dl_get_tramp_got_size
#elif defined (__aarch64__)
#define grub_arch_dl_get_tramp_got_size grub_arm64_dl_get_tramp_got_size
#else
grub_err_t
grub_arch_dl_get_tramp_got_size (const void *ehdr, grub_size_t *tramp,

View file

@ -2068,11 +2068,14 @@ typedef Elf32_Addr Elf32_Conflict;
#define R_AARCH64_NONE 0 /* No relocation. */
#define R_AARCH64_ABS64 257 /* Direct 64 bit. */
#define R_AARCH64_ABS32 258 /* Direct 32 bit. */
#define R_AARCH64_PREL32 261
#define R_AARCH64_ADR_PREL_PG_HI21 275
#define R_AARCH64_ADD_ABS_LO12_NC 277
#define R_AARCH64_LDST64_ABS_LO12_NC 286
#define R_AARCH64_JUMP26 282 /* 26-bit relative. */
#define R_AARCH64_CALL26 283 /* 26-bit relative. */
#define R_AARCH64_ADR_GOT_PAGE 311
#define R_AARCH64_LD64_GOT_LO12_NC 312
#define R_AARCH64_COPY 1024 /* Copy symbol at runtime. */
#define R_AARCH64_GLOB_DAT 1025 /* Create GOT entry. */
#define R_AARCH64_JUMP_SLOT 1026 /* Create PLT entry. */

View file

@ -30,7 +30,7 @@ struct grub_mkimage_layout
size_t align;
grub_size_t ia64jmp_off;
grub_size_t tramp_off;
grub_size_t ia64_got_off;
grub_size_t got_off;
grub_size_t got_size;
unsigned ia64jmpnum;
grub_uint32_t bss_start;

View file

@ -717,6 +717,7 @@ SUFFIX (relocate_addresses) (Elf_Ehdr *e, Elf_Shdr *sections,
#ifdef MKIMAGE_ELF64
struct grub_ia64_trampoline *tr = (void *) (pe_target + tramp_off);
grub_uint64_t *gpptr = (void *) (pe_target + got_off);
unsigned unmatched_adr_got_page = 0;
#define MASK19 ((1 << 19) - 1)
#else
grub_uint32_t *tr = (void *) (pe_target + tramp_off);
@ -965,6 +966,18 @@ SUFFIX (relocate_addresses) (Elf_Ehdr *e, Elf_Shdr *sections,
*target = grub_host_to_target64 (grub_target_to_host64 (*target) + sym_addr);
}
break;
case R_AARCH64_PREL32:
{
grub_uint32_t *t32 = (grub_uint32_t *) target;
*t32 = grub_host_to_target64 (grub_target_to_host32 (*t32)
+ sym_addr
- target_section_addr - offset
- image_target->vaddr_offset);
grub_util_info ("relocating an R_AARCH64_PREL32 entry to 0x%x at the offset 0x%"
GRUB_HOST_PRIxLONG_LONG,
*t32, (unsigned long long) offset);
break;
}
case R_AARCH64_ADD_ABS_LO12_NC:
grub_arm64_set_abs_lo12 ((grub_uint32_t *) target,
sym_addr);
@ -985,6 +998,41 @@ SUFFIX (relocate_addresses) (Elf_Ehdr *e, Elf_Shdr *sections,
sym_addr);
}
break;
case R_AARCH64_ADR_GOT_PAGE:
{
Elf64_Rela *rel2;
grub_int64_t gpoffset = (((char *) gpptr - (char *) pe_target + image_target->vaddr_offset) & ~0xfffULL)
- ((offset + target_section_addr + image_target->vaddr_offset) & ~0xfffULL);
unsigned k;
*gpptr = grub_host_to_target64 (sym_addr);
unmatched_adr_got_page++;
if (!grub_arm64_check_hi21_signed (gpoffset))
grub_util_error ("HI21 out of range");
grub_arm64_set_hi21((grub_uint32_t *)target,
gpoffset);
for (k = 0, rel2 = (Elf_Rela *) ((char *) r + r_size);
k < num_rs;
k++, rel2 = (Elf_Rela *) ((char *) rel2 + r_size))
if (ELF_R_SYM (rel2->r_info)
== ELF_R_SYM (r->r_info)
&& r->r_addend == rel2->r_addend
&& ELF_R_TYPE (rel2->r_info) == R_AARCH64_LD64_GOT_LO12_NC)
{
grub_arm64_set_abs_lo12_ldst64 ((grub_uint32_t *) SUFFIX (get_target_address) (e, target_section,
grub_target_to_host (rel2->r_offset), image_target),
((char *) gpptr - (char *) pe_target + image_target->vaddr_offset));
break;
}
if (k >= num_rs)
grub_util_error ("ADR_GOT_PAGE without matching LD64_GOT_LO12_NC");
gpptr++;
}
break;
case R_AARCH64_LD64_GOT_LO12_NC:
if (unmatched_adr_got_page == 0)
grub_util_error ("LD64_GOT_LO12_NC without matching ADR_GOT_PAGE");
unmatched_adr_got_page--;
break;
case R_AARCH64_ADR_PREL_PG_HI21:
{
sym_addr &= ~0xfffULL;
@ -1329,6 +1377,7 @@ translate_relocation_pe (struct translate_context *ctx,
/* Relative relocations do not require fixup entries. */
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
case R_AARCH64_PREL32:
break;
/* Page-relative relocations do not require fixup entries. */
case R_AARCH64_ADR_PREL_PG_HI21:
@ -1339,6 +1388,11 @@ translate_relocation_pe (struct translate_context *ctx,
case R_AARCH64_LDST64_ABS_LO12_NC:
break;
/* GOT is relocated separately. */
case R_AARCH64_ADR_GOT_PAGE:
case R_AARCH64_LD64_GOT_LO12_NC:
break;
default:
grub_util_error (_("relocation 0x%x is not implemented yet"),
(unsigned int) ELF_R_TYPE (info));
@ -1547,7 +1601,7 @@ finish_reloc_translation (struct translate_context *ctx, struct grub_mkimage_lay
static void
translate_reloc_jumpers (struct translate_context *ctx,
create_u64_fixups (struct translate_context *ctx,
Elf_Addr jumpers, grub_size_t njumpers,
const struct grub_install_image_target_desc *image_target)
{
@ -1614,10 +1668,16 @@ make_reloc_section (Elf_Ehdr *e, struct grub_mkimage_layout *layout,
}
if (image_target->elf_target == EM_IA_64)
translate_reloc_jumpers (&ctx,
create_u64_fixups (&ctx,
layout->ia64jmp_off
+ image_target->vaddr_offset,
2 * layout->ia64jmpnum + (layout->got_size / 8),
2 * layout->ia64jmpnum,
image_target);
if (image_target->elf_target == EM_IA_64 || image_target->elf_target == EM_AARCH64)
create_u64_fixups (&ctx,
layout->got_off
+ image_target->vaddr_offset,
(layout->got_size / 8),
image_target);
finish_reloc_translation (&ctx, layout, image_target);
@ -1944,7 +2004,18 @@ SUFFIX (grub_mkimage_load_image) (const char *kernel_path,
image_target);
layout->kernel_size += 16 * layout->ia64jmpnum;
layout->ia64_got_off = layout->kernel_size;
layout->got_off = layout->kernel_size;
layout->kernel_size += ALIGN_UP (layout->got_size, 16);
}
if (image_target->elf_target == EM_AARCH64)
{
grub_size_t tramp;
layout->kernel_size = ALIGN_UP (layout->kernel_size, 16);
grub_arm64_dl_get_tramp_got_size (e, &tramp, &layout->got_size);
layout->got_off = layout->kernel_size;
layout->kernel_size += ALIGN_UP (layout->got_size, 16);
}
#endif
@ -1977,7 +2048,7 @@ SUFFIX (grub_mkimage_load_image) (const char *kernel_path,
section_entsize,
num_sections, strtab,
out_img, layout->tramp_off,
layout->ia64_got_off,
layout->got_off,
image_target);
make_reloc_section (e, layout,

View file

@ -107,11 +107,14 @@ struct grub_module_verifier_arch archs[] = {
R_AARCH64_ABS64,
R_AARCH64_CALL26,
R_AARCH64_JUMP26,
R_AARCH64_ADR_GOT_PAGE,
R_AARCH64_LD64_GOT_LO12_NC,
-1
}, (int[]){
R_AARCH64_ADR_PREL_PG_HI21,
R_AARCH64_ADD_ABS_LO12_NC,
R_AARCH64_LDST64_ABS_LO12_NC,
R_AARCH64_PREL32,
-1
}
},

View file

@ -319,6 +319,40 @@ section_check_relocations (const struct grub_module_verifier_arch *arch, void *e
continue;
grub_util_error ("relocation 0x%x is not module-local", type);
}
#if defined(MODULEVERIFIER_ELF64)
if (arch->machine == EM_AARCH64)
{
unsigned unmatched_adr_got_page = 0;
Elf_Rela *rel2;
for (rel = (Elf_Rel *) ((char *) ehdr + grub_target_to_host (s->sh_offset)),
max = (Elf_Rel *) ((char *) rel + grub_target_to_host (s->sh_size));
rel < max;
rel = (Elf_Rel *) ((char *) rel + grub_target_to_host (s->sh_entsize)))
{
switch (ELF_R_TYPE (grub_target_to_host (rel->r_info)))
{
case R_AARCH64_ADR_GOT_PAGE:
unmatched_adr_got_page++;
for (rel2 = (Elf_Rela *) ((char *) rel + grub_target_to_host (s->sh_entsize));
rel2 < (Elf_Rela *) max;
rel2 = (Elf_Rela *) ((char *) rel2 + grub_target_to_host (s->sh_entsize)))
if (ELF_R_SYM (rel2->r_info)
== ELF_R_SYM (rel->r_info)
&& ((Elf_Rela *) rel)->r_addend == rel2->r_addend
&& ELF_R_TYPE (rel2->r_info) == R_AARCH64_LD64_GOT_LO12_NC)
break;
if (rel2 >= (Elf_Rela *) max)
grub_util_error ("ADR_GOT_PAGE without matching LD64_GOT_LO12_NC");
break;
case R_AARCH64_LD64_GOT_LO12_NC:
if (unmatched_adr_got_page == 0)
grub_util_error ("LD64_GOT_LO12_NC without matching ADR_GOT_PAGE");
unmatched_adr_got_page--;
break;
}
}
}
#endif
}
static void