Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Sanitizers support. #31605

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
23 changes: 23 additions & 0 deletions src/bootstrap/build/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions src/bootstrap/build/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -87,6 +90,7 @@ pub struct Target {
struct TomlConfig {
build: Option<Build>,
llvm: Option<Llvm>,
compiler_rt: Option<CompilerRt>,
rust: Option<Rust>,
target: Option<HashMap<String, TomlTarget>>,
}
Expand All @@ -113,6 +117,12 @@ struct Llvm {
static_libstdcpp: Option<bool>,
}

/// TOML representation of how the compiler-rt build is configured.
#[derive(RustcDecodable, Default)]
struct CompilerRt {
sanitizers: Option<bool>,
}

/// TOML representation of how the Rust build is configured.
#[derive(RustcDecodable, Default)]
struct Rust {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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),
Expand Down
14 changes: 12 additions & 2 deletions src/bootstrap/build/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(),
Expand All @@ -142,14 +151,15 @@ 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)
.out_dir(&dst)
.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);
Expand Down
43 changes: 41 additions & 2 deletions src/librustc/session/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -496,6 +506,22 @@ macro_rules! options {
}
}
}

fn parse_sanitize(slot: &mut Option<Sanitize>, 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
}
}
}
) }

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -654,6 +679,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"show spans for compiler debugging (expr|pat|ty)"),
print_trans_items: Option<String> = (None, parse_opt_string,
"print the result of the translation item collection pass"),
sanitize: Option<Sanitize> = (None, parse_sanitize,
"choose the sanitizer to use"),
}

pub fn default_lib_output() -> CrateType {
Expand Down Expand Up @@ -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;
}

Expand Down
3 changes: 3 additions & 0 deletions src/librustc_llvm/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
15 changes: 13 additions & 2 deletions src/librustc_metadata/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
84 changes: 66 additions & 18 deletions src/librustc_trans/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Linker>
} else {
Box::new(GnuLinker { cmd: &mut cmd, sess: &sess }) as Box<Linker>
};

// 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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Loading