Skip to content

Commit f083020

Browse files
authored
Rollup merge of #157691 - cezarbbb:staticlib-move-symbol-edit, r=petrochenkov
Move symbol hiding code to a new file Move the symbol visibility editing code (`apply_hide` and helpers) from `archive.rs` into a new `symbol_edit.rs` module. This is a pure code move with no functional changes — extracted to reduce the size of `archive.rs` and to provide a natural home for future symbol editing logic (e.g. renaming, #156950). Requested in #156950 (comment). r? @petrochenkov
2 parents 2f3c20f + 8ee1dfa commit f083020

3 files changed

Lines changed: 160 additions & 154 deletions

File tree

compiler/rustc_codegen_ssa/src/back/archive.rs

Lines changed: 3 additions & 154 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;
2726
use crate::common;
2827
// Public for ArchiveBuilderBuilder::extract_bundled_libs
2928
pub use crate::errors::ExtractBundledLibsError;
@@ -671,153 +670,3 @@ impl<'a> ArArchiveBuilder<'a> {
671670
fn io_error_context(context: &str, err: io::Error) -> io::Error {
672671
io::Error::new(io::ErrorKind::Other, format!("{context}: {err}"))
673672
}
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/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

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// We use the `object` crate for the read-only pass over ELF/Mach-O object files
2+
// because its `Sym`/`Nlist` traits provide clean access to symbol properties without
3+
// manual byte parsing. However, `object` does not expose mutable views into the data,
4+
// so we cannot use it to modify symbol fields in place. Instead, the read-only pass
5+
// collects byte-level patches (offset + new value), and the write pass
6+
// (`apply_patches`) applies them to a copy of the byte buffer without any ELF/Mach-O
7+
// parsing — similar to how linker relocations work.
8+
9+
use std::mem;
10+
11+
use object::read::elf::Sym as _;
12+
use object::read::macho::Nlist;
13+
use object::{Endianness, elf, macho};
14+
use rustc_data_structures::fx::FxHashSet;
15+
16+
/// A byte-level patch collected in the read-only pass and applied in the write pass.
17+
struct Patch {
18+
offset: usize,
19+
value: u8,
20+
}
21+
22+
/// Apply a list of byte patches to `data`, returning the (possibly modified) bytes.
23+
fn apply_patches(data: &[u8], patches: &[Patch]) -> Vec<u8> {
24+
let mut buf = data.to_vec();
25+
for p in patches {
26+
buf[p.offset] = p.value;
27+
}
28+
buf
29+
}
30+
31+
// ---------------------------------------------------------------------------
32+
// ELF hide – read-only pass uses `object` crate, write pass uses `Patch` list
33+
// ---------------------------------------------------------------------------
34+
35+
fn elf_hide_patches_impl<'data, Elf: object::read::elf::FileHeader<Endian = Endianness>>(
36+
data: &'data [u8],
37+
st_other_offset: usize,
38+
exported: &FxHashSet<String>,
39+
) -> Option<Vec<Patch>>
40+
where
41+
u64: From<Elf::Word>,
42+
{
43+
let header = Elf::parse(data).ok()?;
44+
let endian = header.endian().ok()?;
45+
let sections = header.sections(endian, data).ok()?;
46+
let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?;
47+
48+
let data_ptr = data.as_ptr() as usize;
49+
let strings = symtab.strings();
50+
let mut patches = Vec::new();
51+
52+
for sym in symtab.iter() {
53+
let binding = sym.st_bind();
54+
if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
55+
continue;
56+
}
57+
if sym.is_undefined(endian) {
58+
continue;
59+
}
60+
let Ok(name_bytes) = sym.name(endian, strings) else { continue };
61+
let Ok(name) = str::from_utf8(name_bytes) else { continue };
62+
if !exported.contains(name) {
63+
let sym_addr = sym as *const Elf::Sym as usize;
64+
let offset = sym_addr - data_ptr + st_other_offset;
65+
let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN;
66+
patches.push(Patch { offset, value: new_vis });
67+
}
68+
}
69+
70+
Some(patches)
71+
}
72+
73+
// ---------------------------------------------------------------------------
74+
// Mach-O hide – same architecture: read-only pass via `object`, write via patches
75+
// ---------------------------------------------------------------------------
76+
77+
fn macho_hide_patches_impl<'data, Mach: object::read::macho::MachHeader<Endian = Endianness>>(
78+
data: &'data [u8],
79+
n_type_offset: usize,
80+
exported: &FxHashSet<String>,
81+
) -> Option<Vec<Patch>> {
82+
let header = Mach::parse(data, 0).ok()?;
83+
let endian = header.endian().ok()?;
84+
let mut commands = header.load_commands(endian, data, 0).ok()?;
85+
86+
let symtab_cmd = loop {
87+
let cmd = commands.next().ok()??;
88+
if let Some(st) = cmd.symtab().ok().flatten() {
89+
break st;
90+
}
91+
};
92+
let symtab: object::read::macho::SymbolTable<'_, Mach, &_> =
93+
symtab_cmd.symbols(endian, data).ok()?;
94+
95+
let data_ptr = data.as_ptr() as usize;
96+
let strings = symtab.strings();
97+
let mut patches = Vec::new();
98+
99+
for nlist in symtab.iter() {
100+
if nlist.is_stab() {
101+
continue;
102+
}
103+
if nlist.is_undefined() {
104+
continue;
105+
}
106+
if nlist.n_type() & macho::N_EXT == 0 {
107+
continue;
108+
}
109+
let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
110+
let Ok(name) = str::from_utf8(name_bytes) else { continue };
111+
let name = name.strip_prefix('_').unwrap_or(name);
112+
if !exported.contains(name) {
113+
let nlist_addr = nlist as *const Mach::Nlist as usize;
114+
let offset = nlist_addr - data_ptr + n_type_offset;
115+
patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT });
116+
}
117+
}
118+
119+
Some(patches)
120+
}
121+
122+
// ---------------------------------------------------------------------------
123+
// Unified dispatch: top-level detection via `object::File::parse`
124+
// ---------------------------------------------------------------------------
125+
126+
fn hide_patches(data: &[u8], exported: &FxHashSet<String>) -> Option<Vec<Patch>> {
127+
let file = object::File::parse(data).ok()?;
128+
match file {
129+
object::File::Elf64(_) => elf_hide_patches_impl::<elf::FileHeader64<Endianness>>(
130+
data,
131+
mem::offset_of!(elf::Sym64<Endianness>, st_other),
132+
exported,
133+
),
134+
object::File::Elf32(_) => elf_hide_patches_impl::<elf::FileHeader32<Endianness>>(
135+
data,
136+
mem::offset_of!(elf::Sym32<Endianness>, st_other),
137+
exported,
138+
),
139+
object::File::MachO64(_) => macho_hide_patches_impl::<macho::MachHeader64<Endianness>>(
140+
data,
141+
mem::offset_of!(macho::Nlist64<Endianness>, n_type),
142+
exported,
143+
),
144+
object::File::MachO32(_) => macho_hide_patches_impl::<macho::MachHeader32<Endianness>>(
145+
data,
146+
mem::offset_of!(macho::Nlist32<Endianness>, n_type),
147+
exported,
148+
),
149+
_ => None,
150+
}
151+
}
152+
153+
pub(super) fn apply_hide(data: &[u8], exported: &FxHashSet<String>) -> Vec<u8> {
154+
let patches = hide_patches(data, exported).unwrap_or_default();
155+
apply_patches(data, &patches)
156+
}

0 commit comments

Comments
 (0)