Skip to content

Commit 812ba2f

Browse files
committed
[LLD] [COFF] Fix linking import libraries with -wholearchive:
When LLD links against an import library (for the regular, short import libraries), it doesn't actually link in the header/trailer object files at all, but synthesizes new corresponding data structures into the right sections. If the whole of such an import library is forced to be linked, e.g. with the -wholearchive: option, we actually end up linking in those header/trailer objects. The header objects contain a construct which LLD fails to handle; previously we'd error out with the error ".idata$4 should not refer to special section 0". Within the import library header object, in the import directory we have relocations towards the IAT (.idata$4 and .idata$5), but the header object itself doesn't contain any data for those sections. In the case of GNU generated import libraries, the header objects contain zero length sections .idata$4 and .idata$5, with relocations against them. However in the case of LLVM generated import libraries, the sections .idata$4 and .idata$5 are not included in the list of sections. The symbol table does contain section symbols for these sections, but without any actual associated section. This can probably be seen as a declaration of an empty section. If the header/trailer objects of a short import library are linked forcibly and we also reference other functions in the library, we end up with two import directory entries for this DLL, one that gets synthesized by LLD, and one from the actual header object file. This is inelegant, but should be acceptable. While it would seem unusual to link import libraries with the -wholearchive: option, this can happen in certain scenarios. Rust builds libraries that contain relevant import libraries bundled along with compiled Rust code as regular object files, all within one single archive. Such an archive can then end up linked with the -wholarchive: option, if build systems decide to use such an option for including static libraries. This should fix msys2/MINGW-packages#21017. This works for the header/trailer object files in import libraries generated by LLVM; import libraries generated by MSVC are vaguely different. ecb5ea6 did an attempt at fixing the issue for MSVC generated libraries, but it's not entirely correct, and isn't enough for making things work for that case.
1 parent a10ce71 commit 812ba2f

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

lld/COFF/InputFiles.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,26 @@ std::optional<Symbol *> ObjFile::createDefined(
746746
if (sectionNumber == llvm::COFF::IMAGE_SYM_DEBUG)
747747
return nullptr;
748748

749+
if (sym.isEmptySectionDeclaration()) {
750+
// As there is no coff_section in the object file for these, make a
751+
// new virtual one, with everything zeroed out (i.e. an empty section),
752+
// with only the name and characteristics set.
753+
StringRef name = getName();
754+
auto *hdr = make<coff_section>();
755+
memset(hdr, 0, sizeof(*hdr));
756+
strncpy(hdr->Name, name.data(),
757+
std::min(name.size(), (size_t)COFF::NameSize));
758+
// We have no idea what characteristics should be assumed here; pick
759+
// a default. This matches what is used for .idata sections in the regular
760+
// object files in import libraries.
761+
hdr->Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |
762+
IMAGE_SCN_MEM_WRITE | IMAGE_SCN_ALIGN_4BYTES;
763+
auto *sc = make<SectionChunk>(this, hdr);
764+
chunks.push_back(sc);
765+
return make<DefinedRegular>(this, /*name=*/"", /*isCOMDAT=*/false,
766+
/*isExternal=*/false, sym.getGeneric(), sc);
767+
}
768+
749769
if (llvm::COFF::isReservedSectionNumber(sectionNumber))
750770
Fatal(ctx) << toString(this) << ": " << getName()
751771
<< " should not refer to special section "

lld/test/COFF/empty-section-decl.yaml

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# REQUIRES: x86
2+
3+
# RUN: yaml2obj %s -o %t.obj
4+
# RUN: lld-link -dll -out:%t.dll %t.obj -noentry -subsystem:console -lldmap:%t.map
5+
# RUN: llvm-objdump -s %t.dll | FileCheck %s
6+
# RUN: FileCheck %s --check-prefix=MAP < %t.map
7+
8+
# CHECK: Contents of section .itest:
9+
# CHECK-NEXT: 180001000 0c100080 01000000 00000000 01000000
10+
11+
# MAP: 00001000 0000000a 4 {{.*}}:(.itest$2)
12+
# MAP: 00001000 00000000 0 .itest$2
13+
# MAP: 0000100c 00000000 4 {{.*}}:(.itest$4)
14+
# MAP: 0000100c 00000000 0 .itest$4
15+
# MAP: 0000100c 00000004 2 {{.*}}:(.itest$6)
16+
# MAP: 0000100c 00000000 0 .itest$6
17+
18+
--- !COFF
19+
header:
20+
Machine: IMAGE_FILE_MACHINE_AMD64
21+
Characteristics: [ ]
22+
sections:
23+
- Name: '.itest$2'
24+
Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE ]
25+
Alignment: 4
26+
SectionData: '00000000000000000000'
27+
SizeOfRawData: 10
28+
Relocations:
29+
- VirtualAddress: 0
30+
SymbolName: '.itest$4'
31+
Type: IMAGE_REL_AMD64_ADDR64
32+
- Name: '.itest$6'
33+
Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE ]
34+
Alignment: 2
35+
SectionData: 01000000
36+
SizeOfRawData: 4
37+
symbols:
38+
- Name: '.itest$2'
39+
Value: 0
40+
SectionNumber: 1
41+
SimpleType: IMAGE_SYM_TYPE_NULL
42+
ComplexType: IMAGE_SYM_DTYPE_NULL
43+
StorageClass: IMAGE_SYM_CLASS_SECTION
44+
- Name: '.itest$6'
45+
Value: 0
46+
SectionNumber: 2
47+
SimpleType: IMAGE_SYM_TYPE_NULL
48+
ComplexType: IMAGE_SYM_DTYPE_NULL
49+
StorageClass: IMAGE_SYM_CLASS_STATIC
50+
- Name: '.itest$4'
51+
Value: 0
52+
SectionNumber: 0
53+
SimpleType: IMAGE_SYM_TYPE_NULL
54+
ComplexType: IMAGE_SYM_DTYPE_NULL
55+
StorageClass: IMAGE_SYM_CLASS_SECTION
56+
...

lld/test/COFF/wholearchive-implib.s

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// REQUIRES: x86
2+
// RUN: split-file %s %t.dir
3+
// RUN: llvm-lib -machine:amd64 -out:%t.lib -def:%t.dir/lib.def
4+
// RUN: llvm-mc -filetype=obj -triple=x86_64-windows %t.dir/main.s -o %t.main.obj
5+
6+
// RUN: lld-link -out:%t.exe %t.main.obj -wholearchive:%t.lib -entry:entry -subsystem:console
7+
// RUN: llvm-readobj --coff-imports %t.exe | FileCheck %s
8+
9+
// As LLD usually doesn't use the header/trailer object files from import
10+
// libraries, but instead synthesizes those structures, we end up with two
11+
// import directory entries if we force those objects to be included.
12+
13+
// CHECK: Import {
14+
// CHECK-NEXT: Name: lib.dll
15+
// CHECK-NEXT: ImportLookupTableRVA: 0x2050
16+
// CHECK-NEXT: ImportAddressTableRVA: 0x2068
17+
// CHECK-NEXT: }
18+
// CHECK-NEXT: Import {
19+
// CHECK-NEXT: Name: lib.dll
20+
// CHECK-NEXT: ImportLookupTableRVA: 0x2058
21+
// CHECK-NEXT: ImportAddressTableRVA: 0x2070
22+
// CHECK-NEXT: Symbol: func (0)
23+
// CHECK-NEXT: }
24+
25+
26+
#--- main.s
27+
.global entry
28+
entry:
29+
call func
30+
ret
31+
32+
#--- lib.def
33+
LIBRARY lib.dll
34+
EXPORTS
35+
func

llvm/include/llvm/Object/COFF.h

+5
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,11 @@ class COFFSymbolRef {
392392
getValue() == 0;
393393
}
394394

395+
bool isEmptySectionDeclaration() const {
396+
return isSection() && getSectionNumber() == COFF::IMAGE_SYM_UNDEFINED &&
397+
getValue() == 0;
398+
}
399+
395400
bool isWeakExternal() const {
396401
return getStorageClass() == COFF::IMAGE_SYM_CLASS_WEAK_EXTERNAL;
397402
}

0 commit comments

Comments
 (0)