[2/5,GOLD] PowerPC notoc eh_frame

Message ID 8bd835a722251a1f737df2ff7eb06f64c6fdb88b.1562653486.git.amodra@gmail.com
State New
Headers show
Series
  • PowerPC pc-relative support
Related show

Commit Message

Alan Modra July 9, 2019, 8:56 a.m.
When generating notoc call and branch stubs without the benefit of
pc-relative insns, the stubs need to use LR to access the run time PC.
All LR changes must be described in .eh_frame if we're to support
unwinding through asynchronous exceptions.  That's what this patch
does.

The patch has gone through way too many iterations.  At first I
attempted to add multiple FDEs, one for each stub.  That ran into
difficulties with do_plt_fde_location which is only capable of setting
the address of a single FDE per Output_data section, and with removing
any FDEs added on a previous do_relax pass.  Removing FDEs (git commit
be897fb774) went overboard in matching the FDE contents.  That means
either stashing the contents created for add_eh_frame_for_plt to use
when calling remove_eh_frame_for_plt, or recreating contents on the
fly (*) just to remove FDEs.  In fact, FDE content matching is quite
unnecesary.  FDEs added by a previous do_relax pass are those with
u_.from_linker.post_map set.  So they can easily be recognised just by
looking at that flag.  This patch keeps that part of the multiple FDE
changes.

In the end I went for just one FDE per stub group to describe the call
stubs.  That's reasonably efficient for the common case of only
needing to describe the __tls_get_addr_opt call stub.  We don't expect
to be making many calls using notoc stubs without pc-relative insns.

*) Which has it's own set of problems.  The contents must be recreated
using the old stub layout, but .eh_frame size can affect stub
requirements so you need to temporarily keep the old .eh_frame size
when creating new stubs, then reset .eh_frame size before adding new
FDEs.

	* ehframe.cc (Fde::operator==): Delete.
	(Cie::remove_fde): Delete.
	(Eh_frame::remove_ehframe_for_plt): Delete fde_data and fde_length
	parameters.  Remove all post-map plt FDEs.
	* ehframe.h (Fde:post_map): Make const, add variant to compare plt.
	(Fde::operator==): Delete.
	(Cie::remove_fde): Implement here.
	(Cie::last_fde): New accessor.
	(Eh_frame::remove_ehframe_for_plt): Update prototype.
	* layout.cc (Layout::remove_eh_frame_for_plt): Delete fde_data and
	fde_length parameters.
	* layout.h (Layout::remove_eh_frame_for_plt): Update prototype.
	* powerpc.cc (Stub_table::tls_get_addr_opt_bctrl_): Delete.
	(Stub_table::plt_fde_len_, plt_fde_, init_plt_fde): Delete.
	(Stub_table::add_plt_call_entry): Don't set tls_get_addr_opt_bctrl_.
	(eh_advance): New function.
	(stub_sort): New function.
	(Stub_table::add_eh_frame): Emit eh_frame for notoc plt calls and
	branches as well as __tls_get_addr_opt plt call stub.
	(Stub_table::remove_eh_frame): Update to suit.

Comments

Cary Coutant July 9, 2019, 10:28 p.m. | #1
>         * ehframe.cc (Fde::operator==): Delete.

>         (Cie::remove_fde): Delete.

>         (Eh_frame::remove_ehframe_for_plt): Delete fde_data and fde_length

>         parameters.  Remove all post-map plt FDEs.

>         * ehframe.h (Fde:post_map): Make const, add variant to compare plt.

>         (Fde::operator==): Delete.

>         (Cie::remove_fde): Implement here.

>         (Cie::last_fde): New accessor.

>         (Eh_frame::remove_ehframe_for_plt): Update prototype.


Not that you need it for this, but just in case you were waiting for
an OK from me, here it is. Looks good, thanks.

-cary

Patch

diff --git a/gold/ehframe.cc b/gold/ehframe.cc
index df056d3d36..51490e0e90 100644
--- a/gold/ehframe.cc
+++ b/gold/ehframe.cc
@@ -325,21 +325,6 @@  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
@@ -458,15 +443,6 @@  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.
@@ -1167,26 +1143,31 @@  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.
+// Remove all post-map unwind information for a PLT.
 
 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)
+				 size_t cie_length)
 {
+  if (!this->mappings_are_done_)
+    return;
+
   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());
+  while (pcie->fde_count() != 0)
+    {
+      const Fde* fde = pcie->last_fde();
+      if (!fde->post_map(plt))
+	break;
+      size_t length = fde->length();
+      this->final_data_size_ -= align_address(length + 8, this->addralign());
+      pcie->remove_fde();
+    }
 }
 
 // Return the number of FDEs.
diff --git a/gold/ehframe.h b/gold/ehframe.h
index ba65083206..4fd8f3e2f6 100644
--- a/gold/ehframe.h
+++ b/gold/ehframe.h
@@ -203,9 +203,14 @@  class Fde
 
   // Return whether this FDE was added after merge mapping.
   bool
-  post_map()
+  post_map() const
   { return this->object_ == NULL && this->u_.from_linker.post_map; }
 
+  // Return whether this FDE was added for the PLT after merge mapping.
+  bool
+  post_map(const Output_data* plt) const
+  { return this->post_map() && this->u_.from_linker.plt == plt; }
+
   // Write the FDE to OVIEW starting at OFFSET.  FDE_ENCODING is the
   // encoding, from the CIE.  Round up the bytes to ADDRALIGN if
   // necessary.  ADDRESS is the virtual address of OVIEW.  Record the
@@ -217,8 +222,6 @@  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.
@@ -300,9 +303,15 @@  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.
+  // Remove the last FDE associated with this CIE.
   void
-  remove_fde(const Fde*);
+  remove_fde()
+  { this->fdes_.pop_back(); }
+
+  // Access the last FDE associated with this CIE.
+  const Fde*
+  last_fde() const
+  { return this->fdes_.back(); }
 
   // Return the number of FDEs.
   unsigned int
@@ -411,12 +420,10 @@  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.
+  // Remove all post-map unwind information for a PLT.
   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);
+			 size_t cie_length);
 
   // Return the number of FDEs.
   unsigned int
diff --git a/gold/layout.cc b/gold/layout.cc
index b83e8e6e2d..fc7cdf8b8b 100644
--- a/gold/layout.cc
+++ b/gold/layout.cc
@@ -1604,21 +1604,18 @@  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.
+// Remove all post-map .eh_frame information for a PLT.
 
 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)
+				size_t cie_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);
+  this->eh_frame_data_->remove_ehframe_for_plt(plt, cie_data, cie_length);
 }
 
 // Scan a .debug_info or .debug_types section, and add summary
diff --git a/gold/layout.h b/gold/layout.h
index c4f1f8c35b..bfd44e1307 100644
--- a/gold/layout.h
+++ b/gold/layout.h
@@ -666,12 +666,10 @@  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.
+  // Remove all post-map .eh_frame information for a PLT.
   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);
+			  size_t cie_length);
 
   // Scan a .debug_info or .debug_types section, and add summary
   // information to the .gdb_index section.
diff --git a/gold/powerpc.cc b/gold/powerpc.cc
index 1b62f5552c..494a1fd93b 100644
--- a/gold/powerpc.cc
+++ b/gold/powerpc.cc
@@ -4542,7 +4542,7 @@  class Stub_table : public Output_relaxed_input_section
       plt_size_(0), last_plt_size_(0),
       branch_size_(0), last_branch_size_(0), min_size_threshold_(0),
       need_save_res_(false), need_resize_(false), resizing_(false),
-      uniq_(id), tls_get_addr_opt_bctrl_(-1u), plt_fde_len_(0)
+      uniq_(id)
   {
     this->set_output_section(output_section);
 
@@ -4724,10 +4724,6 @@  class Stub_table : public Output_relaxed_input_section
     return false;
   }
 
-  // Generate a suitable FDE to describe code in this stub group.
-  void
-  init_plt_fde();
-
   // Add .eh_frame info for this stub section.
   void
   add_eh_frame(Layout* layout);
@@ -4932,11 +4928,6 @@  class Stub_table : public Output_relaxed_input_section
   bool resizing_;
   // Per stub table unique identifier.
   uint32_t uniq_;
-  // The bctrl in the __tls_get_addr_opt stub, if present.
-  unsigned int tls_get_addr_opt_bctrl_;
-  // FDE unwind info for this stub group.
-  unsigned int plt_fde_len_;
-  unsigned char plt_fde_[20];
 };
 
 // Add a plt call stub, if we do not already have one for this
@@ -4986,10 +4977,7 @@  Stub_table<size, big_endian>::add_plt_call_entry(
 	}
       this->plt_size_ += this->plt_call_size(p.first);
       if (this->targ_->is_tls_get_addr_opt(gsym))
-	{
-	  this->targ_->set_has_tls_get_addr_opt();
-	  this->tls_get_addr_opt_bctrl_ = this->plt_size_ - 5 * 4;
-	}
+	this->targ_->set_has_tls_get_addr_opt();
       this->plt_size_ = this->plt_call_align(this->plt_size_);
     }
   return this->can_reach_stub(from, p.first->second.off_, r_type);
@@ -5159,48 +5147,39 @@  Stub_table<size, big_endian>::find_long_branch_entry(
   return &p->second;
 }
 
-// Generate a suitable FDE to describe code in this stub group.
-// The __tls_get_addr_opt call stub needs to describe where it saves
-// LR, to support exceptions that might be thrown from __tls_get_addr.
-
-template<int size, bool big_endian>
-void
-Stub_table<size, big_endian>::init_plt_fde()
+template<bool big_endian>
+static void
+eh_advance (std::vector<unsigned char>& fde, unsigned int delta)
 {
-  unsigned char* p = this->plt_fde_;
-  // offset pcrel sdata4, size udata4, and augmentation size byte.
-  memset (p, 0, 9);
-  p += 9;
-  if (this->tls_get_addr_opt_bctrl_ != -1u)
+  delta /= 4;
+  if (delta < 64)
+    fde.push_back(elfcpp::DW_CFA_advance_loc + delta);
+  else if (delta < 256)
     {
-      unsigned int to_bctrl = this->tls_get_addr_opt_bctrl_ / 4;
-      if (to_bctrl < 64)
-	*p++ = elfcpp::DW_CFA_advance_loc + to_bctrl;
-      else if (to_bctrl < 256)
-	{
-	  *p++ = elfcpp::DW_CFA_advance_loc1;
-	  *p++ = to_bctrl;
-	}
-      else if (to_bctrl < 65536)
-	{
-	  *p++ = elfcpp::DW_CFA_advance_loc2;
-	  elfcpp::Swap<16, big_endian>::writeval(p, to_bctrl);
-	  p += 2;
-	}
-      else
-	{
-	  *p++ = elfcpp::DW_CFA_advance_loc4;
-	  elfcpp::Swap<32, big_endian>::writeval(p, to_bctrl);
-	  p += 4;
-	}
-      *p++ = elfcpp::DW_CFA_offset_extended_sf;
-      *p++ = 65;
-      *p++ = -(this->targ_->stk_linker() / 8) & 0x7f;
-      *p++ = elfcpp::DW_CFA_advance_loc + 4;
-      *p++ = elfcpp::DW_CFA_restore_extended;
-      *p++ = 65;
+      fde.push_back(elfcpp::DW_CFA_advance_loc1);
+      fde.push_back(delta);
     }
-  this->plt_fde_len_ = p - this->plt_fde_;
+  else if (delta < 65536)
+    {
+      fde.resize(fde.size() + 3);
+      unsigned char *p = &*fde.end() - 3;
+      *p++ = elfcpp::DW_CFA_advance_loc2;
+      elfcpp::Swap<16, big_endian>::writeval(p, delta);
+    }
+  else
+    {
+      fde.resize(fde.size() + 5);
+      unsigned char *p = &*fde.end() - 5;
+      *p++ = elfcpp::DW_CFA_advance_loc4;
+      elfcpp::Swap<32, big_endian>::writeval(p, delta);
+    }
+}
+
+template<typename T>
+static bool
+stub_sort(T s1, T s2)
+{
+  return s1->second.off_ < s2->second.off_;
 }
 
 // Add .eh_frame info for this stub section.  Unlike other linker
@@ -5212,7 +5191,8 @@  template<int size, bool big_endian>
 void
 Stub_table<size, big_endian>::add_eh_frame(Layout* layout)
 {
-  if (!parameters->options().ld_generated_unwind_info())
+  if (size != 64
+      || !parameters->options().ld_generated_unwind_info())
     return;
 
   // Since we add stub .eh_frame info late, it must be placed
@@ -5223,28 +5203,115 @@  Stub_table<size, big_endian>::add_eh_frame(Layout* layout)
   if (!this->targ_->has_glink())
     return;
 
-  if (this->plt_size_ + this->branch_size_ + this->need_save_res_ == 0)
+  typedef typename Plt_stub_entries::const_iterator plt_iter;
+  std::vector<plt_iter> calls;
+  if (!this->plt_call_stubs_.empty())
+    for (plt_iter cs = this->plt_call_stubs_.begin();
+	 cs != this->plt_call_stubs_.end();
+	 ++cs)
+      if ((this->targ_->is_tls_get_addr_opt(cs->first.sym_)
+	   && cs->second.r2save_
+	   && !cs->second.localentry0_)
+	  || cs->second.notoc_)
+	calls.push_back(cs);
+  if (calls.size() > 1)
+    std::stable_sort(calls.begin(), calls.end(),
+		     stub_sort<plt_iter>);
+
+  typedef typename Branch_stub_entries::const_iterator branch_iter;
+  std::vector<branch_iter> branches;
+  if (!this->long_branch_stubs_.empty())
+    for (branch_iter bs = this->long_branch_stubs_.begin();
+	 bs != this->long_branch_stubs_.end();
+	 ++bs)
+      if (bs->second.notoc_)
+	branches.push_back(bs);
+  if (branches.size() > 1)
+    std::stable_sort(branches.begin(), branches.end(),
+		     stub_sort<branch_iter>);
+
+  if (calls.empty() && branches.empty())
     return;
 
-  this->init_plt_fde();
+  unsigned int last_eh_loc = 0;
+  // offset pcrel sdata4, size udata4, and augmentation size byte.
+  std::vector<unsigned char> fde(9, 0);
+
+  for (unsigned int i = 0; i < calls.size(); i++)
+    {
+      plt_iter cs = calls[i];
+      unsigned int off = cs->second.off_;
+      // The __tls_get_addr_opt call stub needs to describe where
+      // it saves LR, to support exceptions that might be thrown
+      // from __tls_get_addr, and to support asynchronous exceptions.
+      if (this->targ_->is_tls_get_addr_opt(cs->first.sym_))
+	{
+	  off += 7 * 4;
+	  if (cs->second.r2save_
+	      && !cs->second.localentry0_)
+	    {
+	      off += 2 * 4;
+	      eh_advance<big_endian>(fde, off - last_eh_loc);
+	      fde.resize(fde.size() + 6);
+	      unsigned char* p = &*fde.end() - 6;
+	      *p++ = elfcpp::DW_CFA_offset_extended_sf;
+	      *p++ = 65;
+	      *p++ = -(this->targ_->stk_linker() / 8) & 0x7f;
+	      unsigned int delta = this->plt_call_size(cs) - 4 - 9 * 4;
+	      *p++ = elfcpp::DW_CFA_advance_loc + delta / 4;
+	      *p++ = elfcpp::DW_CFA_restore_extended;
+	      *p++ = 65;
+	      last_eh_loc = off + delta;
+	      continue;
+	    }
+	}
+      // notoc stubs also should describe LR changes, to support
+      // asynchronous exceptions.
+      off += (cs->second.r2save_ ? 4 : 0) + 8;
+      eh_advance<big_endian>(fde, off - last_eh_loc);
+      fde.resize(fde.size() + 6);
+      unsigned char* p = &*fde.end() - 6;
+      *p++ = elfcpp::DW_CFA_register;
+      *p++ = 65;
+      *p++ = 12;
+      *p++ = elfcpp::DW_CFA_advance_loc + 8 / 4;
+      *p++ = elfcpp::DW_CFA_restore_extended;
+      *p++ = 65;
+      last_eh_loc = off + 8;
+    }
+
+  for (unsigned int i = 0; i < branches.size(); i++)
+    {
+      branch_iter bs = branches[i];
+      unsigned int off = bs->second.off_ + 8;
+      eh_advance<big_endian>(fde, off - last_eh_loc);
+      fde.resize(fde.size() + 6);
+      unsigned char* p = &*fde.end() - 6;
+      *p++ = elfcpp::DW_CFA_register;
+      *p++ = 65;
+      *p++ = 12;
+      *p++ = elfcpp::DW_CFA_advance_loc + 8 / 4;
+      *p++ = elfcpp::DW_CFA_restore_extended;
+      *p++ = 65;
+      last_eh_loc = off + 8;
+    }
+
   layout->add_eh_frame_for_plt(this,
 			       Eh_cie<size>::eh_frame_cie,
 			       sizeof (Eh_cie<size>::eh_frame_cie),
-			       this->plt_fde_, this->plt_fde_len_);
+			       &*fde.begin(), fde.size());
 }
 
 template<int size, bool big_endian>
 void
 Stub_table<size, big_endian>::remove_eh_frame(Layout* layout)
 {
-  if (this->plt_fde_len_ != 0)
-    {
-      layout->remove_eh_frame_for_plt(this,
-				      Eh_cie<size>::eh_frame_cie,
-				      sizeof (Eh_cie<size>::eh_frame_cie),
-				      this->plt_fde_, this->plt_fde_len_);
-      this->plt_fde_len_ = 0;
-    }
+  if (size == 64
+      && parameters->options().ld_generated_unwind_info()
+      && this->targ_->has_glink())
+    layout->remove_eh_frame_for_plt(this,
+				    Eh_cie<size>::eh_frame_cie,
+				    sizeof (Eh_cie<size>::eh_frame_cie));
 }
 
 // A class to handle .glink.