blob: e5bd7bda0fec5cb07f4ce71ce13b87958adb3f82 [file] [log] [blame]
// aarch64.cc -- aarch64 target support for gold.
// Copyright (C) 2014-2024 Free Software Foundation, Inc.
// Written by Jing Yu <jingyu@google.com> and Han Shen <shenhan@google.com>.
// This file is part of gold.
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
// MA 02110-1301, USA.
#include "gold.h"
#include <cstring>
#include <map>
#include <set>
#include "elfcpp.h"
#include "dwarf.h"
#include "parameters.h"
#include "reloc.h"
#include "aarch64.h"
#include "object.h"
#include "symtab.h"
#include "layout.h"
#include "output.h"
#include "copy-relocs.h"
#include "target.h"
#include "target-reloc.h"
#include "target-select.h"
#include "tls.h"
#include "freebsd.h"
#include "nacl.h"
#include "gc.h"
#include "icf.h"
#include "aarch64-reloc-property.h"
// The first three .got.plt entries are reserved.
const int32_t AARCH64_GOTPLT_RESERVE_COUNT = 3;
namespace
{
using namespace gold;
template<int size, bool big_endian>
class Output_data_plt_aarch64;
template<int size, bool big_endian>
class Output_data_plt_aarch64_standard;
template<int size, bool big_endian>
class Target_aarch64;
template<int size, bool big_endian>
class AArch64_relocate_functions;
// Utility class dealing with insns. This is ported from macros in
// bfd/elfnn-aarch64.cc, but wrapped inside a class as static members. This
// class is used in erratum sequence scanning.
template<bool big_endian>
class AArch64_insn_utilities
{
public:
typedef typename elfcpp::Swap<32, big_endian>::Valtype Insntype;
static const int BYTES_PER_INSN;
// Zero register encoding - 31.
static const unsigned int AARCH64_ZR;
static unsigned int
aarch64_bit(Insntype insn, int pos)
{ return ((1 << pos) & insn) >> pos; }
static unsigned int
aarch64_bits(Insntype insn, int pos, int l)
{ return (insn >> pos) & ((1 << l) - 1); }
// Get the encoding field "op31" of 3-source data processing insns. "op31" is
// the name defined in armv8 insn manual C3.5.9.
static unsigned int
aarch64_op31(Insntype insn)
{ return aarch64_bits(insn, 21, 3); }
// Get the encoding field "ra" of 3-source data processing insns. "ra" is the
// third source register. See armv8 insn manual C3.5.9.
static unsigned int
aarch64_ra(Insntype insn)
{ return aarch64_bits(insn, 10, 5); }
static bool
is_adr(const Insntype insn)
{ return (insn & 0x9F000000) == 0x10000000; }
static bool
is_adrp(const Insntype insn)
{ return (insn & 0x9F000000) == 0x90000000; }
static bool
is_mrs_tpidr_el0(const Insntype insn)
{ return (insn & 0xFFFFFFE0) == 0xd53bd040; }
static unsigned int
aarch64_rm(const Insntype insn)
{ return aarch64_bits(insn, 16, 5); }
static unsigned int
aarch64_rn(const Insntype insn)
{ return aarch64_bits(insn, 5, 5); }
static unsigned int
aarch64_rd(const Insntype insn)
{ return aarch64_bits(insn, 0, 5); }
static unsigned int
aarch64_rt(const Insntype insn)
{ return aarch64_bits(insn, 0, 5); }
static unsigned int
aarch64_rt2(const Insntype insn)
{ return aarch64_bits(insn, 10, 5); }
// Encode imm21 into adr. Signed imm21 is in the range of [-1M, 1M).
static Insntype
aarch64_adr_encode_imm(Insntype adr, int imm21)
{
gold_assert(is_adr(adr));
gold_assert(-(1 << 20) <= imm21 && imm21 < (1 << 20));
const int mask19 = (1 << 19) - 1;
const int mask2 = 3;
adr &= ~((mask19 << 5) | (mask2 << 29));
adr |= ((imm21 & mask2) << 29) | (((imm21 >> 2) & mask19) << 5);
return adr;
}
// Retrieve encoded adrp 33-bit signed imm value. This value is obtained by
// 21-bit signed imm encoded in the insn multiplied by 4k (page size) and
// 64-bit sign-extended, resulting in [-4G, 4G) with 12-lsb being 0.
static int64_t
aarch64_adrp_decode_imm(const Insntype adrp)
{
const int mask19 = (1 << 19) - 1;
const int mask2 = 3;
gold_assert(is_adrp(adrp));
// 21-bit imm encoded in adrp.
uint64_t imm = ((adrp >> 29) & mask2) | (((adrp >> 5) & mask19) << 2);
// Retrieve msb of 21-bit-signed imm for sign extension.
uint64_t msbt = (imm >> 20) & 1;
// Real value is imm multiplied by 4k. Value now has 33-bit information.
int64_t value = imm << 12;
// Sign extend to 64-bit by repeating msbt 31 (64-33) times and merge it
// with value.
return ((((uint64_t)(1) << 32) - msbt) << 33) | value;
}
static bool
aarch64_b(const Insntype insn)
{ return (insn & 0xFC000000) == 0x14000000; }
static bool
aarch64_bl(const Insntype insn)
{ return (insn & 0xFC000000) == 0x94000000; }
static bool
aarch64_blr(const Insntype insn)
{ return (insn & 0xFFFFFC1F) == 0xD63F0000; }
static bool
aarch64_br(const Insntype insn)
{ return (insn & 0xFFFFFC1F) == 0xD61F0000; }
// All ld/st ops. See C4-182 of the ARM ARM. The encoding space for
// LD_PCREL, LDST_RO, LDST_UI and LDST_UIMM cover prefetch ops.
static bool
aarch64_ld(Insntype insn) { return aarch64_bit(insn, 22) == 1; }
static bool
aarch64_ldst(Insntype insn)
{ return (insn & 0x0a000000) == 0x08000000; }
static bool
aarch64_ldst_ex(Insntype insn)
{ return (insn & 0x3f000000) == 0x08000000; }
static bool
aarch64_ldst_pcrel(Insntype insn)
{ return (insn & 0x3b000000) == 0x18000000; }
static bool
aarch64_ldst_nap(Insntype insn)
{ return (insn & 0x3b800000) == 0x28000000; }
static bool
aarch64_ldstp_pi(Insntype insn)
{ return (insn & 0x3b800000) == 0x28800000; }
static bool
aarch64_ldstp_o(Insntype insn)
{ return (insn & 0x3b800000) == 0x29000000; }
static bool
aarch64_ldstp_pre(Insntype insn)
{ return (insn & 0x3b800000) == 0x29800000; }
static bool
aarch64_ldst_ui(Insntype insn)
{ return (insn & 0x3b200c00) == 0x38000000; }
static bool
aarch64_ldst_piimm(Insntype insn)
{ return (insn & 0x3b200c00) == 0x38000400; }
static bool
aarch64_ldst_u(Insntype insn)
{ return (insn & 0x3b200c00) == 0x38000800; }
static bool
aarch64_ldst_preimm(Insntype insn)
{ return (insn & 0x3b200c00) == 0x38000c00; }
static bool
aarch64_ldst_ro(Insntype insn)
{ return (insn & 0x3b200c00) == 0x38200800; }
static bool
aarch64_ldst_uimm(Insntype insn)
{ return (insn & 0x3b000000) == 0x39000000; }
static bool
aarch64_ldst_simd_m(Insntype insn)
{ return (insn & 0xbfbf0000) == 0x0c000000; }
static bool
aarch64_ldst_simd_m_pi(Insntype insn)
{ return (insn & 0xbfa00000) == 0x0c800000; }
static bool
aarch64_ldst_simd_s(Insntype insn)
{ return (insn & 0xbf9f0000) == 0x0d000000; }
static bool
aarch64_ldst_simd_s_pi(Insntype insn)
{ return (insn & 0xbf800000) == 0x0d800000; }
// Classify an INSN if it is indeed a load/store. Return true if INSN is a
// LD/ST instruction otherwise return false. For scalar LD/ST instructions
// PAIR is FALSE, RT is returned and RT2 is set equal to RT. For LD/ST pair
// instructions PAIR is TRUE, RT and RT2 are returned.
static bool
aarch64_mem_op_p(Insntype insn, unsigned int *rt, unsigned int *rt2,
bool *pair, bool *load)
{
uint32_t opcode;
unsigned int r;
uint32_t opc = 0;
uint32_t v = 0;
uint32_t opc_v = 0;
/* Bail out quickly if INSN doesn't fall into the load-store
encoding space. */
if (!aarch64_ldst (insn))
return false;
*pair = false;
*load = false;
if (aarch64_ldst_ex (insn))
{
*rt = aarch64_rt (insn);
*rt2 = *rt;
if (aarch64_bit (insn, 21) == 1)
{
*pair = true;
*rt2 = aarch64_rt2 (insn);
}
*load = aarch64_ld (insn);
return true;
}
else if (aarch64_ldst_nap (insn)
|| aarch64_ldstp_pi (insn)
|| aarch64_ldstp_o (insn)
|| aarch64_ldstp_pre (insn))
{
*pair = true;
*rt = aarch64_rt (insn);
*rt2 = aarch64_rt2 (insn);
*load = aarch64_ld (insn);
return true;
}
else if (aarch64_ldst_pcrel (insn)
|| aarch64_ldst_ui (insn)
|| aarch64_ldst_piimm (insn)
|| aarch64_ldst_u (insn)
|| aarch64_ldst_preimm (insn)
|| aarch64_ldst_ro (insn)
|| aarch64_ldst_uimm (insn))
{
*rt = aarch64_rt (insn);
*rt2 = *rt;
if (aarch64_ldst_pcrel (insn))
*load = true;
opc = aarch64_bits (insn, 22, 2);
v = aarch64_bit (insn, 26);
opc_v = opc | (v << 2);
*load = (opc_v == 1 || opc_v == 2 || opc_v == 3
|| opc_v == 5 || opc_v == 7);
return true;
}
else if (aarch64_ldst_simd_m (insn)
|| aarch64_ldst_simd_m_pi (insn))
{
*rt = aarch64_rt (insn);
*load = aarch64_bit (insn, 22);
opcode = (insn >> 12) & 0xf;
switch (opcode)
{
case 0:
case 2:
*rt2 = *rt + 3;
break;
case 4:
case 6:
*rt2 = *rt + 2;
break;
case 7:
*rt2 = *rt;
break;
case 8:
case 10:
*rt2 = *rt + 1;
break;
default:
return false;
}
return true;
}
else if (aarch64_ldst_simd_s (insn)
|| aarch64_ldst_simd_s_pi (insn))
{
*rt = aarch64_rt (insn);
r = (insn >> 21) & 1;
*load = aarch64_bit (insn, 22);
opcode = (insn >> 13) & 0x7;
switch (opcode)
{
case 0:
case 2:
case 4:
*rt2 = *rt + r;
break;
case 1:
case 3:
case 5:
*rt2 = *rt + (r == 0 ? 2 : 3);
break;
case 6:
*rt2 = *rt + r;
break;
case 7:
*rt2 = *rt + (r == 0 ? 2 : 3);
break;
default:
return false;
}
return true;
}
return false;
} // End of "aarch64_mem_op_p".
// Return true if INSN is mac insn.
static bool
aarch64_mac(Insntype insn)
{ return (insn & 0xff000000) == 0x9b000000; }
// Return true if INSN is multiply-accumulate.
// (This is similar to implementaton in elfnn-aarch64.c.)
static bool
aarch64_mlxl(Insntype insn)
{
uint32_t op31 = aarch64_op31(insn);
if (aarch64_mac(insn)
&& (op31 == 0 || op31 == 1 || op31 == 5)
/* Exclude MUL instructions which are encoded as a multiple-accumulate
with RA = XZR. */
&& aarch64_ra(insn) != AARCH64_ZR)
{
return true;
}
return false;
}
}; // End of "AArch64_insn_utilities".
// Insn length in byte.
template<bool big_endian>
const int AArch64_insn_utilities<big_endian>::BYTES_PER_INSN = 4;
// Zero register encoding - 31.
template<bool big_endian>
const unsigned int AArch64_insn_utilities<big_endian>::AARCH64_ZR = 0x1f;
// Output_data_got_aarch64 class.
template<int size, bool big_endian>
class Output_data_got_aarch64 : public Output_data_got<size, big_endian>
{
public:
typedef typename elfcpp::Elf_types<size>::Elf_Addr Valtype;
Output_data_got_aarch64(Symbol_table* symtab, Layout* layout)
: Output_data_got<size, big_endian>(),
symbol_table_(symtab), layout_(layout)
{ }
// Add a static entry for the GOT entry at OFFSET. GSYM is a global
// symbol and R_TYPE is the code of a dynamic relocation that needs to be
// applied in a static link.
void
add_static_reloc(unsigned int got_offset, unsigned int r_type, Symbol* gsym)
{ this->static_relocs_.push_back(Static_reloc(got_offset, r_type, gsym)); }
// Add a static reloc for the GOT entry at OFFSET. RELOBJ is an object
// defining a local symbol with INDEX. R_TYPE is the code of a dynamic
// relocation that needs to be applied in a static link.
void
add_static_reloc(unsigned int got_offset, unsigned int r_type,
Sized_relobj_file<size, big_endian>* relobj,
unsigned int index)
{
this->static_relocs_.push_back(Static_reloc(got_offset, r_type, relobj,
index));
}
protected:
// Write out the GOT table.
void
do_write(Output_file* of) {
// The first entry in the GOT is the address of the .dynamic section.
gold_assert(this->data_size() >= size / 8);
Output_section* dynamic = this->layout_->dynamic_section();
Valtype dynamic_addr = dynamic == NULL ? 0 : dynamic->address();
this->replace_constant(0, dynamic_addr);
Output_data_got<size, big_endian>::do_write(of);
// Handling static relocs
if (this->static_relocs_.empty())
return;
typedef typename elfcpp::Elf_types<size>::Elf_Addr AArch64_address;
gold_assert(parameters->doing_static_link());
const off_t offset = this->offset();
const section_size_type oview_size =
convert_to_section_size_type(this->data_size());
unsigned char* const oview = of->get_output_view(offset, oview_size);
Output_segment* tls_segment = this->layout_->tls_segment();
gold_assert(tls_segment != NULL);
AArch64_address aligned_tcb_address =
align_address(Target_aarch64<size, big_endian>::TCB_SIZE,
tls_segment->maximum_alignment());
for (size_t i = 0; i < this->static_relocs_.size(); ++i)
{
Static_reloc& reloc(this->static_relocs_[i]);
AArch64_address value;
if (!reloc.symbol_is_global())
{
Sized_relobj_file<size, big_endian>* object = reloc.relobj();
const Symbol_value<size>* psymval =
reloc.relobj()->local_symbol(reloc.index());
// We are doing static linking. Issue an error and skip this
// relocation if the symbol is undefined or in a discarded_section.
bool is_ordinary;
unsigned int shndx = psymval->input_shndx(&is_ordinary);
if ((shndx == elfcpp::SHN_UNDEF)
|| (is_ordinary
&& shndx != elfcpp::SHN_UNDEF
&& !object->is_section_included(shndx)
&& !this->symbol_table_->is_section_folded(object, shndx)))
{
gold_error(_("undefined or discarded local symbol %u from "
" object %s in GOT"),
reloc.index(), reloc.relobj()->name().c_str());
continue;
}
value = psymval->value(object, 0);
}
else
{
const Symbol* gsym = reloc.symbol();
gold_assert(gsym != NULL);
if (gsym->is_forwarder())
gsym = this->symbol_table_->resolve_forwards(gsym);
// We are doing static linking. Issue an error and skip this
// relocation if the symbol is undefined or in a discarded_section
// unless it is a weakly_undefined symbol.
if ((gsym->is_defined_in_discarded_section()
|| gsym->is_undefined())
&& !gsym->is_weak_undefined())
{
gold_error(_("undefined or discarded symbol %s in GOT"),
gsym->name());
continue;
}
if (!gsym->is_weak_undefined())
{
const Sized_symbol<size>* sym =
static_cast<const Sized_symbol<size>*>(gsym);
value = sym->value();
}
else
value = 0;
}
unsigned got_offset = reloc.got_offset();
gold_assert(got_offset < oview_size);
typedef typename elfcpp::Swap<size, big_endian>::Valtype Valtype;
Valtype* wv = reinterpret_cast<Valtype*>(oview + got_offset);
Valtype x;
switch (reloc.r_type())
{
case elfcpp::R_AARCH64_TLS_DTPREL64:
x = value;
break;
case elfcpp::R_AARCH64_TLS_TPREL64:
x = value + aligned_tcb_address;
break;
default:
gold_unreachable();
}
elfcpp::Swap<size, big_endian>::writeval(wv, x);
}
of->write_output_view(offset, oview_size, oview);
}
private:
// Symbol table of the output object.
Symbol_table* symbol_table_;
// A pointer to the Layout class, so that we can find the .dynamic
// section when we write out the GOT section.
Layout* layout_;
// This class represent dynamic relocations that need to be applied by
// gold because we are using TLS relocations in a static link.
class Static_reloc
{
public:
Static_reloc(unsigned int got_offset, unsigned int r_type, Symbol* gsym)
: got_offset_(got_offset), r_type_(r_type), symbol_is_global_(true)
{ this->u_.global.symbol = gsym; }
Static_reloc(unsigned int got_offset, unsigned int r_type,
Sized_relobj_file<size, big_endian>* relobj, unsigned int index)
: got_offset_(got_offset), r_type_(r_type), symbol_is_global_(false)
{
this->u_.local.relobj = relobj;
this->u_.local.index = index;
}
// Return the GOT offset.
unsigned int
got_offset() const
{ return this->got_offset_; }
// Relocation type.
unsigned int
r_type() const
{ return this->r_type_; }
// Whether the symbol is global or not.
bool
symbol_is_global() const
{ return this->symbol_is_global_; }
// For a relocation against a global symbol, the global symbol.
Symbol*
symbol() const
{
gold_assert(this->symbol_is_global_);
return this->u_.global.symbol;
}
// For a relocation against a local symbol, the defining object.
Sized_relobj_file<size, big_endian>*
relobj() const
{
gold_assert(!this->symbol_is_global_);
return this->u_.local.relobj;
}
// For a relocation against a local symbol, the local symbol index.
unsigned int
index() const
{
gold_assert(!this->symbol_is_global_);
return this->u_.local.index;
}
private:
// GOT offset of the entry to which this relocation is applied.
unsigned int got_offset_;
// Type of relocation.
unsigned int r_type_;
// Whether this relocation is against a global symbol.
bool symbol_is_global_;
// A global or local symbol.
union
{
struct
{
// For a global symbol, the symbol itself.
Symbol* symbol;
} global;
struct
{
// For a local symbol, the object defining the symbol.
Sized_relobj_file<size, big_endian>* relobj;
// For a local symbol, the symbol index.
unsigned int index;
} local;
} u_;
}; // End of inner class Static_reloc
std::vector<Static_reloc> static_relocs_;
}; // End of Output_data_got_aarch64
template<int size, bool big_endian>
class AArch64_input_section;
template<int size, bool big_endian>
class AArch64_output_section;
template<int size, bool big_endian>
class AArch64_relobj;
// Stub type enum constants.
enum
{
ST_NONE = 0,
// Using adrp/add pair, 4 insns (including alignment) without mem access,
// the fastest stub. This has a limited jump distance, which is tested by
// aarch64_valid_for_adrp_p.
ST_ADRP_BRANCH = 1,
// Using ldr-absolute-address/br-register, 4 insns with 1 mem access,
// unlimited in jump distance.
ST_LONG_BRANCH_ABS = 2,
// Using ldr/calculate-pcrel/jump, 8 insns (including alignment) with 1
// mem access, slowest one. Only used in position independent executables.
ST_LONG_BRANCH_PCREL = 3,
// Stub for erratum 843419 handling.
ST_E_843419 = 4,
// Stub for erratum 835769 handling.
ST_E_835769 = 5,
// Number of total stub types.
ST_NUMBER = 6
};
// Struct that wraps insns for a particular stub. All stub templates are
// created/initialized as constants by Stub_template_repertoire.
template<bool big_endian>
struct Stub_template
{
const typename AArch64_insn_utilities<big_endian>::Insntype* insns;
const int insn_num;
};
// Simple singleton class that creates/initializes/stores all types of stub
// templates.
template<bool big_endian>
class Stub_template_repertoire
{
public:
typedef typename AArch64_insn_utilities<big_endian>::Insntype Insntype;
// Single static method to get stub template for a given stub type.
static const Stub_template<big_endian>*
get_stub_template(int type)
{
static Stub_template_repertoire<big_endian> singleton;
return singleton.stub_templates_[type];
}
private:
// Constructor - creates/initializes all stub templates.
Stub_template_repertoire();
~Stub_template_repertoire()
{ }
// Disallowing copy ctor and copy assignment operator.
Stub_template_repertoire(Stub_template_repertoire&);
Stub_template_repertoire& operator=(Stub_template_repertoire&);
// Data that stores all insn templates.
const Stub_template<big_endian>* stub_templates_[ST_NUMBER];
}; // End of "class Stub_template_repertoire".
// Constructor - creates/initilizes all stub templates.
template<bool big_endian>
Stub_template_repertoire<big_endian>::Stub_template_repertoire()
{
// Insn array definitions.
const static Insntype ST_NONE_INSNS[] = {};
const static Insntype ST_ADRP_BRANCH_INSNS[] =
{
0x90000010, /* adrp ip0, X */
/* ADR_PREL_PG_HI21(X) */
0x91000210, /* add ip0, ip0, :lo12:X */
/* ADD_ABS_LO12_NC(X) */
0xd61f0200, /* br ip0 */
0x00000000, /* alignment padding */
};
const static Insntype ST_LONG_BRANCH_ABS_INSNS[] =
{
0x58000050, /* ldr ip0, 0x8 */
0xd61f0200, /* br ip0 */
0x00000000, /* address field */
0x00000000, /* address fields */
};
const static Insntype ST_LONG_BRANCH_PCREL_INSNS[] =
{
0x58000090, /* ldr ip0, 0x10 */
0x10000011, /* adr ip1, #0 */
0x8b110210, /* add ip0, ip0, ip1 */
0xd61f0200, /* br ip0 */
0x00000000, /* address field */
0x00000000, /* address field */
0x00000000, /* alignment padding */
0x00000000, /* alignment padding */
};
const static Insntype ST_E_843419_INSNS[] =
{
0x00000000, /* Placeholder for erratum insn. */
0x14000000, /* b <label> */
};
// ST_E_835769 has the same stub template as ST_E_843419
// but we reproduce the array here so that the sizeof
// expressions in install_insn_template will work.
const static Insntype ST_E_835769_INSNS[] =
{
0x00000000, /* Placeholder for erratum insn. */
0x14000000, /* b <label> */
};
#define install_insn_template(T) \
const static Stub_template<big_endian> template_##T = { \
T##_INSNS, sizeof(T##_INSNS) / sizeof(T##_INSNS[0]) }; \
this->stub_templates_[T] = &template_##T
install_insn_template(ST_NONE);
install_insn_template(ST_ADRP_BRANCH);
install_insn_template(ST_LONG_BRANCH_ABS);
install_insn_template(ST_LONG_BRANCH_PCREL);
install_insn_template(ST_E_843419);
install_insn_template(ST_E_835769);
#undef install_insn_template
}
// Base class for stubs.
template<int size, bool big_endian>
class Stub_base
{
public:
typedef typename elfcpp::Elf_types<size>::Elf_Addr AArch64_address;
typedef typename AArch64_insn_utilities<big_endian>::Insntype Insntype;
static const AArch64_address invalid_address =
static_cast<AArch64_address>(-1);
static const section_offset_type invalid_offset =
static_cast<section_offset_type>(-1);
Stub_base(int type)
: destination_address_(invalid_address),
offset_(invalid_offset),
type_(type)
{}
~Stub_base()
{}
// Get stub type.
int
type() const
{ return this->type_; }
// Get stub template that provides stub insn information.
const Stub_template<big_endian>*
stub_template() const
{
return Stub_template_repertoire<big_endian>::
get_stub_template(this->type());
}
// Get destination address.
AArch64_address
destination_address() const
{
gold_assert(this->destination_address_ != this->invalid_address);
return this->destination_address_;
}
// Set destination address.
void
set_destination_address(AArch64_address address)
{
gold_assert(address != this->invalid_address);
this->destination_address_ = address;
}
// Reset the destination address.
void
reset_destination_address()
{ this->destination_address_ = this->invalid_address; }
// Get offset of code stub. For Reloc_stub, it is the offset from the
// beginning of its containing stub table; for Erratum_stub, it is the offset
// from the end of reloc_stubs.
section_offset_type
offset() const
{
gold_assert(this->offset_ != this->invalid_offset);
return this->offset_;
}
// Set stub offset.
void
set_offset(section_offset_type offset)
{ this->offset_ = offset; }
// Return the stub insn.
const Insntype*
insns() const
{ return this->stub_template()->insns; }
// Return num of stub insns.
unsigned int
insn_num() const
{ return this->stub_template()->insn_num; }
// Get size of the stub.
int
stub_size() const
{
return this->insn_num() *
AArch64_insn_utilities<big_endian>::BYTES_PER_INSN;
}
// Write stub to output file.
void
write(unsigned char* view, section_size_type view_size)
{ this->do_write(view, view_size); }
protected:
// Abstract method to be implemented by sub-classes.
virtual void
do_write(unsigned char*, section_size_type) = 0;
private:
// The last insn of a stub is a jump to destination insn. This field records
// the destination address.
AArch64_address destination_address_;
// The stub offset. Note this has difference interpretations between an
// Reloc_stub and an Erratum_stub. For Reloc_stub this is the offset from the
// beginning of the containing stub_table, whereas for Erratum_stub, this is
// the offset from the end of reloc_stubs.
section_offset_type offset_;
// Stub type.
const int type_;
}; // End of "Stub_base".
// Erratum stub class. An erratum stub differs from a reloc stub in that for
// each erratum occurrence, we generate an erratum stub. We never share erratum
// stubs, whereas for reloc stubs, different branch insns share a single reloc
// stub as long as the branch targets are the same. (More to the point, reloc
// stubs can be shared because they're used to reach a specific target, whereas
// erratum stubs branch back to the original control flow.)
template<int size, bool big_endian>
class Erratum_stub : public Stub_base<size, big_endian>
{
public:
typedef AArch64_relobj<size, big_endian> The_aarch64_relobj;
typedef typename elfcpp::Elf_types<size>::Elf_Addr AArch64_address;
typedef AArch64_insn_utilities<big_endian> Insn_utilities;
typedef typename AArch64_insn_utilities<big_endian>::Insntype Insntype;
static const int STUB_ADDR_ALIGN;
static const Insntype invalid_insn = static_cast<Insntype>(-1);
Erratum_stub(The_aarch64_relobj* relobj, int type,
unsigned shndx, unsigned int sh_offset)
: Stub_base<size, big_endian>(type), relobj_(relobj),
shndx_(shndx), sh_offset_(sh_offset),
erratum_insn_(invalid_insn),
erratum_address_(this->invalid_address)
{}
~Erratum_stub() {}
// Return the object that contains the erratum.
The_aarch64_relobj*
relobj()
{ return this->relobj_; }
// Get section index of the erratum.
unsigned int
shndx() const
{ return this->shndx_; }
// Get section offset of the erratum.
unsigned int
sh_offset() const
{ return this->sh_offset_; }
// Get the erratum insn. This is the insn located at erratum_insn_address.
Insntype
erratum_insn() const
{
gold_assert(this->erratum_insn_ != this->invalid_insn);
return this->erratum_insn_;
}
// Set the insn that the erratum happens to.
void
set_erratum_insn(Insntype insn)
{ this->erratum_insn_ = insn; }
// For 843419, the erratum insn is ld/st xt, [xn, #uimm], which may be a
// relocation spot, in this case, the erratum_insn_ recorded at scanning phase
// is no longer the one we want to write out to the stub, update erratum_insn_
// with relocated version. Also note that in this case xn must not be "PC", so
// it is safe to move the erratum insn from the origin place to the stub. For
// 835769, the erratum insn is multiply-accumulate insn, which could not be a
// relocation spot (assertion added though).
void
update_erratum_insn(Insntype insn)
{
gold_assert(this->erratum_insn_ != this->invalid_insn);
switch (this->type())
{
case ST_E_843419:
gold_assert(Insn_utilities::aarch64_ldst_uimm(insn));
gold_assert(Insn_utilities::aarch64_ldst_uimm(this->erratum_insn()));
gold_assert(Insn_utilities::aarch64_rd(insn) ==
Insn_utilities::aarch64_rd(this->erratum_insn()));
gold_assert(Insn_utilities::aarch64_rn(insn) ==
Insn_utilities::aarch64_rn(this->erratum_insn()));
// Update plain ld/st insn with relocated insn.
this->erratum_insn_ = insn;
break;
case ST_E_835769:
gold_assert(insn == this->erratum_insn());
break;
default:
gold_unreachable();
}
}
// Return the address where an erratum must be done.
AArch64_address
erratum_address() const
{
gold_assert(this->erratum_address_ != this->invalid_address);
return this->erratum_address_;
}
// Set the address where an erratum must be done.
void
set_erratum_address(AArch64_address addr)
{ this->erratum_address_ = addr; }
// Later relaxation passes of may alter the recorded erratum and destination
// address. Given an up to date output section address of shidx_ in
// relobj_ we can derive the erratum_address and destination address.
void
update_erratum_address(AArch64_address output_section_addr)
{
const int BPI = AArch64_insn_utilities<big_endian>::BYTES_PER_INSN;
AArch64_address updated_addr = output_section_addr + this->sh_offset_;
this->set_erratum_address(updated_addr);
this->set_destination_address(updated_addr + BPI);
}
// Comparator used to group Erratum_stubs in a set by (obj, shndx,
// sh_offset). We do not include 'type' in the calculation, because there is
// at most one stub type at (obj, shndx, sh_offset).
bool
operator<(const Erratum_stub<size, big_endian>& k) const
{
if (this == &k)
return false;
// We group stubs by relobj.
if (this->relobj_ != k.relobj_)
return this->relobj_ < k.relobj_;
// Then by section index.
if (this->shndx_ != k.shndx_)
return this->shndx_ < k.shndx_;
// Lastly by section offset.
return this->sh_offset_ < k.sh_offset_;
}
void
invalidate_erratum_stub()
{
gold_assert(this->erratum_insn_ != invalid_insn);
this->erratum_insn_ = invalid_insn;
}
bool
is_invalidated_erratum_stub()
{ return this->erratum_insn_ == invalid_insn; }
protected:
virtual void
do_write(unsigned char*, section_size_type);
private:
// The object that needs to be fixed.
The_aarch64_relobj* relobj_;
// The shndx in the object that needs to be fixed.
const unsigned int shndx_;
// The section offset in the obejct that needs to be fixed.
const unsigned int sh_offset_;
// The insn to be fixed.
Insntype erratum_insn_;
// The address of the above insn.
AArch64_address erratum_address_;
}; // End of "Erratum_stub".
// Erratum sub class to wrap additional info needed by 843419. In fixing this
// erratum, we may choose to replace 'adrp' with 'adr', in this case, we need
// adrp's code position (two or three insns before erratum insn itself).
template<int size, bool big_endian>
class E843419_stub : public Erratum_stub<size, big_endian>
{
public:
typedef typename AArch64_insn_utilities<big_endian>::Insntype Insntype;
E843419_stub(AArch64_relobj<size, big_endian>* relobj,
unsigned int shndx, unsigned int sh_offset,
unsigned int adrp_sh_offset)
: Erratum_stub<size, big_endian>(relobj, ST_E_843419, shndx, sh_offset),
adrp_sh_offset_(adrp_sh_offset)
{}
unsigned int
adrp_sh_offset() const
{ return this->adrp_sh_offset_; }
private:
// Section offset of "adrp". (We do not need a "adrp_shndx_" field, because we
// can obtain it from its parent.)
const unsigned int adrp_sh_offset_;
};
template<int size, bool big_endian>
const int Erratum_stub<size, big_endian>::STUB_ADDR_ALIGN = 4;
// Comparator used in set definition.
template<int size, bool big_endian>
struct Erratum_stub_less
{
bool
operator()(const Erratum_stub<size, big_endian>* s1,
const Erratum_stub<size, big_endian>* s2) const
{ return *s1 < *s2; }
};
// Erratum_stub implementation for writing stub to output file.
template<int size, bool big_endian>
void
Erratum_stub<size, big_endian>::do_write(unsigned char* view, section_size_type)
{
typedef typename elfcpp::Swap<32, big_endian>::Valtype Insntype;
const Insntype* insns = this->insns();
uint32_t num_insns = this->insn_num();
Insntype* ip = reinterpret_cast<Insntype*>(view);
// For current implemented erratum 843419 and 835769, the first insn in the
// stub is always a copy of the problematic insn (in 843419, the mem access
// insn, in 835769, the mac insn), followed by a jump-back.
elfcpp::Swap<32, big_endian>::writeval(ip, this->erratum_insn());
for (uint32_t i = 1; i < num_insns; ++i)
elfcpp::Swap<32, big_endian>::writeval(ip + i, insns[i]);
}
// Reloc stub class.
template<int size, bool big_endian>
class Reloc_stub : public Stub_base<size, big_endian>
{
public:
typedef Reloc_stub<size, big_endian> This;
typedef typename elfcpp::Elf_types<size>::Elf_Addr AArch64_address;
// Branch range. This is used to calculate the section group size, as well as
// determine whether a stub is needed.
static const int MAX_BRANCH_OFFSET = ((1 << 25) - 1) << 2;
static const int MIN_BRANCH_OFFSET = -((1 << 25) << 2);
// Constant used to determine if an offset fits in the adrp instruction
// encoding.
static const int MAX_ADRP_IMM = (1 << 20) - 1;
static const int MIN_ADRP_IMM = -(1 << 20);
static const int BYTES_PER_INSN = 4;
static const int STUB_ADDR_ALIGN;
// Determine whether the offset fits in the jump/branch instruction.
static bool
aarch64_valid_branch_offset_p(int64_t offset)
{ return offset >= MIN_BRANCH_OFFSET && offset <= MAX_BRANCH_OFFSET; }
// Determine whether the offset fits in the adrp immediate field.
static bool
aarch64_valid_for_adrp_p(AArch64_address location, AArch64_address dest)
{
typedef AArch64_relocate_functions<size, big_endian> Reloc;
int64_t adrp_imm = Reloc::Page (dest) - Reloc::Page (location);
adrp_imm = adrp_imm < 0 ? ~(~adrp_imm >> 12) : adrp_imm >> 12;
return adrp_imm >= MIN_ADRP_IMM && adrp_imm <= MAX_ADRP_IMM;
}
// Determine the stub type for a certain relocation or ST_NONE, if no stub is
// needed.
static int
stub_type_for_reloc(unsigned int r_type, AArch64_address address,
AArch64_address target);
Reloc_stub(int type)
: Stub_base<size, big_endian>(type)
{ }
~Reloc_stub()
{ }
// The key class used to index the stub instance in the stub table's stub map.
class Key
{
public:
Key(int type, const Symbol* symbol, const Relobj* relobj,
unsigned int r_sym, int32_t addend)
: type_(type), addend_(addend)
{
if (symbol != NULL)
{
this->r_sym_ = Reloc_stub::invalid_index;
this->u_.symbol = symbol;
}
else
{
gold_assert(relobj != NULL && r_sym != invalid_index);
this->r_sym_ = r_sym;
this->u_.relobj = relobj;
}
}
~Key()
{ }
// Return stub type.
int
type() const
{ return this->type_; }
// Return the local symbol index or invalid_index.
unsigned int
r_sym() const
{ return this->r_sym_; }
// Return the symbol if there is one.
const Symbol*
symbol() const
{ return this->r_sym_ == invalid_index ? this->u_.symbol : NULL; }
// Return the relobj if there is one.
const Relobj*
relobj() const
{ return this->r_sym_ != invalid_index ? this->u_.relobj : NULL; }
// Whether this equals to another key k.
bool
eq(const Key& k) const
{
return ((this->type_ == k.type_)
&& (this->r_sym_ == k.r_sym_)
&& ((this->r_sym_ != Reloc_stub::invalid_index)
? (this->u_.relobj == k.u_.relobj)
: (this->u_.symbol == k.u_.symbol))
&& (this->addend_ == k.addend_));
}
// Return a hash value.
size_t
hash_value() const
{
size_t name_hash_value = gold::string_hash<char>(
(this->r_sym_ != Reloc_stub::invalid_index)
? this->u_.relobj->name().c_str()
: this->u_.symbol->name());
// We only have 4 stub types.
size_t stub_type_hash_value = 0x03 & this->type_;
return (name_hash_value
^ stub_type_hash_value
^ ((this->r_sym_ & 0x3fff) << 2)
^ ((this->addend_ & 0xffff) << 16));
}
// Functors for STL associative containers.
struct hash
{
size_t
operator()(const Key& k) const
{ return k.hash_value(); }
};
struct equal_to
{
bool
operator()(const Key& k1, const Key& k2) const
{ return k1.eq(k2); }
};
private:
// Stub type.
const int type_;
// If this is a local symbol, this is the index in the defining object.
// Otherwise, it is invalid_index for a global symbol.
unsigned int r_sym_;
// If r_sym_ is an invalid index, this points to a global symbol.
// Otherwise, it points to a relobj. We used the unsized and target
// independent Symbol and Relobj classes instead of Sized_symbol<32> and
// Arm_relobj, in order to avoid making the stub class a template
// as most of the stub machinery is endianness-neutral. However, it
// may require a bit of casting done by users of this class.
union
{
const Symbol* symbol;
const Relobj* relobj;
} u_;
// Addend associated with a reloc.
int32_t addend_;
}; // End of inner class Reloc_stub::Key
protected:
// This may be overridden in the child class.
virtual void
do_write(unsigned char*, section_size_type);
private:
static const unsigned int invalid_index = static_cast<unsigned int>(-1);
}; // End of Reloc_stub
template<int size, bool big_endian>
const int Reloc_stub<size, big_endian>::STUB_ADDR_ALIGN = 4;
// Write data to output file.
template<int size, bool big_endian>
void
Reloc_stub<size, big_endian>::
do_write(unsigned char* view, section_size_type)
{
typedef typename elfcpp::Swap<32, big_endian>::Valtype Insntype;
const uint32_t* insns = this->insns();
uint32_t num_insns = this->insn_num();
Insntype* ip = reinterpret_cast<Insntype*>(view);
for (uint32_t i = 0; i < num_insns; ++i)
elfcpp::Swap<32, big_endian>::writeval(ip + i, insns[i]);
}
// Determine the stub type for a certain relocation or ST_NONE, if no stub is
// needed.
template<int size, bool big_endian>
inline int
Reloc_stub<size, big_endian>::stub_type_for_reloc(
unsigned int r_type, AArch64_address location, AArch64_address dest)
{
int64_t branch_offset = 0;
switch(r_type)
{
case elfcpp::R_AARCH64_CALL26:
case elfcpp::R_AARCH64_JUMP26:
branch_offset = dest - location;
break;
default:
gold_unreachable();
}
if (aarch64_valid_branch_offset_p(branch_offset))
return ST_NONE;
if (aarch64_valid_for_adrp_p(location, dest))
return ST_ADRP_BRANCH;
// Always use PC-relative addressing in case of -shared or -pie.
if (parameters->options().output_is_position_independent())
return ST_LONG_BRANCH_PCREL;
// This saves 2 insns per stub, compared to ST_LONG_BRANCH_PCREL.
// But is only applicable to non-shared or non-pie.
return ST_LONG_BRANCH_ABS;
}
// A class to hold stubs for the ARM target. This contains 2 different types of
// stubs - reloc stubs and erratum stubs.
template<int size, bool big_endian>
class Stub_table : public Output_data
{
public:
typedef Target_aarch64<size, big_endian> The_target_aarch64;
typedef typename elfcpp::Elf_types<size>::Elf_Addr AArch64_address;
typedef AArch64_relobj<size, big_endian> The_aarch64_relobj;
typedef AArch64_input_section<size, big_endian> The_aarch64_input_section;
typedef Reloc_stub<size, big_endian> The_reloc_stub;
typedef typename The_reloc_stub::Key The_reloc_stub_key;
typedef Erratum_stub<size, big_endian> The_erratum_stub;
typedef Erratum_stub_less<size, big_endian> The_erratum_stub_less;
typedef typename The_reloc_stub_key::hash The_reloc_stub_key_hash;
typedef typename The_reloc_stub_key::equal_to The_reloc_stub_key_equal_to;
typedef Stub_table<size, big_endian> The_stub_table;
typedef Unordered_map<The_reloc_stub_key, The_reloc_stub*,
The_reloc_stub_key_hash, The_reloc_stub_key_equal_to>
Reloc_stub_map;
typedef typename Reloc_stub_map::const_iterator Reloc_stub_map_const_iter;
typedef Relocate_info<size, big_endian> The_relocate_info;
typedef std::set<The_erratum_stub*, The_erratum_stub_less> Erratum_stub_set;
typedef typename Erratum_stub_set::iterator Erratum_stub_set_iter;
Stub_table(The_aarch64_input_section* owner)
: Output_data(), owner_(owner), reloc_stubs_size_(0),
erratum_stubs_size_(0), prev_data_size_(0)
{ }
~Stub_table()
{ }
The_aarch64_input_section*
owner() const
{ return owner_; }
// Whether this stub table is empty.
bool
empty() const
{ return reloc_stubs_.empty() && erratum_stubs_.empty(); }
// Return the current data size.
off_t
current_data_size() const
{ return this->current_data_size_for_child(); }
// Add a STUB using KEY. The caller is responsible for avoiding addition
// if a STUB with the same key has already been added.
void
add_reloc_stub(The_reloc_stub* stub, const The_reloc_stub_key& key);
// Add an erratum stub into the erratum stub set. The set is ordered by
// (relobj, shndx, sh_offset).
void
add_erratum_stub(The_erratum_stub* stub);
// Find if such erratum exists for any given (obj, shndx, sh_offset).
The_erratum_stub*
find_erratum_stub(The_aarch64_relobj* a64relobj,
unsigned int shndx, unsigned int sh_offset);
// Find all the erratums for a given input section. The return value is a pair
// of iterators [begin, end).
std::pair<Erratum_stub_set_iter, Erratum_stub_set_iter>
find_erratum_stubs_for_input_section(The_aarch64_relobj* a64relobj,
unsigned int shndx);
// Compute the erratum stub address.
AArch64_address
erratum_stub_address(The_erratum_stub* stub) const
{
AArch64_address r = align_address(this->address() + this->reloc_stubs_size_,
The_erratum_stub::STUB_ADDR_ALIGN);
r += stub->offset();
return r;
}
// Finalize stubs. No-op here, just for completeness.
void
finalize_stubs()
{ }
// Look up a relocation stub using KEY. Return NULL if there is none.
The_reloc_stub*
find_reloc_stub(The_reloc_stub_key& key)
{
Reloc_stub_map_const_iter p = this->reloc_stubs_.find(key);
return (p != this->reloc_stubs_.end()) ? p->second : NULL;
}
// Relocate reloc stubs in this stub table. This does not relocate erratum stubs.
void
relocate_reloc_stubs(const The_relocate_info*,
The_target_aarch64*,
Output_section*,
unsigned char*,
AArch64_address,
section_size_type);
// Relocate an erratum stub.
void
relocate_erratum_stub(The_erratum_stub*, unsigned char*);
// Update data size at the end of a relaxation pass. Return true if data size
// is different from that of the previous relaxation pass.
bool
update_data_size_changed_p()
{
// No addralign changed here.
off_t s = align_address(this->reloc_stubs_size_,
The_erratum_stub::STUB_ADDR_ALIGN)
+ this->erratum_stubs_size_;
bool changed = (s != this->prev_data_size_);
this->prev_data_size_ = s;
return changed;
}
protected:
// Write out section contents.
void
do_write(Output_file*);
// Return the required alignment.
uint64_t
do_addralign() const
{
return std::max(The_reloc_stub::STUB_ADDR_ALIGN,
The_erratum_stub::STUB_ADDR_ALIGN);
}
// Reset address and file offset.
void
do_reset_address_and_file_offset()
{ this->set_current_data_size_for_child(this->prev_data_size_); }
// Set final data size.
void
set_final_data_size()
{ this->set_data_size(this->current_data_size()); }
private:
// Relocate one reloc stub.
void
relocate_reloc_stub(The_reloc_stub*,
const The_relocate_info*,
The_target_aarch64*,
Output_section*,
unsigned char*,
AArch64_address,
section_size_type);
private:
// Owner of this stub table.
The_aarch64_input_section* owner_;
// The relocation stubs.
Reloc_stub_map reloc_stubs_;
// The erratum stubs.
Erratum_stub_set erratum_stubs_;
// Size of reloc stubs.
off_t reloc_stubs_size_;
// Size of erratum stubs.
off_t erratum_stubs_size_;
// data size of this in the previous pass.
off_t prev_data_size_;
}; // End of Stub_table
// Add an erratum stub into the erratum stub set. The set is ordered by
// (relobj, shndx, sh_offset).
template<int size, bool big_endian>
void
Stub_table<size, big_endian>::add_erratum_stub(The_erratum_stub* stub)
{
std::pair<Erratum_stub_set_iter, bool> ret =
this->erratum_stubs_.insert(stub);
gold_assert(ret.second);
this->erratum_stubs_size_ = align_address(
this->erratum_stubs_size_, The_erratum_stub::STUB_ADDR_ALIGN);
stub->set_offset(this->erratum_stubs_size_);
this->erratum_stubs_size_ += stub->stub_size();
}
// Find if such erratum exists for given (obj, shndx, sh_offset).
template<int size, bool big_endian>
Erratum_stub<size, big_endian>*
Stub_table<size, big_endian>::find_erratum_stub(
The_aarch64_relobj* a64relobj, unsigned int shndx, unsigned int sh_offset)
{
// A dummy object used as key to search in the set.
The_erratum_stub key(a64relobj, ST_NONE,
shndx, sh_offset);
Erratum_stub_set_iter i = this->erratum_stubs_.find(&key);
if (i != this->erratum_stubs_.end())
{
The_erratum_stub* stub(*i);
gold_assert(stub->erratum_insn() != 0);
return stub;
}
return NULL;
}
// Find all the errata for a given input section. The return value is a pair of
// iterators [begin, end).
template<int size, bool big_endian>
std::pair<typename Stub_table<size, big_endian>::Erratum_stub_set_iter,
typename Stub_table<size, big_endian>::Erratum_stub_set_iter>
Stub_table<size, big_endian>::find_erratum_stubs_for_input_section(
The_aarch64_relobj* a64relobj, unsigned int shndx)
{
typedef std::pair<Erratum_stub_set_iter, Erratum_stub_set_iter> Result_pair;
Erratum_stub_set_iter start, end;
The_erratum_stub low_key(a64relobj, ST_NONE, shndx, 0);
start = this->erratum_stubs_.lower_bound(&low_key);
if (start == this->erratum_stubs_.end())
return Result_pair(this->erratum_stubs_.end(),
this->erratum_stubs_.end());
end = start;
while (end != this->erratum_stubs_.end() &&
(*end)->relobj() == a64relobj && (*end)->shndx() == shndx)
++end;
return Result_pair(start, end);
}
// Add a STUB using KEY. The caller is responsible for avoiding addition
// if a STUB with the same key has already been added.
template<int size, bool big_endian>
void
Stub_table<size, big_endian>::add_reloc_stub(
The_reloc_stub* stub, const The_reloc_stub_key& key)
{
gold_assert(stub->type() == key.type());
this->reloc_stubs_[key] = stub;
// Assign stub offset early. We can do this because we never remove
// reloc stubs and they are in the beginning of the stub table.
this->reloc_stubs_size_ = align_address(this->reloc_stubs_size_,
The_reloc_stub::STUB_ADDR_ALIGN);
stub->set_offset(this->reloc_stubs_size_);
this->reloc_stubs_size_ += stub->stub_size();
}
// Relocate an erratum stub.
template<int size, bool big_endian>
void
Stub_table<size, big_endian>::
relocate_erratum_stub(The_erratum_stub* estub,
unsigned char* view)
{
// Just for convenience.
const int BPI = AArch64_insn_utilities<big_endian>::BYTES_PER_INSN;
gold_assert(!estub->is_invalidated_erratum_stub());
AArch64_address stub_address = this->erratum_stub_address(estub);
// The address of "b" in the stub that is to be "relocated".
AArch64_address stub_b_insn_address;
// Branch offset that is to be filled in "b" insn.
int b_offset = 0;
switch (estub->type())
{
case ST_E_843419:
case ST_E_835769:
// The 1st insn of the erratum could be a relocation spot,
// in this case we need to fix it with
// "(*i)->erratum_insn()".
elfcpp::Swap<32, big_endian>::writeval(
view + (stub_address - this->address()),
estub->erratum_insn());
// For the erratum, the 2nd insn is a b-insn to be patched
// (relocated).
stub_b_insn_address = stub_address + 1 * BPI;
b_offset = estub->destination_address() - stub_b_insn_address;
AArch64_relocate_functions<size, big_endian>::construct_b(
view + (stub_b_insn_address - this->address()),
((unsigned int)(b_offset)) & 0xfffffff);
break;
default:
gold_unreachable();
break;
}
estub->invalidate_erratum_stub();
}
// Relocate only reloc stubs in this stub table. This does not relocate erratum
// stubs.
template<int size, bool big_endian>
void
Stub_table<size, big_endian>::
relocate_reloc_stubs(const The_relocate_info* relinfo,
The_target_aarch64* target_aarch64,
Output_section* output_section,
unsigned char* view,
AArch64_address address,
section_size_type view_size)
{
// "view_size" is the total size of the stub_table.
gold_assert(address == this->address() &&
view_size == static_cast<section_size_type>(this->data_size()));
for(Reloc_stub_map_const_iter p = this->reloc_stubs_.begin();
p != this->reloc_stubs_.end(); ++p)
relocate_reloc_stub(p->second, relinfo, target_aarch64, output_section,
view, address, view_size);
}
// Relocate one reloc stub. This is a helper for
// Stub_table::relocate_reloc_stubs().
template<int size, bool big_endian>
void
Stub_table<size, big_endian>::
relocate_reloc_stub(The_reloc_stub* stub,
const The_relocate_info* relinfo,
The_target_aarch64* target_aarch64,
Output_section* output_section,
unsigned char* view,
AArch64_address address,
section_size_type view_size)
{
// "offset" is the offset from the beginning of the stub_table.
section_size_type offset = stub->offset();
section_size_type stub_size = stub->stub_size();
// "view_size" is the total size of the stub_table.
gold_assert(offset + stub_size <= view_size);
target_aarch64->relocate_reloc_stub(stub, relinfo, output_section,
view + offset, address + offset, view_size);
}
// Write out the stubs to file.
template<int size, bool big_endian>
void
Stub_table<size, big_endian>::do_write(Output_file* of)
{
off_t offset = this->offset();
const section_size_type oview_size =
convert_to_section_size_type(this->data_size());
unsigned char* const oview = of->get_output_view(offset, oview_size);
// Write relocation stubs.
for (typename Reloc_stub_map::const_iterator p = this->reloc_stubs_.begin();
p != this->reloc_stubs_.end(); ++p)
{
The_reloc_stub* stub = p->second;
AArch64_address address = this->address() + stub->offset();
gold_assert(address ==
align_address(address, The_reloc_stub::STUB_ADDR_ALIGN));
stub->write(oview + stub->offset(), stub->stub_size());
}
// Write erratum stubs.
unsigned int erratum_stub_start_offset =
align_address(this->reloc_stubs_size_, The_erratum_stub::STUB_ADDR_ALIGN);
for (typename Erratum_stub_set::iterator p = this->erratum_stubs_.begin();
p != this->erratum_stubs_.end(); ++p)
{
The_erratum_stub* stub(*p);
stub->write(oview + erratum_stub_start_offset + stub->offset(),
stub->stub_size());
}
of->write_output_view(this->offset(), oview_size, oview);
}
// AArch64_relobj class.
template<int size, bool big_endian>
class AArch64_relobj : public Sized_relobj_file<size, big_endian>
{
public:
typedef AArch64_relobj<size, big_endian> This;
typedef Target_aarch64<size, big_endian> The_target_aarch64;
typedef AArch64_input_section<size, big_endian> The_aarch64_input_section;
typedef typename elfcpp::Elf_types<size>::Elf_Addr AArch64_address;
typedef Stub_table<size, big_endian> The_stub_table;
typedef Erratum_stub<size, big_endian> The_erratum_stub;
typedef typename The_stub_table::Erratum_stub_set_iter Erratum_stub_set_iter;
typedef std::vector<The_stub_table*> Stub_table_list;
static const AArch64_address invalid_address =
static_cast<AArch64_address>(-1);
AArch64_relobj(const std::string& name, Input_file* input_file, off_t offset,
const typename elfcpp::Ehdr<size, big_endian>& ehdr)
: Sized_relobj_file<size, big_endian>(name, input_file, offset, ehdr),
stub_tables_()
{ }
~AArch64_relobj()
{ }
// Return the stub table of the SHNDX-th section if there is one.
The_stub_table*
stub_table(unsigned int shndx) const
{
gold_assert(shndx < this->stub_tables_.size());
return this->stub_tables_[shndx];
}
// Set STUB_TABLE to be the stub_table of the SHNDX-th section.
void
set_stub_table(unsigned int shndx, The_stub_table* stub_table)
{
gold_assert(shndx < this->stub_tables_.size());
this->stub_tables_[shndx] = stub_table;
}
// Entrance to errata scanning.
void
scan_errata(unsigned int shndx,
const elfcpp::Shdr<size, big_endian>&,
Output_section*, const Symbol_table*,
The_target_aarch64*);
// Scan all relocation sections for stub generation.
void
scan_sections_for_stubs(The_target_aarch64*, const Symbol_table*,
const Layout*);
// Whether a section is a scannable text section.
bool
text_section_is_scannable(const elfcpp::Shdr<size, big_endian>&, unsigned int,
const Output_section*, const Symbol_table*);
// Convert regular input section with index SHNDX to a relaxed section.
void
convert_input_section_to_relaxed_section(unsigned shndx)
{
// The stubs have relocations and we need to process them after writing
// out the stubs. So relocation now must follow section write.
this->set_section_offset(shndx, -1ULL);
this->set_relocs_must_follow_section_writes();
}
// Structure for mapping symbol position.
struct Mapping_symbol_position
{
Mapping_symbol_position(unsigned int shndx, AArch64_address offset):
shndx_(shndx), offset_(offset)
{}
// "<" comparator used in ordered_map container.
bool
operator<(const Mapping_symbol_position& p) const
{
return (this->shndx_ < p.shndx_
|| (this->shndx_ == p.shndx_ && this->offset_ < p.offset_));
}
// Section index.
unsigned int shndx_;
// Section offset.
AArch64_address offset_;
};
typedef std::map<Mapping_symbol_position, char> Mapping_symbol_info;
protected:
// Post constructor setup.
void
do_setup()
{
// Call parent's setup method.
Sized_relobj_file<size, big_endian>::do_setup();
// Initialize look-up tables.
this->stub_tables_.resize(this->shnum());
}
virtual void
do_relocate_sections(
const Symbol_table* symtab, const Layout* layout,
const unsigned char* pshdrs, Output_file* of,
typename Sized_relobj_file<size, big_endian>::Views* pviews);
// Count local symbols and (optionally) record mapping info.
virtual void
do_count_local_symbols(Stringpool_template<char>*,
Stringpool_template<char>*);
private:
// Fix all errata in the object, and for each erratum, relocate corresponding
// erratum stub.
void
fix_errata_and_relocate_erratum_stubs(
typename Sized_relobj_file<size, big_endian>::Views* pviews);
// Try to fix erratum 843419 in an optimized way. Return true if patch is
// applied.
bool
try_fix_erratum_843419_optimized(
The_erratum_stub*, AArch64_address,
typename Sized_relobj_file<size, big_endian>::View_size&);
// Whether a section needs to be scanned for relocation stubs.
bool
section_needs_reloc_stub_scanning(const elfcpp::Shdr<size, big_endian>&,
const Relobj::Output_sections&,
const Symbol_table*, const unsigned char*);
// List of stub tables.
Stub_table_list stub_tables_;
// Mapping symbol information sorted by (section index, section_offset).
Mapping_symbol_info mapping_symbol_info_;
}; // End of AArch64_relobj
// Override to record mapping symbol information.
template<int size, bool big_endian>
void
AArch64_relobj<size, big_endian>::do_count_local_symbols(
Stringpool_template<char>* pool, Stringpool_template<char>* dynpool)
{
Sized_relobj_file<size, big_endian>::do_count_local_symbols(pool, dynpool);
// Only erratum-fixing work needs mapping symbols, so skip this time consuming
// processing if not fixing erratum.
if (!parameters->options().fix_cortex_a53_843419()
&& !parameters->options().fix_cortex_a53_835769())
return;
const unsigned int loccount = this->local_symbol_count();
if (loccount == 0)
return;
// Read the symbol table section header.
const unsigned int symtab_shndx = this->symtab_shndx();
elfcpp::Shdr<size, big_endian>
symtabshdr(this, this->elf_file()->section_header(symtab_shndx));
gold_assert(symtabshdr.get_sh_type() == elfcpp::SHT_SYMTAB);
// Read the local symbols.
const int sym_size =elfcpp::Elf_sizes<size>::sym_size;
gold_assert(loccount == symtabshdr.get_sh_info());
off_t locsize = loccount * sym_size;
const unsigned char* psyms = this->get_view(symtabshdr.get_sh_offset(),
locsize, true, true);
// For mapping symbol processing, we need to read the symbol names.
unsigned int strtab_shndx = this->adjust_shndx(symtabshdr.get_sh_link());
if (strtab_shndx >= this->shnum())
{
this->error(_("invalid symbol table name index: %u"), strtab_shndx);
return;
}
elfcpp::Shdr<size, big_endian>
strtabshdr(this, this->elf_file()->section_header(strtab_shndx));
if (strtabshdr.get_sh_type() != elfcpp::SHT_STRTAB)
{
this->error(_("symbol table name section has wrong type: %u"),
static_cast<unsigned int>(strtabshdr.get_sh_type()));
return;
}
const char* pnames =
reinterpret_cast<const char*>(this->get_view(strtabshdr.get_sh_offset(),
strtabshdr.get_sh_size(),
false, false));
// Skip the first dummy symbol.
psyms += sym_size;
typename Sized_relobj_file<size, big_endian>::Local_values*
plocal_values = this->local_values();
for (unsigned int i = 1; i < loccount; ++i, psyms += sym_size)
{
elfcpp::Sym<size, big_endian> sym(psyms);
Symbol_value<size>& lv((*plocal_values)[i]);
AArch64_address input_value = lv.input_value();
// Check to see if this is a mapping symbol. AArch64 mapping symbols are
// defined in "ELF for the ARM 64-bit Architecture", Table 4-4, Mapping
// symbols.
// Mapping symbols could be one of the following 4 forms -
// a) $x
// b) $x.<any...>
// c) $d
// d) $d.<any...>
const char* sym_name = pnames + sym.get_st_name();
if (sym_name[0] == '$' && (sym_name[1] == 'x' || sym_name[1] == 'd')
&& (sym_name[2] == '\0' || sym_name[2] == '.'))
{
bool is_ordinary;
unsigned int input_shndx =
this->adjust_sym_shndx(i, sym.get_st_shndx(), &is_ordinary);
gold_assert(is_ordinary);
Mapping_symbol_position msp(input_shndx, input_value);
// Insert mapping_symbol_info into map whose ordering is defined by
// (shndx, offset_within_section).
this->mapping_symbol_info_[msp] = sym_name[1];
}
}
}
// Fix all errata in the object and for each erratum, we relocate the
// corresponding erratum stub (by calling Stub_table::relocate_erratum_stub).
template<int size, bool big_endian>
void
AArch64_relobj<size, big_endian>::fix_errata_and_relocate_erratum_stubs(
typename Sized_relobj_file<size, big_endian>::Views* pviews)
{
typedef typename elfcpp::Swap<32,big_endian>::Valtype Insntype;
unsigned int shnum = this->shnum();
const Relobj::Output_sections& out_sections(this->output_sections());
for (unsigned int i = 1; i < shnum; ++i)
{
The_stub_table* stub_table = this->stub_table(i);
if (!stub_table)
continue;
std::pair<Erratum_stub_set_iter, Erratum_stub_set_iter>
ipair(stub_table->find_erratum_stubs_for_input_section(this, i));
Erratum_stub_set_iter p = ipair.first, end = ipair.second;
typename Sized_relobj_file<size, big_endian>::View_size&
pview((*pviews)[i]);
AArch64_address view_offset = 0;
if (pview.is_input_output_view)
{
// In this case, write_sections has not added the output offset to
// the view's address, so we must do so. Currently this only happens
// for a relaxed section.
unsigned int index = this->adjust_shndx(i);
const Output_relaxed_input_section* poris =
out_sections[index]->find_relaxed_input_section(this, index);
gold_assert(poris != NULL);
view_offset = poris->address() - pview.address;
}
while (p != end)
{
The_erratum_stub* stub = *p;
// Double check data before fix.
gold_assert(pview.address + view_offset + stub->sh_offset()
== stub->erratum_address());
// Update previously recorded erratum insn with relocated
// version.
Insntype* ip =
reinterpret_cast<Insntype*>(
pview.view + view_offset + stub->sh_offset());
Insntype insn_to_fix = ip[0];
stub->update_erratum_insn(insn_to_fix);
// First try to see if erratum is 843419 and if it can be fixed
// without using branch-to-stub.
if (!try_fix_erratum_843419_optimized(stub, view_offset, pview))
{
// Replace the erratum insn with a branch-to-stub.
AArch64_address stub_address =
stub_table->erratum_stub_address(stub);
unsigned int b_offset = stub_address - stub->erratum_address();
AArch64_relocate_functions<size, big_endian>::construct_b(
pview.view + view_offset + stub->sh_offset(),
b_offset & 0xfffffff);
}
// Erratum fix is done (or skipped), continue to relocate erratum
// stub. Note, when erratum fix is skipped (either because we
// proactively change the code sequence or the code sequence is
// changed by relaxation, etc), we can still safely relocate the
// erratum stub, ignoring the fact the erratum could never be
// executed.
stub_table->relocate_erratum_stub(
stub,
pview.view + (stub_table->address() - pview.address));
// Next erratum stub.
++p;
}
}
}
// This is an optimization for 843419. This erratum requires the sequence begin
// with 'adrp', when final value calculated by adrp fits in adr, we can just
// replace 'adrp' with 'adr', so we save 2 jumps per occurrence. (Note, however,
// in this case, we do not delete the erratum stub (too late to do so), it is
// merely generated without ever being called.)
template<int size, bool big_endian>
bool
AArch64_relobj<size, big_endian>::try_fix_erratum_843419_optimized(
The_erratum_stub* stub, AArch64_address view_offset,
typename Sized_relobj_file<size, big_endian>::View_size& pview)
{
if (stub->type() != ST_E_843419)
return false;
typedef AArch64_insn_utilities<big_endian> Insn_utilities;
typedef typename elfcpp::Swap<32,big_endian>::Valtype Insntype;
E843419_stub<size, big_endian>* e843419_stub =
reinterpret_cast<E843419_stub<size, big_endian>*>(stub);
AArch64_address pc =
pview.address + view_offset + e843419_stub->adrp_sh_offset();
unsigned int adrp_offset = e843419_stub->adrp_sh_offset ();
Insntype* adrp_view =
reinterpret_cast<Insntype*>(pview.view + view_offset + adrp_offset);
Insntype adrp_insn = adrp_view[0];
// If the instruction at adrp_sh_offset is "mrs R, tpidr_el0", it may come
// from IE -> LE relaxation etc. This is a side-effect of TLS relaxation that
// ADRP has been turned into MRS, there is no erratum risk anymore.
// Therefore, we return true to avoid doing unnecessary branch-to-stub.
if (Insn_utilities::is_mrs_tpidr_el0(adrp_insn))
return true;
// If the instruction at adrp_sh_offset is not ADRP and the instruction before
// it is "mrs R, tpidr_el0", it may come from LD -> LE relaxation etc.
// Like the above case, there is no erratum risk any more, we can safely
// return true.
if (!Insn_utilities::is_adrp(adrp_insn) && adrp_offset)
{
Insntype* prev_view =
reinterpret_cast<Insntype*>(
pview.view + view_offset + adrp_offset - 4);
Insntype prev_insn = prev_view[0];
if (Insn_utilities::is_mrs_tpidr_el0(prev_insn))
return true;
}
/* If we reach here, the first instruction must be ADRP. */
gold_assert(Insn_utilities::is_adrp(adrp_insn));
// Get adrp 33-bit signed imm value.
int64_t adrp_imm = Insn_utilities::
aarch64_adrp_decode_imm(adrp_insn);
// adrp - final value transferred to target register is calculated as:
// PC[11:0] = Zeros(12)
// adrp_dest_value = PC + adrp_imm;
int64_t adrp_dest_value = (pc & ~((1 << 12) - 1)) + adrp_imm;
// adr -final value transferred to target register is calucalted as:
// PC + adr_imm
// So we have:
// PC + adr_imm = adrp_dest_value
// ==>
// adr_imm = adrp_dest_value - PC
int64_t adr_imm = adrp_dest_value - pc;
// Check if imm fits in adr (21-bit signed).
if (-(1 << 20) <= adr_imm && adr_imm < (1 << 20))
{
// Convert 'adrp' into 'adr'.
Insntype adr_insn = adrp_insn & ((1u << 31) - 1);
adr_insn = Insn_utilities::
aarch64_adr_encode_imm(adr_insn, adr_imm);
elfcpp::Swap<32, big_endian>::writeval(adrp_view, adr_insn);
return true;
}
return false;
}
// Relocate sections.
template<int size, bool big_endian>
void
AArch64_relobj<size, big_endian>::do_relocate_sections(
const Symbol_table* symtab, const Layout* layout,
const unsigned char* pshdrs, Output_file* of,
typename Sized_relobj_file<size, big_endian>::Views* pviews)
{
// Relocate the section data.
this->relocate_section_range(symtab, layout, pshdrs, of, pviews,
1, this->shnum() - 1);
// We do not generate stubs if doing a relocatable link.
if (parameters->options().relocatable())
return;
// This part only relocates erratum stubs that belong to input sections of this
// object file.
if (parameters->options().fix_cortex_a53_843419()
|| parameters->options().fix_cortex_a53_835769())
this->fix_errata_and_relocate_erratum_stubs(pviews);
Relocate_info<size, big_endian> relinfo;
relinfo.symtab = symtab;
relinfo.layout = layout;
relinfo.object = this;
// This part relocates all reloc stubs that are contained in stub_tables of
// this object file.
unsigned int shnum = this->shnum();
The_target_aarch64* target = The_target_aarch64::current_target();
for (unsigned int i = 1; i < shnum; ++i)
{
The_aarch64_input_section* aarch64_input_section =
target->find_aarch64_input_section(this, i);
if (aarch64_input_section != NULL
&& aarch64_input_section->is_stub_table_owner()
&& !aarch64_input_section->stub_table()->empty())
{
Output_section* os = this->output_section(i);
gold_assert(os != NULL);
relinfo.reloc_shndx = elfcpp::SHN_UNDEF;
relinfo.reloc_shdr = NULL;
relinfo.data_shndx = i;
relinfo.data_shdr = pshdrs + i * elfcpp::Elf_sizes<size>::shdr_size;
typename Sized_relobj_file<size, big_endian>::View_size&
view_struct = (*pviews)[i];
gold_assert(view_struct.view != NULL);
The_stub_table* stub_table = aarch64_input_section->stub_table();
off_t offset = stub_table->address() - view_struct.address;
unsigned char* view = view_struct.view + offset;
AArch64_address address = stub_table->address();
section_size_type view_size = stub_table->data_size();
stub_table->relocate_reloc_stubs(&relinfo, target, os, view, address,
view_size);
}
}
}
// Determine if an input section is scannable for stub processing. SHDR is
// the header of the section and SHNDX is the section index. OS is the output
// section for the input section and SYMTAB is the global symbol table used to
// look up ICF information.
template<int size, bool big_endian>
bool
AArch64_relobj<size, big_endian>::text_section_is_scannable(
const elfcpp::Shdr<size, big_endian>& text_shdr,
unsigned int text_shndx,
const Output_section* os,
const Symbol_table* symtab)
{
// Skip any empty sections, unallocated sections or sections whose
// type are not SHT_PROGBITS.
if (text_shdr.get_sh_size() == 0
|| (text_shdr.get_sh_flags() & elfcpp::SHF_ALLOC) == 0
|| text_shdr.get_sh_type() != elfcpp::SHT_PROGBITS)
return false;
// Skip any discarded or ICF'ed sections.
if (os == NULL || symtab->is_section_folded(this, text_shndx))
return false;
// Skip exception frame.
if (strcmp(os->name(), ".eh_frame") == 0)
return false ;
gold_assert(!this->is_output_section_offset_invalid(text_shndx) ||
os->find_relaxed_input_section(this, text_shndx) != NULL);
return true;
}
// Determine if we want to scan the SHNDX-th section for relocation stubs.
// This is a helper for AArch64_relobj::scan_sections_for_stubs().
template<int size, bool big_endian>
bool
AArch64_relobj<size, big_endian>::section_needs_reloc_stub_scanning(
const elfcpp::Shdr<size, big_endian>& shdr,
const Relobj::Output_sections& out_sections,
const Symbol_table* symtab,
const unsigned char* pshdrs)
{
unsigned int sh_type = shdr.get_sh_type();
if (sh_type != elfcpp::SHT_RELA)
return false;
// Ignore empty section.
off_t sh_size = shdr.get_sh_size();
if (sh_size == 0)
return false;
// Ignore reloc section with unexpected symbol table. The
// error will be reported in the final link.
if (this->adjust_shndx(shdr.get_sh_link()) != this->symtab_shndx())
return false;
gold_assert(sh_type == elfcpp::SHT_RELA);
unsigned int reloc_size = elfcpp::Elf_sizes<size>::rela_size;
// Ignore reloc section with unexpected entsize or uneven size.
// The error will be reported in the final link.
if (reloc_size != shdr.get_sh_entsize() || sh_size % reloc_size != 0)
return false;
// Ignore reloc section with bad info. This error will be
// reported in the final link.
unsigned int text_shndx = this->adjust_shndx(shdr.get_sh_info());
if (text_shndx >= this->shnum())
return false;
const unsigned int shdr_size = elfcpp::Elf_sizes<size>::shdr_size;
const elfcpp::Shdr<size, big_endian> text_shdr(pshdrs +
text_shndx * shdr_size);
return this->text_section_is_scannable(text_shdr, text_shndx,
out_sections[text_shndx], symtab);
}
// Scan section SHNDX for erratum 843419 and 835769.
template<int size, bool big_endian>
void
AArch64_relobj<size, big_endian>::scan_errata(
unsigned int shndx, const elfcpp::Shdr<size, big_endian>& shdr,
Output_section* os, const Symbol_table* symtab,
The_target_aarch64* target)
{
if (shdr.get_sh_size() == 0
|| (shdr.get_sh_flags() &
(elfcpp::SHF_ALLOC | elfcpp::SHF_EXECINSTR)) == 0
|| shdr.get_sh_type() != elfcpp::SHT_PROGBITS)
return;
if (!os || symtab->is_section_folded(this, shndx)) return;
AArch64_address output_offset = this->get_output_section_offset(shndx);
AArch64_address output_address;
if (output_offset != invalid_address)
output_address = os->address() + output_offset;
else
{
const Output_relaxed_input_section* poris =
os->find_relaxed_input_section(this, shndx);
if (!poris) return;
output_address = poris->address();
}
// Update the addresses in previously generated erratum stubs. Unlike when
// we scan relocations for stubs, if section addresses have changed due to
// other relaxations we are unlikely to scan the same erratum instances
// again.
The_stub_table* stub_table = this->stub_table(shndx);
if (stub_table)
{
std::pair<Erratum_stub_set_iter, Erratum_stub_set_iter>
ipair(stub_table->find_erratum_stubs_for_input_section(this, shndx));
for (Erratum_stub_set_iter p = ipair.first; p != ipair.second; ++p)
(*p)->update_erratum_address(output_address);
}
section_size_type input_view_size = 0;
const unsigned char* input_view =
this->section_contents(shndx, &input_view_size, false);
Mapping_symbol_position section_start(shndx, 0);
// Find the first mapping symbol record within section shndx.
typename Mapping_symbol_info::const_iterator p =
this->mapping_symbol_info_.lower_bound(section_start);
while (p != this->mapping_symbol_info_.end() &&
p->first.shndx_ == shndx)
{
typename Mapping_symbol_info::const_iterator prev = p;
++p;
if (prev->second == 'x')
{
section_size_type span_start =
convert_to_section_size_type(prev->first.offset_);
section_size_type span_end;
if (p != this->mapping_symbol_info_.end()
&& p->first.shndx_ == shndx)
span_end = convert_to_section_size_type(p->first.offset_);
else
span_end = convert_to_section_size_type(shdr.get_sh_size());
// Here we do not share the scanning code of both errata. For 843419,
// only the last few insns of each page are examined, which is fast,
// whereas, for 835769, every insn pair needs to be checked.
if (parameters->options().fix_cortex_a53_843419())
target->scan_erratum_843419_span(
this, shndx, span_start, span_end,
const_cast<unsigned char*>(input_view), output_address);
if (parameters->options().fix_cortex_a53_835769())
target->scan_erratum_835769_span(
this, shndx, span_start, span_end,
const_cast<unsigned char*>(input_view), output_address);
}
}
}
// Scan relocations for stub generation.
template<int size, bool big_endian>
void
AArch64_relobj<size, big_endian>::scan_sections_for_stubs(
The_target_aarch64* target,
const Symbol_table* symtab,
const Layout* layout)
{
unsigned int shnum = this->shnum();
const unsigned int shdr_size = elfcpp::Elf_sizes<size>::shdr_size;
// Read the section headers.
const unsigned char* pshdrs = this->get_view(this->elf_file()->shoff(),
shnum * shdr_size,
true, true);
// To speed up processing, we set up hash tables for fast lookup of
// input offsets to output addresses.
this->initialize_input_to_output_maps();
const Relobj::Output_sections& out_sections(this->output_sections());
Relocate_info<size, big_endian> relinfo;
relinfo.symtab = symtab;
relinfo.layout = layout;
relinfo.object = this;
// Do relocation stubs scanning.
const unsigned char* p = pshdrs + shdr_size;
for (unsigned int i = 1; i < shnum; ++i, p += shdr_size)
{
const elfcpp::Shdr<size, big_endian> shdr(p);
if (parameters->options().fix_cortex_a53_843419()
|| parameters->options().fix_cortex_a53_835769())
scan_errata(i, shdr, out_sections[i], symtab, target);
if (this->section_needs_reloc_stub_scanning(shdr, out_sections, symtab,
pshdrs))
{
unsigned int index = this->adjust_shndx(shdr.get_sh_info());
AArch64_address output_offset =
this->get_output_section_offset(index);
AArch64_address output_address;
if (output_offset != invalid_address)
{
output_address = out_sections[index]->address() + output_offset;
}
else
{
// Currently this only happens for a relaxed section.
const Output_relaxed_input_section* poris =
out_sections[index]->find_relaxed_input_section(this, index);
gold_assert(poris != NULL);
output_address = poris->address();
}
// Get the relocations.
const unsigned char* prelocs = this->get_view(shdr.get_sh_offset(),
shdr.get_sh_size(),
true, false);
// Get the section contents.
section_size_type input_view_size = 0;
const unsigned char* input_view =
this->section_contents(index, &input_view_size, false);
relinfo.reloc_shndx = i;
relinfo.data_shndx = index;
unsigned int sh_type = shdr.get_sh_type();
unsigned int reloc_size;
gold_assert (sh_type == elfcpp::SHT_RELA);
reloc_size = elfcpp::Elf_sizes<size>::rela_size;
Output_section* os = out_sections[index];
target->scan_section_for_stubs(&relinfo, sh_type, prelocs,
shdr.get_sh_size() / reloc_size,
os,
output_offset == invalid_address,
input_view, output_address,
input_view_size);
}
}
}
// A class to wrap an ordinary input section containing executable code.
template<int size, bool big_endian>
class AArch64_input_section : public Output_relaxed_input_section
{
public:
typedef Stub_table<size, big_endian> The_stub_table;
AArch64_input_section(Relobj* relobj, unsigned int shndx)
: Output_relaxed_input_section(relobj, shndx, 1),
stub_table_(NULL),
original_contents_(NULL), original_size_(0),
original_addralign_(1)
{ }
~AArch64_input_section()
{ delete[] this->original_contents_; }
// Initialize.
void
init();
// Set the stub_table.
void
set_stub_table(The_stub_table* st)
{ this->stub_table_ = st; }
// Whether this is a stub table owner.
bool
is_stub_table_owner() const
{ return this->stub_table_ != NULL && this->stub_table_->owner() == this; }
// Return the original size of the section.
uint32_t
original_size() const
{ return this->original_size_; }
// Return the stub table.
The_stub_table*
stub_table()
{ return stub_table_; }
protected:
// Write out this input section.
void
do_write(Output_file*);
// Return required alignment of this.
uint64_t
do_addralign() const
{
if (this->is_stub_table_owner())
return std::max(this->stub_table_->addralign(),
static_cast<uint64_t>(this->original_addralign_));
else
return this->original_addralign_;
}
// Finalize data size.
void
set_final_data_size();
// Reset address and file offset.
void
do_reset_address_and_file_offset();
// Output offset.
bool
do_output_offset(const Relobj* object, unsigned int shndx,
section_offset_type offset,
section_offset_type* poutput) const
{
if ((object == this->relobj())
&& (shndx == this->shndx())
&& (offset >= 0)
&& (offset <=
convert_types<section_offset_type, uint32_t>(this->original_size_)))
{
*poutput = offset;
return true;
}
else
return false;
}
private:
// Copying is not allowed.
AArch64_input_section(const AArch64_input_section&);
AArch64_input_section& operator=(const AArch64_input_section&);
// The relocation stubs.
The_stub_table* stub_table_;
// Original section contents. We have to make a copy here since the file
// containing the original section may not be locked when we need to access
// the contents.
unsigned char* original_contents_;
// Section size of the original input section.
uint32_t original_size_;
// Address alignment of the original input section.
uint32_t original_addralign_;
}; // End of AArch64_input_section
// Finalize data size.
template<int size, bool big_endian>
void
AArch64_input_section<size, big_endian>::set_final_data_size()
{
off_t off = convert_types<off_t, uint64_t>(this->original_size_);
if (this->is_stub_table_owner())
{
this->stub_table_->finalize_data_size();
off = align_address(off, this->stub_table_->addralign());
off += this->stub_table_->data_size();
}
this->set_data_size(off);
}
// Reset address and file offset.
template<int size, bool big_endian>
void
AArch64_input_section<size, big_endian>::do_reset_address_and_file_offset()
{
// Size of the original input section contents.
off_t off = convert_types<off_t, uint64_t>(this->original_size_);
// If this is a stub table owner, account for the stub table size.
if (this->is_stub_table_owner())
{
The_stub_table* stub_table = this->stub_table_;
// Reset the stub table's address and file offset. The
// current data size for child will be updated after that.
stub_table_->reset_address_and_file_offset();
off = align_address(off, stub_table_->addralign());
off += stub_table->current_data_size();
}
this->set_current_data_size(off);
}
// Initialize an Arm_input_section.
template<int size, bool big_endian>
void
AArch64_input_section<size, big_endian>::init()
{
Relobj* relobj = this->relobj();
unsigned int shndx = this->shndx();
// We have to cache original size, alignment and contents to avoid locking
// the original file.
this->original_addralign_ =
convert_types<uint32_t, uint64_t>(relobj->section_addralign(shndx));
// This is not efficient but we expect only a small number of relaxed
// input sections for stubs.
section_size_type section_size;
const unsigned char* section_contents =
relobj->section_contents(shndx, &section_size, false);
this->original_size_ =
convert_types<uint32_t, uint64_t>(relobj->section_size(shndx));
gold_assert(this->original_contents_ == NULL);
this->original_contents_ = new unsigned char[section_size];
memcpy(this->original_contents_, section_contents, section_size);
// We want to make this look like the original input section after
// output sections are finalized.
Output_section* os = relobj->output_section(shndx);
off_t offset = relobj->output_section_offset(shndx);
gold_assert(os != NULL && !relobj->is_output_section_offset_invalid(shndx));
this->set_address(os->address() + offset);
this->set_file_offset(os->offset() + offset);
this->set_current_data_size(this->original_size_);
this->finalize_data_size();
}
// Write data to output file.
template<int size, bool big_endian>
void
AArch64_input_section<size, big_endian>::do_write(Output_file* of)
{
// We have to write out the original section content.
gold_assert(this->original_contents_ != NULL);
of->write(this->offset(), this->original_contents_,
this->original_size_);
// If this owns a stub table and it is not empty, write it.
if (this->is_stub_table_owner() && !this->stub_table_->empty())
this->stub_table_->write(of);
}
// Arm output section class. This is defined mainly to add a number of stub
// generation methods.
template<int size, bool big_endian>
class AArch64_output_section : public Output_section
{
public:
typedef Target_aarch64<size, big_endian> The_target_aarch64;
typedef AArch64_relobj<size, big_endian> The_aarch64_relobj;
typedef Stub_table<size, big_endian> The_stub_table;
typedef AArch64_input_section<size, big_endian> The_aarch64_input_section;
public:
AArch64_output_section(const char* name, elfcpp::Elf_Word type,
elfcpp::Elf_Xword flags)
: Output_section(name, type, flags)
{ }
~AArch64_output_section() {}
// Group input sections for stub generation.
void
group_sections(section_size_type, bool, Target_aarch64<size, big_endian>*,
const Task*);
private:
typedef Output_section::Input_section Input_section;
typedef Output_section::Input_section_list Input_section_list;
// Create a stub group.
void
create_stub_group(Input_section_list::const_iterator,
Input_section_list::const_iterator,
Input_section_list::const_iterator,
The_target_aarch64*,
std::vector<Output_relaxed_input_section*>&,
const Task*);
}; // End of AArch64_output_section
// Create a stub group for input sections from FIRST to LAST. OWNER points to
// the input section that will be the owner of the stub table.
template<int size, bool big_endian> void
AArch64_output_section<size, big_endian>::create_stub_group(
Input_section_list::const_iterator first,
Input_section_list::const_iterator last,
Input_section_list::const_iterator owner,
The_target_aarch64* target,
std::vector<Output_relaxed_input_section*>& new_relaxed_sections,
const Task* task)
{
// Currently we convert ordinary input sections into relaxed sections only
// at this point.
The_aarch64_input_section* input_section;
if (owner->is_relaxed_input_section())
gold_unreachable();
else
{
gold_assert(owner->is_input_section());
// Create a new relaxed input section. We need to lock the original
// file.
Task_lock_obj<Object> tl(task, owner->relobj());
input_section =
target->new_aarch64_input_section(owner->relobj(), owner->shndx());
new_relaxed_sections.push_back(input_section);
}
// Create a stub table.
The_stub_table* stub_table =
target->new_stub_table(input_section);
input_section->set_stub_table(stub_table);
Input_section_list::const_iterator p = first;
// Look for input sections or relaxed input sections in [first ... last].
do
{
if (p->is_input_section() || p->is_relaxed_input_section())
{
// The stub table information for input sections live
// in their objects.
The_aarch64_relobj* aarch64_relobj =
static_cast<The_aarch64_relobj*>(p->relobj());
aarch64_relobj->set_stub_table(p->shndx(), stub_table);
}
}
while (p++ != last);
}
// Group input sections for stub generation. GROUP_SIZE is roughly the limit of
// stub groups. We grow a stub group by adding input section until the size is
// just below GROUP_SIZE. The last input section will be converted into a stub
// table owner. If STUB_ALWAYS_AFTER_BRANCH is false, we also add input sectiond
// after the stub table, effectively doubling the group size.
//
// This is similar to the group_sections() function in elf32-arm.c but is
// implemented differently.
template<int size, bool big_endian>
void AArch64_output_section<size, big_endian>::group_sections(
section_size_type group_size,
bool stubs_always_after_branch,
Target_aarch64<size, big_endian>* target,
const Task* task)
{
typedef enum
{
NO_GROUP,
FINDING_STUB_SECTION,
HAS_STUB_SECTION
} State;
std::vector<Output_relaxed_input_section*> new_relaxed_sections;
State state = NO_GROUP;
section_size_type off = 0;
section_size_type group_begin_offset = 0;
section_size_type group_end_offset = 0;
section_size_type stub_table_end_offset = 0;
Input_section_list::const_iterator group_begin =
this->input_sections().end();
Input_section_list::const_iterator stub_table =
this->input_sections().end();
Input_section_list::const_iterator group_end = this->input_sections().end();
for (Input_section_list::const_iterator p = this->input_sections().begin();
p != this->input_sections().end();
++p)
{
section_size_type section_begin_offset =
align_address(off, p->addralign());
section_size_type section_end_offset =
section_begin_offset + p->data_size();
// Check to see if we should group the previously seen sections.
switch (state)
{
case NO_GROUP:
break;
case FINDING_STUB_SECTION:
// Adding this section makes the group larger than GROUP_SIZE.
if (section_end_offset - group_begin_offset >= group_size)
{
if (stubs_always_after_branch)
{
gold_assert(group_end != this->input_sections().end());
this->create_stub_group(group_begin, group_end, group_end,
target, new_relaxed_sections,
task);
state = NO_GROUP;
}
else
{
// Input sections up to stub_group_size bytes after the stub
// table can be handled by it too.
state = HAS_STUB_SECTION;
stub_table = group_end;
stub_table_end_offset = group_end_offset;
}
}
break;
case HAS_STUB_SECTION:
// Adding this section makes the post stub-section group larger
// than GROUP_SIZE.
gold_unreachable();
// NOT SUPPORTED YET. For completeness only.
if (section_end_offset - stub_table_end_offset >= group_size)
{
gold_assert(group_end != this->input_sections().end());
this->create_stub_group(group_begin, group_end, stub_table,
target, new_relaxed_sections, task);
state = NO_GROUP;
}
break;
default:
gold_unreachable();
}
// If we see an input section and currently there is no group, start
// a new one. Skip any empty sections. We look at the data size
// instead of calling p->relobj()->section_size() to avoid locking.
if ((p->is_input_section() || p->is_relaxed_input_section())
&& (p->data_size() != 0))
{
if (state == NO_GROUP)
{
state = FINDING_STUB_SECTION;
group_begin = p;
group_begin_offset = section_begin_offset;
}
// Keep track of the last input section seen.
group_end = p;
group_end_offset = section_end_offset;
}
off = section_end_offset;
}
// Create a stub group for any ungrouped sections.
if (state == FINDING_STUB_SECTION || state == HAS_STUB_SECTION)
{
gold_assert(group_end != this->input_sections().end());
this->create_stub_group(group_begin, group_end,
(state == FINDING_STUB_SECTION
? group_end
: stub_table),
target, new_relaxed_sections, task);
}
if (!new_relaxed_sections.empty())
this->convert_input_sections_to_relaxed_sections(new_relaxed_sections);
// Update the section offsets
for (size_t i = 0; i < new_relaxed_sections.size(); ++i)
{
The_aarch64_relobj* relobj = static_cast<The_aarch64_relobj*>(
new_relaxed_sections[i]->relobj());
unsigned int shndx = new_relaxed_sections[i]->shndx();
// Tell AArch64_relobj that this input section is converted.
relobj->convert_input_section_to_relaxed_section(shndx);
}
} // End of AArch64_output_section::group_sections
AArch64_reloc_property_table* aarch64_reloc_property_table = NULL;
// The aarch64 target class.
// See the ABI at
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0056b/IHI0056B_aaelf64.pdf
template<int size, bool big_endian>
class Target_aarch64 : public Sized_target<size, big_endian>
{
public:
typedef Target_aarch64<size, big_endian> This;
typedef Output_data_reloc<elfcpp::SHT_RELA, true, size, big_endian>
Reloc_section;
typedef Relocate_info<size, big_endian> The_relocate_info;
typedef typename elfcpp::Elf_types<size>::Elf_Addr Address;
typedef AArch64_relobj<size, big_endian> The_aarch64_relobj;
typedef Reloc_stub<size, big_endian> The_reloc_stub;
typedef Erratum_stub<size, big_endian> The_erratum_stub;
typedef typename Reloc_stub<size, big_endian>::Key The_reloc_stub_key;
typedef Stub_table<size, big_endian> The_stub_table;
typedef std::vector<The_stub_table*> Stub_table_list;
typedef typename Stub_table_list::iterator Stub_table_iterator;
typedef AArch64_input_section<size, big_endian> The_aarch64_input_section;
typedef AArch64_output_section<size, big_endian> The_aarch64_output_section;
typedef Unordered_map<Section_id,
AArch64_input_section<size, big_endian>*,
Section_id_hash> AArch64_input_section_map;
typedef AArch64_insn_utilities<big_endian> Insn_utilities;
const static int TCB_SIZE = size / 8 * 2;
static const Address invalid_address = static_cast<Address>(-1);
Target_aarch64(const Target::Target_info* info = &aarch64_info)
: Sized_target<size, big_endian>(info),
got_(NULL), plt_(NULL), got_plt_(NULL), got_irelative_(NULL),
got_tlsdesc_(NULL), global_offset_table_(NULL), rela_dyn_(NULL),
rela_irelative_(NULL), copy_relocs_(elfcpp::R_AARCH64_COPY),
got_mod_index_offset_(-1U),
tlsdesc_reloc_info_(), tls_base_symbol_defined_(false),
stub_tables_(), stub_group_size_(0), aarch64_input_section_map_()
{ }
// Scan the relocations to determine unreferenced sections for
// garbage collection.
void
gc_process_relocs(Symbol_table* symtab,
Layout* layout,
Sized_relobj_file<size, big_endian>* object,
unsigned int data_shndx,
unsigned int sh_type,
const unsigned char* prelocs,
size_t reloc_count,
Output_section* output_section,
bool needs_special_offset_handling,
size_t local_symbol_count,
const unsigned char* plocal_symbols);
// Scan the relocations to look for symbol adjustments.
void
scan_relocs(Symbol_table* symtab,
Layout* layout,
Sized_relobj_file<size, big_endian>* object,
unsigned int data_shndx,
unsigned int sh_type,
const unsigned char* prelocs,
size_t reloc_count,
Output_section* output_section,
bool needs_special_offset_handling,
size_t local_symbol_count,
const unsigned char* plocal_symbols);
// Finalize the sections.
void
do_finalize_sections(Layout*, const Input_objects*, Symbol_table*);
// Return the value to use for a dynamic which requires special
// treatment.
uint64_t
do_dynsym_value(const Symbol*) const;
// Relocate a section.
void
relocate_section(const Relocate_info<size, big_endian>*,
unsigned int sh_type,
const unsigned char* prelocs,
size_t reloc_count,
Output_section* output_section,
bool needs_special_offset_handling,
unsigned char* view,
typename elfcpp::Elf_types<size>::Elf_Addr view_address,
section_size_type view_size,
const Reloc_symbol_changes*);
// Scan the relocs during a relocatable link.
void
scan_relocatable_relocs(Symbol_table* symtab,
Layout* layout,
Sized_relobj_file<size, big_endian>* object,
unsigned int data_shndx,
unsigned int sh_type,
const unsigned char* prelocs,
size_t reloc_count,
Output_section* output_section,
bool needs_special_offset_handling,
size_t local_symbol_count,
const unsigned char* plocal_symbols,
Relocatable_relocs*);
// Scan the relocs for --emit-relocs.
void
emit_relocs_scan(Symbol_table* symtab,
Layout* layout,
Sized_relobj_file<size, big_endian>* object,
unsigned int data_shndx,
unsigned int sh_type,
const unsigned char* prelocs,
size_t reloc_count,
Output_section* output_section,
bool needs_special_offset_handling,
size_t local_symbol_count,
const unsigned char* plocal_syms,
Relocatable_relocs* rr);
// Relocate a section during a relocatable link.
void
relocate_relocs(
const Relocate_info<size, big_endian>*,
unsigned int sh_type,
const unsigned char* prelocs,