Mingw-packages: lld --allow-multiple-definition

Created on 22 Aug 2020  路  7Comments  路  Source: msys2/MINGW-packages

I'm trying to link a project to lld because it's substantially faster & it works fine with g++, but not clang++
I've attached some source which highlights the issue.

clang++ t.cpp -fuse-ld=lld --static
fails with errors like:
lld-link: error: duplicate symbol: typeinfo name for std::exception

defined at C:\Users....\AppData\Local\Temp\t-c0fb17.o
defined at libstdc++.a(eh_alloc.o)

Normally with clang++ we use --allow-multiple-definition but it isn't supported
clang++ t.cpp -fuse-ld=lld --static -Wl,--allow-multiple-definition
lld: error: unknown argument: --allow-multiple-definition

I've tried using libc++ and while it works for this test
clang++ t.cpp -fuse-ld=lld --static -stdlib=libc++

It's not compatible with the project due to other errors (which I've so far failed to create a test case for)
lld-link: error: undefined symbol: std::basic_streambuf >::imbue(std::locale const&)
lld-link: error: undefined symbol: std::ostream::put(char)

Any help would be appreciated.

include <stdexcept>

int main(int argc, char *argv[])
{
try
{
throw std::runtime_error("error");
}
catch(...)
{
}
}

Most helpful comment

So, I had a closer look at this, and here's a braindump of the issue - I don't have a finished solution yet (but forcing it with that flag, --allow-multiple-definition in current git, or -Xlink=-force:multiple in existing versions, coupled with the usual -Wl,, should work for this issue, but that also masks many other issues). [By the time I had finished typing this, I have a patch at least.]

The issue is that the symbol _ZTSSt9exception gets emitted in multiple object files, both within libstdc++ and the clang-built user code object file. It's emitted as a so-called comdat, that allows multiple objects to provide the same thing, without conflicts (e.g. for things like inline functions, or C++ typeinfo like this is).

For each comdat, there's a selection field that says how conflicts are to be resolved, IMAGE_COMDAT_SELECT_ANY that says just discard all definitions but one, doesn't matter much if they differ, IMAGE_COMDAT_SELECT_EXACT_MATCH that allows multiple definitions provided that they're exact matches of each other, and IMAGE_COMDAT_SELECT_SAME_SIZE that allows multiple definitions, don't look at the contents but require them to be the same size.

In this case, clang emits _ZTSSt9exception as IMAGE_COMDAT_SELECT_ANY, while gcc/binutils emits it as IMAGE_COMDAT_SELECT_SAME_SIZE.

Normally, a linker would flat out reject this situation, if the selection types don't match, then they can't substitute each other. But lld has an exception for mingw interop between clang and gcc; IMAGE_COMDAT_SELECT_SAME_SIZE and IMAGE_COMDAT_SELECT_ANY can interoperate as if both were IMAGE_COMDAT_SELECT_SAME_SIZE, see https://github.com/llvm/llvm-project/commit/9dbc1ab23268abce5db98ad9a1e3aef89c371524. I'm not sure, but it might the case that linking with ld.bfd fails at this issue.

The next issue, when linking with lld, is that binutils seems to pad its section size to a multiple of 4, while llvm doesn't. So for the clang built object file, the comdat section holding _ZTSSt9exception is 13 bytes, while the gcc built one is 16 bytes.

$ obj2yaml clang.o
[...]
  - Name:            '.rdata$_ZTSSt9exception'
    Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_LNK_COMDAT, IMAGE_SCN_MEM_READ ]
    Alignment:       1
    SectionData:     537439657863657074696F6E00
[...]
  - Name:            '.rdata$_ZTSSt9exception'
    Value:           0
    SectionNumber:   7
    SimpleType:      IMAGE_SYM_TYPE_NULL
    ComplexType:     IMAGE_SYM_DTYPE_NULL
    StorageClass:    IMAGE_SYM_CLASS_STATIC
    SectionDefinition:
      Length:          13
      NumberOfRelocations: 0
      NumberOfLinenumbers: 0
      CheckSum:        3269445437
      Number:          7
      Selection:       IMAGE_COMDAT_SELECT_ANY
[...]
$ obj2yaml gcc.o
[...]
  - Name:            '.rdata$_ZTSSt9exception' 
    Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_LNK_COMDAT, IMAGE_SCN_MEM_READ ]
    Alignment:       16
    SectionData:     537439657863657074696F6E00000000
[...]
  - Name:            '.rdata$_ZTSSt9exception'
    Value:           0
    SectionNumber:   10
    SimpleType:      IMAGE_SYM_TYPE_NULL
    ComplexType:     IMAGE_SYM_DTYPE_NULL
    StorageClass:    IMAGE_SYM_CLASS_STATIC
    SectionDefinition:
      Length:          13
      NumberOfRelocations: 0
      NumberOfLinenumbers: 0
      CheckSum:        0
      Number:          0
      Selection:       IMAGE_COMDAT_SELECT_SAME_SIZE

Now the thing is that the binutils provided object files does contain the real, non-padded size in the SectionDefinition (coff_aux_section_definition in lld), but we don't always have this struct readily available when checking for comdat collisions in lld. Or more precisely, when parsing a new object file, we do have the coff_aux_section_definition for that object file, but we don't easily have the coff_aux_section_definition of the previously parsed object file.

So to fix it, at https://github.com/llvm/llvm-project/blob/e524daa7e8719f4b43e6ebcf25fd4e7d74c5d1c4/lld/COFF/InputFiles.cpp#L660 we can easily pass def to handleComdatSelection, but at https://github.com/llvm/llvm-project/blob/e524daa7e8719f4b43e6ebcf25fd4e7d74c5d1c4/lld/COFF/InputFiles.cpp#L543 we'd also need the coff_aux_section_definition associated with leaderChunk, and compare the sizes between those, instead of checking the sizes of the SectionChunks. Or maybe store the size from the original def in a new field in SectionChunk around https://github.com/llvm/llvm-project/blob/e524daa7e8719f4b43e6ebcf25fd4e7d74c5d1c4/lld/COFF/InputFiles.cpp#L273-L275. That does increase the size of SectionChunk (and a nontrivial effort has gone into keeping these objects as small as possible, so new fields shouldn't be added lightly there).

The latter actually wasn't too hard to implement, so with https://martin.st/temp/0001-LLD-COFF-Check-the-aux-section-definition-size-for-I.patch, linking does seem to succeed for me. (The patch is missing a testcase before it can be sent upstream.) [EDIT: Updated the patch later with a testcase.]

All 7 comments

--allow-multiple-definition is something I wanted to use myself recently but haven't got time to investigate yet.

I'm trying to link a project to lld because it's substantially faster & it works fine with g++, but not clang++

It also doesn't work when using Clang+ld.bfd (without --allow-multiple-definition), you should probably report it upstream.

It's not compatible with the project due to other errors (which I've so far failed to create a test case for)

Without small piece of code that triggers that issue that will be hard to solve.

Without small piece of code that triggers that issue that will be hard to solve.

I think the project is fundamentally not compatible with libc++ and I'm not sure how long that will take to figure out, I was mostly hoping that it would sway people away from suggesting it as a solution.

The duplicate symbol itself seems to be an issue with clang++ assuming the wrong lib is being used but I don't know if that is something in the msys64 port.
I don't know who is responsible for ld-link not supporting --allow-multiple-definition (lld itself is documented as supporting it).

@smf-

lld itself is documented as supporting it

ld.lld --help prints ELF backend help by default. To see MinGW backend help use ld.lld -m i386pe --help for 32-bit or ld.lld -m i386pep --help for 64-bit. This should improve somewhere in the future.

I don't know who is responsible

Generally you can report it on LLVM bugzilla and CC mstorsjo for MinGW bugs or at https://github.com/mstorsjo/llvm-mingw.


I hope @mstorsjo doesn't mind if we cc him here.

^ Code from OP works with BFD and LLD when compiled with GCC and linking libstdc++ statically but with our Clang package it errors:


Clang + BFD

$ clang++ --static try_catch.cpp
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(eh_alloc.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(eh_exception.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(eh_personality.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(vterminate.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(stdexcept.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(stdexcept.o): duplicate section `.rdata$_ZTSSt13runtime_error[_ZTSSt13runtime_error]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(stdexcept.o): duplicate section `.rdata$_ZTISt13runtime_error[_ZTISt13runtime_error]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(functexcept.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(functexcept.o): duplicate section `.rdata$_ZTSSt13runtime_error[_ZTSSt13runtime_error]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(functexcept.o): duplicate section `.rdata$_ZTISt13runtime_error[_ZTISt13runtime_error]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(bad_alloc.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(bad_cast.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(bad_typeid.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size
D:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\10.2.0\libstdc++.a(new_op.o): duplicate section `.rdata$_ZTSSt9exception[_ZTSSt9exception]' has different size


Clang + LLD

$ clang++ --static try_catch.cpp -fuse-ld=lld
lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(eh_alloc.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(eh_personality.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(stdexcept.o)

lld-link: error: duplicate symbol: typeinfo name for std::runtime_error
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(stdexcept.o)

lld-link: error: duplicate symbol: typeinfo for std::runtime_error
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(stdexcept.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(eh_exception.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(vterminate.o)

lld-link: error: duplicate symbol: typeinfo for std::runtime_error
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(functexcept.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(functexcept.o)

lld-link: error: duplicate symbol: typeinfo name for std::runtime_error
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(functexcept.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(new_op.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(bad_alloc.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(bad_cast.o)

lld-link: error: duplicate symbol: typeinfo name for std::exception
>>> defined at C:\Users\mateusz\AppData\Local\Temp\try_catch-b86566.o
>>> defined at libstdc++.a(bad_typeid.o)
clang++: error: linker command failed with exit code 1 (use -v to see invocation)

It's unlikely but possible that one of our patches has caused it: https://github.com/msys2/MINGW-packages/tree/master/mingw-w64-clang
GCC package which contains static libstdc++ and headers: https://packages.msys2.org/package/mingw-w64-x86_64-gcc

In general, it should be straightforward to add the --allow-multiple-definition option in the lld mingw frontend. But in most cases, I'd recommend against using it, as one normally really should resolve those issues.

I'll see if I can reproduce the issues myself somewhere, but in the meantime, a general hunch: So this is when linking a gcc-built static libstdc++ with clang-built object files, using either lld or ld.bfd? That's weird. It sounds like a case where clang and gcc do things vaguely differently, and this explodes when linking statically, as there might be functions/data sections, that are included in multiple object files (e.g. like inline functions), but they're expected to be identical in all object files. That's a bit tricky.

I'll see (later) if I can reproduce it and give a more informed comment...

So this is when linking a gcc-built static libstdc++ with clang-built object files, using either lld or ld.bfd?

Exactly.

So, I had a closer look at this, and here's a braindump of the issue - I don't have a finished solution yet (but forcing it with that flag, --allow-multiple-definition in current git, or -Xlink=-force:multiple in existing versions, coupled with the usual -Wl,, should work for this issue, but that also masks many other issues). [By the time I had finished typing this, I have a patch at least.]

The issue is that the symbol _ZTSSt9exception gets emitted in multiple object files, both within libstdc++ and the clang-built user code object file. It's emitted as a so-called comdat, that allows multiple objects to provide the same thing, without conflicts (e.g. for things like inline functions, or C++ typeinfo like this is).

For each comdat, there's a selection field that says how conflicts are to be resolved, IMAGE_COMDAT_SELECT_ANY that says just discard all definitions but one, doesn't matter much if they differ, IMAGE_COMDAT_SELECT_EXACT_MATCH that allows multiple definitions provided that they're exact matches of each other, and IMAGE_COMDAT_SELECT_SAME_SIZE that allows multiple definitions, don't look at the contents but require them to be the same size.

In this case, clang emits _ZTSSt9exception as IMAGE_COMDAT_SELECT_ANY, while gcc/binutils emits it as IMAGE_COMDAT_SELECT_SAME_SIZE.

Normally, a linker would flat out reject this situation, if the selection types don't match, then they can't substitute each other. But lld has an exception for mingw interop between clang and gcc; IMAGE_COMDAT_SELECT_SAME_SIZE and IMAGE_COMDAT_SELECT_ANY can interoperate as if both were IMAGE_COMDAT_SELECT_SAME_SIZE, see https://github.com/llvm/llvm-project/commit/9dbc1ab23268abce5db98ad9a1e3aef89c371524. I'm not sure, but it might the case that linking with ld.bfd fails at this issue.

The next issue, when linking with lld, is that binutils seems to pad its section size to a multiple of 4, while llvm doesn't. So for the clang built object file, the comdat section holding _ZTSSt9exception is 13 bytes, while the gcc built one is 16 bytes.

$ obj2yaml clang.o
[...]
  - Name:            '.rdata$_ZTSSt9exception'
    Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_LNK_COMDAT, IMAGE_SCN_MEM_READ ]
    Alignment:       1
    SectionData:     537439657863657074696F6E00
[...]
  - Name:            '.rdata$_ZTSSt9exception'
    Value:           0
    SectionNumber:   7
    SimpleType:      IMAGE_SYM_TYPE_NULL
    ComplexType:     IMAGE_SYM_DTYPE_NULL
    StorageClass:    IMAGE_SYM_CLASS_STATIC
    SectionDefinition:
      Length:          13
      NumberOfRelocations: 0
      NumberOfLinenumbers: 0
      CheckSum:        3269445437
      Number:          7
      Selection:       IMAGE_COMDAT_SELECT_ANY
[...]
$ obj2yaml gcc.o
[...]
  - Name:            '.rdata$_ZTSSt9exception' 
    Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_LNK_COMDAT, IMAGE_SCN_MEM_READ ]
    Alignment:       16
    SectionData:     537439657863657074696F6E00000000
[...]
  - Name:            '.rdata$_ZTSSt9exception'
    Value:           0
    SectionNumber:   10
    SimpleType:      IMAGE_SYM_TYPE_NULL
    ComplexType:     IMAGE_SYM_DTYPE_NULL
    StorageClass:    IMAGE_SYM_CLASS_STATIC
    SectionDefinition:
      Length:          13
      NumberOfRelocations: 0
      NumberOfLinenumbers: 0
      CheckSum:        0
      Number:          0
      Selection:       IMAGE_COMDAT_SELECT_SAME_SIZE

Now the thing is that the binutils provided object files does contain the real, non-padded size in the SectionDefinition (coff_aux_section_definition in lld), but we don't always have this struct readily available when checking for comdat collisions in lld. Or more precisely, when parsing a new object file, we do have the coff_aux_section_definition for that object file, but we don't easily have the coff_aux_section_definition of the previously parsed object file.

So to fix it, at https://github.com/llvm/llvm-project/blob/e524daa7e8719f4b43e6ebcf25fd4e7d74c5d1c4/lld/COFF/InputFiles.cpp#L660 we can easily pass def to handleComdatSelection, but at https://github.com/llvm/llvm-project/blob/e524daa7e8719f4b43e6ebcf25fd4e7d74c5d1c4/lld/COFF/InputFiles.cpp#L543 we'd also need the coff_aux_section_definition associated with leaderChunk, and compare the sizes between those, instead of checking the sizes of the SectionChunks. Or maybe store the size from the original def in a new field in SectionChunk around https://github.com/llvm/llvm-project/blob/e524daa7e8719f4b43e6ebcf25fd4e7d74c5d1c4/lld/COFF/InputFiles.cpp#L273-L275. That does increase the size of SectionChunk (and a nontrivial effort has gone into keeping these objects as small as possible, so new fields shouldn't be added lightly there).

The latter actually wasn't too hard to implement, so with https://martin.st/temp/0001-LLD-COFF-Check-the-aux-section-definition-size-for-I.patch, linking does seem to succeed for me. (The patch is missing a testcase before it can be sent upstream.) [EDIT: Updated the patch later with a testcase.]

Thanks for this wonderful analysis.

Sounds like this ideally should be fixed in GCC/Binutils by emitting IMAGE_COMDAT_SELECT_ANY.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fspeech picture fspeech  路  6Comments

jagannatharjun picture jagannatharjun  路  3Comments

MenaceInc picture MenaceInc  路  6Comments

Ede123 picture Ede123  路  7Comments

HolyBlackCat picture HolyBlackCat  路  4Comments