cc-rs icon indicating copy to clipboard operation
cc-rs copied to clipboard

Tell MSVC to link with debug CRT on debug builds

Open jdpage opened this issue 4 years ago • 4 comments

When doing a debug build, emits /MTd or /MDd instead of /MT or /MD, matching the usual flags for C++ code. When linking statically, link.exe requires all objects to be built with the same /M* flags, so hopefully this makes it easier to consume Rust static libraries while debugging.

I suspect that this might not be the way this change should be handled, and furthermore, it smells a little bit like it might be a breaking change when doing debug builds, but it's a start.

Background: I'm building a Rust staticlib targeting x86_64-pc-windows-msvc, then linking it against some C++ to make a DLL. To do this I'm using the cxx crate, but my understanding is that this problem would be triggered when consuming any crate containing C++ objects as cxx does.

Everything is fine when doing a release build, but on debug builds, my C++ code gets built using the /MDd flag for debugging purposes. However, the cc crate unconditionally builds objects using /MD. As per the Microsoft documentation, this situation is not allowed. Helpfully, the 2019 MSVC toolchain throws up an error, instead of just letting it pass silently, like it used to.

I do have some concerns about my approach here, but I can't really go about addressing them without input from the maintainers:

  1. My change doesn't provide a way to manually select the debug or non-debug versions without changing debug mode for all of the code. This might not be a problem depending on the API philosophy.
  2. Depending on how they've worked around it (e.g. doing their debug builds with /MD instead of /MDd), any existing users with the same use-case as mine might have their debug builds broken by this change.

jdpage avatar May 27 '20 13:05 jdpage

Thanks for this! One thing about this is that I'm not sure how well /MDd interacts with Rust code. I'm not sure if Rust object files are compiled with this similar directive so they can all intermingle? I know that /MT vs /MD works, but I'm not sure if Rust code compiled today can be linked against /MTd. Mind checking that to see if it works?

alexcrichton avatar May 27 '20 14:05 alexcrichton

Okay, wow, this definitely breaks things. I'm a little embarrassed I didn't think to check letting rustc call the linker. Thanks for the quick response.

Building a staticlib with the C++ bits compiled with /MDd, then linking it into a DLL also built with /MDd seems to work. It happily brings up a tokio RPC server and responds to requests, which I'd guess exercises things pretty well.

Building a cdylib with the C++ bits compiled with /MDd fails because rustc wants to link the library with /MD, as you noted in a comment on an old issue I dug up.

Now that I have a better grasp of what's going on, I'm a little surprised that the staticlib part works at all. Do Rust object files care which library they're linked with in the end? Did I just get really lucky and not manage to hit anything important?

jdpage avatar May 27 '20 15:05 jdpage

AFAIK Rust doesn't do anything to indicate /MT or /MD in object files it produces. I think it has everything to do with what libraries are linked in the final command line. I don't know what cl.exe or clang-cl.exe do to have object files indicate what flag they were compiled with.

As for why things sometimes work and sometimes don't, I'm not sure I'm enough of an MSVC expert to know why :(

alexcrichton avatar May 28 '20 15:05 alexcrichton

/MT, /MTd, etc are C/C++ compiler directives (not linker ones) and so, as Alex says, it won't affect compiling Rust code.

Rust itself manually links to the relevant libraries in libc-rs and the way this is done means it's not overrideable outside of the libc crate. So currently any fixes would need to be made there too.

Ideally, instead of hardcoding the libraries on MSVC, Rust would use a linker argument such as:

/DEFAULITLIB:msvcrt

By using the DEFAULITLIB directive, the library can be overridden by other tools (e.g. cc) using NODEFAULTLIB. Or even non-Rust tools. For example, to replace msvcrt with the debug version, you'd be able to do this:

/NODEFAULTLIB:msvcrt /DEFAULTLIB:msvcrtd

However, I don't think using /DEFAULTLIB is currently easy to do within a crate.

Another possibility would be to use the Rust specific -l directive to replace the library:

-l msvcrt:msvcrtd

However, this does not currently work across crates so you can't override anything set by dependencies.

To be honest my hacky solution has been to create an empty msvcrt.lib and add the folder it's in to the library path. This then means I can use other libraries without conflict. Obviously this is very much a hack and I'm not proposing it as any kind of good solution.


Further reading: C runtime (CRT) and C++ Standard Library (STL) .lib files

ChrisDenton avatar Jul 03 '21 12:07 ChrisDenton

Ok, I think this PR as-is should be closed, though I'd be happy to reopen if there's something I'm missing.

I think this needs some more design work (and maybe some changes on the rustc side of things). Mixing CRT libraries is risky so there should be some way to make sure they're all the same for everything that goes into the same linked binary. But figuring out how to do that perhaps needs more than these changes to cc-rs can provide.

ChrisDenton avatar Nov 08 '22 05:11 ChrisDenton