diff --git a/elf/elf.h b/elf/elf.h index 3c014dda11..53531fa693 100644 --- a/elf/elf.h +++ b/elf/elf.h @@ -291,6 +291,7 @@ enum : u32 { DT_VERNEEDNUM = 0x6fffffff, DT_PPC_GOT = 0x70000000, DT_PPC64_GLINK = 0x70000000, + DT_AARCH64_VARIANT_PCS = 0x70000005, DT_AUXILIARY = 0x7ffffffd, DT_FILTER = 0x7fffffff, }; @@ -1708,6 +1709,35 @@ struct ElfNhdr { // Target-specific ELF data types // +template <> +struct ElfSym { + bool is_undef() const { return st_shndx == SHN_UNDEF; } + bool is_abs() const { return st_shndx == SHN_ABS; } + bool is_common() const { return st_shndx == SHN_COMMON; } + bool is_weak() const { return st_bind == STB_WEAK; } + bool is_undef_weak() const { return is_undef() && is_weak(); } + + ul32 st_name; + +#ifdef __LITTLE_ENDIAN__ + u8 st_type : 4; + u8 st_bind : 4; + u8 st_visibility : 2; + u8 : 5; + u8 arm64_variant_pcs : 1; // ARM64-specific +#else + u8 st_bind : 4; + u8 st_type : 4; + u8 arm64_variant_pcs : 1; + u8 : 6; + u8 st_visibility : 2; +#endif + + ul16 st_shndx; + ul64 st_value; + ul64 st_size; +}; + template <> struct ElfSym { bool is_undef() const { return st_shndx == SHN_UNDEF; } diff --git a/elf/output-chunks.cc b/elf/output-chunks.cc index b88ed7a25a..ec00bf2e43 100644 --- a/elf/output-chunks.cc +++ b/elf/output-chunks.cc @@ -632,6 +632,27 @@ void SymtabSection::copy_buf(Context &ctx) { }); } +// An ARM64 function with a non-standard calling convention is marked with +// STO_AARCH64_VARIANT_PCS bit in the symbol table. +// +// A function with that bit is not safe to be called through a lazy PLT +// stub because the PLT resolver may clobber registers that should be +// preserved in a non-standard calling convention. +// +// To solve the problem, the dynamic linker scans the dynamic symbol table +// at process startup time and resolve symbols with STO_AARCH64_VARIANT_PCS +// bit eagerly, so that the PLT resolver won't be called for that symbol +// lazily. As an optimization, it does so only when DT_AARCH64_VARIANT_PCS +// is set in the dynamic section. +// +// This function returns true if DT_AARCH64_VARIANT_PCS needs to be set. +static bool contains_variant_pcs(Context &ctx) { + for (Symbol *sym : ctx.plt->symbols) + if (sym->esym().arm64_variant_pcs) + return true; + return false; +} + template static std::vector> create_dynamic_section(Context &ctx) { std::vector> vec; @@ -781,6 +802,10 @@ static std::vector> create_dynamic_section(Context &ctx) { if (flags1) define(DT_FLAGS_1, flags1); + if constexpr (is_arm64) + if (contains_variant_pcs(ctx)) + define(DT_AARCH64_VARIANT_PCS, 1); + if constexpr (is_ppc32) define(DT_PPC_GOT, ctx.gotplt->shdr.sh_addr); @@ -1561,6 +1586,9 @@ ElfSym to_output_esym(Context &ctx, Symbol &sym, u32 st_name, else esym.st_bind = sym.esym().st_bind; + if constexpr (is_arm64) + esym.arm64_variant_pcs = sym.esym().arm64_variant_pcs; + if constexpr (is_ppc64v2) esym.ppc_local_entry = sym.esym().ppc_local_entry; diff --git a/test/elf/aarch64_variant-pcs.sh b/test/elf/aarch64_variant-pcs.sh new file mode 100755 index 0000000000..1716e7b0c5 --- /dev/null +++ b/test/elf/aarch64_variant-pcs.sh @@ -0,0 +1,23 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +[ $MACHINE = aarch64 ] || skip + +cat <