about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2020-05-20 05:47:10 +0000
committerbors <bors@rust-lang.org>2020-05-20 05:47:10 +0000
commit64ad709ad4d2863b7995d8b9e90a1bedb7d0ccf1 (patch)
tree72dbfc627b26a19d36533a927c38fc6b094abe92
parent692a26e8d727a79340937565cf77c573d3a2294f (diff)
parent49eb35c05e8bff6197ed0ea7437e0c0fb03c62c5 (diff)
downloadrust-64ad709ad4d2863b7995d8b9e90a1bedb7d0ccf1.tar.gz
rust-64ad709ad4d2863b7995d8b9e90a1bedb7d0ccf1.zip
Auto merge of #71769 - petrochenkov:crto, r=cuviper
linker: More systematic handling of CRT objects

Document which kinds of `crt0.o`-like objects we link and in which cases, discovering bugs in process.
`src/librustc_target/spec/crt_objects.rs` is the place to start reading from.

This PR also automatically contains half of the `-static-pie` support (https://github.com/rust-lang/rust/pull/70740), because that's one of the six cases that we need to consider when linking CRT objects.

This is a breaking change for custom target specifications that specify CRT objects.

Closes https://github.com/rust-lang/rust/issues/30868
-rw-r--r--src/bootstrap/compile.rs13
-rw-r--r--src/librustc_codegen_ssa/back/link.rs147
-rw-r--r--src/librustc_codegen_ssa/back/linker.rs21
-rw-r--r--src/librustc_target/spec/crt_objects.rs145
-rw-r--r--src/librustc_target/spec/fuchsia_base.rs9
-rw-r--r--src/librustc_target/spec/linux_musl_base.rs19
-rw-r--r--src/librustc_target/spec/mod.rs142
-rw-r--r--src/librustc_target/spec/wasm32_base.rs3
-rw-r--r--src/librustc_target/spec/wasm32_unknown_unknown.rs4
-rw-r--r--src/librustc_target/spec/wasm32_wasi.rs7
-rw-r--r--src/librustc_target/spec/windows_gnu_base.rs17
-rw-r--r--src/librustc_target/spec/windows_uwp_gnu_base.rs21
-rw-r--r--src/librustc_target/spec/x86_64_fortanix_unknown_sgx.rs5
-rw-r--r--src/libserialize/json.rs4
14 files changed, 405 insertions, 152 deletions
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index 0c754936bc2..c56114f14ca 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -125,15 +125,16 @@ fn copy_third_party_objects(
         target_deps.push(target);
     };
 
-    // Copies the crt(1,i,n).o startup objects
+    // Copies the CRT objects.
     //
-    // Since musl supports fully static linking, we can cross link for it even
-    // with a glibc-targeting toolchain, given we have the appropriate startup
-    // files. As those shipped with glibc won't work, copy the ones provided by
-    // musl so we have them on linux-gnu hosts.
+    // rustc historically provides a more self-contained installation for musl targets
+    // not requiring the presence of a native musl toolchain. For example, it can fall back
+    // to using gcc from a glibc-targeting toolchain for linking.
+    // To do that we have to distribute musl startup objects as a part of Rust toolchain
+    // and link with them manually in the self-contained mode.
     if target.contains("musl") {
         let srcdir = builder.musl_root(target).unwrap().join("lib");
-        for &obj in &["crt1.o", "crti.o", "crtn.o"] {
+        for &obj in &["crt1.o", "Scrt1.o", "rcrt1.o", "crti.o", "crtn.o"] {
             copy_and_stamp(&srcdir, obj);
         }
     } else if target.ends_with("-wasi") {
diff --git a/src/librustc_codegen_ssa/back/link.rs b/src/librustc_codegen_ssa/back/link.rs
index ce158fb07da..d87b84f880a 100644
--- a/src/librustc_codegen_ssa/back/link.rs
+++ b/src/librustc_codegen_ssa/back/link.rs
@@ -11,7 +11,9 @@ use rustc_session::search_paths::PathKind;
 /// need out of the shared crate context before we get rid of it.
 use rustc_session::{filesearch, Session};
 use rustc_span::symbol::Symbol;
-use rustc_target::spec::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, RelroLevel};
+use rustc_target::spec::crt_objects::CrtObjectsFallback;
+use rustc_target::spec::{LinkOutputKind, LinkerFlavor, LldFlavor};
+use rustc_target::spec::{PanicStrategy, RelocModel, RelroLevel};
 
 use super::archive::ArchiveBuilder;
 use super::command::Command;
@@ -1130,33 +1132,70 @@ fn exec_linker(
     }
 }
 
-/// Add begin object files defined by the target spec.
-fn add_pre_link_objects(cmd: &mut dyn Linker, sess: &Session, crate_type: CrateType) {
-    let pre_link_objects = if crate_type == CrateType::Executable {
-        &sess.target.target.options.pre_link_objects_exe
-    } else {
-        &sess.target.target.options.pre_link_objects_dll
+fn link_output_kind(sess: &Session, crate_type: CrateType) -> LinkOutputKind {
+    let kind = match (crate_type, sess.crt_static(Some(crate_type)), sess.relocation_model()) {
+        (CrateType::Executable, false, RelocModel::Pic) => LinkOutputKind::DynamicPicExe,
+        (CrateType::Executable, false, _) => LinkOutputKind::DynamicNoPicExe,
+        (CrateType::Executable, true, RelocModel::Pic) => LinkOutputKind::StaticPicExe,
+        (CrateType::Executable, true, _) => LinkOutputKind::StaticNoPicExe,
+        (_, true, _) => LinkOutputKind::StaticDylib,
+        (_, false, _) => LinkOutputKind::DynamicDylib,
     };
-    for obj in pre_link_objects {
-        cmd.add_object(&get_object_file_path(sess, obj));
+
+    // Adjust the output kind to target capabilities.
+    let pic_exe_supported = sess.target.target.options.position_independent_executables;
+    let static_pic_exe_supported = false; // FIXME: Add this option to target specs.
+    let static_dylib_supported = sess.target.target.options.crt_static_allows_dylibs;
+    match kind {
+        LinkOutputKind::DynamicPicExe if !pic_exe_supported => LinkOutputKind::DynamicNoPicExe,
+        LinkOutputKind::StaticPicExe if !static_pic_exe_supported => LinkOutputKind::StaticNoPicExe,
+        LinkOutputKind::StaticDylib if !static_dylib_supported => LinkOutputKind::DynamicDylib,
+        _ => kind,
     }
+}
 
-    if crate_type == CrateType::Executable && sess.crt_static(Some(crate_type)) {
-        for obj in &sess.target.target.options.pre_link_objects_exe_crt {
-            cmd.add_object(&get_object_file_path(sess, obj));
-        }
+/// Whether we link to our own CRT objects instead of relying on gcc to pull them.
+/// We only provide such support for a very limited number of targets.
+fn crt_objects_fallback(sess: &Session, crate_type: CrateType) -> bool {
+    match sess.target.target.options.crt_objects_fallback {
+        // FIXME: Find a better heuristic for "native musl toolchain is available",
+        // based on host and linker path, for example.
+        // (https://github.com/rust-lang/rust/pull/71769#issuecomment-626330237).
+        Some(CrtObjectsFallback::Musl) => sess.crt_static(Some(crate_type)),
+        // FIXME: Find some heuristic for "native mingw toolchain is available",
+        // likely based on `get_crt_libs_path` (https://github.com/rust-lang/rust/pull/67429).
+        Some(CrtObjectsFallback::Mingw) => sess.target.target.target_vendor != "uwp",
+        // FIXME: Figure out cases in which WASM needs to link with a native toolchain.
+        Some(CrtObjectsFallback::Wasm) => true,
+        None => false,
     }
 }
 
-/// Add end object files defined by the target spec.
-fn add_post_link_objects(cmd: &mut dyn Linker, sess: &Session, crate_type: CrateType) {
-    for obj in &sess.target.target.options.post_link_objects {
+/// Add pre-link object files defined by the target spec.
+fn add_pre_link_objects(
+    cmd: &mut dyn Linker,
+    sess: &Session,
+    link_output_kind: LinkOutputKind,
+    fallback: bool,
+) {
+    let opts = &sess.target.target.options;
+    let objects = if fallback { &opts.pre_link_objects_fallback } else { &opts.pre_link_objects };
+    for obj in objects.get(&link_output_kind).iter().copied().flatten() {
         cmd.add_object(&get_object_file_path(sess, obj));
     }
-    if sess.crt_static(Some(crate_type)) {
-        for obj in &sess.target.target.options.post_link_objects_crt {
-            cmd.add_object(&get_object_file_path(sess, obj));
-        }
+}
+
+/// Add post-link object files defined by the target spec.
+fn add_post_link_objects(
+    cmd: &mut dyn Linker,
+    sess: &Session,
+    link_output_kind: LinkOutputKind,
+    fallback: bool,
+) {
+    let opts = &sess.target.target.options;
+    let objects = if fallback { &opts.post_link_objects_fallback } else { &opts.post_link_objects };
+    for obj in objects.get(&link_output_kind).iter().copied().flatten() {
+        cmd.add_object(&get_object_file_path(sess, obj));
     }
 }
 
@@ -1342,38 +1381,6 @@ fn add_library_search_dirs(cmd: &mut dyn Linker, sess: &Session) {
     cmd.include_path(&fix_windows_verbatim_for_gcc(&lib_path));
 }
 
-/// Add options requesting executables to be position-independent or not position-independent.
-fn add_position_independent_executable_args(
-    cmd: &mut dyn Linker,
-    sess: &Session,
-    flavor: LinkerFlavor,
-    crate_type: CrateType,
-    codegen_results: &CodegenResults,
-) {
-    if crate_type != CrateType::Executable {
-        return;
-    }
-
-    if sess.target.target.options.position_independent_executables {
-        let attr_link_args = &*codegen_results.crate_info.link_args;
-        let mut user_defined_link_args = sess.opts.cg.link_args.iter().chain(attr_link_args);
-        if sess.relocation_model() == RelocModel::Pic
-            && !sess.crt_static(Some(crate_type))
-            && !user_defined_link_args.any(|x| x == "-static")
-        {
-            cmd.position_independent_executable();
-            return;
-        }
-    }
-
-    // Recent versions of gcc can be configured to generate position
-    // independent executables by default. We have to pass -no-pie to
-    // explicitly turn that off. Not applicable to ld.
-    if sess.target.target.options.linker_is_gnu && flavor != LinkerFlavor::Ld {
-        cmd.no_position_independent_executable();
-    }
-}
-
 /// Add options making relocation sections in the produced ELF files read-only
 /// and suppressing lazy binding.
 fn add_relro_args(cmd: &mut dyn Linker, sess: &Session) {
@@ -1439,6 +1446,8 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
     // to the linker args construction.
     assert!(base_cmd.get_args().is_empty() || sess.target.target.target_vendor == "uwp");
     let cmd = &mut *codegen_results.linker_info.to_linker(base_cmd, &sess, flavor, target_cpu);
+    let link_output_kind = link_output_kind(sess, crate_type);
+    let crt_objects_fallback = crt_objects_fallback(sess, crate_type);
 
     // NO-OPT-OUT, OBJECT-FILES-MAYBE, CUSTOMIZATION-POINT
     add_pre_link_args(cmd, sess, flavor, crate_type);
@@ -1455,8 +1464,13 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
         cmd.arg(format!("--dynamic-linker={}ld.so.1", prefix));
     }
 
+    // NO-OPT-OUT, OBJECT-FILES-NO
+    if crt_objects_fallback {
+        cmd.no_crt_objects();
+    }
+
     // NO-OPT-OUT, OBJECT-FILES-YES
-    add_pre_link_objects(cmd, sess, crate_type);
+    add_pre_link_objects(cmd, sess, link_output_kind, crt_objects_fallback);
 
     // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
     if sess.target.target.options.is_like_emscripten {
@@ -1515,7 +1529,16 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
     }
 
     // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
-    add_position_independent_executable_args(cmd, sess, flavor, crate_type, codegen_results);
+    // FIXME: Support `StaticPicExe` correctly.
+    match link_output_kind {
+        LinkOutputKind::DynamicPicExe | LinkOutputKind::StaticPicExe => {
+            cmd.position_independent_executable()
+        }
+        LinkOutputKind::DynamicNoPicExe | LinkOutputKind::StaticNoPicExe => {
+            cmd.no_position_independent_executable()
+        }
+        _ => {}
+    }
 
     // OBJECT-FILES-NO, AUDIT-ORDER
     add_relro_args(cmd, sess);
@@ -1545,12 +1568,14 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
     );
 
     // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
-    // Tell the linker what we're doing.
-    if crate_type != CrateType::Executable {
-        cmd.build_dylib(out_filename);
-    }
-    if crate_type == CrateType::Executable && sess.crt_static(Some(crate_type)) {
-        cmd.build_static_executable();
+    // FIXME: Merge with the previous `link_output_kind` match,
+    // and support `StaticPicExe` and `StaticDylib` correctly.
+    match link_output_kind {
+        LinkOutputKind::StaticNoPicExe | LinkOutputKind::StaticPicExe => {
+            cmd.build_static_executable()
+        }
+        LinkOutputKind::DynamicDylib | LinkOutputKind::StaticDylib => cmd.build_dylib(out_filename),
+        _ => {}
     }
 
     // OBJECT-FILES-NO, AUDIT-ORDER
@@ -1576,7 +1601,7 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
     add_late_link_args(cmd, sess, flavor, crate_type, codegen_results);
 
     // NO-OPT-OUT, OBJECT-FILES-YES
-    add_post_link_objects(cmd, sess, crate_type);
+    add_post_link_objects(cmd, sess, link_output_kind, crt_objects_fallback);
 
     // NO-OPT-OUT, OBJECT-FILES-MAYBE, CUSTOMIZATION-POINT
     add_post_link_args(cmd, sess, flavor);
diff --git a/src/librustc_codegen_ssa/back/linker.rs b/src/librustc_codegen_ssa/back/linker.rs
index 535c4ff092f..ee5bcf4b9f5 100644
--- a/src/librustc_codegen_ssa/back/linker.rs
+++ b/src/librustc_codegen_ssa/back/linker.rs
@@ -123,6 +123,7 @@ pub trait Linker {
     fn pgo_gen(&mut self);
     fn control_flow_guard(&mut self);
     fn debuginfo(&mut self, strip: Strip);
+    fn no_crt_objects(&mut self);
     fn no_default_libraries(&mut self);
     fn build_dylib(&mut self, out_filename: &Path);
     fn build_static_executable(&mut self);
@@ -266,7 +267,9 @@ impl<'a> Linker for GccLinker<'a> {
         self.cmd.arg("-pie");
     }
     fn no_position_independent_executable(&mut self) {
-        self.cmd.arg("-no-pie");
+        if !self.is_ld {
+            self.cmd.arg("-no-pie");
+        }
     }
     fn full_relro(&mut self) {
         self.linker_arg("-zrelro");
@@ -404,6 +407,12 @@ impl<'a> Linker for GccLinker<'a> {
         }
     }
 
+    fn no_crt_objects(&mut self) {
+        if !self.is_ld {
+            self.cmd.arg("-nostartfiles");
+        }
+    }
+
     fn no_default_libraries(&mut self) {
         if !self.is_ld {
             self.cmd.arg("-nodefaultlibs");
@@ -644,6 +653,10 @@ impl<'a> Linker for MsvcLinker<'a> {
         // noop
     }
 
+    fn no_crt_objects(&mut self) {
+        // noop
+    }
+
     fn no_default_libraries(&mut self) {
         self.cmd.arg("/NODEFAULTLIB");
     }
@@ -907,6 +920,8 @@ impl<'a> Linker for EmLinker<'a> {
         });
     }
 
+    fn no_crt_objects(&mut self) {}
+
     fn no_default_libraries(&mut self) {
         self.cmd.args(&["-s", "DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[]"]);
     }
@@ -1106,6 +1121,8 @@ impl<'a> Linker for WasmLd<'a> {
         self.sess.warn("Windows Control Flow Guard is not supported by this linker.");
     }
 
+    fn no_crt_objects(&mut self) {}
+
     fn no_default_libraries(&mut self) {}
 
     fn build_dylib(&mut self, _out_filename: &Path) {
@@ -1271,6 +1288,8 @@ impl<'a> Linker for PtxLinker<'a> {
 
     fn pgo_gen(&mut self) {}
 
+    fn no_crt_objects(&mut self) {}
+
     fn no_default_libraries(&mut self) {}
 
     fn control_flow_guard(&mut self) {
diff --git a/src/librustc_target/spec/crt_objects.rs b/src/librustc_target/spec/crt_objects.rs
new file mode 100644
index 00000000000..8991691a9a3
--- /dev/null
+++ b/src/librustc_target/spec/crt_objects.rs
@@ -0,0 +1,145 @@
+//! Object files providing support for basic runtime facilities and added to the produced binaries
+//! at the start and at the end of linking.
+//!
+//! Table of CRT objects for popular toolchains.
+//! The `crtx` ones are generally distributed with libc and the `begin/end` ones with gcc.
+//! See https://dev.gentoo.org/~vapier/crt.txt for some more details.
+//!
+//! | Pre-link CRT objects | glibc                  | musl                   | bionic           | mingw             | wasi |
+//! |----------------------|------------------------|------------------------|------------------|-------------------|------|
+//! | dynamic-nopic-exe    | crt1, crti, crtbegin   | crt1, crti, crtbegin   | crtbegin_dynamic | crt2, crtbegin    | crt1 |
+//! | dynamic-pic-exe      | Scrt1, crti, crtbeginS | Scrt1, crti, crtbeginS | crtbegin_dynamic | crt2, crtbegin    | crt1 |
+//! | static-nopic-exe     | crt1, crti, crtbeginT  | crt1, crti, crtbegin   | crtbegin_static  | crt2, crtbegin    | crt1 |
+//! | static-pic-exe       | rcrt1, crti, crtbeginS | rcrt1, crti, crtbeginS | crtbegin_dynamic | crt2, crtbegin    | crt1 |
+//! | dynamic-dylib        | crti, crtbeginS        | crti, crtbeginS        | crtbegin_so      | dllcrt2, crtbegin | -    |
+//! | static-dylib (gcc)   | crti, crtbeginT        | crti, crtbeginS        | crtbegin_so      | dllcrt2, crtbegin | -    |
+//! | static-dylib (clang) | crti, crtbeginT        | N/A                    | crtbegin_static  | dllcrt2, crtbegin | -    |
+//!
+//! | Post-link CRT objects | glibc         | musl          | bionic         | mingw  | wasi |
+//! |-----------------------|---------------|---------------|----------------|--------|------|
+//! | dynamic-nopic-exe     | crtend, crtn  | crtend, crtn  | crtend_android | crtend | -    |
+//! | dynamic-pic-exe       | crtendS, crtn | crtendS, crtn | crtend_android | crtend | -    |
+//! | static-nopic-exe      | crtend, crtn  | crtend, crtn  | crtend_android | crtend | -    |
+//! | static-pic-exe        | crtendS, crtn | crtendS, crtn | crtend_android | crtend | -    |
+//! | dynamic-dylib         | crtendS, crtn | crtendS, crtn | crtend_so      | crtend | -    |
+//! | static-dylib (gcc)    | crtend, crtn  | crtendS, crtn | crtend_so      | crtend | -    |
+//! | static-dylib (clang)  | crtendS, crtn | N/A           | crtend_so      | crtend | -    |
+//!
+//! Use cases for rustc linking the CRT objects explicitly:
+//!     - rustc needs to add its own Rust-specific objects (mingw is the example)
+//!     - gcc wrapper cannot be used for some reason and linker like ld or lld is used directly.
+//!     - gcc wrapper pulls wrong CRT objects (e.g. from glibc when we are targeting musl).
+//!
+//! In general it is preferable to rely on the target's native toolchain to pull the objects.
+//! However, for some targets (musl, mingw) rustc historically provides a more self-contained
+//! installation not requiring users to install the native target's toolchain.
+//! In that case rustc distributes the objects as a part of the target's Rust toolchain
+//! and falls back to linking with them manually.
+//! Unlike native toolchains, rustc only currently adds the libc's objects during linking,
+//! but not gcc's. As a result rustc cannot link with C++ static libraries (#36710)
+//! when linking in self-contained mode.
+
+use crate::spec::LinkOutputKind;
+use rustc_serialize::json::{Json, ToJson};
+use std::collections::BTreeMap;
+use std::str::FromStr;
+
+pub type CrtObjects = BTreeMap<LinkOutputKind, Vec<String>>;
+
+pub(super) fn new(obj_table: &[(LinkOutputKind, &[&str])]) -> CrtObjects {
+    obj_table.iter().map(|(z, k)| (*z, k.iter().map(|b| b.to_string()).collect())).collect()
+}
+
+pub(super) fn all(obj: &str) -> CrtObjects {
+    new(&[
+        (LinkOutputKind::DynamicNoPicExe, &[obj]),
+        (LinkOutputKind::DynamicPicExe, &[obj]),
+        (LinkOutputKind::StaticNoPicExe, &[obj]),
+        (LinkOutputKind::StaticPicExe, &[obj]),
+        (LinkOutputKind::DynamicDylib, &[obj]),
+        (LinkOutputKind::StaticDylib, &[obj]),
+    ])
+}
+
+pub(super) fn pre_musl_fallback() -> CrtObjects {
+    new(&[
+        (LinkOutputKind::DynamicNoPicExe, &["crt1.o", "crti.o"]),
+        (LinkOutputKind::DynamicPicExe, &["Scrt1.o", "crti.o"]),
+        (LinkOutputKind::StaticNoPicExe, &["crt1.o", "crti.o"]),
+        (LinkOutputKind::StaticPicExe, &["rcrt1.o", "crti.o"]),
+        (LinkOutputKind::DynamicDylib, &["crti.o"]),
+        (LinkOutputKind::StaticDylib, &["crti.o"]),
+    ])
+}
+
+pub(super) fn post_musl_fallback() -> CrtObjects {
+    all("crtn.o")
+}
+
+pub(super) fn pre_mingw_fallback() -> CrtObjects {
+    new(&[
+        (LinkOutputKind::DynamicNoPicExe, &["crt2.o", "rsbegin.o"]),
+        (LinkOutputKind::DynamicPicExe, &["crt2.o", "rsbegin.o"]),
+        (LinkOutputKind::StaticNoPicExe, &["crt2.o", "rsbegin.o"]),
+        (LinkOutputKind::StaticPicExe, &["crt2.o", "rsbegin.o"]),
+        (LinkOutputKind::DynamicDylib, &["dllcrt2.o", "rsbegin.o"]),
+        (LinkOutputKind::StaticDylib, &["dllcrt2.o", "rsbegin.o"]),
+    ])
+}
+
+pub(super) fn post_mingw_fallback() -> CrtObjects {
+    all("rsend.o")
+}
+
+pub(super) fn pre_mingw() -> CrtObjects {
+    all("rsbegin.o")
+}
+
+pub(super) fn post_mingw() -> CrtObjects {
+    all("rsend.o")
+}
+
+pub(super) fn pre_wasi_fallback() -> CrtObjects {
+    new(&[
+        (LinkOutputKind::DynamicNoPicExe, &["crt1.o"]),
+        (LinkOutputKind::DynamicPicExe, &["crt1.o"]),
+        (LinkOutputKind::StaticNoPicExe, &["crt1.o"]),
+        (LinkOutputKind::StaticPicExe, &["crt1.o"]),
+    ])
+}
+
+pub(super) fn post_wasi_fallback() -> CrtObjects {
+    new(&[])
+}
+
+/// Which logic to use to determine whether to fall back to the "self-contained" mode or not.
+#[derive(Clone, Copy, PartialEq, Hash, Debug)]
+pub enum CrtObjectsFallback {
+    Musl,
+    Mingw,
+    Wasm,
+}
+
+impl FromStr for CrtObjectsFallback {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<CrtObjectsFallback, ()> {
+        Ok(match s {
+            "musl" => CrtObjectsFallback::Musl,
+            "mingw" => CrtObjectsFallback::Mingw,
+            "wasm" => CrtObjectsFallback::Wasm,
+            _ => return Err(()),
+        })
+    }
+}
+
+impl ToJson for CrtObjectsFallback {
+    fn to_json(&self) -> Json {
+        match *self {
+            CrtObjectsFallback::Musl => "musl",
+            CrtObjectsFallback::Mingw => "mingw",
+            CrtObjectsFallback::Wasm => "wasm",
+        }
+        .to_json()
+    }
+}
diff --git a/src/librustc_target/spec/fuchsia_base.rs b/src/librustc_target/spec/fuchsia_base.rs
index 4060b126cdd..96b5328e1ee 100644
--- a/src/librustc_target/spec/fuchsia_base.rs
+++ b/src/librustc_target/spec/fuchsia_base.rs
@@ -1,4 +1,4 @@
-use crate::spec::{LinkArgs, LinkerFlavor, LldFlavor, TargetOptions};
+use crate::spec::{crt_objects, LinkArgs, LinkOutputKind, LinkerFlavor, LldFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
     let mut pre_link_args = LinkArgs::new();
@@ -23,7 +23,12 @@ pub fn opts() -> TargetOptions {
         linker_is_gnu: true,
         has_rpath: false,
         pre_link_args,
-        pre_link_objects_exe: vec!["Scrt1.o".to_string()],
+        pre_link_objects: crt_objects::new(&[
+            (LinkOutputKind::DynamicNoPicExe, &["Scrt1.o"]),
+            (LinkOutputKind::DynamicPicExe, &["Scrt1.o"]),
+            (LinkOutputKind::StaticNoPicExe, &["Scrt1.o"]),
+            (LinkOutputKind::StaticPicExe, &["Scrt1.o"]),
+        ]),
         position_independent_executables: true,
         has_elf_tls: true,
         ..Default::default()
diff --git a/src/librustc_target/spec/linux_musl_base.rs b/src/librustc_target/spec/linux_musl_base.rs
index e294e63982d..0fdd8760806 100644
--- a/src/librustc_target/spec/linux_musl_base.rs
+++ b/src/librustc_target/spec/linux_musl_base.rs
@@ -1,29 +1,18 @@
+use crate::spec::crt_objects::{self, CrtObjectsFallback};
 use crate::spec::{LinkerFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
     let mut base = super::linux_base::opts();
 
-    // Make sure that the linker/gcc really don't pull in anything, including
-    // default objects, libs, etc.
-    base.pre_link_args_crt.insert(LinkerFlavor::Gcc, Vec::new());
-    base.pre_link_args_crt.get_mut(&LinkerFlavor::Gcc).unwrap().push("-nostdlib".to_string());
-
     // At least when this was tested, the linker would not add the
     // `GNU_EH_FRAME` program header to executables generated, which is required
     // when unwinding to locate the unwinding information. I'm not sure why this
     // argument is *not* necessary for normal builds, but it can't hurt!
     base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-Wl,--eh-frame-hdr".to_string());
 
-    // When generating a statically linked executable there's generally some
-    // small setup needed which is listed in these files. These are provided by
-    // a musl toolchain and are linked by default by the `musl-gcc` script. Note
-    // that `gcc` also does this by default, it just uses some different files.
-    //
-    // Each target directory for musl has these object files included in it so
-    // they'll be included from there.
-    base.pre_link_objects_exe_crt.push("crt1.o".to_string());
-    base.pre_link_objects_exe_crt.push("crti.o".to_string());
-    base.post_link_objects_crt.push("crtn.o".to_string());
+    base.pre_link_objects_fallback = crt_objects::pre_musl_fallback();
+    base.post_link_objects_fallback = crt_objects::post_musl_fallback();
+    base.crt_objects_fallback = Some(CrtObjectsFallback::Musl);
 
     // These targets statically link libc by default
     base.crt_static_default = true;
diff --git a/src/librustc_target/spec/mod.rs b/src/librustc_target/spec/mod.rs
index 49b33059b63..41c2f1d93d2 100644
--- a/src/librustc_target/spec/mod.rs
+++ b/src/librustc_target/spec/mod.rs
@@ -35,6 +35,7 @@
 //! to the list specified by the target, rather than replace.
 
 use crate::spec::abi::{lookup as lookup_abi, Abi};
+use crate::spec::crt_objects::{CrtObjects, CrtObjectsFallback};
 use rustc_serialize::json::{Json, ToJson};
 use std::collections::BTreeMap;
 use std::path::{Path, PathBuf};
@@ -44,6 +45,8 @@ use std::{fmt, io};
 use rustc_macros::HashStable_Generic;
 
 pub mod abi;
+pub mod crt_objects;
+
 mod android_base;
 mod apple_base;
 mod apple_sdk_base;
@@ -378,6 +381,54 @@ impl ToJson for TlsModel {
     }
 }
 
+/// Everything is flattened to a single enum to make the json encoding/decoding less annoying.
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
+pub enum LinkOutputKind {
+    /// Dynamically linked non position-independent executable.
+    DynamicNoPicExe,
+    /// Dynamically linked position-independent executable.
+    DynamicPicExe,
+    /// Statically linked non position-independent executable.
+    StaticNoPicExe,
+    /// Statically linked position-independent executable.
+    StaticPicExe,
+    /// Regular dynamic library ("dynamically linked").
+    DynamicDylib,
+    /// Dynamic library with bundled libc ("statically linked").
+    StaticDylib,
+}
+
+impl LinkOutputKind {
+    fn as_str(&self) -> &'static str {
+        match self {
+            LinkOutputKind::DynamicNoPicExe => "dynamic-nopic-exe",
+            LinkOutputKind::DynamicPicExe => "dynamic-pic-exe",
+            LinkOutputKind::StaticNoPicExe => "static-nopic-exe",
+            LinkOutputKind::StaticPicExe => "static-pic-exe",
+            LinkOutputKind::DynamicDylib => "dynamic-dylib",
+            LinkOutputKind::StaticDylib => "static-dylib",
+        }
+    }
+
+    pub(super) fn from_str(s: &str) -> Option<LinkOutputKind> {
+        Some(match s {
+            "dynamic-nopic-exe" => LinkOutputKind::DynamicNoPicExe,
+            "dynamic-pic-exe" => LinkOutputKind::DynamicPicExe,
+            "static-nopic-exe" => LinkOutputKind::StaticNoPicExe,
+            "static-pic-exe" => LinkOutputKind::StaticPicExe,
+            "dynamic-dylib" => LinkOutputKind::DynamicDylib,
+            "static-dylib" => LinkOutputKind::StaticDylib,
+            _ => return None,
+        })
+    }
+}
+
+impl fmt::Display for LinkOutputKind {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(self.as_str())
+    }
+}
+
 pub enum LoadTargetError {
     BuiltinTargetNotFound(String),
     Other(String),
@@ -683,13 +734,19 @@ pub struct TargetOptions {
     /// Linker arguments that are passed *before* any user-defined libraries.
     pub pre_link_args: LinkArgs, // ... unconditionally
     pub pre_link_args_crt: LinkArgs, // ... when linking with a bundled crt
-    /// Objects to link before all others, always found within the
-    /// sysroot folder.
-    pub pre_link_objects_exe: Vec<String>, // ... when linking an executable, unconditionally
-    pub pre_link_objects_exe_crt: Vec<String>, // ... when linking an executable with a bundled crt
-    pub pre_link_objects_dll: Vec<String>, // ... when linking a dylib
+    /// Objects to link before and after all other object code.
+    pub pre_link_objects: CrtObjects,
+    pub post_link_objects: CrtObjects,
+    /// Same as `(pre|post)_link_objects`, but when we fail to pull the objects with help of the
+    /// target's native gcc and fall back to the "self-contained" mode and pull them manually.
+    /// See `crt_objects.rs` for some more detailed documentation.
+    pub pre_link_objects_fallback: CrtObjects,
+    pub post_link_objects_fallback: CrtObjects,
+    /// Which logic to use to determine whether to fall back to the "self-contained" mode or not.
+    pub crt_objects_fallback: Option<CrtObjectsFallback>,
+
     /// Linker arguments that are unconditionally passed after any
-    /// user-defined but before post_link_objects. Standard platform
+    /// user-defined but before post-link objects. Standard platform
     /// libraries that should be always be linked to, usually go here.
     pub late_link_args: LinkArgs,
     /// Linker arguments used in addition to `late_link_args` if at least one
@@ -698,10 +755,6 @@ pub struct TargetOptions {
     /// Linker arguments used in addition to `late_link_args` if aall Rust
     /// dependencies are statically linked.
     pub late_link_args_static: LinkArgs,
-    /// Objects to link after all others, always found within the
-    /// sysroot folder.
-    pub post_link_objects: Vec<String>, // ... unconditionally
-    pub post_link_objects_crt: Vec<String>, // ... when linking with a bundled crt
     /// Linker arguments that are unconditionally passed *after* any
     /// user-defined libraries.
     pub post_link_args: LinkArgs,
@@ -977,11 +1030,11 @@ impl Default for TargetOptions {
             position_independent_executables: false,
             needs_plt: false,
             relro_level: RelroLevel::None,
-            pre_link_objects_exe: Vec::new(),
-            pre_link_objects_exe_crt: Vec::new(),
-            pre_link_objects_dll: Vec::new(),
-            post_link_objects: Vec::new(),
-            post_link_objects_crt: Vec::new(),
+            pre_link_objects: Default::default(),
+            post_link_objects: Default::default(),
+            pre_link_objects_fallback: Default::default(),
+            post_link_objects_fallback: Default::default(),
+            crt_objects_fallback: None,
             late_link_args: LinkArgs::new(),
             late_link_args_dynamic: LinkArgs::new(),
             late_link_args_static: LinkArgs::new(),
@@ -1248,6 +1301,45 @@ impl Target {
                     })
                 })).unwrap_or(Ok(()))
             } );
+            ($key_name:ident, crt_objects_fallback) => ( {
+                let name = (stringify!($key_name)).replace("_", "-");
+                obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| {
+                    match s.parse::<CrtObjectsFallback>() {
+                        Ok(fallback) => base.options.$key_name = Some(fallback),
+                        _ => return Some(Err(format!("'{}' is not a valid CRT objects fallback. \
+                                                      Use 'musl', 'mingw' or 'wasm'", s))),
+                    }
+                    Some(Ok(()))
+                })).unwrap_or(Ok(()))
+            } );
+            ($key_name:ident, link_objects) => ( {
+                let name = (stringify!($key_name)).replace("_", "-");
+                if let Some(val) = obj.find(&name[..]) {
+                    let obj = val.as_object().ok_or_else(|| format!("{}: expected a \
+                        JSON object with fields per CRT object kind.", name))?;
+                    let mut args = CrtObjects::new();
+                    for (k, v) in obj {
+                        let kind = LinkOutputKind::from_str(&k).ok_or_else(|| {
+                            format!("{}: '{}' is not a valid value for CRT object kind. \
+                                     Use '(dynamic,static)-(nopic,pic)-exe' or \
+                                     '(dynamic,static)-dylib'", name, k)
+                        })?;
+
+                        let v = v.as_array().ok_or_else(||
+                            format!("{}.{}: expected a JSON array", name, k)
+                        )?.iter().enumerate()
+                            .map(|(i,s)| {
+                                let s = s.as_string().ok_or_else(||
+                                    format!("{}.{}[{}]: expected a JSON string", name, k, i))?;
+                                Ok(s.to_owned())
+                            })
+                            .collect::<Result<Vec<_>, String>>()?;
+
+                        args.insert(kind, v);
+                    }
+                    base.options.$key_name = args;
+                }
+            } );
             ($key_name:ident, link_args) => ( {
                 let name = (stringify!($key_name)).replace("_", "-");
                 if let Some(val) = obj.find(&name[..]) {
@@ -1295,16 +1387,16 @@ impl Target {
         key!(is_builtin, bool);
         key!(linker, optional);
         key!(lld_flavor, LldFlavor)?;
+        key!(pre_link_objects, link_objects);
+        key!(post_link_objects, link_objects);
+        key!(pre_link_objects_fallback, link_objects);
+        key!(post_link_objects_fallback, link_objects);
+        key!(crt_objects_fallback, crt_objects_fallback)?;
         key!(pre_link_args, link_args);
         key!(pre_link_args_crt, link_args);
-        key!(pre_link_objects_exe, list);
-        key!(pre_link_objects_exe_crt, list);
-        key!(pre_link_objects_dll, list);
         key!(late_link_args, link_args);
         key!(late_link_args_dynamic, link_args);
         key!(late_link_args_static, link_args);
-        key!(post_link_objects, list);
-        key!(post_link_objects_crt, list);
         key!(post_link_args, link_args);
         key!(link_script, optional);
         key!(link_env, env);
@@ -1526,16 +1618,16 @@ impl ToJson for Target {
         target_option_val!(is_builtin);
         target_option_val!(linker);
         target_option_val!(lld_flavor);
+        target_option_val!(pre_link_objects);
+        target_option_val!(post_link_objects);
+        target_option_val!(pre_link_objects_fallback);
+        target_option_val!(post_link_objects_fallback);
+        target_option_val!(crt_objects_fallback);
         target_option_val!(link_args - pre_link_args);
         target_option_val!(link_args - pre_link_args_crt);
-        target_option_val!(pre_link_objects_exe);
-        target_option_val!(pre_link_objects_exe_crt);
-        target_option_val!(pre_link_objects_dll);
         target_option_val!(link_args - late_link_args);
         target_option_val!(link_args - late_link_args_dynamic);
         target_option_val!(link_args - late_link_args_static);
-        target_option_val!(post_link_objects);
-        target_option_val!(post_link_objects_crt);
         target_option_val!(link_args - post_link_args);
         target_option_val!(link_script);
         target_option_val!(env - link_env);
diff --git a/src/librustc_target/spec/wasm32_base.rs b/src/librustc_target/spec/wasm32_base.rs
index bb19b9d00e8..d4a65aa1a25 100644
--- a/src/librustc_target/spec/wasm32_base.rs
+++ b/src/librustc_target/spec/wasm32_base.rs
@@ -1,3 +1,4 @@
+use super::crt_objects::CrtObjectsFallback;
 use super::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, TargetOptions, TlsModel};
 use std::collections::BTreeMap;
 
@@ -123,6 +124,8 @@ pub fn options() -> TargetOptions {
 
         pre_link_args,
 
+        crt_objects_fallback: Some(CrtObjectsFallback::Wasm),
+
         // This has no effect in LLVM 8 or prior, but in LLVM 9 and later when
         // PIC code is implemented this has quite a drastric effect if it stays
         // at the default, `pic`. In an effort to keep wasm binaries as minimal
diff --git a/src/librustc_target/spec/wasm32_unknown_unknown.rs b/src/librustc_target/spec/wasm32_unknown_unknown.rs
index 22d3885e4af..ded95a34d55 100644
--- a/src/librustc_target/spec/wasm32_unknown_unknown.rs
+++ b/src/librustc_target/spec/wasm32_unknown_unknown.rs
@@ -21,10 +21,6 @@ pub fn target() -> Result<Target, String> {
     // otherwise
     clang_args.push("--target=wasm32-unknown-unknown".to_string());
 
-    // Disable attempting to link crt1.o since it typically isn't present and
-    // isn't needed currently.
-    clang_args.push("-nostdlib".to_string());
-
     // For now this target just never has an entry symbol no matter the output
     // type, so unconditionally pass this.
     clang_args.push("-Wl,--no-entry".to_string());
diff --git a/src/librustc_target/spec/wasm32_wasi.rs b/src/librustc_target/spec/wasm32_wasi.rs
index d5ef230dcf7..0bba7bdd473 100644
--- a/src/librustc_target/spec/wasm32_wasi.rs
+++ b/src/librustc_target/spec/wasm32_wasi.rs
@@ -73,7 +73,7 @@
 //! you know what you're getting in to!
 
 use super::wasm32_base;
-use super::{LinkerFlavor, LldFlavor, Target};
+use super::{crt_objects, LinkerFlavor, LldFlavor, Target};
 
 pub fn target() -> Result<Target, String> {
     let mut options = wasm32_base::options();
@@ -84,9 +84,8 @@ pub fn target() -> Result<Target, String> {
         .or_insert(Vec::new())
         .push("--target=wasm32-wasi".to_string());
 
-    // When generating an executable be sure to put the startup object at the
-    // front so the main function is correctly hooked up.
-    options.pre_link_objects_exe_crt.push("crt1.o".to_string());
+    options.pre_link_objects_fallback = crt_objects::pre_wasi_fallback();
+    options.post_link_objects_fallback = crt_objects::post_wasi_fallback();
 
     // Right now this is a bit of a workaround but we're currently saying that
     // the target by default has a static crt which we're taking as a signal
diff --git a/src/librustc_target/spec/windows_gnu_base.rs b/src/librustc_target/spec/windows_gnu_base.rs
index 33ecb1d0d48..f556bf03f02 100644
--- a/src/librustc_target/spec/windows_gnu_base.rs
+++ b/src/librustc_target/spec/windows_gnu_base.rs
@@ -1,3 +1,4 @@
+use crate::spec::crt_objects::{self, CrtObjectsFallback};
 use crate::spec::{LinkArgs, LinkerFlavor, TargetOptions};
 
 pub fn opts() -> TargetOptions {
@@ -10,8 +11,6 @@ pub fn opts() -> TargetOptions {
             "-fno-use-linker-plugin".to_string(),
             // Always enable DEP (NX bit) when it is available
             "-Wl,--nxcompat".to_string(),
-            // Do not use the standard system startup files or libraries when linking
-            "-nostdlib".to_string(),
         ],
     );
 
@@ -80,18 +79,14 @@ pub fn opts() -> TargetOptions {
         is_like_windows: true,
         allows_weak_linkage: false,
         pre_link_args,
-        pre_link_objects_exe: vec![
-            "crt2.o".to_string(),    // mingw C runtime initialization for executables
-            "rsbegin.o".to_string(), // Rust compiler runtime initialization, see rsbegin.rs
-        ],
-        pre_link_objects_dll: vec![
-            "dllcrt2.o".to_string(), // mingw C runtime initialization for dlls
-            "rsbegin.o".to_string(),
-        ],
+        pre_link_objects: crt_objects::pre_mingw(),
+        post_link_objects: crt_objects::post_mingw(),
+        pre_link_objects_fallback: crt_objects::pre_mingw_fallback(),
+        post_link_objects_fallback: crt_objects::post_mingw_fallback(),
+        crt_objects_fallback: Some(CrtObjectsFallback::Mingw),
         late_link_args,
         late_link_args_dynamic,
         late_link_args_static,
-        post_link_objects: vec!["rsend.o".to_string()],
         abi_return_struct_as_int: true,
         emit_debug_gdb_scripts: false,
         requires_uwtable: true,
diff --git a/src/librustc_target/spec/windows_uwp_gnu_base.rs b/src/librustc_target/spec/windows_uwp_gnu_base.rs
index dd3b60344be..e12a37144da 100644
--- a/src/librustc_target/spec/windows_uwp_gnu_base.rs
+++ b/src/librustc_target/spec/windows_uwp_gnu_base.rs
@@ -3,20 +3,8 @@ use crate::spec::{LinkArgs, LinkerFlavor, TargetOptions};
 pub fn opts() -> TargetOptions {
     let base = super::windows_gnu_base::opts();
 
-    // FIXME: Consider adding `-nostdlib` and inheriting from `windows_gnu_base`.
-    let mut pre_link_args = LinkArgs::new();
-    pre_link_args.insert(
-        LinkerFlavor::Gcc,
-        vec![
-            // Tell GCC to avoid linker plugins, because we are not bundling
-            // them with Windows installer, and Rust does its own LTO anyways.
-            "-fno-use-linker-plugin".to_string(),
-            // Always enable DEP (NX bit) when it is available
-            "-Wl,--nxcompat".to_string(),
-        ],
-    );
-
-    // FIXME: This should be updated for the exception machinery changes from #67502.
+    // FIXME: This should be updated for the exception machinery changes from #67502
+    // and inherit from `windows_gnu_base`, at least partially.
     let mut late_link_args = LinkArgs::new();
     let late_link_args_dynamic = LinkArgs::new();
     let late_link_args_static = LinkArgs::new();
@@ -40,11 +28,6 @@ pub fn opts() -> TargetOptions {
     TargetOptions {
         executables: false,
         limit_rdylib_exports: false,
-        pre_link_args,
-        // FIXME: Consider adding `-nostdlib` and inheriting from `windows_gnu_base`.
-        pre_link_objects_exe: vec!["rsbegin.o".to_string()],
-        // FIXME: Consider adding `-nostdlib` and inheriting from `windows_gnu_base`.
-        pre_link_objects_dll: vec!["rsbegin.o".to_string()],
         late_link_args,
         late_link_args_dynamic,
         late_link_args_static,
diff --git a/src/librustc_target/spec/x86_64_fortanix_unknown_sgx.rs b/src/librustc_target/spec/x86_64_fortanix_unknown_sgx.rs
index 3e9552ef0cf..d26efc09859 100644
--- a/src/librustc_target/spec/x86_64_fortanix_unknown_sgx.rs
+++ b/src/librustc_target/spec/x86_64_fortanix_unknown_sgx.rs
@@ -1,6 +1,6 @@
 use std::iter;
 
-use super::{LinkerFlavor, LldFlavor, PanicStrategy, Target, TargetOptions};
+use super::{crt_objects, LinkerFlavor, LldFlavor, PanicStrategy, Target, TargetOptions};
 
 pub fn target() -> Result<Target, String> {
     const PRE_LINK_ARGS: &[&str] = &[
@@ -68,7 +68,8 @@ pub fn target() -> Result<Target, String> {
             PRE_LINK_ARGS.iter().cloned().map(String::from).collect(),
         ))
         .collect(),
-        post_link_objects: vec!["libunwind.a".into()],
+        // FIXME: libunwind is certainly not a CRT object, use some other option instead.
+        post_link_objects: crt_objects::all("libunwind.a"),
         override_export_symbols: Some(EXPORT_SYMBOLS.iter().cloned().map(String::from).collect()),
         relax_elf_relocations: true,
         ..Default::default()
diff --git a/src/libserialize/json.rs b/src/libserialize/json.rs
index 8f46649048a..2d4e953ac51 100644
--- a/src/libserialize/json.rs
+++ b/src/libserialize/json.rs
@@ -2684,11 +2684,11 @@ impl<A: ToJson> ToJson for Vec<A> {
     }
 }
 
-impl<A: ToJson> ToJson for BTreeMap<string::String, A> {
+impl<T: ToString, A: ToJson> ToJson for BTreeMap<T, A> {
     fn to_json(&self) -> Json {
         let mut d = BTreeMap::new();
         for (key, value) in self {
-            d.insert((*key).clone(), value.to_json());
+            d.insert(key.to_string(), value.to_json());
         }
         Json::Object(d)
     }