Support raw-dylib link kind on ELF

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.
This commit is contained in:
Noratrieb 2025-01-18 18:19:42 +01:00
parent ac91805f31
commit a954c51280
69 changed files with 839 additions and 207 deletions

View File

@ -1,3 +1,5 @@
mod raw_dylib;
use std::collections::BTreeSet;
use std::ffi::OsString;
use std::fs::{File, OpenOptions, read};
@ -12,7 +14,7 @@ use itertools::Itertools;
use regex::Regex;
use rustc_arena::TypedArena;
use rustc_ast::CRATE_NODE_ID;
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_data_structures::fx::FxIndexSet;
use rustc_data_structures::memmap::Mmap;
use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
@ -30,7 +32,6 @@ use rustc_session::config::{
self, CFGuard, CrateType, DebugInfo, LinkerFeaturesCli, OutFileName, OutputFilenames,
OutputType, PrintKind, SplitDwarfKind, Strip,
};
use rustc_session::cstore::DllImport;
use rustc_session::lint::builtin::LINKER_MESSAGES;
use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename};
use rustc_session::search_paths::PathKind;
@ -41,22 +42,21 @@ use rustc_session::{Session, filesearch};
use rustc_span::Symbol;
use rustc_target::spec::crt_objects::CrtObjects;
use rustc_target::spec::{
Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFeatures,
LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
SplitDebuginfo,
BinaryFormat, Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault,
LinkerFeatures, LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel,
SanitizerSet, SplitDebuginfo,
};
use tempfile::Builder as TempFileBuilder;
use tracing::{debug, info, warn};
use super::archive::{ArchiveBuilder, ArchiveBuilderBuilder, ImportLibraryItem};
use super::archive::{ArchiveBuilder, ArchiveBuilderBuilder};
use super::command::Command;
use super::linker::{self, Linker};
use super::metadata::{MetadataPosition, create_wrapper_file};
use super::rpath::{self, RPathConfig};
use super::{apple, versioned_llvm_target};
use crate::{
CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors,
looks_like_rust_object_file,
CodegenResults, CompiledModule, CrateInfo, NativeLib, errors, looks_like_rust_object_file,
};
pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) {
@ -376,16 +376,22 @@ fn link_rlib<'a>(
}
}
for output_path in create_dll_import_libs(
sess,
archive_builder_builder,
codegen_results.crate_info.used_libraries.iter(),
tmpdir.as_ref(),
true,
) {
ab.add_archive(&output_path, Box::new(|_| false)).unwrap_or_else(|error| {
sess.dcx().emit_fatal(errors::AddNativeLibrary { library_path: output_path, error });
});
// On Windows, we add the raw-dylib import libraries to the rlibs already.
// But on ELF, this is not possible, as a shared object cannot be a member of a static library.
// Instead, we add all raw-dylibs to the final link on ELF.
if sess.target.is_like_windows {
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
sess,
archive_builder_builder,
codegen_results.crate_info.used_libraries.iter(),
tmpdir.as_ref(),
true,
) {
ab.add_archive(&output_path, Box::new(|_| false)).unwrap_or_else(|error| {
sess.dcx()
.emit_fatal(errors::AddNativeLibrary { library_path: output_path, error });
});
}
}
if let Some(trailing_metadata) = trailing_metadata {
@ -426,108 +432,6 @@ fn link_rlib<'a>(
ab
}
/// Extract all symbols defined in raw-dylib libraries, collated by library name.
///
/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
/// then the CodegenResults value contains one NativeLib instance for each block. However, the
/// linker appears to expect only a single import library for each library used, so we need to
/// collate the symbols together by library name before generating the import libraries.
fn collate_raw_dylibs<'a>(
sess: &Session,
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
) -> Vec<(String, Vec<DllImport>)> {
// Use index maps to preserve original order of imports and libraries.
let mut dylib_table = FxIndexMap::<String, FxIndexMap<Symbol, &DllImport>>::default();
for lib in used_libraries {
if lib.kind == NativeLibKind::RawDylib {
let ext = if lib.verbatim { "" } else { ".dll" };
let name = format!("{}{}", lib.name, ext);
let imports = dylib_table.entry(name.clone()).or_default();
for import in &lib.dll_imports {
if let Some(old_import) = imports.insert(import.name, import) {
// FIXME: when we add support for ordinals, figure out if we need to do anything
// if we have two DllImport values with the same name but different ordinals.
if import.calling_convention != old_import.calling_convention {
sess.dcx().emit_err(errors::MultipleExternalFuncDecl {
span: import.span,
function: import.name,
library_name: &name,
});
}
}
}
}
}
sess.dcx().abort_if_errors();
dylib_table
.into_iter()
.map(|(name, imports)| {
(name, imports.into_iter().map(|(_, import)| import.clone()).collect())
})
.collect()
}
fn create_dll_import_libs<'a>(
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
tmpdir: &Path,
is_direct_dependency: bool,
) -> Vec<PathBuf> {
collate_raw_dylibs(sess, used_libraries)
.into_iter()
.map(|(raw_dylib_name, raw_dylib_imports)| {
let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" };
let output_path = tmpdir.join(format!("{raw_dylib_name}{name_suffix}.lib"));
let mingw_gnu_toolchain = common::is_mingw_gnu_toolchain(&sess.target);
let items: Vec<ImportLibraryItem> = raw_dylib_imports
.iter()
.map(|import: &DllImport| {
if sess.target.arch == "x86" {
ImportLibraryItem {
name: common::i686_decorated_name(
import,
mingw_gnu_toolchain,
false,
false,
),
ordinal: import.ordinal(),
symbol_name: import.is_missing_decorations().then(|| {
common::i686_decorated_name(
import,
mingw_gnu_toolchain,
false,
true,
)
}),
is_data: !import.is_fn,
}
} else {
ImportLibraryItem {
name: import.name.to_string(),
ordinal: import.ordinal(),
symbol_name: None,
is_data: !import.is_fn,
}
}
})
.collect();
archive_builder_builder.create_dll_import_lib(
sess,
&raw_dylib_name,
items,
&output_path,
);
output_path
})
.collect()
}
/// Create a static archive.
///
/// This is essentially the same thing as an rlib, but it also involves adding all of the upstream
@ -2418,15 +2322,39 @@ fn linker_with_args(
link_output_kind,
);
// Raw-dylibs from all crates.
let raw_dylib_dir = tmpdir.join("raw-dylibs");
if sess.target.binary_format == BinaryFormat::Elf {
// On ELF we can't pass the raw-dylibs stubs to the linker as a path,
// instead we need to pass them via -l. To find the stub, we need to add
// the directory of the stub to the linker search path.
// We make an extra directory for this to avoid polluting the search path.
if let Err(error) = fs::create_dir(&raw_dylib_dir) {
sess.dcx().emit_fatal(errors::CreateTempDir { error })
}
cmd.include_path(&raw_dylib_dir);
}
// Link with the import library generated for any raw-dylib functions.
for output_path in create_dll_import_libs(
sess,
archive_builder_builder,
codegen_results.crate_info.used_libraries.iter(),
tmpdir,
true,
) {
cmd.add_object(&output_path);
if sess.target.is_like_windows {
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
sess,
archive_builder_builder,
codegen_results.crate_info.used_libraries.iter(),
tmpdir,
true,
) {
cmd.add_object(&output_path);
}
} else {
for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects(
sess,
codegen_results.crate_info.used_libraries.iter(),
&raw_dylib_dir,
) {
// Always use verbatim linkage, see comments in create_raw_dylib_elf_stub_shared_objects.
cmd.link_dylib_by_name(&link_path, true, false);
}
}
// As with add_upstream_native_libraries, we need to add the upstream raw-dylib symbols in case
// they are used within inlined functions or instantiated generic functions. We do this *after*
@ -2445,19 +2373,35 @@ fn linker_with_args(
.native_libraries
.iter()
.filter_map(|(&cnum, libraries)| {
(dependency_linkage[cnum] != Linkage::Static).then_some(libraries)
if sess.target.is_like_windows {
(dependency_linkage[cnum] != Linkage::Static).then_some(libraries)
} else {
Some(libraries)
}
})
.flatten()
.collect::<Vec<_>>();
native_libraries_from_nonstatics.sort_unstable_by(|a, b| a.name.as_str().cmp(b.name.as_str()));
for output_path in create_dll_import_libs(
sess,
archive_builder_builder,
native_libraries_from_nonstatics,
tmpdir,
false,
) {
cmd.add_object(&output_path);
if sess.target.is_like_windows {
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
sess,
archive_builder_builder,
native_libraries_from_nonstatics,
tmpdir,
false,
) {
cmd.add_object(&output_path);
}
} else {
for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects(
sess,
native_libraries_from_nonstatics,
&raw_dylib_dir,
) {
// Always use verbatim linkage, see comments in create_raw_dylib_elf_stub_shared_objects.
cmd.link_dylib_by_name(&link_path, true, false);
}
}
// Library linking above uses some global state for things like `-Bstatic`/`-Bdynamic` to make

View File

@ -0,0 +1,373 @@
use std::fs;
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use rustc_abi::Endian;
use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN};
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::stable_hasher::StableHasher;
use rustc_hashes::Hash128;
use rustc_session::Session;
use rustc_session::cstore::DllImport;
use rustc_session::utils::NativeLibKind;
use rustc_span::Symbol;
use crate::back::archive::ImportLibraryItem;
use crate::back::link::ArchiveBuilderBuilder;
use crate::errors::ErrorCreatingImportLibrary;
use crate::{NativeLib, common, errors};
/// Extract all symbols defined in raw-dylib libraries, collated by library name.
///
/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
/// then the CodegenResults value contains one NativeLib instance for each block. However, the
/// linker appears to expect only a single import library for each library used, so we need to
/// collate the symbols together by library name before generating the import libraries.
fn collate_raw_dylibs_windows<'a>(
sess: &Session,
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
) -> Vec<(String, Vec<DllImport>)> {
// Use index maps to preserve original order of imports and libraries.
let mut dylib_table = FxIndexMap::<String, FxIndexMap<Symbol, &DllImport>>::default();
for lib in used_libraries {
if lib.kind == NativeLibKind::RawDylib {
let ext = if lib.verbatim { "" } else { ".dll" };
let name = format!("{}{}", lib.name, ext);
let imports = dylib_table.entry(name.clone()).or_default();
for import in &lib.dll_imports {
if let Some(old_import) = imports.insert(import.name, import) {
// FIXME: when we add support for ordinals, figure out if we need to do anything
// if we have two DllImport values with the same name but different ordinals.
if import.calling_convention != old_import.calling_convention {
sess.dcx().emit_err(errors::MultipleExternalFuncDecl {
span: import.span,
function: import.name,
library_name: &name,
});
}
}
}
}
}
sess.dcx().abort_if_errors();
dylib_table
.into_iter()
.map(|(name, imports)| {
(name, imports.into_iter().map(|(_, import)| import.clone()).collect())
})
.collect()
}
pub(super) fn create_raw_dylib_dll_import_libs<'a>(
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
tmpdir: &Path,
is_direct_dependency: bool,
) -> Vec<PathBuf> {
collate_raw_dylibs_windows(sess, used_libraries)
.into_iter()
.map(|(raw_dylib_name, raw_dylib_imports)| {
let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" };
let output_path = tmpdir.join(format!("{raw_dylib_name}{name_suffix}.lib"));
let mingw_gnu_toolchain = common::is_mingw_gnu_toolchain(&sess.target);
let items: Vec<ImportLibraryItem> = raw_dylib_imports
.iter()
.map(|import: &DllImport| {
if sess.target.arch == "x86" {
ImportLibraryItem {
name: common::i686_decorated_name(
import,
mingw_gnu_toolchain,
false,
false,
),
ordinal: import.ordinal(),
symbol_name: import.is_missing_decorations().then(|| {
common::i686_decorated_name(
import,
mingw_gnu_toolchain,
false,
true,
)
}),
is_data: !import.is_fn,
}
} else {
ImportLibraryItem {
name: import.name.to_string(),
ordinal: import.ordinal(),
symbol_name: None,
is_data: !import.is_fn,
}
}
})
.collect();
archive_builder_builder.create_dll_import_lib(
sess,
&raw_dylib_name,
items,
&output_path,
);
output_path
})
.collect()
}
/// Extract all symbols defined in raw-dylib libraries, collated by library name.
///
/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
/// then the CodegenResults value contains one NativeLib instance for each block. However, the
/// linker appears to expect only a single import library for each library used, so we need to
/// collate the symbols together by library name before generating the import libraries.
fn collate_raw_dylibs_elf<'a>(
sess: &Session,
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
) -> Vec<(String, Vec<DllImport>)> {
// Use index maps to preserve original order of imports and libraries.
let mut dylib_table = FxIndexMap::<String, FxIndexMap<Symbol, &DllImport>>::default();
for lib in used_libraries {
if lib.kind == NativeLibKind::RawDylib {
let filename = if lib.verbatim {
lib.name.as_str().to_owned()
} else {
let ext = sess.target.dll_suffix.as_ref();
let prefix = sess.target.dll_prefix.as_ref();
format!("{prefix}{}{ext}", lib.name)
};
let imports = dylib_table.entry(filename.clone()).or_default();
for import in &lib.dll_imports {
imports.insert(import.name, import);
}
}
}
sess.dcx().abort_if_errors();
dylib_table
.into_iter()
.map(|(name, imports)| {
(name, imports.into_iter().map(|(_, import)| import.clone()).collect())
})
.collect()
}
pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>(
sess: &Session,
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
raw_dylib_so_dir: &Path,
) -> Vec<String> {
collate_raw_dylibs_elf(sess, used_libraries)
.into_iter()
.map(|(load_filename, raw_dylib_imports)| {
use std::hash::Hash;
// `load_filename` is the *target/loader* filename that will end up in NEEDED.
// Usually this will be something like `libc.so` or `libc.so.6` but with
// verbatim it might also be an absolute path.
// To be able to support this properly, we always put this load filename
// into the SONAME of the library and link it via a temporary file with a random name.
// This also avoids naming conflicts with non-raw-dylib linkage of the same library.
let shared_object = create_elf_raw_dylib_stub(sess, &load_filename, &raw_dylib_imports);
let mut file_name_hasher = StableHasher::new();
load_filename.hash(&mut file_name_hasher);
for raw_dylib in raw_dylib_imports {
raw_dylib.name.as_str().hash(&mut file_name_hasher);
}
let library_filename: Hash128 = file_name_hasher.finish();
let temporary_lib_name = format!(
"{}{}{}",
sess.target.dll_prefix,
library_filename.as_u128().to_base_fixed_len(CASE_INSENSITIVE),
sess.target.dll_suffix
);
let link_path = raw_dylib_so_dir.join(&temporary_lib_name);
let file = match fs::File::create_new(&link_path) {
Ok(file) => file,
Err(error) => sess.dcx().emit_fatal(ErrorCreatingImportLibrary {
lib_name: &load_filename,
error: error.to_string(),
}),
};
if let Err(error) = BufWriter::new(file).write_all(&shared_object) {
sess.dcx().emit_fatal(ErrorCreatingImportLibrary {
lib_name: &load_filename,
error: error.to_string(),
});
};
temporary_lib_name
})
.collect()
}
/// Create an ELF .so stub file for raw-dylib.
/// It exports all the provided symbols, but is otherwise empty.
fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]) -> Vec<u8> {
use object::write::elf as write;
use object::{Architecture, elf};
let mut stub_buf = Vec::new();
// Build the stub ELF using the object crate.
// The high-level portable API does not allow for the fine-grained control we need,
// so this uses the low-level object::write::elf API.
// The low-level API consists of two stages: reservation and writing.
// We first reserve space for all the things in the binary and then write them.
// It is important that the order of reservation matches the order of writing.
// The object crate contains many debug asserts that fire if you get this wrong.
let endianness = match sess.target.options.endian {
Endian::Little => object::Endianness::Little,
Endian::Big => object::Endianness::Big,
};
let mut stub = write::Writer::new(endianness, true, &mut stub_buf);
// These initial reservations don't reserve any bytes in the binary yet,
// they just allocate in the internal data structures.
// First, we crate the dynamic symbol table. It starts with a null symbol
// and then all the symbols and their dynamic strings.
stub.reserve_null_dynamic_symbol_index();
let dynstrs = symbols
.iter()
.map(|sym| {
stub.reserve_dynamic_symbol_index();
(sym, stub.add_dynamic_string(sym.name.as_str().as_bytes()))
})
.collect::<Vec<_>>();
let soname = stub.add_dynamic_string(soname.as_bytes());
// Reserve the sections.
// We have the minimal sections for a dynamic SO and .text where we point our dummy symbols to.
stub.reserve_shstrtab_section_index();
let text_section_name = stub.add_section_name(".text".as_bytes());
let text_section = stub.reserve_section_index();
stub.reserve_dynstr_section_index();
stub.reserve_dynsym_section_index();
stub.reserve_dynamic_section_index();
// These reservations now determine the actual layout order of the object file.
stub.reserve_file_header();
stub.reserve_shstrtab();
stub.reserve_section_headers();
stub.reserve_dynstr();
stub.reserve_dynsym();
stub.reserve_dynamic(2); // DT_SONAME, DT_NULL
// First write the ELF header with the arch information.
let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features)
else {
sess.dcx().fatal(format!(
"raw-dylib is not supported for the architecture `{}`",
sess.target.arch
));
};
let e_machine = match (arch, sub_arch) {
(Architecture::Aarch64, None) => elf::EM_AARCH64,
(Architecture::Aarch64_Ilp32, None) => elf::EM_AARCH64,
(Architecture::Arm, None) => elf::EM_ARM,
(Architecture::Avr, None) => elf::EM_AVR,
(Architecture::Bpf, None) => elf::EM_BPF,
(Architecture::Csky, None) => elf::EM_CSKY,
(Architecture::E2K32, None) => elf::EM_MCST_ELBRUS,
(Architecture::E2K64, None) => elf::EM_MCST_ELBRUS,
(Architecture::I386, None) => elf::EM_386,
(Architecture::X86_64, None) => elf::EM_X86_64,
(Architecture::X86_64_X32, None) => elf::EM_X86_64,
(Architecture::Hexagon, None) => elf::EM_HEXAGON,
(Architecture::LoongArch64, None) => elf::EM_LOONGARCH,
(Architecture::M68k, None) => elf::EM_68K,
(Architecture::Mips, None) => elf::EM_MIPS,
(Architecture::Mips64, None) => elf::EM_MIPS,
(Architecture::Mips64_N32, None) => elf::EM_MIPS,
(Architecture::Msp430, None) => elf::EM_MSP430,
(Architecture::PowerPc, None) => elf::EM_PPC,
(Architecture::PowerPc64, None) => elf::EM_PPC64,
(Architecture::Riscv32, None) => elf::EM_RISCV,
(Architecture::Riscv64, None) => elf::EM_RISCV,
(Architecture::S390x, None) => elf::EM_S390,
(Architecture::Sbf, None) => elf::EM_SBF,
(Architecture::Sharc, None) => elf::EM_SHARC,
(Architecture::Sparc, None) => elf::EM_SPARC,
(Architecture::Sparc32Plus, None) => elf::EM_SPARC32PLUS,
(Architecture::Sparc64, None) => elf::EM_SPARCV9,
(Architecture::Xtensa, None) => elf::EM_XTENSA,
_ => {
sess.dcx().fatal(format!(
"raw-dylib is not supported for the architecture `{}`",
sess.target.arch
));
}
};
stub.write_file_header(&write::FileHeader {
os_abi: crate::back::metadata::elf_os_abi(sess),
abi_version: 0,
e_type: object::elf::ET_DYN,
e_machine,
e_entry: 0,
e_flags: crate::back::metadata::elf_e_flags(arch, sess),
})
.unwrap();
// .shstrtab
stub.write_shstrtab();
// Section headers
stub.write_null_section_header();
stub.write_shstrtab_section_header();
// Create a dummy .text section for our dummy symbols.
stub.write_section_header(&write::SectionHeader {
name: Some(text_section_name),
sh_type: elf::SHT_PROGBITS,
sh_flags: 0,
sh_addr: 0,
sh_offset: 0,
sh_size: 0,
sh_link: 0,
sh_info: 0,
sh_addralign: 1,
sh_entsize: 0,
});
stub.write_dynstr_section_header(0);
stub.write_dynsym_section_header(0, 1);
stub.write_dynamic_section_header(0);
// .dynstr
stub.write_dynstr();
// .dynsym
stub.write_null_dynamic_symbol();
for (_, name) in dynstrs {
stub.write_dynamic_symbol(&write::Sym {
name: Some(name),
st_info: (elf::STB_GLOBAL << 4) | elf::STT_NOTYPE,
st_other: elf::STV_DEFAULT,
section: Some(text_section),
st_shndx: 0, // ignored by object in favor of the `section` field
st_value: 0,
st_size: 0,
});
}
// .dynamic
// the DT_SONAME will be used by the linker to populate DT_NEEDED
// which the loader uses to find the library.
// DT_NULL terminates the .dynamic table.
stub.write_dynamic_string(elf::DT_SONAME, soname);
stub.write_dynamic(elf::DT_NULL, 0);
stub_buf
}

View File

@ -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;
@ -206,51 +205,10 @@ 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 Some((architecture, sub_architecture)) =
sess.target.object_architecture(&sess.unstable_target_features)
else {
return None;
};
let binary_format = sess.target.binary_format.to_object();
@ -292,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,
@ -383,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:

View File

@ -599,6 +599,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

View File

@ -244,6 +244,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`

View File

@ -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};
@ -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" => {

View File

@ -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 {

View File

@ -1624,6 +1624,7 @@ symbols! {
quote,
range_inclusive_new,
raw_dylib,
raw_dylib_elf,
raw_eq,
raw_identifiers,
raw_ref_op,

View File

@ -43,7 +43,7 @@ use std::str::FromStr;
use std::{fmt, io};
use rustc_abi::{Endian, ExternAbi, Integer, Size, TargetDataLayout, TargetDataLayoutErrors};
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};
@ -3535,6 +3535,59 @@ impl Target {
s => s.clone(),
}
}
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.

View File

@ -142,6 +142,7 @@ Some examples of `X` in `ignore-X` or `only-X`:
matches that target as well as the emscripten targets.
- Pointer width: `32bit`, `64bit`
- Endianness: `endian-big`
- Binary format: `elf`
- Stage: `stage0`, `stage1`, `stage2`
- Channel: `stable`, `beta`
- When cross compiling: `cross-compile`

View File

@ -52,6 +52,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"ignore-coverage-run",
"ignore-cross-compile",
"ignore-eabi",
"ignore-elf",
"ignore-emscripten",
"ignore-endian-big",
"ignore-enzyme",
@ -182,6 +183,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"only-bpf",
"only-cdb",
"only-dist",
"only-elf",
"only-emscripten",
"only-gnu",
"only-i686-pc-windows-gnu",

View File

@ -166,6 +166,16 @@ fn parse_cfg_name_directive<'a>(
message: "when the target vendor is Apple"
}
condition! {
name: "elf",
condition: !config.target.contains("windows")
&& !config.target.contains("wasm")
&& !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,

View File

@ -60,6 +60,8 @@ fn main() {
regex::escape(run_make_support::build_root().to_str().unwrap()),
"/build-root",
)
.normalize(r#""[^"]*\/symbols.o""#, "\"/symbols.o\"")
.normalize(r#""[^"]*\/raw-dylibs""#, "\"/raw-dylibs\"")
.run();
}

View File

@ -1,6 +1,6 @@
error: linking with `./fake-linker` failed: exit status: 1
|
= note: "./fake-linker" "-m64" "/tmp/rustc/symbols.o" "<2 object files omitted>" "-Wl,--as-needed" "-Wl,-Bstatic" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib/{libstd-*,libpanic_unwind-*,libobject-*,libmemchr-*,libaddr2line-*,libgimli-*,librustc_demangle-*,libstd_detect-*,libhashbrown-*,librustc_std_workspace_alloc-*,libminiz_oxide-*,libadler2-*,libunwind-*,libcfg_if-*,liblibc-*,liballoc-*,librustc_std_workspace_core-*,libcore-*,libcompiler_builtins-*}.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/build-root/test/run-make/linker-warning/rmake_out" "-L" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "main" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "run_make_error"
= note: "./fake-linker" "-m64" "/symbols.o" "<2 object files omitted>" "-Wl,--as-needed" "-Wl,-Bstatic" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib/{libstd-*,libpanic_unwind-*,libobject-*,libmemchr-*,libaddr2line-*,libgimli-*,librustc_demangle-*,libstd_detect-*,libhashbrown-*,librustc_std_workspace_alloc-*,libminiz_oxide-*,libadler2-*,libunwind-*,libcfg_if-*,liblibc-*,liballoc-*,librustc_std_workspace_core-*,libcore-*,libcompiler_builtins-*}.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-L" "/raw-dylibs" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/build-root/test/run-make/linker-warning/rmake_out" "-L" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "main" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "run_make_error"
= note: some arguments are omitted. use `--verbose` to show all linker arguments
= note: error: baz

View File

@ -0,0 +1,11 @@
#![feature(raw_dylib_elf)]
#![allow(incomplete_features)]
#[link(name = "/absolute-path/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())
}

View File

@ -0,0 +1 @@
42

View File

@ -0,0 +1,20 @@
//@ only-elf
//@ ignore-cross-compile: Runs a binary.
//@ needs-dynamic-linking
// FIXME(raw_dylib_elf): Debug the failures on other targets.
//@ only-gnu
//@ only-x86_64
//! Ensure ELF raw-dylib is able to link against a non-existent verbatim absolute path
//! by embedding the absolute path in the DT_SONAME and passing a different path for
//! the linker for the stub.
use run_make_support::{build_native_dynamic_lib, cwd, diff, rfs, run, rustc};
fn main() {
// We compile the binary without having the library present.
// The verbatim library name is an absolute path.
rustc().crate_type("bin").input("main.rs").run();
// FIXME(raw_dylib_elf): Read the NEEDED of the library to ensure it's the absolute path.
}

View File

@ -0,0 +1,3 @@
int this_is_a_library_function() {
return 42;
}

View File

@ -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())
}

View File

@ -0,0 +1 @@
42

View File

@ -0,0 +1,31 @@
//@ only-elf
//@ ignore-cross-compile: Runs a binary.
//@ needs-dynamic-linking
// FIXME(raw_dylib_elf): Debug the failures on other targets.
//@ only-gnu
//@ only-x86_64
//! 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();
}

View File

@ -0,0 +1,3 @@
int this_is_a_library_function() {
return 42;
}

View File

@ -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())
}

View File

@ -0,0 +1 @@
42

View File

@ -0,0 +1,29 @@
//@ only-elf
//@ ignore-cross-compile: Runs a binary.
//@ needs-dynamic-linking
// FIXME(raw_dylib_elf): Debug the failures on other targets.
//@ only-gnu
//@ only-x86_64
//! Ensure ELF raw-dylib is able to link the binary without having the library present,
//! and then successfully run against the real library.
use run_make_support::{build_native_dynamic_lib, cwd, diff, 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");
// ... 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();
}

View File

@ -17,6 +17,8 @@ fn main() {
for arg in env::args().skip(1) {
let path = Path::new(&arg);
if !path.is_file() {
// This directory is produced during linking in a temporary directory (ELF only).
let arg = if arg.ends_with("/raw-dylibs") { "/raw-dylibs" } else { &*arg };
out.push_str(&arg);
out.push_str("\n");
continue;

View File

@ -0,0 +1,9 @@
//@ only-elf
//@ needs-dynamic-linking
#[link(name = "meow", kind = "raw-dylib")] //~ ERROR: link kind `raw-dylib` is unstable on ELF platforms
unsafe extern "C" {
safe fn meowmeow();
}
fn main() {}

View File

@ -0,0 +1,13 @@
error[E0658]: link kind `raw-dylib` is unstable on ELF platforms
--> $DIR/feature-gate-raw-dylib-elf.rs:4:30
|
LL | #[link(name = "meow", kind = "raw-dylib")]
| ^^^^^^^^^^^
|
= note: see issue #135694 <https://github.com/rust-lang/rust/issues/135694> for more information
= help: add `#![feature(raw_dylib_elf)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0658`.

View File

@ -0,0 +1,37 @@
//@ only-elf
//@ needs-dynamic-linking
// FIXME(raw_dylib_elf): Debug the failures on other targets.
//@ only-gnu
//@ only-x86_64
//@ revisions: with without
//@ [without] build-fail
//@ [without] regex-error-pattern:error: linking with `.*` failed
//@ [without] dont-check-compiler-stderr
//@ [with] build-pass
//! Ensures that linking fails when there's an undefined symbol,
//! and that it does succeed with raw-dylib.
#![feature(raw_dylib_elf)]
#![allow(incomplete_features)]
#[cfg_attr(with, link(name = "rawdylibbutforcats", kind = "raw-dylib"))]
#[cfg_attr(without, link(name = "rawdylibbutforcats"))]
unsafe extern "C" {
safe fn meooooooooooooooow();
}
#[cfg_attr(with, link(name = "rawdylibbutfordogs", kind = "raw-dylib"))]
#[cfg_attr(without, link(name = "rawdylibbutfordogs"))]
unsafe extern "C" {
safe fn woooooooooooooooooof();
}
fn main() {
meooooooooooooooow();
woooooooooooooooooof();
}

View File

@ -0,0 +1,28 @@
//@ only-elf
//@ needs-dynamic-linking
// FIXME(raw_dylib_elf): Debug the failures on other targets.
//@ only-gnu
//@ only-x86_64
//@ revisions: with without
//@ [without] build-fail
//@ [without] regex-error-pattern:error: linking with `.*` failed
//@ [without] dont-check-compiler-stderr
//@ [with] build-pass
//! Ensures that linking fails when there's an undefined symbol,
//! and that it does succeed with raw-dylib.
#![feature(raw_dylib_elf)]
#![allow(incomplete_features)]
#[cfg_attr(with, link(name = "rawdylibbutforcats", kind = "raw-dylib"))]
#[cfg_attr(without, link(name = "rawdylibbutforcats"))]
unsafe extern "C" {
safe fn meooooooooooooooow();
}
fn main() {
meooooooooooooooow();
}

View File

@ -0,0 +1,29 @@
//@ only-elf
//@ needs-dynamic-linking
// FIXME(raw_dylib_elf): Debug the failures on other targets.
//@ only-gnu
//@ only-x86_64
//@ revisions: with without
//@ [without] build-fail
//@ [without] regex-error-pattern:error: linking with `.*` failed
//@ [without] dont-check-compiler-stderr
//@ [with] build-pass
//! Ensures that linking fails when there's an undefined symbol,
//! and that it does succeed with raw-dylib, but with verbatim.
#![feature(raw_dylib_elf)]
#![allow(incomplete_features)]
#[cfg_attr(with, link(name = "rawdylibbutforcats", kind = "raw-dylib", modifiers = "+verbatim"))]
#[cfg_attr(without, link(name = "rawdylibbutforcats", modifiers = "+verbatim"))]
unsafe extern "C" {
safe fn meooooooooooooooow();
}
fn main() {
meooooooooooooooow();
}

View File

@ -0,0 +1,13 @@
error[E0658]: link kind `raw-dylib` is unstable on ELF platforms
--> $DIR/raw-dylib-windows-only.rs:6:29
|
LL | #[link(name = "foo", kind = "raw-dylib")]
| ^^^^^^^^^^^
|
= note: see issue #135694 <https://github.com/rust-lang/rust/issues/135694> for more information
= help: add `#![feature(raw_dylib_elf)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0658`.

View File

@ -1,5 +1,5 @@
error[E0455]: link kind `raw-dylib` is only supported on Windows targets
--> $DIR/raw-dylib-windows-only.rs:3:29
--> $DIR/raw-dylib-windows-only.rs:6:29
|
LL | #[link(name = "foo", kind = "raw-dylib")]
| ^^^^^^^^^^^

View File

@ -0,0 +1,9 @@
//@ revisions: elf notelf
//@ [elf] only-elf
//@ [notelf] ignore-windows
//@ [notelf] ignore-elf
//@ compile-flags: --crate-type lib
#[link(name = "foo", kind = "raw-dylib")]
//[notelf]~^ ERROR: link kind `raw-dylib` is only supported on Windows targets
//[elf]~^^ ERROR: link kind `raw-dylib` is unstable on ELF platforms
extern "C" {}

View File

@ -1,5 +0,0 @@
//@ ignore-windows
//@ compile-flags: --crate-type lib
#[link(name = "foo", kind = "raw-dylib")]
//~^ ERROR: link kind `raw-dylib` is only supported on Windows targets
extern "C" {}