Description
There is a very particular set of circumstances that leads to the following error when compiling some C++ files and linking them into a Rust executable targeted at stable-i686-pc-windows-gnu
:
$ cargo run -vv
[...]
= note: C:\...\out\libval.a(val.o): In function `ZN3val9do_assertEv':
C:\....\src\val.h:8: undefined reference to `_imp___wassert'
Summary: I believe the Rust MinGW toolchain needs to be rebuilt or updated to include _imp___wassert
(which I believe is the dynamically linked _wassert
).
I used the repository https://github.com/spl/rust-assert-unicode-test on a Windows VM with MSYS2 and MingW32 installed. Unfortunately, I could not reproduce the issue on Travis-CI or AppVeyor, but it happens reliably on my VM. (I don't have MSVC installed, but I don't know if that's the reason.)
These are the key files:
src/val.h
:
#include <cassert>
class val {
int mv;
public:
val(int v) : mv(v) { }
void do_assert() { assert(mv > 0); }
};
src/val.cpp
:
#include "val.h"
void use_val() {
val v(42);
v.do_assert();
}
build.rs
:
extern crate cc;
fn main() {
println!("cargo:rerun-if-env-changed=UNICODE");
let mut cfg = cc::Build::new();
if std::env::var_os("UNICODE").is_some() {
cfg.define("UNICODE", None);
}
cfg.file("src/val.cpp").cpp(true).compile("libval.a");
}
For context, here is the snippet that defines assert()
in assert.h
from mingw-w64:
#ifdef NDEBUG
#if defined(_UNICODE) || defined(UNICODE)
#define assert(_Expression) \
(void) \
((!!(_Expression)) || \
(_wassert(_CRT_WIDE(#_Expression),_CRT_WIDE(__FILE__),__LINE__),0))
#else /* not unicode */
#define assert(_Expression) \
(void) \
((!!(_Expression)) || \
(_assert(#_Expression,__FILE__,__LINE__),0))
#endif /* _UNICODE||UNICODE */
#endif /* !defined (NDEBUG) */
With nothing in the environment, the build succeeds:
$ cargo build -vv
[...]
[rust-assert-unicode-test 0.1.0] running: "g++.exe" "-O0" "-ffunction-sections" "-fdata-sections" "-g" "-fno-omit-frame-pointer" "-m32" "-Wall" "-Wextra" "-o" "C:\\...\\rust-assert-unicode-test\\target\\debug\\build\\rust-assert-unicode-test-...\\out\\src\\val.o" "-c" "src/val.cpp"
[...]
It's useful to look at the symbols in the library:
$ nm target/debug/build/rust-assert-unicode-test-.../out/libval.a | grep assert
00000000 r .eh_frame$_ZN3val9do_assertEv
00000000 t .text$_ZN3val9do_assertEv
U __imp___assert
00000000 T __ZN3val9do_assertEv
With -DUNICODE
, the build fails:
$ export UNICODE=-DUNICODE
$ cargo build -vv
error: linking with `gcc` failed: exit code: 1
|
= note: "gcc" "-Wl,--enable-long-section-names" "-fno-use-linker-plugin" "-Wl,--nxcompat" "-nostdlib" "-Wl,--large-address-aware" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\crt2.o" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\rsbegin.o" "-L" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps\\rust_assert_unicode_test-....1y0whtr9r0u7l2ur.rcgu.o" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps\\rust_assert_unicode_test-....24fzsbft0yc0w6fe.rcgu.o" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps\\rust_assert_unicode_test-....2b0sh6ymgfums8kn.rcgu.o" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps\\rust_assert_unicode_test-....3vrz8j8szp1mh8k8.rcgu.o" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps\\rust_assert_unicode_test-....4mut1jzgau8wqplt.rcgu.o" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps\\rust_assert_unicode_test-....599jfg9iyzg2frg1.rcgu.o" "-o" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps\\rust_assert_unicode_test-....exe" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps\\rust_assert_unicode_test-....phl7t4fk0vvdm4y.rcgu.o" "-Wl,--gc-sections" "-nodefaultlibs" "-L" "C:\\...\\rust-assert-unicode-test\\target\\debug\\deps" "-L" "C:\\...\\rust-assert-unicode-test\\target\\debug\\build\\rust-assert-unicode-test-...\\out" "-L" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib" "-Wl,-Bstatic" "-Wl,--whole-archive" "-lval" "-Wl,--no-whole-archive" "-Wl,-Bdynamic" "-lstdc++" "-Wl,--start-group" "-Wl,-Bstatic" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\libstd-352224e4f0f52495.rlib" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\libpanic_unwind-1df48bb698babe0f.rlib" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\libbacktrace_sys-e33d2aecefe6e52a.rlib" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\libunwind-6cd6f50b682d440b.rlib" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\librustc_demangle-f70dc1fec692eefd.rlib" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\liblibc-659fb6a5c340607f.rlib" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\liballoc-9d1d98d6712272cf.rlib" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\librustc_std_workspace_core-899502000089d7f2.rlib" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\libcore-dcb03e66209691e3.rlib" "-Wl,--end-group" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\libcompiler_builtins-d3bba173fb7c9a29.rlib" "-Wl,-Bdynamic" "-ladvapi32" "-lws2_32" "-luserenv" "-Wl,-Bstatic" "-lgcc_eh" "-lpthread" "-Wl,-Bdynamic" "-lmingwex" "-lmingw32" "-lgcc" "-lmsvcrt" "-lmsvcrt" "-luser32" "-lkernel32" "C:\\...\\.rustup\\toolchains\\stable-i686-pc-windows-gnu\\lib\\rustlib\\i686-pc-windows-gnu\\lib\\rsend.o"
= note: C:\...\rust-assert-unicode-test\target\debug\build\rust-assert-unicode-test-...\out\libval.a(val.o): In function `ZN3val9do_assertEv':
C:\...\rust-assert-unicode-test/src/val.h:8: undefined reference to `_imp___wassert'
Looking at the symbols in the library confirms that _imp___wassert
is there (and that __imp___assert
is not, as would be expected):
$ nm target/debug/build/rust-assert-unicode-test-.../out/libval.a | grep assert
00000000 r .eh_frame$_ZN3val9do_assertEv
00000000 t .text$_ZN3val9do_assertEv
U __imp___wassert
00000000 T __ZN3val9do_assertEv
One way to work around this issue is to link directly to the MinGW msvcrt
library:
$ RUSTFLAGS="-Clink-arg=C:/msys32/mingw32/i686-w64-mingw32/lib/libmsvcrt.a" cargo build -vv
Looking at what was linked with Process Monitor, I found that the bundled msvcrt
library was being used. And, indeed, that library is missing __imp___wassert
:
$ nm C:/.../.rustup/toolchains/stable-i686-pc-windows-gnu/lib/rustlib/i686-pc-windows-gnu/lib/libmsvcrt.a | grep assert
00000000 I __imp___assert
Looking at the symbols in MinGW's msvcrt
, I can now see why linking to it works:
$ nm C:/msys32/mingw32/i686-w64-mingw32/lib/libmsvcrt.a | grep assert
00000000 T __assert
00000000 I __imp___assert
lib32_libmsvcrt_os_a-wassert.o:
U __assert
00000000 D __imp___wassert
000000d0 t _init_wassert
00000000 t _mingw_wassert
There are no definitions of the __imp___wassert
symbol in C:/.../.rustup/toolchains/stable-i686-pc-windows-gnu/lib/rustlib/i686-pc-windows-gnu/lib/*.a
.