Don't convert R_X86_64_GOTPCREL if it will overflow
authorH.J. Lu <hjl.tools@gmail.com>
Wed, 24 Jun 2015 17:13:55 +0000 (10:13 -0700)
committerH.J. Lu <hjl.tools@gmail.com>
Wed, 24 Jun 2015 17:20:41 +0000 (10:20 -0700)
When converting "mov foo@GOTPCREL(%rip), %reg" to "lea foo(%rip), %reg"
with R_X86_64_PC32 relocation, it may overflow if the target section
is more than 2GB away.  This patch estimates distance between mov
instruction and the target section.  We convert R_X86_64_GOTPCREL to
R_X86_64_PC32 only if their distance is less than 2GB.

PR ld/18591
* elf64-x86-64.c (elf_x86_64_convert_mov_to_lea): Don't convert
R_X86_64_GOTPCREL to R_X86_64_PC32 if it will cause relocation
overflow.

bfd/ChangeLog
bfd/elf64-x86-64.c

index 157e7f7..8918851 100644 (file)
@@ -1,3 +1,10 @@
+2015-06-24  H.J. Lu  <hongjiu.lu@intel.com>
+
+       PR ld/18591
+       * elf64-x86-64.c (elf_x86_64_convert_mov_to_lea): Don't convert
+       R_X86_64_GOTPCREL to R_X86_64_PC32 if it will cause relocation
+       overflow.
+
 2015-06-23  Jiong Wang  <jiong.wang@arm.com>
 
        * elfnn-aarch64.c (aarch64_readonly_dynrelocs): New function.
index 072c00b..f7aea98 100644 (file)
@@ -2901,6 +2901,7 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
   bfd_boolean changed_contents;
   bfd_boolean changed_relocs;
   bfd_signed_vma *local_got_refcounts;
+  bfd_vma maxpagesize;
 
   /* Don't even try to convert non-ELF outputs.  */
   if (!is_elf_hash_table (link_info->hash))
@@ -2925,6 +2926,7 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
   changed_contents = FALSE;
   changed_relocs = FALSE;
   local_got_refcounts = elf_local_got_refcounts (abfd);
+  maxpagesize = get_elf_backend_data (abfd)->maxpagesize;
 
   /* Get the section contents.  */
   if (elf_section_data (sec)->this_hdr.contents != NULL)
@@ -2942,10 +2944,27 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
       unsigned int r_symndx = htab->r_sym (irel->r_info);
       unsigned int indx;
       struct elf_link_hash_entry *h;
+      asection *tsec;
+      char symtype;
+      bfd_vma toff, roff;
+      enum {
+       none, local, global
+      } convert_mov_to_lea;
 
       if (r_type != R_X86_64_GOTPCREL)
        continue;
 
+      roff = irel->r_offset;
+
+      /* Don't convert R_X86_64_GOTPCREL relocation if it isn't for mov
+        instruction.  */
+      if (roff < 2
+         || bfd_get_8 (abfd, contents + roff - 2) != 0x8b)
+       continue;
+
+      tsec = NULL;
+      convert_mov_to_lea = none;
+
       /* Get the symbol referred to by the reloc.  */
       if (r_symndx < symtab_hdr->sh_info)
        {
@@ -2954,46 +2973,140 @@ elf_x86_64_convert_mov_to_lea (bfd *abfd, asection *sec,
          isym = bfd_sym_from_r_symndx (&htab->sym_cache,
                                        abfd, r_symndx);
 
-         /* STT_GNU_IFUNC must keep R_X86_64_GOTPCREL 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)
+         symtype = ELF_ST_TYPE (isym->st_info);
+
+         /* STT_GNU_IFUNC must keep R_X86_64_GOTPCREL relocation and
+            skip relocation against undefined symbols.  */
+         if (symtype != STT_GNU_IFUNC && isym->st_shndx != SHN_UNDEF)
            {
-             bfd_put_8 (abfd, 0x8d, contents + irel->r_offset - 2);
-             irel->r_info = htab->r_info (r_symndx, R_X86_64_PC32);
-             if (local_got_refcounts != NULL
-                 && local_got_refcounts[r_symndx] > 0)
-               local_got_refcounts[r_symndx] -= 1;
-             changed_contents = TRUE;
-             changed_relocs = TRUE;
+             if (isym->st_shndx == SHN_ABS)
+               tsec = bfd_abs_section_ptr;
+             else if (isym->st_shndx == SHN_COMMON)
+               tsec = bfd_com_section_ptr;
+             else if (isym->st_shndx == SHN_X86_64_LCOMMON)
+               tsec = &_bfd_elf_large_com_section;
+             else
+               tsec = bfd_section_from_elf_index (abfd, isym->st_shndx);
+
+             toff = isym->st_value;
+             convert_mov_to_lea = local;
            }
-         continue;
        }
+      else
+       {
+         indx = r_symndx - symtab_hdr->sh_info;
+         h = elf_sym_hashes (abfd)[indx];
+         BFD_ASSERT (h != NULL);
 
-      indx = r_symndx - symtab_hdr->sh_info;
-      h = elf_sym_hashes (abfd)[indx];
-      BFD_ASSERT (h != NULL);
+         while (h->root.type == bfd_link_hash_indirect
+                || h->root.type == bfd_link_hash_warning)
+           h = (struct elf_link_hash_entry *) h->root.u.i.link;
 
-      while (h->root.type == bfd_link_hash_indirect
-            || 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.  */
+         if (h->def_regular
+             && h->type != STT_GNU_IFUNC
+             && h != htab->elf.hdynamic
+             && SYMBOL_REFERENCES_LOCAL (link_info, h))
+           {
+             tsec = h->root.u.def.section;
+             toff = h->root.u.def.value;
+             symtype = h->type;
+             convert_mov_to_lea = global;
+           }
+       }
 
-      /* STT_GNU_IFUNC must keep R_X86_64_GOTPCREL relocation.  We also
-        avoid optimizing _DYNAMIC since ld.so may use its link-time
-        address.  */
-      if (h->def_regular
-         && 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)
+      if (convert_mov_to_lea == none)
+       continue;
+
+      if (tsec->sec_info_type == SEC_INFO_TYPE_MERGE)
+       {
+         /* At this stage in linking, no SEC_MERGE symbol has been
+            adjusted, so all references to such symbols need to be
+            passed through _bfd_merged_section_offset.  (Later, in
+            relocate_section, all SEC_MERGE symbols *except* for
+            section symbols have been adjusted.)
+
+            gas may reduce relocations against symbols in SEC_MERGE
+            sections to a relocation against the section symbol when
+            the original addend was zero.  When the reloc is against
+            a section symbol we should include the addend in the
+            offset passed to _bfd_merged_section_offset, since the
+            location of interest is the original symbol.  On the
+            other hand, an access to "sym+addend" where "sym" is not
+            a section symbol should not include the addend;  Such an
+            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 = _bfd_merged_section_offset (abfd, &tsec,
+                                             elf_section_data (tsec)->sec_info,
+                                             toff);
+
+          if (symtype != STT_SECTION)
+            toff += irel->r_addend;
+       }
+      else
+       toff += irel->r_addend;
+
+      /* Don't convert if R_X86_64_PC32 relocation overflows.  */
+      if (tsec->output_section == sec->output_section)
+       {
+         if ((toff - roff + 0x80000000) > 0xffffffff)
+           continue;
+       }
+      else
+       {
+         asection *asect;
+         bfd_size_type size;
+
+         /* At this point, we don't know the load addresses of TSEC
+            section nor SEC section.  We estimate the distrance between
+            SEC and TSEC.  */
+         size = 0;
+         for (asect = sec->output_section;
+              asect != NULL && asect != tsec->output_section;
+              asect = asect->next)
+           {
+             asection *i;
+             for (i = asect->output_section->map_head.s;
+                  i != NULL;
+                  i = i->map_head.s)
+               {
+                 size = align_power (size, i->alignment_power);
+                 size += i->size;
+               }
+           }
+
+         /* Don't convert R_X86_64_GOTPCREL if TSEC isn't placed after
+            SEC.  */
+         if (asect == NULL)
+           continue;
+
+         /* Take PT_GNU_RELRO segment into account by adding
+            maxpagesize.  */
+         if ((toff + size + maxpagesize - roff + 0x80000000)
+             > 0xffffffff)
+           continue;
+       }
+
+      bfd_put_8 (abfd, 0x8d, contents + roff - 2);
+      irel->r_info = htab->r_info (r_symndx, R_X86_64_PC32);
+      changed_contents = TRUE;
+      changed_relocs = TRUE;
+
+      if (convert_mov_to_lea == local)
+       {
+         if (local_got_refcounts != NULL
+             && local_got_refcounts[r_symndx] > 0)
+           local_got_refcounts[r_symndx] -= 1;
+       }
+      else
        {
-         bfd_put_8 (abfd, 0x8d, contents + irel->r_offset - 2);
-         irel->r_info = htab->r_info (r_symndx, R_X86_64_PC32);
          if (h->got.refcount > 0)
            h->got.refcount -= 1;
-         changed_contents = TRUE;
-         changed_relocs = TRUE;
        }
     }