Read ARM64 build attributes for BTI, PAC and GCS

GCC 16 records -mbranch-protection in AArch64 build attributes
(.ARM.attributes) instead of .note.gnu.property, so an output lost the
BTI, PAC and GCS bits even if all its inputs were compiled with the
flag, and -z force-bti warned about every object file. We now read the
aeabi_feature_and_bits subsection and merge it into
GNU_PROPERTY_AARCH64_FEATURE_1_AND.

Fixes https://github.com/rui314/mold/issues/1725
This commit is contained in:
Rui Ueyama committed 2026-10-09 12:37:02 +09:00
1 parent 7866fb4799
commit ff6a03bc69
4 files changed
+81 -4

No files matched your search

+4 -4
View File
@@ -862,9 +862,9 @@ point symbol in a script.
`-z bti-report` flag is used to make sure that all object files were
compiled with BTI. If `warning` or `error` are given, `mold` prints out a
warning or an error message if an object file lacks
warning or an error message if an object file has neither
`GNU_PROPERTY_AARCH64_FEATURE_1_BTI` bit in its `.note.gnu.property`
section.
section nor `Tag_Feature_BTI` in its build attributes.
* `-z force-bti`:
Turn on `GNU_PROPERTY_AARCH64_FEATURE_1_BTI` bit in `.note.gnu.property`
@@ -880,9 +880,9 @@ point symbol in a script.
off.
* `-z gcs-report`=[ `warning` | `error` | `none` ]:
Print out a warning or an error message if an object file lacks
Print out a warning or an error message if an object file has neither
`GNU_PROPERTY_AARCH64_FEATURE_1_GCS` bit in its `.note.gnu.property`
section.
section nor `Tag_Feature_GCS` in its build attributes.
* `-z gcs-report-dynamic`=[ `warning` | `error` | `none` ]:
Print out a warning or an error message if a shared library lacks
+1
View File
@@ -41,6 +41,7 @@ pub const SHT_HIOS: u32 = 0x6fffffff;
pub const SHT_X86_64_UNWIND: u32 = 0x70000001;
pub const SHT_ARM_EXIDX: u32 = 0x70000001;
pub const SHT_ARM_ATTRIBUTES: u32 = 0x70000003;
pub const SHT_AARCH64_ATTRIBUTES: u32 = 0x70000003;
pub const SHT_RISCV_ATTRIBUTES: u32 = 0x70000003;
pub const SHT_LOUSER: u32 = 0x80000000;
pub const SHT_HIUSER: u32 = 0xffffffff;
+37
View File
@@ -1544,6 +1544,38 @@ impl<E: Target> ObjectFile<E> {
}
}
// AArch64 build attributes. GCC 16 records -mbranch-protection in them
// instead of in .note.gnu.property. Tag N of the aeabi_feature_and_bits
// subsection corresponds to bit N of GNU_PROPERTY_AARCH64_FEATURE_1_AND,
// so we merge them into the property.
//
// 'A' [ <subsection-length> "subsection-name" <optional> <type> [ <tag> <value> ]* ]*
fn read_arm64_attributes(&mut self, data: &'static [u8]) {
let Some(mut data) = data.strip_prefix(b"A") else {
return;
};
while !data.is_empty() {
let size = E::read_u32(data) as usize;
let p = &data[4..size];
data = &data[size..];
if let Some(p) = p.strip_prefix(b"aeabi_feature_and_bits\0") {
// Skip the optional and type fields, which are always 1 and
// 0 (ULEB128) for this subsection.
let mut p = &p[2..];
let mut features = 0;
while !p.is_empty() {
let tag = read_uleb(&mut p);
if read_uleb(&mut p) == 1 && tag < 32 {
features |= 1 << tag;
}
}
*self.gnu_properties.entry(GNU_PROPERTY_AARCH64_FEATURE_1_AND).or_insert(0) |=
features;
}
}
}
// <format-version>
// [ <section-length> "vendor-name" [ <file-tag> <size> <attribute>*]+ ]*
fn read_riscv_attributes(&mut self, data: &'static [u8]) {
@@ -1620,6 +1652,11 @@ impl<E: Target> ObjectFile<E> {
continue;
}
if E::FAMILY == Family::Arm64 && sh_type == SHT_AARCH64_ATTRIBUTES {
let contents = self.base.section_contents_from_shdr(shdr);
self.read_arm64_attributes(contents);
continue;
}
if E::IS_ARM && sh_type == SHT_ARM_ATTRIBUTES {
continue;
}
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
# GCC 16 records -mbranch-protection in AArch64 build attributes instead
# of .note.gnu.property. Skip if the compiler is older.
echo 'void foo() {}' | $CC -mbranch-protection=bti -c -o $t/a.o -xc - || skip
readelf --sections $t/a.o | grep -F .ARM.attributes || skip
# _start calls foo with an indirect branch, which must land on a BTI
# landing pad if the output is BTI-enabled.
cat <<EOF | $CC -O2 -mbranch-protection=standard -c -o $t/b.o -xc -
int foo();
int (*fp)() = foo;
void _start() {
register long x0 __asm__("x0") = fp() == 42 ? 0 : 1;
register long x8 __asm__("x8") = 93; // exit
__asm__ volatile("svc 0" :: "r"(x0), "r"(x8));
}
EOF
cat <<EOF | $CC -O2 -mbranch-protection=bti -c -o $t/c.o -xc -
int foo() { return 42; }
EOF
cat <<EOF | $CC -O2 -mbranch-protection=none -c -o $t/d.o -xc -
int foo() { return 42; }
EOF
./mold -static -o $t/exe1 $t/b.o $t/c.o
readelf -n $t/exe1 | grep 'AArch64 feature: BTI$'
$QEMU $t/exe1
./mold -static -o $t/exe2 $t/b.o $t/d.o
readelf -n $t/exe2 | not grep 'AArch64 feature'
$QEMU $t/exe2
./mold -r -o $t/e.o $t/b.o $t/c.o
readelf -n $t/e.o | grep 'AArch64 feature: BTI$'