bfd/
[binutils-gdb.git] / bfd / elf-eh-frame.c
index f215fca036bbfcb51237169b2c88d420009f7801..e422aa96a55a55ecba823fdb64d7d3f1750725b5 100644 (file)
@@ -1,22 +1,22 @@
 /* .eh_frame section optimization.
-   Copyright 2001 Free Software Foundation, Inc.
+   Copyright 2001, 2002, 2003 Free Software Foundation, Inc.
    Written by Jakub Jelinek <jakub@redhat.com>.
 
-This file is part of BFD, the Binary File Descriptor library.
+   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 2 of the License, or
-(at your option) any later version.
+   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 2 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.
+   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
 #include "bfd.h"
 #include "sysdep.h"
@@ -26,78 +26,20 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
 #define EH_FRAME_HDR_SIZE 8
 
-struct cie_header
-{
-  unsigned int length;
-  unsigned int id;
-};
-
-struct cie
-{
-  struct cie_header hdr;
-  unsigned char version;
-  unsigned char augmentation[20];
-  unsigned int code_align;
-  int data_align;
-  unsigned int ra_column;
-  unsigned int augmentation_size;
-  struct elf_link_hash_entry *personality;
-  unsigned char per_encoding;
-  unsigned char lsda_encoding;
-  unsigned char fde_encoding;
-  unsigned char initial_insn_length;
-  unsigned char make_relative;
-  unsigned char initial_instructions[50];
-};
-
-struct eh_cie_fde
-{
-  unsigned int offset;
-  unsigned int size;
-  asection *sec;
-  unsigned int new_offset;
-  unsigned char fde_encoding;
-  unsigned char cie : 1;
-  unsigned char removed : 1;
-  unsigned char make_relative : 1;
-};
-
-struct eh_frame_sec_info
-{
-  unsigned int count;
-  unsigned int alloced;
-  struct eh_cie_fde entry[1];
-};
-
-struct eh_frame_array_ent
-{
-  bfd_vma initial_loc;
-  bfd_vma fde;
-};
-
-struct eh_frame_hdr_info
-{
-  struct cie last_cie;
-  asection *last_cie_sec;
-  unsigned int last_cie_offset;
-  unsigned int fde_count, array_count;
-  struct eh_frame_array_ent *array;
-  /* TRUE if .eh_frame_hdr should contain the sorted search table.
-     We build it if we successfully read all .eh_frame input sections
-     and recognize them.  */
-  boolean table;
-};
-
 static bfd_vma read_unsigned_leb128
   PARAMS ((bfd *, char *, unsigned int *));
 static bfd_signed_vma read_signed_leb128
   PARAMS ((bfd *, char *, unsigned int *));
 static int get_DW_EH_PE_width
   PARAMS ((int, int));
+static bfd_vma read_value
+  PARAMS ((bfd *, bfd_byte *, int, int));
+static void write_value
+  PARAMS ((bfd *, bfd_byte *, bfd_vma, int));
 static int cie_compare
   PARAMS ((struct cie *, struct cie *));
 static int vma_compare
-  PARAMS ((const PTR a, const PTR b));
+  PARAMS ((const PTR, const PTR));
 
 /* Helper function for reading uleb128 encoded data.  */
 
@@ -199,6 +141,65 @@ int get_DW_EH_PE_width (encoding, ptr_size)
   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 (abfd, buf, width, is_signed)
+     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 (abfd, buf, value, width)
+     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 zero if C1 and C2 CIEs can be merged.  */
 
 static
@@ -229,34 +230,35 @@ int cie_compare (c1, c2)
 
 /* 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
+   functions.  The function returns TRUE iff any entries have been
    deleted.  */
 
-boolean
-_bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
+bfd_boolean
+_bfd_elf_discard_section_eh_frame (abfd, info, sec,
                                   reloc_symbol_deleted_p, cookie)
      bfd *abfd;
      struct bfd_link_info *info;
-     asection *sec, *ehdrsec;
-     boolean (*reloc_symbol_deleted_p) (bfd_vma, PTR);
+     asection *sec;
+     bfd_boolean (*reloc_symbol_deleted_p) PARAMS ((bfd_vma, PTR));
      struct elf_reloc_cookie *cookie;
 {
   bfd_byte *ehbuf = NULL, *buf;
   bfd_byte *last_cie, *last_fde;
   struct cie_header hdr;
   struct cie cie;
+  struct elf_link_hash_table *htab;
   struct eh_frame_hdr_info *hdr_info;
-  struct eh_frame_sec_info *sec_info;
+  struct eh_frame_sec_info *sec_info = NULL;
   unsigned int leb128_tmp;
-  unsigned int cie_usage_count, last_cie_ndx, i, offset, make_relative;
-  Elf_Internal_Rela *rel;
+  unsigned int cie_usage_count, last_cie_ndx, i, offset;
+  unsigned int make_relative, make_lsda_relative;
   bfd_size_type new_size;
   unsigned int ptr_size;
 
   if (sec->_raw_size == 0)
     {
       /* This file does not contain .eh_frame information.  */
-      return false;
+      return FALSE;
     }
 
   if ((sec->output_section != NULL
@@ -264,26 +266,21 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
     {
       /* At least one of the sections is being discarded from the
          link, so we should just ignore them.  */
-      return false;
+      return FALSE;
     }
 
+  htab = elf_hash_table (info);
+  hdr_info = &htab->eh_info;
+
   /* Read the frame unwind information from abfd.  */
 
   ehbuf = (bfd_byte *) bfd_malloc (sec->_raw_size);
-  if (ehbuf == NULL
-      || ! bfd_get_section_contents (abfd, sec, ehbuf, (bfd_vma) 0,
-                                    sec->_raw_size))
-    {
-      if (elf_section_data (ehdrsec)->sec_info_type
-         != ELF_INFO_TYPE_EH_FRAME_HDR)
-       {
-         elf_section_data (ehdrsec)->sec_info
-           = bfd_zmalloc (sizeof (struct eh_frame_hdr_info));
-         elf_section_data (ehdrsec)->sec_info_type
-           = ELF_INFO_TYPE_EH_FRAME_HDR;
-       }
-      return false;
-    }
+  if (ehbuf == NULL)
+    goto free_no_table;
+
+  if (! bfd_get_section_contents (abfd, sec, ehbuf, (bfd_vma) 0,
+                                 sec->_raw_size))
+    goto free_no_table;
 
   if (sec->_raw_size >= 4
       && bfd_get_32 (abfd, ehbuf) == 0
@@ -291,27 +288,13 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
     {
       /* Empty .eh_frame section.  */
       free (ehbuf);
-      return false;
+      return FALSE;
     }
 
-  if (elf_section_data (ehdrsec)->sec_info_type
-      != ELF_INFO_TYPE_EH_FRAME_HDR)
-    {
-      hdr_info = (struct eh_frame_hdr_info *)      
-                bfd_zmalloc (sizeof (struct eh_frame_hdr_info));
-      hdr_info->table = true;
-      elf_section_data (ehdrsec)->sec_info = hdr_info;
-      elf_section_data (ehdrsec)->sec_info_type
-       = ELF_INFO_TYPE_EH_FRAME_HDR;
-    }
-  else
-    hdr_info = (struct eh_frame_hdr_info *)
-              elf_section_data (ehdrsec)->sec_info;
-
   /* 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).  */
   if (sec->_raw_size != (unsigned int) sec->_raw_size)
-    return false;
+    goto free_no_table;
 
   ptr_size = (elf_elfheader (abfd)->e_ident[EI_CLASS]
              == ELFCLASS64) ? 8 : 4;
@@ -322,6 +305,7 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
   cie_usage_count = 0;
   new_size = sec->_raw_size;
   make_relative = hdr_info->last_cie.make_relative;
+  make_lsda_relative = hdr_info->last_cie.make_lsda_relative;
   sec_info = bfd_zmalloc (sizeof (struct eh_frame_sec_info)
                          + 99 * sizeof (struct eh_cie_fde));
   if (sec_info == NULL)
@@ -331,7 +315,8 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
 #define ENSURE_NO_RELOCS(buf)                          \
   if (cookie->rel < cookie->relend                     \
       && (cookie->rel->r_offset                                \
-         < (bfd_size_type) ((buf) - ehbuf)))           \
+         < (bfd_size_type) ((buf) - ehbuf))            \
+      && cookie->rel->r_info != 0)                     \
     goto free_no_table
 
 #define SKIP_RELOCS(buf)                               \
@@ -380,7 +365,7 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
            /* 64-bit .eh_frame is not supported.  */
            goto free_no_table;
          buf += 4;
-         if ((buf - ehbuf) + hdr.length > sec->_raw_size)
+         if ((bfd_size_type) (buf - ehbuf) + hdr.length > sec->_raw_size)
            /* CIE/FDE not contained fully in this .eh_frame input section.  */
            goto free_no_table;
 
@@ -414,11 +399,12 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
          /* CIE  */
          if (last_cie != NULL)
            {
-             /* Now check if this CIE is identical to last CIE, in which case
-                we can remove it, provided we adjust all FDEs.
-                Also, it can be removed if we have removed all FDEs using
-                that. */
-             if (cie_compare (&cie, &hdr_info->last_cie) == 0
+             /* Now check if this CIE is identical to the last CIE,
+                in which case we can remove it provided we adjust
+                all FDEs.  Also, it can be removed if we have removed
+                all FDEs using it.  */
+             if ((!info->relocatable
+                  && cie_compare (&cie, &hdr_info->last_cie) == 0)
                  || cie_usage_count == 0)
                {
                  new_size -= cie.hdr.length + 4;
@@ -434,6 +420,10 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
                  hdr_info->last_cie_offset = last_cie - ehbuf;
                  sec_info->entry[last_cie_ndx].make_relative
                    = cie.make_relative;
+                 sec_info->entry[last_cie_ndx].make_lsda_relative
+                   = cie.make_lsda_relative;
+                 sec_info->entry[last_cie_ndx].per_encoding_relative
+                   = (cie.per_encoding & 0x70) == DW_EH_PE_pcrel;
                }
            }
 
@@ -468,6 +458,11 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
            }
          read_uleb128 (cie.code_align, buf);
          read_sleb128 (cie.data_align, buf);
+         /* Note - in DWARF2 the return address column is an unsigned byte.
+            In DWARF3 it is a ULEB128.  We are following DWARF3.  For most
+            ports this will not matter as the value will be less than 128.
+            For the others (eg FRV, SH, MMIX, IA64) they need a fixed GCC
+            which conforms to the DWARF3 standard.  */
          read_uleb128 (cie.ra_column, buf);
          ENSURE_NO_RELOCS (buf);
          cie.lsda_encoding = DW_EH_PE_omit;
@@ -512,10 +507,9 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
                               + ((buf - ehbuf + per_width - 1)
                                  & ~((bfd_size_type) per_width - 1)));
                      ENSURE_NO_RELOCS (buf);
-                     rel = GET_RELOC (buf);
                      /* Ensure we have a reloc here, against
                         a global symbol.  */
-                     if (rel != NULL)
+                     if (GET_RELOC (buf) != NULL)
                        {
                          unsigned long r_symndx;
 
@@ -551,14 +545,15 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
            }
 
          /* For shared libraries, try to get rid of as many RELATIVE relocs
-            as possible.
-            FIXME: For this to work, ELF backends need to perform the
-            relocation if omitting dynamic relocs, not skip it.  */
-          if (0
-             && info->shared
+            as possible.  */
+          if (info->shared
              && (cie.fde_encoding & 0xf0) == DW_EH_PE_absptr)
            cie.make_relative = 1;
 
+         if (info->shared
+             && (cie.lsda_encoding & 0xf0) == DW_EH_PE_absptr)
+           cie.make_lsda_relative = 1;
+
          /* If FDE encoding was not specified, it defaults to
             DW_EH_absptr.  */
          if (cie.fde_encoding == DW_EH_PE_omit)
@@ -582,34 +577,55 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
            goto free_no_table;
 
          ENSURE_NO_RELOCS (buf);
-         rel = GET_RELOC (buf);
-         if (rel == NULL)
+         if (GET_RELOC (buf) == NULL)
            /* This should not happen.  */
            goto free_no_table;
          if ((*reloc_symbol_deleted_p) (buf - ehbuf, cookie))
            {
-             cookie->rel = rel;
-             /* This is a FDE against discarded section, it should
+             /* This is a FDE against a discarded section.  It should
                 be deleted.  */
              new_size -= hdr.length + 4;
              sec_info->entry[sec_info->count].removed = 1;
            }
          else
            {
+             if (info->shared
+                 && (((cie.fde_encoding & 0xf0) == DW_EH_PE_absptr
+                      && cie.make_relative == 0)
+                     || (cie.fde_encoding & 0xf0) == DW_EH_PE_aligned))
+               {
+                 /* 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->table = FALSE;
+               }
              cie_usage_count++;
              hdr_info->fde_count++;
            }
-         cookie->rel = rel;
+         if (cie.lsda_encoding != DW_EH_PE_omit)
+           {
+             unsigned int dummy;
+
+             aug = buf;
+             buf += 2 * get_DW_EH_PE_width (cie.fde_encoding, ptr_size);
+             if (cie.augmentation[0] == 'z')
+               read_uleb128 (dummy, buf);
+             /* If some new augmentation data is added before LSDA
+                in FDE augmentation area, this need to be adjusted.  */
+             sec_info->entry[sec_info->count].lsda_offset = (buf - aug);
+           }
          buf = last_fde + 4 + hdr.length;
          SKIP_RELOCS (buf);
        }
 
       sec_info->entry[sec_info->count].fde_encoding = cie.fde_encoding;
+      sec_info->entry[sec_info->count].lsda_encoding = cie.lsda_encoding;
       sec_info->count++;
     }
 
   elf_section_data (sec)->sec_info = sec_info;
-  elf_section_data (sec)->sec_info_type = ELF_INFO_TYPE_EH_FRAME;
+  sec->sec_info_type = ELF_INFO_TYPE_EH_FRAME;
 
   /* Ok, now we can assign new offsets.  */
   offset = 0;
@@ -624,9 +640,14 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
            {
              last_cie_ndx = i;
              make_relative = sec_info->entry[i].make_relative;
+             make_lsda_relative = sec_info->entry[i].make_lsda_relative;
            }
          else
-           sec_info->entry[i].make_relative = make_relative;
+           {
+             sec_info->entry[i].make_relative = make_relative;
+             sec_info->entry[i].make_lsda_relative = make_lsda_relative;
+             sec_info->entry[i].per_encoding_relative = 0;
+           }
        }
       else if (sec_info->entry[i].cie && sec_info->entry[i].sec == sec)
        {
@@ -644,56 +665,103 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
       hdr_info->last_cie_offset = sec_info->entry[last_cie_ndx].new_offset;
     }
 
+  /* FIXME: Currently it is not possible to shrink sections to zero size at
+     this point, so build a fake minimal CIE.  */
+  if (new_size == 0)
+    new_size = 16;
+
   /* Shrink the sec as needed.  */
-  
   sec->_cooked_size = new_size;
   if (sec->_cooked_size == 0)
     sec->flags |= SEC_EXCLUDE;
 
+  free (ehbuf);
   return new_size != sec->_raw_size;
 
 free_no_table:
+  if (ehbuf)
+    free (ehbuf);
   if (sec_info)
     free (sec_info);
-  hdr_info->table = false;
+  hdr_info->table = FALSE;
   hdr_info->last_cie.hdr.length = 0;
-  return false;
+  return FALSE;
 }
 
 /* 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.  */
 
-boolean
-_bfd_elf_discard_section_eh_frame_hdr (abfd, info, sec)
+bfd_boolean
+_bfd_elf_discard_section_eh_frame_hdr (abfd, info)
      bfd *abfd;
      struct bfd_link_info *info;
-     asection *sec;
 {
+  struct elf_link_hash_table *htab;
   struct eh_frame_hdr_info *hdr_info;
-  unsigned int ptr_size;
-
-  ptr_size = (elf_elfheader (abfd)->e_ident[EI_CLASS]
-             == ELFCLASS64) ? 8 : 4;
+  asection *sec;
 
-  if ((elf_section_data (sec)->sec_info_type
-       != ELF_INFO_TYPE_EH_FRAME_HDR)
-      || ! info->eh_frame_hdr)
-    {
-      _bfd_strip_section_from_output (info, sec);
-      return false;
-    }
+  htab = elf_hash_table (info);
+  hdr_info = &htab->eh_info;
+  sec = hdr_info->hdr_sec;
+  if (sec == NULL)
+    return FALSE;
 
-  hdr_info = (struct eh_frame_hdr_info *)
-            elf_section_data (sec)->sec_info;
   sec->_cooked_size = EH_FRAME_HDR_SIZE;
   if (hdr_info->table)
     sec->_cooked_size += 4 + hdr_info->fde_count * 8;
 
   /* Request program headers to be recalculated.  */
   elf_tdata (abfd)->program_header_size = 0;
-  elf_tdata (abfd)->eh_frame_hdr = true;
-  return true;
+  elf_tdata (abfd)->eh_frame_hdr = sec;
+  return TRUE;
+}
+
+/* This function is called from size_dynamic_sections.
+   It needs to decide whether .eh_frame_hdr should be output or not,
+   because later on it is too late for calling _bfd_strip_section_from_output,
+   since dynamic symbol table has been sized.  */
+
+bfd_boolean
+_bfd_elf_maybe_strip_eh_frame_hdr (info)
+     struct bfd_link_info *info;
+{
+  asection *o;
+  bfd *abfd;
+  struct elf_link_hash_table *htab;
+  struct eh_frame_hdr_info *hdr_info;
+
+  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))
+    {
+      hdr_info->hdr_sec = NULL;
+      return TRUE;
+    }
+
+  abfd = NULL;
+  if (info->eh_frame_hdr)
+    for (abfd = info->input_bfds; abfd != NULL; abfd = abfd->link_next)
+      {
+       /* Count only sections which have at least a single CIE or FDE.
+          There cannot be any CIE or FDE <= 8 bytes.  */
+       o = bfd_get_section_by_name (abfd, ".eh_frame");
+       if (o && o->_raw_size > 8 && !bfd_is_abs_section (o->output_section))
+         break;
+      }
+
+  if (abfd == NULL)
+    {
+      _bfd_strip_section_from_output (info, hdr_info->hdr_sec);
+      hdr_info->hdr_sec = NULL;
+      return TRUE;
+    }
+
+  hdr_info->table = TRUE;
+  return TRUE;
 }
 
 /* Adjust an address in the .eh_frame section.  Given OFFSET within
@@ -710,7 +778,7 @@ _bfd_elf_eh_frame_section_offset (output_bfd, sec, offset)
   struct eh_frame_sec_info *sec_info;
   unsigned int lo, hi, mid;
 
-  if (elf_section_data (sec)->sec_info_type != ELF_INFO_TYPE_EH_FRAME)
+  if (sec->sec_info_type != ELF_INFO_TYPE_EH_FRAME)
     return offset;
   sec_info = (struct eh_frame_sec_info *)
             elf_section_data (sec)->sec_info;
@@ -744,22 +812,32 @@ _bfd_elf_eh_frame_section_offset (output_bfd, sec, offset)
   if (sec_info->entry[mid].make_relative
       && ! sec_info->entry[mid].cie
       && offset == sec_info->entry[mid].offset + 8)
-    return (bfd_vma) -1;
+    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].make_lsda_relative
+      && ! sec_info->entry[mid].cie
+      && (offset == (sec_info->entry[mid].offset + 8
+                    + sec_info->entry[mid].lsda_offset)))
+    return (bfd_vma) -2;
 
-  return (offset
-         + (sec_info->entry[mid].new_offset - sec_info->entry[mid].offset));
+  return (offset + sec_info->entry[mid].new_offset
+         - sec_info->entry[mid].offset);
 }
 
 /* Write out .eh_frame section.  This is called with the relocated
    contents.  */
 
-boolean
-_bfd_elf_write_section_eh_frame (abfd, sec, ehdrsec, contents)
+bfd_boolean
+_bfd_elf_write_section_eh_frame (abfd, info, sec, contents)
      bfd *abfd;
-     asection *sec, *ehdrsec;
+     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 i;
   bfd_byte *p, *buf;
@@ -770,26 +848,20 @@ _bfd_elf_write_section_eh_frame (abfd, sec, ehdrsec, contents)
   ptr_size = (elf_elfheader (sec->owner)->e_ident[EI_CLASS]
              == ELFCLASS64) ? 8 : 4;
 
-  if (elf_section_data (sec)->sec_info_type != ELF_INFO_TYPE_EH_FRAME)
+  if (sec->sec_info_type != ELF_INFO_TYPE_EH_FRAME)
     return bfd_set_section_contents (abfd, sec->output_section,
                                     contents,
                                     (file_ptr) sec->output_offset,
                                     sec->_raw_size);
   sec_info = (struct eh_frame_sec_info *)
             elf_section_data (sec)->sec_info;
-  hdr_info = NULL;
-  if (ehdrsec
-      && (elf_section_data (ehdrsec)->sec_info_type
-         == ELF_INFO_TYPE_EH_FRAME_HDR))
-    {
-      hdr_info = (struct eh_frame_hdr_info *)
-                elf_section_data (ehdrsec)->sec_info;
-      if (hdr_info->table && hdr_info->array == NULL)
-       hdr_info->array
-         = bfd_malloc (hdr_info->fde_count * sizeof(*hdr_info->array));
-      if (hdr_info->array == NULL)
-        hdr_info = NULL;
-    }
+  htab = elf_hash_table (info);
+  hdr_info = &htab->eh_info;
+  if (hdr_info->table && hdr_info->array == NULL)
+    hdr_info->array
+      = bfd_malloc (hdr_info->fde_count * sizeof(*hdr_info->array));
+  if (hdr_info->array == NULL)
+    hdr_info = NULL;
 
   p = contents;
   for (i = 0; i < sec_info->count; ++i)
@@ -798,25 +870,40 @@ _bfd_elf_write_section_eh_frame (abfd, sec, ehdrsec, contents)
        {
          if (sec_info->entry[i].cie)
            {
-             cie_offset = sec_info->entry[i].new_offset;
-             cie_offset += (sec_info->entry[i].sec->output_section->vma
-                            + sec_info->entry[i].sec->output_offset
-                            - sec->output_section->vma
-                            - sec->output_offset);
+             /* If CIE is removed due to no remaining FDEs referencing it
+                and there were no CIEs kept before it, sec_info->entry[i].sec
+                will be zero.  */
+             if (sec_info->entry[i].sec == NULL)
+               cie_offset = 0;
+             else
+               {
+                 cie_offset = sec_info->entry[i].new_offset;
+                 cie_offset += (sec_info->entry[i].sec->output_section->vma
+                                + sec_info->entry[i].sec->output_offset
+                                - sec->output_section->vma
+                                - sec->output_offset);
+               }
            }
          continue;
        }
+
       if (sec_info->entry[i].cie)
        {
          /* CIE */
          cie_offset = sec_info->entry[i].new_offset;
-         if (sec_info->entry[i].make_relative)
+         if (sec_info->entry[i].make_relative
+             || sec_info->entry[i].make_lsda_relative
+             || sec_info->entry[i].per_encoding_relative)
            {
              unsigned char *aug;
+             unsigned int action;
              unsigned int dummy, per_width, per_encoding;
 
-             /* Need to find 'R' augmentation's argument and modify
+             /* Need to find 'R' or 'L' augmentation's argument and modify
                 DW_EH_PE_* value.  */
+             action = (sec_info->entry[i].make_relative ? 1 : 0)
+                      | (sec_info->entry[i].make_lsda_relative ? 2 : 0)
+                      | (sec_info->entry[i].per_encoding_relative ? 4 : 0);
              buf = contents + sec_info->entry[i].offset;
              /* Skip length, id and version.  */
              buf += 9;
@@ -831,10 +918,16 @@ _bfd_elf_write_section_eh_frame (abfd, sec, ehdrsec, contents)
                  aug++;
                }
 
-             while (*aug != 'R')
+             while (action)
                switch (*aug++)
                  {
                  case 'L':
+                   if (action & 2)
+                     {
+                       BFD_ASSERT (*buf == sec_info->entry[i].lsda_encoding);
+                       *buf |= DW_EH_PE_pcrel;
+                       action &= ~2;
+                     }
                    buf++;
                    break;
                  case 'P':
@@ -842,71 +935,84 @@ _bfd_elf_write_section_eh_frame (abfd, sec, ehdrsec, contents)
                     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)
+                               == sec_info->entry[i].per_encoding_relative);
                    if ((per_encoding & 0xf0) == DW_EH_PE_aligned)
                      buf = (contents
                             + ((buf - contents + per_width - 1)
                                & ~((bfd_size_type) per_width - 1)));
+                   if (action & 4)
+                     {
+                       bfd_vma value;
+
+                       value = read_value (abfd, buf, per_width,
+                                           get_DW_EH_PE_signed
+                                           (per_encoding));
+                       value += (sec_info->entry[i].offset
+                                 - sec_info->entry[i].new_offset);
+                       write_value (abfd, buf, value, per_width);
+                       action &= ~4;
+                     }
                    buf += per_width;
                    break;
+                 case 'R':
+                   if (action & 1)
+                     {
+                       BFD_ASSERT (*buf == sec_info->entry[i].fde_encoding);
+                       *buf |= DW_EH_PE_pcrel;
+                       action &= ~1;
+                     }
+                   buf++;
+                   break;
                  default:
                    BFD_FAIL ();
                  }
-
-             BFD_ASSERT (*buf == sec_info->entry[i].fde_encoding);
-             *buf |= DW_EH_PE_pcrel;
            }
        }
-      else
+      else if (sec_info->entry[i].size > 4)
        {
          /* FDE */
          bfd_vma value = 0, address;
-         unsigned int fde_width;
+         unsigned int width;
 
          buf = contents + sec_info->entry[i].offset;
-         /* Skip length.  */   
+         /* Skip length.  */
          buf += 4;
          bfd_put_32 (abfd,
                      sec_info->entry[i].new_offset + 4 - cie_offset, buf);
          buf += 4;
-         fde_width = get_DW_EH_PE_width (sec_info->entry[i].fde_encoding,
-                                         ptr_size);
-         switch (fde_width)
-           {
-           case 2: value = bfd_get_16 (abfd, buf); break;
-           case 4: value = bfd_get_32 (abfd, buf); break;
-           case 8: value = bfd_get_64 (abfd, buf); break;
-           default: BFD_FAIL ();
-           }
-         address = value;
-         switch (sec_info->entry[i].fde_encoding & 0xf0)
+         width = get_DW_EH_PE_width (sec_info->entry[i].fde_encoding,
+                                     ptr_size);
+         address = value = read_value (abfd, buf, width,
+                                       get_DW_EH_PE_signed
+                                       (sec_info->entry[i].fde_encoding));
+         if (value)
            {
-           case DW_EH_PE_indirect:
-           case DW_EH_PE_textrel:
-             BFD_ASSERT (hdr_info == NULL);
-             break;
-           case DW_EH_PE_datarel:
-             {
-               asection *got = bfd_get_section_by_name (abfd, ".got");
-
-               BFD_ASSERT (got != NULL);
-               address += got->vma;
-             }
-             break;
-           case DW_EH_PE_pcrel:
-             value += (sec_info->entry[i].offset
-                       - sec_info->entry[i].new_offset);
-             address += (sec->output_section->vma + sec->output_offset
+             switch (sec_info->entry[i].fde_encoding & 0xf0)
+               {
+               case DW_EH_PE_indirect:
+               case DW_EH_PE_textrel:
+                 BFD_ASSERT (hdr_info == NULL);
+                 break;
+               case DW_EH_PE_datarel:
+                 {
+                   asection *got = bfd_get_section_by_name (abfd, ".got");
+
+                   BFD_ASSERT (got != NULL);
+                   address += got->vma;
+                 }
+                 break;
+               case DW_EH_PE_pcrel:
+                 value += (sec_info->entry[i].offset
+                           - sec_info->entry[i].new_offset);
+                 address += (sec->output_section->vma + sec->output_offset
+                             + sec_info->entry[i].offset + 8);
+                 break;
+               }
+             if (sec_info->entry[i].make_relative)
+               value -= (sec->output_section->vma + sec->output_offset
                          + sec_info->entry[i].new_offset + 8);
-             break;
-           }
-         if (sec_info->entry[i].make_relative)
-           value -= (sec->output_section->vma + sec->output_offset
-                     + sec_info->entry[i].new_offset + 8);
-         switch (fde_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;
+             write_value (abfd, buf, value, width);
            }
 
          if (hdr_info)
@@ -916,7 +1022,33 @@ _bfd_elf_write_section_eh_frame (abfd, sec, ehdrsec, contents)
                = (sec->output_section->vma + sec->output_offset
                   + sec_info->entry[i].new_offset);
            }
+
+         if ((sec_info->entry[i].lsda_encoding & 0xf0) == DW_EH_PE_pcrel
+             || sec_info->entry[i].make_lsda_relative)
+           {
+             buf += sec_info->entry[i].lsda_offset;
+             width = get_DW_EH_PE_width (sec_info->entry[i].lsda_encoding,
+                                         ptr_size);
+             value = read_value (abfd, buf, width,
+                                 get_DW_EH_PE_signed
+                                 (sec_info->entry[i].lsda_encoding));
+             if (value)
+               {
+                 if ((sec_info->entry[i].lsda_encoding & 0xf0)
+                     == DW_EH_PE_pcrel)
+                   value += (sec_info->entry[i].offset
+                             - sec_info->entry[i].new_offset);
+                 else if (sec_info->entry[i].make_lsda_relative)
+                   value -= (sec->output_section->vma + sec->output_offset
+                             + sec_info->entry[i].new_offset + 8
+                             + sec_info->entry[i].lsda_offset);
+                 write_value (abfd, buf, value, width);
+               }
+           }
        }
+      else
+       /* Terminating FDE must be at the end of .eh_frame section only.  */
+       BFD_ASSERT (i == sec_info->count - 1);
 
       BFD_ASSERT (p == contents + sec_info->entry[i].new_offset);
       memmove (p, contents + sec_info->entry[i].offset,
@@ -924,6 +1056,18 @@ _bfd_elf_write_section_eh_frame (abfd, sec, ehdrsec, contents)
       p += sec_info->entry[i].size;
     }
 
+  /* FIXME: Once _bfd_elf_discard_section_eh_frame will be able to
+     shrink sections to zero size, this won't be needed any more.  */
+  if (p == contents && sec->_cooked_size == 16)
+    {
+      bfd_put_32 (abfd, 12, p);                /* Fake CIE length */
+      bfd_put_32 (abfd, 0, p + 4);     /* Fake CIE id */
+      p[8] = 1;                                /* Fake CIE version */
+      memset (p + 9, 0, 7);            /* Fake CIE augmentation, 3xleb128
+                                          and 3xDW_CFA_nop as pad  */
+      p += 16;
+    }
+
   BFD_ASSERT ((bfd_size_type) (p - contents) == sec->_cooked_size);
 
   return bfd_set_section_contents (abfd, sec->output_section,
@@ -968,44 +1112,48 @@ vma_compare (a, b)
    fde_count x [encoded] initial_loc, fde
                                (array of encoded pairs containing
                                 FDE initial_location field and FDE address,
-                                sorted by increasing initial_loc)  */
+                                sorted by increasing initial_loc).  */
 
-boolean
-_bfd_elf_write_section_eh_frame_hdr (abfd, sec)
+bfd_boolean
+_bfd_elf_write_section_eh_frame_hdr (abfd, info)
      bfd *abfd;
-     asection *sec;
+     struct bfd_link_info *info;
 {
+  struct elf_link_hash_table *htab;
   struct eh_frame_hdr_info *hdr_info;
-  unsigned int ptr_size;
+  asection *sec;
   bfd_byte *contents;
   asection *eh_frame_sec;
   bfd_size_type size;
+  bfd_boolean retval;
 
-  ptr_size = (elf_elfheader (sec->owner)->e_ident[EI_CLASS]
-             == ELFCLASS64) ? 8 : 4;
+  htab = elf_hash_table (info);
+  hdr_info = &htab->eh_info;
+  sec = hdr_info->hdr_sec;
+  if (sec == NULL)
+    return TRUE;
 
-  BFD_ASSERT (elf_section_data (sec)->sec_info_type
-             == ELF_INFO_TYPE_EH_FRAME_HDR);
-  hdr_info = (struct eh_frame_hdr_info *)
-            elf_section_data (sec)->sec_info;
   size = EH_FRAME_HDR_SIZE;
   if (hdr_info->array && hdr_info->array_count == hdr_info->fde_count)
     size += 4 + hdr_info->fde_count * 8;
   contents = bfd_malloc (size);
   if (contents == NULL)
-    return false;
+    return FALSE;
 
   eh_frame_sec = bfd_get_section_by_name (abfd, ".eh_frame");
   if (eh_frame_sec == NULL)
-    return false;
+    {
+      free (contents);
+      return FALSE;
+    }
 
   memset (contents, 0, EH_FRAME_HDR_SIZE);
-  contents[0] = 1;                             /* Version  */
-  contents[1] = DW_EH_PE_pcrel | DW_EH_PE_sdata4; /* .eh_frame offset  */
+  contents[0] = 1;                             /* Version.  */
+  contents[1] = DW_EH_PE_pcrel | DW_EH_PE_sdata4; /* .eh_frame offset.  */
   if (hdr_info->array && hdr_info->array_count == hdr_info->fde_count)
     {
-      contents[2] = DW_EH_PE_udata4;           /* FDE count encoding  */
-      contents[3] = DW_EH_PE_datarel | DW_EH_PE_sdata4; /* search table enc  */
+      contents[2] = DW_EH_PE_udata4;           /* FDE count encoding.  */
+      contents[3] = DW_EH_PE_datarel | DW_EH_PE_sdata4; /* Search table enc.  */
     }
   else
     {
@@ -1033,7 +1181,9 @@ _bfd_elf_write_section_eh_frame_hdr (abfd, sec)
        }
     }
 
-  return bfd_set_section_contents (abfd, sec->output_section,
-                                  contents, (file_ptr) sec->output_offset,
-                                   sec->_cooked_size);
+  retval = bfd_set_section_contents (abfd, sec->output_section,
+                                    contents, (file_ptr) sec->output_offset,
+                                    sec->_cooked_size);
+  free (contents);
+  return retval;
 }