Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MinGW: undefined reference to _imp___wassert #60912

Closed
spl opened this issue May 17, 2019 · 8 comments · Fixed by #67429
Closed

MinGW: undefined reference to _imp___wassert #60912

spl opened this issue May 17, 2019 · 8 comments · Fixed by #67429
Labels
A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. O-windows-gnu Toolchain: GNU, Operating system: Windows T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@spl
Copy link
Contributor

spl commented May 17, 2019

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.

@jonas-schievink jonas-schievink added A-linkage Area: linking into static, shared libraries and binaries O-windows-gnu Toolchain: GNU, Operating system: Windows C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 17, 2019
@tesuji
Copy link
Contributor

tesuji commented May 17, 2019

What is your gcc and g++ version you used? Both in success and failure cases.

@retep998
Copy link
Member

If you're using an external MinGW toolchain to build C/C++ code you should also be using that MinGW toolchain to link everything, and not the MinGW that is bundled with Rust.

@spl
Copy link
Contributor Author

spl commented May 17, 2019

What is your gcc and g++ version you used? Both in success and failure cases.

@lzutao Both gcc and g++ on my VM are 7.4.0. And rustc is 1.34.1, and cargo is 1.34.0.

If you're using an external MinGW toolchain to build C/C++ code you should also be using that MinGW toolchain to link everything, and not the MinGW that is bundled with Rust.

@retep007 If I understand the situation correctly, there is no internal-to-Rust MinGW toolchain that can build C/C++ code, because the bundled gcc is only for linking, so I should use the system MinGW toolchain for linking with any crate that involves building C/C++.

I've seen this advice mentioned elsewhere (not involving Rust), and it sounds reasonable, but I would appreciate it if you could give me some background reasoning behind it or point me to some sources. Is it because of differences in ABI, symbol names, or something else? Thanks!

That said, I guess you might expect the following to make a difference in this issue:

$ export UNICODE=1
$ CARGO_TARGET_I686_PC_WINDOWS_GNU_LINKER="C:/msys32/mingw32/bin/gcc" cargo build -vv

But it doesn't. The error is almost exactly the same as above. The only difference is note: "gcc" is replaced by note: "C:/msys32/mingw32/bin/gcc".

@retep998
Copy link
Member

Is it because of differences in ABI, symbol names, or something else?

Yes. Things change between MinGW versions and C/C++ code compiled against headers for a specific MinGW version are only guaranteed to link against libraries from that same MinGW version.

Just changing the linker alone isn't enough because rustc still adds the paths to its bundled MinGW libraries in the linker invocation. You'd have to disable the bundled MinGW entirely.

@spl
Copy link
Contributor Author

spl commented May 20, 2019

Just changing the linker alone isn't enough because rustc still adds the paths to its bundled MinGW libraries in the linker invocation.

Right. I forgot to set the $PATH there. (Edit: Actually, I realize this doesn't affect cargo, but I have done this when invoking the underlying gcc command that appears in the error.)

You'd have to disable the bundled MinGW entirely.

How do I do that?

@mati865
Copy link
Contributor

mati865 commented May 20, 2019

I cannot reproduce it with nightly-i686-pc-windows-gnu in up to date MINGW32 shell in MSYS2.

There is similar issue #47048, you can try copying important libraries into rust toolchain directory:

cp /mingw32/i686-w64-mingw32/lib/lib{mingwex,msvcrt}.a /c/Users/$USER/.rustup/toolchains/nightly-i686-pc-windows-gnu/lib/rustlib/i686-pc-windows-gnu/lib

Note: you may need to copy more libraries depending on the errors.

@spl
Copy link
Contributor Author

spl commented May 20, 2019

I cannot reproduce it with nightly-i686-pc-windows-gnu in up to date MINGW32 shell in MSYS2.

@mati865 Does the symbol _imp___wassert appear in your .rustup/toolchains/nightly-i686-pc-windows-gnu/lib/rustlib/i686-pc-windows-gnu/lib/msvcrt.a? If not, can you guess where it might be found?

There is similar issue #47048, you can try copying important libraries into rust toolchain directory:

Indeed! Copying libmingwex.a and libmsvcrt.a from /mingw32/i686-w64-mingw32/lib to $USERPROFILE/.rustup/toolchains/stable-i686-pc-windows-gnu/lib/rustlib/i686-pc-windows-gnu/lib does work.

@mati865
Copy link
Contributor

mati865 commented May 20, 2019

I won't have access to Windows until evening (Europe time) and I have already overridden Rust shipped libs. I haven't made backup because they were broken in other ways.

bors added a commit that referenced this issue Jan 31, 2020
windows-gnu: prefer system crt libraries if they are available

This is my proposal (based on `Amanieu`'s idea) on how to fix #47048 and related issues.

The origin of the issue is the fact Rust ships mingw-w64 libraries but no headers and prefers own libraries over the system ones.
This leads to situation when headers aren't compatible with libraries (mingw-w64 doesn't provide any forward compatibility and AFAIK backwards compatibility is guaranteed only within major release series).

It's easier to understand how this PR works when looking at the linker invocation before and with this PR: https://www.diffchecker.com/GEuYFmzo
It adds system libraries path before Rust libraries so the linker will prefer them.
It has potential issue when system has files with the same names as Rust but that could be avoided by moving Rust shipped mingw-w64 libraries from `lib/rustlib/x86_64-pc-windows-gnu/lib` to say `lib/rustlib/x86_64-pc-windows-gnu/lib/mingw`. Then adding linker paths in this order: Rust libraries, system libraries, Rust shipped mingw-w64 libraries.

I don't know if it's worth to cache system libraries path. You can look for `cache: ` string during build Rust: https://pastebin.com/kGEQZGWP
I think there are enough calls to justify caching.

Fixes #47048
Fixes #49078
Fixes #53454
Fixes #60912
@bors bors closed this as completed in 58b8343 Feb 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. O-windows-gnu Toolchain: GNU, Operating system: Windows T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants