arm64: Add support for GOT and PCREL32 relocations.
authorVladimir Serbinenko <phcoder@gmail.com>
Wed, 1 Feb 2017 20:46:19 +0000 (21:46 +0100)
committerVladimir Serbinenko <phcoder@gmail.com>
Wed, 1 Feb 2017 20:46:19 +0000 (21:46 +0100)
grub-core/kern/arm64/dl.c
grub-core/kern/arm64/dl_helper.c
include/grub/arm64/reloc.h
include/grub/dl.h
include/grub/elf.h
include/grub/util/mkimage.h
util/grub-mkimagexx.c
util/grub-module-verifier.c
util/grub-module-verifierXX.c

index cf50d72..fb03373 100644 (file)
 #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);
index f031b1a..4af3c4a 100644 (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;
+}
index 452c148..c8765de 100644 (file)
 #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);
index 9562fa6..2bca56c 100644 (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,
index db15ace..c8492f9 100644 (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.  */
index 2a48942..1a18708 100644 (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;
index 59b6bd2..32be4bf 100644 (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,9 +1601,9 @@ finish_reloc_translation (struct translate_context *ctx, struct grub_mkimage_lay
 
 
 static void
-translate_reloc_jumpers (struct translate_context *ctx,
-                        Elf_Addr jumpers, grub_size_t njumpers,
-                        const struct grub_install_image_target_desc *image_target)
+create_u64_fixups (struct translate_context *ctx,
+                  Elf_Addr jumpers, grub_size_t njumpers,
+                  const struct grub_install_image_target_desc *image_target)
 {
   unsigned i;
   assert (image_target->id == IMAGE_EFI);
@@ -1614,11 +1668,17 @@ make_reloc_section (Elf_Ehdr *e, struct grub_mkimage_layout *layout,
       }
 
   if (image_target->elf_target == EM_IA_64)
-    translate_reloc_jumpers (&ctx,
-                            layout->ia64jmp_off
-                            + image_target->vaddr_offset,
-                            2 * layout->ia64jmpnum + (layout->got_size / 8),
-                            image_target);
+    create_u64_fixups (&ctx,
+                      layout->ia64jmp_off
+                      + image_target->vaddr_offset,
+                      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,
index dd72d78..d0cf817 100644 (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
     }
   },
index 2c0c690..1feaafc 100644 (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