-
-
Notifications
You must be signed in to change notification settings - Fork 15k
-Zstaticlib-hide-internal-symbols and Zstaticlib-rename-internal-symbols: hide/rename internal symbols in staticlibs
#155338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b46b873
96bce1d
9bd2d74
dc60c54
14097d0
b4f977a
da4e849
e9e5f9a
5811b9e
b8a2fe1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| 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 by setting their ELF visibility to `STV_HIDDEN`. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the difference with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| This is a lightweight, zero-overhead operation: only the visibility byte of each | ||
| internal symbol is modified in-place. No strtab manipulation or section header | ||
| copying is performed. | ||
|
|
||
| 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 error. | ||
|
|
||
| Currently only ELF targets are supported (Linux, BSD, etc.). On non-ELF | ||
| targets (macOS, 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,21 @@ | ||||||
| # `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. | ||||||
|
|
||||||
| the Rust compiler already sets `STV_HIDDEN` visibility on non-exported | ||||||
| symbols by default in the generated `.o` files, so renamed internal symbols | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm? It does not?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, that doc statement is wrong. The default visibility is Interposable ( |
||||||
| retain their original `STV_HIDDEN` visibility even without | ||||||
| `-Zstaticlib-hide-internal-symbols`. Use `-Zstaticlib-hide-internal-symbols` | ||||||
| alone if you only need explicit visibility hiding without renaming (zero overhead). | ||||||
|
|
||||||
| 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 error. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(Same for the hiding flag.)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accepted. |
||||||
|
|
||||||
| Currently only ELF targets are supported (Linux, BSD, etc.). On non-ELF | ||||||
| targets (macOS, Windows), a warning is emitted and the flag has no effect. | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| #![crate_type = "staticlib"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some test files, e.g. these ones are nontrivial but their contents are exactly the same. Perhaps it's possible to deduplicate the tests to avoid copypasting source files several times?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, will deduplicate. |
||
|
|
||
| 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, | ||
| } | ||
| } | ||
| 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; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| //@ 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::{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 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 | ||
| } |
| 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, | ||
| } | ||
| } |
| 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; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can two short hashes be the same if two different crates have the same name?
I guess not due to the
stable_crate_idcomponent of themetadata_symbol?View changes since the review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly.
StableCrateIdincorporates-Cmetadatawhich Cargo always sets uniquely per crate, so same-name crates get different suffixes. The only way to get a collision is compiling two same-name crates manually without-Cmetadata, but then their mangled symbols are already identical anyway (v0uses the sameStableCrateIdas disambiguator).I'll clean this up to read
StableCrateIddirectly fromCrateInfoinstead of parsing it out of the metadata symbol string. Is that OK?