Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
729 changes: 726 additions & 3 deletions compiler/rustc_codegen_ssa/src/back/archive.rs

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,30 @@ fn link_staticlib(
sess.dcx().emit_fatal(e);
}

if sess.opts.unstable_opts.staticlib_hide_internal_symbols {
if !matches!(&*sess.target.archive_format, "gnu" | "bsd" | "darwin") {
sess.dcx().emit_warn(errors::StaticlibHideInternalSymbolsUnsupported {
archive_format: sess.target.archive_format.to_string(),
});
} else if let Some(symbols) = crate_info.exported_symbols.get(&CrateType::StaticLib) {
use rustc_data_structures::fx::FxHashSet;
let keep: FxHashSet<String> = symbols.iter().map(|(s, _)| s.clone()).collect();
ab.set_hide_symbols(keep);
}
}

if sess.opts.unstable_opts.staticlib_rename_internal_symbols {
if !matches!(&*sess.target.archive_format, "gnu" | "bsd" | "darwin") {
sess.dcx().emit_warn(errors::StaticlibRenameInternalSymbolsUnsupported {
archive_format: sess.target.archive_format.to_string(),
});
} else if let Some(symbols) = crate_info.exported_symbols.get(&CrateType::StaticLib) {
use rustc_data_structures::fx::FxHashSet;
let keep: FxHashSet<String> = symbols.iter().map(|(s, _)| s.clone()).collect();
ab.set_rename_symbols(keep, crate_info.rename_suffix.clone());
}
}

ab.build(out_filename);

let crates = crate_info.used_crates.iter();
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,7 @@ impl CrateInfo {
natvis_debugger_visualizers: Default::default(),
lint_level_specs: CodegenLintLevelSpecs::from_tcx(tcx),
metadata_symbol: exported_symbols::metadata_symbol_name(tcx),
rename_suffix: format!("_rs{:x}", tcx.stable_crate_id(LOCAL_CRATE)),
each_linked_rlib_file_for_lto: Default::default(),
exported_symbols_for_lto: Default::default(),
};
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_codegen_ssa/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,22 @@ pub(crate) struct IncompatibleArchiveFormat {
#[diag("linking static libraries is not supported for BPF")]
pub(crate) struct BpfStaticlibNotSupported;

#[derive(Diagnostic)]
#[diag(
"-Zstaticlib-hide-internal-symbols only supports ELF and Mach-O archive formats (gnu/bsd/darwin), but the target uses `{$archive_format}`"
)]
pub(crate) struct StaticlibHideInternalSymbolsUnsupported {
pub archive_format: String,
}

#[derive(Diagnostic)]
#[diag(
"-Zstaticlib-rename-internal-symbols only supports ELF and Mach-O archive formats (gnu/bsd/darwin), but the target uses `{$archive_format}`"
)]
pub(crate) struct StaticlibRenameInternalSymbolsUnsupported {
pub archive_format: String,
}

#[derive(Diagnostic)]
#[diag("entry symbol `main` declared multiple times")]
#[help(
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ pub struct CrateInfo {
pub natvis_debugger_visualizers: BTreeSet<DebuggerVisualizerFile>,
pub lint_level_specs: CodegenLintLevelSpecs,
pub metadata_symbol: String,
pub rename_suffix: String,
pub each_linked_rlib_file_for_lto: Vec<PathBuf>,
pub exported_symbols_for_lto: Vec<String>,
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,8 @@ fn test_unstable_options_tracking_hash() {
tracked!(split_lto_unit, Some(true));
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
tracked!(stack_protector, StackProtector::All);
tracked!(staticlib_hide_internal_symbols, true);
tracked!(staticlib_rename_internal_symbols, true);
tracked!(teach, true);
tracked!(thinlto, Some(true));
tracked!(tiny_const_eval_limit, true);
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2456,6 +2456,22 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
let mut collected_options = Default::default();

let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut collected_options);

if unstable_opts.staticlib_hide_internal_symbols && !crate_types.contains(&CrateType::StaticLib)
{
early_dcx.early_warn(
"-Zstaticlib-hide-internal-symbols has no effect without `--crate-type staticlib`",
);
}

if unstable_opts.staticlib_rename_internal_symbols
&& !crate_types.contains(&CrateType::StaticLib)
{
early_dcx.early_warn(
"-Zstaticlib-rename-internal-symbols has no effect without `--crate-type staticlib`",
);
}

let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);

if !unstable_opts.unstable_options && json_timings {
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2661,6 +2661,10 @@ written to standard error output)"),
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED],
"allow staticlibs to have rust dylib dependencies"),
staticlib_hide_internal_symbols: bool = (false, parse_bool, [TRACKED],
"hide non-exported symbols in ELF static libraries by setting STV_HIDDEN"),
staticlib_rename_internal_symbols: bool = (false, parse_bool, [TRACKED],
"rename Rust internal symbols when building staticlibs to avoid conflicts"),
staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED],
"prefer dynamic linking to static linking for staticlibs (default: no)"),
strict_init_checks: bool = (false, parse_bool, [TRACKED],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# `staticlib-hide-internal-symbols`

When building a `staticlib`, this option hides all non-exported Rust-internal
symbols. On ELF targets, this sets `STV_HIDDEN` visibility. On Apple (Mach-O)
targets, this sets the `N_PEXT` (private external) bit.

This is a lightweight, zero-overhead operation: only the visibility/type byte of
each internal symbol is modified in-place.

Only symbols explicitly exported via `#[no_mangle]` or `#[export_name]` are left
unchanged. All other `GLOBAL`/`WEAK` symbols (including `pub(crate)` and `pub`
items without `#[no_mangle]`) are hidden.

This option can only be used with `--crate-type staticlib`. Using it with
other crate types will result in a compilation warning.

Supported on ELF targets (Linux, BSD, etc.) and Apple targets (macOS, iOS, etc.).
On unsupported targets (Windows), a warning is emitted and the flag has no effect.

This option can be combined with `-Zstaticlib-rename-internal-symbols`.
When both are enabled, symbols are both renamed and hidden.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# `staticlib-rename-internal-symbols`

When building a `staticlib`, this option renames all non-exported Rust-internal
symbols by appending a `_rs{hash}` suffix. This prevents symbol collisions when
multiple Rust static libraries are linked into the same final binary.

This option only renames symbols; it does not change their visibility.
Use `-Zstaticlib-hide-internal-symbols` in addition if you also want to hide
internal symbols.

Only symbols explicitly exported via `#[no_mangle]` or `#[export_name]` are left
unchanged. All other `GLOBAL`/`WEAK` symbols (including `pub(crate)` and `pub`
items without `#[no_mangle]`) are renamed.

This option can only be used with `--crate-type staticlib`. Using it with
other crate types will result in a compilation warning.

Supported on ELF targets (Linux, BSD, etc.) and Apple targets (macOS, iOS, etc.).
On unsupported targets (Windows), a warning is emitted and the flag has no effect.
115 changes: 115 additions & 0 deletions tests/run-make/staticlib-hide-internal-symbols-macho/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//@ only-apple
//@ ignore-cross-compile

use std::collections::HashSet;

use run_make_support::object::Endianness;
use run_make_support::object::macho::{MachHeader64, N_EXT, N_PEXT, N_SECT, N_STAB, N_TYPE};
use run_make_support::object::read::archive::ArchiveFile;
use run_make_support::object::read::macho::{MachHeader as _, Nlist as _};
use run_make_support::path_helpers::source_root;
use run_make_support::{cc, extra_c_flags, object, rfs, run, rustc, static_lib_name};

type MachOFileHeader64 = MachHeader64<Endianness>;
type SymbolTable<'data> =
run_make_support::object::read::macho::SymbolTable<'data, MachOFileHeader64>;

const EXPORTED: &[&str] = &["my_add", "my_hash_lookup", "call_internal", "my_safe_div"];

fn main() {
let sibling = source_root().join("tests/run-make/staticlib-hide-internal-symbols");
rfs::copy(sibling.join("lib.rs"), "lib.rs");
rfs::copy(sibling.join("main.c"), "main.c");

let lib_name = static_lib_name("lib");

rustc()
.input("lib.rs")
.crate_type("staticlib")
.arg("-Zstaticlib-hide-internal-symbols")
.opt()
.run();

cc().input("main.c").input(&lib_name).out_exe("main").args(extra_c_flags()).run();
run("main");

let data = rfs::read(&lib_name);
check_symbols(&data, true);

rfs::remove_file(&lib_name);
rustc().input("lib.rs").crate_type("staticlib").opt().run();

let data = rfs::read(&lib_name);
check_symbols(&data, false);
}

fn check_symbols(archive_data: &[u8], with_flag: bool) {
let archive = ArchiveFile::parse(archive_data).unwrap();
let mut found_exported = HashSet::new();

for member in archive.members() {
let member = member.unwrap();
let data = member.data(archive_data).unwrap();

let Ok(header) = MachOFileHeader64::parse(data, 0) else { continue };
let Ok(endian) = header.endian() else { continue };

let Some(symtab) = find_symtab(header, endian, data) else { continue };
let strtab = symtab.strings();

for nlist in symtab.iter() {
let n_type = nlist.n_type();
if n_type & N_STAB != 0 {
continue;
}
if n_type & N_EXT == 0 {
continue;
}
if n_type & N_TYPE != N_SECT {
continue;
}

let Ok(name_bytes) = nlist.name(endian, strtab) else { continue };
let Ok(name) = std::str::from_utf8(name_bytes) else { continue };
let name = name.strip_prefix('_').unwrap_or(name);

let exported = EXPORTED.contains(&name);
let has_pext = n_type & N_PEXT != 0;

if with_flag {
if exported {
assert!(!has_pext, "with -Z hide: exported `{name}` should NOT have N_PEXT");
} else {
assert!(has_pext, "with -Z hide: internal `{name}` should have N_PEXT");
}
} else if exported {
assert!(!has_pext, "without -Z: exported `{name}` should NOT have N_PEXT");
}

if exported {
found_exported.insert(name.to_string());
}
}
}

for expected in EXPORTED {
assert!(
found_exported.contains(*expected),
"expected to find exported symbol `{expected}` in archive"
);
}
}

fn find_symtab<'data>(
header: &MachOFileHeader64,
endian: Endianness,
data: &'data [u8],
) -> Option<SymbolTable<'data>> {
let mut commands = header.load_commands(endian, data, 0).ok()?;
while let Ok(Some(command)) = commands.next() {
if let Ok(Some(symtab_cmd)) = command.symtab() {
return symtab_cmd.symbols::<MachOFileHeader64, _>(endian, data).ok();
}
}
None
}
40 changes: 40 additions & 0 deletions tests/run-make/staticlib-hide-internal-symbols/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#![crate_type = "staticlib"]

use std::collections::HashMap;
use std::panic::{AssertUnwindSafe, catch_unwind};

#[no_mangle]
pub extern "C" fn my_add(a: i32, b: i32) -> i32 {
a + b
}

#[no_mangle]
pub extern "C" fn my_hash_lookup(key: u64) -> u64 {
let mut map = HashMap::new();
for i in 0..100u64 {
map.insert(i, i.wrapping_mul(2654435761));
}
*map.get(&key).unwrap_or(&0)
}

fn internal_helper() -> i32 {
42
}

#[no_mangle]
pub extern "C" fn call_internal() -> i32 {
internal_helper()
}

#[no_mangle]
pub extern "C" fn my_safe_div(a: i32, b: i32) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if b == 0 {
panic!("division by zero!");
}
a / b
})) {
Ok(result) => result,
Err(_) => -1,
}
}
18 changes: 18 additions & 0 deletions tests/run-make/staticlib-hide-internal-symbols/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
extern int my_add(int a, int b);
extern unsigned long my_hash_lookup(unsigned long key);
extern int call_internal(void);
extern int my_safe_div(int a, int b);

int main() {
if (my_add(10, 20) != 30)
return 1;
if (my_hash_lookup(5) != 5UL * 2654435761UL)
return 1;
if (call_internal() != 42)
return 1;
if (my_safe_div(100, 5) != 20)
return 1;
if (my_safe_div(100, 0) != -1)
return 1;
return 0;
}
Loading
Loading