Edit .eh_frame symbols
authorAlan Modra <amodra@gmail.com>
Thu, 27 Apr 2017 01:38:14 +0000 (11:08 +0930)
committerAlan Modra <amodra@gmail.com>
Thu, 27 Apr 2017 02:24:29 +0000 (11:54 +0930)
Experimental support for moving symbols defined in .eh_frame as their
CIEs/FDEs are edited or merged.

* elf-bfd.h (struct eh_cie_fde): Add aug_str_len and aug_data_len.
(_bfd_elf_adjust_eh_frame_global_symbol): Declare.
* elf-eh-frame.c (_bfd_elf_parse_eh_frame): Set aug_str_len and
aug_data_len.
(offset_adjust): New function.
(_bfd_elf_adjust_eh_frame_global_symbol): Likewise.
(adjust_eh_frame_local_symbols): Likewise.
(_bfd_elf_discard_section_eh_frame): Call adjust_eh_frame_local_symbols
after changing anything.  Return true if anything changed.
* elflink.c (bfd_elf_discard_info): If .eh_frame changed, call
_bfd_elf_adjust_eh_frame_global_symbol for globals.

bfd/ChangeLog
bfd/elf-bfd.h
bfd/elf-eh-frame.c
bfd/elflink.c

index c870cc712a616beb6010a1980e4409fb59fbe51c..0f08ddd1e3e9108130e7d1242217d5881163d640 100644 (file)
@@ -1,3 +1,17 @@
+2017-04-27  Alan Modra  <amodra@gmail.com>
+
+       * elf-bfd.h (struct eh_cie_fde): Add aug_str_len and aug_data_len.
+       (_bfd_elf_adjust_eh_frame_global_symbol): Declare.
+       * elf-eh-frame.c (_bfd_elf_parse_eh_frame): Set aug_str_len and
+       aug_data_len.
+       (offset_adjust): New function.
+       (_bfd_elf_adjust_eh_frame_global_symbol): Likewise.
+       (adjust_eh_frame_local_symbols): Likewise.
+       (_bfd_elf_discard_section_eh_frame): Call adjust_eh_frame_local_symbols
+       after changing anything.  Return true if anything changed.
+       * elflink.c (bfd_elf_discard_info): If .eh_frame changed, call
+       _bfd_elf_adjust_eh_frame_global_symbol for globals.
+
 2017-04-27  Alan Modra  <amodra@gmail.com>
 
        * elflink.c (_bfd_elf_link_hash_hide_symbol): Clear dynstr_index
index 2649939d6a5bed8445fbd42cbe09f07c9cdd0edc..bff6abc6691776a4d6e737b13ca1ad17efa589ad 100644 (file)
@@ -326,6 +326,12 @@ struct eh_cie_fde
         or 0 if the CIE doesn't have any.  */
       unsigned int personality_offset : 8;
 
+      /* Length of augmentation.  aug_str_len is the length of the
+        string including null terminator.  aug_data_len is the length
+        of the rest up to the initial insns.  */
+      unsigned int aug_str_len : 3;
+      unsigned int aug_data_len : 5;
+
       /* True if we have marked relocations associated with this CIE.  */
       unsigned int gc_mark : 1;
 
@@ -354,7 +360,7 @@ struct eh_cie_fde
       unsigned int merged : 1;
 
       /* Unused bits.  */
-      unsigned int pad1 : 17;
+      unsigned int pad1 : 9;
     } cie;
   } u;
   unsigned int reloc_index;
@@ -2180,6 +2186,8 @@ extern bfd_boolean _bfd_elf_end_eh_frame_parsing
 extern bfd_boolean _bfd_elf_discard_section_eh_frame
   (bfd *, struct bfd_link_info *, asection *,
    bfd_boolean (*) (bfd_vma, void *), struct elf_reloc_cookie *);
+extern bfd_boolean _bfd_elf_adjust_eh_frame_global_symbol
+  (struct elf_link_hash_entry *, void *);
 extern bfd_boolean _bfd_elf_discard_section_eh_frame_hdr
   (bfd *, struct bfd_link_info *);
 extern bfd_vma _bfd_elf_eh_frame_section_offset
index 1743823891541545a5e380536c56cd25ac7ded90..1af6f30908313603b372c43d6be0626481040225 100644 (file)
@@ -757,6 +757,7 @@ _bfd_elf_parse_eh_frame (bfd *abfd, struct bfd_link_info *info,
 
          strcpy (cie->augmentation, (char *) buf);
          buf = (bfd_byte *) strchr ((char *) buf, '\0') + 1;
+         this_inf->u.cie.aug_str_len = buf - start - 1;
          ENSURE_NO_RELOCS (buf);
          if (buf[0] == 'e' && buf[1] == 'h')
            {
@@ -845,6 +846,8 @@ _bfd_elf_parse_eh_frame (bfd *abfd, struct bfd_link_info *info,
                    goto free_no_table;
                  }
            }
+         this_inf->u.cie.aug_data_len
+           = buf - start - 1 - this_inf->u.cie.aug_str_len;
 
          /* For shared libraries, try to get rid of as many RELATIVE relocs
             as possible.  */
@@ -1327,6 +1330,143 @@ find_merged_cie (bfd *abfd, struct bfd_link_info *info, asection *sec,
   return new_cie->cie_inf;
 }
 
+/* For a given OFFSET in SEC, return the delta to the new location
+   after .eh_frame editing.  */
+
+static bfd_signed_vma
+offset_adjust (bfd_vma offset, asection *sec)
+{
+  struct eh_frame_sec_info *sec_info
+    = (struct eh_frame_sec_info *) elf_section_data (sec)->sec_info;
+  unsigned int lo, hi, mid;
+  struct eh_cie_fde *ent;
+  bfd_signed_vma delta;
+
+  lo = 0;
+  hi = sec_info->count;
+  if (hi == 0)
+    return 0;
+
+  while (lo < hi)
+    {
+      mid = (lo + hi) / 2;
+      ent = &sec_info->entry[mid];
+      if (offset < ent->offset)
+       hi = mid;
+      else if (mid + 1 >= hi)
+       break;
+      else if (offset >= ent[1].offset)
+       lo = mid + 1;
+      else
+       break;
+    }
+
+  if (!ent->removed)
+    delta = (bfd_vma) ent->new_offset - (bfd_vma) ent->offset;
+  else if (ent->cie && ent->u.cie.merged)
+    {
+      struct eh_cie_fde *cie = ent->u.cie.u.merged_with;
+      delta = ((bfd_vma) cie->new_offset + cie->u.cie.u.sec->output_offset
+              - (bfd_vma) ent->offset - sec->output_offset);
+    }
+  else
+    {
+      /* Is putting the symbol on the next entry best for a deleted
+        CIE/FDE?  */
+      struct eh_cie_fde *last = sec_info->entry + sec_info->count;
+      delta = ((bfd_vma) next_cie_fde_offset (ent, last, sec)
+              - (bfd_vma) ent->offset);
+      return delta;
+    }
+
+  /* Account for editing within this CIE/FDE.  */
+  offset -= ent->offset;
+  if (ent->cie)
+    {
+      unsigned int extra
+       = ent->add_augmentation_size + ent->u.cie.add_fde_encoding;
+      if (extra == 0
+         || offset <= 9u + ent->u.cie.aug_str_len)
+       return delta;
+      delta += extra;
+      if (offset <= 9u + ent->u.cie.aug_str_len + ent->u.cie.aug_data_len)
+       return delta;
+      delta += extra;
+    }
+  else
+    {
+      unsigned int ptr_size, width, extra = ent->add_augmentation_size;
+      if (offset <= 12 || extra == 0)
+       return delta;
+      ptr_size = (get_elf_backend_data (sec->owner)
+                 ->elf_backend_eh_frame_address_size (sec->owner, sec));
+      width = get_DW_EH_PE_width (ent->fde_encoding, ptr_size);
+      if (offset <= 8 + 2 * width)
+       return delta;
+      delta += extra;
+    }
+
+  return delta;
+}
+
+/* Adjust a global symbol defined in .eh_frame, so that it stays
+   relative to its original CIE/FDE.  It is assumed that a symbol
+   defined at the beginning of a CIE/FDE belongs to that CIE/FDE
+   rather than marking the end of the previous CIE/FDE.  This matters
+   when a CIE is merged with a previous CIE, since the symbol is
+   moved to the merged CIE.  */
+
+bfd_boolean
+_bfd_elf_adjust_eh_frame_global_symbol (struct elf_link_hash_entry *h,
+                                       void *arg ATTRIBUTE_UNUSED)
+{
+  asection *sym_sec;
+  bfd_signed_vma delta;
+
+  if (h->root.type != bfd_link_hash_defined
+      && h->root.type != bfd_link_hash_defweak)
+    return TRUE;
+
+  sym_sec = h->root.u.def.section;
+  if (sym_sec->sec_info_type != SEC_INFO_TYPE_EH_FRAME
+      || elf_section_data (sym_sec)->sec_info == NULL)
+    return TRUE;
+
+  delta = offset_adjust (h->root.u.def.value, sym_sec);
+  h->root.u.def.value += delta;
+
+  return TRUE;
+}
+
+/* The same for all local symbols defined in .eh_frame.  Returns true
+   if any symbol was changed.  */
+
+static int
+adjust_eh_frame_local_symbols (asection *sec,
+                              struct elf_reloc_cookie *cookie)
+{
+  unsigned int shndx;
+  Elf_Internal_Sym *sym;
+  Elf_Internal_Sym *end_sym;
+  int adjusted = 0;
+
+  shndx = elf_section_data (sec)->this_idx;
+  end_sym = cookie->locsyms + cookie->locsymcount;
+  for (sym = cookie->locsyms + 1; sym < end_sym; ++sym)
+    if (sym->st_info <= ELF_ST_INFO (STB_LOCAL, STT_OBJECT)
+       && sym->st_shndx == shndx)
+      {
+       bfd_signed_vma delta = offset_adjust (sym->st_value, sec);
+
+       if (delta != 0)
+         {
+           adjusted = 1;
+           sym->st_value += delta;
+         }
+      }
+  return adjusted;
+}
+
 /* This function is called for each input file before the .eh_frame
    section is relocated.  It discards duplicate CIEs and FDEs for discarded
    functions.  The function returns TRUE iff any entries have been
@@ -1342,6 +1482,7 @@ _bfd_elf_discard_section_eh_frame
   struct eh_frame_sec_info *sec_info;
   struct eh_frame_hdr_info *hdr_info;
   unsigned int ptr_size, offset, eh_alignment;
+  int changed;
 
   if (sec->sec_info_type != SEC_INFO_TYPE_EH_FRAME)
     return FALSE;
@@ -1428,6 +1569,7 @@ _bfd_elf_discard_section_eh_frame
      last FDE instead.  For other FDEs we align according to their
      encoding, in order to align FDE address range entries naturally.  */
   offset = 0;
+  changed = 0;
   for (ent = sec_info->entry; ent < sec_info->entry + sec_info->count; ++ent)
     if (!ent->removed)
       {
@@ -1447,6 +1589,8 @@ _bfd_elf_discard_section_eh_frame
          }
        offset = (offset + eh_alignment - 1) & -eh_alignment;
        ent->new_offset = offset;
+       if (ent->new_offset != ent->offset)
+         changed = 1;
        offset += size_of_output_cie_fde (ent);
       }
 
@@ -1463,7 +1607,15 @@ _bfd_elf_discard_section_eh_frame
   offset = (offset + eh_alignment - 1) & -eh_alignment;
   sec->rawsize = sec->size;
   sec->size = offset;
-  return offset != sec->rawsize;
+  if (sec->size != sec->rawsize)
+    changed = 1;
+
+  if (changed && adjust_eh_frame_local_symbols (sec, cookie))
+    {
+      Elf_Internal_Shdr *symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+      symtab_hdr->contents = (unsigned char *) cookie->locsyms;
+    }
+  return changed;
 }
 
 /* This function is called for .eh_frame_hdr section after
index 59300b7cde71d38a4de9a0b6116c9656d893d26d..4af4b1cd3d33c0b566349d545038accee4ffd51a 100644 (file)
@@ -13807,6 +13807,7 @@ bfd_elf_discard_info (bfd *output_bfd, struct bfd_link_info *info)
   if (o != NULL)
     {
       asection *i;
+      int eh_changed = 0;
 
       for (i = o->map_head.s; i != NULL; i = i->map_head.s)
        {
@@ -13824,10 +13825,17 @@ bfd_elf_discard_info (bfd *output_bfd, struct bfd_link_info *info)
          if (_bfd_elf_discard_section_eh_frame (abfd, info, i,
                                                 bfd_elf_reloc_symbol_deleted_p,
                                                 &cookie))
-           changed = 1;
+           {
+             eh_changed = 1;
+             if (i->size != i->rawsize)
+               changed = 1;
+           }
 
          fini_reloc_cookie_for_section (&cookie, i);
        }
+      if (eh_changed)
+       elf_link_hash_traverse (elf_hash_table (info),
+                               _bfd_elf_adjust_eh_frame_global_symbol, NULL);
     }
 
   for (abfd = info->input_bfds; abfd != NULL; abfd = abfd->link.next)
This page took 0.032079 seconds and 4 git commands to generate.