From 1d9e6eef0102f939776d660729dd71e2bc1fd170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Fri, 12 Feb 2016 16:17:39 +0100 Subject: [PATCH 1/3] Sanitizers support. --- configure | 1 + src/bootstrap/build/compile.rs | 23 ++++++++++++++++ src/bootstrap/build/config.rs | 14 ++++++++++ src/bootstrap/build/native.rs | 14 ++++++++-- src/librustc/session/config.rs | 42 ++++++++++++++++++++++++++-- src/librustc_llvm/lib.rs | 3 ++ src/librustc_metadata/creader.rs | 15 ++++++++-- src/librustc_trans/back/link.rs | 47 +++++++++++++++++++++++++++++++- src/librustc_trans/back/write.rs | 17 ++++++++++++ src/librustc_trans/trans/base.rs | 11 ++++++++ 10 files changed, 180 insertions(+), 7 deletions(-) diff --git a/configure b/configure index 7958ac9bfcfe3..2ad895827a173 100755 --- a/configure +++ b/configure @@ -607,6 +607,7 @@ opt dist-host-only 0 "only install bins for the host architecture" opt inject-std-version 1 "inject the current compiler version of libstd into programs" opt llvm-version-check 1 "check if the LLVM version is supported, build anyway" opt rustbuild 0 "use the rust and cargo based build system" +opt sanitizers 0 "build sanitizers runtime libraries (works only with --enable-rustbuild)" # Optimization and debugging options. These may be overridden by the release channel, etc. opt_nosave optimize 1 "build optimized rust code" diff --git a/src/bootstrap/build/compile.rs b/src/bootstrap/build/compile.rs index c22648b471098..cd72a794ee060 100644 --- a/src/bootstrap/build/compile.rs +++ b/src/bootstrap/build/compile.rs @@ -37,6 +37,29 @@ pub fn std<'a>(build: &'a Build, stage: u32, target: &str, t!(fs::hard_link(&build.compiler_rt_built.borrow()[target], libdir.join(staticlib("compiler-rt", target)))); + // Non-portable hack that copies sanitizer runtime libraries. + let runtimes = ["asan", "lsan", "msan", "tsan"]; + for runtime in runtimes.iter() { + let arch = target.split('-').next().unwrap(); + let llvm_lib_name = staticlib(&format!("clang_rt.{}-{}", runtime, arch), target); + let llvm_dir = build.compiler_rt_out(target) + .join("build").join("lib").join("linux"); + + // Avoid potential confusion with runtime libraries used by other + // compilers by appending rustc to library name. + let rust_lib_name = staticlib(&format!("rustc_{}", runtime), target); + + let llvm_path = llvm_dir.join(&llvm_lib_name); + let rust_path = libdir.join(&rust_lib_name); + match fs::hard_link(&llvm_path, &rust_path) { + Err(e) => { + println!("Ignored runtime library {}: {}", runtime, e); + continue + } + Ok(_) => {}, + } + } + build_startup_objects(build, target, &libdir); let out_dir = build.cargo_out(stage, &host, true, target); diff --git a/src/bootstrap/build/config.rs b/src/bootstrap/build/config.rs index 1e67c4a9a3e8d..1448b3c42f642 100644 --- a/src/bootstrap/build/config.rs +++ b/src/bootstrap/build/config.rs @@ -43,6 +43,9 @@ pub struct Config { pub llvm_version_check: bool, pub llvm_static_stdcpp: bool, + // compiler-rt options + pub compiler_rt_sanitizers: bool, + // rust codegen options pub rust_optimize: bool, pub rust_codegen_units: u32, @@ -87,6 +90,7 @@ pub struct Target { struct TomlConfig { build: Option, llvm: Option, + compiler_rt: Option, rust: Option, target: Option>, } @@ -113,6 +117,12 @@ struct Llvm { static_libstdcpp: Option, } +/// TOML representation of how the compiler-rt build is configured. +#[derive(RustcDecodable, Default)] +struct CompilerRt { + sanitizers: Option, +} + /// TOML representation of how the Rust build is configured. #[derive(RustcDecodable, Default)] struct Rust { @@ -206,6 +216,9 @@ impl Config { set(&mut config.llvm_version_check, llvm.version_check); set(&mut config.llvm_static_stdcpp, llvm.static_libstdcpp); } + if let Some(ref compiler_rt) = toml.compiler_rt { + set(&mut config.compiler_rt_sanitizers, compiler_rt.sanitizers); + } if let Some(ref rust) = toml.rust { set(&mut config.rust_debug_assertions, rust.debug_assertions); set(&mut config.rust_debuginfo, rust.debuginfo); @@ -286,6 +299,7 @@ impl Config { ("OPTIMIZE_LLVM", self.llvm_optimize), ("LLVM_VERSION_CHECK", self.llvm_version_check), ("LLVM_STATIC_STDCPP", self.llvm_static_stdcpp), + ("SANITIZERS", self.compiler_rt_sanitizers), ("OPTIMIZE", self.rust_optimize), ("DEBUG_ASSERTIONS", self.rust_debug_assertions), ("DEBUGINFO", self.rust_debuginfo), diff --git a/src/bootstrap/build/native.rs b/src/bootstrap/build/native.rs index 6ad5f40412394..c19e93b805f54 100644 --- a/src/bootstrap/build/native.rs +++ b/src/bootstrap/build/native.rs @@ -113,7 +113,7 @@ pub fn compiler_rt(build: &Build, target: &str) { let dst = build.compiler_rt_out(target); let arch = target.split('-').next().unwrap(); let mode = if build.config.rust_optimize {"Release"} else {"Debug"}; - let (dir, build_target, libname) = if target.contains("linux") { + let (dir, mut build_target, libname) = if target.contains("linux") { let os = if target.contains("android") {"-android"} else {""}; let target = format!("clang_rt.builtins-{}{}", arch, os); ("linux".to_string(), target.clone(), target) @@ -130,6 +130,15 @@ pub fn compiler_rt(build: &Build, target: &str) { } else { panic!("can't get os from target: {}", target) }; + let build_sanitizers = if build.config.compiler_rt_sanitizers { + // Leverage compiler-rt build system to compile only sanitizers + // supported on given target. + build_target = "all".to_string(); + "ON" + } else { + "OFF" + }; + let output = dst.join("build/lib").join(dir) .join(staticlib(&libname, target)); build.compiler_rt_built.borrow_mut().insert(target.to_string(), @@ -142,6 +151,7 @@ pub fn compiler_rt(build: &Build, target: &str) { let build_llvm_config = build.llvm_out(&build.config.build) .join("bin") .join(exe("llvm-config", &build.config.build)); + let mut cfg = cmake::Config::new(build.src.join("src/compiler-rt")); cfg.target(target) .host(&build.config.build) @@ -149,7 +159,7 @@ pub fn compiler_rt(build: &Build, target: &str) { .profile(mode) .define("LLVM_CONFIG_PATH", build_llvm_config) .define("COMPILER_RT_DEFAULT_TARGET_TRIPLE", target) - .define("COMPILER_RT_BUILD_SANITIZERS", "OFF") + .define("COMPILER_RT_BUILD_SANITIZERS", build_sanitizers) .define("COMPILER_RT_BUILD_EMUTLS", "OFF") .define("CMAKE_C_COMPILER", build.cc(target)) .build_target(&build_target); diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index c96713a72851a..90322065a390b 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -106,6 +106,14 @@ impl OutputType { } } +#[derive(Clone, Copy, PartialEq)] +pub enum Sanitize { + Address, + Leak, + Memory, + Thread, +} + #[derive(Clone)] pub struct Options { // The crate config requested for the session, which may be combined @@ -388,11 +396,13 @@ macro_rules! options { Some("a space-separated list of passes, or `all`"); pub const parse_opt_uint: Option<&'static str> = Some("a number"); + pub const parse_sanitize: Option<&'static str> = + Some("one of: `address`, `leak`, `memory`, or `thread`"); } #[allow(dead_code)] mod $mod_set { - use super::{$struct_name, Passes, SomePasses, AllPasses}; + use super::{$struct_name, Passes, SomePasses, AllPasses, Sanitize}; $( pub fn $opt(cg: &mut $struct_name, v: Option<&str>) -> bool { @@ -496,6 +506,22 @@ macro_rules! options { } } } + + fn parse_sanitize(slot: &mut Option, v: Option<&str>) -> bool { + if let Some(s) = v { + let sanitize = match s { + "address" => Sanitize::Address, + "leak" => Sanitize::Leak, + "memory" => Sanitize::Memory, + "thread" => Sanitize::Thread, + _ => return false, + }; + *slot = Some(sanitize); + true + } else { + false + } + } } ) } @@ -561,9 +587,10 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options, "explicitly enable the cfg(debug_assertions) directive"), inline_threshold: Option = (None, parse_opt_uint, "set the inlining threshold for"), + sanitize: Option = (None, parse_sanitize, + "choose the sanitizer to use"), } - options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, build_debugging_options, "Z", "debugging", DB_OPTIONS, db_type_desc, dbsetters, @@ -698,6 +725,17 @@ pub fn default_configuration(sess: &Session) -> ast::CrateConfig { if sess.opts.debug_assertions { ret.push(attr::mk_word_item(InternedString::new("debug_assertions"))); } + + sess.opts.cg.sanitize.map(|s| { + let name = match s { + Sanitize::Address => "sanitize_address", + Sanitize::Leak => "sanitize_leak", + Sanitize::Memory => "sanitize_memory", + Sanitize::Thread => "sanitize_thread" + }; + ret.push(attr::mk_word_item(InternedString::new(name))); + }); + return ret; } diff --git a/src/librustc_llvm/lib.rs b/src/librustc_llvm/lib.rs index 1933c926e3018..c9bb6bfc49a00 100644 --- a/src/librustc_llvm/lib.rs +++ b/src/librustc_llvm/lib.rs @@ -160,6 +160,9 @@ bitflags! { const ReturnsTwice = 1 << 29, const UWTable = 1 << 30, const NonLazyBind = 1 << 31, + const SanitizeAddress = 1 << 32, + const SanitizeThread = 1 << 36, + const SanitizeMemory = 1 << 37, const OptimizeNone = 1 << 42, } } diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs index 35d7a0d4b9c27..a85ab6e722a5b 100644 --- a/src/librustc_metadata/creader.rs +++ b/src/librustc_metadata/creader.rs @@ -639,16 +639,27 @@ impl<'a> CrateReader<'a> { // prefer-dynamic`, then we interpret this as a *Rust* dynamic library // is being produced so we use the exe allocator instead. // - // What this boils down to is: + // If some sanitizer is enabled, then we prefer to use library allocator + // as sanitizers work by intercepting calls to standard memory + // allocation routines (i.e., malloc and friends). If non-standard + // memory allocation routines would be used, then sanitizers would be + // completly unaware of memory allocations, which considerably limits + // their utility (or in some cases just don't work at all). + // + // What this boils down to is following. If sanitizers are enabled, use + // system malloc, otherwise: // // * Binaries use jemalloc // * Staticlibs and Rust dylibs use system malloc // * Rust dylibs used as dependencies to rust use jemalloc - let name = if need_lib_alloc && !self.sess.opts.cg.prefer_dynamic { + let use_lib_alloc = (need_lib_alloc && !self.sess.opts.cg.prefer_dynamic) + || self.sess.opts.cg.sanitize.is_some(); + let name = if use_lib_alloc { &self.sess.target.target.options.lib_allocation_crate } else { &self.sess.target.target.options.exe_allocation_crate }; + let (cnum, data, _) = self.resolve_crate(&None, name, name, None, codemap::DUMMY_SP, PathKind::Crate, false); diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index 69a70cdf144b3..962e5a54a754e 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -15,6 +15,7 @@ use super::rpath; use super::msvc; use super::svh::Svh; use session::config; +use session::config::Sanitize; use session::config::NoDebugInfo; use session::config::{OutputFilenames, Input, OutputType}; use session::filesearch; @@ -857,6 +858,9 @@ fn link_natively(sess: &Session, dylib: bool, let (pname, mut cmd) = get_linker(sess); cmd.env("PATH", command_path(sess)); + // Sanitizer runtimes go first if any. + let needs_sanitizers_runtime_deps = link_sanitizers(sess, dylib, &mut cmd); + let root = sess.target_filesearch(PathKind::Native).get_lib_path(); cmd.args(&sess.target.target.options.pre_link_args); @@ -887,6 +891,10 @@ fn link_natively(sess: &Session, dylib: bool, } cmd.args(&sess.target.target.options.post_link_args); + if needs_sanitizers_runtime_deps { + link_sanitizers_runtime_deps(sess, &mut cmd); + } + if sess.opts.debugging_opts.print_link_args { println!("{:?}", &cmd); } @@ -939,6 +947,38 @@ fn link_natively(sess: &Session, dylib: bool, } } +fn link_sanitizers(sess: &Session, dylib: bool, cmd: &mut Command) -> bool { + // Sanitizer runtimes are linked into final executable only. + if dylib { return false; } + + sess.opts.cg.sanitize.map(|s| { + let runtime = match s { + Sanitize::Address => "rustc_asan", + Sanitize::Leak => "rustc_lsan", + Sanitize::Memory => "rustc_msan", + Sanitize::Thread => "rustc_tsan", + }; + // FIXME: This should be done through a linker trait. + cmd.arg("-Wl,-whole-archive"); + cmd.arg("-l").arg(runtime); + cmd.arg("-Wl,-no-whole-archive"); + }); + true +} + +fn link_sanitizers_runtime_deps(sess: &Session, cmd: &mut Command) { + // Make sure that sanitizers runtime dependencies are always linked in. + // This avoids potential problems when using --as-needed. + // FIXME: This should be done through a linker trait. + cmd.arg("-Wl,--no-as-needed"); + cmd.arg("-lpthread"); + cmd.arg("-lrt"); + cmd.arg("-lm"); + if sess.target.target.target_os != "freebsd" { + cmd.arg("-ldl"); + } +} + fn link_args(cmd: &mut Linker, sess: &Session, dylib: bool, @@ -982,7 +1022,7 @@ fn link_args(cmd: &mut Linker, let used_link_args = sess.cstore.used_link_args(); - if !dylib && t.options.position_independent_executables { + if !dylib && t.options.position_independent_executables && sanitizer_support_pie(sess) { let empty_vec = Vec::new(); let empty_str = String::new(); let args = sess.opts.cg.link_args.as_ref().unwrap_or(&empty_vec); @@ -1079,6 +1119,11 @@ fn link_args(cmd: &mut Linker, cmd.args(&used_link_args); } +// Checks if sanitizer supports position independent executables. +fn sanitizer_support_pie(sess: &Session) -> bool { + sess.opts.cg.sanitize != Some(Sanitize::Thread) +} + // # Native library linking // // User-supplied library search paths (-L on the command line). These are diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index 92d8b928ef428..ac6d298407115 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -636,6 +636,23 @@ pub fn run_passes(sess: &Session, let mut modules_config = ModuleConfig::new(tm, sess.opts.cg.passes.clone()); let mut metadata_config = ModuleConfig::new(tm, vec!()); + sess.opts.cg.sanitize.map(|s| { + let ref mut passes = modules_config.passes; + match s { + config::Sanitize::Address => { + passes.push("asan".to_owned()); + passes.push("asan-module".to_owned()); + }, + config::Sanitize::Leak => {}, + config::Sanitize::Memory => { + passes.push("msan".to_owned()); + }, + config::Sanitize::Thread => { + passes.push("tsan".to_owned()); + }, + }; + }); + modules_config.opt_level = Some(get_llvm_opt_level(sess.opts.optimize)); // Save all versions of the bytecode if we're saving our temporaries. diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs index e36905c6d90ea..2483908131da7 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -1636,6 +1636,17 @@ pub fn init_function<'a, 'tcx>(fcx: &'a FunctionContext<'a, 'tcx>, skip_retptr: bool, output: ty::FnOutput<'tcx>) -> Block<'a, 'tcx> { + + fcx.ccx.sess().opts.cg.sanitize.map(|s| { + let attr = match s { + config::Sanitize::Address => llvm::Attribute::SanitizeAddress, + config::Sanitize::Memory => llvm::Attribute::SanitizeMemory, + config::Sanitize::Thread => llvm::Attribute::SanitizeThread, + config::Sanitize::Leak => return, + }; + llvm::SetFunctionAttribute(fcx.llfn, attr); + }); + let entry_bcx = fcx.new_temp_block("entry-block"); // Use a dummy instruction as the insertion point for all allocas. From d1dcd1c11d459ebea8b9f4bd4c3b2d72a9186282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Sat, 13 Feb 2016 14:53:26 +0100 Subject: [PATCH 2/3] Move sanitize option to -Z. Use name value CLI for sanitizers cfg. --- src/librustc/session/config.rs | 17 +++++++++-------- src/librustc_metadata/creader.rs | 2 +- src/librustc_trans/back/link.rs | 9 +++++++-- src/librustc_trans/back/write.rs | 2 +- src/librustc_trans/trans/base.rs | 2 +- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 90322065a390b..7b3182c0a588f 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -587,8 +587,6 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options, "explicitly enable the cfg(debug_assertions) directive"), inline_threshold: Option = (None, parse_opt_uint, "set the inlining threshold for"), - sanitize: Option = (None, parse_sanitize, - "choose the sanitizer to use"), } options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, @@ -681,6 +679,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "show spans for compiler debugging (expr|pat|ty)"), print_trans_items: Option = (None, parse_opt_string, "print the result of the translation item collection pass"), + sanitize: Option = (None, parse_sanitize, + "choose the sanitizer to use"), } pub fn default_lib_output() -> CrateType { @@ -726,14 +726,15 @@ pub fn default_configuration(sess: &Session) -> ast::CrateConfig { ret.push(attr::mk_word_item(InternedString::new("debug_assertions"))); } - sess.opts.cg.sanitize.map(|s| { + sess.opts.debugging_opts.sanitize.map(|s| { let name = match s { - Sanitize::Address => "sanitize_address", - Sanitize::Leak => "sanitize_leak", - Sanitize::Memory => "sanitize_memory", - Sanitize::Thread => "sanitize_thread" + Sanitize::Address => "address", + Sanitize::Leak => "leak", + Sanitize::Memory => "memory", + Sanitize::Thread => "thread" }; - ret.push(attr::mk_word_item(InternedString::new(name))); + ret.push(mk(InternedString::new("sanitize"), + InternedString::new(name))); }); return ret; diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs index a85ab6e722a5b..c5489a30898e3 100644 --- a/src/librustc_metadata/creader.rs +++ b/src/librustc_metadata/creader.rs @@ -653,7 +653,7 @@ impl<'a> CrateReader<'a> { // * Staticlibs and Rust dylibs use system malloc // * Rust dylibs used as dependencies to rust use jemalloc let use_lib_alloc = (need_lib_alloc && !self.sess.opts.cg.prefer_dynamic) - || self.sess.opts.cg.sanitize.is_some(); + || self.sess.opts.debugging_opts.sanitize.is_some(); let name = if use_lib_alloc { &self.sess.target.target.options.lib_allocation_crate } else { diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index 962e5a54a754e..6b4ceebd71caf 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -951,7 +951,7 @@ fn link_sanitizers(sess: &Session, dylib: bool, cmd: &mut Command) -> bool { // Sanitizer runtimes are linked into final executable only. if dylib { return false; } - sess.opts.cg.sanitize.map(|s| { + sess.opts.debugging_opts.sanitize.map(|s| { let runtime = match s { Sanitize::Address => "rustc_asan", Sanitize::Leak => "rustc_lsan", @@ -1121,7 +1121,12 @@ fn link_args(cmd: &mut Linker, // Checks if sanitizer supports position independent executables. fn sanitizer_support_pie(sess: &Session) -> bool { - sess.opts.cg.sanitize != Some(Sanitize::Thread) + // Thread sanitizer does not support memory mapping changes introduced in + // Linux kernel 4.1.2 and will fail with following error when run: + // FATAL: ThreadSanitizer: unexpected memory mapping + // + // Issue on thread sanitizer bugtracker: https://github.com/google/sanitizers/issues/503 + sess.opts.debugging_opts.sanitize != Some(Sanitize::Thread) } // # Native library linking diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs index ac6d298407115..35908a88e419c 100644 --- a/src/librustc_trans/back/write.rs +++ b/src/librustc_trans/back/write.rs @@ -636,7 +636,7 @@ pub fn run_passes(sess: &Session, let mut modules_config = ModuleConfig::new(tm, sess.opts.cg.passes.clone()); let mut metadata_config = ModuleConfig::new(tm, vec!()); - sess.opts.cg.sanitize.map(|s| { + sess.opts.debugging_opts.sanitize.map(|s| { let ref mut passes = modules_config.passes; match s { config::Sanitize::Address => { diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs index 2483908131da7..ba239f29d8d61 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -1637,7 +1637,7 @@ pub fn init_function<'a, 'tcx>(fcx: &'a FunctionContext<'a, 'tcx>, output: ty::FnOutput<'tcx>) -> Block<'a, 'tcx> { - fcx.ccx.sess().opts.cg.sanitize.map(|s| { + fcx.ccx.sess().opts.debugging_opts.sanitize.map(|s| { let attr = match s { config::Sanitize::Address => llvm::Attribute::SanitizeAddress, config::Sanitize::Memory => llvm::Attribute::SanitizeMemory, From 1b0de5b6a08d4001db3db1f3dd65493728ff254a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Sat, 13 Feb 2016 20:50:09 +0100 Subject: [PATCH 3/3] Use linker trait when linking sanitizer runtime libraries. --- src/librustc_trans/back/link.rs | 68 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index 6b4ceebd71caf..c8ba26ce525a1 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -858,41 +858,43 @@ fn link_natively(sess: &Session, dylib: bool, let (pname, mut cmd) = get_linker(sess); cmd.env("PATH", command_path(sess)); - // Sanitizer runtimes go first if any. - let needs_sanitizers_runtime_deps = link_sanitizers(sess, dylib, &mut cmd); - - let root = sess.target_filesearch(PathKind::Native).get_lib_path(); - cmd.args(&sess.target.target.options.pre_link_args); - - let pre_link_objects = if dylib { - &sess.target.target.options.pre_link_objects_dll - } else { - &sess.target.target.options.pre_link_objects_exe - }; - for obj in pre_link_objects { - cmd.arg(root.join(obj)); - } - { let mut linker = if sess.target.target.options.is_like_msvc { Box::new(MsvcLinker { cmd: &mut cmd, sess: &sess }) as Box } else { Box::new(GnuLinker { cmd: &mut cmd, sess: &sess }) as Box }; + + // Sanitizer runtimes go first if any. + let needs_sanitizers_runtime_deps = link_sanitizers(sess, dylib, &mut *linker); + + let root = sess.target_filesearch(PathKind::Native).get_lib_path(); + linker.args(&sess.target.target.options.pre_link_args); + + let pre_link_objects = if dylib { + &sess.target.target.options.pre_link_objects_dll + } else { + &sess.target.target.options.pre_link_objects_exe + }; + for obj in pre_link_objects { + linker.add_object(&root.join(obj)); + } + link_args(&mut *linker, sess, dylib, tmpdir, objects, out_filename, trans, outputs); if !sess.target.target.options.no_compiler_rt { linker.link_staticlib("compiler-rt"); } - } - cmd.args(&sess.target.target.options.late_link_args); - for obj in &sess.target.target.options.post_link_objects { - cmd.arg(root.join(obj)); - } - cmd.args(&sess.target.target.options.post_link_args); - if needs_sanitizers_runtime_deps { - link_sanitizers_runtime_deps(sess, &mut cmd); + linker.args(&sess.target.target.options.late_link_args); + for obj in &sess.target.target.options.post_link_objects { + linker.add_object(&root.join(obj)); + } + linker.args(&sess.target.target.options.post_link_args); + + if needs_sanitizers_runtime_deps { + link_sanitizers_runtime_deps(sess, &mut *linker); + } } if sess.opts.debugging_opts.print_link_args { @@ -947,7 +949,7 @@ fn link_natively(sess: &Session, dylib: bool, } } -fn link_sanitizers(sess: &Session, dylib: bool, cmd: &mut Command) -> bool { +fn link_sanitizers(sess: &Session, dylib: bool, linker: &mut Linker) -> bool { // Sanitizer runtimes are linked into final executable only. if dylib { return false; } @@ -958,24 +960,20 @@ fn link_sanitizers(sess: &Session, dylib: bool, cmd: &mut Command) -> bool { Sanitize::Memory => "rustc_msan", Sanitize::Thread => "rustc_tsan", }; - // FIXME: This should be done through a linker trait. - cmd.arg("-Wl,-whole-archive"); - cmd.arg("-l").arg(runtime); - cmd.arg("-Wl,-no-whole-archive"); + linker.link_whole_staticlib(runtime, &[]); }); true } -fn link_sanitizers_runtime_deps(sess: &Session, cmd: &mut Command) { +fn link_sanitizers_runtime_deps(sess: &Session, linker: &mut Linker) { // Make sure that sanitizers runtime dependencies are always linked in. // This avoids potential problems when using --as-needed. - // FIXME: This should be done through a linker trait. - cmd.arg("-Wl,--no-as-needed"); - cmd.arg("-lpthread"); - cmd.arg("-lrt"); - cmd.arg("-lm"); + linker.args(&["-Wl,--no-as-needed".to_owned()]); + linker.link_dylib("pthread"); + linker.link_dylib("rt"); + linker.link_dylib("m"); if sess.target.target.target_os != "freebsd" { - cmd.arg("-ldl"); + linker.link_dylib("dl"); } }