Skip to content

Commit ff707ad

Browse files
committed
staticlib symbol trimming: hide non-exported symbols in staticlibs
1 parent 383b9c4 commit ff707ad

14 files changed

Lines changed: 424 additions & 3 deletions

File tree

compiler/rustc_codegen_llvm/src/back/archive.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ impl<'a> ArchiveBuilder for LlvmArchiveBuilder<'a> {
8484
}
8585
}
8686
}
87+
88+
fn set_keep_symbols(&mut self, _keep: rustc_data_structures::fx::FxHashSet<String>) {
89+
// The LLVM archive builder does not support symbol filtering.
90+
// This is a no-op; symbol hiding is only supported via the ArArchiveBuilder path.
91+
}
8792
}
8893

8994
pub(crate) struct LlvmArchiveBuilderBuilder;

compiler/rustc_codegen_ssa/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ codegen_ssa_shuffle_indices_evaluation = could not evaluate shuffle_indices at c
261261
262262
codegen_ssa_specify_libraries_to_link = use the `-l` flag to specify native libraries to link
263263
264+
codegen_ssa_staticlib_hide_internal_symbols_unsupported =
265+
-Zstaticlib-hide-internal-symbols only supports ELF archive formats (gnu/bsd), but the target uses `{$archive_format}`
266+
264267
codegen_ssa_static_library_native_artifacts = Link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms.
265268
266269
codegen_ssa_static_library_native_artifacts_to_file = Native artifacts to link against have been written to {$path}. The order and any duplication can be significant on some platforms.

compiler/rustc_codegen_ssa/src/back/archive.rs

Lines changed: 169 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use ar_archive_writer::{
1111
pub use ar_archive_writer::{DEFAULT_OBJECT_READER, ObjectReader};
1212
use object::read::archive::ArchiveFile;
1313
use object::read::macho::FatArch;
14-
use rustc_data_structures::fx::FxIndexSet;
14+
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
1515
use rustc_data_structures::memmap::Mmap;
1616
use rustc_fs_util::TempDirBuilder;
1717
use rustc_metadata::EncodedMetadata;
@@ -312,6 +312,8 @@ pub trait ArchiveBuilder {
312312
) -> io::Result<()>;
313313

314314
fn build(self: Box<Self>, output: &Path) -> bool;
315+
316+
fn set_keep_symbols(&mut self, keep: FxHashSet<String>);
315317
}
316318

317319
pub struct ArArchiveBuilderBuilder;
@@ -331,6 +333,7 @@ pub struct ArArchiveBuilder<'a> {
331333
// Don't use an `HashMap` here, as the order is important. `lib.rmeta` needs
332334
// to be at the end of an archive in some cases for linkers to not get confused.
333335
entries: Vec<(Vec<u8>, ArchiveEntry)>,
336+
keep_symbols: Option<FxHashSet<String>>,
334337
}
335338

336339
#[derive(Debug)]
@@ -341,7 +344,17 @@ enum ArchiveEntry {
341344

342345
impl<'a> ArArchiveBuilder<'a> {
343346
pub fn new(sess: &'a Session, object_reader: &'static ObjectReader) -> ArArchiveBuilder<'a> {
344-
ArArchiveBuilder { sess, object_reader, src_archives: vec![], entries: vec![] }
347+
ArArchiveBuilder {
348+
sess,
349+
object_reader,
350+
src_archives: vec![],
351+
entries: vec![],
352+
keep_symbols: None,
353+
}
354+
}
355+
356+
pub fn set_keep_symbols(&mut self, keep: FxHashSet<String>) {
357+
self.keep_symbols = Some(keep);
345358
}
346359
}
347360

@@ -454,6 +467,10 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
454467
}
455468
}
456469
}
470+
471+
fn set_keep_symbols(&mut self, keep: FxHashSet<String>) {
472+
self.keep_symbols = Some(keep);
473+
}
457474
}
458475

459476
impl<'a> ArArchiveBuilder<'a> {
@@ -472,7 +489,7 @@ impl<'a> ArArchiveBuilder<'a> {
472489
let mut entries = Vec::new();
473490

474491
for (entry_name, entry) in self.entries {
475-
let data =
492+
let data: Box<dyn AsRef<[u8]>> =
476493
match entry {
477494
ArchiveEntry::FromArchive { archive_index, file_range } => {
478495
let src_archive = &self.src_archives[archive_index];
@@ -492,6 +509,16 @@ impl<'a> ArArchiveBuilder<'a> {
492509
},
493510
};
494511

512+
let data: Box<dyn AsRef<[u8]>> = if let Some(ref keep) = self.keep_symbols {
513+
if let Some(filtered) = elf_filter_global_symbols(data.as_ref().as_ref(), keep) {
514+
Box::new(filtered)
515+
} else {
516+
data
517+
}
518+
} else {
519+
data
520+
};
521+
495522
entries.push(NewArchiveMember {
496523
buf: data,
497524
object_reader: self.object_reader,
@@ -551,3 +578,142 @@ impl<'a> ArArchiveBuilder<'a> {
551578
fn io_error_context(context: &str, err: io::Error) -> io::Error {
552579
io::Error::new(io::ErrorKind::Other, format!("{context}: {err}"))
553580
}
581+
582+
/// For ELF object files, set `STV_HIDDEN` visibility on GLOBAL/WEAK symbols
583+
/// that are NOT in the `keep_symbols` set. Returns `Some(modified_data)` if
584+
/// any changes were made, `None` if the data is not an ELF object or no
585+
/// changes were needed.
586+
fn elf_filter_global_symbols(data: &[u8], keep_symbols: &FxHashSet<String>) -> Option<Vec<u8>> {
587+
use object::{Endianness, elf};
588+
589+
if data.len() < 16 || &data[0..4] != elf::ELFMAG {
590+
return None;
591+
}
592+
593+
match data[4] {
594+
elf::ELFCLASS64 => elf_filter_symbols_inner::<elf::FileHeader64<Endianness>>(
595+
data,
596+
keep_symbols,
597+
/* sym_entry_size= */ 24,
598+
/* st_info_offset= */ 4,
599+
/* st_other_offset= */ 5,
600+
),
601+
elf::ELFCLASS32 => elf_filter_symbols_inner::<elf::FileHeader32<Endianness>>(
602+
data,
603+
keep_symbols,
604+
/* sym_entry_size= */ 16,
605+
/* st_info_offset= */ 12,
606+
/* st_other_offset= */ 13,
607+
),
608+
_ => None,
609+
}
610+
}
611+
612+
fn elf_filter_symbols_inner<Elf: object::read::elf::FileHeader<Endian = object::Endianness>>(
613+
data: &[u8],
614+
keep_symbols: &FxHashSet<String>,
615+
sym_entry_size: usize,
616+
st_info_offset: usize,
617+
st_other_offset: usize,
618+
) -> Option<Vec<u8>>
619+
where
620+
u64: From<Elf::Word>,
621+
{
622+
use object::read::elf::SectionHeader;
623+
use object::{Endianness, elf};
624+
625+
let endian = match Elf::parse(data) {
626+
Ok(h) => match h.endian() {
627+
Ok(e) => e,
628+
Err(_) => return None,
629+
},
630+
Err(_) => return None,
631+
};
632+
633+
let header = Elf::parse(data).unwrap();
634+
let sections = match header.sections(endian, data) {
635+
Ok(s) => s,
636+
Err(_) => return None,
637+
};
638+
639+
let mut modified: Option<Vec<u8>> = None;
640+
641+
for section in sections.iter() {
642+
if section.sh_type(endian) != elf::SHT_SYMTAB {
643+
continue;
644+
}
645+
646+
let strtab_index = section.sh_link(endian) as usize;
647+
let strtab_section = match sections.section(object::SectionIndex(strtab_index)) {
648+
Ok(s) => s,
649+
Err(_) => continue,
650+
};
651+
let strtab_data = match strtab_section.data(endian, data) {
652+
Ok(d) => d,
653+
Err(_) => continue,
654+
};
655+
656+
if sym_entry_size == 0 {
657+
continue;
658+
}
659+
660+
let sym_offset = u64::from(section.sh_offset(endian)) as usize;
661+
let sym_size = u64::from(section.sh_size(endian)) as usize;
662+
let sym_count = sym_size / sym_entry_size;
663+
664+
let buf = modified.get_or_insert_with(|| data.to_vec());
665+
666+
for i in 1..sym_count {
667+
let off = sym_offset + i * sym_entry_size;
668+
if off + sym_entry_size > buf.len() {
669+
break;
670+
}
671+
672+
let st_info = buf[off + st_info_offset];
673+
let binding = st_info >> 4;
674+
675+
// Only process GLOBAL and WEAK symbols
676+
if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
677+
continue;
678+
}
679+
680+
let st_shndx_offset = st_other_offset + 1;
681+
let st_shndx_bytes: [u8; 2] =
682+
buf[off + st_shndx_offset..off + st_shndx_offset + 2].try_into().unwrap_or([0, 0]);
683+
let st_shndx = match endian {
684+
Endianness::Little => u16::from_le_bytes(st_shndx_bytes),
685+
Endianness::Big => u16::from_be_bytes(st_shndx_bytes),
686+
};
687+
if st_shndx == elf::SHN_UNDEF as u16 {
688+
continue;
689+
}
690+
691+
let st_name_bytes: [u8; 4] = buf[off..off + 4].try_into().unwrap_or([0; 4]);
692+
let st_name_off = match endian {
693+
Endianness::Little => u32::from_le_bytes(st_name_bytes),
694+
Endianness::Big => u32::from_be_bytes(st_name_bytes),
695+
} as usize;
696+
697+
if st_name_off >= strtab_data.len() {
698+
continue;
699+
}
700+
let name_end = strtab_data[st_name_off..]
701+
.iter()
702+
.position(|&b| b == 0)
703+
.unwrap_or(strtab_data.len() - st_name_off);
704+
let name = match std::str::from_utf8(&strtab_data[st_name_off..st_name_off + name_end])
705+
{
706+
Ok(s) => s,
707+
Err(_) => continue,
708+
};
709+
710+
if keep_symbols.contains(name) {
711+
continue;
712+
}
713+
714+
buf[off + st_other_offset] = elf::STV_HIDDEN;
715+
}
716+
}
717+
718+
modified
719+
}

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,24 @@ fn link_staticlib(
521521
sess.dcx().emit_fatal(e);
522522
}
523523

524+
if sess.opts.unstable_opts.staticlib_hide_internal_symbols {
525+
if !matches!(&*sess.target.archive_format, "gnu" | "bsd") {
526+
sess.dcx().emit_warn(errors::StaticlibHideInternalSymbolsUnsupported {
527+
archive_format: sess.target.archive_format.to_string(),
528+
});
529+
} else if let Some(symbols) =
530+
codegen_results.crate_info.exported_symbols.get(&CrateType::Staticlib)
531+
{
532+
use rustc_data_structures::fx::FxHashSet;
533+
let mut keep: FxHashSet<String> = symbols.iter().map(|(s, _)| s.clone()).collect();
534+
// rust_eh_personality cannot be mangled and hidden.
535+
// See discussions in https://github.com/rust-lang/rust/issues/104707
536+
// Keep it visible so that .eh_frame references from other object files
537+
keep.insert("rust_eh_personality".to_owned());
538+
ab.set_keep_symbols(keep);
539+
}
540+
}
541+
524542
ab.build(out_filename);
525543

526544
let crates = codegen_results.crate_info.used_crates.iter();

compiler/rustc_codegen_ssa/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,13 @@ pub struct UnknownArchiveKind<'a> {
733733
pub kind: &'a str,
734734
}
735735

736+
#[derive(Diagnostic)]
737+
#[diag(codegen_ssa_staticlib_hide_internal_symbols_unsupported)]
738+
#[warning]
739+
pub(crate) struct StaticlibHideInternalSymbolsUnsupported {
740+
pub archive_format: String,
741+
}
742+
736743
#[derive(Diagnostic)]
737744
#[diag(codegen_ssa_multiple_main_functions)]
738745
#[help]

compiler/rustc_interface/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,7 @@ fn test_unstable_options_tracking_hash() {
869869
tracked!(split_lto_unit, Some(true));
870870
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
871871
tracked!(stack_protector, StackProtector::All);
872+
tracked!(staticlib_hide_internal_symbols, true);
872873
tracked!(teach, true);
873874
tracked!(thinlto, Some(true));
874875
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
@@ -2595,6 +2595,14 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
25952595
let mut target_modifiers = BTreeMap::<OptionsTargetModifiers, String>::new();
25962596

25972597
let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers);
2598+
2599+
if unstable_opts.staticlib_hide_internal_symbols && !crate_types.contains(&CrateType::Staticlib)
2600+
{
2601+
early_dcx.early_fatal(
2602+
"-Zstaticlib-hide-internal-symbols can only be used with `--crate-type staticlib`",
2603+
);
2604+
}
2605+
25982606
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
25992607

26002608
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
@@ -2571,6 +2571,8 @@ written to standard error output)"),
25712571
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
25722572
staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED],
25732573
"allow staticlibs to have rust dylib dependencies"),
2574+
staticlib_hide_internal_symbols: bool = (false, parse_bool, [TRACKED],
2575+
"hide Rust internal symbols when building staticlibs"),
25742576
staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED],
25752577
"prefer dynamic linking to static linking for staticlibs (default: no)"),
25762578
strict_init_checks: bool = (false, parse_bool, [TRACKED],
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# `staticlib-hide-internal-symbols`
2+
3+
When building a `staticlib`, this option hides all Rust-internal symbols
4+
(except `rust_eh_personality`) by setting their ELF visibility to
5+
`STV_HIDDEN`.
6+
7+
This option can only be used with `--crate-type staticlib`. Using it with
8+
other crate types will result in a compilation error.
9+
10+
Currently only ELF targets are supported (Linux, BSD, etc.). On non-ELF
11+
targets (macOS, Windows), a warning is emitted and the flag has no effect.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#![crate_type = "staticlib"]
2+
3+
use std::collections::HashMap;
4+
use std::panic::{AssertUnwindSafe, catch_unwind};
5+
6+
#[no_mangle]
7+
pub extern "C" fn my_add(a: i32, b: i32) -> i32 {
8+
a + b
9+
}
10+
11+
#[no_mangle]
12+
pub extern "C" fn my_hash_lookup(key: u64) -> u64 {
13+
let mut map = HashMap::new();
14+
for i in 0..100u64 {
15+
map.insert(i, i.wrapping_mul(2654435761));
16+
}
17+
*map.get(&key).unwrap_or(&0)
18+
}
19+
20+
fn internal_helper() -> i32 {
21+
42
22+
}
23+
24+
#[no_mangle]
25+
pub extern "C" fn call_internal() -> i32 {
26+
internal_helper()
27+
}
28+
29+
#[no_mangle]
30+
pub extern "C" fn my_safe_div(a: i32, b: i32) -> i32 {
31+
match catch_unwind(AssertUnwindSafe(|| {
32+
if b == 0 {
33+
panic!("division by zero!");
34+
}
35+
a / b
36+
})) {
37+
Ok(result) => result,
38+
Err(_) => -1,
39+
}
40+
}

0 commit comments

Comments
 (0)