Make #[used] work when linking with ld64
This commit is contained in:
parent
2162e9d4b1
commit
b202430084
7 changed files with 202 additions and 9 deletions
|
@ -2,6 +2,7 @@ use std::env;
|
|||
use std::fmt::{Display, from_fn};
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use rustc_middle::middle::exported_symbols::SymbolExportKind;
|
||||
use rustc_session::Session;
|
||||
use rustc_target::spec::Target;
|
||||
|
||||
|
@ -26,6 +27,89 @@ pub(super) fn macho_platform(target: &Target) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Add relocation and section data needed for a symbol to be considered
|
||||
/// undefined by ld64.
|
||||
///
|
||||
/// The relocation must be valid, and hence must point to a valid piece of
|
||||
/// machine code, and hence this is unfortunately very architecture-specific.
|
||||
///
|
||||
///
|
||||
/// # New architectures
|
||||
///
|
||||
/// The values here are basically the same as emitted by the following program:
|
||||
///
|
||||
/// ```c
|
||||
/// // clang -c foo.c -target $CLANG_TARGET
|
||||
/// void foo(void);
|
||||
///
|
||||
/// extern int bar;
|
||||
///
|
||||
/// void* foobar[2] = {
|
||||
/// (void*)foo,
|
||||
/// (void*)&bar,
|
||||
/// // ...
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Can be inspected with:
|
||||
/// ```console
|
||||
/// objdump --macho --reloc foo.o
|
||||
/// objdump --macho --full-contents foo.o
|
||||
/// ```
|
||||
pub(super) fn add_data_and_relocation(
|
||||
file: &mut object::write::Object<'_>,
|
||||
section: object::write::SectionId,
|
||||
symbol: object::write::SymbolId,
|
||||
target: &Target,
|
||||
kind: SymbolExportKind,
|
||||
) -> object::write::Result<()> {
|
||||
let authenticated_pointer =
|
||||
kind == SymbolExportKind::Text && target.llvm_target.starts_with("arm64e");
|
||||
|
||||
let data: &[u8] = match target.pointer_width {
|
||||
_ if authenticated_pointer => &[0, 0, 0, 0, 0, 0, 0, 0x80],
|
||||
32 => &[0; 4],
|
||||
64 => &[0; 8],
|
||||
pointer_width => unimplemented!("unsupported Apple pointer width {pointer_width:?}"),
|
||||
};
|
||||
|
||||
if target.arch == "x86_64" {
|
||||
// Force alignment for the entire section to be 16 on x86_64.
|
||||
file.section_mut(section).append_data(&[], 16);
|
||||
} else {
|
||||
// Elsewhere, the section alignment is the same as the pointer width.
|
||||
file.section_mut(section).append_data(&[], target.pointer_width as u64);
|
||||
}
|
||||
|
||||
let offset = file.section_mut(section).append_data(data, data.len() as u64);
|
||||
|
||||
let flags = if authenticated_pointer {
|
||||
object::write::RelocationFlags::MachO {
|
||||
r_type: object::macho::ARM64_RELOC_AUTHENTICATED_POINTER,
|
||||
r_pcrel: false,
|
||||
r_length: 3,
|
||||
}
|
||||
} else if target.arch == "arm" {
|
||||
// FIXME(madsmtm): Remove once `object` supports 32-bit ARM relocations:
|
||||
// https://github.com/gimli-rs/object/pull/757
|
||||
object::write::RelocationFlags::MachO {
|
||||
r_type: object::macho::ARM_RELOC_VANILLA,
|
||||
r_pcrel: false,
|
||||
r_length: 2,
|
||||
}
|
||||
} else {
|
||||
object::write::RelocationFlags::Generic {
|
||||
kind: object::RelocationKind::Absolute,
|
||||
encoding: object::RelocationEncoding::Generic,
|
||||
size: target.pointer_width as u8,
|
||||
}
|
||||
};
|
||||
|
||||
file.add_relocation(section, object::write::Relocation { offset, addend: 0, symbol, flags })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deployment target or SDK version.
|
||||
///
|
||||
/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`.
|
||||
|
|
|
@ -2063,8 +2063,8 @@ fn add_post_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor
|
|||
/// linker, and since they never participate in the linking, using `KEEP` in the linker scripts
|
||||
/// can't keep them either. This causes #47384.
|
||||
///
|
||||
/// To keep them around, we could use `--whole-archive` and equivalents to force rlib to
|
||||
/// participate in linking like object files, but this proves to be expensive (#93791). Therefore
|
||||
/// To keep them around, we could use `--whole-archive`, `-force_load` and equivalents to force rlib
|
||||
/// to participate in linking like object files, but this proves to be expensive (#93791). Therefore
|
||||
/// we instead just introduce an undefined reference to them. This could be done by `-u` command
|
||||
/// line option to the linker or `EXTERN(...)` in linker scripts, however they does not only
|
||||
/// introduce an undefined reference, but also make them the GC roots, preventing `--gc-sections`
|
||||
|
@ -2106,8 +2106,20 @@ fn add_linked_symbol_object(
|
|||
file.set_mangling(object::write::Mangling::None);
|
||||
}
|
||||
|
||||
// ld64 requires a relocation to load undefined symbols, see below.
|
||||
// Not strictly needed if linking with lld, but might as well do it there too.
|
||||
let ld64_section_helper = if file.format() == object::BinaryFormat::MachO {
|
||||
Some(file.add_section(
|
||||
file.segment_name(object::write::StandardSegment::Data).to_vec(),
|
||||
"__data".into(),
|
||||
object::SectionKind::Data,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for (sym, kind) in symbols.iter() {
|
||||
file.add_symbol(object::write::Symbol {
|
||||
let symbol = file.add_symbol(object::write::Symbol {
|
||||
name: sym.clone().into(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
|
@ -2121,6 +2133,47 @@ fn add_linked_symbol_object(
|
|||
section: object::write::SymbolSection::Undefined,
|
||||
flags: object::SymbolFlags::None,
|
||||
});
|
||||
|
||||
// The linker shipped with Apple's Xcode, ld64, works a bit differently from other linkers.
|
||||
//
|
||||
// Code-wise, the relevant parts of ld64 are roughly:
|
||||
// 1. Find the `ArchiveLoadMode` based on commandline options, default to `parseObjects`.
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/Options.cpp#L924-L932
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/Options.h#L55
|
||||
//
|
||||
// 2. Read the archive table of contents (__.SYMDEF file).
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/parsers/archive_file.cpp#L294-L325
|
||||
//
|
||||
// 3. Begin linking by loading "atoms" from input files.
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/doc/design/linker.html
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/InputFiles.cpp#L1349
|
||||
//
|
||||
// a. Directly specified object files (`.o`) are parsed immediately.
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/parsers/macho_relocatable_file.cpp#L4611-L4627
|
||||
//
|
||||
// - Undefined symbols are not atoms (`n_value > 0` denotes a common symbol).
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/parsers/macho_relocatable_file.cpp#L2455-L2468
|
||||
// https://maskray.me/blog/2022-02-06-all-about-common-symbols
|
||||
//
|
||||
// - Relocations/fixups are atoms.
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ce6341ae966b3451aa54eeb049f2be865afbd578/src/ld/parsers/macho_relocatable_file.cpp#L2088-L2114
|
||||
//
|
||||
// b. Archives are not parsed yet.
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/parsers/archive_file.cpp#L467-L577
|
||||
//
|
||||
// 4. When a symbol is needed by an atom, parse the object file that contains the symbol.
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/InputFiles.cpp#L1417-L1491
|
||||
// https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/parsers/archive_file.cpp#L579-L597
|
||||
//
|
||||
// All of the steps above are fairly similar to other linkers, except that **it completely
|
||||
// ignores undefined symbols**.
|
||||
//
|
||||
// So to make this trick work on ld64, we need to do something else to load the relevant
|
||||
// object files. We do this by inserting a relocation (fixup) for each symbol.
|
||||
if let Some(section) = ld64_section_helper {
|
||||
apple::add_data_and_relocation(&mut file, section, symbol, &sess.target, *kind)
|
||||
.expect("failed adding relocation");
|
||||
}
|
||||
}
|
||||
|
||||
let path = tmpdir.join("symbols.o");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
mod foo {
|
||||
#[link_section = ".rodata.STATIC"]
|
||||
#[cfg_attr(target_os = "linux", link_section = ".rodata.STATIC")]
|
||||
#[cfg_attr(target_vendor = "apple", link_section = "__DATA,STATIC")]
|
||||
#[used]
|
||||
static STATIC: [u32; 10] = [1; 10];
|
||||
}
|
||||
|
|
|
@ -7,15 +7,20 @@
|
|||
// See https://github.com/rust-lang/rust/pull/95604
|
||||
// See https://github.com/rust-lang/rust/issues/47384
|
||||
|
||||
//@ only-linux
|
||||
// Reason: differences in object file formats on OSX and Windows
|
||||
// causes errors in the llvm_objdump step
|
||||
//@ ignore-wasm differences in object file formats causes errors in the llvm_objdump step.
|
||||
//@ ignore-windows differences in object file formats causes errors in the llvm_objdump step.
|
||||
|
||||
use run_make_support::{dynamic_lib_name, llvm_objdump, llvm_readobj, rustc};
|
||||
use run_make_support::{dynamic_lib_name, llvm_objdump, llvm_readobj, rustc, target};
|
||||
|
||||
fn main() {
|
||||
rustc().crate_type("lib").input("lib.rs").run();
|
||||
rustc().crate_type("cdylib").link_args("-Tlinker.ld").input("main.rs").run();
|
||||
let mut main = rustc();
|
||||
main.crate_type("cdylib");
|
||||
if target().contains("linux") {
|
||||
main.link_args("-Tlinker.ld");
|
||||
}
|
||||
main.input("main.rs").run();
|
||||
|
||||
// Ensure `#[used]` and `KEEP`-ed section is there
|
||||
llvm_objdump()
|
||||
.arg("--full-contents")
|
||||
|
|
32
tests/ui/attributes/auxiliary/used_pre_main_constructor.rs
Normal file
32
tests/ui/attributes/auxiliary/used_pre_main_constructor.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
//! Add a constructor that runs pre-main, similar to what the `ctor` crate does.
|
||||
//!
|
||||
//! #[ctor]
|
||||
//! fn constructor() {
|
||||
//! println!("constructor");
|
||||
//! }
|
||||
|
||||
//@ no-prefer-dynamic explicitly test with crates that are built as an archive
|
||||
#![crate_type = "rlib"]
|
||||
|
||||
#[cfg_attr(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
),
|
||||
link_section = ".init_array"
|
||||
)]
|
||||
#[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func,mod_init_funcs")]
|
||||
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
|
||||
#[used]
|
||||
static CONSTRUCTOR: extern "C" fn() = constructor;
|
||||
|
||||
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
|
||||
extern "C" fn constructor() {
|
||||
println!("constructor");
|
||||
}
|
16
tests/ui/attributes/used_with_archive.rs
Normal file
16
tests/ui/attributes/used_with_archive.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
//! Ensure that `#[used]` in archives are correctly registered.
|
||||
//!
|
||||
//! Regression test for https://github.com/rust-lang/rust/issues/133491.
|
||||
|
||||
//@ run-pass
|
||||
//@ check-run-results
|
||||
//@ aux-build: used_pre_main_constructor.rs
|
||||
|
||||
//@ ignore-wasm ctor doesn't work on WASM
|
||||
|
||||
// Make sure `rustc` links the archive, but intentionally do not import/use any items.
|
||||
extern crate used_pre_main_constructor as _;
|
||||
|
||||
fn main() {
|
||||
println!("main");
|
||||
}
|
2
tests/ui/attributes/used_with_archive.run.stdout
Normal file
2
tests/ui/attributes/used_with_archive.run.stdout
Normal file
|
@ -0,0 +1,2 @@
|
|||
constructor
|
||||
main
|
Loading…
Add table
Add a link
Reference in a new issue