mirror of
https://github.com/rui314/mold.git
synced 2026-11-07 09:16:04 -10:00
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:
1 parent
eec9e37a95
commit
358b0ca290
6 files changed
+107
-15
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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}"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+32
@@ -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
|
||||
Reference in new issue
Block a user