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..7b3182c0a588f 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 + } + } } ) } @@ -563,7 +589,6 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options, "set the inlining threshold for"), } - options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, build_debugging_options, "Z", "debugging", DB_OPTIONS, db_type_desc, dbsetters, @@ -654,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 { @@ -698,6 +725,18 @@ 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.debugging_opts.sanitize.map(|s| { + let name = match s { + Sanitize::Address => "address", + Sanitize::Leak => "leak", + Sanitize::Memory => "memory", + Sanitize::Thread => "thread" + }; + ret.push(mk(InternedString::new("sanitize"), + 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..c5489a30898e3 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.debugging_opts.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..c8ba26ce525a1 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,35 +858,44 @@ fn link_natively(sess: &Session, dylib: bool, let (pname, mut cmd) = get_linker(sess); cmd.env("PATH", command_path(sess)); - 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"); } + + 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); + } } - 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 sess.opts.debugging_opts.print_link_args { println!("{:?}", &cmd); @@ -939,6 +949,34 @@ fn link_natively(sess: &Session, dylib: bool, } } +fn link_sanitizers(sess: &Session, dylib: bool, linker: &mut Linker) -> bool { + // Sanitizer runtimes are linked into final executable only. + if dylib { return false; } + + sess.opts.debugging_opts.sanitize.map(|s| { + let runtime = match s { + Sanitize::Address => "rustc_asan", + Sanitize::Leak => "rustc_lsan", + Sanitize::Memory => "rustc_msan", + Sanitize::Thread => "rustc_tsan", + }; + linker.link_whole_staticlib(runtime, &[]); + }); + true +} + +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. + 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" { + linker.link_dylib("dl"); + } +} + fn link_args(cmd: &mut Linker, sess: &Session, dylib: bool, @@ -982,7 +1020,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 +1117,16 @@ 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 { + // 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 // // 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..35908a88e419c 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.debugging_opts.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..ba239f29d8d61 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.debugging_opts.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.