llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.81k stars 11.45k forks source link

[lld] ld.lld: error: undefined symbol: _ZSt21ios_base_library_initv (mingw/i686) #99286

Closed jschueller closed 1 month ago

jschueller commented 1 month ago

compiling this code with gcc into a shared lib for mingw/i686 and linking with lld fails:

#include <fstream>
#include <iostream>
#include <ostream>

int foo() {
  std::ofstream file("/tmp/a.log");
  return 0;
}

$ i686-w64-mingw32-g++ -fuse-ld=lld foo.cxx -shared -o libfoo.dll -Wl,--out-implib,libfoo.dll.a
ld.lld: error: undefined symbol: _ZSt21ios_base_library_initv
>>> referenced by /tmp/ccV85ZXq.o
collect2: error: ld returned 1 exit status

but it goes fine with x86_64-w64-mingw32-g++ also tried to force -lstdc++ versions used are gcc 14.1 / mingw 12.0, lld 18.1 to reproduce on archlinux (or fedora) install mingw-w64-gcc & lld, symlink /usr/i686-w64-mingw32/bin/ld.lld to /usr/bin/lld

/cc @mstorsjo since you seem interested in lld/mingw

llvmbot commented 1 month ago

@llvm/issue-subscribers-lld-elf

Author: Julien Schueller (jschueller)

compiling this code into a shared lib with mingw/i686 and linking with lld fails: ``` #include <fstream> #include <iostream> #include <ostream> int foo() { std::ofstream file("/tmp/a.log"); return 0; } $ i686-w64-mingw32-g++ -fuse-ld=lld foo.cxx -shared -o libfoo.dll -Wl,--out-implib,libfoo.dll.a ld.lld: error: undefined symbol: _ZSt21ios_base_library_initv >>> referenced by /tmp/ccV85ZXq.o collect2: error: ld returned 1 exit status ``` but it goes fine with x86_64-w64-mingw32-g++ also tried to force -lstdc++ versions used are gcc 14.1 / mingw 12.0, lld 18.1 to reproduce on archlinux (or fedora) install mingw-w64-gcc & lld, symlink /usr/i686-w64-mingw32/bin/ld.lld to /usr/bin/lld
mstorsjo commented 1 month ago

This primarily seems to be a libstdc++ issue for this target.

In your case, if you compile the test source to an object file, and inspect it:

$ i686-w64-mingw32-g++ -c foo.cxx
$ i686-w64-mingw32-nm foo.o
00000000 b .bss
00000000 d .data
00000000 r .eh_frame
00000000 r .rdata
00000000 r .rdata$zzz
00000000 t .text
         U _ZSt21ios_base_library_initv
00000000 T __Z3foov
         U __ZNSt14basic_ofstreamIcSt11char_traitsIcEEC1EPKcSt13_Ios_Openmode
         U __ZNSt14basic_ofstreamIcSt11char_traitsIcEED1Ev
0000000b r __ZNSt8__detail30__integer_to_chars_is_unsignedIjEE
0000000c r __ZNSt8__detail30__integer_to_chars_is_unsignedImEE
0000000d r __ZNSt8__detail30__integer_to_chars_is_unsignedIyEE

Note how the reference to _ZSt21ios_base_library_initv only has one leading underscore, while the other ones have two. Normally, for this target, Itanium C++ ABI symbols would have two leading underscores - one as part of the Itanium name mangling itself, and one extra added as part of the i386/windows environment.

If we inspect the libraries that should provide it:

$ i686-w64-mingw32-nm /usr/i686-w64-mingw32/sys-root/mingw/lib/libstdc++.dll.a | grep _ZSt21ios_base_library_initv
00000000 T __ZSt21ios_base_library_initv
00000000 I __imp___ZSt21ios_base_library_initv
$ i686-w64-mingw32-nm /usr/i686-w64-mingw32/sys-root/mingw/lib/libstdc++.a | grep _ZSt21ios_base_library_initv
00000000 T __ZSt21ios_base_library_initv

If we have a look at /usr/i686-w64-mingw32/sys-root/mingw/include/c++/iostream, we find this:

#if !(_GLIBCXX_USE_INIT_PRIORITY_ATTRIBUTE \
      && __has_attribute(__init_priority__))
  static ios_base::Init __ioinit;
#elif defined(_GLIBCXX_SYMVER_GNU)
  __extension__ __asm (".globl _ZSt21ios_base_library_initv");
#endif

(Also found here: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/iostream#L78-L83)

Also looking at /usr/i686-w64-mingw32/sys-root/mingw/include/c++/i686-w64-mingw32/bits/c++config.h, we have this:

#define _GLIBCXX_USE_INIT_PRIORITY_ATTRIBUTE 1
#define _GLIBCXX_SYMVER_GNU 1

So for this particular build configuration, it seems like libstdc++ would need to inject __USER_LABEL_PREFIX__ as a prefix in the __extension__ __asm statement.


With the root issue clarified - GNU ld still manages to link this, while LLD errors out. I guess the deal is that there are no relocations against the symbol, I guess GNU ld feels that it's not an issue for the end result, and only errors out for references against undefined symbols, not undefined symbols in general.

I guess it would be possible to do such a change to LLD, but I'm not sure if that's really something we want to do here. The linker input is quite apparently incorrect...

jschueller commented 1 month ago

thanks for the detailed explanation, indeed I could fix it using __USER_LABEL_PREFIX__:

diff --git a/libstdc++-v3/include/std/iostream b/libstdc++-v3/include/std/iostream
index 0c6a2d8a4b3..6bfb7e25271 100644
--- a/libstdc++-v3/include/std/iostream
+++ b/libstdc++-v3/include/std/iostream
@@ -79,7 +79,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       && __has_attribute(__init_priority__))
   static ios_base::Init __ioinit;
 #elif defined(_GLIBCXX_SYMVER_GNU)
-  __extension__ __asm (".globl _ZSt21ios_base_library_initv");
+  #define XSTRINGIFY(X) STRINGIFY(X)
+  #define STRINGIFY(X) #X
+  __extension__ __asm (".globl " XSTRINGIFY(__USER_LABEL_PREFIX__) "_ZSt21ios_base_library_initv");
 #endif

 _GLIBCXX_END_NAMESPACE_VERSION

with this patch it links properly for both i686 & x86_64

xref https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116159