Skip to content

Commit

Permalink
Make #[used] work when linking with ld64
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Dec 31, 2024
1 parent 2061630 commit 78a1c2d
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 7 deletions.
76 changes: 76 additions & 0 deletions compiler/rustc_codegen_ssa/src/back/apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,82 @@ pub(super) fn macho_platform(target: &Target) -> u32 {
}
}

/// Add relocation and section data needed for a symbol to be considered
/// undefined by ld64.
///
/// Inherently very architecture-specific (unfortunately).
///
/// # New architectures
///
/// The values here can be found by compiling the following program:
///
/// ```c
/// void foo(void);
///
/// void use_foo() {
/// foo();
/// }
/// ```
///
/// With:
///
/// ```console
/// $ clang -c foo.c -O2 -g0 -fno-exceptions -target $CLANG_TARGET
/// ```
///
/// And then inspecting with `objdump -d foo.o` and/or:
///
/// ```no_run
/// use object::read::macho::{MachHeader, MachOFile};
/// use object::{File, Object, ObjectSection};
///
/// fn read(file: MachOFile<'_, impl MachHeader>) {
/// for section in file.sections() {
/// dbg!(section.name().unwrap(), section.data().unwrap(), section.align());
/// for reloc in section.relocations() {
/// dbg!(reloc);
/// }
/// }
/// }
///
/// fn main() {
/// match File::parse(&*std::fs::read("foo.o").unwrap()).unwrap() {
/// File::MachO32(file) => read(file),
/// File::MachO64(file) => read(file),
/// _ => unimplemented!(),
/// }
/// }
/// ```
pub(super) fn add_data_and_relocation(
file: &mut object::write::Object<'_>,
section: object::write::SectionId,
symbol: object::write::SymbolId,
target: &Target,
) -> object::write::Result<()> {
let (data, align, addend, r_type): (&[u8], _, _, _) = match &*target.arch {
"arm" => (&[0xff, 0xf7, 0xfe, 0xbf], 2, 0, object::macho::ARM_THUMB_RELOC_BR22),
"aarch64" => (&[0, 0, 0, 0x14], 4, 0, object::macho::ARM64_RELOC_BRANCH26),
"x86_64" => (
&[0x55, 0x48, 0x89, 0xe5, 0x5d, 0xe9, 0, 0, 0, 0],
16,
-4,
object::macho::X86_64_RELOC_BRANCH,
),
"x86" => (&[0x55, 0x89, 0xe5, 0x5d, 0xe9, 0xf7, 0xff, 0xff, 0xff], 16, -4, 0),
arch => unimplemented!("unsupported Apple architecture {arch:?}"),
};

let offset = file.section_mut(section).append_data(data, align);
file.add_relocation(section, object::write::Relocation {
offset,
addend,
symbol,
flags: object::write::RelocationFlags::MachO { r_type, r_pcrel: true, r_length: 2 },
})?;

Ok(())
}

/// Deployment target or SDK version.
///
/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`.
Expand Down
29 changes: 28 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2070,8 +2070,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::Text).to_vec(),
"__text".into(),
object::SectionKind::Text,
))
} 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 @@ -2085,6 +2097,21 @@ 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.
// In particular, it completely ignores undefined symbols by themselves, and only consider
// undefined symbols if they have relocations.
//
// So to make this trick work on ld64, we need to actually insert a relocation. The
// relocation must be valid though, and hence must point to a valid piece of machine code,
// and hence this is all very architecture-specific.
//
// See the following for a few details on the design of ld64:
// https://github.com/apple-oss-distributions/ld64/blob/ld64-951.9/doc/design/linker.html
if let Some(section) = ld64_section_helper {
apple::add_data_and_relocation(&mut file, section, symbol, &sess.target)
.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-windows differences in object file formats causes errors in the llvm_objdump step.
//@ ignore-wasm 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

0 comments on commit 78a1c2d

Please sign in to comment.