Add R_386_GOT32X support to gas and ld
[deliverable/binutils-gdb.git] / bfd / elf32-i386.c
index 8c1dadb4cf3a1dd1711a3ea53da3f140f418704a..f56b39e838640ab6ca5d369cb062893f46623318 100644 (file)
@@ -29,6 +29,7 @@
 #include "objalloc.h"
 #include "hashtab.h"
 #include "dwarf2.h"
+#include "opcode/i386.h"
 
 /* 386 uses REL relocations instead of RELA.  */
 #define USE_REL        1
@@ -146,9 +147,12 @@ static reloc_howto_type elf_howto_table[]=
   HOWTO(R_386_IRELATIVE, 0, 2, 32, FALSE, 0, complain_overflow_bitfield,
        bfd_elf_generic_reloc, "R_386_IRELATIVE",
        TRUE, 0xffffffff, 0xffffffff, FALSE),
+  HOWTO(R_386_GOT32X, 0, 2, 32, FALSE, 0, complain_overflow_bitfield,
+       bfd_elf_generic_reloc, "R_386_GOT32X",
+       TRUE, 0xffffffff, 0xffffffff, FALSE),
 
   /* Another gap.  */
-#define R_386_ext2 (R_386_IRELATIVE + 1 - R_386_tls_offset)
+#define R_386_ext2 (R_386_GOT32X + 1 - R_386_tls_offset)
 #define R_386_vt_offset (R_386_GNU_VTINHERIT - R_386_ext2)
 
 /* GNU extension to record C++ vtable hierarchy.  */
@@ -332,6 +336,10 @@ elf_i386_reloc_type_lookup (bfd *abfd ATTRIBUTE_UNUSED,
       TRACE ("BFD_RELOC_386_IRELATIVE");
       return &elf_howto_table[R_386_IRELATIVE - R_386_tls_offset];
 
+    case BFD_RELOC_386_GOT32X:
+      TRACE ("BFD_RELOC_386_GOT32X");
+      return &elf_howto_table[R_386_GOT32X - R_386_tls_offset];
+
     case BFD_RELOC_VTABLE_INHERIT:
       TRACE ("BFD_RELOC_VTABLE_INHERIT");
       return &elf_howto_table[R_386_GNU_VTINHERIT - R_386_vt_offset];
@@ -1468,7 +1476,7 @@ elf_i386_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 linkage
@@ -1580,6 +1588,7 @@ elf_i386_check_relocs (bfd *abfd,
            case R_386_PC32:
            case R_386_PLT32:
            case R_386_GOT32:
+           case R_386_GOT32X:
              if (htab->elf.dynobj == NULL)
                htab->elf.dynobj = abfd;
              if (!_bfd_elf_create_ifunc_sections (htab->elf.dynobj, info))
@@ -1637,6 +1646,7 @@ elf_i386_check_relocs (bfd *abfd,
          /* Fall through */
 
        case R_386_GOT32:
+       case R_386_GOT32X:
        case R_386_TLS_GD:
        case R_386_TLS_GOTDESC:
        case R_386_TLS_DESC_CALL:
@@ -1647,7 +1657,10 @@ elf_i386_check_relocs (bfd *abfd,
            switch (r_type)
              {
              default:
-             case R_386_GOT32: tls_type = GOT_NORMAL; break;
+             case R_386_GOT32:
+             case R_386_GOT32X:
+               tls_type = GOT_NORMAL;
+               break;
              case R_386_TLS_GD: tls_type = GOT_TLS_GD; break;
              case R_386_TLS_GOTDESC:
              case R_386_TLS_DESC_CALL:
@@ -1948,9 +1961,9 @@ do_size:
            return FALSE;
        }
 
-      if (r_type == R_386_GOT32
+      if ((r_type == R_386_GOT32 || r_type == R_386_GOT32X)
          && (h == NULL || h->type != STT_GNU_IFUNC))
-       sec->need_convert_mov_to_lea = 1;
+       sec->need_convert_load = 1;
     }
 
   return TRUE;
@@ -2074,6 +2087,7 @@ elf_i386_gc_sweep_hook (bfd *abfd,
        case R_386_TLS_IE:
        case R_386_TLS_GOTIE:
        case R_386_GOT32:
+       case R_386_GOT32X:
          if (h != NULL)
            {
              if (h->got.refcount > 0)
@@ -2718,14 +2732,27 @@ elf_i386_readonly_dynrelocs (struct elf_link_hash_entry *h, void *inf)
   return TRUE;
 }
 
-/* Convert
-   mov foo@GOT(%reg), %reg
+/* With the local symbol, foo, we convert
+   mov foo@GOT[(%reg1)], %reg2
+   to
+   lea foo[@GOTOFF(%reg1)], %reg2
+   and convert
+   call/jmp *foo@GOT[(%reg)]
    to
-   lea foo@GOTOFF(%reg), %reg
-   with the local symbol, foo.  */
+   nop call foo/jmp foo nop
+   When PIC is false, convert
+   test %reg1, foo@GOT[(%reg2)]
+   to
+   test $foo, %reg1
+   and convert
+   binop foo@GOT[(%reg1)], %reg2
+   to
+   binop $foo, %reg2
+   where binop is one of adc, add, and, cmp, or, sbb, sub, xor
+   instructions.  */
 
 static bfd_boolean
-elf_i386_convert_mov_to_lea (bfd *abfd, asection *sec,
+elf_i386_convert_load (bfd *abfd, asection *sec,
                             struct bfd_link_info *link_info)
 {
   Elf_Internal_Shdr *symtab_hdr;
@@ -2743,7 +2770,7 @@ elf_i386_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;
 
@@ -2777,32 +2804,92 @@ elf_i386_convert_mov_to_lea (bfd *abfd, asection *sec,
       unsigned int r_symndx = ELF32_R_SYM (irel->r_info);
       unsigned int indx;
       struct elf_link_hash_entry *h;
+      unsigned int opcode;
+      unsigned int modrm;
+      bfd_vma roff;
+      bfd_boolean baseless;
+      Elf_Internal_Sym *isym;
+      unsigned int addend;
+      unsigned int nop;
+      bfd_vma nop_offset;
 
-      if (r_type != R_386_GOT32)
+      if (r_type != R_386_GOT32 && r_type != R_386_GOT32X)
        continue;
 
-      /* Get the symbol referred to by the reloc.  */
-      if (r_symndx < symtab_hdr->sh_info)
+      roff = irel->r_offset;
+      if (roff < 2)
+       continue;
+
+      modrm = bfd_get_8 (abfd, contents + roff - 1);
+      baseless = (modrm & 0xc7) == 0x5;
+
+      if (r_type == R_386_GOT32X
+         && baseless
+         && bfd_link_pic (link_info))
        {
-         Elf_Internal_Sym *isym;
+         /* For PIC, disallow R_386_GOT32X without a base register
+            since we don't know what the GOT base is.   Allow
+            R_386_GOT32 for existing object files.  */
+         const char *name;
+
+         if (r_symndx < symtab_hdr->sh_info)
+           {
+             isym = bfd_sym_from_r_symndx (&htab->sym_cache, abfd,
+                                           r_symndx);
+             name = bfd_elf_sym_name (abfd, symtab_hdr, isym, NULL);
+           }
+         else
+           {
+             indx = r_symndx - symtab_hdr->sh_info;
+             h = elf_sym_hashes (abfd)[indx];
+             BFD_ASSERT (h != NULL);
+             name = h->root.root.string;
+           }
+
+         (*_bfd_error_handler)
+           (_("%B: direct GOT relocation R_386_GOT32X against `%s' without base register can not be used when making a shared object"),
+            abfd, name);
+         goto error_return;
+       }
+
+      opcode = bfd_get_8 (abfd, contents + roff - 2);
+
+      /* It is OK to convert mov to lea.  */
+      if (opcode != 0x8b)
+       {
+         /* Only convert R_386_GOT32X relocation for call, jmp or
+            one of adc, add, and, cmp, or, sbb, sub, test, xor
+            instructions.  */
+         if (r_type != R_386_GOT32X)
+           continue;
+
+         /* It is OK to convert indirect branch to direct branch.  It
+            is OK to convert adc, add, and, cmp, or, sbb, sub, test,
+            xor only when PIC is false.   */
+         if (opcode != 0xff && bfd_link_pic (link_info))
+           continue;
+       }
 
+      /* Try to convert R_386_GOT32 and R_386_GOT32X.  Get the symbol
+        referred to by the reloc.  */
+      if (r_symndx < symtab_hdr->sh_info)
+       {
          isym = bfd_sym_from_r_symndx (&htab->sym_cache,
                                        abfd, r_symndx);
 
-         /* STT_GNU_IFUNC must keep R_386_GOT32 relocation.  */
-         if (ELF_ST_TYPE (isym->st_info) != STT_GNU_IFUNC
-             && irel->r_offset >= 2
-             && bfd_get_8 (abfd, contents + irel->r_offset - 2) == 0x8b)
-           {
-             bfd_put_8 (abfd, 0x8d, contents + irel->r_offset - 2);
-             irel->r_info = ELF32_R_INFO (r_symndx, R_386_GOTOFF);
-             if (local_got_refcounts != NULL
-                 && local_got_refcounts[r_symndx] > 0)
-               local_got_refcounts[r_symndx] -= 1;
-             changed_contents = TRUE;
-             changed_relocs = TRUE;
-           }
-         continue;
+         /* STT_GNU_IFUNC must keep GOT32 relocations.  */
+         if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+           continue;
+
+         h = NULL;
+         if (opcode == 0x0ff)
+           /* Convert "call/jmp *foo@GOT[(%reg)]".  */
+           goto convert_branch;
+         else
+           /* Convert "mov foo@GOT[(%reg1)], %reg2",
+              "test %reg1, foo@GOT(%reg2)" and
+              "binop foo@GOT[(%reg1)], %reg2". */
+           goto convert_load;
        }
 
       indx = r_symndx - symtab_hdr->sh_info;
@@ -2813,22 +2900,147 @@ elf_i386_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_386_GOT32 relocation.  We also avoid
-        optimizing _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
-         && h != htab->elf.hdynamic
-         && SYMBOL_REFERENCES_LOCAL (link_info, h)
-         && irel->r_offset >= 2
-         && bfd_get_8 (abfd, contents + irel->r_offset - 2) == 0x8b)
+      /* STT_GNU_IFUNC must keep GOT32 relocations.  */
+      if (h->type == STT_GNU_IFUNC)
+       continue;
+
+      if (opcode == 0xff)
+       {
+         /* We have "call/jmp *foo@GOT[(%reg)]".  */
+         if ((h->root.type == bfd_link_hash_defined
+              || h->root.type == bfd_link_hash_defweak)
+             && SYMBOL_REFERENCES_LOCAL (link_info, h))
+           {
+             /* The function is locally defined.   */
+convert_branch:
+             addend = bfd_get_32 (abfd, contents + roff);
+             /* Addend for R_386_GOT32X relocation must be 0.  */
+             if (addend != 0)
+               continue;
+
+             /* Convert R_386_GOT32X to R_386_PC32.  */
+             if (modrm == 0x15 || (modrm & 0xf8) == 0x90)
+               {
+                 /* Convert to "nop call foo".  ADDR_PREFIX_OPCODE
+                    is a nop prefix.  */
+                 modrm = 0xe8;
+                 nop = ADDR_PREFIX_OPCODE;
+                 nop_offset = roff - 2;
+               }
+             else
+               {
+                 /* Convert to "jmp foo nop".  */
+                 modrm = 0xe9;
+                 nop = NOP_OPCODE;
+                 nop_offset = roff + 3;
+                 irel->r_offset -= 1;
+               }
+
+             bfd_put_8 (abfd, nop, contents + nop_offset);
+             bfd_put_8 (abfd, modrm, contents + irel->r_offset - 1);
+             /* When converting to PC-relative relocation, we
+                need to adjust addend by -4.  */
+             bfd_put_32 (abfd, -4, contents + irel->r_offset);
+             irel->r_info = ELF32_R_INFO (r_symndx, R_386_PC32);
+
+             if (h)
+               {
+                 if (h->got.refcount > 0)
+                   h->got.refcount -= 1;
+               }
+             else
+               {
+                 if (local_got_refcounts != NULL
+                     && local_got_refcounts[r_symndx] > 0)
+                   local_got_refcounts[r_symndx] -= 1;
+               }
+
+             changed_contents = TRUE;
+             changed_relocs = TRUE;
+           }
+       }
+      else
        {
-         bfd_put_8 (abfd, 0x8d, contents + irel->r_offset - 2);
-         irel->r_info = ELF32_R_INFO (r_symndx, R_386_GOTOFF);
-         if (h->got.refcount > 0)
-           h->got.refcount -= 1;
-         changed_contents = TRUE;
-         changed_relocs = TRUE;
+         /* We have "mov foo@GOT[(%re1g)], %reg2",
+            "test %reg1, foo@GOT(%reg2)" and
+            "binop foo@GOT[(%reg1)], %reg2".
+
+            Avoid optimizing _DYNAMIC since ld.so may use its
+            link-time address.  */
+         if (h == htab->elf.hdynamic)
+           continue;
+
+         if ((h->root.type == bfd_link_hash_defined
+              || h->root.type == bfd_link_hash_defweak)
+             && SYMBOL_REFERENCES_LOCAL (link_info, h))
+           {
+convert_load:
+             if (opcode == 0x8b)
+               {
+                 /* Convert "mov foo@GOT(%reg1), %reg2" to
+                    "lea foo@GOTOFF(%reg1), %reg2".  */
+                 if (r_type == R_386_GOT32X
+                     && (baseless || !bfd_link_pic (link_info)))
+                   {
+                     r_type = R_386_32;
+                     /* For R_386_32, convert
+                        "lea foo@GOTOFF(%reg1), %reg2" to
+                        "lea foo@GOT, %reg2".  */
+                     if (!baseless)
+                       {
+                         modrm = 0x5 | (modrm & 0x38);
+                         bfd_put_8 (abfd, modrm, contents + roff - 1);
+                       }
+                   }
+                 else
+                   r_type = R_386_GOTOFF;
+                 opcode = 0x8d;
+               }
+             else
+               {
+                 /* Addend for R_386_GOT32X relocation must be 0.  */
+                 addend = bfd_get_32 (abfd, contents + roff);
+                 if (addend != 0)
+                   continue;
+
+                 if (opcode == 0x85)
+                   {
+                     /* Convert "test %reg1, foo@GOT(%reg2)" to
+                        "test $foo, %reg1".  */
+                     modrm = 0xc0 | (modrm & 0x38) >> 3;
+                     opcode = 0xf7;
+                   }
+                 else
+                   {
+                     /* Convert "binop foo@GOT(%reg1), %reg2" to
+                        "binop $foo, %reg2".  */
+                     modrm = (0xc0
+                              | (modrm & 0x38) >> 3
+                              | (opcode & 0x3c));
+                     opcode = 0x81;
+                   }
+                 bfd_put_8 (abfd, modrm, contents + roff - 1);
+                 r_type = R_386_32;
+               }
+
+             bfd_put_8 (abfd, opcode, contents + roff - 2);
+             irel->r_info = ELF32_R_INFO (r_symndx, r_type);
+
+             if (h)
+               {
+                 if (h->got.refcount > 0)
+                   h->got.refcount -= 1;
+               }
+             else
+               {
+                 if (local_got_refcounts != NULL
+                     && local_got_refcounts[r_symndx] > 0)
+                   local_got_refcounts[r_symndx] -= 1;
+               }
+
+             changed_contents = TRUE;
+             changed_relocs = TRUE;
+           }
        }
     }
 
@@ -2914,7 +3126,7 @@ elf_i386_size_dynamic_sections (bfd *output_bfd, struct bfd_link_info *info)
        {
          struct elf_dyn_relocs *p;
 
-         if (!elf_i386_convert_mov_to_lea (ibfd, s, info))
+         if (!elf_i386_convert_load (ibfd, s, info))
            return FALSE;
 
          for (p = ((struct elf_dyn_relocs *)
@@ -3664,6 +3876,7 @@ elf_i386_relocate_section (bfd *output_bfd,
              goto do_relocation;
 
            case R_386_GOT32:
+           case R_386_GOT32X:
              base_got = htab->elf.sgot;
              off = h->got.offset;
 
@@ -3741,7 +3954,61 @@ elf_i386_relocate_section (bfd *output_bfd,
       eh = (struct elf_i386_link_hash_entry *) h;
       switch (r_type)
        {
+       case R_386_GOT32X:
+         /* Avoid optimizing _DYNAMIC since ld.so may use its
+            link-time address.  */
+         if (h == htab->elf.hdynamic)
+           goto r_386_got32;
+
+         if (bfd_link_pic (info))
+           {
+             /* It is OK to convert mov to lea and convert indirect
+                branch to direct branch.  It is OK to convert adc,
+                add, and, cmp, or, sbb, sub, test, xor only when PIC
+                is false.   */
+             unsigned int opcode;
+             opcode = bfd_get_8 (abfd, contents + rel->r_offset - 2);
+             if (opcode != 0x8b && opcode != 0xff)
+               goto r_386_got32;
+           }
+
+         /* Resolve "mov GOT[(%reg)], %reg",
+            "call/jmp *GOT[(%reg)]", "test %reg, foo@GOT[(%reg)]"
+            and "binop foo@GOT[(%reg)], %reg".  */
+         if (h == NULL
+             || (h->plt.offset == (bfd_vma) -1
+                 && h->got.offset == (bfd_vma) -1)
+             || htab->elf.sgotplt == NULL)
+           abort ();
+
+         offplt = (htab->elf.sgotplt->output_section->vma
+                   + htab->elf.sgotplt->output_offset);
+
+         /* It is relative to .got.plt section.  */
+         if (h->got.offset != (bfd_vma) -1)
+           /* Use GOT entry.  */
+           relocation = (htab->elf.sgot->output_section->vma
+                         + htab->elf.sgot->output_offset
+                         + h->got.offset - offplt);
+         else
+           /* Use GOTPLT entry.  */
+           relocation = (h->plt.offset / plt_entry_size - 1 + 3) * 4;
+
+         if (!bfd_link_pic (info))
+           {
+             /* If not PIC, add the .got.plt section address for
+                baseless adressing.  */
+             unsigned int modrm;
+             modrm = bfd_get_8 (abfd, contents + rel->r_offset - 1);
+             if ((modrm & 0xc7) == 0x5)
+               relocation += offplt;
+           }
+
+         unresolved_reloc = FALSE;
+         break;
+
        case R_386_GOT32:
+r_386_got32:
          /* Relocation is to the entry for this symbol in the global
             offset table.  */
          if (htab->elf.sgot == NULL)
This page took 0.032233 seconds and 4 git commands to generate.