Cannot compile a simple #[no_std] program

Here is a glance of my main.rs:

#![no_std]
#![no_main]

#[no_mangle]
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
  0
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
  loop {}
}

Also note that I have panic = "abort" set in Cargo.toml.

The error seems to be the linking process. Here is a glance of the compiler output:

error: linking with `link.exe` failed: exit code: 1120
  |
  = note: "[some_path]\\MSVC\\14.24.28314\\bin\\HostX64\\x64\\link.exe" "/NOLOGO" "/NXCOMPAT" "/LIBPATH:[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib" "[some_path]\\target\\debug\\deps\\program.3tqh3p0993sfz3qb.rcgu.o" "/OUT:[some_path]\\target\\debug\\deps\\program.exe" "/OPT:REF,NOICF" "/DEBUG" "/NATVIS:[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\etc\\intrinsic.natvis" "/NATVIS:[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\etc\\liballoc.natvis" "/NATVIS:[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\etc\\libcore.natvis" "/NATVIS:[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\etc\\libstd.natvis" "/LIBPATH:[some_path]\\target\\debug\\deps" "/LIBPATH:[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib" "[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\librustc_std_workspace_core-f256746e283311d5.rlib" "[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libcore-1c74e9c554982673.rlib" "[some_path]\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libcompiler_builtins-c86932987f7f705f.rlib"
  = note: LINK : error LNK2001: unresolved external symbol mainCRTStartup
          [some_path]\target\debug\deps\program.exe : fatal error LNK1120: 1 unresolved externals
          

error: aborting due to previous error

Note that I have the following installed already:

  • MSVC v142 build tools
  • Windows Universal C Runtime
  • Windows 10 SDK (18362)

For a "normal" Rust program compiled with the standard library, the standard library provides a shim normally called _start or mainCRTStartup which does some setup (initialize the heap, stash away environment variables, etc.) before calling into main(). Using the #[no_main] attribute tells the compiler that you'll provide this mainCRTStartup entrypoint function yourself, and we're getting a linker error because it wasn't defined.

When targeting a Windows platform the entrypoint will change depending on whether you target the "windows" subsystem (a GUI) or the "console", meaning you need to declare the appropriate extern "C" function as an entrypoint. I'd recommend looking through the Windows Subsystem RFC for the exact details.

You may also want to look at this thread on writing a #[no_std], #[no_main] win32 program... Which looks to be exactly what you're trying to do.

2 Likes

I'm being slightly pedantic but on Windows it's the C runtime that provides mainCRTStartup, etc (rust just knows how to link it). Rust provides another main function which is called by this function. Rust's internal main function then calls the application's main function. That makes a total of three mains :wink:.

The mainCRTStartup (et al) functions are defined in libcmt so if you link that library your code should work as it is.

That said, I'm not sure if the C runtime does anything particularly useful for Rust (even if you do use std). If you want to skip it then it would be easiest to set the entry point yourself.

src\main.rs:

#![no_std]
#![no_main]

#[no_mangle]
pub extern "C" fn main() -> i32 {
    0
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
  loop {}
}

Note that the main function doesn't take any arguments.

Next make a file .cargo\config. The file is in TOML format even though it has no extension.

[target.'cfg(target_env="msvc")']
rustflags = [
    "-C", "link-arg=/entry:main"
]

As you can probably guess, this tells msvc's linker to set the entry point to main. I'm not sure if there's a better way to do it.