From 220f99066d6ce2a6caf17692fcc57d714d8f6910 Mon Sep 17 00:00:00 2001 From: Alan Modra Date: Mon, 1 Jul 2019 14:37:49 +0930 Subject: [PATCH] [GOLD] PowerPC notoc eh_frame 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. --- gold/ChangeLog | 23 ++++++ gold/ehframe.cc | 47 ++++-------- gold/ehframe.h | 25 +++--- gold/layout.cc | 9 +-- gold/layout.h | 6 +- gold/powerpc.cc | 197 ++++++++++++++++++++++++++++++++---------------- 6 files changed, 190 insertions(+), 117 deletions(-) diff --git a/gold/ChangeLog b/gold/ChangeLog index e8bfea973e..355ffc56df 100644 --- a/gold/ChangeLog +++ b/gold/ChangeLog @@ -1,3 +1,26 @@ +2019-07-13 Alan Modra + + * 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. + 2019-07-13 Alan Modra * powerpc.cc (Target_powerpc::maybe_skip_tls_get_addr_call): Handle 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::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::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 -void -Stub_table::init_plt_fde() +template +static void +eh_advance (std::vector& 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 +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 void Stub_table::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::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 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); + + typedef typename Branch_stub_entries::const_iterator branch_iter; + std::vector 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); + + 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 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(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(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(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::eh_frame_cie, sizeof (Eh_cie::eh_frame_cie), - this->plt_fde_, this->plt_fde_len_); + &*fde.begin(), fde.size()); } template void Stub_table::remove_eh_frame(Layout* layout) { - if (this->plt_fde_len_ != 0) - { - layout->remove_eh_frame_for_plt(this, - Eh_cie::eh_frame_cie, - sizeof (Eh_cie::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::eh_frame_cie, + sizeof (Eh_cie::eh_frame_cie)); } // A class to handle .glink. -- 2.34.1