Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -837,23 +837,31 @@ fn link_staticlib(
let rename = sess.opts.unstable_opts.staticlib_rename_internal_symbols;

let exported_symbols = if hide || rename {
if !matches!(sess.target.binary_format, BinaryFormat::Elf | BinaryFormat::MachO) {
if hide {
sess.dcx().emit_warn(diagnostics::StaticlibHideInternalSymbolsUnsupported {
binary_format: sess.target.archive_format.to_string(),
});
}
if rename {
sess.dcx().emit_warn(diagnostics::StaticlibRenameInternalSymbolsUnsupported {
binary_format: sess.target.archive_format.to_string(),
});
}
None
} else {
let hide_supported =
matches!(sess.target.binary_format, BinaryFormat::Elf | BinaryFormat::MachO);
// Rename only rewrites symbol names, so it also works on COFF; hide
// needs a visibility concept COFF lacks.
let rename_supported = matches!(
sess.target.binary_format,
BinaryFormat::Elf | BinaryFormat::MachO | BinaryFormat::Coff
);
if hide && !hide_supported {
sess.dcx().emit_warn(diagnostics::StaticlibHideInternalSymbolsUnsupported {
binary_format: sess.target.archive_format.to_string(),
});
}
if rename && !rename_supported {
sess.dcx().emit_warn(diagnostics::StaticlibRenameInternalSymbolsUnsupported {
binary_format: sess.target.archive_format.to_string(),
});
}
if (hide && hide_supported) || (rename && rename_supported) {
crate_info
.exported_symbols
.get(&CrateType::StaticLib)
.map(|symbols| symbols.iter().map(|symbol| symbol.name.clone()).collect())
} else {
None
}
} else {
None
Expand Down
157 changes: 156 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/symbol_edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
use std::borrow::Cow;
use std::mem;

use object::read::coff::{CoffHeader, ImageSymbol as _};
use object::read::elf::{SectionHeader as _, Sym as _};
use object::read::macho::Nlist;
use object::{Endianness, elf, macho};
use object::{Endianness, elf, macho, pe};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};

struct Patch {
Expand Down Expand Up @@ -57,6 +58,22 @@ pub(super) fn apply_edits<'a>(
rename,
mem::offset_of!(macho::Nlist32<Endianness>, n_type),
),
Some(object::File::Coff(f)) => coff_edit_impl(
data,
f.coff_header(),
exported,
hide,
rename,
coff_strip_underscore(f.coff_header()),
),
Some(object::File::CoffBig(f)) => coff_edit_impl(
data,
f.coff_header(),
exported,
hide,
rename,
coff_strip_underscore(f.coff_header()),
),
_ => None,
};
match result {
Expand Down Expand Up @@ -84,10 +101,30 @@ pub(super) fn collect_internal_names(
object::File::MachO32(_) => {
macho_collect_impl::<macho::MachHeader32<Endianness>>(data, exported, out)
}
object::File::Coff(f) => coff_collect_impl(
data,
f.coff_header(),
exported,
out,
coff_strip_underscore(f.coff_header()),
),
object::File::CoffBig(f) => coff_collect_impl(
data,
f.coff_header(),
exported,
out,
coff_strip_underscore(f.coff_header()),
),
_ => {}
}
}

/// Whether this machine's COFF ABI decorates external symbols with a leading `_`
/// (i686 only).
fn coff_strip_underscore(header: &impl CoffHeader) -> bool {
header.machine() == pe::IMAGE_FILE_MACHINE_I386
}

fn elf_collect_impl<Elf: object::read::elf::FileHeader<Endian = Endianness>>(
data: &[u8],
exported: &FxHashSet<String>,
Expand Down Expand Up @@ -441,6 +478,124 @@ fn macho_rebuild_strtab(
Some(result)
}

// ---------------------------------------------------------------------------
// COFF: single-pass collection + apply
// ---------------------------------------------------------------------------

fn coff_collect_impl<'data, Coff: CoffHeader>(
data: &'data [u8],
header: &'data Coff,
exported: &FxHashSet<String>,
out: &mut FxHashSet<String>,
strip_underscore: bool,
) {
let Ok(symbols) = header.symbols(data) else { return };
let strings = symbols.strings();

for (_index, sym) in symbols.iter() {
let sclass = sym.storage_class();
if sclass != pe::IMAGE_SYM_CLASS_EXTERNAL && sclass != pe::IMAGE_SYM_CLASS_WEAK_EXTERNAL {
continue;
}
if sym.section_number() <= 0 {
continue;
}
let Ok(name_bytes) = sym.name(strings) else { continue };
let Ok(mut name) = str::from_utf8(name_bytes).map(String::from) else { continue };
if strip_underscore {
name = name.strip_prefix('_').unwrap_or(&name).to_string();
}
if !exported.contains(&name) {
out.insert(name);
}
}
}

fn coff_edit_impl<'data, Coff: CoffHeader>(
data: &'data [u8],
header: &'data Coff,
exported: &FxHashSet<String>,
hide: bool,
rename: Option<&(FxHashSet<String>, &str)>,
strip_underscore: bool,
) -> Option<Vec<u8>> {
// COFF has no visibility concept, so hide is a no-op.
let _ = (hide, exported);

let pointer_to_symbol_table = header.pointer_to_symbol_table() as usize;
let number_of_symbols = header.number_of_symbols() as usize;
// ImageSymbol is 18 bytes; ImageSymbolEx (bigobj) is 20.
let sym_size = mem::size_of::<Coff::ImageSymbolBytes>();

let symbol_bytes_offset = pointer_to_symbol_table;
let strtab_base = pointer_to_symbol_table + number_of_symbols * sym_size;
if strtab_base > data.len() {
return None;
}

if data.len() < strtab_base + 4 {
return None;
}
let old_len =
u32::from_le_bytes(data[strtab_base..strtab_base + 4].try_into().unwrap()) as usize;
if strtab_base + old_len > data.len() {
return None;
}

let Ok(symbols) = header.symbols(data) else { return None };
let strings = symbols.strings();

let mut renames = Vec::new();
for (index, sym) in symbols.iter() {
let sclass = sym.storage_class();
if sclass != pe::IMAGE_SYM_CLASS_EXTERNAL && sclass != pe::IMAGE_SYM_CLASS_WEAK_EXTERNAL {
continue;
}
let Ok(name_bytes) = sym.name(strings) else { continue };
let Ok(name) = str::from_utf8(name_bytes) else { continue };
let check_name =
if strip_underscore { name.strip_prefix('_').unwrap_or(name) } else { name };
if rename.is_some_and(|(rename_set, _)| rename_set.contains(check_name)) {
renames.push(RenameEntry {
name_field_offset: symbol_bytes_offset + index.0 * sym_size,
name: name.to_string(),
});
}
}
if renames.is_empty() {
return None;
}
let suffix = rename.unwrap().1;

let mut new_strtab = Vec::new();
let mut map: FxHashMap<String, u32> = FxHashMap::default();
let mut sorted_names: Vec<&str> = renames.iter().map(|r| r.name.as_str()).collect();
sorted_names.sort();
sorted_names.dedup();
for name in &sorted_names {
let rel_offset = (old_len + new_strtab.len()) as u32;
new_strtab.extend_from_slice(name.as_bytes());
new_strtab.extend_from_slice(suffix.as_bytes());
new_strtab.push(0);
map.insert(name.to_string(), rel_offset);
}

let mut result = data.to_vec();
let new_len = (old_len + new_strtab.len()) as u32;
result[strtab_base..strtab_base + 4].copy_from_slice(&new_len.to_le_bytes());
result.extend_from_slice(&new_strtab);

// Long names are stored as name[0] = 0 and name[4..8] = string table offset.
for r in &renames {
result[r.name_field_offset] = 0;
let rel = map[&r.name];
result[r.name_field_offset + 4..r.name_field_offset + 8]
.copy_from_slice(&rel.to_le_bytes());
}

Some(result)
}

// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ pub(crate) struct StaticlibHideInternalSymbolsUnsupported {

#[derive(Diagnostic)]
#[diag(
"-Zstaticlib-rename-internal-symbols only supports ELF and Mach-O targets, but the target uses `{$binary_format}`"
"-Zstaticlib-rename-internal-symbols only supports ELF, Mach-O, and COFF targets, but the target uses `{$binary_format}`"
)]
pub(crate) struct StaticlibRenameInternalSymbolsUnsupported {
pub binary_format: String,
Expand Down
Loading
Loading