diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 68a4f928464f1..c1b2c8e041d75 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1248,6 +1248,14 @@ pub fn rustc_cargo( // . cargo.rustflag("-Zon-broken-pipe=kill"); + // /Brepro tells the MSVC linker to omit non-deterministic COFF data + // (namely the PE timestamp) from the produced binary. Only applied when + // building rustc itself via bootstrap. See discussion: + // https://github.com/rust-lang/rust/pull/158873 + if target.is_msvc() { + cargo.rustflag("-Clink-arg=/Brepro"); + } + // Building with protected visibility reduces the number of dynamic relocations needed, giving // us a faster startup time. However GNU ld < 2.40 will error if we try to link a shared object // with direct references to protected symbols, so for now we only use protected symbols if diff --git a/tests/run-make/brepro-msvc-determinism/main.rs b/tests/run-make/brepro-msvc-determinism/main.rs new file mode 100644 index 0000000000000..31610b59e57d4 --- /dev/null +++ b/tests/run-make/brepro-msvc-determinism/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("brepro"); +} diff --git a/tests/run-make/brepro-msvc-determinism/rmake.rs b/tests/run-make/brepro-msvc-determinism/rmake.rs new file mode 100644 index 0000000000000..a6562fec541d6 --- /dev/null +++ b/tests/run-make/brepro-msvc-determinism/rmake.rs @@ -0,0 +1,73 @@ +use std::path::PathBuf; + +use object::pod::slice_from_all_bytes; +use object::read::pe::{ImageNtHeaders, PeFile, PeFile32, PeFile64}; +use object::{FileKind, Object, pe}; +use run_make_support::{bin_name, is_windows_msvc, object, rfs, rustc}; + +// Returns the TimeDateStamp values from every IMAGE_DEBUG_DIRECTORY entry +// in a parsed PE image. +// Shared by both PE32 and PE32+ binaries. +fn timestamps(obj: &PeFile<'_, Pe>) -> Vec { + let data_dir = + obj.data_directory(pe::IMAGE_DIRECTORY_ENTRY_DEBUG).expect("no debug directory found"); + + let debug_data = + data_dir.data(obj.data(), &obj.section_table()).expect("failed to read debug directory"); + + let debug_dirs = slice_from_all_bytes::(debug_data) + .expect("invalid IMAGE_DEBUG_DIRECTORY"); + + let mut stamps = Vec::new(); + + // COFF file header TimeDateStamp. + stamps.push(obj.nt_headers().file_header().time_date_stamp.get(object::LittleEndian)); + + // IMAGE_DEBUG_DIRECTORY TimeDateStamp values. + stamps.extend(debug_dirs.iter().map(|d| d.time_date_stamp.get(object::LittleEndian))); + + stamps +} + +fn main() { + if !is_windows_msvc() { + return; + } + + // Compile the test crate and collect the + // IMAGE_DEBUG_DIRECTORY timestamps from the resulting executable. + let build = || -> Vec { + rustc().input("main.rs").arg("-Clink-arg=/Brepro").output(bin_name("brepro-test")).run(); + + let bytes = rfs::read(bin_name("brepro-test")); + + // Parse the generated executable according to its PE format so the + // test works for both 32-bit and 64-bit MSVC targets. + match FileKind::parse(bytes.as_slice()).unwrap() { + FileKind::Pe32 => { + let obj = PeFile32::parse(bytes.as_slice()).unwrap(); + timestamps(&obj) + } + FileKind::Pe64 => { + let obj = PeFile64::parse(bytes.as_slice()).unwrap(); + timestamps(&obj) + } + kind => panic!("unexpected file kind: {kind:?}"), + } + }; + + // First build. + let stamps_a = build(); + + // Remove the generated PDB so the second build starts fresh. + rfs::remove_file(PathBuf::from(bin_name("brepro-test")).with_extension("pdb")); + + // Second build. + let stamps_b = build(); + + assert!(!stamps_a.is_empty(), "no IMAGE_DEBUG_DIRECTORY entries found"); + + // /Brepro should make the linker emit deterministic timestamps across + // identical builds. + assert_eq!(stamps_a, stamps_b, "TimeDateStamp differs between identical builds"); +}