Skip to content

Commit

Permalink
Support raw-dylib link kind on ELF
Browse files Browse the repository at this point in the history
raw-dylib is a link kind that allows rustc to link against a library
without having any library files present.
This currently only exists on Windows. rustc will take all the symbols
from raw-dylib link blocks and put them in an import library, where they
can then be resolved by the linker.

While import libraries don't exist on ELF, it would still be convenient
to have this same functionality. Not having the libraries present at
build-time can be convenient for several reasons, especially
cross-compilation. With raw-dylib, code linking against a library can be
cross-compiled without needing to have these libraries available on the
build machine. If the libc crate makes use of this, it would allow
cross-compilation without having any libc available on the build
machine. This is not yet possible with this implementation, at least
against libc's like glibc that use symbol versioning.
The raw-dylib kind could be extended with support for symbol versioning
in the future.

This implementation is very experimental and I have not tested it very
well. I have tested it for a toy example and the lz4-sys crate, where it
was able to successfully link a binary despite not having a
corresponding library at build-time.
  • Loading branch information
Noratrieb committed Jan 18, 2025
1 parent 8e59cf9 commit be04b07
Show file tree
Hide file tree
Showing 62 changed files with 609 additions and 127 deletions.
311 changes: 270 additions & 41 deletions compiler/rustc_codegen_ssa/src/back/link.rs

Large diffs are not rendered by default.

96 changes: 27 additions & 69 deletions compiler/rustc_codegen_ssa/src/back/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ use itertools::Itertools;
use object::write::{self, StandardSegment, Symbol, SymbolSection};
use object::{
Architecture, BinaryFormat, Endianness, FileFlags, Object, ObjectSection, ObjectSymbol,
SectionFlags, SectionKind, SubArchitecture, SymbolFlags, SymbolKind, SymbolScope, elf, pe,
xcoff,
SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope, elf, pe, xcoff,
};
use rustc_abi::Endian;
use rustc_data_structures::memmap::Mmap;
Expand Down Expand Up @@ -206,61 +205,12 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
Endian::Little => Endianness::Little,
Endian::Big => Endianness::Big,
};
let (architecture, sub_architecture) = match &sess.target.arch[..] {
"arm" => (Architecture::Arm, None),
"aarch64" => (
if sess.target.pointer_width == 32 {
Architecture::Aarch64_Ilp32
} else {
Architecture::Aarch64
},
None,
),
"x86" => (Architecture::I386, None),
"s390x" => (Architecture::S390x, None),
"mips" | "mips32r6" => (Architecture::Mips, None),
"mips64" | "mips64r6" => (Architecture::Mips64, None),
"x86_64" => (
if sess.target.pointer_width == 32 {
Architecture::X86_64_X32
} else {
Architecture::X86_64
},
None,
),
"powerpc" => (Architecture::PowerPc, None),
"powerpc64" => (Architecture::PowerPc64, None),
"riscv32" => (Architecture::Riscv32, None),
"riscv64" => (Architecture::Riscv64, None),
"sparc" => {
if sess.unstable_target_features.contains(&sym::v8plus) {
// Target uses V8+, aka EM_SPARC32PLUS, aka 64-bit V9 but in 32-bit mode
(Architecture::Sparc32Plus, None)
} else {
// Target uses V7 or V8, aka EM_SPARC
(Architecture::Sparc, None)
}
}
"sparc64" => (Architecture::Sparc64, None),
"avr" => (Architecture::Avr, None),
"msp430" => (Architecture::Msp430, None),
"hexagon" => (Architecture::Hexagon, None),
"bpf" => (Architecture::Bpf, None),
"loongarch64" => (Architecture::LoongArch64, None),
"csky" => (Architecture::Csky, None),
"arm64ec" => (Architecture::Aarch64, Some(SubArchitecture::Arm64EC)),
// Unsupported architecture.
_ => return None,
};
let binary_format = if sess.target.is_like_osx {
BinaryFormat::MachO
} else if sess.target.is_like_windows {
BinaryFormat::Coff
} else if sess.target.is_like_aix {
BinaryFormat::Xcoff
} else {
BinaryFormat::Elf
let Some((architecture, sub_architecture)) =
sess.target.object_architecture(&sess.unstable_target_features)
else {
return None;
};
let binary_format = sess.target.binary_format();

let mut file = write::Object::new(binary_format, architecture, endianness);
file.set_sub_architecture(sub_architecture);
Expand Down Expand Up @@ -300,7 +250,26 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static

file.set_mangling(original_mangling);
}
let e_flags = match architecture {
let e_flags = elf_e_flags(architecture, sess);
// adapted from LLVM's `MCELFObjectTargetWriter::getOSABI`
let os_abi = elf_os_abi(sess);
let abi_version = 0;
add_gnu_property_note(&mut file, architecture, binary_format, endianness);
file.flags = FileFlags::Elf { os_abi, abi_version, e_flags };
Some(file)
}

pub(super) fn elf_os_abi(sess: &Session) -> u8 {
match sess.target.options.os.as_ref() {
"hermit" => elf::ELFOSABI_STANDALONE,
"freebsd" => elf::ELFOSABI_FREEBSD,
"solaris" => elf::ELFOSABI_SOLARIS,
_ => elf::ELFOSABI_NONE,
}
}

pub(super) fn elf_e_flags(architecture: Architecture, sess: &Session) -> u32 {
match architecture {
Architecture::Mips => {
let arch = match sess.target.options.cpu.as_ref() {
"mips1" => elf::EF_MIPS_ARCH_1,
Expand Down Expand Up @@ -391,18 +360,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
e_flags
}
_ => 0,
};
// adapted from LLVM's `MCELFObjectTargetWriter::getOSABI`
let os_abi = match sess.target.options.os.as_ref() {
"hermit" => elf::ELFOSABI_STANDALONE,
"freebsd" => elf::ELFOSABI_FREEBSD,
"solaris" => elf::ELFOSABI_SOLARIS,
_ => elf::ELFOSABI_NONE,
};
let abi_version = 0;
add_gnu_property_note(&mut file, architecture, binary_format, endianness);
file.flags = FileFlags::Elf { os_abi, abi_version, e_flags };
Some(file)
}
}

/// Mach-O files contain information about:
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,8 @@ declare_features! (
(unstable, precise_capturing_in_traits, "1.83.0", Some(130044)),
/// Allows macro attributes on expressions, statements and non-inline modules.
(unstable, proc_macro_hygiene, "1.30.0", Some(54727)),
/// Allows the use of raw-dylibs on ELF platforms
(incomplete, raw_dylib_elf, "CURRENT_RUSTC_VERSION", Some(135694)),
/// Makes `&` and `&mut` patterns eat only one layer of references in Rust 2024.
(incomplete, ref_pat_eat_one_layer_2024, "1.79.0", Some(123076)),
/// Makes `&` and `&mut` patterns eat only one layer of references in Rust 2024—structural variant
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_metadata/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ metadata_prev_alloc_error_handler =
metadata_prev_global_alloc =
previous global allocator defined here
metadata_raw_dylib_elf_unstable =
link kind `raw-dylib` is unstable on ELF platforms
metadata_raw_dylib_no_nul =
link name must not contain NUL characters if link kind is `raw-dylib`
Expand Down
21 changes: 19 additions & 2 deletions compiler/rustc_metadata/src/native_libs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use rustc_session::search_paths::PathKind;
use rustc_session::utils::NativeLibKind;
use rustc_span::def_id::{DefId, LOCAL_CRATE};
use rustc_span::{Symbol, sym};
use rustc_target::spec::LinkSelfContainedComponents;
use rustc_target::spec::{BinaryFormat, LinkSelfContainedComponents};

use crate::{errors, fluent_generated};

Expand Down Expand Up @@ -263,9 +263,26 @@ impl<'tcx> Collector<'tcx> {
NativeLibKind::Framework { as_needed: None }
}
"raw-dylib" => {
if !sess.target.is_like_windows {
if sess.target.is_like_windows {
// raw-dylib is stable and working on Windows
} else if sess.target.binary_format() == BinaryFormat::Elf
&& features.raw_dylib_elf()
{
// raw-dylib is unstable on ELF, but the user opted in
} else if sess.target.binary_format() == BinaryFormat::Elf
&& sess.is_nightly_build()
{
feature_err(
sess,
sym::raw_dylib_elf,
span,
fluent_generated::metadata_raw_dylib_elf_unstable,
)
.emit();
} else {
sess.dcx().emit_err(errors::RawDylibOnlyWindows { span });
}

NativeLibKind::RawDylib
}
"link-arg" => {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_session/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub enum NativeLibKind {
as_needed: Option<bool>,
},
/// Dynamic library (e.g. `foo.dll` on Windows) without a corresponding import library.
/// On Linux, it refers to a generated shared library stub.
RawDylib,
/// A macOS-specific kind of dynamic libraries.
Framework {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,7 @@ symbols! {
quote,
range_inclusive_new,
raw_dylib,
raw_dylib_elf,
raw_eq,
raw_identifiers,
raw_ref_op,
Expand Down
68 changes: 67 additions & 1 deletion compiler/rustc_target/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{fmt, io};

use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_fs_util::try_canonicalize;
use rustc_macros::{Decodable, Encodable, HashStable_Generic};
use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
Expand All @@ -69,6 +69,7 @@ mod base;
mod json;

pub use base::avr_gnu::ef_avr_arch;
pub use object::BinaryFormat;

/// Linker is called through a C/C++ compiler.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
Expand Down Expand Up @@ -3394,6 +3395,71 @@ impl Target {
s => s.clone(),
}
}

pub fn binary_format(&self) -> object::BinaryFormat {
if self.is_like_osx {
object::BinaryFormat::MachO
} else if self.is_like_windows {
object::BinaryFormat::Coff
} else if self.is_like_aix {
object::BinaryFormat::Xcoff
} else {
object::BinaryFormat::Elf
}
}

pub fn object_architecture(
&self,
unstable_target_features: &FxIndexSet<Symbol>,
) -> Option<(object::Architecture, Option<object::SubArchitecture>)> {
use object::Architecture;
Some(match self.arch.as_ref() {
"arm" => (Architecture::Arm, None),
"aarch64" => (
if self.pointer_width == 32 {
Architecture::Aarch64_Ilp32
} else {
Architecture::Aarch64
},
None,
),
"x86" => (Architecture::I386, None),
"s390x" => (Architecture::S390x, None),
"mips" | "mips32r6" => (Architecture::Mips, None),
"mips64" | "mips64r6" => (Architecture::Mips64, None),
"x86_64" => (
if self.pointer_width == 32 {
Architecture::X86_64_X32
} else {
Architecture::X86_64
},
None,
),
"powerpc" => (Architecture::PowerPc, None),
"powerpc64" => (Architecture::PowerPc64, None),
"riscv32" => (Architecture::Riscv32, None),
"riscv64" => (Architecture::Riscv64, None),
"sparc" => {
if unstable_target_features.contains(&sym::v8plus) {
// Target uses V8+, aka EM_SPARC32PLUS, aka 64-bit V9 but in 32-bit mode
(Architecture::Sparc32Plus, None)
} else {
// Target uses V7 or V8, aka EM_SPARC
(Architecture::Sparc, None)
}
}
"sparc64" => (Architecture::Sparc64, None),
"avr" => (Architecture::Avr, None),
"msp430" => (Architecture::Msp430, None),
"hexagon" => (Architecture::Hexagon, None),
"bpf" => (Architecture::Bpf, None),
"loongarch64" => (Architecture::LoongArch64, None),
"csky" => (Architecture::Csky, None),
"arm64ec" => (Architecture::Aarch64, Some(object::SubArchitecture::Arm64EC)),
// Unsupported architecture.
_ => return None,
})
}
}

/// Either a target tuple string or a path to a JSON file.
Expand Down
2 changes: 2 additions & 0 deletions src/tools/compiletest/src/directive-list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"ignore-coverage-run",
"ignore-cross-compile",
"ignore-eabi",
"ignore-elf",
"ignore-emscripten",
"ignore-endian-big",
"ignore-enzyme",
Expand Down Expand Up @@ -175,6 +176,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"only-beta",
"only-bpf",
"only-cdb",
"only-elf",
"only-gnu",
"only-i686-pc-windows-gnu",
"only-i686-pc-windows-msvc",
Expand Down
9 changes: 9 additions & 0 deletions src/tools/compiletest/src/header/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ fn parse_cfg_name_directive<'a>(
message: "when the target vendor is Apple"
}

condition! {
name: "elf",
condition: !config.target.contains("windows")
&& !config.target.contains("apple")
&& !config.target.contains("aix")
&& !config.target.contains("uefi"),
message: "when the target binary format is ELF"
}

condition! {
name: "enzyme",
condition: config.has_enzyme,
Expand Down
3 changes: 3 additions & 0 deletions tests/run-make/raw-dylib-elf-verbatim/library.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
int this_is_a_library_function() {
return 42;
}
11 changes: 11 additions & 0 deletions tests/run-make/raw-dylib-elf-verbatim/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![feature(raw_dylib_elf)]
#![allow(incomplete_features)]

#[link(name = "liblibrary.so.1", kind = "raw-dylib", modifiers = "+verbatim")]
unsafe extern "C" {
safe fn this_is_a_library_function() -> core::ffi::c_int;
}

fn main() {
println!("{}", this_is_a_library_function())
}
1 change: 1 addition & 0 deletions tests/run-make/raw-dylib-elf-verbatim/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
26 changes: 26 additions & 0 deletions tests/run-make/raw-dylib-elf-verbatim/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//@ only-elf

//! Ensure ELF raw-dylib is able to link against the verbatim versioned library
//! without it being present, and then be executed against this library.
use run_make_support::{build_native_dynamic_lib, cwd, diff, rfs, run, rustc};

fn main() {
// We compile the binary without having the library present.
// We also set the rpath to the current directory so we can pick up the library at runtime.
rustc()
.crate_type("bin")
.input("main.rs")
.arg(&format!("-Wl,-rpath={}", cwd().display()))
.run();

// Now, *after* building the binary, we build the library...
build_native_dynamic_lib("library");
// ... rename it to have the versioned library name...
rfs::rename("liblibrary.so", "liblibrary.so.1");

// ... and run with this library, ensuring it was linked correctly at runtime.
let output = run("main").stdout_utf8();

diff().expected_file("output.txt").actual_text("actual", output).run();
}
3 changes: 3 additions & 0 deletions tests/run-make/raw-dylib-elf/library.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
int this_is_a_library_function() {
return 42;
}
11 changes: 11 additions & 0 deletions tests/run-make/raw-dylib-elf/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![feature(raw_dylib_elf)]
#![allow(incomplete_features)]

#[link(name = "library", kind = "raw-dylib")]
unsafe extern "C" {
safe fn this_is_a_library_function() -> core::ffi::c_int;
}

fn main() {
println!("{}", this_is_a_library_function())
}
1 change: 1 addition & 0 deletions tests/run-make/raw-dylib-elf/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
Loading

0 comments on commit be04b07

Please sign in to comment.