Skip to content

Commit 3e76129

Browse files
committed
Auto merge of #155338 - cezarbbb:staticlib-symbol-hygiene, r=<try>
`-Zstaticlib-hide-internal-symbols`: Hide non-exported internal symbols from staticlibs try-job: aarch64-*
2 parents e8e4541 + c7d4e98 commit 3e76129

13 files changed

Lines changed: 413 additions & 3 deletions

File tree

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;
@@ -318,6 +318,8 @@ pub trait ArchiveBuilder {
318318
) -> io::Result<()>;
319319

320320
fn build(self: Box<Self>, output: &Path) -> bool;
321+
322+
fn set_keep_symbols(&mut self, keep: FxHashSet<String>);
321323
}
322324

323325
pub struct ArArchiveBuilderBuilder;
@@ -337,6 +339,7 @@ pub struct ArArchiveBuilder<'a> {
337339
// Don't use an `HashMap` here, as the order is important. `lib.rmeta` needs
338340
// to be at the end of an archive in some cases for linkers to not get confused.
339341
entries: Vec<(Vec<u8>, ArchiveEntry)>,
342+
keep_symbols: Option<FxHashSet<String>>,
340343
}
341344

342345
#[derive(Debug)]
@@ -347,7 +350,17 @@ enum ArchiveEntry {
347350

348351
impl<'a> ArArchiveBuilder<'a> {
349352
pub fn new(sess: &'a Session, object_reader: &'static ObjectReader) -> ArArchiveBuilder<'a> {
350-
ArArchiveBuilder { sess, object_reader, src_archives: vec![], entries: vec![] }
353+
ArArchiveBuilder {
354+
sess,
355+
object_reader,
356+
src_archives: vec![],
357+
entries: vec![],
358+
keep_symbols: None,
359+
}
360+
}
361+
362+
pub fn set_keep_symbols(&mut self, keep: FxHashSet<String>) {
363+
self.keep_symbols = Some(keep);
351364
}
352365
}
353366

@@ -460,6 +473,10 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
460473
}
461474
}
462475
}
476+
477+
fn set_keep_symbols(&mut self, keep: FxHashSet<String>) {
478+
self.keep_symbols = Some(keep);
479+
}
463480
}
464481

465482
impl<'a> ArArchiveBuilder<'a> {
@@ -478,7 +495,7 @@ impl<'a> ArArchiveBuilder<'a> {
478495
let mut entries = Vec::new();
479496

480497
for (entry_name, entry) in self.entries {
481-
let data =
498+
let data: Box<dyn AsRef<[u8]>> =
482499
match entry {
483500
ArchiveEntry::FromArchive { archive_index, file_range } => {
484501
let src_archive = &self.src_archives[archive_index];
@@ -498,6 +515,16 @@ impl<'a> ArArchiveBuilder<'a> {
498515
},
499516
};
500517

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

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,18 @@ fn link_staticlib(
525525
sess.dcx().emit_fatal(e);
526526
}
527527

528+
if sess.opts.unstable_opts.staticlib_hide_internal_symbols {
529+
if !matches!(&*sess.target.archive_format, "gnu" | "bsd") {
530+
sess.dcx().emit_warn(errors::StaticlibHideInternalSymbolsUnsupported {
531+
archive_format: sess.target.archive_format.to_string(),
532+
});
533+
} else if let Some(symbols) = crate_info.exported_symbols.get(&CrateType::StaticLib) {
534+
use rustc_data_structures::fx::FxHashSet;
535+
let keep: FxHashSet<String> = symbols.iter().map(|(s, _)| s.clone()).collect();
536+
ab.set_keep_symbols(keep);
537+
}
538+
}
539+
528540
ab.build(out_filename);
529541

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

compiler/rustc_codegen_ssa/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,14 @@ pub(crate) struct UnknownArchiveKind<'a> {
675675
#[diag("linking static libraries is not supported for BPF")]
676676
pub(crate) struct BpfStaticlibNotSupported;
677677

678+
#[derive(Diagnostic)]
679+
#[diag(
680+
"-Zstaticlib-hide-internal-symbols only supports ELF archive formats (gnu/bsd), but the target uses `{$archive_format}`"
681+
)]
682+
pub(crate) struct StaticlibHideInternalSymbolsUnsupported {
683+
pub archive_format: String,
684+
}
685+
678686
#[derive(Diagnostic)]
679687
#[diag("entry symbol `main` declared multiple times")]
680688
#[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
@@ -2473,6 +2473,14 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
24732473
let mut collected_options = Default::default();
24742474

24752475
let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut collected_options);
2476+
2477+
if unstable_opts.staticlib_hide_internal_symbols && !crate_types.contains(&CrateType::StaticLib)
2478+
{
2479+
early_dcx.early_fatal(
2480+
"-Zstaticlib-hide-internal-symbols can only be used with `--crate-type staticlib`",
2481+
);
2482+
}
2483+
24762484
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
24772485

24782486
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
@@ -2838,6 +2838,8 @@ written to standard error output)"),
28382838
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
28392839
staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED],
28402840
"allow staticlibs to have rust dylib dependencies"),
2841+
staticlib_hide_internal_symbols: bool = (false, parse_bool, [TRACKED],
2842+
"hide Rust internal symbols when building staticlibs"),
28412843
staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED],
28422844
"prefer dynamic linking to static linking for staticlibs (default: no)"),
28432845
strict_init_checks: bool = (false, parse_bool, [TRACKED],
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# `staticlib-hide-internal-symbols`
2+
3+
When building a `staticlib`, this option hides all Rust-internal symbols
4+
by setting their ELF visibility to `STV_HIDDEN`.
5+
6+
This option can only be used with `--crate-type staticlib`. Using it with
7+
other crate types will result in a compilation error.
8+
9+
Currently only ELF targets are supported (Linux, BSD, etc.). On non-ELF
10+
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+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
extern int my_add(int a, int b);
2+
extern unsigned long my_hash_lookup(unsigned long key);
3+
extern int call_internal(void);
4+
extern int my_safe_div(int a, int b);
5+
6+
int main() {
7+
if (my_add(10, 20) != 30)
8+
return 1;
9+
if (my_hash_lookup(5) != 5UL * 2654435761UL)
10+
return 1;
11+
if (call_internal() != 42)
12+
return 1;
13+
if (my_safe_div(100, 5) != 20)
14+
return 1;
15+
if (my_safe_div(100, 0) != -1)
16+
return 1;
17+
return 0;
18+
}

0 commit comments

Comments
 (0)