Skip to content

Commit 0762f85

Browse files
committed
add -Zstaticlib-rename-internal-symbols
1 parent 83b3bfc commit 0762f85

21 files changed

Lines changed: 1102 additions & 174 deletions

File tree

compiler/rustc_codegen_ssa/src/back/archive.rs

Lines changed: 79 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1+
use std::env;
12
use std::error::Error;
23
use std::ffi::OsString;
34
use std::fs::{self, File};
45
use std::io::{self, BufWriter, Write};
56
use std::path::{Path, PathBuf};
6-
use std::{env, mem};
77

88
use ar_archive_writer::{
99
ArchiveKind, COFFShortExport, MachineTypes, NewArchiveMember, write_archive_to_stream,
1010
};
1111
pub use ar_archive_writer::{DEFAULT_OBJECT_READER, ObjectReader};
1212
use object::read::archive::{ArchiveFile, ArchiveKind as ObjectArchiveKind};
13-
use object::read::elf::Sym as _;
14-
use object::read::macho::{FatArch, Nlist};
15-
use object::{Endianness, elf, macho};
13+
use object::read::macho::FatArch;
1614
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
1715
use rustc_data_structures::memmap::Mmap;
1816
use rustc_fs_util::TempDirBuilder;
@@ -24,6 +22,7 @@ use tracing::trace;
2422

2523
use super::metadata::{create_compressed_metadata_file, search_for_section};
2624
use super::rmeta_link;
25+
use super::symbol_edit::{apply_hide, collect_internal_set, rename_impl};
2726
use crate::common;
2827
// Public for ArchiveBuilderBuilder::extract_bundled_libs
2928
pub use crate::errors::ExtractBundledLibsError;
@@ -320,7 +319,13 @@ pub trait ArchiveBuilder {
320319

321320
fn add_archive(&mut self, archive: &Path, kind: AddArchiveKind<'_>) -> io::Result<()>;
322321

323-
fn build(self: Box<Self>, output: &Path, exported_symbols: Option<FxHashSet<String>>) -> bool;
322+
fn build(
323+
self: Box<Self>,
324+
output: &Path,
325+
exported_symbols: Option<FxHashSet<String>>,
326+
rename_suffix: Option<String>,
327+
hide: bool,
328+
) -> bool;
324329
}
325330

326331
fn target_archive_format_to_object_kind(format: &str) -> Option<ObjectArchiveKind> {
@@ -535,9 +540,15 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
535540

536541
/// Combine the provided files, rlibs, and native libraries into a single
537542
/// `Archive`.
538-
fn build(self: Box<Self>, output: &Path, exported_symbols: Option<FxHashSet<String>>) -> bool {
543+
fn build(
544+
self: Box<Self>,
545+
output: &Path,
546+
exported_symbols: Option<FxHashSet<String>>,
547+
rename_suffix: Option<String>,
548+
hide: bool,
549+
) -> bool {
539550
let sess = self.sess;
540-
match self.build_inner(output, exported_symbols) {
551+
match self.build_inner(output, exported_symbols, rename_suffix, hide) {
541552
Ok(any_members) => any_members,
542553
Err(error) => {
543554
sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error })
@@ -551,6 +562,8 @@ impl<'a> ArArchiveBuilder<'a> {
551562
self,
552563
output: &Path,
553564
exported_symbols: Option<FxHashSet<String>>,
565+
rename_suffix: Option<String>,
566+
hide: bool,
554567
) -> io::Result<bool> {
555568
let archive_kind = match &*self.sess.target.archive_format {
556569
"gnu" => ArchiveKind::Gnu,
@@ -563,6 +576,37 @@ impl<'a> ArArchiveBuilder<'a> {
563576
}
564577
};
565578

579+
// Rename requires a two-pass approach: first collect all internal symbols
580+
// across all .o files to ensure consistent renaming, then apply.
581+
let global_rename_set = if let Some(suffix) = &rename_suffix
582+
&& let Some(exported) = &exported_symbols
583+
{
584+
let mut all_names = FxHashSet::default();
585+
for (_, entry) in &self.entries {
586+
if entry.kind != ArchiveEntryKind::RustObj {
587+
continue;
588+
}
589+
match &entry.source {
590+
ArchiveEntrySource::Archive { archive_index, file_range } => {
591+
let src_archive = &self.src_archives[*archive_index];
592+
let start = file_range.0 as usize;
593+
let end = start + file_range.1 as usize;
594+
if let Some(data) = src_archive.1.get(start..end) {
595+
collect_internal_set(data, exported, &mut all_names);
596+
}
597+
}
598+
ArchiveEntrySource::File(file) => {
599+
if let Ok(data) = std::fs::read(file) {
600+
collect_internal_set(&data, exported, &mut all_names);
601+
}
602+
}
603+
}
604+
}
605+
Some((all_names, suffix.as_str()))
606+
} else {
607+
None
608+
};
609+
566610
let mut entries = Vec::new();
567611

568612
for (entry_name, entry) in self.entries {
@@ -588,10 +632,18 @@ impl<'a> ArArchiveBuilder<'a> {
588632
));
589633
};
590634

591-
if entry.kind == ArchiveEntryKind::RustObj
592-
&& let Some(exported) = &exported_symbols
593-
{
594-
Box::new(apply_hide(data, exported))
635+
if entry.kind == ArchiveEntryKind::RustObj {
636+
let renamed = global_rename_set
637+
.as_ref()
638+
.and_then(|(rename_set, suffix)| rename_impl(data, rename_set, suffix));
639+
if hide && let Some(exported) = &exported_symbols {
640+
let src = renamed.as_deref().unwrap_or(data);
641+
Box::new(apply_hide(src, exported))
642+
} else if let Some(renamed) = renamed {
643+
Box::new(renamed)
644+
} else {
645+
Box::new(data)
646+
}
595647
} else {
596648
Box::new(data)
597649
}
@@ -602,10 +654,22 @@ impl<'a> ArArchiveBuilder<'a> {
602654
.map_err(|err| io_error_context("failed to open object file", err))?,
603655
)
604656
.map_err(|err| io_error_context("failed to map object file", err))?;
605-
if entry.kind == ArchiveEntryKind::RustObj
606-
&& let Some(exported) = &exported_symbols
607-
{
608-
Box::new(apply_hide(&mmap, exported))
657+
if entry.kind == ArchiveEntryKind::RustObj {
658+
let renamed =
659+
global_rename_set.as_ref().and_then(|(rename_set, suffix)| {
660+
rename_impl(&mmap, rename_set, suffix)
661+
});
662+
if hide && let Some(exported) = &exported_symbols {
663+
let src = match &renamed {
664+
Some(v) => v.as_slice(),
665+
None => &*mmap,
666+
};
667+
Box::new(apply_hide(src, exported))
668+
} else if let Some(renamed) = renamed {
669+
Box::new(renamed)
670+
} else {
671+
Box::new(mmap) as Box<dyn AsRef<[u8]>>
672+
}
609673
} else {
610674
Box::new(mmap) as Box<dyn AsRef<[u8]>>
611675
}
@@ -671,153 +735,3 @@ impl<'a> ArArchiveBuilder<'a> {
671735
fn io_error_context(context: &str, err: io::Error) -> io::Error {
672736
io::Error::new(io::ErrorKind::Other, format!("{context}: {err}"))
673737
}
674-
675-
// We use the `object` crate for the read-only pass over ELF/Mach-O object files
676-
// because its `Sym`/`Nlist` traits provide clean access to symbol properties without
677-
// manual byte parsing. However, `object` does not expose mutable views into the data,
678-
// so we cannot use it to modify symbol fields in place. Instead, the read-only pass
679-
// collects byte-level patches (offset + new value), and the write pass
680-
// (`apply_patches`) applies them to a copy of the byte buffer without any ELF/Mach-O
681-
// parsing — similar to how linker relocations work.
682-
683-
/// A byte-level patch collected in the read-only pass and applied in the write pass.
684-
struct Patch {
685-
offset: usize,
686-
value: u8,
687-
}
688-
689-
/// Apply a list of byte patches to `data`, returning the (possibly modified) bytes.
690-
fn apply_patches(data: &[u8], patches: &[Patch]) -> Vec<u8> {
691-
let mut buf = data.to_vec();
692-
for p in patches {
693-
buf[p.offset] = p.value;
694-
}
695-
buf
696-
}
697-
698-
// ---------------------------------------------------------------------------
699-
// ELF hide – read-only pass uses `object` crate, write pass uses `Patch` list
700-
// ---------------------------------------------------------------------------
701-
702-
fn elf_hide_patches_impl<'data, Elf: object::read::elf::FileHeader<Endian = Endianness>>(
703-
data: &'data [u8],
704-
st_other_offset: usize,
705-
exported: &FxHashSet<String>,
706-
) -> Option<Vec<Patch>>
707-
where
708-
u64: From<Elf::Word>,
709-
{
710-
let header = Elf::parse(data).ok()?;
711-
let endian = header.endian().ok()?;
712-
let sections = header.sections(endian, data).ok()?;
713-
let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?;
714-
715-
let data_ptr = data.as_ptr() as usize;
716-
let strings = symtab.strings();
717-
let mut patches = Vec::new();
718-
719-
for sym in symtab.iter() {
720-
let binding = sym.st_bind();
721-
if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
722-
continue;
723-
}
724-
if sym.is_undefined(endian) {
725-
continue;
726-
}
727-
let Ok(name_bytes) = sym.name(endian, strings) else { continue };
728-
let Ok(name) = str::from_utf8(name_bytes) else { continue };
729-
if !exported.contains(name) {
730-
let sym_addr = sym as *const Elf::Sym as usize;
731-
let offset = sym_addr - data_ptr + st_other_offset;
732-
let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN;
733-
patches.push(Patch { offset, value: new_vis });
734-
}
735-
}
736-
737-
Some(patches)
738-
}
739-
740-
// ---------------------------------------------------------------------------
741-
// Mach-O hide – same architecture: read-only pass via `object`, write via patches
742-
// ---------------------------------------------------------------------------
743-
744-
fn macho_hide_patches_impl<'data, Mach: object::read::macho::MachHeader<Endian = Endianness>>(
745-
data: &'data [u8],
746-
n_type_offset: usize,
747-
exported: &FxHashSet<String>,
748-
) -> Option<Vec<Patch>> {
749-
let header = Mach::parse(data, 0).ok()?;
750-
let endian = header.endian().ok()?;
751-
let mut commands = header.load_commands(endian, data, 0).ok()?;
752-
753-
let symtab_cmd = loop {
754-
let cmd = commands.next().ok()??;
755-
if let Some(st) = cmd.symtab().ok().flatten() {
756-
break st;
757-
}
758-
};
759-
let symtab: object::read::macho::SymbolTable<'_, Mach, &_> =
760-
symtab_cmd.symbols(endian, data).ok()?;
761-
762-
let data_ptr = data.as_ptr() as usize;
763-
let strings = symtab.strings();
764-
let mut patches = Vec::new();
765-
766-
for nlist in symtab.iter() {
767-
if nlist.is_stab() {
768-
continue;
769-
}
770-
if nlist.is_undefined() {
771-
continue;
772-
}
773-
if nlist.n_type() & macho::N_EXT == 0 {
774-
continue;
775-
}
776-
let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
777-
let Ok(name) = str::from_utf8(name_bytes) else { continue };
778-
let name = name.strip_prefix('_').unwrap_or(name);
779-
if !exported.contains(name) {
780-
let nlist_addr = nlist as *const Mach::Nlist as usize;
781-
let offset = nlist_addr - data_ptr + n_type_offset;
782-
patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT });
783-
}
784-
}
785-
786-
Some(patches)
787-
}
788-
789-
// ---------------------------------------------------------------------------
790-
// Unified dispatch: top-level detection via `object::File::parse`
791-
// ---------------------------------------------------------------------------
792-
793-
fn hide_patches(data: &[u8], exported: &FxHashSet<String>) -> Option<Vec<Patch>> {
794-
let file = object::File::parse(data).ok()?;
795-
match file {
796-
object::File::Elf64(_) => elf_hide_patches_impl::<elf::FileHeader64<Endianness>>(
797-
data,
798-
mem::offset_of!(elf::Sym64<Endianness>, st_other),
799-
exported,
800-
),
801-
object::File::Elf32(_) => elf_hide_patches_impl::<elf::FileHeader32<Endianness>>(
802-
data,
803-
mem::offset_of!(elf::Sym32<Endianness>, st_other),
804-
exported,
805-
),
806-
object::File::MachO64(_) => macho_hide_patches_impl::<macho::MachHeader64<Endianness>>(
807-
data,
808-
mem::offset_of!(macho::Nlist64<Endianness>, n_type),
809-
exported,
810-
),
811-
object::File::MachO32(_) => macho_hide_patches_impl::<macho::MachHeader32<Endianness>>(
812-
data,
813-
mem::offset_of!(macho::Nlist32<Endianness>, n_type),
814-
exported,
815-
),
816-
_ => None,
817-
}
818-
}
819-
820-
fn apply_hide(data: &[u8], exported: &FxHashSet<String>) -> Vec<u8> {
821-
let patches = hide_patches(data, exported).unwrap_or_default();
822-
apply_patches(data, &patches)
823-
}

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ pub fn link_binary(
131131
RlibFlavor::Normal,
132132
&path,
133133
)
134-
.build(&out_filename, None);
134+
.build(&out_filename, None, None, false);
135135
}
136136
CrateType::StaticLib => {
137137
link_staticlib(
@@ -566,11 +566,21 @@ fn link_staticlib(
566566
sess.dcx().emit_fatal(e);
567567
}
568568

569-
let exported_symbols = if sess.opts.unstable_opts.staticlib_hide_internal_symbols {
569+
let hide = sess.opts.unstable_opts.staticlib_hide_internal_symbols;
570+
let rename = sess.opts.unstable_opts.staticlib_rename_internal_symbols;
571+
572+
let exported_symbols = if hide || rename {
570573
if !matches!(sess.target.binary_format, BinaryFormat::Elf | BinaryFormat::MachO) {
571-
sess.dcx().emit_warn(errors::StaticlibHideInternalSymbolsUnsupported {
572-
binary_format: sess.target.archive_format.to_string(),
573-
});
574+
if hide {
575+
sess.dcx().emit_warn(errors::StaticlibHideInternalSymbolsUnsupported {
576+
binary_format: sess.target.archive_format.to_string(),
577+
});
578+
}
579+
if rename {
580+
sess.dcx().emit_warn(errors::StaticlibRenameInternalSymbolsUnsupported {
581+
binary_format: sess.target.archive_format.to_string(),
582+
});
583+
}
574584
None
575585
} else {
576586
crate_info
@@ -581,7 +591,15 @@ fn link_staticlib(
581591
} else {
582592
None
583593
};
584-
ab.build(out_filename, exported_symbols);
594+
595+
let rename_suffix =
596+
if rename && matches!(sess.target.binary_format, BinaryFormat::Elf | BinaryFormat::MachO) {
597+
Some(crate_info.rename_suffix.clone())
598+
} else {
599+
None
600+
};
601+
602+
ab.build(out_filename, exported_symbols, rename_suffix, hide);
585603

586604
let crates = crate_info.used_crates.iter();
587605

@@ -1280,7 +1298,7 @@ fn link_natively(
12801298
if should_archive {
12811299
let mut ab = archive_builder_builder.new_archive_builder(sess);
12821300
ab.add_file(temp_filename, ArchiveEntryKind::Other);
1283-
ab.build(out_filename, None);
1301+
ab.build(out_filename, None, None, false);
12841302
}
12851303
}
12861304

@@ -3280,7 +3298,7 @@ fn add_static_crate(
32803298
sess.dcx()
32813299
.emit_fatal(errors::RlibArchiveBuildFailure { path: cratepath.clone(), error });
32823300
}
3283-
if archive.build(&dst, None) {
3301+
if archive.build(&dst, None, None, false) {
32843302
link_upstream(&dst);
32853303
}
32863304
});

compiler/rustc_codegen_ssa/src/back/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod lto;
1111
pub mod metadata;
1212
pub mod rmeta_link;
1313
pub(crate) mod rpath;
14+
mod symbol_edit;
1415
pub mod symbol_export;
1516
pub mod write;
1617

0 commit comments

Comments
 (0)