-
-
Notifications
You must be signed in to change notification settings - Fork 15.1k
Staticlib hide internal symbols #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
Open
cezarbbb
wants to merge
2
commits into
rust-lang:main
Choose a base branch
from
cezarbbb:staticlib-symbol-hygiene
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+655
−61
Open
Staticlib hide internal symbols #155338
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/doc/unstable-book/src/compiler-flags/staticlib-hide-internal-symbols.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # `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. | ||
|
|
||
| ## Comparison with `-Zdefault-visibility=hidden` | ||
|
|
||
| `-Zdefault-visibility=hidden` sets visibility at LLVM IR codegen time. It targets | ||
| shared objects (`cdylib`/`dylib`) and only affects the current crate's codegen. | ||
|
|
||
| `-Zstaticlib-hide-internal-symbols` patches visibility bytes post-compilation in | ||
| the final `.a` archive, which includes object files from all upstream static | ||
| dependencies. This means internal symbols from the entire dependency tree are | ||
| hidden, not just those from the current crate. Hidden symbols are also excluded | ||
| from the linker's global symbol table, which can slightly reduce final binary size. |
115 changes: 115 additions & 0 deletions
115
tests/run-make/staticlib-hide-internal-symbols-macho/rmake.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, 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) = 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 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I suspect the right condition here is "is the target using ELF or Mach-O" rather than something based on archive format?
We should be able to update ELF/Macho-O files in archives regardless of the archive's own format.
The object file format can be obtained from
sess.target.binary_format.View changes since the review