| /* .eh_frame section optimization. |
| Copyright (C) 2001-2024 Free Software Foundation, Inc. |
| Written by Jakub Jelinek <jakub@redhat.com>. |
| |
| This file is part of BFD, the Binary File Descriptor library. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, |
| MA 02110-1301, USA. */ |
| |
| #include "sysdep.h" |
| #include "bfd.h" |
| #include "libbfd.h" |
| #include "elf-bfd.h" |
| #include "dwarf2.h" |
| |
| #define EH_FRAME_HDR_SIZE 8 |
| |
| struct cie |
| { |
| unsigned int length; |
| unsigned int hash; |
| unsigned char version; |
| unsigned char local_personality; |
| char augmentation[20]; |
| bfd_vma code_align; |
| bfd_signed_vma data_align; |
| bfd_vma ra_column; |
| bfd_vma augmentation_size; |
| union { |
| struct elf_link_hash_entry *h; |
| struct { |
| unsigned int bfd_id; |
| unsigned int index; |
| } sym; |
| unsigned int reloc_index; |
| } personality; |
| struct eh_cie_fde *cie_inf; |
| unsigned char per_encoding; |
| unsigned char lsda_encoding; |
| unsigned char fde_encoding; |
| unsigned char initial_insn_length; |
| unsigned char can_make_lsda_relative; |
| unsigned char initial_instructions[50]; |
| }; |
| |
| |
| |
| /* If *ITER hasn't reached END yet, read the next byte into *RESULT and |
| move onto the next byte. Return true on success. */ |
| |
| static inline bool |
| read_byte (bfd_byte **iter, bfd_byte *end, unsigned char *result) |
| { |
| if (*iter >= end) |
| return false; |
| *result = *((*iter)++); |
| return true; |
| } |
| |
| /* Move *ITER over LENGTH bytes, or up to END, whichever is closer. |
| Return true it was possible to move LENGTH bytes. */ |
| |
| static inline bool |
| skip_bytes (bfd_byte **iter, bfd_byte *end, bfd_size_type length) |
| { |
| if ((bfd_size_type) (end - *iter) < length) |
| { |
| *iter = end; |
| return false; |
| } |
| *iter += length; |
| return true; |
| } |
| |
| /* Move *ITER over an leb128, stopping at END. Return true if the end |
| of the leb128 was found. */ |
| |
| static bool |
| skip_leb128 (bfd_byte **iter, bfd_byte *end) |
| { |
| unsigned char byte; |
| do |
| if (!read_byte (iter, end, &byte)) |
| return false; |
| while (byte & 0x80); |
| return true; |
| } |
| |
| /* Like skip_leb128, but treat the leb128 as an unsigned value and |
| store it in *VALUE. */ |
| |
| static bool |
| read_uleb128 (bfd_byte **iter, bfd_byte *end, bfd_vma *value) |
| { |
| bfd_byte *start, *p; |
| |
| start = *iter; |
| if (!skip_leb128 (iter, end)) |
| return false; |
| |
| p = *iter; |
| *value = *--p; |
| while (p > start) |
| *value = (*value << 7) | (*--p & 0x7f); |
| |
| return true; |
| } |
| |
| /* Like read_uleb128, but for signed values. */ |
| |
| static bool |
| read_sleb128 (bfd_byte **iter, bfd_byte *end, bfd_signed_vma *value) |
| { |
| bfd_byte *start, *p; |
| |
| start = *iter; |
| if (!skip_leb128 (iter, end)) |
| return false; |
| |
| p = *iter; |
| *value = ((*--p & 0x7f) ^ 0x40) - 0x40; |
| while (p > start) |
| *value = (*value << 7) | (*--p & 0x7f); |
| |
| return true; |
| } |
| |
| /* Return 0 if either encoding is variable width, or not yet known to bfd. */ |
| |
| static |
| int get_DW_EH_PE_width (int encoding, int ptr_size) |
| { |
| /* DW_EH_PE_ values of 0x60 and 0x70 weren't defined at the time .eh_frame |
| was added to bfd. */ |
| if ((encoding & 0x60) == 0x60) |
| return 0; |
| |
| switch (encoding & 7) |
| { |
| case DW_EH_PE_udata2: return 2; |
| case DW_EH_PE_udata4: return 4; |
| case DW_EH_PE_udata8: return 8; |
| case DW_EH_PE_absptr: return ptr_size; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| #define get_DW_EH_PE_signed(encoding) (((encoding) & DW_EH_PE_signed) != 0) |
| |
| /* Read a width sized value from memory. */ |
| |
| static bfd_vma |
| read_value (bfd *abfd, bfd_byte *buf, int width, int is_signed) |
| { |
| bfd_vma value; |
| |
| switch (width) |
| { |
| case 2: |
| if (is_signed) |
| value = bfd_get_signed_16 (abfd, buf); |
| else |
| value = bfd_get_16 (abfd, buf); |
| break; |
| case 4: |
| if (is_signed) |
| value = bfd_get_signed_32 (abfd, buf); |
| else |
| value = bfd_get_32 (abfd, buf); |
| break; |
| case 8: |
| if (is_signed) |
| value = bfd_get_signed_64 (abfd, buf); |
| else |
| value = bfd_get_64 (abfd, buf); |
| break; |
| default: |
| BFD_FAIL (); |
| return 0; |
| } |
| |
| return value; |
| } |
| |
| /* Store a width sized value to memory. */ |
| |
| static void |
| write_value (bfd *abfd, bfd_byte *buf, bfd_vma value, int width) |
| { |
| switch (width) |
| { |
| case 2: bfd_put_16 (abfd, value, buf); break; |
| case 4: bfd_put_32 (abfd, value, buf); break; |
| case 8: bfd_put_64 (abfd, value, buf); break; |
| default: BFD_FAIL (); |
| } |
| } |
| |
| /* Return one if C1 and C2 CIEs can be merged. */ |
| |
| static int |
| cie_eq (const void *e1, const void *e2) |
| { |
| const struct cie *c1 = (const struct cie *) e1; |
| const struct cie *c2 = (const struct cie *) e2; |
| |
| if (c1->hash == c2->hash |
| && c1->length == c2->length |
| && c1->version == c2->version |
| && c1->local_personality == c2->local_personality |
| && strcmp (c1->augmentation, c2->augmentation) == 0 |
| && strcmp (c1->augmentation, "eh") != 0 |
| && c1->code_align == c2->code_align |
| && c1->data_align == c2->data_align |
| && c1->ra_column == c2->ra_column |
| && c1->augmentation_size == c2->augmentation_size |
| && memcmp (&c1->personality, &c2->personality, |
| sizeof (c1->personality)) == 0 |
| && (c1->cie_inf->u.cie.u.sec->output_section |
| == c2->cie_inf->u.cie.u.sec->output_section) |
| && c1->per_encoding == c2->per_encoding |
| && c1->lsda_encoding == c2->lsda_encoding |
| && c1->fde_encoding == c2->fde_encoding |
| && c1->initial_insn_length == c2->initial_insn_length |
| && c1->initial_insn_length <= sizeof (c1->initial_instructions) |
| && memcmp (c1->initial_instructions, |
| c2->initial_instructions, |
| c1->initial_insn_length) == 0) |
| return 1; |
| |
| return 0; |
| } |
| |
| static hashval_t |
| cie_hash (const void *e) |
| { |
| const struct cie *c = (const struct cie *) e; |
| return c->hash; |
| } |
| |
| static hashval_t |
| cie_compute_hash (struct cie *c) |
| { |
| hashval_t h = 0; |
| size_t len; |
| h = iterative_hash_object (c->length, h); |
| h = iterative_hash_object (c->version, h); |
| h = iterative_hash (c->augmentation, strlen (c->augmentation) + 1, h); |
| h = iterative_hash_object (c->code_align, h); |
| h = iterative_hash_object (c->data_align, h); |
| h = iterative_hash_object (c->ra_column, h); |
| h = iterative_hash_object (c->augmentation_size, h); |
| h = iterative_hash_object (c->personality, h); |
| h = iterative_hash_object (c->cie_inf->u.cie.u.sec->output_section, h); |
| h = iterative_hash_object (c->per_encoding, h); |
| h = iterative_hash_object (c->lsda_encoding, h); |
| h = iterative_hash_object (c->fde_encoding, h); |
| h = iterative_hash_object (c->initial_insn_length, h); |
| len = c->initial_insn_length; |
| if (len > sizeof (c->initial_instructions)) |
| len = sizeof (c->initial_instructions); |
| h = iterative_hash (c->initial_instructions, len, h); |
| c->hash = h; |
| return h; |
| } |
| |
| /* Return the number of extra bytes that we'll be inserting into |
| ENTRY's augmentation string. */ |
| |
| static inline unsigned int |
| extra_augmentation_string_bytes (struct eh_cie_fde *entry) |
| { |
| unsigned int size = 0; |
| if (entry->cie) |
| { |
| if (entry->add_augmentation_size) |
| size++; |
| if (entry->u.cie.add_fde_encoding) |
| size++; |
| } |
| return size; |
| } |
| |
| /* Likewise ENTRY's augmentation data. */ |
| |
| static inline unsigned int |
| extra_augmentation_data_bytes (struct eh_cie_fde *entry) |
| { |
| unsigned int size = 0; |
| if (entry->add_augmentation_size) |
| size++; |
| if (entry->cie && entry->u.cie.add_fde_encoding) |
| size++; |
| return size; |
| } |
| |
| /* Return the size that ENTRY will have in the output. */ |
| |
| static unsigned int |
| size_of_output_cie_fde (struct eh_cie_fde *entry) |
| { |
| if (entry->removed) |
| return 0; |
| if (entry->size == 4) |
| return 4; |
| return (entry->size |
| + extra_augmentation_string_bytes (entry) |
| + extra_augmentation_data_bytes (entry)); |
| } |
| |
| /* Return the offset of the FDE or CIE after ENT. */ |
| |
| static unsigned int |
| next_cie_fde_offset (const struct eh_cie_fde *ent, |
| const struct eh_cie_fde *last, |
| const asection *sec) |
| { |
| while (++ent < last) |
| { |
| if (!ent->removed) |
| return ent->new_offset; |
| } |
| return sec->size; |
| } |
| |
| /* Assume that the bytes between *ITER and END are CFA instructions. |
| Try to move *ITER past the first instruction and return true on |
| success. ENCODED_PTR_WIDTH gives the width of pointer entries. */ |
| |
| static bool |
| skip_cfa_op (bfd_byte **iter, bfd_byte *end, unsigned int encoded_ptr_width) |
| { |
| bfd_byte op = 0; |
| bfd_vma length; |
| |
| if (!read_byte (iter, end, &op)) |
| return false; |
| |
| switch (op & 0xc0 ? op & 0xc0 : op) |
| { |
| case DW_CFA_nop: |
| case DW_CFA_advance_loc: |
| case DW_CFA_restore: |
| case DW_CFA_remember_state: |
| case DW_CFA_restore_state: |
| case DW_CFA_GNU_window_save: |
| /* No arguments. */ |
| return true; |
| |
| case DW_CFA_offset: |
| case DW_CFA_restore_extended: |
| case DW_CFA_undefined: |
| case DW_CFA_same_value: |
| case DW_CFA_def_cfa_register: |
| case DW_CFA_def_cfa_offset: |
| case DW_CFA_def_cfa_offset_sf: |
| case DW_CFA_GNU_args_size: |
| /* One leb128 argument. */ |
| return skip_leb128 (iter, end); |
| |
| case DW_CFA_val_offset: |
| case DW_CFA_val_offset_sf: |
| case DW_CFA_offset_extended: |
| case DW_CFA_register: |
| case DW_CFA_def_cfa: |
| case DW_CFA_offset_extended_sf: |
| case DW_CFA_GNU_negative_offset_extended: |
| case DW_CFA_def_cfa_sf: |
| /* Two leb128 arguments. */ |
| return (skip_leb128 (iter, end) |
| && skip_leb128 (iter, end)); |
| |
| case DW_CFA_def_cfa_expression: |
| /* A variable-length argument. */ |
| return (read_uleb128 (iter, end, &length) |
| && skip_bytes (iter, end, length)); |
| |
| case DW_CFA_expression: |
| case DW_CFA_val_expression: |
| /* A leb128 followed by a variable-length argument. */ |
| return (skip_leb128 (iter, end) |
| && read_uleb128 (iter, end, &length) |
| && skip_bytes (iter, end, length)); |
| |
| case DW_CFA_set_loc: |
| return skip_bytes (iter, end, encoded_ptr_width); |
| |
| case DW_CFA_advance_loc1: |
| return skip_bytes (iter, end, 1); |
| |
| case DW_CFA_advance_loc2: |
| return skip_bytes (iter, end, 2); |
| |
| case DW_CFA_advance_loc4: |
| return skip_bytes (iter, end, 4); |
| |
| case DW_CFA_MIPS_advance_loc8: |
| return skip_bytes (iter, end, 8); |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Try to interpret the bytes between BUF and END as CFA instructions. |
| If every byte makes sense, return a pointer to the first DW_CFA_nop |
| padding byte, or END if there is no padding. Return null otherwise. |
| ENCODED_PTR_WIDTH is as for skip_cfa_op. */ |
| |
| static bfd_byte * |
| skip_non_nops (bfd_byte *buf, bfd_byte *end, unsigned int encoded_ptr_width, |
| unsigned int *set_loc_count) |
| { |
| bfd_byte *last; |
| |
| last = buf; |
| while (buf < end) |
| if (*buf == DW_CFA_nop) |
| buf++; |
| else |
| { |
| if (*buf == DW_CFA_set_loc) |
| ++*set_loc_count; |
| if (!skip_cfa_op (&buf, end, encoded_ptr_width)) |
| return 0; |
| last = buf; |
| } |
| return last; |
| } |
| |
| /* Convert absolute encoding ENCODING into PC-relative form. |
| SIZE is the size of a pointer. */ |
| |
| static unsigned char |
| make_pc_relative (unsigned char encoding, unsigned int ptr_size) |
| { |
| if ((encoding & 0x7f) == DW_EH_PE_absptr) |
| switch (ptr_size) |
| { |
| case 2: |
| encoding |= DW_EH_PE_sdata2; |
| break; |
| case 4: |
| encoding |= DW_EH_PE_sdata4; |
| break; |
| case 8: |
| encoding |= DW_EH_PE_sdata8; |
| break; |
| } |
| return encoding | DW_EH_PE_pcrel; |
| } |
| |
| /* Examine each .eh_frame_entry section and discard those |
| those that are marked SEC_EXCLUDE. */ |
| |
| static void |
| bfd_elf_discard_eh_frame_entry (struct eh_frame_hdr_info *hdr_info) |
| { |
| unsigned int i; |
| for (i = 0; i < hdr_info->array_count; i++) |
| { |
| if (hdr_info->u.compact.entries[i]->flags & SEC_EXCLUDE) |
| { |
| unsigned int j; |
| for (j = i + 1; j < hdr_info->array_count; j++) |
| hdr_info->u.compact.entries[j-1] = hdr_info->u.compact.entries[j]; |
| |
| hdr_info->array_count--; |
| hdr_info->u.compact.entries[hdr_info->array_count] = NULL; |
| i--; |
| } |
| } |
| } |
| |
| /* Add a .eh_frame_entry section. */ |
| |
| static void |
| bfd_elf_record_eh_frame_entry (struct eh_frame_hdr_info *hdr_info, |
| asection *sec) |
| { |
| if (hdr_info->array_count == hdr_info->u.compact.allocated_entries) |
| { |
| if (hdr_info->u.compact.allocated_entries == 0) |
| { |
| hdr_info->frame_hdr_is_compact = true; |
| hdr_info->u.compact.allocated_entries = 2; |
| hdr_info->u.compact.entries = |
| bfd_malloc (hdr_info->u.compact.allocated_entries |
| * sizeof (hdr_info->u.compact.entries[0])); |
| } |
| else |
| { |
| hdr_info->u.compact.allocated_entries *= 2; |
| hdr_info->u.compact.entries = |
| bfd_realloc (hdr_info->u.compact.entries, |
| hdr_info->u.compact.allocated_entries |
| * sizeof (hdr_info->u.compact.entries[0])); |
| } |
| |
| BFD_ASSERT (hdr_info->u.compact.entries); |
| } |
| |
| hdr_info->u.compact.entries[hdr_info->array_count++] = sec; |
| } |
| |
| /* Parse a .eh_frame_entry section. Figure out which text section it |
| references. */ |
| |
| bool |
| _bfd_elf_parse_eh_frame_entry (struct bfd_link_info *info, |
| asection *sec, struct elf_reloc_cookie *cookie) |
| { |
| struct elf_link_hash_table *htab; |
| struct eh_frame_hdr_info *hdr_info; |
| unsigned long r_symndx; |
| asection *text_sec; |
| |
| htab = elf_hash_table (info); |
| hdr_info = &htab->eh_info; |
| |
| if (sec->size == 0 |
| || sec->sec_info_type != SEC_INFO_TYPE_NONE) |
| { |
| return true; |
| } |
| |
| if (sec->output_section && bfd_is_abs_section (sec->output_section)) |
| { |
| /* At least one of the sections is being discarded from the |
| link, so we should just ignore them. */ |
| return true; |
| } |
| |
| if (cookie->rel == cookie->relend) |
| return false; |
| |
| /* The first relocation is the function start. */ |
| r_symndx = cookie->rel->r_info >> cookie->r_sym_shift; |
| if (r_symndx == STN_UNDEF) |
| return false; |
| |
| text_sec = _bfd_elf_section_for_symbol (cookie, r_symndx, false); |
| |
| if (text_sec == NULL) |
| return false; |
| |
| elf_section_eh_frame_entry (text_sec) = sec; |
| if (text_sec->output_section |
| && bfd_is_abs_section (text_sec->output_section)) |
| sec->flags |= SEC_EXCLUDE; |
| |
| sec->sec_info_type = SEC_INFO_TYPE_EH_FRAME_ENTRY; |
| elf_section_data (sec)->sec_info = text_sec; |
| bfd_elf_record_eh_frame_entry (hdr_info, sec); |
| return true; |
| } |
| |
| /* Try to parse .eh_frame section SEC, which belongs to ABFD. Store the |
| information in the section's sec_info field on success. COOKIE |
| describes the relocations in SEC. */ |
| |
| void |
| _bfd_elf_parse_eh_frame (bfd *abfd, struct bfd_link_info *info, |
| asection *sec, struct elf_reloc_cookie *cookie) |
| { |
| #define REQUIRE(COND) \ |
| do \ |
| if (!(COND)) \ |
| goto free_no_table; \ |
| while (0) |
| |
| bfd_byte *ehbuf = NULL, *buf, *end; |
| bfd_byte *last_fde; |
| struct eh_cie_fde *this_inf; |
| unsigned int hdr_length, hdr_id; |
| unsigned int cie_count; |
| struct cie *cie, *local_cies = NULL; |
| struct elf_link_hash_table *htab; |
| struct eh_frame_hdr_info *hdr_info; |
| struct eh_frame_sec_info *sec_info = NULL; |
| unsigned int ptr_size; |
| unsigned int num_cies; |
| unsigned int num_entries; |
| elf_gc_mark_hook_fn gc_mark_hook; |
| |
| htab = elf_hash_table (info); |
| hdr_info = &htab->eh_info; |
| |
| if (sec->size == 0 |
| || (sec->flags & SEC_HAS_CONTENTS) == 0 |
| || sec->sec_info_type != SEC_INFO_TYPE_NONE) |
| { |
| /* This file does not contain .eh_frame information. */ |
| return; |
| } |
| |
| if (bfd_is_abs_section (sec->output_section)) |
| { |
| /* At least one of the sections is being discarded from the |
| link, so we should just ignore them. */ |
| return; |
| } |
| |
| /* Read the frame unwind information from abfd. */ |
| |
| REQUIRE (_bfd_elf_mmap_section_contents (abfd, sec, &ehbuf)); |
| |
| /* If .eh_frame section size doesn't fit into int, we cannot handle |
| it (it would need to use 64-bit .eh_frame format anyway). */ |
| REQUIRE (sec->size == (unsigned int) sec->size); |
| |
| ptr_size = (get_elf_backend_data (abfd) |
| ->elf_backend_eh_frame_address_size (abfd, sec)); |
| REQUIRE (ptr_size != 0); |
| |
| /* Go through the section contents and work out how many FDEs and |
| CIEs there are. */ |
| buf = ehbuf; |
| end = ehbuf + sec->size; |
| num_cies = 0; |
| num_entries = 0; |
| while (buf != end) |
| { |
| num_entries++; |
| |
| /* Read the length of the entry. */ |
| REQUIRE (skip_bytes (&buf, end, 4)); |
| hdr_length = bfd_get_32 (abfd, buf - 4); |
| |
| /* 64-bit .eh_frame is not supported. */ |
| REQUIRE (hdr_length != 0xffffffff); |
| if (hdr_length == 0) |
| break; |
| |
| REQUIRE (skip_bytes (&buf, end, 4)); |
| hdr_id = bfd_get_32 (abfd, buf - 4); |
| if (hdr_id == 0) |
| num_cies++; |
| |
| REQUIRE (skip_bytes (&buf, end, hdr_length - 4)); |
| } |
| |
| sec_info = (struct eh_frame_sec_info *) |
| bfd_zmalloc (sizeof (struct eh_frame_sec_info) |
| + (num_entries - 1) * sizeof (struct eh_cie_fde)); |
| REQUIRE (sec_info); |
| |
| /* We need to have a "struct cie" for each CIE in this section. */ |
| if (num_cies) |
| { |
| local_cies = (struct cie *) bfd_zmalloc (num_cies * sizeof (*local_cies)); |
| REQUIRE (local_cies); |
| } |
| |
| /* FIXME: octets_per_byte. */ |
| #define ENSURE_NO_RELOCS(buf) \ |
| while (cookie->rel < cookie->relend \ |
| && (cookie->rel->r_offset \ |
| < (bfd_size_type) ((buf) - ehbuf))) \ |
| { \ |
| REQUIRE (cookie->rel->r_info == 0); \ |
| cookie->rel++; \ |
| } |
| |
| /* FIXME: octets_per_byte. */ |
| #define SKIP_RELOCS(buf) \ |
| while (cookie->rel < cookie->relend \ |
| && (cookie->rel->r_offset \ |
| < (bfd_size_type) ((buf) - ehbuf))) \ |
| cookie->rel++ |
| |
| /* FIXME: octets_per_byte. */ |
| #define GET_RELOC(buf) \ |
| ((cookie->rel < cookie->relend \ |
| && (cookie->rel->r_offset \ |
| == (bfd_size_type) ((buf) - ehbuf))) \ |
| ? cookie->rel : NULL) |
| |
| buf = ehbuf; |
| cie_count = 0; |
| gc_mark_hook = get_elf_backend_data (abfd)->gc_mark_hook; |
| while ((bfd_size_type) (buf - ehbuf) != sec->size) |
| { |
| char *aug; |
| bfd_byte *start, *insns, *insns_end; |
| bfd_size_type length; |
| unsigned int set_loc_count; |
| |
| this_inf = sec_info->entry + sec_info->count; |
| last_fde = buf; |
| |
| /* Read the length of the entry. */ |
| REQUIRE (skip_bytes (&buf, ehbuf + sec->size, 4)); |
| hdr_length = bfd_get_32 (abfd, buf - 4); |
| |
| /* The CIE/FDE must be fully contained in this input section. */ |
| REQUIRE ((bfd_size_type) (buf - ehbuf) + hdr_length <= sec->size); |
| end = buf + hdr_length; |
| |
| this_inf->offset = last_fde - ehbuf; |
| this_inf->size = 4 + hdr_length; |
| this_inf->reloc_index = cookie->rel - cookie->rels; |
| |
| if (hdr_length == 0) |
| { |
| /* A zero-length CIE should only be found at the end of |
| the section, but allow multiple terminators. */ |
| while (skip_bytes (&buf, ehbuf + sec->size, 4)) |
| REQUIRE (bfd_get_32 (abfd, buf - 4) == 0); |
| REQUIRE ((bfd_size_type) (buf - ehbuf) == sec->size); |
| ENSURE_NO_RELOCS (buf); |
| sec_info->count++; |
| break; |
| } |
| |
| REQUIRE (skip_bytes (&buf, end, 4)); |
| hdr_id = bfd_get_32 (abfd, buf - 4); |
| |
| if (hdr_id == 0) |
| { |
| unsigned int initial_insn_length; |
| |
| /* CIE */ |
| this_inf->cie = 1; |
| |
| /* Point CIE to one of the section-local cie structures. */ |
| cie = local_cies + cie_count++; |
| |
| cie->cie_inf = this_inf; |
| cie->length = hdr_length; |
| start = buf; |
| REQUIRE (read_byte (&buf, end, &cie->version)); |
| |
| /* Cannot handle unknown versions. */ |
| REQUIRE (cie->version == 1 |
| || cie->version == 3 |
| || cie->version == 4); |
| REQUIRE (strlen ((char *) buf) < sizeof (cie->augmentation)); |
| |
| 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') |
| { |
| /* GCC < 3.0 .eh_frame CIE */ |
| /* We cannot merge "eh" CIEs because __EXCEPTION_TABLE__ |
| is private to each CIE, so we don't need it for anything. |
| Just skip it. */ |
| REQUIRE (skip_bytes (&buf, end, ptr_size)); |
| SKIP_RELOCS (buf); |
| } |
| if (cie->version >= 4) |
| { |
| REQUIRE (buf + 1 < end); |
| REQUIRE (buf[0] == ptr_size); |
| REQUIRE (buf[1] == 0); |
| buf += 2; |
| } |
| REQUIRE (read_uleb128 (&buf, end, &cie->code_align)); |
| REQUIRE (read_sleb128 (&buf, end, &cie->data_align)); |
| if (cie->version == 1) |
| { |
| REQUIRE (buf < end); |
| cie->ra_column = *buf++; |
| } |
| else |
| REQUIRE (read_uleb128 (&buf, end, &cie->ra_column)); |
| ENSURE_NO_RELOCS (buf); |
| cie->lsda_encoding = DW_EH_PE_omit; |
| cie->fde_encoding = DW_EH_PE_omit; |
| cie->per_encoding = DW_EH_PE_omit; |
| aug = cie->augmentation; |
| if (aug[0] != 'e' || aug[1] != 'h') |
| { |
| if (*aug == 'z') |
| { |
| aug++; |
| REQUIRE (read_uleb128 (&buf, end, &cie->augmentation_size)); |
| ENSURE_NO_RELOCS (buf); |
| } |
| |
| while (*aug != '\0') |
| switch (*aug++) |
| { |
| case 'B': |
| break; |
| case 'L': |
| REQUIRE (read_byte (&buf, end, &cie->lsda_encoding)); |
| ENSURE_NO_RELOCS (buf); |
| REQUIRE (get_DW_EH_PE_width (cie->lsda_encoding, ptr_size)); |
| break; |
| case 'R': |
| REQUIRE (read_byte (&buf, end, &cie->fde_encoding)); |
| ENSURE_NO_RELOCS (buf); |
| REQUIRE (get_DW_EH_PE_width (cie->fde_encoding, ptr_size)); |
| break; |
| case 'S': |
| break; |
| case 'P': |
| { |
| int per_width; |
| |
| REQUIRE (read_byte (&buf, end, &cie->per_encoding)); |
| per_width = get_DW_EH_PE_width (cie->per_encoding, |
| ptr_size); |
| REQUIRE (per_width); |
| if ((cie->per_encoding & 0x70) == DW_EH_PE_aligned) |
| { |
| length = -(buf - ehbuf) & (per_width - 1); |
| REQUIRE (skip_bytes (&buf, end, length)); |
| if (per_width == 8) |
| this_inf->u.cie.per_encoding_aligned8 = 1; |
| } |
| this_inf->u.cie.personality_offset = buf - start; |
| ENSURE_NO_RELOCS (buf); |
| /* Ensure we have a reloc here. */ |
| REQUIRE (GET_RELOC (buf)); |
| cie->personality.reloc_index |
| = cookie->rel - cookie->rels; |
| /* Cope with MIPS-style composite relocations. */ |
| do |
| cookie->rel++; |
| while (GET_RELOC (buf) != NULL); |
| REQUIRE (skip_bytes (&buf, end, per_width)); |
| } |
| break; |
| default: |
| /* Unrecognized augmentation. Better bail out. */ |
| 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. */ |
| if (bfd_link_pic (info) |
| && (get_elf_backend_data (abfd) |
| ->elf_backend_can_make_relative_eh_frame |
| (abfd, info, sec))) |
| { |
| if ((cie->fde_encoding & 0x70) == DW_EH_PE_absptr) |
| this_inf->make_relative = 1; |
| /* If the CIE doesn't already have an 'R' entry, it's fairly |
| easy to add one, provided that there's no aligned data |
| after the augmentation string. */ |
| else if (cie->fde_encoding == DW_EH_PE_omit |
| && (cie->per_encoding & 0x70) != DW_EH_PE_aligned) |
| { |
| if (*cie->augmentation == 0) |
| this_inf->add_augmentation_size = 1; |
| this_inf->u.cie.add_fde_encoding = 1; |
| this_inf->make_relative = 1; |
| } |
| |
| if ((cie->lsda_encoding & 0x70) == DW_EH_PE_absptr) |
| cie->can_make_lsda_relative = 1; |
| } |
| |
| /* If FDE encoding was not specified, it defaults to |
| DW_EH_absptr. */ |
| if (cie->fde_encoding == DW_EH_PE_omit) |
| cie->fde_encoding = DW_EH_PE_absptr; |
| |
| initial_insn_length = end - buf; |
| cie->initial_insn_length = initial_insn_length; |
| memcpy (cie->initial_instructions, buf, |
| initial_insn_length <= sizeof (cie->initial_instructions) |
| ? initial_insn_length : sizeof (cie->initial_instructions)); |
| insns = buf; |
| buf += initial_insn_length; |
| ENSURE_NO_RELOCS (buf); |
| |
| if (!bfd_link_relocatable (info)) |
| { |
| /* Keep info for merging cies. */ |
| this_inf->u.cie.u.full_cie = cie; |
| this_inf->u.cie.per_encoding_relative |
| = (cie->per_encoding & 0x70) == DW_EH_PE_pcrel; |
| } |
| } |
| else |
| { |
| /* Find the corresponding CIE. */ |
| unsigned int cie_offset = this_inf->offset + 4 - hdr_id; |
| for (cie = local_cies; cie < local_cies + cie_count; cie++) |
| if (cie_offset == cie->cie_inf->offset) |
| break; |
| |
| /* Ensure this FDE references one of the CIEs in this input |
| section. */ |
| REQUIRE (cie != local_cies + cie_count); |
| this_inf->u.fde.cie_inf = cie->cie_inf; |
| this_inf->make_relative = cie->cie_inf->make_relative; |
| this_inf->add_augmentation_size |
| = cie->cie_inf->add_augmentation_size; |
| |
| ENSURE_NO_RELOCS (buf); |
| if ((sec->flags & SEC_LINKER_CREATED) == 0 || cookie->rels != NULL) |
| { |
| asection *rsec; |
| |
| REQUIRE (GET_RELOC (buf)); |
| |
| /* Chain together the FDEs for each section. */ |
| rsec = _bfd_elf_gc_mark_rsec (info, sec, gc_mark_hook, |
| cookie, NULL); |
| /* RSEC will be NULL if FDE was cleared out as it was belonging to |
| a discarded SHT_GROUP. */ |
| if (rsec) |
| { |
| REQUIRE (rsec->owner == abfd); |
| this_inf->u.fde.next_for_section = elf_fde_list (rsec); |
| elf_fde_list (rsec) = this_inf; |
| } |
| } |
| |
| /* Skip the initial location and address range. */ |
| start = buf; |
| length = get_DW_EH_PE_width (cie->fde_encoding, ptr_size); |
| REQUIRE (skip_bytes (&buf, end, 2 * length)); |
| |
| SKIP_RELOCS (buf - length); |
| if (!GET_RELOC (buf - length) |
| && read_value (abfd, buf - length, length, false) == 0) |
| { |
| (*info->callbacks->minfo) |
| /* xgettext:c-format */ |
| (_("discarding zero address range FDE in %pB(%pA).\n"), |
| abfd, sec); |
| this_inf->u.fde.cie_inf = NULL; |
| } |
| |
| /* Skip the augmentation size, if present. */ |
| if (cie->augmentation[0] == 'z') |
| REQUIRE (read_uleb128 (&buf, end, &length)); |
| else |
| length = 0; |
| |
| /* Of the supported augmentation characters above, only 'L' |
| adds augmentation data to the FDE. This code would need to |
| be adjusted if any future augmentations do the same thing. */ |
| if (cie->lsda_encoding != DW_EH_PE_omit) |
| { |
| SKIP_RELOCS (buf); |
| if (cie->can_make_lsda_relative && GET_RELOC (buf)) |
| cie->cie_inf->u.cie.make_lsda_relative = 1; |
| this_inf->lsda_offset = buf - start; |
| /* If there's no 'z' augmentation, we don't know where the |
| CFA insns begin. Assume no padding. */ |
| if (cie->augmentation[0] != 'z') |
| length = end - buf; |
| } |
| |
| /* Skip over the augmentation data. */ |
| REQUIRE (skip_bytes (&buf, end, length)); |
| insns = buf; |
| |
| buf = last_fde + 4 + hdr_length; |
| |
| /* For NULL RSEC (cleared FDE belonging to a discarded section) |
| the relocations are commonly cleared. We do not sanity check if |
| all these relocations are cleared as (1) relocations to |
| .gcc_except_table will remain uncleared (they will get dropped |
| with the drop of this unused FDE) and (2) BFD already safely drops |
| relocations of any type to .eh_frame by |
| elf_section_ignore_discarded_relocs. |
| TODO: The .gcc_except_table entries should be also filtered as |
| .eh_frame entries; or GCC could rather use COMDAT for them. */ |
| SKIP_RELOCS (buf); |
| } |
| |
| /* Try to interpret the CFA instructions and find the first |
| padding nop. Shrink this_inf's size so that it doesn't |
| include the padding. */ |
| length = get_DW_EH_PE_width (cie->fde_encoding, ptr_size); |
| set_loc_count = 0; |
| insns_end = skip_non_nops (insns, end, length, &set_loc_count); |
| /* If we don't understand the CFA instructions, we can't know |
| what needs to be adjusted there. */ |
| if (insns_end == NULL |
| /* For the time being we don't support DW_CFA_set_loc in |
| CIE instructions. */ |
| || (set_loc_count && this_inf->cie)) |
| goto free_no_table; |
| this_inf->size -= end - insns_end; |
| if (insns_end != end && this_inf->cie) |
| { |
| cie->initial_insn_length -= end - insns_end; |
| cie->length -= end - insns_end; |
| } |
| if (set_loc_count |
| && ((cie->fde_encoding & 0x70) == DW_EH_PE_pcrel |
| || this_inf->make_relative)) |
| { |
| unsigned int cnt; |
| bfd_byte *p; |
| |
| this_inf->set_loc = (unsigned int *) |
| bfd_malloc ((set_loc_count + 1) * sizeof (unsigned int)); |
| REQUIRE (this_inf->set_loc); |
| this_inf->set_loc[0] = set_loc_count; |
| p = insns; |
| cnt = 0; |
| while (p < end) |
| { |
| if (*p == DW_CFA_set_loc) |
| this_inf->set_loc[++cnt] = p + 1 - start; |
| REQUIRE (skip_cfa_op (&p, end, length)); |
| } |
| } |
| |
| this_inf->removed = 1; |
| this_inf->fde_encoding = cie->fde_encoding; |
| this_inf->lsda_encoding = cie->lsda_encoding; |
| sec_info->count++; |
| } |
| BFD_ASSERT (sec_info->count == num_entries); |
| BFD_ASSERT (cie_count == num_cies); |
| |
| elf_section_data (sec)->sec_info = sec_info; |
| sec->sec_info_type = SEC_INFO_TYPE_EH_FRAME; |
| if (!bfd_link_relocatable (info)) |
| { |
| /* Keep info for merging cies. */ |
| sec_info->cies = local_cies; |
| local_cies = NULL; |
| } |
| goto success; |
| |
| free_no_table: |
| _bfd_error_handler |
| /* xgettext:c-format */ |
| (_("error in %pB(%pA); no .eh_frame_hdr table will be created"), |
| abfd, sec); |
| hdr_info->u.dwarf.table = false; |
| free (sec_info); |
| success: |
| _bfd_elf_munmap_section_contents (sec, ehbuf); |
| free (local_cies); |
| #undef REQUIRE |
| } |
| |
| /* Order eh_frame_hdr entries by the VMA of their text section. */ |
| |
| static int |
| cmp_eh_frame_hdr (const void *a, const void *b) |
| { |
| bfd_vma text_a; |
| bfd_vma text_b; |
| asection *sec; |
| |
| sec = *(asection *const *)a; |
| sec = (asection *) elf_section_data (sec)->sec_info; |
| text_a = sec->output_section->vma + sec->output_offset; |
| sec = *(asection *const *)b; |
| sec = (asection *) elf_section_data (sec)->sec_info; |
| text_b = sec->output_section->vma + sec->output_offset; |
| |
| if (text_a < text_b) |
| return -1; |
| return text_a > text_b; |
| |
| } |
| |
| /* Add space for a CANTUNWIND terminator to SEC if the text sections |
| referenced by it and NEXT are not contiguous, or NEXT is NULL. */ |
| |
| static void |
| add_eh_frame_hdr_terminator (asection *sec, |
| asection *next) |
| { |
| bfd_vma end; |
| bfd_vma next_start; |
| asection *text_sec; |
| |
| if (next) |
| { |
| /* See if there is a gap (presumably a text section without unwind info) |
| between these two entries. */ |
| text_sec = (asection *) elf_section_data (sec)->sec_info; |
| end = text_sec->output_section->vma + text_sec->output_offset |
| + text_sec->size; |
| text_sec = (asection *) elf_section_data (next)->sec_info; |
| next_start = text_sec->output_section->vma + text_sec->output_offset; |
| if (end == next_start) |
| return; |
| } |
| |
| /* Add space for a CANTUNWIND terminator. */ |
| if (!sec->rawsize) |
| sec->rawsize = sec->size; |
| |
| bfd_set_section_size (sec, sec->size + 8); |
| } |
| |
| /* Finish a pass over all .eh_frame_entry sections. */ |
| |
| bool |
| _bfd_elf_end_eh_frame_parsing (struct bfd_link_info *info) |
| { |
| struct eh_frame_hdr_info *hdr_info; |
| unsigned int i; |
| |
| hdr_info = &elf_hash_table (info)->eh_info; |
| |
| if (info->eh_frame_hdr_type != COMPACT_EH_HDR |
| || hdr_info->array_count == 0) |
| return false; |
| |
| bfd_elf_discard_eh_frame_entry (hdr_info); |
| |
| qsort (hdr_info->u.compact.entries, hdr_info->array_count, |
| sizeof (asection *), cmp_eh_frame_hdr); |
| |
| for (i = 0; i < hdr_info->array_count - 1; i++) |
| { |
| add_eh_frame_hdr_terminator (hdr_info->u.compact.entries[i], |
| hdr_info->u.compact.entries[i + 1]); |
| } |
| |
| /* Add a CANTUNWIND terminator after the last entry. */ |
| add_eh_frame_hdr_terminator (hdr_info->u.compact.entries[i], NULL); |
| return true; |
| } |
| |
| /* Mark all relocations against CIE or FDE ENT, which occurs in |
| .eh_frame section SEC. COOKIE describes the relocations in SEC; |
| its "rel" field can be changed freely. */ |
| |
| static bool |
| mark_entry (struct bfd_link_info *info, asection *sec, |
| struct eh_cie_fde *ent, elf_gc_mark_hook_fn gc_mark_hook, |
| struct elf_reloc_cookie *cookie) |
| { |
| /* FIXME: octets_per_byte. */ |
| for (cookie->rel = cookie->rels + ent->reloc_index; |
| cookie->rel < cookie->relend |
| && cookie->rel->r_offset < ent->offset + ent->size; |
| cookie->rel++) |
| if (!_bfd_elf_gc_mark_reloc (info, sec, gc_mark_hook, cookie)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Mark all the relocations against FDEs that relate to code in input |
| section SEC. The FDEs belong to .eh_frame section EH_FRAME, whose |
| relocations are described by COOKIE. */ |
| |
| bool |
| _bfd_elf_gc_mark_fdes (struct bfd_link_info *info, asection *sec, |
| asection *eh_frame, elf_gc_mark_hook_fn gc_mark_hook, |
| struct elf_reloc_cookie *cookie) |
| { |
| struct eh_cie_fde *fde, *cie; |
| |
| for (fde = elf_fde_list (sec); fde; fde = fde->u.fde.next_for_section) |
| { |
| if (!mark_entry (info, eh_frame, fde, gc_mark_hook, cookie)) |
| return false; |
| |
| /* At this stage, all cie_inf fields point to local CIEs, so we |
| can use the same cookie to refer to them. */ |
| cie = fde->u.fde.cie_inf; |
| if (cie != NULL && !cie->u.cie.gc_mark) |
| { |
| cie->u.cie.gc_mark = 1; |
| if (!mark_entry (info, eh_frame, cie, gc_mark_hook, cookie)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /* Input section SEC of ABFD is an .eh_frame section that contains the |
| CIE described by CIE_INF. Return a version of CIE_INF that is going |
| to be kept in the output, adding CIE_INF to the output if necessary. |
| |
| HDR_INFO is the .eh_frame_hdr information and COOKIE describes the |
| relocations in REL. */ |
| |
| static struct eh_cie_fde * |
| find_merged_cie (bfd *abfd, struct bfd_link_info *info, asection *sec, |
| struct eh_frame_hdr_info *hdr_info, |
| struct elf_reloc_cookie *cookie, |
| struct eh_cie_fde *cie_inf) |
| { |
| unsigned long r_symndx; |
| struct cie *cie, *new_cie; |
| Elf_Internal_Rela *rel; |
| void **loc; |
| |
| /* Use CIE_INF if we have already decided to keep it. */ |
| if (!cie_inf->removed) |
| return cie_inf; |
| |
| /* If we have merged CIE_INF with another CIE, use that CIE instead. */ |
| if (cie_inf->u.cie.merged) |
| return cie_inf->u.cie.u.merged_with; |
| |
| cie = cie_inf->u.cie.u.full_cie; |
| |
| /* Assume we will need to keep CIE_INF. */ |
| cie_inf->removed = 0; |
| cie_inf->u.cie.u.sec = sec; |
| |
| /* If we are not merging CIEs, use CIE_INF. */ |
| if (cie == NULL) |
| return cie_inf; |
| |
| if (cie->per_encoding != DW_EH_PE_omit) |
| { |
| bool per_binds_local; |
| |
| /* Work out the address of personality routine, or at least |
| enough info that we could calculate the address had we made a |
| final section layout. The symbol on the reloc is enough, |
| either the hash for a global, or (bfd id, index) pair for a |
| local. The assumption here is that no one uses addends on |
| the reloc. */ |
| rel = cookie->rels + cie->personality.reloc_index; |
| memset (&cie->personality, 0, sizeof (cie->personality)); |
| #ifdef BFD64 |
| if (elf_elfheader (abfd)->e_ident[EI_CLASS] == ELFCLASS64) |
| r_symndx = ELF64_R_SYM (rel->r_info); |
| else |
| #endif |
| r_symndx = ELF32_R_SYM (rel->r_info); |
| if (r_symndx >= cookie->locsymcount |
| || ELF_ST_BIND (cookie->locsyms[r_symndx].st_info) != STB_LOCAL) |
| { |
| struct elf_link_hash_entry *h; |
| |
| r_symndx -= cookie->extsymoff; |
| h = cookie->sym_hashes[r_symndx]; |
| |
| 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; |
| |
| cie->personality.h = h; |
| per_binds_local = SYMBOL_REFERENCES_LOCAL (info, h); |
| } |
| else |
| { |
| Elf_Internal_Sym *sym; |
| asection *sym_sec; |
| |
| sym = &cookie->locsyms[r_symndx]; |
| sym_sec = bfd_section_from_elf_index (abfd, sym->st_shndx); |
| if (sym_sec == NULL) |
| return cie_inf; |
| |
| if (sym_sec->kept_section != NULL) |
| sym_sec = sym_sec->kept_section; |
| if (sym_sec->output_section == NULL) |
| return cie_inf; |
| |
| cie->local_personality = 1; |
| cie->personality.sym.bfd_id = abfd->id; |
| cie->personality.sym.index = r_symndx; |
| per_binds_local = true; |
| } |
| |
| if (per_binds_local |
| && bfd_link_pic (info) |
| && (cie->per_encoding & 0x70) == DW_EH_PE_absptr |
| && (get_elf_backend_data (abfd) |
| ->elf_backend_can_make_relative_eh_frame (abfd, info, sec))) |
| { |
| cie_inf->u.cie.make_per_encoding_relative = 1; |
| cie_inf->u.cie.per_encoding_relative = 1; |
| } |
| } |
| |
| /* See if we can merge this CIE with an earlier one. */ |
| cie_compute_hash (cie); |
| if (hdr_info->u.dwarf.cies == NULL) |
| { |
| hdr_info->u.dwarf.cies = htab_try_create (1, cie_hash, cie_eq, free); |
| if (hdr_info->u.dwarf.cies == NULL) |
| return cie_inf; |
| } |
| loc = htab_find_slot_with_hash (hdr_info->u.dwarf.cies, cie, |
| cie->hash, INSERT); |
| if (loc == NULL) |
| return cie_inf; |
| |
| new_cie = (struct cie *) *loc; |
| if (new_cie == NULL) |
| { |
| /* Keep CIE_INF and record it in the hash table. */ |
| new_cie = (struct cie *) malloc (sizeof (struct cie)); |
| if (new_cie == NULL) |
| return cie_inf; |
| |
| memcpy (new_cie, cie, sizeof (struct cie)); |
| *loc = new_cie; |
| } |
| else |
| { |
| /* Merge CIE_INF with NEW_CIE->CIE_INF. */ |
| cie_inf->removed = 1; |
| cie_inf->u.cie.merged = 1; |
| cie_inf->u.cie.u.merged_with = new_cie->cie_inf; |
| if (cie_inf->u.cie.make_lsda_relative) |
| new_cie->cie_inf->u.cie.make_lsda_relative = 1; |
| } |
| 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, const 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 = NULL; |
| 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. */ |
| |
| bool |
| _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 (const asection *sec, |
| struct elf_reloc_cookie *cookie) |
| { |
| int adjusted = 0; |
| |
| if (cookie->locsymcount > 1) |
| { |
| unsigned int shndx = elf_section_data (sec)->this_idx; |
| Elf_Internal_Sym *end_sym = cookie->locsyms + cookie->locsymcount; |
| Elf_Internal_Sym *sym; |
| |
| 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 |
| deleted. */ |
| |
| bool |
| _bfd_elf_discard_section_eh_frame |
| (bfd *abfd, struct bfd_link_info *info, asection *sec, |
| bool (*reloc_symbol_deleted_p) (bfd_vma, void *), |
| struct elf_reloc_cookie *cookie) |
| { |
| struct eh_cie_fde *ent; |
| 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; |
| |
| sec_info = (struct eh_frame_sec_info *) elf_section_data (sec)->sec_info; |
| if (sec_info == NULL) |
| return false; |
| |
| ptr_size = (get_elf_backend_data (sec->owner) |
| ->elf_backend_eh_frame_address_size (sec->owner, sec)); |
| |
| hdr_info = &elf_hash_table (info)->eh_info; |
| for (ent = sec_info->entry; ent < sec_info->entry + sec_info->count; ++ent) |
| if (ent->size == 4) |
| /* There should only be one zero terminator, on the last input |
| file supplying .eh_frame (crtend.o). Remove any others. */ |
| ent->removed = sec->map_head.s != NULL; |
| else if (!ent->cie && ent->u.fde.cie_inf != NULL) |
| { |
| bool keep; |
| if ((sec->flags & SEC_LINKER_CREATED) != 0 && cookie->rels == NULL) |
| { |
| unsigned int width |
| = get_DW_EH_PE_width (ent->fde_encoding, ptr_size); |
| bfd_vma value |
| = read_value (abfd, sec->contents + ent->offset + 8 + width, |
| width, get_DW_EH_PE_signed (ent->fde_encoding)); |
| keep = value != 0; |
| } |
| else |
| { |
| cookie->rel = cookie->rels + ent->reloc_index; |
| /* FIXME: octets_per_byte. */ |
| BFD_ASSERT (cookie->rel < cookie->relend |
| && cookie->rel->r_offset == ent->offset + 8); |
| keep = !(*reloc_symbol_deleted_p) (ent->offset + 8, cookie); |
| } |
| if (keep) |
| { |
| if (bfd_link_pic (info) |
| && (((ent->fde_encoding & 0x70) == DW_EH_PE_absptr |
| && ent->make_relative == 0) |
| || (ent->fde_encoding & 0x70) == DW_EH_PE_aligned)) |
| { |
| static int num_warnings_issued = 0; |
| |
| /* If a shared library uses absolute pointers |
| which we cannot turn into PC relative, |
| don't create the binary search table, |
| since it is affected by runtime relocations. */ |
| hdr_info->u.dwarf.table = false; |
| /* Only warn if --eh-frame-hdr was specified. */ |
| if (info->eh_frame_hdr_type != 0) |
| { |
| if (num_warnings_issued < 10) |
| { |
| _bfd_error_handler |
| /* xgettext:c-format */ |
| (_("FDE encoding in %pB(%pA) prevents .eh_frame_hdr" |
| " table being created"), abfd, sec); |
| num_warnings_issued ++; |
| } |
| else if (num_warnings_issued == 10) |
| { |
| _bfd_error_handler |
| (_("further warnings about FDE encoding preventing .eh_frame_hdr generation dropped")); |
| num_warnings_issued ++; |
| } |
| } |
| } |
| ent->removed = 0; |
| hdr_info->u.dwarf.fde_count++; |
| ent->u.fde.cie_inf = find_merged_cie (abfd, info, sec, hdr_info, |
| cookie, ent->u.fde.cie_inf); |
| } |
| } |
| |
| free (sec_info->cies); |
| sec_info->cies = NULL; |
| |
| /* It may be that some .eh_frame input section has greater alignment |
| than other .eh_frame sections. In that case we run the risk of |
| padding with zeros before that section, which would be seen as a |
| zero terminator. Alignment padding must be added *inside* the |
| 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) |
| { |
| eh_alignment = 4; |
| if (ent->size == 4) |
| ; |
| else if (ent->cie) |
| { |
| if (ent->u.cie.per_encoding_aligned8) |
| eh_alignment = 8; |
| } |
| else |
| { |
| eh_alignment = get_DW_EH_PE_width (ent->fde_encoding, ptr_size); |
| if (eh_alignment < 4) |
| eh_alignment = 4; |
| } |
| 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); |
| } |
| |
| eh_alignment = 4; |
| offset = (offset + eh_alignment - 1) & -eh_alignment; |
| sec->rawsize = sec->size; |
| sec->size = offset; |
| 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 |
| _bfd_elf_discard_section_eh_frame has been called on all .eh_frame |
| input sections. It finalizes the size of .eh_frame_hdr section. */ |
| |
| bool |
| _bfd_elf_discard_section_eh_frame_hdr (struct bfd_link_info *info) |
| { |
| struct elf_link_hash_table *htab; |
| struct eh_frame_hdr_info *hdr_info; |
| asection *sec; |
| |
| htab = elf_hash_table (info); |
| hdr_info = &htab->eh_info; |
| |
| if (!hdr_info->frame_hdr_is_compact && hdr_info->u.dwarf.cies != NULL) |
| { |
| htab_delete (hdr_info->u.dwarf.cies); |
| hdr_info->u.dwarf.cies = NULL; |
| } |
| |
| sec = hdr_info->hdr_sec; |
| if (sec == NULL) |
| return false; |
| |
| if (info->eh_frame_hdr_type == COMPACT_EH_HDR) |
| { |
| /* For compact frames we only add the header. The actual table comes |
| from the .eh_frame_entry sections. */ |
| sec->size = 8; |
| } |
| else |
| { |
| sec->size = EH_FRAME_HDR_SIZE; |
| if (hdr_info->u.dwarf.table) |
| sec->size += 4 + hdr_info->u.dwarf.fde_count * 8; |
| } |
| |
| return true; |
| } |
| |
| /* Return true if there is at least one non-empty .eh_frame section in |
| input files. Can only be called after ld has mapped input to |
| output sections, and before sections are stripped. */ |
| |
| bool |
| _bfd_elf_eh_frame_present (struct bfd_link_info *info) |
| { |
| asection *eh = bfd_get_section_by_name (info->output_bfd, ".eh_frame"); |
| |
| if (eh == NULL) |
| return false; |
| |
| /* Count only sections which have at least a single CIE or FDE. |
| There cannot be any CIE or FDE <= 8 bytes. */ |
| for (eh = eh->map_head.s; eh != NULL; eh = eh->map_head.s) |
| if (eh->size > 8) |
| return true; |
| |
| return false; |
| } |
| |
| /* Return true if there is at least one .eh_frame_entry section in |
| input files. */ |
| |
| bool |
| _bfd_elf_eh_frame_entry_present (struct bfd_link_info *info) |
| { |
| asection *o; |
| bfd *abfd; |
| |
| for (abfd = info->input_bfds; abfd != NULL; abfd = abfd->link.next) |
| { |
| for (o = abfd->sections; o; o = o->next) |
| { |
| const char *name = bfd_section_name (o); |
| |
| if (strcmp (name, ".eh_frame_entry") |
| && !bfd_is_abs_section (o->output_section)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* This function is called from size_dynamic_sections. |
| It needs to decide whether .eh_frame_hdr should be output or not, |
| because when the dynamic symbol table has been sized it is too late |
| to strip sections. */ |
| |
| bool |
| _bfd_elf_maybe_strip_eh_frame_hdr (struct bfd_link_info *info) |
| { |
| struct elf_link_hash_table *htab; |
| struct eh_frame_hdr_info *hdr_info; |
| struct bfd_link_hash_entry *bh = NULL; |
| struct elf_link_hash_entry *h; |
| |
| htab = elf_hash_table (info); |
| hdr_info = &htab->eh_info; |
| if (hdr_info->hdr_sec == NULL) |
| return true; |
| |
| if (bfd_is_abs_section (hdr_info->hdr_sec->output_section) |
| || info->eh_frame_hdr_type == 0 |
| || (info->eh_frame_hdr_type == DWARF2_EH_HDR |
| && !_bfd_elf_eh_frame_present (info)) |
| || (info->eh_frame_hdr_type == COMPACT_EH_HDR |
| && !_bfd_elf_eh_frame_entry_present (info))) |
| { |
| hdr_info->hdr_sec->flags |= SEC_EXCLUDE; |
| hdr_info->hdr_sec = NULL; |
| return true; |
| } |
| |
| /* Add a hidden symbol so that systems without access to PHDRs can |
| find the table. */ |
| if (! (_bfd_generic_link_add_one_symbol |
| (info, info->output_bfd, "__GNU_EH_FRAME_HDR", BSF_LOCAL, |
| hdr_info->hdr_sec, 0, NULL, false, false, &bh))) |
| return false; |
| |
| h = (struct elf_link_hash_entry *) bh; |
| h->def_regular = 1; |
| h->other = STV_HIDDEN; |
| get_elf_backend_data |
| (info->output_bfd)->elf_backend_hide_symbol (info, h, true); |
| |
| if (!hdr_info->frame_hdr_is_compact) |
| hdr_info->u.dwarf.table = true; |
| return true; |
| } |
| |
| /* Adjust an address in the .eh_frame section. Given OFFSET within |
| SEC, this returns the new offset in the adjusted .eh_frame section, |
| or -1 if the address refers to a CIE/FDE which has been removed |
| or to offset with dynamic relocation which is no longer needed. */ |
| |
| bfd_vma |
| _bfd_elf_eh_frame_section_offset (bfd *output_bfd ATTRIBUTE_UNUSED, |
| struct bfd_link_info *info ATTRIBUTE_UNUSED, |
| asection *sec, |
| bfd_vma offset) |
| { |
| struct eh_frame_sec_info *sec_info; |
| unsigned int lo, hi, mid; |
| |
| if (sec->sec_info_type != SEC_INFO_TYPE_EH_FRAME) |
| return offset; |
| sec_info = (struct eh_frame_sec_info *) elf_section_data (sec)->sec_info; |
| |
| if (offset >= sec->rawsize) |
| return offset - sec->rawsize + sec->size; |
| |
| lo = 0; |
| hi = sec_info->count; |
| mid = 0; |
| while (lo < hi) |
| { |
| mid = (lo + hi) / 2; |
| if (offset < sec_info->entry[mid].offset) |
| hi = mid; |
| else if (offset |
| >= sec_info->entry[mid].offset + sec_info->entry[mid].size) |
| lo = mid + 1; |
| else |
| break; |
| } |
| |
| BFD_ASSERT (lo < hi); |
| |
| /* FDE or CIE was removed. */ |
| if (sec_info->entry[mid].removed) |
| return (bfd_vma) -1; |
| |
| /* If converting personality pointers to DW_EH_PE_pcrel, there will be |
| no need for run-time relocation against the personality field. */ |
| if (sec_info->entry[mid].cie |
| && sec_info->entry[mid].u.cie.make_per_encoding_relative |
| && offset == (sec_info->entry[mid].offset + 8 |
| + sec_info->entry[mid].u.cie.personality_offset)) |
| return (bfd_vma) -2; |
| |
| /* If converting to DW_EH_PE_pcrel, there will be no need for run-time |
| relocation against FDE's initial_location field. */ |
| if (!sec_info->entry[mid].cie |
| && sec_info->entry[mid].make_relative |
| && offset == sec_info->entry[mid].offset + 8) |
| return (bfd_vma) -2; |
| |
| /* If converting LSDA pointers to DW_EH_PE_pcrel, there will be no need |
| for run-time relocation against LSDA field. */ |
| if (!sec_info->entry[mid].cie |
| && sec_info->entry[mid].u.fde.cie_inf->u.cie.make_lsda_relative |
| && offset == (sec_info->entry[mid].offset + 8 |
| + sec_info->entry[mid].lsda_offset)) |
| return (bfd_vma) -2; |
| |
| /* If converting to DW_EH_PE_pcrel, there will be no need for run-time |
| relocation against DW_CFA_set_loc's arguments. */ |
| if (sec_info->entry[mid].set_loc |
| && sec_info->entry[mid].make_relative |
| && (offset >= sec_info->entry[mid].offset + 8 |
| + sec_info->entry[mid].set_loc[1])) |
| { |
| unsigned int cnt; |
| |
| for (cnt = 1; cnt <= sec_info->entry[mid].set_loc[0]; cnt++) |
| if (offset == sec_info->entry[mid].offset + 8 |
| + sec_info->entry[mid].set_loc[cnt]) |
| return (bfd_vma) -2; |
| } |
| |
| /* Any new augmentation bytes go before the first relocation. */ |
| return (offset + sec_info->entry[mid].new_offset |
| - sec_info->entry[mid].offset |
| + extra_augmentation_string_bytes (sec_info->entry + mid) |
| + extra_augmentation_data_bytes (sec_info->entry + mid)); |
| } |
| |
| /* Write out .eh_frame_entry section. Add CANTUNWIND terminator if needed. |
| Also check that the contents look sane. */ |
| |
| bool |
| _bfd_elf_write_section_eh_frame_entry (bfd *abfd, struct bfd_link_info *info, |
| asection *sec, bfd_byte *contents) |
| { |
| const struct elf_backend_data *bed; |
| bfd_byte cantunwind[8]; |
| bfd_vma addr; |
| bfd_vma last_addr; |
| bfd_vma offset; |
| asection *text_sec = (asection *) elf_section_data (sec)->sec_info; |
| |
| if (!sec->rawsize) |
| sec->rawsize = sec->size; |
| |
| BFD_ASSERT (sec->sec_info_type == SEC_INFO_TYPE_EH_FRAME_ENTRY); |
| |
| /* Check to make sure that the text section corresponding to this eh_frame_entry |
| section has not been excluded. In particular, mips16 stub entries will be |
| excluded outside of the normal process. */ |
| if (sec->flags & SEC_EXCLUDE |
| || text_sec->flags & SEC_EXCLUDE) |
| return true; |
| |
| if (!bfd_set_section_contents (abfd, sec->output_section, contents, |
| sec->output_offset, sec->rawsize)) |
| return false; |
| |
| last_addr = bfd_get_signed_32 (abfd, contents); |
| /* Check that all the entries are in order. */ |
| for (offset = 8; offset < sec->rawsize; offset += 8) |
| { |
| addr = bfd_get_signed_32 (abfd, contents + offset) + offset; |
| if (addr <= last_addr) |
| { |
| /* xgettext:c-format */ |
| _bfd_error_handler (_("%pB: %pA not in order"), sec->owner, sec); |
| return false; |
| } |
| |
| last_addr = addr; |
| } |
| |
| addr = text_sec->output_section->vma + text_sec->output_offset |
| + text_sec->size; |
| addr &= ~1; |
| addr -= (sec->output_section->vma + sec->output_offset + sec->rawsize); |
| if (addr & 1) |
| { |
| /* xgettext:c-format */ |
| _bfd_error_handler (_("%pB: %pA invalid input section size"), |
| sec->owner, sec); |
| bfd_set_error (bfd_error_bad_value); |
| return false; |
| } |
| if (last_addr >= addr + sec->rawsize) |
| { |
| /* xgettext:c-format */ |
| _bfd_error_handler (_("%pB: %pA points past end of text section"), |
| sec->owner, sec); |
| bfd_set_error (bfd_error_bad_value); |
| return false; |
| } |
| |
| if (sec->size == sec->rawsize) |
| return true; |
| |
| bed = get_elf_backend_data (abfd); |
| BFD_ASSERT (sec->size == sec->rawsize + 8); |
| BFD_ASSERT ((addr & 1) == 0); |
| BFD_ASSERT (bed->cant_unwind_opcode); |
| |
| bfd_put_32 (abfd, addr, cantunwind); |
| bfd_put_32 (abfd, (*bed->cant_unwind_opcode) (info), cantunwind + 4); |
| return bfd_set_section_contents (abfd, sec->output_section, cantunwind, |
| sec->output_offset + sec->rawsize, 8); |
| } |
| |
| /* Write out .eh_frame section. This is called with the relocated |
| contents. */ |
| |
| bool |
| _bfd_elf_write_section_eh_frame (bfd *abfd, |
| struct bfd_link_info *info, |
| asection *sec, |
| bfd_byte *contents) |
| { |
| struct eh_frame_sec_info *sec_info; |
| struct elf_link_hash_table *htab; |
| struct eh_frame_hdr_info *hdr_info; |
| unsigned int ptr_size; |
| struct eh_cie_fde *ent, *last_ent; |
| |
| if (sec->sec_info_type != SEC_INFO_TYPE_EH_FRAME) |
| /* FIXME: octets_per_byte. */ |
| return bfd_set_section_contents (abfd, sec->output_section, contents, |
| sec->output_offset, sec->size); |
| |
| ptr_size = (get_elf_backend_data (abfd) |
| ->elf_backend_eh_frame_address_size (abfd, sec)); |
| BFD_ASSERT (ptr_size != 0); |
| |
| sec_info = (struct eh_frame_sec_info *) elf_section_data (sec)->sec_info; |
| htab = elf_hash_table (info); |
| hdr_info = &htab->eh_info; |
| |
| if (hdr_info->u.dwarf.table && hdr_info->u.dwarf.array == NULL) |
| { |
| hdr_info->frame_hdr_is_compact = false; |
| hdr_info->u.dwarf.array = (struct eh_frame_array_ent *) |
| bfd_malloc (hdr_info->u.dwarf.fde_count |
| * sizeof (*hdr_info->u.dwarf.array)); |
| } |
| if (hdr_info->u.dwarf.array == NULL) |
| hdr_info = NULL; |
| |
| /* The new offsets can be bigger or smaller than the original offsets. |
| We therefore need to make two passes over the section: one backward |
| pass to move entries up and one forward pass to move entries down. |
| The two passes won't interfere with each other because entries are |
| not reordered */ |
| for (ent = sec_info->entry + sec_info->count; ent-- != sec_info->entry;) |
| if (!ent->removed && ent->new_offset > ent->offset) |
| memmove (contents + ent->new_offset, contents + ent->offset, ent->size); |
| |
| for (ent = sec_info->entry; ent < sec_info->entry + sec_info->count; ++ent) |
| if (!ent->removed && ent->new_offset < ent->offset) |
| memmove (contents + ent->new_offset, contents + ent->offset, ent->size); |
| |
| last_ent = sec_info->entry + sec_info->count; |
| for (ent = sec_info->entry; ent < last_ent; ++ent) |
| { |
| unsigned char *buf, *end; |
| unsigned int new_size; |
| |
| if (ent->removed) |
| continue; |
| |
| if (ent->size == 4) |
| { |
| /* Any terminating FDE must be at the end of the section. */ |
| BFD_ASSERT (ent == last_ent - 1); |
| continue; |
| } |
| |
| buf = contents + ent->new_offset; |
| end = buf + ent->size; |
| new_size = next_cie_fde_offset (ent, last_ent, sec) - ent->new_offset; |
| |
| /* Update the size. It may be shrinked. */ |
| bfd_put_32 (abfd, new_size - 4, buf); |
| |
| /* Filling the extra bytes with DW_CFA_nops. */ |
| if (new_size != ent->size) |
| memset (end, 0, new_size - ent->size); |
| |
| if (ent->cie) |
| { |
| /* CIE */ |
| if (ent->make_relative |
| || ent->u.cie.make_lsda_relative |
| || ent->u.cie.per_encoding_relative) |
| { |
| char *aug; |
| unsigned int version, action, extra_string, extra_data; |
| unsigned int per_width, per_encoding; |
| |
| /* Need to find 'R' or 'L' augmentation's argument and modify |
| DW_EH_PE_* value. */ |
| action = ((ent->make_relative ? 1 : 0) |
| | (ent->u.cie.make_lsda_relative ? 2 : 0) |
| | (ent->u.cie.per_encoding_relative ? 4 : 0)); |
| extra_string = extra_augmentation_string_bytes (ent); |
| extra_data = extra_augmentation_data_bytes (ent); |
| |
| /* Skip length, id. */ |
| buf += 8; |
| version = *buf++; |
| aug = (char *) buf; |
| buf += strlen (aug) + 1; |
| skip_leb128 (&buf, end); |
| skip_leb128 (&buf, end); |
| if (version == 1) |
| skip_bytes (&buf, end, 1); |
| else |
| skip_leb128 (&buf, end); |
| if (*aug == 'z') |
| { |
| /* The uleb128 will always be a single byte for the kind |
| of augmentation strings that we're prepared to handle. */ |
| *buf++ += extra_data; |
| aug++; |
| } |
| |
| /* Make room for the new augmentation string and data bytes. */ |
| memmove (buf + extra_string + extra_data, buf, end - buf); |
| memmove (aug + extra_string, aug, buf - (bfd_byte *) aug); |
| buf += extra_string; |
| end += extra_string + extra_data; |
| |
| if (ent->add_augmentation_size) |
| { |
| *aug++ = 'z'; |
| *buf++ = extra_data - 1; |
| } |
| if (ent->u.cie.add_fde_encoding) |
| { |
| BFD_ASSERT (action & 1); |
| *aug++ = 'R'; |
| *buf++ = make_pc_relative (DW_EH_PE_absptr, ptr_size); |
| action &= ~1; |
| } |
| |
| while (action) |
| switch (*aug++) |
| { |
| case 'L': |
| if (action & 2) |
| { |
| BFD_ASSERT (*buf == ent->lsda_encoding); |
| *buf = make_pc_relative (*buf, ptr_size); |
| action &= ~2; |
| } |
| buf++; |
| break; |
| case 'P': |
| if (ent->u.cie.make_per_encoding_relative) |
| *buf = make_pc_relative (*buf, ptr_size); |
| per_encoding = *buf++; |
| per_width = get_DW_EH_PE_width (per_encoding, ptr_size); |
| BFD_ASSERT (per_width != 0); |
| BFD_ASSERT (((per_encoding & 0x70) == DW_EH_PE_pcrel) |
| == ent->u.cie.per_encoding_relative); |
| if ((per_encoding & 0x70) == DW_EH_PE_aligned) |
| buf = (contents |
| + ((buf - contents + per_width - 1) |
| & ~((bfd_size_type) per_width - 1))); |
| if (action & 4) |
| { |
| bfd_vma val; |
| |
| val = read_value (abfd, buf, per_width, |
| get_DW_EH_PE_signed (per_encoding)); |
| if (ent->u.cie.make_per_encoding_relative) |
| val -= (sec->output_section->vma |
| + sec->output_offset |
| + (buf - contents)); |
| else |
| { |
| val += (bfd_vma) ent->offset - ent->new_offset; |
| val -= extra_string + extra_data; |
| } |
| write_value (abfd, buf, val, per_width); |
| action &= ~4; |
| } |
| buf += per_width; |
| break; |
| case 'R': |
| if (action & 1) |
| { |
| BFD_ASSERT (*buf == ent->fde_encoding); |
| *buf = make_pc_relative (*buf, ptr_size); |
| action &= ~1; |
| } |
| buf++; |
| break; |
| case 'S': |
| break; |
| default: |
| BFD_FAIL (); |
| } |
| } |
| } |
| else |
| { |
| /* FDE */ |
| bfd_vma value, address; |
| unsigned int width; |
| bfd_byte *start; |
| struct eh_cie_fde *cie; |
| |
| /* Skip length. */ |
| cie = ent->u.fde.cie_inf; |
| buf += 4; |
| value = ((ent->new_offset + sec->output_offset + 4) |
| - (cie->new_offset + cie->u.cie.u.sec->output_offset)); |
| bfd_put_32 (abfd, value, buf); |
| if (bfd_link_relocatable (info)) |
| continue; |
| buf += 4; |
| width = get_DW_EH_PE_width (ent->fde_encoding, ptr_size); |
| value = read_value (abfd, buf, width, |
| get_DW_EH_PE_signed (ent->fde_encoding)); |
| address = value; |
| if (value) |
| { |
| switch (ent->fde_encoding & 0x70) |
| { |
| case DW_EH_PE_textrel: |
| BFD_ASSERT (hdr_info == NULL); |
| break; |
| case DW_EH_PE_datarel: |
| { |
| switch (abfd->arch_info->arch) |
| { |
| case bfd_arch_ia64: |
| BFD_ASSERT (elf_gp (abfd) != 0); |
| address += elf_gp (abfd); |
| break; |
| default: |
| _bfd_error_handler |
| (_("DW_EH_PE_datarel unspecified" |
| " for this architecture")); |
| /* Fall thru */ |
| case bfd_arch_frv: |
| case bfd_arch_i386: |
| case bfd_arch_nios2: |
| BFD_ASSERT (htab->hgot != NULL |
| && ((htab->hgot->root.type |
| == bfd_link_hash_defined) |
| || (htab->hgot->root.type |
| == bfd_link_hash_defweak))); |
| address |
| += (htab->hgot->root.u.def.value |
| + htab->hgot->root.u.def.section->output_offset |
| + (htab->hgot->root.u.def.section->output_section |
| ->vma)); |
| break; |
| } |
| } |
| break; |
| case DW_EH_PE_pcrel: |
| value += (bfd_vma) ent->offset - ent->new_offset; |
| address += (sec->output_section->vma |
| + sec->output_offset |
| + ent->offset + 8); |
| break; |
| } |
| if (ent->make_relative) |
| value -= (sec->output_section->vma |
| + sec->output_offset |
| + ent->new_offset + 8); |
| write_value (abfd, buf, value, width); |
| } |
| |
| start = buf; |
| |
| if (hdr_info) |
| { |
| /* The address calculation may overflow, giving us a |
| value greater than 4G on a 32-bit target when |
| dwarf_vma is 64-bit. */ |
| if (sizeof (address) > 4 && ptr_size == 4) |
| address &= 0xffffffff; |
| hdr_info->u.dwarf.array[hdr_info->array_count].initial_loc |
| = address; |
| hdr_info->u.dwarf.array[hdr_info->array_count].range |
| = read_value (abfd, buf + width, width, false); |
| hdr_info->u.dwarf.array[hdr_info->array_count++].fde |
| = (sec->output_section->vma |
| + sec->output_offset |
| + ent->new_offset); |
| } |
| |
| if ((ent->lsda_encoding & 0x70) == DW_EH_PE_pcrel |
| || cie->u.cie.make_lsda_relative) |
| { |
| buf += ent->lsda_offset; |
| width = get_DW_EH_PE_width (ent->lsda_encoding, ptr_size); |
| value = read_value (abfd, buf, width, |
| get_DW_EH_PE_signed (ent->lsda_encoding)); |
| if (value) |
| { |
| if ((ent->lsda_encoding & 0x70) == DW_EH_PE_pcrel) |
| value += (bfd_vma) ent->offset - ent->new_offset; |
| else if (cie->u.cie.make_lsda_relative) |
| value -= (sec->output_section->vma |
| + sec->output_offset |
| + ent->new_offset + 8 + ent->lsda_offset); |
| write_value (abfd, buf, value, width); |
| } |
| } |
| else if (ent->add_augmentation_size) |
| { |
| /* Skip the PC and length and insert a zero byte for the |
| augmentation size. */ |
| buf += width * 2; |
| memmove (buf + 1, buf, end - buf); |
| *buf = 0; |
| } |
| |
| if (ent->set_loc) |
| { |
| /* Adjust DW_CFA_set_loc. */ |
| unsigned int cnt; |
| bfd_vma new_offset; |
| |
| width = get_DW_EH_PE_width (ent->fde_encoding, ptr_size); |
| new_offset = ent->new_offset + 8 |
| + extra_augmentation_string_bytes (ent) |
| + extra_augmentation_data_bytes (ent); |
| |
| for (cnt = 1; cnt <= ent->set_loc[0]; cnt++) |
| { |
| buf = start + ent->set_loc[cnt]; |
| |
| value = read_value (abfd, buf, width, |
| get_DW_EH_PE_signed (ent->fde_encoding)); |
| if (!value) |
| continue; |
| |
| if ((ent->fde_encoding & 0x70) == DW_EH_PE_pcrel) |
| value += (bfd_vma) ent->offset + 8 - new_offset; |
| if (ent->make_relative) |
| value -= (sec->output_section->vma |
| + sec->output_offset |
| + new_offset + ent->set_loc[cnt]); |
| write_value (abfd, buf, value, width); |
| } |
| } |
| } |
| } |
| |
| /* FIXME: octets_per_byte. */ |
| return bfd_set_section_contents (abfd, sec->output_section, |
| contents, (file_ptr) sec->output_offset, |
| sec->size); |
| } |
| |
| /* Helper function used to sort .eh_frame_hdr search table by increasing |
| VMA of FDE initial location. */ |
| |
| static int |
| vma_compare (const void *a, const void *b) |
| { |
| const struct eh_frame_array_ent *p = (const struct eh_frame_array_ent *) a; |
| const struct eh_frame_array_ent *q = (const struct eh_frame_array_ent *) b; |
| if (p->initial_loc > q->initial_loc) |
| return 1; |
| if (p->initial_loc < q->initial_loc) |
| return -1; |
| if (p->range > q->range) |
| return 1; |
| if (p->range < q->range) |
| return -1; |
| return 0; |
| } |
| |
| /* Reorder .eh_frame_entry sections to match the associated text sections. |
| This routine is called during the final linking step, just before writing |
| the contents. At this stage, sections in the eh_frame_hdr_info are already |
| sorted in order of increasing text section address and so we simply need |
| to make the .eh_frame_entrys follow that same order. Note that it is |
| invalid for a linker script to try to force a particular order of |
| .eh_frame_entry sections. */ |
| |
| bool |
| _bfd_elf_fixup_eh_frame_hdr (struct bfd_link_info *info) |
| { |
| asection *sec = NULL; |
| asection *osec; |
| struct eh_frame_hdr_info *hdr_info; |
| unsigned int i; |
| bfd_vma offset; |
| struct bfd_link_order *p; |
| |
| hdr_info = &elf_hash_table (info)->eh_info; |
| |
| if (hdr_info->hdr_sec == NULL |
| || info->eh_frame_hdr_type != COMPACT_EH_HDR |
| || hdr_info->array_count == 0) |
| return true; |
| |
| /* Change section output offsets to be in text section order. */ |
| offset = 8; |
| osec = hdr_info->u.compact.entries[0]->output_section; |
| for (i = 0; i < hdr_info->array_count; i++) |
| { |
| sec = hdr_info->u.compact.entries[i]; |
| if (sec->output_section != osec) |
| { |
| _bfd_error_handler |
| (_("invalid output section for .eh_frame_entry: %pA"), |
| sec->output_section); |
| return false; |
| } |
| sec->output_offset = offset; |
| offset += sec->size; |
| } |
| |
| |
| /* Fix the link_order to match. */ |
| for (p = sec->output_section->map_head.link_order; p != NULL; p = p->next) |
| { |
| if (p->type != bfd_indirect_link_order) |
| abort(); |
| |
| p->offset = p->u.indirect.section->output_offset; |
| if (p->next != NULL) |
| i--; |
| } |
| |
| if (i != 0) |
| { |
| _bfd_error_handler |
| (_("invalid contents in %pA section"), osec); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* The .eh_frame_hdr format for Compact EH frames: |
| ubyte version (2) |
| ubyte eh_ref_enc (DW_EH_PE_* encoding of typinfo references) |
| uint32_t count (Number of entries in table) |
| [array from .eh_frame_entry sections] */ |
| |
| static bool |
| write_compact_eh_frame_hdr (bfd *abfd, struct bfd_link_info *info) |
| { |
| struct elf_link_hash_table *htab; |
| struct eh_frame_hdr_info *hdr_info; |
| asection *sec; |
| const struct elf_backend_data *bed; |
| bfd_vma count; |
| bfd_byte contents[8]; |
| unsigned int i; |
| |
| htab = elf_hash_table (info); |
| hdr_info = &htab->eh_info; |
| sec = hdr_info->hdr_sec; |
| |
| if (sec->size != 8) |
| abort(); |
| |
| for (i = 0; i < sizeof (contents); i++) |
| contents[i] = 0; |
| |
| contents[0] = COMPACT_EH_HDR; |
| bed = get_elf_backend_data (abfd); |
| |
| BFD_ASSERT (bed->compact_eh_encoding); |
| contents[1] = (*bed->compact_eh_encoding) (info); |
| |
| count = (sec->output_section->size - 8) / 8; |
| bfd_put_32 (abfd, count, contents + 4); |
| return bfd_set_section_contents (abfd, sec->output_section, contents, |
| (file_ptr) sec->output_offset, sec->size); |
| } |
| |
| /* The .eh_frame_hdr format for DWARF frames: |
| |
| ubyte version (currently 1) |
| ubyte eh_frame_ptr_enc (DW_EH_PE_* encoding of pointer to start of |
| .eh_frame section) |
| ubyte fde_count_enc (DW_EH_PE_* encoding of total FDE count |
| number (or DW_EH_PE_omit if there is no |
| binary search table computed)) |
| ubyte table_enc (DW_EH_PE_* encoding of binary search table, |
| or DW_EH_PE_omit if not present. |
| DW_EH_PE_datarel is using address of |
| .eh_frame_hdr section start as base) |
| [encoded] eh_frame_ptr (pointer to start of .eh_frame section) |
| optionally followed by: |
| [encoded] fde_count (total number of FDEs in .eh_frame section) |
| fde_count x [encoded] initial_loc, fde |
| (array of encoded pairs containing |
| FDE initial_location field and FDE address, |
| sorted by increasing initial_loc). */ |
| |
| static bool |
| write_dwarf_eh_frame_hdr (bfd *abfd, struct bfd_link_info *info) |
| { |
| struct elf_link_hash_table *htab; |
| struct eh_frame_hdr_info *hdr_info; |
| asection *sec; |
| bool retval = true; |
| |
| htab = elf_hash_table (info); |
| hdr_info = &htab->eh_info; |
| sec = hdr_info->hdr_sec; |
| bfd_byte *contents; |
| asection *eh_frame_sec; |
| bfd_size_type size; |
| bfd_vma encoded_eh_frame; |
| |
| size = EH_FRAME_HDR_SIZE; |
| if (hdr_info->u.dwarf.array |
| && hdr_info->array_count == hdr_info->u.dwarf.fde_count) |
| size += 4 + hdr_info->u.dwarf.fde_count * 8; |
| contents = (bfd_byte *) bfd_malloc (size); |
| if (contents == NULL) |
| return false; |
| |
| eh_frame_sec = bfd_get_section_by_name (abfd, ".eh_frame"); |
| if (eh_frame_sec == NULL) |
| { |
| free (contents); |
| return false; |
| } |
| |
| memset (contents, 0, EH_FRAME_HDR_SIZE); |
| /* Version. */ |
| contents[0] = 1; |
| /* .eh_frame offset. */ |
| contents[1] = get_elf_backend_data (abfd)->elf_backend_encode_eh_address |
| (abfd, info, eh_frame_sec, 0, sec, 4, &encoded_eh_frame); |
| |
| if (hdr_info->u.dwarf.array |
| && hdr_info->array_count == hdr_info->u.dwarf.fde_count) |
| { |
| /* FDE count encoding. */ |
| contents[2] = DW_EH_PE_udata4; |
| /* Search table encoding. */ |
| contents[3] = DW_EH_PE_datarel | DW_EH_PE_sdata4; |
| } |
| else |
| { |
| contents[2] = DW_EH_PE_omit; |
| contents[3] = DW_EH_PE_omit; |
| } |
| bfd_put_32 (abfd, encoded_eh_frame, contents + 4); |
| |
| if (contents[2] != DW_EH_PE_omit) |
| { |
| unsigned int i; |
| bool overlap, overflow; |
| |
| bfd_put_32 (abfd, hdr_info->u.dwarf.fde_count, |
| contents + EH_FRAME_HDR_SIZE); |
| qsort (hdr_info->u.dwarf.array, hdr_info->u.dwarf.fde_count, |
| sizeof (*hdr_info->u.dwarf.array), vma_compare); |
| overlap = false; |
| overflow = false; |
| for (i = 0; i < hdr_info->u.dwarf.fde_count; i++) |
| { |
| bfd_vma val; |
| |
| val = hdr_info->u.dwarf.array[i].initial_loc |
| - sec->output_section->vma; |
| val = ((val & 0xffffffff) ^ 0x80000000) - 0x80000000; |
| if (elf_elfheader (abfd)->e_ident[EI_CLASS] == ELFCLASS64 |
| && (hdr_info->u.dwarf.array[i].initial_loc |
| != sec->output_section->vma + val)) |
| overflow = true; |
| bfd_put_32 (abfd, val, contents + EH_FRAME_HDR_SIZE + i * 8 + 4); |
| val = hdr_info->u.dwarf.array[i].fde - sec->output_section->vma; |
| val = ((val & 0xffffffff) ^ 0x80000000) - 0x80000000; |
| if (elf_elfheader (abfd)->e_ident[EI_CLASS] == ELFCLASS64 |
| && (hdr_info->u.dwarf.array[i].fde |
| != sec->output_section->vma + val)) |
| overflow = true; |
| bfd_put_32 (abfd, val, contents + EH_FRAME_HDR_SIZE + i * 8 + 8); |
| if (i != 0 |
| && (hdr_info->u.dwarf.array[i].initial_loc |
| < (hdr_info->u.dwarf.array[i - 1].initial_loc |
| + hdr_info->u.dwarf.array[i - 1].range))) |
| overlap = true; |
| } |
| if (overflow) |
| _bfd_error_handler (_(".eh_frame_hdr entry overflow")); |
| if (overlap) |
| _bfd_error_handler (_(".eh_frame_hdr refers to overlapping FDEs")); |
| if (overflow || overlap) |
| { |
| bfd_set_error (bfd_error_bad_value); |
| retval = false; |
| } |
| } |
| |
| /* FIXME: octets_per_byte. */ |
| if (!bfd_set_section_contents (abfd, sec->output_section, contents, |
| (file_ptr) sec->output_offset, |
| sec->size)) |
| retval = false; |
| free (contents); |
| |
| free (hdr_info->u.dwarf.array); |
| return retval; |
| } |
| |
| /* Write out .eh_frame_hdr section. This must be called after |
| _bfd_elf_write_section_eh_frame has been called on all input |
| .eh_frame sections. */ |
| |
| bool |
| _bfd_elf_write_section_eh_frame_hdr (bfd *abfd, struct bfd_link_info *info) |
| { |
| struct elf_link_hash_table *htab; |
| struct eh_frame_hdr_info *hdr_info; |
| asection *sec; |
| |
| htab = elf_hash_table (info); |
| hdr_info = &htab->eh_info; |
| sec = hdr_info->hdr_sec; |
| |
| if (info->eh_frame_hdr_type == 0 || sec == NULL) |
| return true; |
| |
| if (info->eh_frame_hdr_type == COMPACT_EH_HDR) |
| return write_compact_eh_frame_hdr (abfd, info); |
| else |
| return write_dwarf_eh_frame_hdr (abfd, info); |
| } |
| |
| /* Return the width of FDE addresses. This is the default implementation. */ |
| |
| unsigned int |
| _bfd_elf_eh_frame_address_size (bfd *abfd, const asection *sec ATTRIBUTE_UNUSED) |
| { |
| return elf_elfheader (abfd)->e_ident[EI_CLASS] == ELFCLASS64 ? 8 : 4; |
| } |
| |
| /* Decide whether we can use a PC-relative encoding within the given |
| EH frame section. This is the default implementation. */ |
| |
| bool |
| _bfd_elf_can_make_relative (bfd *input_bfd ATTRIBUTE_UNUSED, |
| struct bfd_link_info *info ATTRIBUTE_UNUSED, |
| asection *eh_frame_section ATTRIBUTE_UNUSED) |
| { |
| return true; |
| } |
| |
| /* Select an encoding for the given address. Preference is given to |
| PC-relative addressing modes. */ |
| |
| bfd_byte |
| _bfd_elf_encode_eh_address (bfd *abfd ATTRIBUTE_UNUSED, |
| struct bfd_link_info *info ATTRIBUTE_UNUSED, |
| asection *osec, bfd_vma offset, |
| asection *loc_sec, bfd_vma loc_offset, |
| bfd_vma *encoded) |
| { |
| *encoded = osec->vma + offset - |
| (loc_sec->output_section->vma + loc_sec->output_offset + loc_offset); |
| return DW_EH_PE_pcrel | DW_EH_PE_sdata4; |
| } |