Support -z gcs and -z gcs-report

-z gcs=implicit|never|always controls the GCS bit of the output's
GNU_PROPERTY_AARCH64_FEATURE_1_AND, and -z gcs-report diagnoses input
files lacking it. -z gcs=always implies -z gcs-report=warning.
This commit is contained in:
Rui Ueyama committed 2026-10-09 10:49:20 +09:00
1 parent eec9e37a95
commit 358b0ca290
6 files changed
+107 -15

No files matched your search

+12
View File
@@ -871,6 +871,18 @@ point symbol in a script.
section even if some object files were not compiled with BTI, and generate
a PLT with BTI landing pads. This option implies `-z bti-report=warning`.
* `-z gcs`=[ `implicit` | `always` | `never` ]:
Guarded Control Stack (GCS) is an ARM64 shadow stack feature. With
`implicit`, which is the default, `GNU_PROPERTY_AARCH64_FEATURE_1_GCS` bit
in `.note.gnu.property` section is turned on only if all object files have
the bit. `always` turns it on regardless of object files and implies `-z
gcs-report=warning`. `never` turns it off.
* `-z gcs-report`=[ `warning` | `error` | `none` ]:
Print out a warning or an error message if an object file lacks
`GNU_PROPERTY_AARCH64_FEATURE_1_GCS` bit in its `.note.gnu.property`
section.
* `-z now`, `-z lazy`:
By default, functions referring to other ELF modules are resolved by the
dynamic linker when they are called for the first time. `-z now` marks an
+6
View File
@@ -4,6 +4,7 @@ use std::collections::{BTreeMap, BTreeSet};
use crate::arch::{Family, Target};
use crate::chunks::ChunkHeader;
use crate::cmdline::GcsKind;
use crate::context::Context;
use crate::elf::*;
@@ -106,6 +107,11 @@ pub fn construct<E: Target>(ctx: &mut Context<E>) {
if ctx.args.z_force_bti {
*features |= GNU_PROPERTY_AARCH64_FEATURE_1_BTI;
}
match ctx.args.z_gcs {
GcsKind::Implicit => {}
GcsKind::Never => *features &= !GNU_PROPERTY_AARCH64_FEATURE_1_GCS,
GcsKind::Always => *features |= GNU_PROPERTY_AARCH64_FEATURE_1_GCS,
}
}
ctx.note_property.as_mut().unwrap().contents =
+32 -1
View File
@@ -293,6 +293,14 @@ pub enum ReportKind {
Error,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum GcsKind {
#[default]
Implicit,
Never,
Always,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ShuffleSections {
#[default]
@@ -431,6 +439,8 @@ pub struct Args {
pub build_id: BuildId,
pub z_cet_report: ReportKind,
pub z_bti_report: ReportKind,
pub z_gcs_report: ReportKind,
pub z_gcs: GcsKind,
pub undefined_glob: Glob,
pub unique: Glob,
pub z_separate_code: SeparateCodeKind,
@@ -572,6 +582,8 @@ impl Default for Args {
build_id: BuildId::default(),
z_cet_report: ReportKind::None,
z_bti_report: ReportKind::None,
z_gcs_report: ReportKind::None,
z_gcs: GcsKind::Implicit,
undefined_glob: Glob::new(),
unique: Glob::new(),
z_separate_code: SeparateCodeKind::NoSeparateCode,
@@ -1226,6 +1238,7 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
let mut z_relro: Option<bool> = None;
let mut z_dynamic_undefined_weak: Option<bool> = None;
let mut z_bti_report: Option<ReportKind> = None;
let mut z_gcs_report: Option<ReportKind> = None;
let mut separate_debug_file: Option<PathBuf> = None;
// An explicit seed survives intervening --reverse-sections options.
let mut shuffle_sections_seed: Option<u64> = None;
@@ -1637,6 +1650,12 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
z_bti_report = Some(ReportKind::Warning);
} else if cursor.read_z_flag("bti-report=error") {
z_bti_report = Some(ReportKind::Error);
} else if cursor.read_z_flag("gcs-report=none") {
z_gcs_report = Some(ReportKind::None);
} else if cursor.read_z_flag("gcs-report=warning") {
z_gcs_report = Some(ReportKind::Warning);
} else if cursor.read_z_flag("gcs-report=error") {
z_gcs_report = Some(ReportKind::Error);
} else if cursor.read_z_flag("execstack") {
a.z_execstack = true;
} else if cursor.read_z_flag("execstack-if-needed") {
@@ -1679,6 +1698,12 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
} else if cursor.read_z_flag("ibtplt") {
} else if cursor.read_z_flag("force-bti") {
a.z_force_bti = true;
} else if cursor.read_z_flag("gcs=implicit") {
a.z_gcs = GcsKind::Implicit;
} else if cursor.read_z_flag("gcs=never") {
a.z_gcs = GcsKind::Never;
} else if cursor.read_z_flag("gcs=always") {
a.z_gcs = GcsKind::Always;
} else if cursor.read_z_flag("muldefs") {
a.allow_multiple_definition = true;
} else if let Some(value) =
@@ -2050,9 +2075,15 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[Cow<'_, OsStr>]) -> Pars
// `-z dynamic-undefined-weak` is enabled by default for DSOs.
a.z_dynamic_undefined_weak = z_dynamic_undefined_weak.unwrap_or(a.shared);
// `-z force-bti` implies `-z bti-report=warning`.
// `-z force-bti` implies `-z bti-report=warning`, and `-z gcs=always`
// implies `-z gcs-report=warning`.
a.z_bti_report =
z_bti_report.unwrap_or(if a.z_force_bti { ReportKind::Warning } else { ReportKind::None });
a.z_gcs_report = z_gcs_report.unwrap_or(if a.z_gcs == GcsKind::Always {
ReportKind::Warning
} else {
ReportKind::None
});
// --section-order implies `-z norelro`
a.z_relro = z_relro.unwrap_or(a.section_order.is_empty());
+5 -2
View File
@@ -319,8 +319,11 @@ pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
if ctx.args.z_cet_report != cmdline::ReportKind::None {
passes::check_cet_errors(&ctx);
}
// Handle `-z bti-report`.
if E::FAMILY == Family::Arm64 && ctx.args.z_bti_report != cmdline::ReportKind::None {
// Handle `-z bti-report` and `-z gcs-report`.
if E::FAMILY == Family::Arm64
&& (ctx.args.z_bti_report != cmdline::ReportKind::None
|| ctx.args.z_gcs_report != cmdline::ReportKind::None)
{
passes::check_arm64_feature_errors(&ctx);
}
// Handle `-z execstack-if-needed`.
+20 -12
View File
@@ -1547,22 +1547,30 @@ pub fn check_cet_errors<E: Target>(ctx: &Context<E>) {
}
pub fn check_arm64_feature_errors<E: Target>(ctx: &Context<E>) {
let has_feature = |file: &ObjectFile<E>, feature: u32| {
file.gnu_properties
.get(&GNU_PROPERTY_AARCH64_FEATURE_1_AND)
.is_some_and(|v| v & feature != 0)
};
for file in &ctx.objs {
if ctx.is_internal(file.id())
|| file
.gnu_properties
.get(&GNU_PROPERTY_AARCH64_FEATURE_1_AND)
.is_some_and(|v| v & GNU_PROPERTY_AARCH64_FEATURE_1_BTI != 0)
{
if ctx.is_internal(file.id()) {
continue;
}
match ctx.args.z_bti_report {
ReportKind::None => {}
ReportKind::Warning => {
warn!("{file}: -z bti-report=warning: missing GNU_PROPERTY_AARCH64_FEATURE_1_BTI")
for (kind, option, feature, name) in [
(ctx.args.z_bti_report, "bti-report", GNU_PROPERTY_AARCH64_FEATURE_1_BTI, "BTI"),
(ctx.args.z_gcs_report, "gcs-report", GNU_PROPERTY_AARCH64_FEATURE_1_GCS, "GCS"),
] {
if has_feature(file, feature) {
continue;
}
ReportKind::Error => {
error!("{file}: -z bti-report=error: missing GNU_PROPERTY_AARCH64_FEATURE_1_BTI")
match kind {
ReportKind::None => {}
ReportKind::Warning => warn!(
"{file}: -z {option}=warning: missing GNU_PROPERTY_AARCH64_FEATURE_1_{name}"
),
ReportKind::Error => error!(
"{file}: -z {option}=error: missing GNU_PROPERTY_AARCH64_FEATURE_1_{name}"
),
}
}
}
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
# GCC supports -mbranch-protection=gcs only since version 15, so use clang.
CLANG="clang ${TRIPLE:+--target=$TRIPLE}"
echo 'void foo() {}' | $CLANG -mbranch-protection=gcs -c -o /dev/null -xc - >& /dev/null || skip
# a.o is marked with BTI and GCS, and b.o only with BTI.
echo 'void _start() {}' | $CLANG -mbranch-protection=bti+gcs -c -o $t/a.o -xc -
echo 'void foo() {}' | $CLANG -mbranch-protection=bti -c -o $t/b.o -xc -
# Older readelf doesn't know the name of the GCS bit.
./mold -o $t/exe1 $t/a.o
readelf -n $t/exe1 | grep -E 'AArch64 feature: BTI, (GCS|<unknown: 4>)$'
./mold -o $t/exe2 $t/a.o -z gcs=never
readelf -n $t/exe2 | grep 'AArch64 feature: BTI$'
./mold -o $t/exe3 $t/a.o $t/b.o 2> $t/log
readelf -n $t/exe3 | grep 'AArch64 feature: BTI$'
not grep . $t/log
./mold -o $t/exe4 $t/a.o $t/b.o -z gcs=always 2> $t/log
readelf -n $t/exe4 | grep -E 'AArch64 feature: BTI, (GCS|<unknown: 4>)$'
grep 'b.o: -z gcs-report=warning: missing GNU_PROPERTY_AARCH64_FEATURE_1_GCS' $t/log
not grep a.o: $t/log
./mold -o $t/exe5 $t/a.o $t/b.o -z gcs=always -z gcs-report=none 2> $t/log
not grep . $t/log
not ./mold -o $t/exe6 $t/a.o $t/b.o -z gcs-report=error 2> $t/log
grep 'b.o: -z gcs-report=error: missing GNU_PROPERTY_AARCH64_FEATURE_1_GCS' $t/log