about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs218
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs373
-rw-r--r--compiler/rustc_codegen_ssa/src/back/metadata.rs86
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-rw-r--r--compiler/rustc_metadata/messages.ftl3
-rw-r--r--compiler/rustc_metadata/src/native_libs.rs21
-rw-r--r--compiler/rustc_session/src/utils.rs1
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_target/src/spec/mod.rs55
9 files changed, 560 insertions, 200 deletions
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index b090730ac6b..17716146140 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -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
diff --git a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs
new file mode 100644
index 00000000000..2c24378afe1
--- /dev/null
+++ b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs
@@ -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
+}
diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs
index 236507ac0cd..75179a65e34 100644
--- a/compiler/rustc_codegen_ssa/src/back/metadata.rs
+++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs
@@ -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:
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index 66c26a541f1..813a773c70e 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -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
diff --git a/compiler/rustc_metadata/messages.ftl b/compiler/rustc_metadata/messages.ftl
index df0a25712cc..20f66fae5c0 100644
--- a/compiler/rustc_metadata/messages.ftl
+++ b/compiler/rustc_metadata/messages.ftl
@@ -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`
 
diff --git a/compiler/rustc_metadata/src/native_libs.rs b/compiler/rustc_metadata/src/native_libs.rs
index e2d043f47c0..b96921a63f3 100644
--- a/compiler/rustc_metadata/src/native_libs.rs
+++ b/compiler/rustc_metadata/src/native_libs.rs
@@ -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" => {
diff --git a/compiler/rustc_session/src/utils.rs b/compiler/rustc_session/src/utils.rs
index 9182789cf02..fcede379b89 100644
--- a/compiler/rustc_session/src/utils.rs
+++ b/compiler/rustc_session/src/utils.rs
@@ -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 {
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 501c9039f2d..e8d47d4330a 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1624,6 +1624,7 @@ symbols! {
         quote,
         range_inclusive_new,
         raw_dylib,
+        raw_dylib_elf,
         raw_eq,
         raw_identifiers,
         raw_ref_op,
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index cc8162a2f27..72202129f5b 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -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.