Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make #[used] work when linking with ld64 #133832

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
84 changes: 84 additions & 0 deletions compiler/rustc_codegen_ssa/src/back/apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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`.
Expand Down
59 changes: 56 additions & 3 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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,
Expand All @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion tests/run-make/include-all-symbols-linking/lib.rs
Original file line number Diff line number Diff line change
@@ -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];
}
Expand Down
15 changes: 10 additions & 5 deletions tests/run-make/include-all-symbols-linking/rmake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
32 changes: 32 additions & 0 deletions tests/ui/attributes/auxiliary/used_pre_main_constructor.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions tests/ui/attributes/used_with_archive.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 0 deletions tests/ui/attributes/used_with_archive.run.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
constructor
main
Loading