Unresolved external symbol(s) when trying to link a no_std binary to a Windows DLL

I've compiled a DLL that internally statically links the Windows CRT. However, if I now compile a binary that does nothing except link to my DLL, I get a 200KB binary, which includes the entire libstd. I don't want that because it's unnecessary, as I'm exclusively calling functions from the DLL, so I don't need libstd again.

I migrated my crate to use libc_alloc as a memory allocator and made my binary and my DLL bindings no_std compatible:

#![no_std]
#![feature(start, lang_items, rustc_private, libc, default_alloc_error_handler)]

extern crate azul;
extern crate alloc;

use core::panic::PanicInfo;

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

#[lang = "eh_personality"]
extern fn eh_personality() {}

#[global_allocator]
static ALLOC: libc_alloc::LibcAlloc = libc_alloc::LibcAlloc;

#[start]
fn main(_: isize, _: *const *const u8) -> isize {
    return 0;
}

So far, so good. However, if I now compile my binary while linking to the DLL, it compiles fine but I get linker errors:

   Compiling azul v0.1.0 (C:\Users\fschutt\Development\testcargo\azul)
   Compiling libc_alloc v1.0.2
error: linking with `link.exe` failed: exit code: 1120
  |
  = note: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.28.29333\\bin\\HostX64\\x64\\link.exe"
   "/NOLOGO"
   "/NXCOMPAT"
   "/LIBPATH:C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib"
   "C:\\Users\\fschutt\\Development\\testcargo\\target\\release\\examples\\bug.bug.sd2k4tsq-cgu.0.rcgu.o"
   "/OUT:C:\\Users\\fschutt\\Development\\testcargo\\target\\release\\examples\\bug.exe"
   "C:\\Users\\fschutt\\Development\\testcargo\\target\\release\\examples\\bug.3gfezggxlffz7fvh.rcgu.o"
   "/OPT:REF,ICF"
   "/DEBUG"
   "/NATVIS:C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\etc\\intrinsic.natvis"
   "/NATVIS:C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\etc\\liballoc.natvis"
   "/NATVIS:C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\etc\\libcore.natvis"
   "/NATVIS:C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\etc\\libstd.natvis"
   "/LIBPATH:C:\\Users\\fschutt\\Development\\testcargo\\target\\release\\deps"
   "/LIBPATH:dylib=C:\\Users\\fschutt\\Development\\testcargo\\azul/../target/release"
   "/LIBPATH:C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib"
   "C:\\Users\\fschutt\\Development\\testcargo\\target\\release\\deps\\liblibc_alloc-ea93ac27e9eae008.rlib"
   "C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\liballoc-8f7976570d658574.rlib"
   "C:\\Users\\fschutt\\Development\\testcargo\\target\\release\\deps\\libazul-31530d1d2ffa1270.rlib"
   "C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\librustc_std_workspace_core-5b224c8bbe956ad9.rlib"
   "C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libcore-0c481e05c8858bf7.rlib"
   "C:\\Users\\fschutt\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libcompiler_builtins-558737ae27bfd2fc.rlib"
   "azul.lib"
  = note: bug.bug.sd2k4tsq-cgu.0.rcgu.o : error LNK2019: unresolved external symbol "posix_memalign" in functions "__rg_alloc".
          bug.bug.sd2k4tsq-cgu.0.rcgu.o : error LNK2019: unresolved external symbol "free" in functions "__rg_dealloc".
          bug.bug.sd2k4tsq-cgu.0.rcgu.o : error LNK2019: unresolved external symbol "realloc" in functions "__rg_realloc".
          bug.bug.sd2k4tsq-cgu.0.rcgu.o : error LNK2019: unresolved external symbol "memset" in functions "__rg_alloc_zeroed".
          libcore-0c481e05c8858bf7.rlib(core-0c481e05c8858bf7.core.892fumdo-cgu.0.rcgu.o) : error LNK2001: unresolved external symbol "memset".
          LINK : error LNK2001: unresolved external symbol "mainCRTStartup".
          liballoc-8f7976570d658574.rlib(alloc-8f7976570d658574.alloc.5ehvmrsq-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol "memcpy" in functions "_ZN50_$LT$$RF$mut$u20$W$u20$as$u20$core..fmt..Write$GT$9write_str17h8babd47198d05200E".
          libcore-0c481e05c8858bf7.rlib(core-0c481e05c8858bf7.core.892fumdo-cgu.0.rcgu.o) : error LNK2001: unresolved external symbol "memcpy".
          liballoc-8f7976570d658574.rlib(alloc-8f7976570d658574.alloc.5ehvmrsq-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol "rust_oom" in functions "__rg_oom".
          liballoc-8f7976570d658574.rlib(alloc-8f7976570d658574.alloc.5ehvmrsq-cgu.0.rcgu.o) : error LNK2001: unresolved external symbol "__CxxFrameHandler3".
          liballoc-8f7976570d658574.rlib(alloc-8f7976570d658574.alloc.5ehvmrsq-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol "memmove" in functions "_ZN5alloc6string6String12insert_bytes17hb933dfee3425d6daE".
          libcore-0c481e05c8858bf7.rlib(core-0c481e05c8858bf7.core.892fumdo-cgu.0.rcgu.o) : error LNK2019: unresolved external symbol "memcmp" in functions "_ZN4core3str7pattern11StrSearcher3new17h8440bb1fc68f9868E".
          libcore-0c481e05c8858bf7.rlib(core-0c481e05c8858bf7.core.892fumdo-cgu.0.rcgu.o) : error LNK2001: unresolved external symbol "_fltused".
          C:\Users\fschutt\Development\testcargo\target\release\examples\bug.exe : fatal error LNK1120: 11 unresolved external symbols

How do I resolve these external symbols / errors? Maybe this is an issue with the libc_alloc crate, but I haven't found any other no_std-compatible memory allocator (that would just use the system memory allocator)?

Steps

  1. git clone https://github.com/fschutt/cargo-link-dll-nostd
  2. cd cargo-link-dll-nostd && ./bug.bat

Notes

cargo 1.51.0-nightly (a73e5b7d5 2021-01-12)
release: 1.51.0
commit-hash: a73e5b7d567c3036b296fc6b33ed52c5edcd882e
commit-date: 2021-01-12
rustc 1.51.0-nightly (bc39d4d9c 2021-01-15)
binary: rustc
commit-hash: bc39d4d9c514e5fdb40a5782e6ca08924f979c35
commit-date: 2021-01-15
host: x86_64-pc-windows-msvc
release: 1.51.0-nightly
LLVM version: 11.0

Looking at libc_alloc, it seems windows support was recently added, and has not yet made it to crates.io.

Alternatively, you may be able to avoid this in the first place by using link-time optimization ?

In Cargo.toml:

[profile.release]
lto = true

Which should make rustc remove any unused dependencies when building on release, hopefully allowing you to depend on std again.

If this doesn't work, I would advise you to just take the bite and make the 200kB binary. If you're not doing this for fun/learning, it seems like a lot of effort to get a very small optimization :sweat_smile:

Judging from the error it looks like you need to manually specify the native libs you depend on. For the required libraries, see: CRT Library Features | Microsoft Docs

So you'll need kernel32.lib, libvcruntime.lib and either:

  • msvcrt.lib and ucrt.lib or
  • libcmt and libucrt

Though I don't know which library the symbol posix_memalign is in.

I got it down to rust_oom, __CxxFrameHandler3 and _fltused after a while, with something similar to the OP (differing details below for people following along), but the lto = true got me a working 8kB exe that can actually do things! (Print "hello!" in my case). Adding enough fmt to get a sensible panic does bump me up to 20kB :scream:

My differences from OP I can think of:

  • I used the windows Heap* functions to implement #[global_allocator], might have avoided some extra deps?
  • I used #{link_args = "/ENTRY:entry"] and #[no_mangle] fn entry() { } to avoid needing a mainCRTStartup
  • I used panic = "abort" in cargo profiles instead of eh_personality, cause I dunno what that does
  • Still needed mem{cpy,move,set,cmp} symbols, so added the compiler_builtins crate with the mem feature, which is apparently what std pulls in, or something? A bit unclear on this one.

The eh_personality function is what gets called to begin unwinding the stack and calling destructors ("eh" is short for "exception handler").

While true, there's essentially no documentation on how exactly one would do this.