binutils .eh_frame_hdr fixes

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

binutils .eh_frame_hdr fixes

Mark Kettenis
While investigating an exception handling issue reported by otto@ on
sparc64, I noticed that the .eh_frame_hdr section wasn't properly
populated in some cases.  Turns out the code that parses the .eh_frame
section to populate .eh_frame_hdr has some deficiencies that make it
bail out.  The diff below adds some fixes from upstream (before the
GPLv3 switch).  With these fixes .eh_frame_hdr seems to be populated
correctly.

ok?


Index: gnu/usr.bin/binutils-2.17/bfd/elf-bfd.h
===================================================================
RCS file: /cvs/src/gnu/usr.bin/binutils-2.17/bfd/elf-bfd.h,v
retrieving revision 1.7
diff -u -p -r1.7 elf-bfd.h
--- gnu/usr.bin/binutils-2.17/bfd/elf-bfd.h 3 Dec 2018 02:59:51 -0000 1.7
+++ gnu/usr.bin/binutils-2.17/bfd/elf-bfd.h 2 Feb 2019 15:56:57 -0000
@@ -259,31 +259,6 @@ struct elf_link_loaded_list
 };
 
 /* Structures used by the eh_frame optimization code.  */
-struct cie_header
-{
-  unsigned int length;
-  unsigned int id;
-};
-
-struct cie
-{
-  struct cie_header hdr;
-  unsigned char version;
-  char augmentation[20];
-  bfd_vma code_align;
-  bfd_signed_vma data_align;
-  bfd_vma ra_column;
-  bfd_vma 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 make_lsda_relative;
-  unsigned char initial_instructions[50];
-};
-
 struct eh_cie_fde
 {
   /* For FDEs, this points to the CIE used.  */
@@ -302,12 +277,12 @@ struct eh_cie_fde
   unsigned int make_lsda_relative : 1;
   unsigned int need_lsda_relative : 1;
   unsigned int per_encoding_relative : 1;
+  unsigned int *set_loc;
 };
 
 struct eh_frame_sec_info
 {
   unsigned int count;
-  unsigned int alloced;
   struct eh_cie_fde entry[1];
 };
 
@@ -317,11 +292,11 @@ struct eh_frame_array_ent
   bfd_vma fde;
 };
 
+struct htab;
+
 struct eh_frame_hdr_info
 {
-  struct cie last_cie;
-  asection *last_cie_sec;
-  struct eh_cie_fde *last_cie_inf;
+  struct htab *cies;
   asection *hdr_sec;
   unsigned int fde_count, array_count;
   struct eh_frame_array_ent *array;
Index: gnu/usr.bin/binutils-2.17/bfd/elf-eh-frame.c
===================================================================
RCS file: /cvs/src/gnu/usr.bin/binutils-2.17/bfd/elf-eh-frame.c,v
retrieving revision 1.1.1.1
diff -u -p -r1.1.1.1 elf-eh-frame.c
--- gnu/usr.bin/binutils-2.17/bfd/elf-eh-frame.c 24 Apr 2011 20:14:41 -0000 1.1.1.1
+++ gnu/usr.bin/binutils-2.17/bfd/elf-eh-frame.c 2 Feb 2019 15:56:57 -0000
@@ -26,6 +26,30 @@
 
 #define EH_FRAME_HDR_SIZE 8
 
+struct cie
+{
+  unsigned int length;
+  unsigned int hash;
+  unsigned char version;
+  char augmentation[20];
+  bfd_vma code_align;
+  bfd_signed_vma data_align;
+  bfd_vma ra_column;
+  bfd_vma augmentation_size;
+  struct elf_link_hash_entry *personality;
+  asection *output_sec;
+  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 make_relative;
+  unsigned char 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.  */
 
@@ -180,12 +204,16 @@ write_value (bfd *abfd, bfd_byte *buf, b
     }
 }
 
-/* Return zero if C1 and C2 CIEs can be merged.  */
+/* Return one if C1 and C2 CIEs can be merged.  */
 
-static
-int cie_compare (struct cie *c1, struct cie *c2)
+static int
+cie_eq (const void *e1, const void *e2)
 {
-  if (c1->hdr.length == c2->hdr.length
+  const struct cie *c1 = e1;
+  const struct cie *c2 = e2;
+
+  if (c1->hash == c2->hash
+      && c1->length == c2->length
       && c1->version == c2->version
       && strcmp (c1->augmentation, c2->augmentation) == 0
       && strcmp (c1->augmentation, "eh") != 0
@@ -194,6 +222,7 @@ int cie_compare (struct cie *c1, struct
       && c1->ra_column == c2->ra_column
       && c1->augmentation_size == c2->augmentation_size
       && c1->personality == c2->personality
+      && c1->output_sec == c2->output_sec
       && c1->per_encoding == c2->per_encoding
       && c1->lsda_encoding == c2->lsda_encoding
       && c1->fde_encoding == c2->fde_encoding
@@ -201,9 +230,38 @@ int cie_compare (struct cie *c1, struct
       && memcmp (c1->initial_instructions,
  c2->initial_instructions,
  c1->initial_insn_length) == 0)
-    return 0;
+    return 1;
 
-  return 1;
+  return 0;
+}
+
+static hashval_t
+cie_hash (const void *e)
+{
+  const struct cie *c = e;
+  return c->hash;
+}
+
+static hashval_t
+cie_compute_hash (struct cie *c)
+{
+  hashval_t h = 0;
+  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->output_sec, 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);
+  h = iterative_hash (c->initial_instructions, c->initial_insn_length, h);
+  c->hash = h;
+  return h;
 }
 
 /* Return the number of extra bytes that we'll be inserting into
@@ -273,11 +331,14 @@ skip_cfa_op (bfd_byte **iter, bfd_byte *
   if (!read_byte (iter, end, &op))
     return FALSE;
 
-  switch (op & 0x80 ? op & 0xc0 : op)
+  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;
 
@@ -292,6 +353,8 @@ skip_cfa_op (bfd_byte **iter, bfd_byte *
       /* 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:
@@ -308,6 +371,7 @@ skip_cfa_op (bfd_byte **iter, bfd_byte *
       && 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)
@@ -339,7 +403,8 @@ skip_cfa_op (bfd_byte **iter, bfd_byte *
    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)
+skip_non_nops (bfd_byte *buf, bfd_byte *end, unsigned int encoded_ptr_width,
+       unsigned int *set_loc_count)
 {
   bfd_byte *last;
 
@@ -349,6 +414,8 @@ skip_non_nops (bfd_byte *buf, bfd_byte *
       buf++;
     else
       {
+ if (*buf == DW_CFA_set_loc)
+  ++*set_loc_count;
  if (!skip_cfa_op (&buf, end, encoded_ptr_width))
   return 0;
  last = buf;
@@ -374,15 +441,24 @@ _bfd_elf_discard_section_eh_frame
   while (0)
 
   bfd_byte *ehbuf = NULL, *buf;
-  bfd_byte *last_cie, *last_fde;
-  struct eh_cie_fde *ent, *last_cie_inf, *this_inf;
-  struct cie_header hdr;
-  struct cie cie;
+  bfd_byte *last_fde;
+  struct eh_cie_fde *ent, *this_inf;
+  unsigned int hdr_length, hdr_id;
+  struct extended_cie
+    {
+      struct cie cie;
+      unsigned int offset;
+      unsigned int usage_count;
+      unsigned int entry;
+    } *ecies = NULL, *ecie;
+  unsigned int ecie_count = 0, ecie_alloced = 0;
+  struct cie *cie;
   struct elf_link_hash_table *htab;
   struct eh_frame_hdr_info *hdr_info;
   struct eh_frame_sec_info *sec_info = NULL;
-  unsigned int cie_usage_count, offset;
+  unsigned int offset;
   unsigned int ptr_size;
+  unsigned int entry_alloced;
 
   if (sec->size == 0)
     {
@@ -390,8 +466,7 @@ _bfd_elf_discard_section_eh_frame
       return FALSE;
     }
 
-  if ((sec->output_section != NULL
-       && bfd_is_abs_section (sec->output_section)))
+  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.  */
@@ -401,6 +476,9 @@ _bfd_elf_discard_section_eh_frame
   htab = elf_hash_table (info);
   hdr_info = &htab->eh_info;
 
+  if (hdr_info->cies == NULL && !info->relocatable)
+    hdr_info->cies = htab_try_create (1, cie_hash, cie_eq, free);
+
   /* Read the frame unwind information from abfd.  */
 
   REQUIRE (bfd_malloc_and_get_section (abfd, sec, &ehbuf));
@@ -423,15 +501,11 @@ _bfd_elf_discard_section_eh_frame
   REQUIRE (ptr_size != 0);
 
   buf = ehbuf;
-  last_cie = NULL;
-  last_cie_inf = NULL;
-  memset (&cie, 0, sizeof (cie));
-  cie_usage_count = 0;
   sec_info = bfd_zmalloc (sizeof (struct eh_frame_sec_info)
   + 99 * sizeof (struct eh_cie_fde));
   REQUIRE (sec_info);
 
-  sec_info->alloced = 100;
+  entry_alloced = 100;
 
 #define ENSURE_NO_RELOCS(buf) \
   REQUIRE (!(cookie->rel < cookie->relend \
@@ -454,118 +528,84 @@ _bfd_elf_discard_section_eh_frame
   for (;;)
     {
       char *aug;
-      bfd_byte *start, *end, *insns;
+      bfd_byte *start, *end, *insns, *insns_end;
       bfd_size_type length;
+      unsigned int set_loc_count;
 
-      if (sec_info->count == sec_info->alloced)
+      if (sec_info->count == entry_alloced)
  {
-  struct eh_cie_fde *old_entry = sec_info->entry;
   sec_info = bfd_realloc (sec_info,
   sizeof (struct eh_frame_sec_info)
-  + ((sec_info->alloced + 99)
+  + ((entry_alloced + 99)
      * sizeof (struct eh_cie_fde)));
   REQUIRE (sec_info);
 
-  memset (&sec_info->entry[sec_info->alloced], 0,
+  memset (&sec_info->entry[entry_alloced], 0,
   100 * sizeof (struct eh_cie_fde));
-  sec_info->alloced += 100;
-
-  /* Now fix any pointers into the array.  */
-  if (last_cie_inf >= old_entry
-      && last_cie_inf < old_entry + sec_info->count)
-    last_cie_inf = sec_info->entry + (last_cie_inf - old_entry);
+  entry_alloced += 100;
  }
 
       this_inf = sec_info->entry + sec_info->count;
       last_fde = buf;
-      /* If we are at the end of the section, we still need to decide
- on whether to output or discard last encountered CIE (if any).  */
+
       if ((bfd_size_type) (buf - ehbuf) == sec->size)
- {
-  hdr.length = 0;
-  hdr.id = (unsigned int) -1;
-  end = buf;
- }
-      else
- {
-  /* Read the length of the entry.  */
-  REQUIRE (skip_bytes (&buf, ehbuf + sec->size, 4));
-  hdr.length = bfd_get_32 (abfd, buf - 4);
+ break;
 
-  /* 64-bit .eh_frame is not supported.  */
-  REQUIRE (hdr.length != 0xffffffff);
+      /* 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;
+      /* 64-bit .eh_frame is not supported.  */
+      REQUIRE (hdr_length != 0xffffffff);
 
-  this_inf->offset = last_fde - ehbuf;
-  this_inf->size = 4 + hdr.length;
+      /* 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;
 
-  if (hdr.length == 0)
-    {
-      /* A zero-length CIE should only be found at the end of
- the section.  */
-      REQUIRE ((bfd_size_type) (buf - ehbuf) == sec->size);
-      ENSURE_NO_RELOCS (buf);
-      sec_info->count++;
-      /* Now just finish last encountered CIE processing and break
- the loop.  */
-      hdr.id = (unsigned int) -1;
-    }
-  else
-    {
-      REQUIRE (skip_bytes (&buf, end, 4));
-      hdr.id = bfd_get_32 (abfd, buf - 4);
-      REQUIRE (hdr.id != (unsigned int) -1);
-    }
+      this_inf->offset = last_fde - ehbuf;
+      this_inf->size = 4 + hdr_length;
+
+      if (hdr_length == 0)
+ {
+  /* A zero-length CIE should only be found at the end of
+     the section.  */
+  REQUIRE ((bfd_size_type) (buf - ehbuf) == sec->size);
+  ENSURE_NO_RELOCS (buf);
+  sec_info->count++;
+  break;
  }
 
-      if (hdr.id == 0 || hdr.id == (unsigned int) -1)
+      REQUIRE (skip_bytes (&buf, end, 4));
+      hdr_id = bfd_get_32 (abfd, buf - 4);
+
+      if (hdr_id == 0)
  {
   unsigned int initial_insn_length;
 
   /* CIE  */
-  if (last_cie != NULL)
+  this_inf->cie = 1;
+
+  if (ecie_count == ecie_alloced)
     {
-      /* 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
-   && hdr_info->last_cie_sec
-   && (sec->output_section
-       == hdr_info->last_cie_sec->output_section)
-   && cie_compare (&cie, &hdr_info->last_cie) == 0)
-  || cie_usage_count == 0)
- last_cie_inf->removed = 1;
-      else
- {
-  hdr_info->last_cie = cie;
-  hdr_info->last_cie_sec = sec;
-  last_cie_inf->make_relative = cie.make_relative;
-  last_cie_inf->make_lsda_relative = cie.make_lsda_relative;
-  last_cie_inf->per_encoding_relative
-    = (cie.per_encoding & 0x70) == DW_EH_PE_pcrel;
- }
+      ecies = bfd_realloc (ecies,
+   (ecie_alloced + 20) * sizeof (*ecies));
+      REQUIRE (ecies);
+      memset (&ecies[ecie_alloced], 0, 20 * sizeof (*ecies));
+      ecie_alloced += 20;
     }
 
-  if (hdr.id == (unsigned int) -1)
-    break;
-
-  last_cie_inf = this_inf;
-  this_inf->cie = 1;
-
-  cie_usage_count = 0;
-  memset (&cie, 0, sizeof (cie));
-  cie.hdr = hdr;
-  REQUIRE (read_byte (&buf, end, &cie.version));
+  cie = &ecies[ecie_count].cie;
+  ecies[ecie_count].offset = this_inf->offset;
+  ecies[ecie_count++].entry = sec_info->count;
+  cie->length = hdr_length;
+  start = buf;
+  REQUIRE (read_byte (&buf, end, &cie->version));
 
   /* Cannot handle unknown versions.  */
-  REQUIRE (cie.version == 1 || cie.version == 3);
-  REQUIRE (strlen ((char *) buf) < sizeof (cie.augmentation));
+  REQUIRE (cie->version == 1 || cie->version == 3);
+  REQUIRE (strlen ((char *) buf) < sizeof (cie->augmentation));
 
-  strcpy (cie.augmentation, (char *) buf);
+  strcpy (cie->augmentation, (char *) buf);
   buf = (bfd_byte *) strchr ((char *) buf, '\0') + 1;
   ENSURE_NO_RELOCS (buf);
   if (buf[0] == 'e' && buf[1] == 'h')
@@ -577,26 +617,26 @@ _bfd_elf_discard_section_eh_frame
       REQUIRE (skip_bytes (&buf, end, ptr_size));
       SKIP_RELOCS (buf);
     }
-  REQUIRE (read_uleb128 (&buf, end, &cie.code_align));
-  REQUIRE (read_sleb128 (&buf, end, &cie.data_align));
-  if (cie.version == 1)
+  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++;
+      cie->ra_column = *buf++;
     }
   else
-    REQUIRE (read_uleb128 (&buf, end, &cie.ra_column));
+    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;
+  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));
+  REQUIRE (read_uleb128 (&buf, end, &cie->augmentation_size));
     ENSURE_NO_RELOCS (buf);
  }
 
@@ -604,14 +644,14 @@ _bfd_elf_discard_section_eh_frame
  switch (*aug++)
   {
   case 'L':
-    REQUIRE (read_byte (&buf, end, &cie.lsda_encoding));
+    REQUIRE (read_byte (&buf, end, &cie->lsda_encoding));
     ENSURE_NO_RELOCS (buf);
-    REQUIRE (get_DW_EH_PE_width (cie.lsda_encoding, ptr_size));
+    REQUIRE (get_DW_EH_PE_width (cie->lsda_encoding, ptr_size));
     break;
   case 'R':
-    REQUIRE (read_byte (&buf, end, &cie.fde_encoding));
+    REQUIRE (read_byte (&buf, end, &cie->fde_encoding));
     ENSURE_NO_RELOCS (buf);
-    REQUIRE (get_DW_EH_PE_width (cie.fde_encoding, ptr_size));
+    REQUIRE (get_DW_EH_PE_width (cie->fde_encoding, ptr_size));
     break;
   case 'S':
     break;
@@ -619,11 +659,11 @@ _bfd_elf_discard_section_eh_frame
     {
       int per_width;
 
-      REQUIRE (read_byte (&buf, end, &cie.per_encoding));
-      per_width = get_DW_EH_PE_width (cie.per_encoding,
+      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 & 0xf0) == DW_EH_PE_aligned)
+      if ((cie->per_encoding & 0xf0) == DW_EH_PE_aligned)
  {
   length = -(buf - ehbuf) & (per_width - 1);
   REQUIRE (skip_bytes (&buf, end, length));
@@ -653,7 +693,7 @@ _bfd_elf_discard_section_eh_frame
  h = (struct elf_link_hash_entry *)
     h->root.u.i.link;
 
-      cie.personality = h;
+      cie->personality = h;
     }
   /* Cope with MIPS-style composite relocations.  */
   do
@@ -661,6 +701,7 @@ _bfd_elf_discard_section_eh_frame
   while (GET_RELOC (buf) != NULL);
  }
       REQUIRE (skip_bytes (&buf, end, per_width));
+      REQUIRE (cie->personality);
     }
     break;
   default:
@@ -676,18 +717,18 @@ _bfd_elf_discard_section_eh_frame
   ->elf_backend_can_make_relative_eh_frame
   (abfd, info, sec)))
     {
-      if ((cie.fde_encoding & 0xf0) == DW_EH_PE_absptr)
- cie.make_relative = 1;
+      if ((cie->fde_encoding & 0xf0) == DW_EH_PE_absptr)
+ cie->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 & 0xf0) != DW_EH_PE_aligned)
+      else if (cie->fde_encoding == DW_EH_PE_omit
+       && (cie->per_encoding & 0xf0) != DW_EH_PE_aligned)
  {
-  if (*cie.augmentation == 0)
+  if (*cie->augmentation == 0)
     this_inf->add_augmentation_size = 1;
   this_inf->add_fde_encoding = 1;
-  cie.make_relative = 1;
+  cie->make_relative = 1;
  }
     }
 
@@ -695,30 +736,36 @@ _bfd_elf_discard_section_eh_frame
       && (get_elf_backend_data (abfd)
   ->elf_backend_can_make_lsda_relative_eh_frame
   (abfd, info, sec))
-      && (cie.lsda_encoding & 0xf0) == DW_EH_PE_absptr)
-    cie.make_lsda_relative = 1;
+      && (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)
-    cie.fde_encoding = DW_EH_PE_absptr;
+  if (cie->fde_encoding == DW_EH_PE_omit)
+    cie->fde_encoding = DW_EH_PE_absptr;
 
   initial_insn_length = end - buf;
-  if (initial_insn_length <= 50)
+  if (initial_insn_length <= sizeof (cie->initial_instructions))
     {
-      cie.initial_insn_length = initial_insn_length;
-      memcpy (cie.initial_instructions, buf, initial_insn_length);
+      cie->initial_insn_length = initial_insn_length;
+      memcpy (cie->initial_instructions, buf, initial_insn_length);
     }
   insns = buf;
   buf += initial_insn_length;
   ENSURE_NO_RELOCS (buf);
-  last_cie = last_fde;
  }
       else
  {
-  /* Ensure this FDE uses the last CIE encountered.  */
-  REQUIRE (last_cie);
-  REQUIRE (hdr.id == (unsigned int) (buf - 4 - last_cie));
+  /* Find the corresponding CIE.  */
+  unsigned int cie_offset = this_inf->offset + 4 - hdr_id;
+  for (ecie = ecies; ecie < ecies + ecie_count; ++ecie)
+    if (cie_offset == ecie->offset)
+      break;
+
+  /* Ensure this FDE references one of the CIEs in this input
+     section.  */
+  REQUIRE (ecie != ecies + ecie_count);
+  cie = &ecie->cie;
 
   ENSURE_NO_RELOCS (buf);
   REQUIRE (GET_RELOC (buf));
@@ -730,9 +777,9 @@ _bfd_elf_discard_section_eh_frame
   else
     {
       if (info->shared
-  && (((cie.fde_encoding & 0xf0) == DW_EH_PE_absptr
-       && cie.make_relative == 0)
-      || (cie.fde_encoding & 0xf0) == DW_EH_PE_aligned))
+  && (((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,
@@ -740,16 +787,18 @@ _bfd_elf_discard_section_eh_frame
      since it is affected by runtime relocations.  */
   hdr_info->table = FALSE;
  }
-      cie_usage_count++;
+      ecie->usage_count++;
       hdr_info->fde_count++;
+      this_inf->cie_inf = (void *) (ecie - ecies);
     }
+
   /* Skip the initial location and address range.  */
   start = buf;
-  length = get_DW_EH_PE_width (cie.fde_encoding, ptr_size);
+  length = get_DW_EH_PE_width (cie->fde_encoding, ptr_size);
   REQUIRE (skip_bytes (&buf, end, 2 * length));
 
   /* Skip the augmentation size, if present.  */
-  if (cie.augmentation[0] == 'z')
+  if (cie->augmentation[0] == 'z')
     REQUIRE (read_uleb128 (&buf, end, &length));
   else
     length = 0;
@@ -757,12 +806,12 @@ _bfd_elf_discard_section_eh_frame
   /* 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)
+  if (cie->lsda_encoding != DW_EH_PE_omit)
     {
       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')
+      if (cie->augmentation[0] != 'z')
  length = end - buf;
     }
 
@@ -770,40 +819,110 @@ _bfd_elf_discard_section_eh_frame
   REQUIRE (skip_bytes (&buf, end, length));
   insns = buf;
 
-  buf = last_fde + 4 + hdr.length;
+  buf = last_fde + 4 + hdr_length;
   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
- including the padding.  */
-      length = get_DW_EH_PE_width (cie.fde_encoding, ptr_size);
-      insns = skip_non_nops (insns, end, length);
-      if (insns != 0)
- this_inf->size -= end - insns;
+ 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 & 0xf0) == DW_EH_PE_pcrel
+      || cie->make_relative))
+ {
+  unsigned int cnt;
+  bfd_byte *p;
 
-      this_inf->fde_encoding = cie.fde_encoding;
-      this_inf->lsda_encoding = cie.lsda_encoding;
+  this_inf->set_loc = 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->fde_encoding = cie->fde_encoding;
+      this_inf->lsda_encoding = cie->lsda_encoding;
       sec_info->count++;
     }
 
   elf_section_data (sec)->sec_info = sec_info;
   sec->sec_info_type = ELF_INFO_TYPE_EH_FRAME;
 
+  /* Look at all CIEs in this section and determine which can be
+     removed as unused, which can be merged with previous duplicate
+     CIEs and which need to be kept.  */
+  for (ecie = ecies; ecie < ecies + ecie_count; ++ecie)
+    {
+      if (ecie->usage_count == 0)
+ {
+  sec_info->entry[ecie->entry].removed = 1;
+  continue;
+ }
+      ecie->cie.output_sec = sec->output_section;
+      ecie->cie.cie_inf = sec_info->entry + ecie->entry;
+      cie_compute_hash (&ecie->cie);
+      if (hdr_info->cies != NULL)
+ {
+  void **loc = htab_find_slot_with_hash (hdr_info->cies, &ecie->cie,
+ ecie->cie.hash, INSERT);
+  if (loc != NULL)
+    {
+      if (*loc != HTAB_EMPTY_ENTRY)
+ {
+  sec_info->entry[ecie->entry].removed = 1;
+  ecie->cie.cie_inf = ((struct cie *) *loc)->cie_inf;
+  continue;
+ }
+
+      *loc = malloc (sizeof (struct cie));
+      if (*loc == NULL)
+ *loc = HTAB_DELETED_ENTRY;
+      else
+ memcpy (*loc, &ecie->cie, sizeof (struct cie));
+    }
+ }
+      ecie->cie.cie_inf->make_relative = ecie->cie.make_relative;
+      ecie->cie.cie_inf->make_lsda_relative = ecie->cie.make_lsda_relative;
+      ecie->cie.cie_inf->per_encoding_relative
+ = (ecie->cie.per_encoding & 0x70) == DW_EH_PE_pcrel;
+    }
+
   /* Ok, now we can assign new offsets.  */
   offset = 0;
-  last_cie_inf = hdr_info->last_cie_inf;
   for (ent = sec_info->entry; ent < sec_info->entry + sec_info->count; ++ent)
     if (!ent->removed)
       {
- if (ent->cie)
-  last_cie_inf = ent;
- else
-  ent->cie_inf = last_cie_inf;
+ if (!ent->cie)
+  {
+    ecie = ecies + (unsigned long) ent->cie_inf;
+    ent->cie_inf = ecie->cie.cie_inf;
+  }
  ent->new_offset = offset;
  offset += size_of_output_cie_fde (ent, ptr_size);
       }
-  hdr_info->last_cie_inf = last_cie_inf;
 
   /* Resize the sec as needed.  */
   sec->rawsize = sec->size;
@@ -812,6 +931,8 @@ _bfd_elf_discard_section_eh_frame
     sec->flags |= SEC_EXCLUDE;
 
   free (ehbuf);
+  if (ecies)
+    free (ecies);
   return offset != sec->rawsize;
 
 free_no_table:
@@ -819,8 +940,9 @@ free_no_table:
     free (ehbuf);
   if (sec_info)
     free (sec_info);
+  if (ecies)
+    free (ecies);
   hdr_info->table = FALSE;
-  hdr_info->last_cie.hdr.length = 0;
   return FALSE;
 
 #undef REQUIRE
@@ -839,6 +961,13 @@ _bfd_elf_discard_section_eh_frame_hdr (b
 
   htab = elf_hash_table (info);
   hdr_info = &htab->eh_info;
+
+  if (hdr_info->cies != NULL)
+    {
+      htab_delete (hdr_info->cies);
+      hdr_info->cies = NULL;
+    }
+
   sec = hdr_info->hdr_sec;
   if (sec == NULL)
     return FALSE;
@@ -968,6 +1097,23 @@ _bfd_elf_eh_frame_section_offset (bfd *o
       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].cie
+  ? sec_info->entry[mid].make_relative
+  : sec_info->entry[mid].cie_inf->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;
+    }
+
   if (hdr_info->offsets_adjusted)
     offset -= sec->output_offset;
   /* Any new augmentation bytes go before the first relocation.  */
@@ -1192,6 +1338,7 @@ _bfd_elf_write_section_eh_frame (bfd *ab
   /* FDE */
   bfd_vma value, address;
   unsigned int width;
+  bfd_byte *start;
 
   /* Skip length.  */
   buf += 4;
@@ -1228,6 +1375,8 @@ _bfd_elf_write_section_eh_frame (bfd *ab
       write_value (abfd, buf, value, width);
     }
 
+  start = buf;
+
   if (hdr_info)
     {
       hdr_info->array[hdr_info->array_count].initial_loc = address;
@@ -1259,6 +1408,36 @@ _bfd_elf_write_section_eh_frame (bfd *ab
       buf += width * 2;
       memmove (buf + 1, buf, end - buf);
       *buf = 0;
+    }
+
+  if (ent->set_loc)
+    {
+      /* Adjust DW_CFA_set_loc.  */
+      unsigned int cnt, width;
+      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++)
+ {
+  bfd_vma value;
+  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 & 0xf0) == DW_EH_PE_pcrel)
+    value += ent->offset + 8 - new_offset;
+  if (ent->cie_inf->make_relative)
+    value -= sec->output_section->vma + new_offset
+     + ent->set_loc[cnt];
+  write_value (abfd, buf, value, width);
+ }
     }
  }
     }