This is the mail archive of the
binutils@sourceware.org
mailing list for the binutils project.
Edit .eh_frame symbols
- From: Alan Modra <amodra at gmail dot com>
- To: binutils at sourceware dot org
- Date: Thu, 27 Apr 2017 11:53:45 +0930
- Subject: Edit .eh_frame symbols
- Authentication-results: sourceware.org; auth=none
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.
diff --git a/bfd/ChangeLog b/bfd/ChangeLog
index c870cc7..0f08ddd 100644
--- a/bfd/ChangeLog
+++ b/bfd/ChangeLog
@@ -1,5 +1,19 @@
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
when force_local.
diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
index 2649939..bff6abc 100644
--- a/bfd/elf-bfd.h
+++ b/bfd/elf-bfd.h
@@ -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
diff --git a/bfd/elf-eh-frame.c b/bfd/elf-eh-frame.c
index 1743823..1af6f30 100644
--- a/bfd/elf-eh-frame.c
+++ b/bfd/elf-eh-frame.c
@@ -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
diff --git a/bfd/elflink.c b/bfd/elflink.c
index 59300b7..4af4b1c 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -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)
--
Alan Modra
Australia Development Lab, IBM