[lld][AArch64][Build Attributes] Add support for AArch64 Build Attributes (#147970)

This patch enables lld to read AArch64 Build Attributes and convert them
into GNU Properties.

Changes:
    - Parses AArch64 Build Attributes from input object files.
    - Converts known attributes into corresponding GNU Properties.
    - Merges attributes when linking multiple objects.

Spec reference:
    https://github.com/ARM-software/abi-aa/pull/230/files#r1030

Co-authored-by: Sivan Shani <sivan.shani@arm.com>

---------

Co-authored-by: Sivan Shani <sivan.shani@arm.com>
This commit is contained in:
Mark Murray 2025-07-24 10:38:36 +01:00 committed by GitHub
parent a073cbbb1a
commit d52675e0a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 310 additions and 23 deletions

View File

@ -21,6 +21,7 @@
#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLExtras.h"
#include "llvm/LTO/LTO.h" #include "llvm/LTO/LTO.h"
#include "llvm/Object/IRObjectFile.h" #include "llvm/Object/IRObjectFile.h"
#include "llvm/Support/AArch64AttributeParser.h"
#include "llvm/Support/ARMAttributeParser.h" #include "llvm/Support/ARMAttributeParser.h"
#include "llvm/Support/ARMBuildAttributes.h" #include "llvm/Support/ARMBuildAttributes.h"
#include "llvm/Support/Endian.h" #include "llvm/Support/Endian.h"
@ -537,6 +538,41 @@ uint32_t ObjFile<ELFT>::getSectionIndex(const Elf_Sym &sym) const {
this); this);
} }
template <class ELFT>
static void
handleAArch64BAAndGnuProperties(ObjFile<ELFT> *file, Ctx &ctx,
const AArch64BuildAttrSubsections &baInfo) {
if (file->aarch64PauthAbiCoreInfo) {
// Check for data mismatch.
if (file->aarch64PauthAbiCoreInfo) {
if (baInfo.Pauth.TagPlatform != file->aarch64PauthAbiCoreInfo->platform ||
baInfo.Pauth.TagSchema != file->aarch64PauthAbiCoreInfo->version)
Err(ctx) << file
<< " GNU properties and build attributes have conflicting "
"AArch64 PAuth data";
}
if (baInfo.AndFeatures != file->andFeatures)
Err(ctx) << file
<< " GNU properties and build attributes have conflicting "
"AArch64 PAuth data";
} else {
// When BuildAttributes are missing, PauthABI value defaults to (TagPlatform
// = 0, TagSchema = 0). GNU properties do not write PAuthAbiCoreInfo if GNU
// property is not present. To match this behaviour, we only write
// PAuthAbiCoreInfo when there is at least one non-zero value. The
// specification reserves TagPlatform = 0, TagSchema = 1 values to match the
// 'Invalid' GNU property section with platform = 0, version = 0.
if (baInfo.Pauth.TagPlatform || baInfo.Pauth.TagSchema) {
if (baInfo.Pauth.TagPlatform == 0 && baInfo.Pauth.TagSchema == 1)
file->aarch64PauthAbiCoreInfo = {0, 0};
else
file->aarch64PauthAbiCoreInfo = {baInfo.Pauth.TagPlatform,
baInfo.Pauth.TagSchema};
}
file->andFeatures = baInfo.AndFeatures;
}
}
template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) { template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
object::ELFFile<ELFT> obj = this->getObj(); object::ELFFile<ELFT> obj = this->getObj();
// Read a section table. justSymbols is usually false. // Read a section table. justSymbols is usually false.
@ -554,6 +590,7 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
sections.resize(size); sections.resize(size);
for (size_t i = 0; i != size; ++i) { for (size_t i = 0; i != size; ++i) {
const Elf_Shdr &sec = objSections[i]; const Elf_Shdr &sec = objSections[i];
if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS)) if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS))
continue; continue;
if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) { if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) {
@ -637,13 +674,6 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
} }
break; break;
case EM_AARCH64: case EM_AARCH64:
// FIXME: BuildAttributes have been implemented in llvm, but not yet in
// lld. Remove the section so that it does not accumulate in the output
// file. When support is implemented we expect not to output a build
// attributes section in files of type ET_EXEC or ET_SHARED, but ld -r
// ouptut will need a single merged attributes section.
if (sec.sh_type == SHT_AARCH64_ATTRIBUTES)
sections[i] = &InputSection::discarded;
// Producing a static binary with MTE globals is not currently supported, // Producing a static binary with MTE globals is not currently supported,
// remove all SHT_AARCH64_MEMTAG_GLOBALS_STATIC sections as they're unused // remove all SHT_AARCH64_MEMTAG_GLOBALS_STATIC sections as they're unused
// medatada, and we don't want them to end up in the output file for // medatada, and we don't want them to end up in the output file for
@ -744,6 +774,8 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
StringRef shstrtab = CHECK2(obj.getSectionStringTable(objSections), this); StringRef shstrtab = CHECK2(obj.getSectionStringTable(objSections), this);
uint64_t size = objSections.size(); uint64_t size = objSections.size();
SmallVector<ArrayRef<Elf_Word>, 0> selectedGroups; SmallVector<ArrayRef<Elf_Word>, 0> selectedGroups;
AArch64BuildAttrSubsections aarch64BAsubSections;
bool hasAArch64BuildAttributes = false;
for (size_t i = 0; i != size; ++i) { for (size_t i = 0; i != size; ++i) {
if (this->sections[i] == &InputSection::discarded) if (this->sections[i] == &InputSection::discarded)
continue; continue;
@ -775,6 +807,26 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
continue; continue;
} }
// Processor-specific types that do not use the following switch statement.
//
// Extract Build Attributes section contents into aarch64BAsubSections.
// Input objects may contain both build Build Attributes and GNU
// properties. We delay processing Build Attributes until we have finished
// reading all sections so that we can check that these are consistent.
if (type == SHT_AARCH64_ATTRIBUTES && ctx.arg.emachine == EM_AARCH64) {
ArrayRef<uint8_t> contents = check(obj.getSectionContents(sec));
AArch64AttributeParser attributes;
if (Error e = attributes.parse(contents, ELFT::Endianness)) {
StringRef name = check(obj.getSectionName(sec, shstrtab));
InputSection isec(*this, sec, name);
Warn(ctx) << &isec << ": " << std::move(e);
} else {
aarch64BAsubSections = extractBuildAttributesSubsections(attributes);
hasAArch64BuildAttributes = true;
}
this->sections[i] = &InputSection::discarded;
continue;
}
switch (type) { switch (type) {
case SHT_GROUP: { case SHT_GROUP: {
if (!ctx.arg.relocatable) if (!ctx.arg.relocatable)
@ -912,6 +964,12 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
<< linkSec; << linkSec;
} }
// Handle AArch64 Build Attributes and GNU properties:
// - Err on mismatched values.
// - Store missing values as GNU properties.
if (hasAArch64BuildAttributes)
handleAArch64BAAndGnuProperties<ELFT>(this, ctx, aarch64BAsubSections);
for (ArrayRef<Elf_Word> entries : selectedGroups) for (ArrayRef<Elf_Word> entries : selectedGroups)
handleSectionGroup<ELFT>(this->sections, entries); handleSectionGroup<ELFT>(this->sections, entries);
} }

View File

@ -0,0 +1,29 @@
// REQUIRES: aarch64
// RUN: llvm-mc -triple=aarch64_be %s -filetype=obj -o %t.o
// RUN: ld.lld %t.o --shared -o %t.so
// RUN: llvm-readelf -n %t.so | FileCheck %s --check-prefix=NOTE
// RUN: llvm-mc -triple=aarch64_be %s -filetype=obj -o %t.o
// RUN: ld.lld %t.o --shared -o %t.so
// RUN: llvm-readelf -n %t.so | FileCheck %s --check-prefix=NOTE
// RUN: ld.lld %t.o -o %t
// RUN: llvm-readelf -n %t.so | FileCheck %s --check-prefix=NOTE
// RUN: ld.lld -r %t.o -o %t2.o
// RUN: llvm-readelf -n %t.so | FileCheck %s --check-prefix=NOTE
/// Test that lld can read big-endian build-attributes.
// NOTE: Displaying notes found in: .note.gnu.property
// NOTE-NEXT: Owner Data size Description
// NOTE-NEXT: GNU 0x00000028 NT_GNU_PROPERTY_TYPE_0 (property note)
// NOTE-NEXT: Properties: aarch64 feature: BTI, PAC, GCS
// NOTE-NEXT: AArch64 PAuth ABI core info: platform 0x89abcdef (unknown), version 0x89abcdef
.aeabi_subsection aeabi_pauthabi, required, uleb128
.aeabi_attribute Tag_PAuth_Platform, 0x123456789ABCDEF
.aeabi_attribute Tag_PAuth_Schema, 0x123456789ABCDEF
.aeabi_subsection aeabi_feature_and_bits, optional, uleb128
.aeabi_attribute Tag_Feature_BTI, 1
.aeabi_attribute Tag_Feature_PAC, 1
.aeabi_attribute Tag_Feature_GCS, 1

View File

@ -0,0 +1,35 @@
// REQUIRES: aarch64
// RUN: llvm-mc -triple=aarch64 %s -filetype=obj -o %t.o
// RUN: not ld.lld %t.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR
// ERR: GNU properties and build attributes have conflicting AArch64 PAuth data
// ERR-NEXT: GNU properties and build attributes have conflicting AArch64 PAuth data
.aeabi_subsection aeabi_pauthabi, required, uleb128
.aeabi_attribute Tag_PAuth_Platform, 5
.aeabi_attribute Tag_PAuth_Schema, 5
.aeabi_subsection aeabi_feature_and_bits, optional, uleb128
.aeabi_attribute Tag_Feature_BTI, 1
.aeabi_attribute Tag_Feature_PAC, 1
.aeabi_attribute Tag_Feature_GCS, 1
.section ".note.gnu.property", "a"
.long 0x4
.long 0x10
.long 0x5
.asciz "GNU"
.long 0xc0000000 // GNU_PROPERTY_AARCH64_FEATURE_1_AND
.long 0x4
.long 0x2 // GNU_PROPERTY_AARCH64_FEATURE_1_PAC
.long 0x0
.section ".note.gnu.property", "a"
.long 0x4
.long 0x18
.long 0x5
.asciz "GNU"
.long 0xc0000001
.long 0x10
.quad 0x12345678 // platform
.quad 0x87654321 // version

View File

@ -0,0 +1,18 @@
// REQUIRES: aarch64
// RUN: llvm-mc -triple=aarch64 -filetype=obj %s -o %t.o
// RUN: ld.lld -r %t.o -o %t.invalid.o
// RUN: llvm-readelf -n %t.invalid.o | FileCheck %s
/// According to the BuildAttributes specification Build Attributes
/// A (TagPlatform, TagSchema)of (0, 1) maps to an explicit PAuth property
/// of platform = 0, version = 0 ('Invalid').
// CHECK: Displaying notes found in: .note.gnu.property
// CHECK-NEXT: Owner Data size Description
// CHECK-NEXT: GNU 0x00000018 NT_GNU_PROPERTY_TYPE_0 (property note)
// CHECK-NEXT: Properties: AArch64 PAuth ABI core info: platform 0x0 (invalid), version 0x0
.aeabi_subsection aeabi_pauthabi, required, uleb128
.aeabi_attribute Tag_PAuth_Platform, 0
.aeabi_attribute Tag_PAuth_Schema, 1

View File

@ -0,0 +1,18 @@
# REQUIRES: aarch64
# RUN: llvm-mc -triple=aarch64 -filetype=obj %s -o %t.o
# RUN: ld.lld %t.o /dev/null 2>&1 | FileCheck %s
# CHECK: (.ARM.attributes): unexpected end of data at offset 0x3f while reading [0x3d, 0x41)
.section .ARM.attributes,"",%0x70000003
.byte 0x41 // Tag 'A' (format version)
.long 0x00000019 // Subsection length
.asciz "aeabi_pauthabi" // Subsection name
.byte 0x00, 0x00 // Optionality and Type
.byte 0x01, 0x01, 0x02, 0x01 // PAuth_Platform and PAuth_Schema
.long 0x00000023 // Subsection length
.asciz "aeabi_feature_and_bits" // Subsection name
.byte 0x01, 0x00 // Optionality and Type
.byte 0x00, 0x01, 0x01, 0x01, 0x02, 0x01 // BTI, PAC, GCS
.byte 0x00, 0x00 // This is the malformation, data is too long.

View File

@ -0,0 +1,67 @@
// REQUIRES: aarch64
// RUN: rm -rf %t && split-file %s %t && cd %t
// RUN: llvm-mc -triple=aarch64 -filetype=obj %s -o %t11.o
// RUN: llvm-mc -triple=aarch64 -filetype=obj merged-property.s -o %t12.o
// RUN: llvm-mc -triple=aarch64 -filetype=obj merged-property2.s -o %t13.o
// RUN: ld.lld -r %t11.o %t12.o %t13.o -o %t.merged1.o
// RUN: llvm-readelf -n %t.merged1.o | FileCheck %s --check-prefix=NOTE-MIXED
/// This test verifies merging of AArch64 build attributes and GNU property notes.
/// Three object files are combined: one with build attributes (PAuth information, BTI, PAC, GCS),
/// and two with GNU property notes encoding the same feature bits.
/// PAuth ABI info is provided in one of the files and it is expected to be preserved in the merged output.
// NOTE-MIXED: Displaying notes found in: .note.gnu.property
// NOTE-MIXED-NEXT: Owner Data size Description
// NOTE-MIXED-NEXT: GNU 0x00000028 NT_GNU_PROPERTY_TYPE_0 (property note)
// NOTE-MIXED-NEXT: Properties: aarch64 feature: BTI, PAC
// NOTE-MIXED-NEXT: AArch64 PAuth ABI core info: platform 0x31 (unknown), version 0x13
// CHECK: .note.gnu.property
// CHECK-NOT: .ARM.attributes
.aeabi_subsection aeabi_pauthabi, required, uleb128
.aeabi_attribute Tag_PAuth_Platform, 49
.aeabi_attribute Tag_PAuth_Schema, 19
.aeabi_subsection aeabi_feature_and_bits, optional, uleb128
.aeabi_attribute Tag_Feature_BTI, 1
.aeabi_attribute Tag_Feature_PAC, 1
.aeabi_attribute Tag_Feature_GCS, 1
//--- merged-property.s
.section ".note.gnu.property", "a"
.long 0x4 // Name length is always 4 ("GNU")
.long end - begin // Data length
.long 0x5 // Type: NT_GNU_PROPERTY_TYPE_0
.asciz "GNU" // Name
.p2align 0x3
begin:
.long 0xc0000000 // GNU_PROPERTY_AARCH64_FEATURE_1_AND
.long 0x4
.long 0x7 // pr_data: BTI (1), PAC (2), GCS (4) = 0b111 = 7
.long 0x0
// PAuth ABI property note
.long 0xc0000001 // GNU_PROPERTY_AARCH64_FEATURE_PAUTH
.long 0x10 // Data length
.quad 0x31 // PAuth ABI platform
.quad 0x13 // PAuth ABI version
.p2align 0x3 // Align to 8 byte for 64 bit
end:
//--- merged-property2.s
.section .note.gnu.property, "a"
.align 0x4
.long 0x4 // Name length is always 4 ("GNU")
.long end2 - begin2 // Data length
.long 0x5 // Type: NT_GNU_PROPERTY_TYPE_0
.asciz "GNU" // Name
begin2:
.align 0x4
.long 0xc0000000 // Type: GNU_PROPERTY_AARCH64_FEATURE_1_AND
.long 0x4 // Data length
.long 0x7 // pr_data: BTI (1), PAC (2), GCS (4) = 0b111 = 7
.long 0x0
end2:

View File

@ -1,26 +1,50 @@
// REQUIRES: aarch64 // REQUIRES: aarch64
// RUN: llvm-mc -triple=aarch64 %s -filetype=obj -o %t.o // RUN: rm -rf %t && split-file %s %t && cd %t
// RUN: ld.lld %t.o --shared -o %t.so
// RUN: llvm-readelf --sections %t.so | FileCheck %s
// RUN: ld.lld %t.o -o %t
// RUN: llvm-readelf --sections %t | FileCheck %s
// RUN: ld.lld -r %t.o -o %t2.o
// RUN: llvm-readelf --sections %t2.o | FileCheck %s
/// File has a Build attributes section. This should not appear in // RUN: llvm-mc -triple=aarch64 -filetype=obj %s -o %t1.o
/// ET_EXEC or ET_SHARED files as there is no requirement for it to // RUN: llvm-mc -triple=aarch64 -filetype=obj pauth-bti-gcs.s -o %t2.o
/// do so. FIXME, the ld -r (relocatable link) should output a single // RUN: llvm-mc -triple=aarch64 -filetype=obj pauth-bti-pac.s -o %t3.o
/// merged build attributes section. When full support is added in // RUN: ld.lld -r %t1.o %t2.o %t3.o -o %t.merged.o
/// ld.lld this test should be updated. // RUN: llvm-readelf -n %t.merged.o | FileCheck %s --check-prefix=NOTE
/// This test merges three object files with AArch64 build attributes.
/// All contain identical PAuth ABI info (platform/version), which must be preserved.
/// Only BTI is common across all three in the AND feature set, so the merged output
/// must show BTI only. PAC and GCS are present in subsets and should not appear.
// NOTE: Displaying notes found in: .note.gnu.property
// NOTE-NEXT: Owner Data size Description
// NOTE-NEXT: GNU 0x00000028 NT_GNU_PROPERTY_TYPE_0 (property note)
// NOTE-NEXT: Properties: aarch64 feature: BTI
// NOTE-NEXT: AArch64 PAuth ABI core info: platform 0x31 (unknown), version 0x13
// CHECK: .note.gnu.property
// CHECK-NOT: .ARM.attributes // CHECK-NOT: .ARM.attributes
.aeabi_subsection aeabi_pauthabi, required, uleb128
.aeabi_attribute Tag_PAuth_Platform, 49
.aeabi_attribute Tag_PAuth_Schema, 19
.aeabi_subsection aeabi_feature_and_bits, optional, uleb128 .aeabi_subsection aeabi_feature_and_bits, optional, uleb128
.aeabi_attribute Tag_Feature_BTI, 1 .aeabi_attribute Tag_Feature_BTI, 1
.aeabi_attribute Tag_Feature_PAC, 1 .aeabi_attribute Tag_Feature_PAC, 1
.aeabi_attribute Tag_Feature_GCS, 1 .aeabi_attribute Tag_Feature_GCS, 1
.global _start
.type _start, %function //--- pauth-bti-gcs.s
_start: .aeabi_subsection aeabi_pauthabi, required, uleb128
ret .aeabi_attribute Tag_PAuth_Platform, 49
.aeabi_attribute Tag_PAuth_Schema, 19
.aeabi_subsection aeabi_feature_and_bits, optional, uleb128
.aeabi_attribute Tag_Feature_BTI, 1
.aeabi_attribute Tag_Feature_PAC, 0
.aeabi_attribute Tag_Feature_GCS, 1
//--- pauth-bti-pac.s
.aeabi_subsection aeabi_pauthabi, required, uleb128
.aeabi_attribute Tag_PAuth_Platform, 49
.aeabi_attribute Tag_PAuth_Schema, 19
.aeabi_subsection aeabi_feature_and_bits, optional, uleb128
.aeabi_attribute Tag_Feature_BTI, 1
.aeabi_attribute Tag_Feature_PAC, 1
.aeabi_attribute Tag_Feature_GCS, 0

View File

@ -25,6 +25,17 @@ public:
: ELFExtendedAttrParser(nullptr, returnTagsNamesMap()) {} : ELFExtendedAttrParser(nullptr, returnTagsNamesMap()) {}
}; };
// Used for extracting AArch64 Build Attributes
struct AArch64BuildAttrSubsections {
struct PauthSubSection {
uint64_t TagPlatform = 0;
uint64_t TagSchema = 0;
} Pauth;
uint32_t AndFeatures = 0;
};
AArch64BuildAttrSubsections
extractBuildAttributesSubsections(const llvm::AArch64AttributeParser &);
} // namespace llvm } // namespace llvm
#endif // LLVM_SUPPORT_AARCH64ATTRIBUTEPARSER_H #endif // LLVM_SUPPORT_AARCH64ATTRIBUTEPARSER_H

View File

@ -8,6 +8,7 @@
//===---------------------------------------------------------------------===// //===---------------------------------------------------------------------===//
#include "llvm/Support/AArch64AttributeParser.h" #include "llvm/Support/AArch64AttributeParser.h"
#include "llvm/Support/AArch64BuildAttributes.h"
std::vector<llvm::SubsectionAndTagToTagName> & std::vector<llvm::SubsectionAndTagToTagName> &
llvm::AArch64AttributeParser::returnTagsNamesMap() { llvm::AArch64AttributeParser::returnTagsNamesMap() {
@ -19,3 +20,29 @@ llvm::AArch64AttributeParser::returnTagsNamesMap() {
{"aeabi_feature_and_bits", 2, "Tag_Feature_GCS"}}; {"aeabi_feature_and_bits", 2, "Tag_Feature_GCS"}};
return TagsNamesMap; return TagsNamesMap;
} }
llvm::AArch64BuildAttrSubsections llvm::extractBuildAttributesSubsections(
const llvm::AArch64AttributeParser &Attributes) {
llvm::AArch64BuildAttrSubsections SubSections;
auto GetPauthValue = [&Attributes](unsigned Tag) {
return Attributes.getAttributeValue("aeabi_pauthabi", Tag).value_or(0);
};
SubSections.Pauth.TagPlatform =
GetPauthValue(llvm::AArch64BuildAttributes::TAG_PAUTH_PLATFORM);
SubSections.Pauth.TagSchema =
GetPauthValue(llvm::AArch64BuildAttributes::TAG_PAUTH_SCHEMA);
auto GetFeatureValue = [&Attributes](unsigned Tag) {
return Attributes.getAttributeValue("aeabi_feature_and_bits", Tag)
.value_or(0);
};
SubSections.AndFeatures |=
GetFeatureValue(llvm::AArch64BuildAttributes::TAG_FEATURE_BTI);
SubSections.AndFeatures |=
GetFeatureValue(llvm::AArch64BuildAttributes::TAG_FEATURE_PAC) << 1;
SubSections.AndFeatures |=
GetFeatureValue(llvm::AArch64BuildAttributes::TAG_FEATURE_GCS) << 2;
return SubSections;
}