Skip to content

Commit

Permalink
Link sanitizer runtimes instead of injecting crate dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
tmiasko committed Jan 9, 2020
1 parent 36d0812 commit 0c6b1a7
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 175 deletions.
89 changes: 42 additions & 47 deletions src/librustc_codegen_ssa/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(

{
let mut linker = codegen_results.linker_info.to_linker(cmd, &sess, flavor, target_cpu);
link_sanitizer_runtime(sess, crate_type, &mut *linker);
link_args::<B>(
&mut *linker,
flavor,
Expand Down Expand Up @@ -735,6 +736,47 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(
}
}

fn link_sanitizer_runtime(sess: &Session, crate_type: config::CrateType, linker: &mut dyn Linker) {
let sanitizer = match &sess.opts.debugging_opts.sanitizer {
Some(s) => s,
None => return,
};

if crate_type != config::CrateType::Executable {
return;
}

let name = match sanitizer {
Sanitizer::Address => "asan",
Sanitizer::Leak => "lsan",
Sanitizer::Memory => "msan",
Sanitizer::Thread => "tsan",
};

let default_sysroot = filesearch::get_or_default_sysroot();
let default_tlib =
filesearch::make_target_lib_path(&default_sysroot, sess.opts.target_triple.triple());

match sess.opts.target_triple.triple() {
"x86_64-apple-darwin" => {
// On Apple platforms, the sanitizer is always built as a dylib, and
// LLVM will link to `@rpath/*.dylib`, so we need to specify an
// rpath to the library as well (the rpath should be absolute, see
// PR #41352 for details).
let libname = format!("rustc_rt.{}", name);
let rpath = default_tlib.to_str().expect("non-utf8 component in path");
linker.args(&["-Wl,-rpath".into(), "-Xlinker".into(), rpath.into()]);
linker.link_dylib(Symbol::intern(&libname));
}
"x86_64-unknown-linux-gnu" => {
let filename = format!("librustc_rt.{}.a", name);
let path = default_tlib.join(&filename);
linker.link_whole_rlib(&path);
}
_ => {}
}
}

/// Returns a boolean indicating whether the specified crate should be ignored
/// during LTO.
///
Expand Down Expand Up @@ -1415,12 +1457,6 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>(
_ if codegen_results.crate_info.profiler_runtime == Some(cnum) => {
add_static_crate::<B>(cmd, sess, codegen_results, tmpdir, crate_type, cnum);
}
_ if codegen_results.crate_info.sanitizer_runtime == Some(cnum)
&& crate_type == config::CrateType::Executable =>
{
// Link the sanitizer runtimes only if we are actually producing an executable
link_sanitizer_runtime::<B>(cmd, sess, codegen_results, tmpdir, cnum);
}
// compiler-builtins are always placed last to ensure that they're
// linked correctly.
_ if codegen_results.crate_info.compiler_builtins == Some(cnum) => {
Expand Down Expand Up @@ -1457,47 +1493,6 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>(
}
}

// We must link the sanitizer runtime using -Wl,--whole-archive but since
// it's packed in a .rlib, it contains stuff that are not objects that will
// make the linker error. So we must remove those bits from the .rlib before
// linking it.
fn link_sanitizer_runtime<'a, B: ArchiveBuilder<'a>>(
cmd: &mut dyn Linker,
sess: &'a Session,
codegen_results: &CodegenResults,
tmpdir: &Path,
cnum: CrateNum,
) {
let src = &codegen_results.crate_info.used_crate_source[&cnum];
let cratepath = &src.rlib.as_ref().unwrap().0;

if sess.target.target.options.is_like_osx {
// On Apple platforms, the sanitizer is always built as a dylib, and
// LLVM will link to `@rpath/*.dylib`, so we need to specify an
// rpath to the library as well (the rpath should be absolute, see
// PR #41352 for details).
//
// FIXME: Remove this logic into librustc_*san once Cargo supports it
let rpath = cratepath.parent().unwrap();
let rpath = rpath.to_str().expect("non-utf8 component in path");
cmd.args(&["-Wl,-rpath".into(), "-Xlinker".into(), rpath.into()]);
}

let dst = tmpdir.join(cratepath.file_name().unwrap());
let mut archive = <B as ArchiveBuilder>::new(sess, &dst, Some(cratepath));
archive.update_symbols();

for f in archive.src_files() {
if f.ends_with(RLIB_BYTECODE_EXTENSION) || f == METADATA_FILENAME {
archive.remove_file(&f);
}
}

archive.build();

cmd.link_whole_rlib(&dst);
}

// Adds the static "rlib" versions of all crates to the command line.
// There's a bit of magic which happens here specifically related to LTO and
// dynamic libraries. Specifically:
Expand Down
105 changes: 1 addition & 104 deletions src/librustc_metadata/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::rmeta::{CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob
use rustc::hir::map::Definitions;
use rustc::middle::cstore::DepKind;
use rustc::middle::cstore::{CrateSource, ExternCrate, ExternCrateSource, MetadataLoaderDyn};
use rustc::session::config::{self, Sanitizer};
use rustc::session::config;
use rustc::session::search_paths::PathKind;
use rustc::session::{CrateDisambiguator, Session};
use rustc::ty::TyCtxt;
Expand Down Expand Up @@ -674,108 +674,6 @@ impl<'a> CrateLoader<'a> {
self.inject_dependency_if(cnum, "a panic runtime", &|data| data.needs_panic_runtime());
}

fn inject_sanitizer_runtime(&mut self) {
if let Some(ref sanitizer) = self.sess.opts.debugging_opts.sanitizer {
// Sanitizers can only be used on some tested platforms with
// executables linked to `std`
const ASAN_SUPPORTED_TARGETS: &[&str] =
&["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
const TSAN_SUPPORTED_TARGETS: &[&str] =
&["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
const LSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];
const MSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];

let supported_targets = match *sanitizer {
Sanitizer::Address => ASAN_SUPPORTED_TARGETS,
Sanitizer::Thread => TSAN_SUPPORTED_TARGETS,
Sanitizer::Leak => LSAN_SUPPORTED_TARGETS,
Sanitizer::Memory => MSAN_SUPPORTED_TARGETS,
};
if !supported_targets.contains(&&*self.sess.opts.target_triple.triple()) {
self.sess.err(&format!(
"{:?}Sanitizer only works with the `{}` target",
sanitizer,
supported_targets.join("` or `")
));
return;
}

// firstyear 2017 - during testing I was unable to access an OSX machine
// to make this work on different crate types. As a result, today I have
// only been able to test and support linux as a target.
if self.sess.opts.target_triple.triple() == "x86_64-unknown-linux-gnu" {
if !self.sess.crate_types.borrow().iter().all(|ct| {
match *ct {
// Link the runtime
config::CrateType::Executable => true,
// This crate will be compiled with the required
// instrumentation pass
config::CrateType::Staticlib
| config::CrateType::Rlib
| config::CrateType::Dylib
| config::CrateType::Cdylib => false,
_ => {
self.sess.err(&format!(
"Only executables, staticlibs, \
cdylibs, dylibs and rlibs can be compiled with \
`-Z sanitizer`"
));
false
}
}
}) {
return;
}
} else {
if !self.sess.crate_types.borrow().iter().all(|ct| {
match *ct {
// Link the runtime
config::CrateType::Executable => true,
// This crate will be compiled with the required
// instrumentation pass
config::CrateType::Rlib => false,
_ => {
self.sess.err(&format!(
"Only executables and rlibs can be \
compiled with `-Z sanitizer`"
));
false
}
}
}) {
return;
}
}

let mut uses_std = false;
self.cstore.iter_crate_data(|_, data| {
if data.name() == sym::std {
uses_std = true;
}
});

if uses_std {
let name = Symbol::intern(match sanitizer {
Sanitizer::Address => "rustc_asan",
Sanitizer::Leak => "rustc_lsan",
Sanitizer::Memory => "rustc_msan",
Sanitizer::Thread => "rustc_tsan",
});
info!("loading sanitizer: {}", name);

let cnum = self.resolve_crate(name, DUMMY_SP, DepKind::Explicit, None);
let data = self.cstore.get_crate_data(cnum);

// Sanity check the loaded crate to ensure it is indeed a sanitizer runtime
if !data.is_sanitizer_runtime() {
self.sess.err(&format!("the crate `{}` is not a sanitizer runtime", name));
}
} else {
self.sess.err("Must link std to be compiled with `-Z sanitizer`");
}
}
}

fn inject_profiler_runtime(&mut self) {
if self.sess.opts.debugging_opts.profile || self.sess.opts.cg.profile_generate.enabled() {
info!("loading profiler");
Expand Down Expand Up @@ -927,7 +825,6 @@ impl<'a> CrateLoader<'a> {
}

pub fn postprocess(&mut self, krate: &ast::Crate) {
self.inject_sanitizer_runtime();
self.inject_profiler_runtime();
self.inject_allocator_crate(krate);
self.inject_panic_runtime(krate);
Expand Down
26 changes: 26 additions & 0 deletions src/librustc_session/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,32 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
See https://github.com/rust-lang/rust/issues/61002 for details.",
);
}

// Sanitizers can only be used on some tested platforms.
if let Some(ref sanitizer) = sess.opts.debugging_opts.sanitizer {
const ASAN_SUPPORTED_TARGETS: &[&str] =
&["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
const TSAN_SUPPORTED_TARGETS: &[&str] =
&["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
const LSAN_SUPPORTED_TARGETS: &[&str] =
&["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
const MSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];

let supported_targets = match *sanitizer {
Sanitizer::Address => ASAN_SUPPORTED_TARGETS,
Sanitizer::Thread => TSAN_SUPPORTED_TARGETS,
Sanitizer::Leak => LSAN_SUPPORTED_TARGETS,
Sanitizer::Memory => MSAN_SUPPORTED_TARGETS,
};

if !supported_targets.contains(&&*sess.opts.target_triple.triple()) {
sess.err(&format!(
"{:?}Sanitizer only works with the `{}` target",
sanitizer,
supported_targets.join("` or `")
));
}
}
}

/// Hash value constructed out of all the `-C metadata` arguments passed to the
Expand Down
2 changes: 1 addition & 1 deletion src/test/run-make-fulldeps/sanitizer-address/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ endif
endif

all:
$(RUSTC) -g -Z sanitizer=address -Z print-link-args $(EXTRA_RUSTFLAG) overflow.rs | $(CGREP) librustc_asan
$(RUSTC) -g -Z sanitizer=address -Z print-link-args $(EXTRA_RUSTFLAG) overflow.rs | $(CGREP) rustc_rt.asan
# Verify that stack buffer overflow is detected:
$(TMPDIR)/overflow 2>&1 | $(CGREP) stack-buffer-overflow
# Verify that variable name is included in address sanitizer report:
Expand Down
16 changes: 0 additions & 16 deletions src/test/run-make-fulldeps/sanitizer-invalid-cratetype/Makefile

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

all:
$(RUSTC) -Z sanitizer=leak --target i686-unknown-linux-gnu hello.rs 2>&1 | \
$(CGREP) 'LeakSanitizer only works with the `x86_64-unknown-linux-gnu` target'
$(CGREP) 'LeakSanitizer only works with the `x86_64-unknown-linux-gnu` or `x86_64-apple-darwin` target'
2 changes: 1 addition & 1 deletion src/test/run-make-fulldeps/sanitizer-leak/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
# FIXME(#46126) ThinLTO for libstd broke this test

all:
$(RUSTC) -C opt-level=1 -g -Z sanitizer=leak -Z print-link-args leak.rs | $(CGREP) librustc_lsan
$(RUSTC) -C opt-level=1 -g -Z sanitizer=leak -Z print-link-args leak.rs | $(CGREP) rustc_rt.lsan
$(TMPDIR)/leak 2>&1 | $(CGREP) 'detected memory leaks'
4 changes: 2 additions & 2 deletions src/test/run-make-fulldeps/sanitizer-memory/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# only-x86_64

all:
$(RUSTC) -g -Z sanitizer=memory -Z print-link-args uninit.rs | $(CGREP) librustc_msan
$(RUSTC) -g -Z sanitizer=memory -Z print-link-args uninit.rs | $(CGREP) rustc_rt.msan
$(TMPDIR)/uninit 2>&1 | $(CGREP) use-of-uninitialized-value
$(RUSTC) -g -Z sanitizer=memory -Z print-link-args maybeuninit.rs | $(CGREP) librustc_msan
$(RUSTC) -g -Z sanitizer=memory -Z print-link-args maybeuninit.rs | $(CGREP) rustc_rt.msan
$(TMPDIR)/maybeuninit 2>&1 | $(CGREP) use-of-uninitialized-value

0 comments on commit 0c6b1a7

Please sign in to comment.