Add R_X86_64_[REX_]GOTPCRELX support to gas and ld
[external/binutils.git] / bfd / elf64-x86-64.c
index dc5fd0d..f8bb0d5 100644 (file)
@@ -31,6 +31,7 @@
 #include "dwarf2.h"
 #include "libiberty.h"
 
+#include "opcode/i386.h"
 #include "elf/x86-64.h"
 
 #ifdef CORE_HEADER
@@ -176,12 +177,18 @@ static reloc_howto_type x86_64_elf_howto_table[] =
   HOWTO(R_X86_64_PLT32_BND, 0, 2, 32, TRUE, 0, complain_overflow_signed,
        bfd_elf_generic_reloc, "R_X86_64_PLT32_BND", FALSE, 0xffffffff, 0xffffffff,
        TRUE),
+  HOWTO(R_X86_64_GOTPCRELX, 0, 2, 32, TRUE, 0, complain_overflow_signed,
+       bfd_elf_generic_reloc, "R_X86_64_GOTPCRELX", FALSE, 0xffffffff,
+       0xffffffff, TRUE),
+  HOWTO(R_X86_64_REX_GOTPCRELX, 0, 2, 32, TRUE, 0, complain_overflow_signed,
+       bfd_elf_generic_reloc, "R_X86_64_REX_GOTPCRELX", FALSE, 0xffffffff,
+       0xffffffff, TRUE),
 
   /* We have a gap in the reloc numbers here.
      R_X86_64_standard counts the number up to this point, and
      R_X86_64_vt_offset is the value to subtract from a reloc type of
      R_X86_64_GNU_VT* to form an index into this table.  */
-#define R_X86_64_standard (R_X86_64_PLT32_BND + 1)
+#define R_X86_64_standard (R_X86_64_REX_GOTPCRELX + 1)
 #define R_X86_64_vt_offset (R_X86_64_GNU_VTINHERIT - R_X86_64_standard)
 
 /* GNU extension to record C++ vtable hierarchy.  */
@@ -253,8 +260,10 @@ static const struct elf_reloc_map x86_64_reloc_map[] =
   { BFD_RELOC_X86_64_TLSDESC_CALL, R_X86_64_TLSDESC_CALL, },
   { BFD_RELOC_X86_64_TLSDESC,  R_X86_64_TLSDESC, },
   { BFD_RELOC_X86_64_IRELATIVE,        R_X86_64_IRELATIVE, },
-  { BFD_RELOC_X86_64_PC32_BND, R_X86_64_PC32_BND,},
-  { BFD_RELOC_X86_64_PLT32_BND,        R_X86_64_PLT32_BND,},
+  { BFD_RELOC_X86_64_PC32_BND, R_X86_64_PC32_BND, },
+  { BFD_RELOC_X86_64_PLT32_BND,        R_X86_64_PLT32_BND, },
+  { BFD_RELOC_X86_64_GOTPCRELX, R_X86_64_GOTPCRELX, },
+  { BFD_RELOC_X86_64_REX_GOTPCRELX, R_X86_64_REX_GOTPCRELX, },
   { BFD_RELOC_VTABLE_INHERIT,  R_X86_64_GNU_VTINHERIT, },
   { BFD_RELOC_VTABLE_ENTRY,    R_X86_64_GNU_VTENTRY, },
 };
@@ -1545,7 +1554,7 @@ elf_x86_64_tls_transition (struct bfd_link_info *info, bfd *abfd,
 
 /* Rename some of the generic section flags to better document how they
    are used here.  */
-#define need_convert_mov_to_lea sec_flg0
+#define need_convert_load sec_flg0
 
 /* Look through the relocs for a section during the first phase, and
    calculate needed space in the global offset table, procedure
@@ -1726,6 +1735,8 @@ elf_x86_64_check_relocs (bfd *abfd, struct bfd_link_info *info,
            case R_X86_64_32S:
            case R_X86_64_PC64:
            case R_X86_64_GOTPCREL:
+           case R_X86_64_GOTPCRELX:
+           case R_X86_64_REX_GOTPCRELX:
            case R_X86_64_GOTPCREL64:
              if (htab->elf.dynobj == NULL)
                htab->elf.dynobj = abfd;
@@ -1779,6 +1790,8 @@ elf_x86_64_check_relocs (bfd *abfd, struct bfd_link_info *info,
 
        case R_X86_64_GOT32:
        case R_X86_64_GOTPCREL:
+       case R_X86_64_GOTPCRELX:
+       case R_X86_64_REX_GOTPCRELX:
        case R_X86_64_TLSGD:
        case R_X86_64_GOT64:
        case R_X86_64_GOTPCREL64:
@@ -2151,9 +2164,11 @@ do_size:
            return FALSE;
        }
 
-      if (r_type == R_X86_64_GOTPCREL
+      if ((r_type == R_X86_64_GOTPCREL
+          || r_type == R_X86_64_GOTPCRELX
+          || r_type == R_X86_64_REX_GOTPCRELX)
          && (h == NULL || h->type != STT_GNU_IFUNC))
-       sec->need_convert_mov_to_lea = 1;
+       sec->need_convert_load = 1;
     }
 
   return TRUE;
@@ -2279,6 +2294,8 @@ elf_x86_64_gc_sweep_hook (bfd *abfd, struct bfd_link_info *info,
        case R_X86_64_GOTTPOFF:
        case R_X86_64_GOT32:
        case R_X86_64_GOTPCREL:
+       case R_X86_64_GOTPCRELX:
+       case R_X86_64_REX_GOTPCRELX:
        case R_X86_64_GOT64:
        case R_X86_64_GOTPCREL64:
        case R_X86_64_GOTPLT64:
@@ -2953,15 +2970,28 @@ elf_x86_64_readonly_dynrelocs (struct elf_link_hash_entry *h,
   return TRUE;
 }
 
-/* Convert
+/* With the local symbol, foo, we convert
    mov foo@GOTPCREL(%rip), %reg
    to
    lea foo(%rip), %reg
-   with the local symbol, foo.  */
+   and convert
+   call/jmp *foo@GOTPCREL(%rip)
+   to
+   nop call foo/jmp foo nop
+   When PIC is false, convert
+   test %reg, foo@GOTPCREL(%rip)
+   to
+   test $foo, %reg
+   and convert
+   binop foo@GOTPCREL(%rip), %reg
+   to
+   binop $foo, %reg
+   where binop is one of adc, add, and, cmp, or, sbb, sub, xor
+   instructions.  */
 
 static bfd_boolean
-elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
-                              struct bfd_link_info *link_info)
+elf_x86_64_convert_load (bfd *abfd, asection *sec,
+                        struct bfd_link_info *link_info)
 {
   Elf_Internal_Shdr *symtab_hdr;
   Elf_Internal_Rela *internal_relocs;
@@ -2979,7 +3009,7 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
 
   /* Nothing to do if there is no need or no output.  */
   if ((sec->flags & (SEC_CODE | SEC_RELOC)) != (SEC_CODE | SEC_RELOC)
-      || sec->need_convert_mov_to_lea == 0
+      || sec->need_convert_load == 0
       || bfd_is_abs_section (sec->output_section))
     return TRUE;
 
@@ -3017,22 +3047,45 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
       asection *tsec;
       char symtype;
       bfd_vma toff, roff;
+      bfd_signed_vma raddend;
       unsigned int opcode;
+      unsigned int modrm;
 
-      if (r_type != R_X86_64_GOTPCREL)
+      if (r_type != R_X86_64_GOTPCREL
+         && r_type != R_X86_64_GOTPCRELX
+         && r_type != R_X86_64_REX_GOTPCRELX)
        continue;
 
       roff = irel->r_offset;
+      if (roff < (r_type == R_X86_64_REX_GOTPCRELX ? 3 : 2))
+       continue;
 
-      if (roff < 2)
+      raddend = irel->r_addend;
+      /* Addend for 32-bit PC-relative relocation must be -4.  */
+      if (raddend != -4)
        continue;
 
       opcode = bfd_get_8 (abfd, contents + roff - 2);
 
-      /* PR ld/18591: Don't convert R_X86_64_GOTPCREL relocation if it
-         isn't for mov instruction.  */
+      /* It is OK to convert mov to lea.  */
       if (opcode != 0x8b)
-       continue;
+       {
+         /* Only convert R_X86_64_GOTPCRELX and R_X86_64_REX_GOTPCRELX
+            for mov call, jmp or one of adc, add, and, cmp, or, sbb,
+            sub, test, xor instructions.  */
+         if (r_type != R_X86_64_GOTPCRELX
+             && r_type != R_X86_64_REX_GOTPCRELX)
+           continue;
+
+         /* It is OK to convert indirect branch to direct branch.  */
+         if (opcode != 0xff)
+           {
+             /* It is OK to convert adc, add, and, cmp, or, sbb, sub,
+                test, xor only when PIC is false.   */
+             if (bfd_link_pic (link_info))
+               continue;
+           }
+       }
 
       /* Get the symbol referred to by the reloc.  */
       if (r_symndx < symtab_hdr->sh_info)
@@ -3044,8 +3097,8 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
 
          symtype = ELF_ST_TYPE (isym->st_info);
 
-         /* STT_GNU_IFUNC must keep R_X86_64_GOTPCREL relocation and
-            skip relocation against undefined symbols.  */
+         /* STT_GNU_IFUNC must keep GOTPCREL relocations and skip
+            relocation against undefined symbols.  */
          if (symtype == STT_GNU_IFUNC || isym->st_shndx == SHN_UNDEF)
            continue;
 
@@ -3071,9 +3124,9 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
                 || h->root.type == bfd_link_hash_warning)
            h = (struct elf_link_hash_entry *) h->root.u.i.link;
 
-         /* STT_GNU_IFUNC must keep R_X86_64_GOTPCREL relocation.  We also
-            avoid optimizing _DYNAMIC since ld.so may use its link-time
-            address.  */
+         /* STT_GNU_IFUNC must keep GOTPCREL relocations.  We also
+            avoid optimizing GOTPCREL relocations againt _DYNAMIC
+            since ld.so may use its link-time address.  */
          if ((h->root.type == bfd_link_hash_defined
               || h->root.type == bfd_link_hash_defweak)
              && h->type != STT_GNU_IFUNC
@@ -3107,17 +3160,17 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
             access is presumed to be an offset from "sym";  The
             location of interest is just "sym".  */
           if (symtype == STT_SECTION)
-            toff += irel->r_addend;
+            toff += raddend;
 
           toff = _bfd_merged_section_offset (abfd, &tsec,
                                              elf_section_data (tsec)->sec_info,
                                              toff);
 
           if (symtype != STT_SECTION)
-            toff += irel->r_addend;
+            toff += raddend;
        }
       else
-       toff += irel->r_addend;
+       toff += raddend;
 
       /* Don't convert if R_X86_64_PC32 relocation overflows.  */
       if (tsec->output_section == sec->output_section)
@@ -3148,8 +3201,8 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
                }
            }
 
-         /* Don't convert R_X86_64_GOTPCREL if TSEC isn't placed after
-            SEC.  */
+         /* Don't convert GOTPCREL relocations if TSEC isn't placed
+            after SEC.  */
          if (asect == NULL)
            continue;
 
@@ -3160,8 +3213,82 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
            continue;
        }
 
-      bfd_put_8 (abfd, 0x8d, contents + roff - 2);
-      irel->r_info = htab->r_info (r_symndx, R_X86_64_PC32);
+      if (opcode == 0xff)
+       {
+         /* We have "call/jmp *foo@GOTPCREL(%rip)".  */
+         unsigned int nop;
+         bfd_vma nop_offset;
+
+         /* Convert R_X86_64_GOTPCRELX and R_X86_64_REX_GOTPCRELX to
+            R_X86_64_PC32.  */
+         modrm = bfd_get_8 (abfd, contents + roff - 1);
+         if (modrm == 0x25)
+           {
+             unsigned int disp;
+             /* Convert to "jmp foo nop".  */
+             modrm = 0xe9;
+             nop = NOP_OPCODE;
+             nop_offset = irel->r_offset + 3;
+             disp = bfd_get_32 (abfd, contents + irel->r_offset);
+             irel->r_offset -= 1;
+             bfd_put_32 (abfd, disp, contents + irel->r_offset);
+           }
+         else
+           {
+             /* Convert to "nop call foo".  ADDR_PREFIX_OPCODE
+                is a nop prefix.  */
+             modrm = 0xe8;
+             nop = ADDR_PREFIX_OPCODE;
+             nop_offset = irel->r_offset - 2;
+           }
+         bfd_put_8 (abfd, nop, contents + nop_offset);
+         bfd_put_8 (abfd, modrm, contents + irel->r_offset - 1);
+         r_type = R_X86_64_PC32;
+       }
+      else
+       {
+         if (opcode == 0x8b)
+           {
+             /* Convert "mov foo@GOTPCREL(%rip), %reg" to
+                "lea foo(%rip), %reg".  */
+             opcode = 0x8d;
+             r_type = R_X86_64_PC32;
+           }
+         else
+           {
+             modrm = bfd_get_8 (abfd, contents + roff - 1);
+             if (opcode == 0x85)
+               {
+                 /* Convert "test %reg, foo@GOTPCREL(%rip)" to
+                    "test $foo, %reg".  */
+                 modrm = 0xc0 | (modrm & 0x38) >> 3;
+                 opcode = 0xf7;
+               }
+             else
+               {
+                 /* Convert "binop foo@GOTPCREL(%rip), %reg" to
+                    "binop $foo, %reg".  */
+                 modrm = 0xc0 | (modrm & 0x38) >> 3 | (opcode & 0x3c);
+                 opcode = 0x81;
+               }
+             bfd_put_8 (abfd, modrm, contents + roff - 1);
+
+             if (r_type == R_X86_64_REX_GOTPCRELX)
+               {
+                 /* Move the R bit to the B bit in REX byte.  */
+                 unsigned int rex = bfd_get_8 (abfd, contents + roff - 3);
+                 rex = (rex & ~REX_R) | (rex & REX_R) >> 2;
+                 bfd_put_8 (abfd, rex, contents + roff - 3);
+               }
+             /* No addend for R_X86_64_32S relocation.  */
+             irel->r_addend = 0;
+             r_type = R_X86_64_32S;
+           }
+
+         bfd_put_8 (abfd, opcode, contents + roff - 2);
+       }
+
+      irel->r_info = htab->r_info (r_symndx, r_type);
       changed_contents = TRUE;
       changed_relocs = TRUE;
 
@@ -3264,7 +3391,7 @@ elf_x86_64_size_dynamic_sections (bfd *output_bfd,
        {
          struct elf_dyn_relocs *p;
 
-         if (!elf_x86_64_convert_mov_to_lea (ibfd, s, info))
+         if (!elf_x86_64_convert_load (ibfd, s, info))
            return FALSE;
 
          for (p = (struct elf_dyn_relocs *)
@@ -3985,6 +4112,8 @@ elf_x86_64_relocate_section (bfd *output_bfd,
              goto do_relocation;
 
            case R_X86_64_GOTPCREL:
+           case R_X86_64_GOTPCRELX:
+           case R_X86_64_REX_GOTPCRELX:
            case R_X86_64_GOTPCREL64:
              base_got = htab->elf.sgot;
              off = h->got.offset;
@@ -4053,6 +4182,8 @@ elf_x86_64_relocate_section (bfd *output_bfd,
          /* Relocation is to the entry for this symbol in the global
             offset table.  */
        case R_X86_64_GOTPCREL:
+       case R_X86_64_GOTPCRELX:
+       case R_X86_64_REX_GOTPCRELX:
        case R_X86_64_GOTPCREL64:
          /* Use global offset table entry as symbol value.  */
        case R_X86_64_GOTPLT64:
@@ -4158,7 +4289,10 @@ elf_x86_64_relocate_section (bfd *output_bfd,
 
          relocation = base_got->output_section->vma
                       + base_got->output_offset + off;
-         if (r_type != R_X86_64_GOTPCREL && r_type != R_X86_64_GOTPCREL64)
+         if (r_type != R_X86_64_GOTPCREL
+             && r_type != R_X86_64_GOTPCRELX
+             && r_type != R_X86_64_REX_GOTPCRELX
+             && r_type != R_X86_64_GOTPCREL64)
            relocation -= htab->elf.sgotplt->output_section->vma
                          - htab->elf.sgotplt->output_offset;