diff --git a/src/librustc_codegen_ssa/back/link.rs b/src/librustc_codegen_ssa/back/link.rs index a2b50ea8e2bf7..34e0628511a2d 100644 --- a/src/librustc_codegen_ssa/back/link.rs +++ b/src/librustc_codegen_ssa/back/link.rs @@ -460,6 +460,136 @@ fn link_staticlib<'a, B: ArchiveBuilder<'a>>(sess: &'a Session, } } + +/// rust-lang/rust#61539: bugs in older versions of GNU `ld` cause problems that +/// are readily exposed under our default setting of disabling PLT (PR +/// rust-lang/rust#54592). Heuristically detect and warn about this. +fn check_for_buggy_ld_version(sess: &Session, + program_name: &Path, + flavor: LinkerFlavor, + crate_type: config::CrateType) { + debug!("check_for_buggy_ld_version: \ + program_name: {:?} flavor: {:?} crate_type: {:?}", + program_name, flavor, crate_type); + + match crate_type { + // This is the one case that we fire on, because it is the one place we + // know of where using the output in a "supported" fashion (*) can + // trigger the bug in old GNU ld versions. + // + // (*) Of course this raises the question of how much support do we give + // Rust dylibs in the first place + config::CrateType::Dylib => (), + + // FIXME: should we include CrateType::Cdylib in the warning? It is not + // clear why we haven't seen it there. + config::CrateType::Cdylib => return, + + // We deliberately do not include CrateType::ProcMacro in the warning, + // as it would cause too many false-positives (and ot actually observe + // the known bugs in that context, you would have to be using the + // geneated dylib in an unsupported fashion anyway). + config::CrateType::ProcMacro => return, + + // Static objects won't run into this (unless they load a dynamic + // object, which this heuristic is not attempting to detect). + config::CrateType::Executable | + config::CrateType::Rlib | + config::CrateType::Staticlib => return, + }; + + let mut version_cmd = Command::new(program_name); + match flavor { + LinkerFlavor::Gcc => { + // run `gcc -v -Xlinker --version` to query gcc for version info of underlying linker + version_cmd.args(&["-v", "-Xlinker", "--version"]); + } + LinkerFlavor::Ld => { + // run `ld --version` + version_cmd.args(&["--version"]); + } + LinkerFlavor::Msvc | + LinkerFlavor::Em | + LinkerFlavor::Lld(..) | + LinkerFlavor::PtxLinker => { + // Not GNU ld, so don't bother inspecting version. + return; + } + } + let version_check_invocation = format!("{:?}", &version_cmd); + debug!("check_for_buggy_ld_version version_check_invocation: {:?}", + version_check_invocation); + let output = match version_cmd.output() { + Err(_) => { + sess.warn(&format!("Linker version inspection failed to execute: `{}`", + version_check_invocation)); + return; + } + Ok(output) => output, + }; + let out = String::from_utf8_lossy(&output.stdout); + + debug!("check_for_buggy_ld_version out: {:?}", out); + + let first_line = match out.lines().next() { + Some(line) => line, + None => { + sess.warn(&format!("Linker version inspection had no lines of output: `{}`", + version_check_invocation)); + return; + } + }; + debug!("check_for_buggy_ld_version first_line: {:?}", first_line); + + if !first_line.contains("GNU ld") { + // If we cannot find "GNU ld" in the version string, then assume that + // this is not actually GNU ld; no need for warning. + return; + } + + let version_suffix_start = match first_line.find(" 2.") { + None => { + // if we cannot find ` 2.`, then assume that this an ld version that + // does not have the bug; no need for warning. + return; + } + Some(space_version_start) => { + // skip the space character so we are looking at "2.x.y-zzz" + space_version_start + 1 + } + }; + let version_suffix = &first_line[version_suffix_start..]; + debug!("check_for_buggy_ld_version version_suffix: {:?}", version_suffix); + + let parse = |suffix: &str| -> Option<(u32, u32)> { + let mut components = suffix.split('.'); + let major: u32 = components.next()?.parse().ok()?; + let minor: u32 = components.next()?.parse().ok()?; + Some((major, minor)) + }; + let (major, minor) = match parse(version_suffix) { + None => { + sess.warn(&format!("Linker version inspection failed to parse: `{}`, output: {}", + version_check_invocation, out)); + return; + } + Some(version) => version, + }; + debug!("check_for_buggy_ld_version (major, minor): {:?}", (major, minor)); + + let is_old_buggy_version = major < 2 || (major == 2 && minor < 29); + + if is_old_buggy_version { + let diag = sess.diagnostic(); + diag.warn(&format!("Using linker `{}` with Rust dynamic libraries has known bugs.", + first_line)); + diag.note_without_error( + "Consider upgrading to GNU ld version 2.29 or newer, or using a different linker."); + diag.note_without_error( + "For more information, see https://github.com/rust-lang/rust/issues/61539"); + } +} + // Create a dynamic library or executable // // This will invoke the system linker/cc to create the resulting file. This @@ -476,6 +606,9 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(sess: &'a Session, // The invocations of cc share some flags across platforms let (pname, mut cmd) = get_linker(sess, &linker, flavor); + // rust-lang/rust#61539: heuristically inspect ld version to warn about bugs + check_for_buggy_ld_version(sess, &pname, flavor, crate_type); + if let Some(args) = sess.target.target.options.pre_link_args.get(&flavor) { cmd.args(args); }