mirror of
https://github.com/rust-lang/rust.git
synced 2026-07-20 22:56:57 -04:00
add -Zstaticlib-hide-internal-symbols
This commit is contained in:
@@ -1,17 +1,19 @@
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufWriter, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{env, mem};
|
||||
|
||||
use ar_archive_writer::{
|
||||
ArchiveKind, COFFShortExport, MachineTypes, NewArchiveMember, write_archive_to_stream,
|
||||
};
|
||||
pub use ar_archive_writer::{DEFAULT_OBJECT_READER, ObjectReader};
|
||||
use object::read::archive::{ArchiveFile, ArchiveKind as ObjectArchiveKind};
|
||||
use object::read::macho::FatArch;
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
use object::read::elf::Sym as _;
|
||||
use object::read::macho::{FatArch, Nlist};
|
||||
use object::{Endianness, elf, macho};
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
|
||||
use rustc_data_structures::memmap::Mmap;
|
||||
use rustc_fs_util::TempDirBuilder;
|
||||
use rustc_metadata::EncodedMetadata;
|
||||
@@ -318,7 +320,7 @@ pub trait ArchiveBuilder {
|
||||
|
||||
fn add_archive(&mut self, archive: &Path, kind: AddArchiveKind<'_>) -> io::Result<()>;
|
||||
|
||||
fn build(self: Box<Self>, output: &Path) -> bool;
|
||||
fn build(self: Box<Self>, output: &Path, exported_symbols: Option<FxHashSet<String>>) -> bool;
|
||||
}
|
||||
|
||||
fn target_archive_format_to_object_kind(format: &str) -> Option<ObjectArchiveKind> {
|
||||
@@ -401,7 +403,6 @@ enum ArchiveEntrySource {
|
||||
#[derive(Debug)]
|
||||
struct ArchiveEntry {
|
||||
source: ArchiveEntrySource,
|
||||
#[expect(dead_code)] // used in #155338
|
||||
kind: ArchiveEntryKind,
|
||||
}
|
||||
|
||||
@@ -534,9 +535,9 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
|
||||
|
||||
/// Combine the provided files, rlibs, and native libraries into a single
|
||||
/// `Archive`.
|
||||
fn build(self: Box<Self>, output: &Path) -> bool {
|
||||
fn build(self: Box<Self>, output: &Path, exported_symbols: Option<FxHashSet<String>>) -> bool {
|
||||
let sess = self.sess;
|
||||
match self.build_inner(output) {
|
||||
match self.build_inner(output, exported_symbols) {
|
||||
Ok(any_members) => any_members,
|
||||
Err(error) => {
|
||||
sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error })
|
||||
@@ -546,7 +547,11 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ArArchiveBuilder<'a> {
|
||||
fn build_inner(self, output: &Path) -> io::Result<bool> {
|
||||
fn build_inner(
|
||||
self,
|
||||
output: &Path,
|
||||
exported_symbols: Option<FxHashSet<String>>,
|
||||
) -> io::Result<bool> {
|
||||
let archive_kind = match &*self.sess.target.archive_format {
|
||||
"gnu" => ArchiveKind::Gnu,
|
||||
"bsd" => ArchiveKind::Bsd,
|
||||
@@ -561,40 +566,51 @@ impl<'a> ArArchiveBuilder<'a> {
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for (entry_name, entry) in self.entries {
|
||||
let data =
|
||||
match entry.source {
|
||||
ArchiveEntrySource::Archive { archive_index, file_range } => {
|
||||
let src_archive = &self.src_archives[archive_index];
|
||||
let archive_data = &src_archive.1;
|
||||
let start = file_range.0 as usize;
|
||||
let end = start + file_range.1 as usize;
|
||||
let Some(data) = archive_data.get(start..end) else {
|
||||
return Err(io_error_context(
|
||||
"invalid archive member",
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"archive member at offset {start} with size {} \
|
||||
let data: Box<dyn AsRef<[u8]>> = match entry.source {
|
||||
ArchiveEntrySource::Archive { archive_index, file_range } => {
|
||||
let src_archive = &self.src_archives[archive_index];
|
||||
let archive_data = &src_archive.1;
|
||||
let start = file_range.0 as usize;
|
||||
let end = start + file_range.1 as usize;
|
||||
let Some(data) = archive_data.get(start..end) else {
|
||||
return Err(io_error_context(
|
||||
"invalid archive member",
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"archive member at offset {start} with size {} \
|
||||
exceeds archive size {} in `{}`",
|
||||
file_range.1,
|
||||
archive_data.len(),
|
||||
src_archive.0.display(),
|
||||
),
|
||||
file_range.1,
|
||||
archive_data.len(),
|
||||
src_archive.0.display(),
|
||||
),
|
||||
));
|
||||
};
|
||||
),
|
||||
));
|
||||
};
|
||||
|
||||
Box::new(data) as Box<dyn AsRef<[u8]>>
|
||||
if entry.kind == ArchiveEntryKind::RustObj
|
||||
&& let Some(exported) = &exported_symbols
|
||||
{
|
||||
Box::new(apply_hide(data, exported))
|
||||
} else {
|
||||
Box::new(data)
|
||||
}
|
||||
ArchiveEntrySource::File(file) => unsafe {
|
||||
Box::new(
|
||||
Mmap::map(File::open(file).map_err(|err| {
|
||||
io_error_context("failed to open object file", err)
|
||||
})?)
|
||||
.map_err(|err| io_error_context("failed to map object file", err))?,
|
||||
) as Box<dyn AsRef<[u8]>>
|
||||
},
|
||||
};
|
||||
}
|
||||
ArchiveEntrySource::File(file) => unsafe {
|
||||
let mmap = Mmap::map(
|
||||
File::open(file)
|
||||
.map_err(|err| io_error_context("failed to open object file", err))?,
|
||||
)
|
||||
.map_err(|err| io_error_context("failed to map object file", err))?;
|
||||
if entry.kind == ArchiveEntryKind::RustObj
|
||||
&& let Some(exported) = &exported_symbols
|
||||
{
|
||||
Box::new(apply_hide(&mmap, exported))
|
||||
} else {
|
||||
Box::new(mmap) as Box<dyn AsRef<[u8]>>
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
entries.push(NewArchiveMember {
|
||||
buf: data,
|
||||
@@ -655,3 +671,153 @@ impl<'a> ArArchiveBuilder<'a> {
|
||||
fn io_error_context(context: &str, err: io::Error) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, format!("{context}: {err}"))
|
||||
}
|
||||
|
||||
// We use the `object` crate for the read-only pass over ELF/Mach-O object files
|
||||
// because its `Sym`/`Nlist` traits provide clean access to symbol properties without
|
||||
// manual byte parsing. However, `object` does not expose mutable views into the data,
|
||||
// so we cannot use it to modify symbol fields in place. Instead, the read-only pass
|
||||
// collects byte-level patches (offset + new value), and the write pass
|
||||
// (`apply_patches`) applies them to a copy of the byte buffer without any ELF/Mach-O
|
||||
// parsing — similar to how linker relocations work.
|
||||
|
||||
/// A byte-level patch collected in the read-only pass and applied in the write pass.
|
||||
struct Patch {
|
||||
offset: usize,
|
||||
value: u8,
|
||||
}
|
||||
|
||||
/// Apply a list of byte patches to `data`, returning the (possibly modified) bytes.
|
||||
fn apply_patches(data: &[u8], patches: &[Patch]) -> Vec<u8> {
|
||||
let mut buf = data.to_vec();
|
||||
for p in patches {
|
||||
buf[p.offset] = p.value;
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ELF hide – read-only pass uses `object` crate, write pass uses `Patch` list
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn elf_hide_patches_impl<'data, Elf: object::read::elf::FileHeader<Endian = Endianness>>(
|
||||
data: &'data [u8],
|
||||
st_other_offset: usize,
|
||||
exported: &FxHashSet<String>,
|
||||
) -> Option<Vec<Patch>>
|
||||
where
|
||||
u64: From<Elf::Word>,
|
||||
{
|
||||
let header = Elf::parse(data).ok()?;
|
||||
let endian = header.endian().ok()?;
|
||||
let sections = header.sections(endian, data).ok()?;
|
||||
let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?;
|
||||
|
||||
let data_ptr = data.as_ptr() as usize;
|
||||
let strings = symtab.strings();
|
||||
let mut patches = Vec::new();
|
||||
|
||||
for sym in symtab.iter() {
|
||||
let binding = sym.st_bind();
|
||||
if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
|
||||
continue;
|
||||
}
|
||||
if sym.is_undefined(endian) {
|
||||
continue;
|
||||
}
|
||||
let Ok(name_bytes) = sym.name(endian, strings) else { continue };
|
||||
let Ok(name) = str::from_utf8(name_bytes) else { continue };
|
||||
if !exported.contains(name) {
|
||||
let sym_addr = sym as *const Elf::Sym as usize;
|
||||
let offset = sym_addr - data_ptr + st_other_offset;
|
||||
let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN;
|
||||
patches.push(Patch { offset, value: new_vis });
|
||||
}
|
||||
}
|
||||
|
||||
Some(patches)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mach-O hide – same architecture: read-only pass via `object`, write via patches
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn macho_hide_patches_impl<'data, Mach: object::read::macho::MachHeader<Endian = Endianness>>(
|
||||
data: &'data [u8],
|
||||
n_type_offset: usize,
|
||||
exported: &FxHashSet<String>,
|
||||
) -> Option<Vec<Patch>> {
|
||||
let header = Mach::parse(data, 0).ok()?;
|
||||
let endian = header.endian().ok()?;
|
||||
let mut commands = header.load_commands(endian, data, 0).ok()?;
|
||||
|
||||
let symtab_cmd = loop {
|
||||
let cmd = commands.next().ok()??;
|
||||
if let Some(st) = cmd.symtab().ok().flatten() {
|
||||
break st;
|
||||
}
|
||||
};
|
||||
let symtab: object::read::macho::SymbolTable<'_, Mach, &_> =
|
||||
symtab_cmd.symbols(endian, data).ok()?;
|
||||
|
||||
let data_ptr = data.as_ptr() as usize;
|
||||
let strings = symtab.strings();
|
||||
let mut patches = Vec::new();
|
||||
|
||||
for nlist in symtab.iter() {
|
||||
if nlist.is_stab() {
|
||||
continue;
|
||||
}
|
||||
if nlist.is_undefined() {
|
||||
continue;
|
||||
}
|
||||
if nlist.n_type() & macho::N_EXT == 0 {
|
||||
continue;
|
||||
}
|
||||
let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
|
||||
let Ok(name) = str::from_utf8(name_bytes) else { continue };
|
||||
let name = name.strip_prefix('_').unwrap_or(name);
|
||||
if !exported.contains(name) {
|
||||
let nlist_addr = nlist as *const Mach::Nlist as usize;
|
||||
let offset = nlist_addr - data_ptr + n_type_offset;
|
||||
patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT });
|
||||
}
|
||||
}
|
||||
|
||||
Some(patches)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Unified dispatch: top-level detection via `object::File::parse`
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn hide_patches(data: &[u8], exported: &FxHashSet<String>) -> Option<Vec<Patch>> {
|
||||
let file = object::File::parse(data).ok()?;
|
||||
match file {
|
||||
object::File::Elf64(_) => elf_hide_patches_impl::<elf::FileHeader64<Endianness>>(
|
||||
data,
|
||||
mem::offset_of!(elf::Sym64<Endianness>, st_other),
|
||||
exported,
|
||||
),
|
||||
object::File::Elf32(_) => elf_hide_patches_impl::<elf::FileHeader32<Endianness>>(
|
||||
data,
|
||||
mem::offset_of!(elf::Sym32<Endianness>, st_other),
|
||||
exported,
|
||||
),
|
||||
object::File::MachO64(_) => macho_hide_patches_impl::<macho::MachHeader64<Endianness>>(
|
||||
data,
|
||||
mem::offset_of!(macho::Nlist64<Endianness>, n_type),
|
||||
exported,
|
||||
),
|
||||
object::File::MachO32(_) => macho_hide_patches_impl::<macho::MachHeader32<Endianness>>(
|
||||
data,
|
||||
mem::offset_of!(macho::Nlist32<Endianness>, n_type),
|
||||
exported,
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_hide(data: &[u8], exported: &FxHashSet<String>) -> Vec<u8> {
|
||||
let patches = hide_patches(data, exported).unwrap_or_default();
|
||||
apply_patches(data, &patches)
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ pub fn link_binary(
|
||||
RlibFlavor::Normal,
|
||||
&path,
|
||||
)
|
||||
.build(&out_filename);
|
||||
.build(&out_filename, None);
|
||||
}
|
||||
CrateType::StaticLib => {
|
||||
link_staticlib(
|
||||
@@ -566,7 +566,22 @@ fn link_staticlib(
|
||||
sess.dcx().emit_fatal(e);
|
||||
}
|
||||
|
||||
ab.build(out_filename);
|
||||
let exported_symbols = if sess.opts.unstable_opts.staticlib_hide_internal_symbols {
|
||||
if !matches!(sess.target.binary_format, BinaryFormat::Elf | BinaryFormat::MachO) {
|
||||
sess.dcx().emit_warn(errors::StaticlibHideInternalSymbolsUnsupported {
|
||||
binary_format: sess.target.archive_format.to_string(),
|
||||
});
|
||||
None
|
||||
} else {
|
||||
crate_info
|
||||
.exported_symbols
|
||||
.get(&CrateType::StaticLib)
|
||||
.map(|symbols| symbols.iter().map(|(s, _)| s.clone()).collect())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ab.build(out_filename, exported_symbols);
|
||||
|
||||
let crates = crate_info.used_crates.iter();
|
||||
|
||||
@@ -1265,7 +1280,7 @@ fn link_natively(
|
||||
if should_archive {
|
||||
let mut ab = archive_builder_builder.new_archive_builder(sess);
|
||||
ab.add_file(temp_filename, ArchiveEntryKind::Other);
|
||||
ab.build(out_filename);
|
||||
ab.build(out_filename, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3265,7 +3280,7 @@ fn add_static_crate(
|
||||
sess.dcx()
|
||||
.emit_fatal(errors::RlibArchiveBuildFailure { path: cratepath.clone(), error });
|
||||
}
|
||||
if archive.build(&dst) {
|
||||
if archive.build(&dst, None) {
|
||||
link_upstream(&dst);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -693,6 +693,14 @@ pub(crate) struct IncompatibleArchiveFormat {
|
||||
#[diag("linking static libraries is not supported for BPF")]
|
||||
pub(crate) struct BpfStaticlibNotSupported;
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(
|
||||
"-Zstaticlib-hide-internal-symbols only supports ELF and Mach-O targets, but the target uses `{$binary_format}`"
|
||||
)]
|
||||
pub(crate) struct StaticlibHideInternalSymbolsUnsupported {
|
||||
pub binary_format: String,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag("entry symbol `main` declared multiple times")]
|
||||
#[help(
|
||||
|
||||
@@ -865,6 +865,7 @@ fn test_unstable_options_tracking_hash() {
|
||||
tracked!(split_lto_unit, Some(true));
|
||||
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
|
||||
tracked!(stack_protector, StackProtector::All);
|
||||
tracked!(staticlib_hide_internal_symbols, true);
|
||||
tracked!(teach, true);
|
||||
tracked!(thinlto, Some(true));
|
||||
tracked!(tiny_const_eval_limit, true);
|
||||
|
||||
@@ -471,7 +471,7 @@ impl ArchiveBuilderBuilder for DummyArchiveBuilderBuilder {
|
||||
output_path: &Path,
|
||||
) {
|
||||
// Build an empty static library to avoid calling an external dlltool on mingw
|
||||
ArArchiveBuilderBuilder.new_archive_builder(sess).build(output_path);
|
||||
ArArchiveBuilderBuilder.new_archive_builder(sess).build(output_path, None);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2456,6 +2456,14 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
|
||||
let mut collected_options = Default::default();
|
||||
|
||||
let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut collected_options);
|
||||
|
||||
if unstable_opts.staticlib_hide_internal_symbols && !crate_types.contains(&CrateType::StaticLib)
|
||||
{
|
||||
early_dcx.early_warn(
|
||||
"-Zstaticlib-hide-internal-symbols has no effect without `--crate-type staticlib`",
|
||||
);
|
||||
}
|
||||
|
||||
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
|
||||
|
||||
if !unstable_opts.unstable_options && json_timings {
|
||||
|
||||
@@ -2659,6 +2659,8 @@ written to standard error output)"),
|
||||
"control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"),
|
||||
staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED],
|
||||
"allow staticlibs to have rust dylib dependencies"),
|
||||
staticlib_hide_internal_symbols: bool = (false, parse_bool, [TRACKED],
|
||||
"hide non-exported symbols in ELF static libraries by setting STV_HIDDEN"),
|
||||
staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED],
|
||||
"prefer dynamic linking to static linking for staticlibs (default: no)"),
|
||||
strict_init_checks: bool = (false, parse_bool, [TRACKED],
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# `staticlib-hide-internal-symbols`
|
||||
|
||||
When building a `staticlib`, this option hides all non-exported Rust-internal
|
||||
symbols. On ELF targets, this sets `STV_HIDDEN` visibility. On Apple (Mach-O)
|
||||
targets, this sets the `N_PEXT` (private external) bit.
|
||||
|
||||
This is a lightweight, zero-overhead operation: only the visibility/type byte of
|
||||
each internal symbol is modified in-place.
|
||||
|
||||
Only symbols explicitly exported via `#[no_mangle]` or `#[export_name]` are left
|
||||
unchanged. All other `GLOBAL`/`WEAK` symbols (including `pub(crate)` and `pub`
|
||||
items without `#[no_mangle]`) are hidden.
|
||||
|
||||
This option can only be used with `--crate-type staticlib`. Using it with
|
||||
other crate types will result in a compilation warning.
|
||||
|
||||
Supported on ELF targets (Linux, BSD, etc.) and Apple targets (macOS, iOS, etc.).
|
||||
On unsupported targets (Windows), a warning is emitted and the flag has no effect.
|
||||
|
||||
This option can be combined with `-Zstaticlib-rename-internal-symbols`.
|
||||
When both are enabled, symbols are both renamed and hidden.
|
||||
|
||||
## Comparison with `-Zdefault-visibility=hidden`
|
||||
|
||||
`-Zdefault-visibility=hidden` sets visibility at LLVM IR codegen time. It targets
|
||||
shared objects (`cdylib`/`dylib`) and only affects the current crate's codegen.
|
||||
|
||||
`-Zstaticlib-hide-internal-symbols` patches visibility bytes post-compilation in
|
||||
the final `.a` archive, which includes object files from all upstream static
|
||||
dependencies. This means internal symbols from the entire dependency tree are
|
||||
hidden, not just those from the current crate. Hidden symbols are also excluded
|
||||
from the linker's global symbol table, which can slightly reduce final binary size.
|
||||
@@ -0,0 +1,115 @@
|
||||
//@ only-apple
|
||||
//@ ignore-cross-compile
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use run_make_support::object::Endianness;
|
||||
use run_make_support::object::macho::{MachHeader64, N_EXT, N_PEXT, N_SECT, N_STAB, N_TYPE};
|
||||
use run_make_support::object::read::archive::ArchiveFile;
|
||||
use run_make_support::object::read::macho::{MachHeader as _, Nlist as _};
|
||||
use run_make_support::path_helpers::source_root;
|
||||
use run_make_support::{cc, extra_c_flags, rfs, run, rustc, static_lib_name};
|
||||
|
||||
type MachOFileHeader64 = MachHeader64<Endianness>;
|
||||
type SymbolTable<'data> =
|
||||
run_make_support::object::read::macho::SymbolTable<'data, MachOFileHeader64>;
|
||||
|
||||
const EXPORTED: &[&str] = &["my_add", "my_hash_lookup", "call_internal", "my_safe_div"];
|
||||
|
||||
fn main() {
|
||||
let sibling = source_root().join("tests/run-make/staticlib-hide-internal-symbols");
|
||||
rfs::copy(sibling.join("lib.rs"), "lib.rs");
|
||||
rfs::copy(sibling.join("main.c"), "main.c");
|
||||
|
||||
let lib_name = static_lib_name("lib");
|
||||
|
||||
rustc()
|
||||
.input("lib.rs")
|
||||
.crate_type("staticlib")
|
||||
.arg("-Zstaticlib-hide-internal-symbols")
|
||||
.opt()
|
||||
.run();
|
||||
|
||||
cc().input("main.c").input(&lib_name).out_exe("main").args(extra_c_flags()).run();
|
||||
run("main");
|
||||
|
||||
let data = rfs::read(&lib_name);
|
||||
check_symbols(&data, true);
|
||||
|
||||
rfs::remove_file(&lib_name);
|
||||
rustc().input("lib.rs").crate_type("staticlib").opt().run();
|
||||
|
||||
let data = rfs::read(&lib_name);
|
||||
check_symbols(&data, false);
|
||||
}
|
||||
|
||||
fn check_symbols(archive_data: &[u8], with_flag: bool) {
|
||||
let archive = ArchiveFile::parse(archive_data).unwrap();
|
||||
let mut found_exported = HashSet::new();
|
||||
|
||||
for member in archive.members() {
|
||||
let member = member.unwrap();
|
||||
let data = member.data(archive_data).unwrap();
|
||||
|
||||
let Ok(header) = MachOFileHeader64::parse(data, 0) else { continue };
|
||||
let Ok(endian) = header.endian() else { continue };
|
||||
|
||||
let Some(symtab) = find_symtab(header, endian, data) else { continue };
|
||||
let strtab = symtab.strings();
|
||||
|
||||
for nlist in symtab.iter() {
|
||||
let n_type = nlist.n_type();
|
||||
if n_type & N_STAB != 0 {
|
||||
continue;
|
||||
}
|
||||
if n_type & N_EXT == 0 {
|
||||
continue;
|
||||
}
|
||||
if n_type & N_TYPE != N_SECT {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(name_bytes) = nlist.name(endian, strtab) else { continue };
|
||||
let Ok(name) = str::from_utf8(name_bytes) else { continue };
|
||||
let name = name.strip_prefix('_').unwrap_or(name);
|
||||
|
||||
let exported = EXPORTED.contains(&name);
|
||||
let has_pext = n_type & N_PEXT != 0;
|
||||
|
||||
if with_flag {
|
||||
if exported {
|
||||
assert!(!has_pext, "with -Z hide: exported `{name}` should NOT have N_PEXT");
|
||||
} else {
|
||||
assert!(has_pext, "with -Z hide: internal `{name}` should have N_PEXT");
|
||||
}
|
||||
} else if exported {
|
||||
assert!(!has_pext, "without -Z: exported `{name}` should NOT have N_PEXT");
|
||||
}
|
||||
|
||||
if exported {
|
||||
found_exported.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for expected in EXPORTED {
|
||||
assert!(
|
||||
found_exported.contains(*expected),
|
||||
"expected to find exported symbol `{expected}` in archive"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn find_symtab<'data>(
|
||||
header: &MachOFileHeader64,
|
||||
endian: Endianness,
|
||||
data: &'data [u8],
|
||||
) -> Option<SymbolTable<'data>> {
|
||||
let mut commands = header.load_commands(endian, data, 0).ok()?;
|
||||
while let Ok(Some(command)) = commands.next() {
|
||||
if let Ok(Some(symtab_cmd)) = command.symtab() {
|
||||
return symtab_cmd.symbols::<MachOFileHeader64, _>(endian, data).ok();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#![crate_type = "staticlib"]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn my_add(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn my_hash_lookup(key: u64) -> u64 {
|
||||
let mut map = HashMap::new();
|
||||
for i in 0..100u64 {
|
||||
map.insert(i, i.wrapping_mul(2654435761));
|
||||
}
|
||||
*map.get(&key).unwrap_or(&0)
|
||||
}
|
||||
|
||||
fn internal_helper() -> i32 {
|
||||
42
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn call_internal() -> i32 {
|
||||
internal_helper()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn my_safe_div(a: i32, b: i32) -> i32 {
|
||||
match catch_unwind(AssertUnwindSafe(|| {
|
||||
if b == 0 {
|
||||
panic!("division by zero!");
|
||||
}
|
||||
a / b
|
||||
})) {
|
||||
Ok(result) => result,
|
||||
Err(_) => -1,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <stdint.h>
|
||||
|
||||
extern int my_add(int a, int b);
|
||||
extern uint64_t my_hash_lookup(uint64_t key);
|
||||
extern int call_internal(void);
|
||||
extern int my_safe_div(int a, int b);
|
||||
|
||||
int main() {
|
||||
if (my_add(10, 20) != 30)
|
||||
return 1;
|
||||
if (my_hash_lookup(5) != (uint64_t)5 * 2654435761ULL)
|
||||
return 1;
|
||||
if (call_internal() != 42)
|
||||
return 1;
|
||||
if (my_safe_div(100, 5) != 20)
|
||||
return 1;
|
||||
if (my_safe_div(100, 0) != -1)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
//@ only-elf
|
||||
//@ ignore-cross-compile
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use run_make_support::object::read::archive::ArchiveFile;
|
||||
use run_make_support::object::read::elf::{FileHeader as _, SectionHeader as _, Sym as _};
|
||||
use run_make_support::object::{Endianness, elf};
|
||||
use run_make_support::{cc, extra_c_flags, rfs, run, rustc, static_lib_name};
|
||||
|
||||
const EXPORTED: &[&str] = &["my_add", "my_hash_lookup", "call_internal", "my_safe_div"];
|
||||
|
||||
fn main() {
|
||||
let lib_name = static_lib_name("lib");
|
||||
|
||||
rustc()
|
||||
.input("lib.rs")
|
||||
.crate_type("staticlib")
|
||||
.arg("-Zstaticlib-hide-internal-symbols")
|
||||
.opt()
|
||||
.run();
|
||||
|
||||
cc().input("main.c").input(&lib_name).out_exe("main").args(extra_c_flags()).run();
|
||||
run("main");
|
||||
|
||||
let data = rfs::read(&lib_name);
|
||||
check_symbols(&data, true);
|
||||
|
||||
rfs::remove_file(&lib_name);
|
||||
rustc().input("lib.rs").crate_type("staticlib").opt().run();
|
||||
|
||||
let data = rfs::read(&lib_name);
|
||||
check_symbols(&data, false);
|
||||
}
|
||||
|
||||
fn check_symbols(archive_data: &[u8], with_flag: bool) {
|
||||
let archive = ArchiveFile::parse(archive_data).unwrap();
|
||||
let mut found_exported = HashSet::new();
|
||||
|
||||
for member in archive.members() {
|
||||
let member = member.unwrap();
|
||||
if !member.name().ends_with(b".rcgu.o") {
|
||||
continue;
|
||||
}
|
||||
let data = member.data(archive_data).unwrap();
|
||||
|
||||
if let Ok(header) = elf::FileHeader64::<Endianness>::parse(data) {
|
||||
check_elf_symbols(header, data, with_flag, &mut found_exported);
|
||||
} else if let Ok(header) = elf::FileHeader32::<Endianness>::parse(data) {
|
||||
check_elf_symbols(header, data, with_flag, &mut found_exported);
|
||||
}
|
||||
}
|
||||
|
||||
for expected in EXPORTED {
|
||||
assert!(
|
||||
found_exported.contains(*expected),
|
||||
"expected to find exported symbol `{expected}` in archive"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_elf_symbols<Elf: run_make_support::object::read::elf::FileHeader<Endian = Endianness>>(
|
||||
header: &Elf,
|
||||
data: &[u8],
|
||||
with_flag: bool,
|
||||
found_exported: &mut HashSet<String>,
|
||||
) {
|
||||
let Ok(endian) = header.endian() else { return };
|
||||
let Ok(sections) = header.sections(endian, data) else { return };
|
||||
|
||||
for (si, section) in sections.enumerate() {
|
||||
if section.sh_type(endian) != elf::SHT_SYMTAB {
|
||||
continue;
|
||||
}
|
||||
let Ok(symbols) = run_make_support::object::read::elf::SymbolTable::parse(
|
||||
endian, data, §ions, si, section,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let strtab = symbols.strings();
|
||||
|
||||
for symbol in symbols.symbols() {
|
||||
let vis = symbol.st_visibility();
|
||||
let bind = symbol.st_bind();
|
||||
let shndx = symbol.st_shndx(endian);
|
||||
|
||||
if shndx == elf::SHN_UNDEF {
|
||||
continue;
|
||||
}
|
||||
if bind != elf::STB_GLOBAL && bind != elf::STB_WEAK {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(name_bytes) = symbol.name(endian, strtab) else { continue };
|
||||
let Ok(name) = str::from_utf8(name_bytes) else { continue };
|
||||
|
||||
let exported = EXPORTED.contains(&name);
|
||||
|
||||
if with_flag {
|
||||
let expected = if exported { elf::STV_DEFAULT } else { elf::STV_HIDDEN };
|
||||
assert_eq!(
|
||||
vis,
|
||||
expected,
|
||||
"with -Z hide: `{name}` should be {}, got {}",
|
||||
visibility_name(expected),
|
||||
visibility_name(vis)
|
||||
);
|
||||
} else if exported {
|
||||
assert_eq!(
|
||||
vis,
|
||||
elf::STV_DEFAULT,
|
||||
"without -Z: `{name}` should be STV_DEFAULT, got {}",
|
||||
visibility_name(vis)
|
||||
);
|
||||
}
|
||||
|
||||
if exported {
|
||||
found_exported.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visibility_name(v: u8) -> &'static str {
|
||||
match v {
|
||||
elf::STV_DEFAULT => "STV_DEFAULT",
|
||||
elf::STV_HIDDEN => "STV_HIDDEN",
|
||||
elf::STV_INTERNAL => "STV_INTERNAL",
|
||||
elf::STV_PROTECTED => "STV_PROTECTED",
|
||||
_ => "UNKNOWN",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//@ check-pass
|
||||
//@ compile-flags: -Zstaticlib-hide-internal-symbols --crate-type bin
|
||||
|
||||
#![feature(no_core)]
|
||||
#![no_core]
|
||||
#![no_main]
|
||||
|
||||
//~? WARN has no effect without `--crate-type staticlib`
|
||||
@@ -0,0 +1,2 @@
|
||||
warning: -Zstaticlib-hide-internal-symbols has no effect without `--crate-type staticlib`
|
||||
|
||||
Reference in New Issue
Block a user