[GOLD] PowerPC recreate eh_frame for stubs on each relax pass
authorAlan Modra <amodra@gmail.com>
Tue, 1 Aug 2017 04:38:53 +0000 (14:08 +0930)
committerAlan Modra <amodra@gmail.com>
Tue, 1 Aug 2017 04:38:53 +0000 (14:08 +0930)
There is a very small but non-zero probability that a stub group
contains stubs on one relax pass, but does not on the next.  In that
case we would get an FDE covering a zero length address range.
(Actually, it's even worse.  Alignment padding for stubs can mean the
address for the non-existent stubs is past the end of the original
section to which stubs are attached, and due to the way
do_plt_fde_location calculates the length we can get a negative
length.)  Fixing this properly requires removing the FDE.

Also, I have been implementing the __tls_get_addr_opt support for
gold, and that stub needs something other than the default FDE.  The
necessary FDE will depend on the offset to the __tls_get_addr_opt
stub, which of course can change during relaxation.  That means at the
very least, rewriting the FDE on each pass, possibly changing the FDE
size.  I think that is better done by completely recreating PLT
eh_frame FDEs.

* ehframe.cc (Fde::operator==): New.
(Cie::remove_fde, Eh_frame::remove_ehframe_for_plt): New.
* ehframe.h (Fde::operator==): Declare.
(Cie::remove_fde, Eh_frame::remove_ehframe_for_plt): Likewise.
* layout.cc (Layout::remove_eh_frame_for_plt): New.
* layout.h (Layout::remove_eh_frame_for_plt): Declare.
* powerpc.cc (Target_powerpc::do_relax): Remove old eh_frame FDEs.
(Stub_table::add_eh_frame): Delete eh_frame_added_ condition.
Don't add eh_frame for empty stub section.
(Stub_table::remove_eh_frame): New.

gold/ChangeLog
gold/ehframe.cc
gold/ehframe.h
gold/layout.cc
gold/layout.h
gold/powerpc.cc

index 869a01ddd52cc2fc40d578861699717131d0afde..c886cc1ca4f11cc90b32c73edae8017079e5b253 100644 (file)
@@ -1,3 +1,16 @@
+2017-08-01  Alan Modra  <amodra@gmail.com>
+
+       * ehframe.cc (Fde::operator==): New.
+       (Cie::remove_fde, Eh_frame::remove_ehframe_for_plt): New.
+       * ehframe.h (Fde::operator==): Declare.
+       (Cie::remove_fde, Eh_frame::remove_ehframe_for_plt): Likewise.
+       * layout.cc (Layout::remove_eh_frame_for_plt): New.
+       * layout.h (Layout::remove_eh_frame_for_plt): Declare.
+       * powerpc.cc (Target_powerpc::do_relax): Remove old eh_frame FDEs.
+       (Stub_table::add_eh_frame): Delete eh_frame_added_ condition.
+       Don't add eh_frame for empty stub section.
+       (Stub_table::remove_eh_frame): New.
+
 2017-07-31  Alan Modra  <amodra@gmail.com>
 
        * options.h (no_tls_optimize): New powerpc option.
index fd72a4fd06c96c3e7dbcae92ac1c6b5ea95194c9..61b51932bcfcdb715352ca53d668045fc0bcf3ed 100644 (file)
@@ -325,6 +325,21 @@ Eh_frame_hdr::get_fde_addresses(Output_file* of,
 
 // Class Fde.
 
+bool
+Fde::operator==(const Fde& that) const
+{
+  if (this->object_ != that.object_
+      || this->contents_ != that.contents_)
+    return false;
+  if (this->object_ == NULL)
+    return (this->u_.from_linker.plt == that.u_.from_linker.plt
+           && this->u_.from_linker.post_map == that.u_.from_linker.post_map);
+  else
+    return (this->u_.from_object.shndx == that.u_.from_object.shndx
+           && (this->u_.from_object.input_offset
+               == that.u_.from_object.input_offset));
+}
+
 // Write the FDE to OVIEW starting at OFFSET.  CIE_OFFSET is the
 // offset of the CIE in OVIEW.  OUTPUT_OFFSET is the offset of the
 // Eh_frame section within the output section.  FDE_ENCODING is the
@@ -443,6 +458,15 @@ Cie::set_output_offset(section_offset_type output_offset,
   return output_offset + length;
 }
 
+// Remove FDE.  Only the last FDE using this CIE may be removed.
+
+void
+Cie::remove_fde(const Fde* fde)
+{
+  gold_assert(*fde == *this->fdes_.back());
+  this->fdes_.pop_back();
+}
+
 // Write the CIE to OVIEW starting at OFFSET.  OUTPUT_OFFSET is the
 // offset of the Eh_frame section within the output section.  Round up
 // the bytes to ADDRALIGN.  ADDRESS is the virtual address of OVIEW.
@@ -1143,6 +1167,28 @@ Eh_frame::add_ehframe_for_plt(Output_data* plt, const unsigned char* cie_data,
     this->final_data_size_ += align_address(fde_length + 8, this->addralign());
 }
 
+// Remove unwind information for a PLT.  Only the last FDE added may be removed.
+
+void
+Eh_frame::remove_ehframe_for_plt(Output_data* plt,
+                                const unsigned char* cie_data,
+                                size_t cie_length,
+                                const unsigned char* fde_data,
+                                size_t fde_length)
+{
+  Cie cie(NULL, 0, 0, elfcpp::DW_EH_PE_pcrel | elfcpp::DW_EH_PE_sdata4, "",
+         cie_data, cie_length);
+  Cie_offsets::iterator find_cie = this->cie_offsets_.find(&cie);
+  gold_assert (find_cie != this->cie_offsets_.end());
+  Cie* pcie = *find_cie;
+
+  Fde* fde = new Fde(plt, fde_data, fde_length, this->mappings_are_done_);
+  pcie->remove_fde(fde);
+
+  if (this->mappings_are_done_)
+    this->final_data_size_ -= align_address(fde_length + 8, this->addralign());
+}
+
 // Return the number of FDEs.
 
 unsigned int
index 347ce46e45b5087ff4350e672e8e0717937496ec..f501634c2f62afa2679ddb19f962cabec423f834 100644 (file)
@@ -217,6 +217,8 @@ class Fde
        section_offset_type cie_offset, unsigned char fde_encoding,
        Eh_frame_hdr* eh_frame_hdr);
 
+  bool operator==(const Fde&) const;
+
  private:
   // The object in which this FDE was seen.  This will be NULL for a
   // linker generated FDE.
@@ -298,6 +300,10 @@ class Cie
   add_fde(Fde* fde)
   { this->fdes_.push_back(fde); }
 
+  // Remove an FDE associated with this CIE.  Only the last FDE may be removed.
+  void
+  remove_fde(const Fde*);
+
   // Return the number of FDEs.
   unsigned int
   fde_count() const
@@ -405,6 +411,13 @@ class Eh_frame : public Output_section_data
                      size_t cie_length, const unsigned char* fde_data,
                      size_t fde_length);
 
+  // Remove unwind information for a PLT.  Only the last FDE added may
+  // be removed.
+  void
+  remove_ehframe_for_plt(Output_data* plt, const unsigned char* cie_data,
+                        size_t cie_length, const unsigned char* fde_data,
+                        size_t fde_length);
+
   // Return the number of FDEs.
   unsigned int
   fde_count() const;
index 963ae523ca8300e5327d715cb6a688ad4b33e028..5f25faea5532b5291d097adbacfb52b0a24abafb 100644 (file)
@@ -1581,6 +1581,23 @@ Layout::add_eh_frame_for_plt(Output_data* plt, const unsigned char* cie_data,
     }
 }
 
+// Remove .eh_frame information for a PLT.  FDEs using the CIE must
+// be removed in reverse order to the order they were added.
+
+void
+Layout::remove_eh_frame_for_plt(Output_data* plt, const unsigned char* cie_data,
+                               size_t cie_length, const unsigned char* fde_data,
+                               size_t fde_length)
+{
+  if (parameters->incremental())
+    {
+      // FIXME: Maybe this could work some day....
+      return;
+    }
+  this->eh_frame_data_->remove_ehframe_for_plt(plt, cie_data, cie_length,
+                                              fde_data, fde_length);
+}
+
 // Scan a .debug_info or .debug_types section, and add summary
 // information to the .gdb_index section.
 
index 5f58c2c67f69905ae5be98ab302577f44612bef0..15ee9246788d68310906784801867de4cb7e64d4 100644 (file)
@@ -653,6 +653,13 @@ class Layout
                       size_t cie_length, const unsigned char* fde_data,
                       size_t fde_length);
 
+  // Remove .eh_frame information for a PLT.  FDEs using the CIE must
+  // be removed in reverse order to the order they were added.
+  void
+  remove_eh_frame_for_plt(Output_data* plt, const unsigned char* cie_data,
+                         size_t cie_length, const unsigned char* fde_data,
+                         size_t fde_length);
+
   // Scan a .debug_info or .debug_types section, and add summary
   // information to the .gdb_index section.
   template<int size, bool big_endian>
index 9a200ab63e12527cafbbb716fd1ebb28b51fadb7..6f610cbe8cb4c0f3eb3f83d9cad3679fadbffb6b 100644 (file)
@@ -3333,6 +3333,16 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
   if (size == 64 && again)
     this->brlt_section_->set_current_size(num_huge_branches);
 
+  for (typename Stub_tables::reverse_iterator p = this->stub_tables_.rbegin();
+       p != this->stub_tables_.rend();
+       ++p)
+    (*p)->remove_eh_frame(layout);
+
+  for (typename Stub_tables::iterator p = this->stub_tables_.begin();
+       p != this->stub_tables_.end();
+       ++p)
+    (*p)->add_eh_frame(layout);
+
   typedef Unordered_set<Output_section*> Output_sections;
   Output_sections os_need_update;
   for (typename Stub_tables::iterator p = this->stub_tables_.begin();
@@ -3342,7 +3352,6 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
       if ((*p)->size_update())
        {
          again = true;
-         (*p)->add_eh_frame(layout);
          os_need_update.insert((*p)->output_section());
        }
     }
@@ -4244,25 +4253,39 @@ class Stub_table : public Output_relaxed_input_section
   void
   add_eh_frame(Layout* layout)
   {
-    if (!this->eh_frame_added_)
-      {
-       if (!parameters->options().ld_generated_unwind_info())
-         return;
+    if (!parameters->options().ld_generated_unwind_info())
+      return;
 
-       // Since we add stub .eh_frame info late, it must be placed
-       // after all other linker generated .eh_frame info so that
-       // merge mapping need not be updated for input sections.
-       // There is no provision to use a different CIE to that used
-       // by .glink.
-       if (!this->targ_->has_glink())
-         return;
+    // Since we add stub .eh_frame info late, it must be placed
+    // after all other linker generated .eh_frame info so that
+    // merge mapping need not be updated for input sections.
+    // There is no provision to use a different CIE to that used
+    // by .glink.
+    if (!this->targ_->has_glink())
+      return;
 
-       layout->add_eh_frame_for_plt(this,
-                                    Eh_cie<size>::eh_frame_cie,
-                                    sizeof (Eh_cie<size>::eh_frame_cie),
-                                    default_fde,
-                                    sizeof (default_fde));
-       this->eh_frame_added_ = true;
+    if (this->plt_size_ + this->branch_size_ + this->need_save_res_ == 0)
+      return;
+
+    layout->add_eh_frame_for_plt(this,
+                                Eh_cie<size>::eh_frame_cie,
+                                sizeof (Eh_cie<size>::eh_frame_cie),
+                                default_fde,
+                                sizeof (default_fde));
+    this->eh_frame_added_ = true;
+  }
+
+  void
+  remove_eh_frame(Layout* layout)
+  {
+    if (this->eh_frame_added_)
+      {
+       layout->remove_eh_frame_for_plt(this,
+                                       Eh_cie<size>::eh_frame_cie,
+                                       sizeof (Eh_cie<size>::eh_frame_cie),
+                                       default_fde,
+                                       sizeof (default_fde));
+       this->eh_frame_added_ = false;
       }
   }