rustc always links against non-debug Windows runtime

200bab2
Opened by Josh Matthews at 2025-01-15 16:27:01

When building rust-mozjs with debug symbols enabled, the underlying C++ library links against the debug Windows runtime (msvcrtd.lib) rather than the regular one (msvcrt.lib). Unfortunately, rustc always links against msvcrt.lib unconditionally: https://github.com/rust-lang/rust/blob/da2ce2276873242a101f205537e7ce297d68f8dd/src/vendor/libc/src/windows.rs#L150 . This makes it impossible to build a debug-enabled version of rust-mozjs on Windows.

  1. Would there be any objection to doing something like:

    #[cfg(all(target_env = "msvc", debug))] // " if " -- appease style checker
    #[link(name = "msvcrtd")]
    extern {}
    
    #[cfg(all(target_env = "msvc", not(debug_assertions)))] // " if " -- appease style checker
    #[link(name = "msvcrt")]
    extern {}
    

    cc @retep998

    Josh Matthews at 2017-01-12 16:04:05

  2. We already have a feature for choosing whether to link the static or dynamic CRT. It would be a fairly trivial matter to add another feature to choose whether to link the debug or release CRT (which I did suggest but nobody seemed to notice). I'd rather not tie this sort of thing to whether optimizations are enabled.

    Peter Atashian at 2017-01-12 16:17:41

  3. @retep998 says that the way to fix this is to find the relevant bits in https://github.com/rust-lang/rust/pull/37545 and duplicate it for crt-debug.

    Josh Matthews at 2017-01-23 19:50:58

  4. #37545 landed, what is needed to make some progress on this?

    Anthony Ramine at 2017-10-07 10:45:40

  5. @nox As I said before, someone has to find the relevant bits in #37545 and duplicate it for crt-debug.

    Peter Atashian at 2017-10-07 11:13:14

  6. This cost me a couple of days debugging why debugmozjs builds of mozjs_sys were failing with link errors under msvc.

    Alan Jeffrey at 2018-05-15 15:15:35

  7. Would love to be able to configure this as well for c++ code.

    Dickson Tan at 2018-09-03 13:02:40

  8. It would make sense to transfer this issue to https://github.com/rust-lang/libc now that the responsible code is in that repository, but I don't have the permissions to do so.

    Josh Matthews at 2019-07-09 15:54:41

  9. cc @alexcrichton @retep998 @newpavlov - please review the libc C PR at: https://github.com/rust-lang/libc/pull/1433 - I'm not sure how this will interact with linking libc both via libstd and crates.io, what happens if the features are different in each, what happens if the binary is #[no_std] and no libstd is linked, etc.

    gnzlbg at 2019-07-09 16:58:46

  10. Solving this issue requires changes both in rust and libc.

    Peter Atashian at 2019-08-28 16:55:18

  11. Since libstd depends on libc, and libc would need to be compiled differently for debug and release on windows, wouldn't we need to ship two libstds one for debug and one for release ?

    gnzlbg at 2019-08-28 18:22:53

  12. Only if there are actual changes to the bindings in libc depending on debug vs release. Simply linking to a different CRT is already handled in std's libc using deferred linking decisions.

    Peter Atashian at 2019-08-28 18:32:12

  13. This looks like a pretty big footgun, and it can cause mysterious runtime error taking people lots of time to figure out. Until this gets fixed, there should probably be some document / compiler message warning about this, so that people know that they need to have their C/C++ program always link against the non-debug crt.

    Xidorn Quan at 2020-01-03 09:27:48

  14. I'm curious how this can be done cleanly, when the base windows target itself adds -lmsvcrt directly into the link args.

    Anthony Ramine at 2020-02-20 10:08:16

  15. @nox That is specifically for the gcc linker flavor, so -gnu targets only, where the debug CRT isn't a thing anyway. For -msvc where this matters we already have dynamic CRT selection through the libc crate. It just has to be expanded to also handle the option of debug vs non-debug CRT and ditto for the CRT target feature in rustc for it.

    Peter Atashian at 2020-02-20 18:28:52

  16. I don't think this needs any support from rustc?

    A new cargo feature needs to be added to libc crate. Then libc crate will link debug crt using #[link(..., cfg(the_debug_crt_feature))] (the link-time configuration in this case will be done during the user build, not when we build std on CI).

    cc crate will probably need a debug_crt method for building C/C++ code with debug crt (similar to already existing static_crt).

    So this issue can be closed in favor of two new issues in the repos mentioned above.

    Vadim Petrochenkov at 2020-07-17 08:04:59

  17. @petrochenkov How is the user supposed to specify cargo features of the libc that std depends on? And why should choosing the debug CRT be different from choosing the static CRT?

    Peter Atashian at 2020-07-17 08:12:08

  18. @retep998

    How is the user supposed to specify cargo features of the libc that std depends on?

    By specifying the feature in Cargo.toml, apparently. The only thing that is necessary is for the the_debug_crt_feature or feature = the_debug_crt_feature predicate to be defined during the user build. Then link-time configuration will work, you don't need to build libc or std with those features enabled. (Someone needs to make a proof of concept to show that it indeed works, though.)

    And why should choosing the debug CRT be different from choosing the static CRT?

    Because +crt-static does many different things on different platforms (cc https://github.com/rust-lang/rust/pull/71586), and the compiler has to be aware of it and change its behavior (e.g. pass different linker options or startup objects). Debug CRT simply changes name of the linked library, libc crate can do that without rustc's involvement.

    The only reason to do this through rustc that I can see is to officially bless the feature name, if libc seems to not be official enough.

    Vadim Petrochenkov at 2020-07-17 08:27:59

  19. For anyone else coming here with MSVCRTD issues, I found that setting CFLAGS and CXXFLAGS in the CMD environment to -MDd before doing cargo build solved our problem, and the modification to libc in the 1433 patch was not necessary.

    Here's the full scenario: We are building a Rust library using cxx crate to aid with interfacing to C++. The Rust code does network I/O with mio crate so is using libc. The resulting code is compiled as a "staticlib" in order to get a LIB to link with the rest of a (much larger) C++ application which we are attempting to contribute to. The C++ team use debug builds for testing on Windows, so we have to fit in with that. Looking at the LIB generated normally by cargo build using dumpbin /all, I see various mentions of MSVCRT. After observing the processes started during the build using Process Monitor, and checking cc crate documentation, I tried setting CFLAGS=-MDd and CXXFLAGS=-MDd. This was observed to insert the -MDd into the cl.exe command-line in a place suitable to override the default of -MD. After a complete rebuild, checking the dumpbin /all output I see only mentions of MSVCRTD. Now when I do a debug (/MDd) build of the C++ code with the generated Rust LIB, it succeeds.

    If this is suitable as a solution for others, it would be good to document it somewhere. I do not fully understand Windows building and linking, so I can't be sure myself.

    Jim Peters at 2021-06-03 15:38:31

  20. Late to this discussion (and still hoping this gets improved) but that's an interesting workaround. The problem is that when you e.g. try to use cmake or whatever you still need the debug vs. release knowledge to propagate downward. I would guess that setting CFLAGS and CXXFLAGS at the top level only works by chance, and not reliably.

    Austin Hicks at 2022-02-12 03:29:30

  21. I really want to find a way forward here as it's a common issue when trying to integrate Rust with existing C/C++ builds.

    I do find it slightly odd that linking the C runtime is the responsibility of the libc crate on Windows. That seems unusual compared to other platforms? But ok, I can live with that.

    @petrochenkov suggested adding a feature in people's Cargo.toml, which seems a bit odd for this? Like I'm not entirely clear how this will work in practice without special Cargo support, otherwise it puts a lot of the effort of supporting this onto crate authors. A global --cfg (i.e. set in rustflags) might work better. It is super important that everything agrees on which CRT to use. Though I'm still not clear on the benefits of --cfg over a compiler codegen option.

    So to sum up I think to do this outside of rustc will require changes to:

    • [ ] libc needs additional #[link(name = "...", cfg(...))] options
    • [ ] cc needs to be able understand these target cfgs
    • [ ] rustc needs to document that the special cfg name(s) are reserved for this use

    Is that right?

    Btw, there should also be an option to not link a CRT. This is the default for no_std on Windows but is not yet supported for std builds. Having this option is important for more exotic CRTs.

    Chris Denton at 2023-03-30 19:30:10

  22. Hitting this issue in Chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=1434719

    Dana Jansens at 2023-04-19 16:51:18

  23. Update on this: You can now use a build script (or other build method) to disable the default CRT for a binary. Then you can add back the one you want. However, you need to use a linker argument:

    For the dynamic CRT use: /nodefaultlib:msvcrt For the static CRT use: /nodefaultlib:libcmt

    This can be done in a build script, for example:

    fn main() {
        // Don't link the default CRT
        println!("cargo::rustc-link-arg-bins=/nodefaultlib:msvcrt")`
        // Link the debug CRT instead
        println!("cargo::rustc-link-arg-bins=/defaultlib:msvcrtd")`
    }
    

    Which isn't as nice as having a proper feature but it's at least serviceable.

    Chris Denton at 2024-10-03 10:44:51

  24. Update on this: You can now use a build script (or other build method) to disable the default CRT for a binary. Then you can add back the one you want. However, you need to use a linker argument:

    For the dynamic CRT use: /nodefaultlib:msvcrt For the static CRT use: /nodefaultlib:libcmt

    This can be done in a build script, for example:

    fn main() { // Don't link the default CRT println!("cargo::rustc-link-arg-bins=/nodefaultlib:msvcrt") // Link the debug CRT instead println!("cargo::rustc-link-arg-bins=/defaultlib:msvcrtd") }

    Which isn't as nice as having a proper feature but it's at least serviceable.

    What is MSRV for this?

    sagudev at 2024-10-03 10:51:27

  25. 1.79

    Chris Denton at 2024-10-03 11:09:19

  26. Also note that if you do that, you need to make sure every C/C++ library you link against was built using the correct version of the CRT as well. If you end up mixing them then you're going to have a bad time with a lot of linker errors.

    Peter Atashian at 2024-10-06 16:34:09

  27. Update on this: You can now use a build script (or other build method) to disable the default CRT for a binary. Then you can add back the one you want. However, you need to use a linker argument:

    For the dynamic CRT use: /nodefaultlib:msvcrt For the static CRT use: /nodefaultlib:libcmt

    This can be done in a build script, for example:

    fn main() { // Don't link the default CRT println!("cargo::rustc-link-arg-bins=/nodefaultlib:msvcrt") // Link the debug CRT instead println!("cargo::rustc-link-arg-bins=/defaultlib:msvcrtd") }

    Which isn't as nice as having a proper feature but it's at least serviceable.

    Thx for this, it helped me a great deal!

    It does seem like it does not work on Rust 1.79, but does work on 1.83.

    I've documented in great detail here: https://github.com/dtolnay/cxx/issues/880#issuecomment-2521375384

    Here's my working build.rs construct.

    if env::var("TARGET").is_ok_and(|s| s.contains("windows-msvc")) {
        // MSVC compiler suite
        if env::var("CFLAGS").is_ok_and(|s| s.contains("/MDd")) {
            // debug runtime flag is set
    
            // Don't link the default CRT
            println!("cargo::rustc-link-arg=/nodefaultlib:msvcrt");
            // Link the debug CRT instead
            println!("cargo::rustc-link-arg=/defaultlib:msvcrtd");
        }
    }
    

    Note that, if you're not using CFLAGS env var for your build, you should use PROFILE for the second if check

    if Ok("debug".to_owned()) == env::var("PROFILE") {
    

    Jean-Luc Deprez at 2024-12-05 20:57:41