about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNoratrieb <48135649+Noratrieb@users.noreply.github.com>2025-01-18 18:19:42 +0100
committerNoratrieb <48135649+Noratrieb@users.noreply.github.com>2025-02-26 19:09:51 +0100
commita954c512809a2d6f3e592c0287dbe41264b54808 (patch)
treec912fd946b93e04c18fdb94e241f24eef192a286
parentac91805f3179fc2225c60e8ccf5a1daa09d43f3d (diff)
downloadrust-a954c512809a2d6f3e592c0287dbe41264b54808.tar.gz
rust-a954c512809a2d6f3e592c0287dbe41264b54808.zip
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.
-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
-rw-r--r--src/doc/rustc-dev-guide/src/tests/directives.md1
-rw-r--r--src/tools/compiletest/src/directive-list.rs2
-rw-r--r--src/tools/compiletest/src/header/cfg.rs10
-rw-r--r--tests/run-make/linker-warning/rmake.rs2
-rw-r--r--tests/run-make/linker-warning/short-error.txt2
-rw-r--r--tests/run-make/raw-dylib-elf-verbatim-absolute/main.rs11
-rw-r--r--tests/run-make/raw-dylib-elf-verbatim-absolute/output.txt1
-rw-r--r--tests/run-make/raw-dylib-elf-verbatim-absolute/rmake.rs20
-rw-r--r--tests/run-make/raw-dylib-elf-verbatim/library.c3
-rw-r--r--tests/run-make/raw-dylib-elf-verbatim/main.rs11
-rw-r--r--tests/run-make/raw-dylib-elf-verbatim/output.txt1
-rw-r--r--tests/run-make/raw-dylib-elf-verbatim/rmake.rs31
-rw-r--r--tests/run-make/raw-dylib-elf/library.c3
-rw-r--r--tests/run-make/raw-dylib-elf/main.rs11
-rw-r--r--tests/run-make/raw-dylib-elf/output.txt1
-rw-r--r--tests/run-make/raw-dylib-elf/rmake.rs29
-rw-r--r--tests/run-make/reproducible-build/linker.rs2
-rw-r--r--tests/ui/feature-gates/feature-gate-raw-dylib-elf.rs9
-rw-r--r--tests/ui/feature-gates/feature-gate-raw-dylib-elf.stderr13
-rw-r--r--tests/ui/linkage-attr/raw-dylib/elf/multiple-libraries.rs37
-rw-r--r--tests/ui/linkage-attr/raw-dylib/elf/single-symbol.rs28
-rw-r--r--tests/ui/linkage-attr/raw-dylib/elf/verbatim.rs29
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/dlltool-failed.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/dlltool-failed.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-invalid-format.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-invalid-format.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-invalid-format.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-invalid-format.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-multiple.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-multiple.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-multiple.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-multiple.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unknown-value.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unknown-value.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unknown-value.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unknown-value.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unsupported-link-kind.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unsupported-link-kind.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unsupported-link-kind.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unsupported-link-kind.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-x86-only.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-x86-only.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/import-name-type-x86-only.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-x86-only.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/invalid-dlltool.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/invalid-dlltool.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-and-name.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-and-name.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-and-name.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-and-name.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-invalid-format.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-invalid-format.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-invalid-format.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-invalid-format.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-missing-argument.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-missing-argument.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-missing-argument.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-missing-argument.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-multiple.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-multiple.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-not-foreign-fn.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-not-foreign-fn.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-not-foreign-fn.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-not-foreign-fn.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-large.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-large.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-large.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-large.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-many-arguments.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-many-arguments.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-unsupported-link-kind.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-unsupported-link-kind.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-unsupported-link-kind.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-unsupported-link-kind.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/multiple-declarations.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/multiple-declarations.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/multiple-declarations.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/multiple-declarations.stderr)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.elf.stderr13
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.notelf.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/raw-dylib-windows-only.stderr)2
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.rs9
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/unsupported-abi.rs (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/unsupported-abi.rs)0
-rw-r--r--tests/ui/linkage-attr/raw-dylib/windows/unsupported-abi.stderr (renamed from tests/ui/rfcs/rfc-2627-raw-dylib/unsupported-abi.stderr)0
-rw-r--r--tests/ui/rfcs/rfc-2627-raw-dylib/raw-dylib-windows-only.rs5
69 files changed, 839 insertions, 207 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.
diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md
index 00bb2bc4dbb..af9cb85d27b 100644
--- a/src/doc/rustc-dev-guide/src/tests/directives.md
+++ b/src/doc/rustc-dev-guide/src/tests/directives.md
@@ -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`
diff --git a/src/tools/compiletest/src/directive-list.rs b/src/tools/compiletest/src/directive-list.rs
index 8c909bcb195..b2ad5a3b3d0 100644
--- a/src/tools/compiletest/src/directive-list.rs
+++ b/src/tools/compiletest/src/directive-list.rs
@@ -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",
diff --git a/src/tools/compiletest/src/header/cfg.rs b/src/tools/compiletest/src/header/cfg.rs
index 72a3b9d85c8..c369fff97f4 100644
--- a/src/tools/compiletest/src/header/cfg.rs
+++ b/src/tools/compiletest/src/header/cfg.rs
@@ -167,6 +167,16 @@ fn parse_cfg_name_directive<'a>(
     }
 
     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,
         message: "when rustc is built with LLVM Enzyme"
diff --git a/tests/run-make/linker-warning/rmake.rs b/tests/run-make/linker-warning/rmake.rs
index 30387af428c..73ad248b6f3 100644
--- a/tests/run-make/linker-warning/rmake.rs
+++ b/tests/run-make/linker-warning/rmake.rs
@@ -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();
     }
 
diff --git a/tests/run-make/linker-warning/short-error.txt b/tests/run-make/linker-warning/short-error.txt
index dd3b742bbfd..a7f48af885a 100644
--- a/tests/run-make/linker-warning/short-error.txt
+++ b/tests/run-make/linker-warning/short-error.txt
@@ -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
           
diff --git a/tests/run-make/raw-dylib-elf-verbatim-absolute/main.rs b/tests/run-make/raw-dylib-elf-verbatim-absolute/main.rs
new file mode 100644
index 00000000000..75bd7747f4f
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf-verbatim-absolute/main.rs
@@ -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())
+}
diff --git a/tests/run-make/raw-dylib-elf-verbatim-absolute/output.txt b/tests/run-make/raw-dylib-elf-verbatim-absolute/output.txt
new file mode 100644
index 00000000000..d81cc0710eb
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf-verbatim-absolute/output.txt
@@ -0,0 +1 @@
+42
diff --git a/tests/run-make/raw-dylib-elf-verbatim-absolute/rmake.rs b/tests/run-make/raw-dylib-elf-verbatim-absolute/rmake.rs
new file mode 100644
index 00000000000..d2a9b0477e5
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf-verbatim-absolute/rmake.rs
@@ -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.
+}
diff --git a/tests/run-make/raw-dylib-elf-verbatim/library.c b/tests/run-make/raw-dylib-elf-verbatim/library.c
new file mode 100644
index 00000000000..2e3a95b7ede
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf-verbatim/library.c
@@ -0,0 +1,3 @@
+int this_is_a_library_function() {
+    return 42;
+}
diff --git a/tests/run-make/raw-dylib-elf-verbatim/main.rs b/tests/run-make/raw-dylib-elf-verbatim/main.rs
new file mode 100644
index 00000000000..044b7400a84
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf-verbatim/main.rs
@@ -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())
+}
diff --git a/tests/run-make/raw-dylib-elf-verbatim/output.txt b/tests/run-make/raw-dylib-elf-verbatim/output.txt
new file mode 100644
index 00000000000..d81cc0710eb
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf-verbatim/output.txt
@@ -0,0 +1 @@
+42
diff --git a/tests/run-make/raw-dylib-elf-verbatim/rmake.rs b/tests/run-make/raw-dylib-elf-verbatim/rmake.rs
new file mode 100644
index 00000000000..319534b24c8
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf-verbatim/rmake.rs
@@ -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();
+}
diff --git a/tests/run-make/raw-dylib-elf/library.c b/tests/run-make/raw-dylib-elf/library.c
new file mode 100644
index 00000000000..2e3a95b7ede
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf/library.c
@@ -0,0 +1,3 @@
+int this_is_a_library_function() {
+    return 42;
+}
diff --git a/tests/run-make/raw-dylib-elf/main.rs b/tests/run-make/raw-dylib-elf/main.rs
new file mode 100644
index 00000000000..3be944d2951
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf/main.rs
@@ -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())
+}
diff --git a/tests/run-make/raw-dylib-elf/output.txt b/tests/run-make/raw-dylib-elf/output.txt
new file mode 100644
index 00000000000..d81cc0710eb
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf/output.txt
@@ -0,0 +1 @@
+42
diff --git a/tests/run-make/raw-dylib-elf/rmake.rs b/tests/run-make/raw-dylib-elf/rmake.rs
new file mode 100644
index 00000000000..59f901ac1ed
--- /dev/null
+++ b/tests/run-make/raw-dylib-elf/rmake.rs
@@ -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();
+}
diff --git a/tests/run-make/reproducible-build/linker.rs b/tests/run-make/reproducible-build/linker.rs
index ab3b4049cc3..2f57d9f9be9 100644
--- a/tests/run-make/reproducible-build/linker.rs
+++ b/tests/run-make/reproducible-build/linker.rs
@@ -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;
diff --git a/tests/ui/feature-gates/feature-gate-raw-dylib-elf.rs b/tests/ui/feature-gates/feature-gate-raw-dylib-elf.rs
new file mode 100644
index 00000000000..ec49e53d9b4
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-raw-dylib-elf.rs
@@ -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() {}
diff --git a/tests/ui/feature-gates/feature-gate-raw-dylib-elf.stderr b/tests/ui/feature-gates/feature-gate-raw-dylib-elf.stderr
new file mode 100644
index 00000000000..dfbf39f5a18
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-raw-dylib-elf.stderr
@@ -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`.
diff --git a/tests/ui/linkage-attr/raw-dylib/elf/multiple-libraries.rs b/tests/ui/linkage-attr/raw-dylib/elf/multiple-libraries.rs
new file mode 100644
index 00000000000..f4715ff2d3d
--- /dev/null
+++ b/tests/ui/linkage-attr/raw-dylib/elf/multiple-libraries.rs
@@ -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();
+}
diff --git a/tests/ui/linkage-attr/raw-dylib/elf/single-symbol.rs b/tests/ui/linkage-attr/raw-dylib/elf/single-symbol.rs
new file mode 100644
index 00000000000..fe9c7884e54
--- /dev/null
+++ b/tests/ui/linkage-attr/raw-dylib/elf/single-symbol.rs
@@ -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();
+}
diff --git a/tests/ui/linkage-attr/raw-dylib/elf/verbatim.rs b/tests/ui/linkage-attr/raw-dylib/elf/verbatim.rs
new file mode 100644
index 00000000000..72cba18d841
--- /dev/null
+++ b/tests/ui/linkage-attr/raw-dylib/elf/verbatim.rs
@@ -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();
+}
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/dlltool-failed.rs b/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.rs
index e69a4537935..e69a4537935 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/dlltool-failed.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/dlltool-failed.stderr b/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.stderr
index 90cca83d1c1..90cca83d1c1 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/dlltool-failed.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/dlltool-failed.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-invalid-format.rs b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-invalid-format.rs
index 50ad8a173ad..50ad8a173ad 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-invalid-format.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-invalid-format.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-invalid-format.stderr b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-invalid-format.stderr
index d2cf7a0ba1f..d2cf7a0ba1f 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-invalid-format.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-invalid-format.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-multiple.rs b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-multiple.rs
index cf456b9b261..cf456b9b261 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-multiple.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-multiple.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-multiple.stderr b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-multiple.stderr
index 8e65baf65df..8e65baf65df 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-multiple.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-multiple.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unknown-value.rs b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unknown-value.rs
index b3859ba1ce6..b3859ba1ce6 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unknown-value.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unknown-value.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unknown-value.stderr b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unknown-value.stderr
index 4b8b90eb6e2..4b8b90eb6e2 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unknown-value.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unknown-value.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unsupported-link-kind.rs b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unsupported-link-kind.rs
index 3ead5cb1fd7..3ead5cb1fd7 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unsupported-link-kind.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unsupported-link-kind.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unsupported-link-kind.stderr b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unsupported-link-kind.stderr
index 75cadc471c4..75cadc471c4 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-unsupported-link-kind.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-unsupported-link-kind.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-x86-only.rs b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-x86-only.rs
index ab0dcda64e6..ab0dcda64e6 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-x86-only.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-x86-only.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-x86-only.stderr b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-x86-only.stderr
index 757f1f7994e..757f1f7994e 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/import-name-type-x86-only.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/import-name-type-x86-only.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/invalid-dlltool.rs b/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.rs
index 057242246f0..057242246f0 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/invalid-dlltool.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/invalid-dlltool.stderr b/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.stderr
index 4bbad9b30a7..4bbad9b30a7 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/invalid-dlltool.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/invalid-dlltool.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-and-name.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-and-name.rs
index b04c2facbcd..b04c2facbcd 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-and-name.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-and-name.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-and-name.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-and-name.stderr
index f1e54d37827..f1e54d37827 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-and-name.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-and-name.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-invalid-format.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-invalid-format.rs
index 9b7e8d70743..9b7e8d70743 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-invalid-format.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-invalid-format.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-invalid-format.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-invalid-format.stderr
index 6341e57a0be..6341e57a0be 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-invalid-format.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-invalid-format.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-missing-argument.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-missing-argument.rs
index 6b8cd49566d..6b8cd49566d 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-missing-argument.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-missing-argument.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-missing-argument.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-missing-argument.stderr
index 1b04bb228e7..1b04bb228e7 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-missing-argument.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-missing-argument.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-multiple.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.rs
index f5fb1649cdc..f5fb1649cdc 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-multiple.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-multiple.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.stderr
index 2e6cf3761c2..2e6cf3761c2 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-multiple.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-multiple.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-not-foreign-fn.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-not-foreign-fn.rs
index 5982c771033..5982c771033 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-not-foreign-fn.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-not-foreign-fn.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-not-foreign-fn.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-not-foreign-fn.stderr
index 8f279508720..8f279508720 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-not-foreign-fn.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-not-foreign-fn.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-large.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-large.rs
index 9d741630fc9..9d741630fc9 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-large.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-large.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-large.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-large.stderr
index 811145e77ee..811145e77ee 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-large.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-large.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-many-arguments.rs
index 9988115fd8b..9988115fd8b 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-many-arguments.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-many-arguments.stderr
index d5ce8aff34f..d5ce8aff34f 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-too-many-arguments.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-too-many-arguments.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-unsupported-link-kind.rs b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-unsupported-link-kind.rs
index 14e915d602a..14e915d602a 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-unsupported-link-kind.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-unsupported-link-kind.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-unsupported-link-kind.stderr b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-unsupported-link-kind.stderr
index 200b8f62874..200b8f62874 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/link-ordinal-unsupported-link-kind.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/link-ordinal-unsupported-link-kind.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/multiple-declarations.rs b/tests/ui/linkage-attr/raw-dylib/windows/multiple-declarations.rs
index bf3c5e4d435..bf3c5e4d435 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/multiple-declarations.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/multiple-declarations.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/multiple-declarations.stderr b/tests/ui/linkage-attr/raw-dylib/windows/multiple-declarations.stderr
index b766b5c8dd8..b766b5c8dd8 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/multiple-declarations.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/multiple-declarations.stderr
diff --git a/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.elf.stderr b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.elf.stderr
new file mode 100644
index 00000000000..70945ed6fc0
--- /dev/null
+++ b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.elf.stderr
@@ -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`.
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/raw-dylib-windows-only.stderr b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.notelf.stderr
index ede20cb8c3f..daed15d784a 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/raw-dylib-windows-only.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.notelf.stderr
@@ -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")]
    |                             ^^^^^^^^^^^
diff --git a/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.rs b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.rs
new file mode 100644
index 00000000000..935c59b5aaa
--- /dev/null
+++ b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.rs
@@ -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" {}
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/unsupported-abi.rs b/tests/ui/linkage-attr/raw-dylib/windows/unsupported-abi.rs
index 48af6b009d3..48af6b009d3 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/unsupported-abi.rs
+++ b/tests/ui/linkage-attr/raw-dylib/windows/unsupported-abi.rs
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/unsupported-abi.stderr b/tests/ui/linkage-attr/raw-dylib/windows/unsupported-abi.stderr
index ef022404e7f..ef022404e7f 100644
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/unsupported-abi.stderr
+++ b/tests/ui/linkage-attr/raw-dylib/windows/unsupported-abi.stderr
diff --git a/tests/ui/rfcs/rfc-2627-raw-dylib/raw-dylib-windows-only.rs b/tests/ui/rfcs/rfc-2627-raw-dylib/raw-dylib-windows-only.rs
deleted file mode 100644
index 3b982857db8..00000000000
--- a/tests/ui/rfcs/rfc-2627-raw-dylib/raw-dylib-windows-only.rs
+++ /dev/null
@@ -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" {}