Skip to content

Commit 2445a49

Browse files
committed
add -Zstaticlib-rename-internal-symbols
1 parent 40c254b commit 2445a49

18 files changed

Lines changed: 786 additions & 17 deletions

File tree

compiler/rustc_codegen_ssa/src/back/archive.rs

Lines changed: 294 additions & 17 deletions
Large diffs are not rendered by default.

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,18 @@ fn link_staticlib(
562562
}
563563
}
564564

565+
if sess.opts.unstable_opts.staticlib_rename_internal_symbols {
566+
if !matches!(&*sess.target.archive_format, "gnu" | "bsd" | "darwin") {
567+
sess.dcx().emit_warn(errors::StaticlibRenameInternalSymbolsUnsupported {
568+
archive_format: sess.target.archive_format.to_string(),
569+
});
570+
} else if let Some(symbols) = crate_info.exported_symbols.get(&CrateType::StaticLib) {
571+
use rustc_data_structures::fx::FxHashSet;
572+
let keep: FxHashSet<String> = symbols.iter().map(|(s, _)| s.clone()).collect();
573+
ab.set_rename_symbols(keep, crate_info.rename_suffix.clone());
574+
}
575+
}
576+
565577
ab.build(out_filename);
566578

567579
let crates = crate_info.used_crates.iter();

compiler/rustc_codegen_ssa/src/base.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,7 @@ impl CrateInfo {
957957
natvis_debugger_visualizers: Default::default(),
958958
lint_level_specs: CodegenLintLevelSpecs::from_tcx(tcx),
959959
metadata_symbol: exported_symbols::metadata_symbol_name(tcx),
960+
rename_suffix: format!("_rs{:x}", tcx.stable_crate_id(LOCAL_CRATE)),
960961
each_linked_rlib_file_for_lto: Default::default(),
961962
exported_symbols_for_lto: Default::default(),
962963
};

compiler/rustc_codegen_ssa/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,14 @@ pub(crate) struct StaticlibHideInternalSymbolsUnsupported {
701701
pub archive_format: String,
702702
}
703703

704+
#[derive(Diagnostic)]
705+
#[diag(
706+
"-Zstaticlib-rename-internal-symbols only supports ELF and Mach-O archive formats (gnu/bsd/darwin), but the target uses `{$archive_format}`"
707+
)]
708+
pub(crate) struct StaticlibRenameInternalSymbolsUnsupported {
709+
pub archive_format: String,
710+
}
711+
704712
#[derive(Diagnostic)]
705713
#[diag("entry symbol `main` declared multiple times")]
706714
#[help(

compiler/rustc_codegen_ssa/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ pub struct CrateInfo {
257257
pub natvis_debugger_visualizers: BTreeSet<DebuggerVisualizerFile>,
258258
pub lint_level_specs: CodegenLintLevelSpecs,
259259
pub metadata_symbol: String,
260+
pub rename_suffix: String,
260261
pub each_linked_rlib_file_for_lto: Vec<PathBuf>,
261262
pub exported_symbols_for_lto: Vec<String>,
262263
}

compiler/rustc_interface/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,7 @@ fn test_unstable_options_tracking_hash() {
867867
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
868868
tracked!(stack_protector, StackProtector::All);
869869
tracked!(staticlib_hide_internal_symbols, true);
870+
tracked!(staticlib_rename_internal_symbols, true);
870871
tracked!(teach, true);
871872
tracked!(thinlto, Some(true));
872873
tracked!(tiny_const_eval_limit, true);

compiler/rustc_session/src/config.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,6 +2464,14 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
24642464
);
24652465
}
24662466

2467+
if unstable_opts.staticlib_rename_internal_symbols
2468+
&& !crate_types.contains(&CrateType::StaticLib)
2469+
{
2470+
early_dcx.early_warn(
2471+
"-Zstaticlib-rename-internal-symbols has no effect without `--crate-type staticlib`",
2472+
);
2473+
}
2474+
24672475
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
24682476

24692477
if !unstable_opts.unstable_options && json_timings {

compiler/rustc_session/src/options.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2663,6 +2663,8 @@ written to standard error output)"),
26632663
"allow staticlibs to have rust dylib dependencies"),
26642664
staticlib_hide_internal_symbols: bool = (false, parse_bool, [TRACKED],
26652665
"hide non-exported symbols in ELF static libraries by setting STV_HIDDEN"),
2666+
staticlib_rename_internal_symbols: bool = (false, parse_bool, [TRACKED],
2667+
"rename Rust internal symbols when building staticlibs to avoid conflicts"),
26662668
staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED],
26672669
"prefer dynamic linking to static linking for staticlibs (default: no)"),
26682670
strict_init_checks: bool = (false, parse_bool, [TRACKED],
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `staticlib-rename-internal-symbols`
2+
3+
When building a `staticlib`, this option renames all non-exported Rust-internal
4+
symbols by appending a `_rs{hash}` suffix. This prevents symbol collisions when
5+
multiple Rust static libraries are linked into the same final binary.
6+
7+
This option only renames symbols; it does not change their visibility.
8+
Use `-Zstaticlib-hide-internal-symbols` in addition if you also want to hide
9+
internal symbols.
10+
11+
Only symbols explicitly exported via `#[no_mangle]` or `#[export_name]` are left
12+
unchanged. All other `GLOBAL`/`WEAK` symbols (including `pub(crate)` and `pub`
13+
items without `#[no_mangle]`) are renamed.
14+
15+
This option can only be used with `--crate-type staticlib`. Using it with
16+
other crate types will result in a compilation warning.
17+
18+
Supported on ELF targets (Linux, BSD, etc.) and Apple targets (macOS, iOS, etc.).
19+
On unsupported targets (Windows), a warning is emitted and the flag has no effect.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//@ only-apple
2+
//@ ignore-cross-compile
3+
4+
use std::collections::HashSet;
5+
6+
use run_make_support::object::Endianness;
7+
use run_make_support::object::macho::{MachHeader64, N_EXT, N_PEXT, N_SECT, N_STAB, N_TYPE};
8+
use run_make_support::object::read::archive::ArchiveFile;
9+
use run_make_support::object::read::macho::{MachHeader as _, Nlist as _};
10+
use run_make_support::path_helpers::source_root;
11+
use run_make_support::{cc, extra_c_flags, object, rfs, run, rustc, static_lib_name};
12+
13+
type MachOFileHeader64 = MachHeader64<Endianness>;
14+
type SymbolTable<'data> =
15+
run_make_support::object::read::macho::SymbolTable<'data, MachOFileHeader64>;
16+
17+
const EXPORTED: &[&str] = &["my_add", "my_hash_lookup", "call_internal", "my_safe_div"];
18+
19+
fn main() {
20+
let sibling = source_root().join("tests/run-make/staticlib-rename-internal-symbols");
21+
rfs::copy(sibling.join("lib.rs"), "lib.rs");
22+
rfs::copy(sibling.join("main.c"), "main.c");
23+
rfs::copy(sibling.join("liba.rs"), "liba.rs");
24+
rfs::copy(sibling.join("libb.rs"), "libb.rs");
25+
rfs::copy(sibling.join("dual_main.c"), "dual_main.c");
26+
27+
test_basic_functionality();
28+
test_suffix_present();
29+
test_dual_staticlib_linking();
30+
}
31+
32+
fn test_basic_functionality() {
33+
let lib_name = static_lib_name("lib");
34+
35+
rustc()
36+
.input("lib.rs")
37+
.crate_type("staticlib")
38+
.arg("-Zstaticlib-rename-internal-symbols")
39+
.opt()
40+
.run();
41+
42+
cc().input("main.c").input(&lib_name).out_exe("main").args(extra_c_flags()).run();
43+
run("main");
44+
45+
rfs::remove_file(&lib_name);
46+
}
47+
48+
fn test_suffix_present() {
49+
let lib_name = static_lib_name("lib");
50+
51+
rustc()
52+
.input("lib.rs")
53+
.crate_type("staticlib")
54+
.arg("-Zstaticlib-rename-internal-symbols")
55+
.opt()
56+
.run();
57+
58+
let data = rfs::read(&lib_name);
59+
check_rename_symbols(&data);
60+
61+
rfs::remove_file(&lib_name);
62+
}
63+
64+
fn test_dual_staticlib_linking() {
65+
let liba_name = static_lib_name("liba");
66+
let libb_name = static_lib_name("libb");
67+
68+
rustc()
69+
.input("liba.rs")
70+
.crate_type("staticlib")
71+
.arg("-Zstaticlib-rename-internal-symbols")
72+
.opt()
73+
.run();
74+
75+
rustc()
76+
.input("libb.rs")
77+
.crate_type("staticlib")
78+
.arg("-Zstaticlib-rename-internal-symbols")
79+
.opt()
80+
.run();
81+
82+
cc().input("dual_main.c")
83+
.input(&liba_name)
84+
.input(&libb_name)
85+
.out_exe("dual_main")
86+
.args(extra_c_flags())
87+
.run();
88+
run("dual_main");
89+
}
90+
91+
fn check_rename_symbols(archive_data: &[u8]) {
92+
let archive = ArchiveFile::parse(archive_data).unwrap();
93+
let mut found_exported = HashSet::new();
94+
let mut found_suffix = false;
95+
96+
for member in archive.members() {
97+
let member = member.unwrap();
98+
let data = member.data(archive_data).unwrap();
99+
100+
let Ok(header) = MachOFileHeader64::parse(data, 0) else { continue };
101+
let Ok(endian) = header.endian() else { continue };
102+
103+
let Some(symtab) = find_symtab(header, endian, data) else { continue };
104+
let strtab = symtab.strings();
105+
106+
for nlist in symtab.iter() {
107+
let n_type = nlist.n_type();
108+
if n_type & N_STAB != 0 {
109+
continue;
110+
}
111+
if n_type & N_EXT == 0 {
112+
continue;
113+
}
114+
if n_type & N_TYPE != N_SECT {
115+
continue;
116+
}
117+
118+
let Ok(name_bytes) = nlist.name(endian, strtab) else { continue };
119+
let Ok(name) = std::str::from_utf8(name_bytes) else { continue };
120+
let name = name.strip_prefix('_').unwrap_or(name);
121+
122+
if EXPORTED.contains(&name) {
123+
assert!(
124+
!name.contains("_rs"),
125+
"exported symbol `{name}` should not contain _rs suffix"
126+
);
127+
found_exported.insert(name.to_string());
128+
} else {
129+
assert!(
130+
name.contains("_rs"),
131+
"internal symbol `{name}` should contain _rs suffix after rename"
132+
);
133+
found_suffix = true;
134+
}
135+
}
136+
}
137+
138+
assert!(found_suffix, "expected to find at least one renamed symbol with _rs suffix");
139+
for expected in EXPORTED {
140+
assert!(
141+
found_exported.contains(*expected),
142+
"expected to find exported symbol `{expected}` in archive"
143+
);
144+
}
145+
}
146+
147+
fn find_symtab<'data>(
148+
header: &MachOFileHeader64,
149+
endian: Endianness,
150+
data: &'data [u8],
151+
) -> Option<SymbolTable<'data>> {
152+
let mut commands = header.load_commands(endian, data, 0).ok()?;
153+
while let Ok(Some(command)) = commands.next() {
154+
if let Ok(Some(symtab_cmd)) = command.symtab() {
155+
return symtab_cmd.symbols::<MachOFileHeader64, _>(endian, data).ok();
156+
}
157+
}
158+
None
159+
}

0 commit comments

Comments
 (0)