Windows: Does Rust need the x86/x64 C runtime to be initalized?

Currently on Windows, mainCRTStartup is run before the Rust std internal main (which in turn calls the application's main function). This initializes the C/C++ runtime. However, as far as I understand it, Rust handles things like setting up the main thread and SEH exceptions. Rust is also very insistent that there shouldn't be any constructors run before the application's main (aka no life before main) so there's no issue with trying to ensure constructors are initialized before the application's main is called. The Microsoft docs make mention of calling __security_init_cookie but I think Rust has its own stack guard.

So I'm wondering if mainCRTStartup does anything that's useful for Rust? I'm finding it hard to find definitive information on this. My tests so far don't show a problem with replacing the entry point with a stub that simply calls Rust's internal std main directly but I may well be missing something very important.

@retep998?

1 Like

Are TLS and spawning threads allowed before the C runtime is initialized? If not, then Rust needs the C runtime to be initialized.

Long story short, the win32 loader creates the initial thread and runs a function called LdrInitializeThunk before the exe's (or dll's) entry point. This function is part of the OS, not the binary. As far as I'm aware, this function does all the necessary initialization for threads, TLS, etc. So for those keeping score, the functions before main are:

  • LdrInitializeThunk: sets up a win32 process/thread
  • mainCRTStartup: Entry point. Initializes the CRT
  • lang_start: Initializes Rust's std.
  • main: The Rust program's main.

I think it should be possible for Rust to set the entry point to lang_start (which is exported as main), therefore skipping mainCRTStartup. But I'm not 100% certain the C initialization isn't doing anything useful for Rust, hence my question.

1 Like

I think Rust code could be linked with C/C++ static libraries that need the CRT, and also I think it's legitimate to actually have global constructors in Rust code (albeit unsafe and with inline asm or a C/C++ helper), so it seems problematic to unconditionally not initialize the CRT, and thus it probably needs to be an opt-in option and probably still need to have code to call global constructors.

2 Likes

According to http://zetcode.com/gui/winapi/main/ mainCRTStartup initializes file IO support and other things, so it would not be possible to omit it even when only using rust code and no C code.

1 Like

That link confuses me. What memory management and file IO needs to be set up for C/C++ on Windows? Perhaps that's talking about managed code? Also this is more of a nitpick but the Windows C++ main function called by mainCRTStartup has this definition:

int main(int argc, char *argv[], char *envp[]);

Though it is allowed to define it to take no arguments.

The thing is I'm unsure if C itself needs any particular set up on Windows. I know mainCRTStartup sets up the arguments for main but Rust doesn't use them. I'm assuming it also does something to run (or set to run) global static constructors which is where I think there could be a problem but I'm unsure of the implementation.

What I've found so far is the Windows CRT initialization does the following:

The absence of the last two of these would, as bill_myers says, be an issue when static linking C or C++ libraries.

In summary, for a pure Rust exe the CRT initialization can be skipped (I think) even if calling C functions. However when statically linking C/C++ libraries there may be issues if they use globals that require a function call before main.

For what it's worth, the entire source to mainCRTStartup is shipped with MSVC- in VC\Tools\MSVC\14.24.28314\crt\src\vcruntime of the installation. You can also get to it by walking up the call stack from main in the VS debugger.

The thing that concerns me about looking at source code or disassembling/debugging is the license. I don't know if having looked at the code could come back to bite me if I do write some code of my own.

Is there something you're trying to avoid by skipping mainCRTStartup? Surely Windows process launch is expensive enough that the time to run mainCRTStartup is in the noise.

Even if it's safe to skip mainCRTStartup today, I would worry about future changes to the CRT invalidating that reasoning.

The license is not a problem. That source is provided explicitly and only for the purpose of reading and debugging it- it is not used by VS for anything else. (i.e. VS doesn't build it to produce the binary form of the vcruntime, it doesn't depend on it itself, etc.)

You can dig up the VS EULA which covers it and the rest of the toolchain, so it falls under the same rules as the compiler/linker/libraries/debugger, which you're already using.

What I mean is that, sure I'm allowed to look at the code. But what if one day I want to write and distribute my own Windows C runtime initializer, outside of VC++? Could my knowledge of Microsoft's code be used to accuse me of violating their copyright? I'd prefer not have to deal with that.

I'm investigating how Rust compiles/links on Windows and how or if the experience could be improved. As part of that I've looking into, for example, how Rust handles imported dll functions and wondering to what extent Rust depends on the Windows C startup routine.

Sure, the ultimate answers may all end up being "keep doing what Rust currently does" but I'd at least like to understand why instead of simply assuming everything has to be the way it is.

I'm definitely not advocating Rust changes anything at this point.

1 Like

No more than your use of their compiler to build it.

Ok but what if I don't use their compiler or any Visual Studio tools?

That changes nothing. The license is the same- look at "reading the vcruntime source" as an equivalent action to "compiling a program with MSVC."

Avoiding looking at someone else's code can be useful as an affirmative defense if you are accused of copyright infringement. This is a practice known as clean room design. It's probably useful only if you document the entire specification and implementation process sufficiently to prove it in court. If you aren't working with an independent written specification and a knowledgeable legal team, then it's probably not useful to worry about this.

6 Likes

Ah, thanks for the explanation.

If you are using the CRT then you need the CRT entry point. If you statically link to any C/C++ code then you most likely are using the CRT and therefore need the CRT entry point. If you write a pure Rust program where the only C/C++ you touch is in other dlls then you likely don't need the CRT and therefore don't need the CRT entry point. In the future when https://github.com/rust-lang/rust/issues/58713 is finally implemented, Rust will gain a new pure Rust target for windows which won't link to the CRT at all and therefore won't use the CRT entry point.

6 Likes