1- use std:: env;
21use std:: error:: Error ;
32use std:: ffi:: OsString ;
43use std:: fs:: { self , File } ;
54use std:: io:: { self , BufWriter , Write } ;
65use std:: path:: { Path , PathBuf } ;
6+ use std:: { env, mem} ;
77
88use ar_archive_writer:: {
99 ArchiveKind , COFFShortExport , MachineTypes , NewArchiveMember , write_archive_to_stream,
1010} ;
1111pub use ar_archive_writer:: { DEFAULT_OBJECT_READER , ObjectReader } ;
1212use object:: read:: archive:: { ArchiveFile , ArchiveKind as ObjectArchiveKind } ;
13- use object:: read:: macho:: FatArch ;
14- use rustc_data_structures:: fx:: FxIndexSet ;
13+ use object:: read:: elf:: Sym as _;
14+ use object:: read:: macho:: { FatArch , Nlist } ;
15+ use object:: { Endianness , elf, macho} ;
16+ use rustc_data_structures:: fx:: { FxHashSet , FxIndexSet } ;
1517use rustc_data_structures:: memmap:: Mmap ;
1618use rustc_fs_util:: TempDirBuilder ;
1719use rustc_metadata:: EncodedMetadata ;
@@ -318,7 +320,7 @@ pub trait ArchiveBuilder {
318320
319321 fn add_archive ( & mut self , archive : & Path , kind : AddArchiveKind < ' _ > ) -> io:: Result < ( ) > ;
320322
321- fn build ( self : Box < Self > , output : & Path ) -> bool ;
323+ fn build ( self : Box < Self > , output : & Path , exported_symbols : Option < FxHashSet < String > > ) -> bool ;
322324}
323325
324326fn target_archive_format_to_object_kind ( format : & str ) -> Option < ObjectArchiveKind > {
@@ -401,7 +403,6 @@ enum ArchiveEntrySource {
401403#[ derive( Debug ) ]
402404struct ArchiveEntry {
403405 source : ArchiveEntrySource ,
404- #[ expect( dead_code) ] // used in #155338
405406 kind : ArchiveEntryKind ,
406407}
407408
@@ -534,9 +535,9 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
534535
535536 /// Combine the provided files, rlibs, and native libraries into a single
536537 /// `Archive`.
537- fn build ( self : Box < Self > , output : & Path ) -> bool {
538+ fn build ( self : Box < Self > , output : & Path , exported_symbols : Option < FxHashSet < String > > ) -> bool {
538539 let sess = self . sess ;
539- match self . build_inner ( output) {
540+ match self . build_inner ( output, exported_symbols ) {
540541 Ok ( any_members) => any_members,
541542 Err ( error) => {
542543 sess. dcx ( ) . emit_fatal ( ArchiveBuildFailure { path : output. to_owned ( ) , error } )
@@ -546,7 +547,11 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
546547}
547548
548549impl < ' a > ArArchiveBuilder < ' a > {
549- fn build_inner ( self , output : & Path ) -> io:: Result < bool > {
550+ fn build_inner (
551+ self ,
552+ output : & Path ,
553+ exported_symbols : Option < FxHashSet < String > > ,
554+ ) -> io:: Result < bool > {
550555 let archive_kind = match & * self . sess . target . archive_format {
551556 "gnu" => ArchiveKind :: Gnu ,
552557 "bsd" => ArchiveKind :: Bsd ,
@@ -561,40 +566,51 @@ impl<'a> ArArchiveBuilder<'a> {
561566 let mut entries = Vec :: new ( ) ;
562567
563568 for ( entry_name, entry) in self . entries {
564- let data =
565- match entry. source {
566- ArchiveEntrySource :: Archive { archive_index, file_range } => {
567- let src_archive = & self . src_archives [ archive_index] ;
568- let archive_data = & src_archive. 1 ;
569- let start = file_range. 0 as usize ;
570- let end = start + file_range. 1 as usize ;
571- let Some ( data) = archive_data. get ( start..end) else {
572- return Err ( io_error_context (
573- "invalid archive member" ,
574- io:: Error :: new (
575- io:: ErrorKind :: InvalidData ,
576- format ! (
577- "archive member at offset {start} with size {} \
569+ let data: Box < dyn AsRef < [ u8 ] > > = match entry. source {
570+ ArchiveEntrySource :: Archive { archive_index, file_range } => {
571+ let src_archive = & self . src_archives [ archive_index] ;
572+ let archive_data = & src_archive. 1 ;
573+ let start = file_range. 0 as usize ;
574+ let end = start + file_range. 1 as usize ;
575+ let Some ( data) = archive_data. get ( start..end) else {
576+ return Err ( io_error_context (
577+ "invalid archive member" ,
578+ io:: Error :: new (
579+ io:: ErrorKind :: InvalidData ,
580+ format ! (
581+ "archive member at offset {start} with size {} \
578582 exceeds archive size {} in `{}`",
579- file_range. 1 ,
580- archive_data. len( ) ,
581- src_archive. 0 . display( ) ,
582- ) ,
583+ file_range. 1 ,
584+ archive_data. len( ) ,
585+ src_archive. 0 . display( ) ,
583586 ) ,
584- ) ) ;
585- } ;
586-
587- Box :: new ( data) as Box < dyn AsRef < [ u8 ] > >
587+ ) ,
588+ ) ) ;
589+ } ;
590+
591+ if entry. kind == ArchiveEntryKind :: RustObj
592+ && let Some ( exported) = & exported_symbols
593+ {
594+ Box :: new ( apply_hide ( data, exported) )
595+ } else {
596+ Box :: new ( data)
588597 }
589- ArchiveEntrySource :: File ( file) => unsafe {
590- Box :: new (
591- Mmap :: map ( File :: open ( file) . map_err ( |err| {
592- io_error_context ( "failed to open object file" , err)
593- } ) ?)
594- . map_err ( |err| io_error_context ( "failed to map object file" , err) ) ?,
595- ) as Box < dyn AsRef < [ u8 ] > >
596- } ,
597- } ;
598+ }
599+ ArchiveEntrySource :: File ( file) => unsafe {
600+ let mmap = Mmap :: map (
601+ File :: open ( file)
602+ . map_err ( |err| io_error_context ( "failed to open object file" , err) ) ?,
603+ )
604+ . 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) )
609+ } else {
610+ Box :: new ( mmap) as Box < dyn AsRef < [ u8 ] > >
611+ }
612+ } ,
613+ } ;
598614
599615 entries. push ( NewArchiveMember {
600616 buf : data,
@@ -655,3 +671,152 @@ impl<'a> ArArchiveBuilder<'a> {
655671fn io_error_context ( context : & str , err : io:: Error ) -> io:: Error {
656672 io:: Error :: new ( io:: ErrorKind :: Other , format ! ( "{context}: {err}" ) )
657673}
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+ if !exported. contains ( name) {
779+ let nlist_addr = nlist as * const Mach :: Nlist as usize ;
780+ let offset = nlist_addr - data_ptr + n_type_offset;
781+ patches. push ( Patch { offset, value : nlist. n_type ( ) | macho:: N_PEXT } ) ;
782+ }
783+ }
784+
785+ Some ( patches)
786+ }
787+
788+ // ---------------------------------------------------------------------------
789+ // Unified dispatch: top-level detection via `object::File::parse`
790+ // ---------------------------------------------------------------------------
791+
792+ fn hide_patches ( data : & [ u8 ] , exported : & FxHashSet < String > ) -> Option < Vec < Patch > > {
793+ let file = object:: File :: parse ( data) . ok ( ) ?;
794+ match file {
795+ object:: File :: Elf64 ( _) => elf_hide_patches_impl :: < elf:: FileHeader64 < Endianness > > (
796+ data,
797+ mem:: offset_of!( elf:: Sym64 <Endianness >, st_other) ,
798+ exported,
799+ ) ,
800+ object:: File :: Elf32 ( _) => elf_hide_patches_impl :: < elf:: FileHeader32 < Endianness > > (
801+ data,
802+ mem:: offset_of!( elf:: Sym32 <Endianness >, st_other) ,
803+ exported,
804+ ) ,
805+ object:: File :: MachO64 ( _) => macho_hide_patches_impl :: < macho:: MachHeader64 < Endianness > > (
806+ data,
807+ mem:: offset_of!( macho:: Nlist64 <Endianness >, n_type) ,
808+ exported,
809+ ) ,
810+ object:: File :: MachO32 ( _) => macho_hide_patches_impl :: < macho:: MachHeader32 < Endianness > > (
811+ data,
812+ mem:: offset_of!( macho:: Nlist32 <Endianness >, n_type) ,
813+ exported,
814+ ) ,
815+ _ => None ,
816+ }
817+ }
818+
819+ fn apply_hide ( data : & [ u8 ] , exported : & FxHashSet < String > ) -> Vec < u8 > {
820+ let patches = hide_patches ( data, exported) . unwrap_or_default ( ) ;
821+ apply_patches ( data, & patches)
822+ }
0 commit comments