diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index 846b4de81426..18b5d4426b1e 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -33,7 +33,7 @@ use crate::utils::exec::command;
 use crate::utils::helpers::{
     exe, get_clang_cl_resource_dir, is_debug_info, is_dylib, symlink_dir, t, up_to_date,
 };
-use crate::{CLang, Compiler, DependencyType, GitRepo, LLVM_TOOLS, Mode, debug, trace};
+use crate::{CLang, Compiler, DependencyType, FileType, GitRepo, LLVM_TOOLS, Mode, debug, trace};
 
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Std {
@@ -321,7 +321,7 @@ fn copy_and_stamp(
     dependency_type: DependencyType,
 ) {
     let target = libdir.join(name);
-    builder.copy_link(&sourcedir.join(name), &target);
+    builder.copy_link(&sourcedir.join(name), &target, FileType::Regular);
 
     target_deps.push((target, dependency_type));
 }
@@ -330,7 +330,7 @@ fn copy_llvm_libunwind(builder: &Builder<'_>, target: TargetSelection, libdir: &
     let libunwind_path = builder.ensure(llvm::Libunwind { target });
     let libunwind_source = libunwind_path.join("libunwind.a");
     let libunwind_target = libdir.join("libunwind.a");
-    builder.copy_link(&libunwind_source, &libunwind_target);
+    builder.copy_link(&libunwind_source, &libunwind_target, FileType::NativeLibrary);
     libunwind_target
 }
 
@@ -401,7 +401,7 @@ fn copy_self_contained_objects(
             for &obj in &["crtbegin.o", "crtbeginS.o", "crtend.o", "crtendS.o"] {
                 let src = crt_path.join(obj);
                 let target = libdir_self_contained.join(obj);
-                builder.copy_link(&src, &target);
+                builder.copy_link(&src, &target, FileType::NativeLibrary);
                 target_deps.push((target, DependencyType::TargetSelfContained));
             }
         } else {
@@ -443,9 +443,9 @@ fn copy_self_contained_objects(
     } else if target.is_windows_gnu() {
         for obj in ["crt2.o", "dllcrt2.o"].iter() {
             let src = compiler_file(builder, &builder.cc(target), target, CLang::C, obj);
-            let target = libdir_self_contained.join(obj);
-            builder.copy_link(&src, &target);
-            target_deps.push((target, DependencyType::TargetSelfContained));
+            let dst = libdir_self_contained.join(obj);
+            builder.copy_link(&src, &dst, FileType::NativeLibrary);
+            target_deps.push((dst, DependencyType::TargetSelfContained));
         }
     }
 
@@ -790,8 +790,11 @@ impl Step for StdLink {
                     let file = t!(file);
                     let path = file.path();
                     if path.is_file() {
-                        builder
-                            .copy_link(&path, &sysroot.join("lib").join(path.file_name().unwrap()));
+                        builder.copy_link(
+                            &path,
+                            &sysroot.join("lib").join(path.file_name().unwrap()),
+                            FileType::Regular,
+                        );
                     }
                 }
             }
@@ -829,7 +832,7 @@ fn copy_sanitizers(
 
     for runtime in &runtimes {
         let dst = libdir.join(&runtime.name);
-        builder.copy_link(&runtime.path, &dst);
+        builder.copy_link(&runtime.path, &dst, FileType::NativeLibrary);
 
         // The `aarch64-apple-ios-macabi` and `x86_64-apple-ios-macabi` are also supported for
         // sanitizers, but they share a sanitizer runtime with `${arch}-apple-darwin`, so we do
@@ -934,9 +937,9 @@ impl Step for StartupObjects {
                     .run(builder);
             }
 
-            let target = sysroot_dir.join((*file).to_string() + ".o");
-            builder.copy_link(dst_file, &target);
-            target_deps.push((target, DependencyType::Target));
+            let obj = sysroot_dir.join((*file).to_string() + ".o");
+            builder.copy_link(dst_file, &obj, FileType::NativeLibrary);
+            target_deps.push((obj, DependencyType::Target));
         }
 
         target_deps
@@ -952,7 +955,7 @@ fn cp_rustc_component_to_ci_sysroot(builder: &Builder<'_>, sysroot: &Path, conte
         if src.is_dir() {
             t!(fs::create_dir_all(dst));
         } else {
-            builder.copy_link(&src, &dst);
+            builder.copy_link(&src, &dst, FileType::Regular);
         }
     }
 }
@@ -1707,7 +1710,7 @@ fn copy_codegen_backends_to_sysroot(
             let dot = filename.find('.').unwrap();
             format!("{}-{}{}", &filename[..dash], builder.rust_release(), &filename[dot..])
         };
-        builder.copy_link(file, &dst.join(target_filename));
+        builder.copy_link(file, &dst.join(target_filename), FileType::NativeLibrary);
     }
 }
 
@@ -2011,7 +2014,11 @@ impl Step for Assemble {
                         extra_features: vec![],
                     });
                 let tool_exe = exe("llvm-bitcode-linker", target_compiler.host);
-                builder.copy_link(&llvm_bitcode_linker.tool_path, &libdir_bin.join(tool_exe));
+                builder.copy_link(
+                    &llvm_bitcode_linker.tool_path,
+                    &libdir_bin.join(tool_exe),
+                    FileType::Executable,
+                );
             }
         };
 
@@ -2072,8 +2079,8 @@ impl Step for Assemble {
                 builder.sysroot_target_libdir(target_compiler, target_compiler.host);
             let dst_lib = libdir.join(&libenzyme).with_extension(lib_ext);
             let target_dst_lib = target_libdir.join(&libenzyme).with_extension(lib_ext);
-            builder.copy_link(&src_lib, &dst_lib);
-            builder.copy_link(&src_lib, &target_dst_lib);
+            builder.copy_link(&src_lib, &dst_lib, FileType::NativeLibrary);
+            builder.copy_link(&src_lib, &target_dst_lib, FileType::NativeLibrary);
         }
 
         // Build the libraries for this compiler to link to (i.e., the libraries
@@ -2168,7 +2175,7 @@ impl Step for Assemble {
             };
 
             if is_dylib_or_debug && can_be_rustc_dynamic_dep && !is_proc_macro {
-                builder.copy_link(&f.path(), &rustc_libdir.join(&filename));
+                builder.copy_link(&f.path(), &rustc_libdir.join(&filename), FileType::Regular);
             }
         }
 
@@ -2196,7 +2203,11 @@ impl Step for Assemble {
             // See <https://github.com/rust-lang/rust/issues/132719>.
             let src_exe = exe("llvm-objcopy", target_compiler.host);
             let dst_exe = exe("rust-objcopy", target_compiler.host);
-            builder.copy_link(&libdir_bin.join(src_exe), &libdir_bin.join(dst_exe));
+            builder.copy_link(
+                &libdir_bin.join(src_exe),
+                &libdir_bin.join(dst_exe),
+                FileType::Executable,
+            );
         }
 
         // In addition to `rust-lld` also install `wasm-component-ld` when
@@ -2212,6 +2223,7 @@ impl Step for Assemble {
             builder.copy_link(
                 &wasm_component.tool_path,
                 &libdir_bin.join(wasm_component.tool_path.file_name().unwrap()),
+                FileType::Executable,
             );
         }
 
@@ -2234,7 +2246,7 @@ impl Step for Assemble {
         t!(fs::create_dir_all(bindir));
         let compiler = builder.rustc(target_compiler);
         debug!(src = ?rustc, dst = ?compiler, "linking compiler binary itself");
-        builder.copy_link(&rustc, &compiler);
+        builder.copy_link(&rustc, &compiler, FileType::Executable);
 
         target_compiler
     }
@@ -2260,7 +2272,7 @@ pub fn add_to_sysroot(
             DependencyType::Target => sysroot_dst,
             DependencyType::TargetSelfContained => self_contained_dst,
         };
-        builder.copy_link(&path, &dst.join(path.file_name().unwrap()));
+        builder.copy_link(&path, &dst.join(path.file_name().unwrap()), FileType::Regular);
     }
 }
 
diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs
index 39f9680cb2f6..3305b7c2d362 100644
--- a/src/bootstrap/src/core/build_steps/dist.rs
+++ b/src/bootstrap/src/core/build_steps/dist.rs
@@ -32,7 +32,7 @@ use crate::utils::helpers::{
     exe, is_dylib, move_file, t, target_supports_cranelift_backend, timeit,
 };
 use crate::utils::tarball::{GeneratedTarball, OverlayKind, Tarball};
-use crate::{Compiler, DependencyType, LLVM_TOOLS, Mode, trace};
+use crate::{Compiler, DependencyType, FileType, LLVM_TOOLS, Mode, trace};
 
 pub fn pkgname(builder: &Builder<'_>, component: &str) -> String {
     format!("{}-{}", component, builder.rust_package_vers())
@@ -81,7 +81,7 @@ impl Step for Docs {
         let mut tarball = Tarball::new(builder, "rust-docs", &host.triple);
         tarball.set_product_name("Rust Documentation");
         tarball.add_bulk_dir(builder.doc_out(host), dest);
-        tarball.add_file(builder.src.join("src/doc/robots.txt"), dest, 0o644);
+        tarball.add_file(builder.src.join("src/doc/robots.txt"), dest, FileType::Regular);
         Some(tarball.generate())
     }
 }
@@ -418,7 +418,7 @@ impl Step for Rustc {
                 .is_none_or(|tools| tools.iter().any(|tool| tool == "rustdoc"))
             {
                 let rustdoc = builder.rustdoc(compiler);
-                builder.install(&rustdoc, &image.join("bin"), 0o755);
+                builder.install(&rustdoc, &image.join("bin"), FileType::Executable);
             }
 
             if let Some(ra_proc_macro_srv) = builder.ensure_if_default(
@@ -432,7 +432,8 @@ impl Step for Rustc {
                 },
                 builder.kind,
             ) {
-                builder.install(&ra_proc_macro_srv.tool_path, &image.join("libexec"), 0o755);
+                let dst = image.join("libexec");
+                builder.install(&ra_proc_macro_srv.tool_path, &dst, FileType::Executable);
             }
 
             let libdir_relative = builder.libdir_relative(compiler);
@@ -444,7 +445,7 @@ impl Step for Rustc {
                     if is_dylib(&entry.path()) {
                         // Don't use custom libdir here because ^lib/ will be resolved again
                         // with installer
-                        builder.install(&entry.path(), &image.join("lib"), 0o644);
+                        builder.install(&entry.path(), &image.join("lib"), FileType::NativeLibrary);
                     }
                 }
             }
@@ -463,7 +464,11 @@ impl Step for Rustc {
             if builder.config.lld_enabled {
                 let src_dir = builder.sysroot_target_bindir(compiler, host);
                 let rust_lld = exe("rust-lld", compiler.host);
-                builder.copy_link(&src_dir.join(&rust_lld), &dst_dir.join(&rust_lld));
+                builder.copy_link(
+                    &src_dir.join(&rust_lld),
+                    &dst_dir.join(&rust_lld),
+                    FileType::Executable,
+                );
                 let self_contained_lld_src_dir = src_dir.join("gcc-ld");
                 let self_contained_lld_dst_dir = dst_dir.join("gcc-ld");
                 t!(fs::create_dir(&self_contained_lld_dst_dir));
@@ -472,6 +477,7 @@ impl Step for Rustc {
                     builder.copy_link(
                         &self_contained_lld_src_dir.join(&exe_name),
                         &self_contained_lld_dst_dir.join(&exe_name),
+                        FileType::Executable,
                     );
                 }
             }
@@ -480,13 +486,17 @@ impl Step for Rustc {
                 let src_dir = builder.sysroot_target_bindir(compiler, host);
                 let llvm_objcopy = exe("llvm-objcopy", compiler.host);
                 let rust_objcopy = exe("rust-objcopy", compiler.host);
-                builder.copy_link(&src_dir.join(&llvm_objcopy), &dst_dir.join(&rust_objcopy));
+                builder.copy_link(
+                    &src_dir.join(&llvm_objcopy),
+                    &dst_dir.join(&rust_objcopy),
+                    FileType::Executable,
+                );
             }
 
             if builder.tool_enabled("wasm-component-ld") {
                 let src_dir = builder.sysroot_target_bindir(compiler, host);
                 let ld = exe("wasm-component-ld", compiler.host);
-                builder.copy_link(&src_dir.join(&ld), &dst_dir.join(&ld));
+                builder.copy_link(&src_dir.join(&ld), &dst_dir.join(&ld), FileType::Executable);
             }
 
             // Man pages
@@ -511,15 +521,19 @@ impl Step for Rustc {
             // HTML copyright files
             let file_list = builder.ensure(super::run::GenerateCopyright);
             for file in file_list {
-                builder.install(&file, &image.join("share/doc/rust"), 0o644);
+                builder.install(&file, &image.join("share/doc/rust"), FileType::Regular);
             }
 
             // README
-            builder.install(&builder.src.join("README.md"), &image.join("share/doc/rust"), 0o644);
+            builder.install(
+                &builder.src.join("README.md"),
+                &image.join("share/doc/rust"),
+                FileType::Regular,
+            );
 
             // The REUSE-managed license files
             let license = |path: &Path| {
-                builder.install(path, &image.join("share/doc/rust/licenses"), 0o644);
+                builder.install(path, &image.join("share/doc/rust/licenses"), FileType::Regular);
             };
             for entry in t!(std::fs::read_dir(builder.src.join("LICENSES"))).flatten() {
                 license(&entry.path());
@@ -548,14 +562,14 @@ impl Step for DebuggerScripts {
         let dst = sysroot.join("lib/rustlib/etc");
         t!(fs::create_dir_all(&dst));
         let cp_debugger_script = |file: &str| {
-            builder.install(&builder.src.join("src/etc/").join(file), &dst, 0o644);
+            builder.install(&builder.src.join("src/etc/").join(file), &dst, FileType::Regular);
         };
         if host.contains("windows-msvc") {
             // windbg debugger scripts
             builder.install(
                 &builder.src.join("src/etc/rust-windbg.cmd"),
                 &sysroot.join("bin"),
-                0o755,
+                FileType::Script,
             );
 
             cp_debugger_script("natvis/intrinsic.natvis");
@@ -567,15 +581,27 @@ impl Step for DebuggerScripts {
         cp_debugger_script("rust_types.py");
 
         // gdb debugger scripts
-        builder.install(&builder.src.join("src/etc/rust-gdb"), &sysroot.join("bin"), 0o755);
-        builder.install(&builder.src.join("src/etc/rust-gdbgui"), &sysroot.join("bin"), 0o755);
+        builder.install(
+            &builder.src.join("src/etc/rust-gdb"),
+            &sysroot.join("bin"),
+            FileType::Script,
+        );
+        builder.install(
+            &builder.src.join("src/etc/rust-gdbgui"),
+            &sysroot.join("bin"),
+            FileType::Script,
+        );
 
         cp_debugger_script("gdb_load_rust_pretty_printers.py");
         cp_debugger_script("gdb_lookup.py");
         cp_debugger_script("gdb_providers.py");
 
         // lldb debugger scripts
-        builder.install(&builder.src.join("src/etc/rust-lldb"), &sysroot.join("bin"), 0o755);
+        builder.install(
+            &builder.src.join("src/etc/rust-lldb"),
+            &sysroot.join("bin"),
+            FileType::Script,
+        );
 
         cp_debugger_script("lldb_lookup.py");
         cp_debugger_script("lldb_providers.py");
@@ -640,9 +666,13 @@ fn copy_target_libs(
     t!(fs::create_dir_all(&self_contained_dst));
     for (path, dependency_type) in builder.read_stamp_file(stamp) {
         if dependency_type == DependencyType::TargetSelfContained {
-            builder.copy_link(&path, &self_contained_dst.join(path.file_name().unwrap()));
+            builder.copy_link(
+                &path,
+                &self_contained_dst.join(path.file_name().unwrap()),
+                FileType::NativeLibrary,
+            );
         } else if dependency_type == DependencyType::Target || builder.is_builder_target(target) {
-            builder.copy_link(&path, &dst.join(path.file_name().unwrap()));
+            builder.copy_link(&path, &dst.join(path.file_name().unwrap()), FileType::NativeLibrary);
         }
     }
 }
@@ -750,7 +780,11 @@ impl Step for RustcDev {
             &tarball.image_dir().join("lib/rustlib/rustc-src/rust"),
         );
         for file in src_files {
-            tarball.add_file(builder.src.join(file), "lib/rustlib/rustc-src/rust", 0o644);
+            tarball.add_file(
+                builder.src.join(file),
+                "lib/rustlib/rustc-src/rust",
+                FileType::Regular,
+            );
         }
 
         Some(tarball.generate())
@@ -1045,7 +1079,11 @@ impl Step for PlainSourceTarball {
 
         // Copy the files normally
         for item in &src_files {
-            builder.copy_link(&builder.src.join(item), &plain_dst_src.join(item));
+            builder.copy_link(
+                &builder.src.join(item),
+                &plain_dst_src.join(item),
+                FileType::Regular,
+            );
         }
 
         // Create the version file
@@ -1147,9 +1185,14 @@ impl Step for Cargo {
         let mut tarball = Tarball::new(builder, "cargo", &target.triple);
         tarball.set_overlay(OverlayKind::Cargo);
 
-        tarball.add_file(cargo.tool_path, "bin", 0o755);
-        tarball.add_file(etc.join("_cargo"), "share/zsh/site-functions", 0o644);
-        tarball.add_renamed_file(etc.join("cargo.bashcomp.sh"), "etc/bash_completion.d", "cargo");
+        tarball.add_file(&cargo.tool_path, "bin", FileType::Executable);
+        tarball.add_file(etc.join("_cargo"), "share/zsh/site-functions", FileType::Regular);
+        tarball.add_renamed_file(
+            etc.join("cargo.bashcomp.sh"),
+            "etc/bash_completion.d",
+            "cargo",
+            FileType::Regular,
+        );
         tarball.add_dir(etc.join("man"), "share/man/man1");
         tarball.add_legal_and_readme_to("share/doc/cargo");
 
@@ -1193,7 +1236,7 @@ impl Step for RustAnalyzer {
         let mut tarball = Tarball::new(builder, "rust-analyzer", &target.triple);
         tarball.set_overlay(OverlayKind::RustAnalyzer);
         tarball.is_preview(true);
-        tarball.add_file(rust_analyzer.tool_path, "bin", 0o755);
+        tarball.add_file(&rust_analyzer.tool_path, "bin", FileType::Executable);
         tarball.add_legal_and_readme_to("share/doc/rust-analyzer");
         Some(tarball.generate())
     }
@@ -1239,8 +1282,8 @@ impl Step for Clippy {
         let mut tarball = Tarball::new(builder, "clippy", &target.triple);
         tarball.set_overlay(OverlayKind::Clippy);
         tarball.is_preview(true);
-        tarball.add_file(clippy.tool_path, "bin", 0o755);
-        tarball.add_file(cargoclippy.tool_path, "bin", 0o755);
+        tarball.add_file(&clippy.tool_path, "bin", FileType::Executable);
+        tarball.add_file(&cargoclippy.tool_path, "bin", FileType::Executable);
         tarball.add_legal_and_readme_to("share/doc/clippy");
         Some(tarball.generate())
     }
@@ -1289,8 +1332,8 @@ impl Step for Miri {
         let mut tarball = Tarball::new(builder, "miri", &target.triple);
         tarball.set_overlay(OverlayKind::Miri);
         tarball.is_preview(true);
-        tarball.add_file(miri.tool_path, "bin", 0o755);
-        tarball.add_file(cargomiri.tool_path, "bin", 0o755);
+        tarball.add_file(&miri.tool_path, "bin", FileType::Executable);
+        tarball.add_file(&cargomiri.tool_path, "bin", FileType::Executable);
         tarball.add_legal_and_readme_to("share/doc/miri");
         Some(tarball.generate())
     }
@@ -1374,7 +1417,11 @@ impl Step for CodegenBackend {
         for backend in fs::read_dir(&backends_src).unwrap() {
             let file_name = backend.unwrap().file_name();
             if file_name.to_str().unwrap().contains(&backend_name) {
-                tarball.add_file(backends_src.join(file_name), &backends_dst, 0o644);
+                tarball.add_file(
+                    backends_src.join(file_name),
+                    &backends_dst,
+                    FileType::NativeLibrary,
+                );
                 found_backend = true;
             }
         }
@@ -1420,8 +1467,8 @@ impl Step for Rustfmt {
         let mut tarball = Tarball::new(builder, "rustfmt", &target.triple);
         tarball.set_overlay(OverlayKind::Rustfmt);
         tarball.is_preview(true);
-        tarball.add_file(rustfmt.tool_path, "bin", 0o755);
-        tarball.add_file(cargofmt.tool_path, "bin", 0o755);
+        tarball.add_file(&rustfmt.tool_path, "bin", FileType::Executable);
+        tarball.add_file(&cargofmt.tool_path, "bin", FileType::Executable);
         tarball.add_legal_and_readme_to("share/doc/rustfmt");
         Some(tarball.generate())
     }
@@ -1578,7 +1625,7 @@ impl Step for Extended {
                     &work.join(format!("{}-{}", pkgname(builder, name), target.triple)),
                     &pkg.join(name),
                 );
-                builder.install(&etc.join("pkg/postinstall"), &pkg.join(name), 0o755);
+                builder.install(&etc.join("pkg/postinstall"), &pkg.join(name), FileType::Script);
                 pkgbuild(name);
             };
             prepare("rustc");
@@ -1593,12 +1640,12 @@ impl Step for Extended {
                 }
             }
             // create an 'uninstall' package
-            builder.install(&etc.join("pkg/postinstall"), &pkg.join("uninstall"), 0o755);
+            builder.install(&etc.join("pkg/postinstall"), &pkg.join("uninstall"), FileType::Script);
             pkgbuild("uninstall");
 
             builder.create_dir(&pkg.join("res"));
             builder.create(&pkg.join("res/LICENSE.txt"), &license);
-            builder.install(&etc.join("gfx/rust-logo.png"), &pkg.join("res"), 0o644);
+            builder.install(&etc.join("gfx/rust-logo.png"), &pkg.join("res"), FileType::Regular);
             let mut cmd = command("productbuild");
             cmd.arg("--distribution")
                 .arg(xform(&etc.join("pkg/Distribution.xml")))
@@ -1655,7 +1702,7 @@ impl Step for Extended {
                 prepare("rust-mingw");
             }
 
-            builder.install(&etc.join("gfx/rust-logo.ico"), &exe, 0o644);
+            builder.install(&etc.join("gfx/rust-logo.ico"), &exe, FileType::Regular);
 
             // Generate msi installer
             let wix_path = env::var_os("WIX")
@@ -1874,8 +1921,8 @@ impl Step for Extended {
             }
 
             builder.create(&exe.join("LICENSE.rtf"), &rtf);
-            builder.install(&etc.join("gfx/banner.bmp"), &exe, 0o644);
-            builder.install(&etc.join("gfx/dialogbg.bmp"), &exe, 0o644);
+            builder.install(&etc.join("gfx/banner.bmp"), &exe, FileType::Regular);
+            builder.install(&etc.join("gfx/dialogbg.bmp"), &exe, FileType::Regular);
 
             builder.info(&format!("building `msi` installer with {light:?}"));
             let filename = format!("{}-{}.msi", pkgname(builder, "rust"), target.triple);
@@ -1961,13 +2008,13 @@ fn install_llvm_file(
     if source.is_symlink() {
         // If we have a symlink like libLLVM-18.so -> libLLVM.so.18.1, install the target of the
         // symlink, which is what will actually get loaded at runtime.
-        builder.install(&t!(fs::canonicalize(source)), destination, 0o644);
+        builder.install(&t!(fs::canonicalize(source)), destination, FileType::NativeLibrary);
 
         let full_dest = destination.join(source.file_name().unwrap());
         if install_symlink {
             // For download-ci-llvm, also install the symlink, to match what LLVM does. Using a
             // symlink is fine here, as this is not a rustup component.
-            builder.copy_link(source, &full_dest);
+            builder.copy_link(source, &full_dest, FileType::NativeLibrary);
         } else {
             // Otherwise, replace the symlink with an equivalent linker script. This is used when
             // projects like miri link against librustc_driver.so. We don't use a symlink, as
@@ -1984,7 +2031,7 @@ fn install_llvm_file(
             }
         }
     } else {
-        builder.install(source, destination, 0o644);
+        builder.install(source, destination, FileType::NativeLibrary);
     }
 }
 
@@ -2036,7 +2083,7 @@ fn maybe_install_llvm(
         let src_libdir = builder.llvm_out(target).join("lib");
         let llvm_dylib_path = src_libdir.join("libLLVM.dylib");
         if llvm_dylib_path.exists() {
-            builder.install(&llvm_dylib_path, dst_libdir, 0o644);
+            builder.install(&llvm_dylib_path, dst_libdir, FileType::NativeLibrary);
         }
         !builder.config.dry_run()
     } else if let llvm::LlvmBuildStatus::AlreadyBuilt(llvm::LlvmResult { llvm_config, .. }) =
@@ -2186,7 +2233,7 @@ impl Step for LlvmTools {
             let dst_bindir = format!("lib/rustlib/{}/bin", target.triple);
             for tool in tools_to_install(&builder.paths) {
                 let exe = src_bindir.join(exe(tool, target));
-                tarball.add_file(&exe, &dst_bindir, 0o755);
+                tarball.add_file(&exe, &dst_bindir, FileType::Executable);
             }
         }
 
@@ -2241,7 +2288,7 @@ impl Step for LlvmBitcodeLinker {
         tarball.set_overlay(OverlayKind::LlvmBitcodeLinker);
         tarball.is_preview(true);
 
-        tarball.add_file(llbc_linker.tool_path, self_contained_bin_dir, 0o755);
+        tarball.add_file(&llbc_linker.tool_path, self_contained_bin_dir, FileType::Executable);
 
         Some(tarball.generate())
     }
@@ -2302,7 +2349,7 @@ impl Step for RustDev {
                 let entry = t!(entry);
                 if entry.file_type().is_file() && !entry.path_is_symlink() {
                     let name = entry.file_name().to_str().unwrap();
-                    tarball.add_file(src_bindir.join(name), "bin", 0o755);
+                    tarball.add_file(src_bindir.join(name), "bin", FileType::Executable);
                 }
             }
         }
@@ -2314,11 +2361,11 @@ impl Step for RustDev {
             // We don't build LLD on some platforms, so only add it if it exists
             let lld_path = lld_out.join("bin").join(exe("lld", target));
             if lld_path.exists() {
-                tarball.add_file(lld_path, "bin", 0o755);
+                tarball.add_file(&lld_path, "bin", FileType::Executable);
             }
         }
 
-        tarball.add_file(builder.llvm_filecheck(target), "bin", 0o755);
+        tarball.add_file(builder.llvm_filecheck(target), "bin", FileType::Executable);
 
         // Copy the include directory as well; needed mostly to build
         // librustc_llvm properly (e.g., llvm-config.h is in here). But also
@@ -2379,7 +2426,11 @@ impl Step for Bootstrap {
 
         let bootstrap_outdir = &builder.bootstrap_out;
         for file in &["bootstrap", "rustc", "rustdoc"] {
-            tarball.add_file(bootstrap_outdir.join(exe(file, target)), "bootstrap/bin", 0o755);
+            tarball.add_file(
+                bootstrap_outdir.join(exe(file, target)),
+                "bootstrap/bin",
+                FileType::Executable,
+            );
         }
 
         Some(tarball.generate())
@@ -2412,7 +2463,7 @@ impl Step for BuildManifest {
         let build_manifest = builder.tool_exe(Tool::BuildManifest);
 
         let tarball = Tarball::new(builder, "build-manifest", &self.target.triple);
-        tarball.add_file(build_manifest, "bin", 0o755);
+        tarball.add_file(&build_manifest, "bin", FileType::Executable);
         tarball.generate()
     }
 }
@@ -2444,15 +2495,15 @@ impl Step for ReproducibleArtifacts {
         let mut added_anything = false;
         let tarball = Tarball::new(builder, "reproducible-artifacts", &self.target.triple);
         if let Some(path) = builder.config.rust_profile_use.as_ref() {
-            tarball.add_file(path, ".", 0o644);
+            tarball.add_file(path, ".", FileType::Regular);
             added_anything = true;
         }
         if let Some(path) = builder.config.llvm_profile_use.as_ref() {
-            tarball.add_file(path, ".", 0o644);
+            tarball.add_file(path, ".", FileType::Regular);
             added_anything = true;
         }
         for profile in &builder.config.reproducible_artifacts {
-            tarball.add_file(profile, ".", 0o644);
+            tarball.add_file(profile, ".", FileType::Regular);
             added_anything = true;
         }
         if added_anything { Some(tarball.generate()) } else { None }
@@ -2481,7 +2532,7 @@ impl Step for Gcc {
     fn run(self, builder: &Builder<'_>) -> Self::Output {
         let tarball = Tarball::new(builder, "gcc", &self.target.triple);
         let output = builder.ensure(super::gcc::Gcc { target: self.target });
-        tarball.add_file(output.libgccjit, "lib", 0o644);
+        tarball.add_file(&output.libgccjit, "lib", FileType::NativeLibrary);
         tarball.generate()
     }
 }
diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs
index a8da41461006..7fccf85a0ea9 100644
--- a/src/bootstrap/src/core/build_steps/doc.rs
+++ b/src/bootstrap/src/core/build_steps/doc.rs
@@ -11,7 +11,6 @@ use std::io::{self, Write};
 use std::path::{Path, PathBuf};
 use std::{env, fs, mem};
 
-use crate::Mode;
 use crate::core::build_steps::compile;
 use crate::core::build_steps::tool::{self, SourceType, Tool, prepare_tool_cargo};
 use crate::core::builder::{
@@ -19,6 +18,7 @@ use crate::core::builder::{
 };
 use crate::core::config::{Config, TargetSelection};
 use crate::helpers::{submodule_path_of, symlink_dir, t, up_to_date};
+use crate::{FileType, Mode};
 
 macro_rules! book {
     ($($name:ident, $path:expr, $book_name:expr, $lang:expr ;)+) => {
@@ -546,6 +546,7 @@ impl Step for SharedAssets {
         builder.copy_link(
             &builder.src.join("src").join("doc").join("rust.css"),
             &out.join("rust.css"),
+            FileType::Regular,
         );
 
         SharedAssetsPaths { version_info }
diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs
index aaf6712102cf..cd57e06ae04a 100644
--- a/src/bootstrap/src/core/build_steps/tool.rs
+++ b/src/bootstrap/src/core/build_steps/tool.rs
@@ -26,7 +26,7 @@ use crate::core::config::{DebuginfoLevel, RustcLto, TargetSelection};
 use crate::utils::channel::GitInfo;
 use crate::utils::exec::{BootstrapCommand, command};
 use crate::utils::helpers::{add_dylib_path, exe, t};
-use crate::{Compiler, Kind, Mode, gha};
+use crate::{Compiler, FileType, Kind, Mode, gha};
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub enum SourceType {
@@ -353,7 +353,7 @@ fn copy_link_tool_bin(
 ) -> PathBuf {
     let cargo_out = builder.cargo_out(compiler, mode, target).join(exe(name, target));
     let bin = builder.tools_dir(compiler).join(exe(name, target));
-    builder.copy_link(&cargo_out, &bin);
+    builder.copy_link(&cargo_out, &bin, FileType::Executable);
     bin
 }
 
@@ -696,7 +696,7 @@ impl Step for Rustdoc {
                     .join(exe("rustdoc", target_compiler.host));
 
                 let bin_rustdoc = bin_rustdoc();
-                builder.copy_link(&precompiled_rustdoc, &bin_rustdoc);
+                builder.copy_link(&precompiled_rustdoc, &bin_rustdoc, FileType::Executable);
 
                 return ToolBuildResult {
                     tool_path: bin_rustdoc,
@@ -743,7 +743,7 @@ impl Step for Rustdoc {
                 compile::strip_debug(builder, target, &tool_path);
             }
             let bin_rustdoc = bin_rustdoc();
-            builder.copy_link(&tool_path, &bin_rustdoc);
+            builder.copy_link(&tool_path, &bin_rustdoc, FileType::Executable);
             ToolBuildResult { tool_path: bin_rustdoc, build_compiler, target_compiler }
         } else {
             ToolBuildResult { tool_path, build_compiler, target_compiler }
@@ -846,13 +846,20 @@ impl Step for LldWrapper {
         let src_exe = exe("lld", target);
         let dst_exe = exe("rust-lld", target);
 
-        builder.copy_link(&lld_install.join("bin").join(src_exe), &libdir_bin.join(dst_exe));
+        builder.copy_link(
+            &lld_install.join("bin").join(src_exe),
+            &libdir_bin.join(dst_exe),
+            FileType::Executable,
+        );
         let self_contained_lld_dir = libdir_bin.join("gcc-ld");
         t!(fs::create_dir_all(&self_contained_lld_dir));
 
         for name in crate::LLD_FILE_NAMES {
-            builder
-                .copy_link(&tool_result.tool_path, &self_contained_lld_dir.join(exe(name, target)));
+            builder.copy_link(
+                &tool_result.tool_path,
+                &self_contained_lld_dir.join(exe(name, target)),
+                FileType::Executable,
+            );
         }
 
         tool_result
@@ -949,8 +956,11 @@ impl Step for RustAnalyzerProcMacroSrv {
         // so that r-a can use it.
         let libexec_path = builder.sysroot(self.compiler).join("libexec");
         t!(fs::create_dir_all(&libexec_path));
-        builder
-            .copy_link(&tool_result.tool_path, &libexec_path.join("rust-analyzer-proc-macro-srv"));
+        builder.copy_link(
+            &tool_result.tool_path,
+            &libexec_path.join("rust-analyzer-proc-macro-srv"),
+            FileType::Executable,
+        );
 
         Some(tool_result)
     }
@@ -1007,7 +1017,7 @@ impl Step for LlvmBitcodeLinker {
             t!(fs::create_dir_all(&bindir_self_contained));
             let bin_destination = bindir_self_contained
                 .join(exe("llvm-bitcode-linker", tool_result.target_compiler.host));
-            builder.copy_link(&tool_result.tool_path, &bin_destination);
+            builder.copy_link(&tool_result.tool_path, &bin_destination, FileType::Executable);
             ToolBuildResult {
                 tool_path: bin_destination,
                 build_compiler: tool_result.build_compiler,
@@ -1189,7 +1199,7 @@ fn run_tool_build_step(
 
         for add_bin in add_bins_to_sysroot {
             let bin_destination = bindir.join(exe(add_bin, target_compiler.host));
-            builder.copy_link(&tool_path, &bin_destination);
+            builder.copy_link(&tool_path, &bin_destination, FileType::Executable);
         }
 
         // Return a path into the bin dir.
diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index 1943d0299b9e..a47b7a27790b 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -36,7 +36,9 @@ use crate::core::builder;
 use crate::core::builder::Kind;
 use crate::core::config::{DryRun, LldMode, LlvmLibunwind, Target, TargetSelection, flags};
 use crate::utils::exec::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode, command};
-use crate::utils::helpers::{self, dir_is_empty, exe, libdir, output, set_file_times, symlink_dir};
+use crate::utils::helpers::{
+    self, dir_is_empty, exe, libdir, output, set_file_times, split_debuginfo, symlink_dir,
+};
 
 mod core;
 mod utils;
@@ -274,6 +276,35 @@ pub enum CLang {
     Cxx,
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FileType {
+    /// An executable binary file (like a `.exe`).
+    Executable,
+    /// A native, binary library file (like a `.so`, `.dll`, `.a`, `.lib` or `.o`).
+    NativeLibrary,
+    /// An executable (non-binary) script file (like a `.py` or `.sh`).
+    Script,
+    /// Any other regular file that is non-executable.
+    Regular,
+}
+
+impl FileType {
+    /// Get Unix permissions appropriate for this file type.
+    pub fn perms(self) -> u32 {
+        match self {
+            FileType::Executable | FileType::Script => 0o755,
+            FileType::Regular | FileType::NativeLibrary => 0o644,
+        }
+    }
+
+    pub fn could_have_split_debuginfo(self) -> bool {
+        match self {
+            FileType::Executable | FileType::NativeLibrary => true,
+            FileType::Script | FileType::Regular => false,
+        }
+    }
+}
+
 macro_rules! forward {
     ( $( $fn:ident( $($param:ident: $ty:ty),* ) $( -> $ret:ty)? ),+ $(,)? ) => {
         impl Build {
@@ -1744,8 +1775,18 @@ Executed at: {executed_at}"#,
     /// Attempts to use hard links if possible, falling back to copying.
     /// You can neither rely on this being a copy nor it being a link,
     /// so do not write to dst.
-    pub fn copy_link(&self, src: &Path, dst: &Path) {
+    pub fn copy_link(&self, src: &Path, dst: &Path, file_type: FileType) {
         self.copy_link_internal(src, dst, false);
+
+        if file_type.could_have_split_debuginfo() {
+            if let Some(dbg_file) = split_debuginfo(src) {
+                self.copy_link_internal(
+                    &dbg_file,
+                    &dst.with_extension(dbg_file.extension().unwrap()),
+                    false,
+                );
+            }
+        }
     }
 
     fn copy_link_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) {
@@ -1808,7 +1849,7 @@ Executed at: {executed_at}"#,
                 t!(fs::create_dir_all(&dst));
                 self.cp_link_r(&path, &dst);
             } else {
-                self.copy_link(&path, &dst);
+                self.copy_link(&path, &dst, FileType::Regular);
             }
         }
     }
@@ -1844,7 +1885,7 @@ Executed at: {executed_at}"#,
                     self.cp_link_filtered_recurse(&path, &dst, &relative, filter);
                 } else {
                     let _ = fs::remove_file(&dst);
-                    self.copy_link(&path, &dst);
+                    self.copy_link(&path, &dst, FileType::Regular);
                 }
             }
         }
@@ -1853,10 +1894,10 @@ Executed at: {executed_at}"#,
     fn copy_link_to_folder(&self, src: &Path, dest_folder: &Path) {
         let file_name = src.file_name().unwrap();
         let dest = dest_folder.join(file_name);
-        self.copy_link(src, &dest);
+        self.copy_link(src, &dest, FileType::Regular);
     }
 
-    fn install(&self, src: &Path, dstdir: &Path, perms: u32) {
+    fn install(&self, src: &Path, dstdir: &Path, file_type: FileType) {
         if self.config.dry_run() {
             return;
         }
@@ -1866,8 +1907,16 @@ Executed at: {executed_at}"#,
         if !src.exists() {
             panic!("ERROR: File \"{}\" not found!", src.display());
         }
+
         self.copy_link_internal(src, &dst, true);
-        chmod(&dst, perms);
+        chmod(&dst, file_type.perms());
+
+        // If this file can have debuginfo, look for split debuginfo and install it too.
+        if file_type.could_have_split_debuginfo() {
+            if let Some(dbg_file) = split_debuginfo(src) {
+                self.install(&dbg_file, dstdir, FileType::Regular);
+            }
+        }
     }
 
     fn read(&self, path: &Path) -> String {
diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs
index 89d93a29acbc..f8e4d4e04717 100644
--- a/src/bootstrap/src/utils/helpers.rs
+++ b/src/bootstrap/src/utils/helpers.rs
@@ -52,6 +52,23 @@ pub fn exe(name: &str, target: TargetSelection) -> String {
     crate::utils::shared_helpers::exe(name, &target.triple)
 }
 
+/// Returns the path to the split debug info for the specified file if it exists.
+pub fn split_debuginfo(name: impl Into<PathBuf>) -> Option<PathBuf> {
+    // FIXME: only msvc is currently supported
+
+    let path = name.into();
+    let pdb = path.with_extension("pdb");
+    if pdb.exists() {
+        return Some(pdb);
+    }
+
+    // pdbs get named with '-' replaced by '_'
+    let file_name = pdb.file_name()?.to_str()?.replace("-", "_");
+
+    let pdb: PathBuf = [path.parent()?, Path::new(&file_name)].into_iter().collect();
+    pdb.exists().then_some(pdb)
+}
+
 /// Returns `true` if the file name given looks like a dynamic library.
 pub fn is_dylib(path: &Path) -> bool {
     path.extension().and_then(|ext| ext.to_str()).is_some_and(|ext| {
diff --git a/src/bootstrap/src/utils/tarball.rs b/src/bootstrap/src/utils/tarball.rs
index f1678bacc976..7b77b2129341 100644
--- a/src/bootstrap/src/utils/tarball.rs
+++ b/src/bootstrap/src/utils/tarball.rs
@@ -7,6 +7,7 @@
 
 use std::path::{Path, PathBuf};
 
+use crate::FileType;
 use crate::core::build_steps::dist::distdir;
 use crate::core::builder::{Builder, Kind};
 use crate::core::config::BUILDER_CONFIG_FILENAME;
@@ -182,7 +183,12 @@ impl<'a> Tarball<'a> {
         &self.image_dir
     }
 
-    pub(crate) fn add_file(&self, src: impl AsRef<Path>, destdir: impl AsRef<Path>, perms: u32) {
+    pub(crate) fn add_file(
+        &self,
+        src: impl AsRef<Path>,
+        destdir: impl AsRef<Path>,
+        file_type: FileType,
+    ) {
         // create_dir_all fails to create `foo/bar/.`, so when the destination is "." this simply
         // uses the base directory as the destination directory.
         let destdir = if destdir.as_ref() == Path::new(".") {
@@ -192,7 +198,7 @@ impl<'a> Tarball<'a> {
         };
 
         t!(std::fs::create_dir_all(&destdir));
-        self.builder.install(src.as_ref(), &destdir, perms);
+        self.builder.install(src.as_ref(), &destdir, file_type);
     }
 
     pub(crate) fn add_renamed_file(
@@ -200,15 +206,16 @@ impl<'a> Tarball<'a> {
         src: impl AsRef<Path>,
         destdir: impl AsRef<Path>,
         new_name: &str,
+        file_type: FileType,
     ) {
         let destdir = self.image_dir.join(destdir.as_ref());
         t!(std::fs::create_dir_all(&destdir));
-        self.builder.copy_link(src.as_ref(), &destdir.join(new_name));
+        self.builder.copy_link(src.as_ref(), &destdir.join(new_name), file_type);
     }
 
     pub(crate) fn add_legal_and_readme_to(&self, destdir: impl AsRef<Path>) {
         for file in self.overlay.legal_and_readme() {
-            self.add_file(self.builder.src.join(file), destdir.as_ref(), 0o644);
+            self.add_file(self.builder.src.join(file), destdir.as_ref(), FileType::Regular);
         }
     }
 
@@ -318,11 +325,20 @@ impl<'a> Tarball<'a> {
 
         // Add config file if present.
         if let Some(config) = &self.builder.config.config {
-            self.add_renamed_file(config, &self.overlay_dir, BUILDER_CONFIG_FILENAME);
+            self.add_renamed_file(
+                config,
+                &self.overlay_dir,
+                BUILDER_CONFIG_FILENAME,
+                FileType::Regular,
+            );
         }
 
         for file in self.overlay.legal_and_readme() {
-            self.builder.install(&self.builder.src.join(file), &self.overlay_dir, 0o644);
+            self.builder.install(
+                &self.builder.src.join(file),
+                &self.overlay_dir,
+                FileType::Regular,
+            );
         }
 
         let mut cmd = self.builder.tool_cmd(crate::core::build_steps::tool::Tool::RustInstaller);