Skip to content

Commit d2279f8

Browse files
committed
Enable address sanitizer for MSVC targets using INFERASANLIBS linker flag
1 parent c9c9f52 commit d2279f8

File tree

14 files changed

+80
-25
lines changed

14 files changed

+80
-25
lines changed

compiler/rustc_codegen_ssa/src/back/link.rs

+30-14
Original file line numberDiff line numberDiff line change
@@ -1186,15 +1186,22 @@ mod win {
11861186
}
11871187
}
11881188

1189-
fn add_sanitizer_libraries(sess: &Session, crate_type: CrateType, linker: &mut dyn Linker) {
1190-
// On macOS the runtimes are distributed as dylibs which should be linked to
1191-
// both executables and dynamic shared objects. Everywhere else the runtimes
1192-
// are currently distributed as static libraries which should be linked to
1193-
// executables only.
1189+
fn add_sanitizer_libraries(
1190+
sess: &Session,
1191+
flavor: LinkerFlavor,
1192+
crate_type: CrateType,
1193+
linker: &mut dyn Linker,
1194+
) {
1195+
// On macOS and Windows using MSVC the runtimes are distributed as dylibs
1196+
// which should be linked to both executables and dynamic libraries.
1197+
// Everywhere else the runtimes are currently distributed as static
1198+
// libraries which should be linked to executables only.
11941199
let needs_runtime = !sess.target.is_like_android
11951200
&& match crate_type {
11961201
CrateType::Executable => true,
1197-
CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro => sess.target.is_like_osx,
1202+
CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro => {
1203+
sess.target.is_like_osx || sess.target.is_like_msvc
1204+
}
11981205
CrateType::Rlib | CrateType::Staticlib => false,
11991206
};
12001207

@@ -1204,26 +1211,31 @@ fn add_sanitizer_libraries(sess: &Session, crate_type: CrateType, linker: &mut d
12041211

12051212
let sanitizer = sess.opts.unstable_opts.sanitizer;
12061213
if sanitizer.contains(SanitizerSet::ADDRESS) {
1207-
link_sanitizer_runtime(sess, linker, "asan");
1214+
link_sanitizer_runtime(sess, flavor, linker, "asan");
12081215
}
12091216
if sanitizer.contains(SanitizerSet::LEAK) {
1210-
link_sanitizer_runtime(sess, linker, "lsan");
1217+
link_sanitizer_runtime(sess, flavor, linker, "lsan");
12111218
}
12121219
if sanitizer.contains(SanitizerSet::MEMORY) {
1213-
link_sanitizer_runtime(sess, linker, "msan");
1220+
link_sanitizer_runtime(sess, flavor, linker, "msan");
12141221
}
12151222
if sanitizer.contains(SanitizerSet::THREAD) {
1216-
link_sanitizer_runtime(sess, linker, "tsan");
1223+
link_sanitizer_runtime(sess, flavor, linker, "tsan");
12171224
}
12181225
if sanitizer.contains(SanitizerSet::HWADDRESS) {
1219-
link_sanitizer_runtime(sess, linker, "hwasan");
1226+
link_sanitizer_runtime(sess, flavor, linker, "hwasan");
12201227
}
12211228
if sanitizer.contains(SanitizerSet::SAFESTACK) {
1222-
link_sanitizer_runtime(sess, linker, "safestack");
1229+
link_sanitizer_runtime(sess, flavor, linker, "safestack");
12231230
}
12241231
}
12251232

1226-
fn link_sanitizer_runtime(sess: &Session, linker: &mut dyn Linker, name: &str) {
1233+
fn link_sanitizer_runtime(
1234+
sess: &Session,
1235+
flavor: LinkerFlavor,
1236+
linker: &mut dyn Linker,
1237+
name: &str,
1238+
) {
12271239
fn find_sanitizer_runtime(sess: &Session, filename: &str) -> PathBuf {
12281240
let session_tlib =
12291241
filesearch::make_target_lib_path(&sess.sysroot, sess.opts.target_triple.triple());
@@ -1254,6 +1266,10 @@ fn link_sanitizer_runtime(sess: &Session, linker: &mut dyn Linker, name: &str) {
12541266
let rpath = path.to_str().expect("non-utf8 component in path");
12551267
linker.args(&["-Wl,-rpath", "-Xlinker", rpath]);
12561268
linker.link_dylib(&filename, false, true);
1269+
} else if sess.target.is_like_msvc && flavor == LinkerFlavor::Msvc(Lld::No) && name == "asan" {
1270+
// MSVC provides the `/INFERASANLIBS` argument to automatically find the
1271+
// compatible ASAN library.
1272+
linker.arg("/INFERASANLIBS");
12571273
} else {
12581274
let filename = format!("librustc{channel}_rt.{name}.a");
12591275
let path = find_sanitizer_runtime(sess, &filename).join(&filename);
@@ -2076,7 +2092,7 @@ fn linker_with_args<'a>(
20762092
);
20772093

20782094
// Sanitizer libraries.
2079-
add_sanitizer_libraries(sess, crate_type, cmd);
2095+
add_sanitizer_libraries(sess, flavor, crate_type, cmd);
20802096

20812097
// Object code from the current crate.
20822098
// Take careful note of the ordering of the arguments we pass to the linker

compiler/rustc_session/src/session.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1582,7 +1582,10 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
15821582
}
15831583

15841584
// Cannot enable crt-static with sanitizers on Linux
1585-
if sess.crt_static(None) && !sess.opts.unstable_opts.sanitizer.is_empty() {
1585+
if sess.crt_static(None)
1586+
&& !sess.opts.unstable_opts.sanitizer.is_empty()
1587+
&& !sess.target.is_like_msvc
1588+
{
15861589
sess.emit_err(errors::CannotEnableCrtStaticLinux);
15871590
}
15881591

compiler/rustc_target/src/spec/targets/i686_pc_windows_msvc.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use crate::spec::{base, LinkerFlavor, Lld, Target};
1+
use crate::spec::{base, LinkerFlavor, Lld, SanitizerSet, Target};
22

33
pub fn target() -> Target {
44
let mut base = base::windows_msvc::opts();
55
base.cpu = "pentium4".into();
66
base.max_atomic_width = Some(64);
7+
base.supported_sanitizers = SanitizerSet::ADDRESS;
78

89
base.add_pre_link_args(
910
LinkerFlavor::Msvc(Lld::No),

compiler/rustc_target/src/spec/targets/x86_64_pc_windows_msvc.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use crate::spec::{base, Target};
1+
use crate::spec::{base, SanitizerSet, Target};
22

33
pub fn target() -> Target {
44
let mut base = base::windows_msvc::opts();
55
base.cpu = "x86-64".into();
66
base.plt_by_default = false;
77
base.max_atomic_width = Some(64);
8+
base.supported_sanitizers = SanitizerSet::ADDRESS;
89

910
Target {
1011
llvm_target: "x86_64-pc-windows-msvc".into(),

src/bootstrap/src/core/build_steps/compile.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ fn copy_third_party_objects(
274274
) -> Vec<(PathBuf, DependencyType)> {
275275
let mut target_deps = vec![];
276276

277-
if builder.config.sanitizers_enabled(target) && compiler.stage != 0 {
277+
if builder.config.needs_sanitizer_runtime_built(target) && compiler.stage != 0 {
278278
// The sanitizers are only copied in stage1 or above,
279279
// to avoid creating dependency on LLVM.
280280
target_deps.extend(

src/bootstrap/src/core/build_steps/test.rs

+23
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,29 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
19401940
}
19411941
}
19421942

1943+
// Special setup to enable running with sanitizers on MSVC.
1944+
if !builder.config.dry_run()
1945+
&& target.contains("msvc")
1946+
&& builder.config.sanitizers_enabled(target)
1947+
{
1948+
// Ignore interception failures: not all dlls in the process will have been built with
1949+
// address sanitizer enabled (e.g., ntdll.dll).
1950+
cmd.env("ASAN_WIN_CONTINUE_ON_INTERCEPTION_FAILURE", "1");
1951+
// Add the address sanitizer runtime to the PATH - it is located next to cl.exe.
1952+
let asan_runtime_path =
1953+
builder.cc.borrow()[&target].path().parent().unwrap().to_path_buf();
1954+
let old_path = cmd
1955+
.get_envs()
1956+
.find_map(|(k, v)| (k == "PATH").then_some(v))
1957+
.flatten()
1958+
.map_or_else(|| env::var_os("PATH").unwrap_or_default(), |v| v.to_owned());
1959+
let new_path = env::join_paths(
1960+
env::split_paths(&old_path).chain(std::iter::once(asan_runtime_path)),
1961+
)
1962+
.expect("Could not add ASAN runtime path to PATH");
1963+
cmd.env("PATH", new_path);
1964+
}
1965+
19431966
// Some UI tests trigger behavior in rustc where it reads $CARGO and changes behavior if it exists.
19441967
// To make the tests work that rely on it not being set, make sure it is not set.
19451968
cmd.env_remove("CARGO");

src/bootstrap/src/core/config/config.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -2180,8 +2180,15 @@ impl Config {
21802180
self.target_config.get(&target).map(|t| t.sanitizers).flatten().unwrap_or(self.sanitizers)
21812181
}
21822182

2183-
pub fn any_sanitizers_enabled(&self) -> bool {
2184-
self.target_config.values().any(|t| t.sanitizers == Some(true)) || self.sanitizers
2183+
pub fn needs_sanitizer_runtime_built(&self, target: TargetSelection) -> bool {
2184+
// MSVC uses the Microsoft-provided sanitizer runtime, but all other runtimes we build.
2185+
!target.is_msvc() && self.sanitizers_enabled(target)
2186+
}
2187+
2188+
pub fn any_sanitizers_to_build(&self) -> bool {
2189+
self.target_config
2190+
.iter()
2191+
.any(|(ts, t)| !ts.is_msvc() && t.sanitizers.unwrap_or(self.sanitizers))
21852192
}
21862193

21872194
pub fn profiler_path(&self, target: TargetSelection) -> Option<&str> {

src/bootstrap/src/core/sanity.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub fn check(build: &mut Build) {
9696
})
9797
.any(|build_llvm_ourselves| build_llvm_ourselves);
9898

99-
let need_cmake = building_llvm || build.config.any_sanitizers_enabled();
99+
let need_cmake = building_llvm || build.config.any_sanitizers_to_build();
100100
if need_cmake && cmd_finder.maybe_have("cmake").is_none() {
101101
eprintln!(
102102
"

tests/run-make/sanitizer-cdylib-link/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ LOG := $(TMPDIR)/log.txt
1212

1313
all:
1414
$(RUSTC) -g -Z sanitizer=address --crate-type cdylib --target $(TARGET) library.rs
15-
$(RUSTC) -g -Z sanitizer=address --crate-type bin --target $(TARGET) -llibrary program.rs
15+
$(RUSTC) -g -Z sanitizer=address --crate-type bin --target $(TARGET) program.rs
1616
LD_LIBRARY_PATH=$(TMPDIR) $(TMPDIR)/program 2>&1 | $(CGREP) stack-buffer-overflow

tests/run-make/sanitizer-cdylib-link/program.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg_attr(windows, link(name = "library.dll.lib", modifiers = "+verbatim"))]
2+
#[cfg_attr(not(windows), link(name = "library"))]
13
extern "C" {
24
fn overflow();
35
}

tests/run-make/sanitizer-dylib-link/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ LOG := $(TMPDIR)/log.txt
1212

1313
all:
1414
$(RUSTC) -g -Z sanitizer=address --crate-type dylib --target $(TARGET) library.rs
15-
$(RUSTC) -g -Z sanitizer=address --crate-type bin --target $(TARGET) -llibrary program.rs
15+
$(RUSTC) -g -Z sanitizer=address --crate-type bin --target $(TARGET) program.rs
1616
LD_LIBRARY_PATH=$(TMPDIR) $(TMPDIR)/program 2>&1 | $(CGREP) stack-buffer-overflow

tests/run-make/sanitizer-dylib-link/program.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg_attr(windows, link(name = "library.dll.lib", modifiers = "+verbatim"))]
2+
#[cfg_attr(not(windows), link(name = "library"))]
13
extern "C" {
24
fn overflow();
35
}

tests/run-make/sanitizer-staticlib-link/program.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#[link(name = "library")]
1+
#[link(name = "library", kind = "static")]
22
extern "C" {
33
fn overflow();
44
}

tests/ui/sanitize/badfree.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// compile-flags: -Z sanitizer=address -O
66
//
77
// run-fail
8-
// error-pattern: AddressSanitizer: SEGV
8+
// regex-error-pattern: AddressSanitizer: (SEGV|attempting free on address which was not malloc)
99

1010
use std::ffi::c_void;
1111

0 commit comments

Comments
 (0)