* elf-eh-frame.c (_bfd_elf_discard_section_eh_frame): Zero relocs
[binutils-gdb.git] / bfd / elf-eh-frame.c
index 14c690be3db20bdac79f851b4359f84f037b1c2b..e80fc6c9d5704ac6d1119cba11afc5a5ad606ec6 100644 (file)
@@ -1,5 +1,5 @@
 /* .eh_frame section optimization.
-   Copyright 2001 Free Software Foundation, Inc.
+   Copyright 2001, 2002 Free Software Foundation, Inc.
    Written by Jakub Jelinek <jakub@redhat.com>.
 
 This file is part of BFD, the Binary File Descriptor library.
@@ -47,6 +47,7 @@ struct cie
   unsigned char fde_encoding;
   unsigned char initial_insn_length;
   unsigned char make_relative;
+  unsigned char make_lsda_relative;
   unsigned char initial_instructions[50];
 };
 
@@ -57,9 +58,13 @@ struct eh_cie_fde
   asection *sec;
   unsigned int new_offset;
   unsigned char fde_encoding;
+  unsigned char lsda_encoding;
+  unsigned char lsda_offset;
   unsigned char cie : 1;
   unsigned char removed : 1;
   unsigned char make_relative : 1;
+  unsigned char make_lsda_relative : 1;
+  unsigned char per_encoding_relative : 1;
 };
 
 struct eh_frame_sec_info
@@ -95,6 +100,10 @@ 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));
+static void write_value
+  PARAMS ((bfd *, bfd_byte *, bfd_vma, int));
 static int cie_compare
   PARAMS ((struct cie *, struct cie *));
 static int vma_compare
@@ -200,6 +209,45 @@ int get_DW_EH_PE_width (encoding, ptr_size)
   return 0;
 }
 
+/* Read a width sized value from memory.  */
+
+static bfd_vma
+read_value (abfd, buf, width)
+     bfd *abfd;
+     bfd_byte *buf;
+     int width;
+{
+  bfd_vma value;
+
+  switch (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 (); 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
@@ -239,7 +287,7 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
      bfd *abfd;
      struct bfd_link_info *info;
      asection *sec, *ehdrsec;
-     boolean (*reloc_symbol_deleted_p) (bfd_vma, PTR);
+     boolean (*reloc_symbol_deleted_p) PARAMS ((bfd_vma, PTR));
      struct elf_reloc_cookie *cookie;
 {
   bfd_byte *ehbuf = NULL, *buf;
@@ -249,7 +297,8 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
   struct eh_frame_hdr_info *hdr_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;
+  unsigned int cie_usage_count, last_cie_ndx, i, offset;
+  unsigned int make_relative, make_lsda_relative;
   Elf_Internal_Rela *rel;
   bfd_size_type new_size;
   unsigned int ptr_size;
@@ -306,6 +355,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)
@@ -418,6 +468,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;
                }
            }
 
@@ -452,6 +506,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;
@@ -535,14 +594,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)
@@ -572,23 +632,47 @@ _bfd_elf_discard_section_eh_frame (abfd, info, sec, ehdrsec,
            goto free_no_table;
          if ((*reloc_symbol_deleted_p) (buf - ehbuf, cookie))
            {
-             cookie->rel = rel;
              /* This is a FDE against discarded section, it should
                 be deleted.  */
              new_size -= hdr.length + 4;
              sec_info->entry[sec_info->count].removed = 1;
+             memset (rel, 0, sizeof (*rel));
            }
          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 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++;
     }
 
@@ -608,9 +692,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)
        {
@@ -628,8 +717,12 @@ _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;
@@ -699,7 +792,7 @@ _bfd_elf_maybe_strip_eh_frame_hdr (info)
   struct eh_frame_hdr_info *hdr_info;
 
   sec = bfd_get_section_by_name (elf_hash_table (info)->dynobj, ".eh_frame_hdr");
-  if (sec == NULL)
+  if (sec == NULL || bfd_is_abs_section (sec->output_section))
     return true;
 
   hdr_info
@@ -717,7 +810,7 @@ _bfd_elf_maybe_strip_eh_frame_hdr (info)
        /* 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)
+       if (o && o->_raw_size > 8 && !bfd_is_abs_section (o->output_section))
          break;
       }
 
@@ -779,10 +872,19 @@ _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;
 
-  return (offset
-         + (sec_info->entry[mid].new_offset - sec_info->entry[mid].offset));
+  /* 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);
 }
 
 /* Write out .eh_frame section.  This is called with the relocated
@@ -833,25 +935,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;
@@ -866,10 +983,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':
@@ -877,25 +1000,43 @@ _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);
+                       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.  */   
@@ -903,45 +1044,36 @@ _bfd_elf_write_section_eh_frame (abfd, sec, ehdrsec, contents)
          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);
+         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)
@@ -951,7 +1083,31 @@ _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);
+             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,
@@ -959,6 +1115,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,
@@ -1023,6 +1191,9 @@ _bfd_elf_write_section_eh_frame_hdr (abfd, sec)
              == ELF_INFO_TYPE_EH_FRAME_HDR);
   hdr_info = (struct eh_frame_hdr_info *)
             elf_section_data (sec)->sec_info;
+  if (hdr_info->strip)
+    return true;
+
   size = EH_FRAME_HDR_SIZE;
   if (hdr_info->array && hdr_info->array_count == hdr_info->fde_count)
     size += 4 + hdr_info->fde_count * 8;