Skip to content

Commit eac1b05

Browse files
committed
[COFF] Support MinGW automatic dllimport of data
Normally, in order to reference exported data symbols from a different DLL, the declarations need to have the dllimport attribute, in order to use the __imp_<var> symbol (which contains an address to the actual variable) instead of the variable itself directly. This isn't an issue in the same way for functions, since any reference to the function without the dllimport attribute will end up as a reference to a thunk which loads the actual target function from the import address table (IAT). GNU ld, in MinGW environments, supports automatically importing data symbols from DLLs, even if the references didn't have the appropriate dllimport attribute. Since the PE/COFF format doesn't support the kind of relocations that this would require, the MinGW's CRT startup code has an custom framework of their own for manually fixing the missing relocations once module is loaded and the target addresses in the IAT are known. For this to work, the linker (originall in GNU ld) creates a list of remaining references needing fixup, which the runtime processes on startup before handing over control to user code. While this feature is rather controversial, it's one of the main features allowing unix style libraries to be used on windows without any extra porting effort. Some sort of automatic fixing of data imports is also necessary for the itanium C++ ABI on windows (as clang implements it right now) for importing vtable pointers in certain cases, see D43184 for some discussion on that. The runtime pseudo relocation handler supports 8/16/32/64 bit addresses, either PC relative references (like IMAGE_REL_*_REL32*) or absolute references (IMAGE_REL_AMD64_ADDR32, IMAGE_REL_AMD64_ADDR32, IMAGE_REL_I386_DIR32). On linking, the relocation is handled as a relocation against the corresponding IAT slot. For the absolute references, a normal base relocation is created, to update the embedded address in case the image is loaded at a different address. The list of runtime pseudo relocations contains the RVA of the imported symbol (the IAT slot), the RVA of the location the relocation should be applied to, and a size of the memory location. When the relocations are fixed at runtime, the difference between the actual IAT slot value and the IAT slot address is added to the reference, doing the right thing for both absolute and relative references. With this patch alone, things work fine for i386 binaries, and mostly for x86_64 binaries, with feature parity with GNU ld. Despite this, there are a few gotchas: - References to data from within code works fine on both x86 architectures, since their relocations consist of plain 32 or 64 bit absolute/relative references. On ARM and AArch64, references to data doesn't consist of a plain 32 or 64 bit embedded address or offset in the code. On ARMNT, it's usually a MOVW+MOVT instruction pair represented by a IMAGE_REL_ARM_MOV32T relocation, each instruction containing 16 bit of the target address), on AArch64, it's usually an ADRP+ADD/LDR/STR instruction pair with an even more complex encoding, storing a PC relative address (with a range of +/- 4 GB). This could theoretically be remedied by extending the runtime pseudo relocation handler with new relocation types, to support these instruction encodings. This isn't an issue for GCC/GNU ld since they don't support windows on ARMNT/AArch64. - For x86_64, if references in code are encoded as 32 bit PC relative offsets, the runtime relocation will fail if the target turns out to be out of range for a 32 bit offset. - Fixing up the relocations at runtime requires making sections writable if necessary, with the VirtualProtect function. In Windows Store/UWP apps, this function is forbidden. These limitations are addressed by a few later patches in lld and llvm. Differential Revision: https://reviews.llvm.org/D50917 llvm-svn: 340726
1 parent 114ebf4 commit eac1b05

14 files changed

+495
-0
lines changed

lld/COFF/Chunks.cpp

+133
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,111 @@ void SectionChunk::getBaserels(std::vector<Baserel> *Res) {
421421
}
422422
}
423423

424+
// MinGW specific.
425+
// Check whether a static relocation of type Type can be deferred and
426+
// handled at runtime as a pseudo relocation (for references to a module
427+
// local variable, which turned out to actually need to be imported from
428+
// another DLL) This returns the size the relocation is supposed to update,
429+
// in bits, or 0 if the relocation cannot be handled as a runtime pseudo
430+
// relocation.
431+
static int getRuntimePseudoRelocSize(uint16_t Type) {
432+
// Relocations that either contain an absolute address, or a plain
433+
// relative offset, since the runtime pseudo reloc implementation
434+
// adds 8/16/32/64 bit values to a memory address.
435+
//
436+
// Given a pseudo relocation entry,
437+
//
438+
// typedef struct {
439+
// DWORD sym;
440+
// DWORD target;
441+
// DWORD flags;
442+
// } runtime_pseudo_reloc_item_v2;
443+
//
444+
// the runtime relocation performs this adjustment:
445+
// *(base + .target) += *(base + .sym) - (base + .sym)
446+
//
447+
// This works for both absolute addresses (IMAGE_REL_*_ADDR32/64,
448+
// IMAGE_REL_I386_DIR32, where the memory location initially contains
449+
// the address of the IAT slot, and for relative addresses (IMAGE_REL*_REL32),
450+
// where the memory location originally contains the relative offset to the
451+
// IAT slot.
452+
//
453+
// This requires the target address to be writable, either directly out of
454+
// the image, or temporarily changed at runtime with VirtualProtect.
455+
// Since this only operates on direct address values, it doesn't work for
456+
// ARM/ARM64 relocations, other than the plain ADDR32/ADDR64 relocations.
457+
switch (Config->Machine) {
458+
case AMD64:
459+
switch (Type) {
460+
case IMAGE_REL_AMD64_ADDR64:
461+
return 64;
462+
case IMAGE_REL_AMD64_ADDR32:
463+
case IMAGE_REL_AMD64_REL32:
464+
case IMAGE_REL_AMD64_REL32_1:
465+
case IMAGE_REL_AMD64_REL32_2:
466+
case IMAGE_REL_AMD64_REL32_3:
467+
case IMAGE_REL_AMD64_REL32_4:
468+
case IMAGE_REL_AMD64_REL32_5:
469+
return 32;
470+
default:
471+
return 0;
472+
}
473+
case I386:
474+
switch (Type) {
475+
case IMAGE_REL_I386_DIR32:
476+
case IMAGE_REL_I386_REL32:
477+
return 32;
478+
default:
479+
return 0;
480+
}
481+
case ARMNT:
482+
switch (Type) {
483+
case IMAGE_REL_ARM_ADDR32:
484+
return 32;
485+
default:
486+
return 0;
487+
}
488+
case ARM64:
489+
switch (Type) {
490+
case IMAGE_REL_ARM64_ADDR64:
491+
return 64;
492+
case IMAGE_REL_ARM64_ADDR32:
493+
return 32;
494+
default:
495+
return 0;
496+
}
497+
default:
498+
llvm_unreachable("unknown machine type");
499+
}
500+
}
501+
502+
// MinGW specific.
503+
// Append information to the provided vector about all relocations that
504+
// need to be handled at runtime as runtime pseudo relocations (references
505+
// to a module local variable, which turned out to actually need to be
506+
// imported from another DLL).
507+
void SectionChunk::getRuntimePseudoRelocs(
508+
std::vector<RuntimePseudoReloc> &Res) {
509+
for (const coff_relocation &Rel : Relocs) {
510+
auto *Target = dyn_cast_or_null<DefinedImportData>(
511+
File->getSymbol(Rel.SymbolTableIndex));
512+
if (!Target || !Target->IsRuntimePseudoReloc)
513+
continue;
514+
int SizeInBits = getRuntimePseudoRelocSize(Rel.Type);
515+
if (SizeInBits == 0) {
516+
error("unable to automatically import from " + Target->getName() +
517+
" with relocation type " +
518+
File->getCOFFObj()->getRelocationTypeName(Rel.Type) + " in " +
519+
toString(File));
520+
continue;
521+
}
522+
// SizeInBits is used to initialize the Flags field; currently no
523+
// other flags are defined.
524+
Res.emplace_back(
525+
RuntimePseudoReloc(Target, this, Rel.VirtualAddress, SizeInBits));
526+
}
527+
}
528+
424529
bool SectionChunk::hasData() const {
425530
return !(Header->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA);
426531
}
@@ -539,6 +644,34 @@ void RVATableChunk::writeTo(uint8_t *Buf) const {
539644
"RVA tables should be de-duplicated");
540645
}
541646

647+
// MinGW specific, for the "automatic import of variables from DLLs" feature.
648+
size_t PseudoRelocTableChunk::getSize() const {
649+
if (Relocs.empty())
650+
return 0;
651+
return 12 + 12 * Relocs.size();
652+
}
653+
654+
// MinGW specific.
655+
void PseudoRelocTableChunk::writeTo(uint8_t *Buf) const {
656+
if (Relocs.empty())
657+
return;
658+
659+
ulittle32_t *Table = reinterpret_cast<ulittle32_t *>(Buf + OutputSectionOff);
660+
// This is the list header, to signal the runtime pseudo relocation v2
661+
// format.
662+
Table[0] = 0;
663+
Table[1] = 0;
664+
Table[2] = 1;
665+
666+
size_t Idx = 3;
667+
for (const RuntimePseudoReloc &RPR : Relocs) {
668+
Table[Idx + 0] = RPR.Sym->getRVA();
669+
Table[Idx + 1] = RPR.Target->getRVA() + RPR.TargetOffset;
670+
Table[Idx + 2] = RPR.Flags;
671+
Idx += 3;
672+
}
673+
}
674+
542675
// Windows-specific. This class represents a block in .reloc section.
543676
// The format is described here.
544677
//

lld/COFF/Chunks.h

+50
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class DefinedImportData;
3636
class DefinedRegular;
3737
class ObjFile;
3838
class OutputSection;
39+
class RuntimePseudoReloc;
3940
class Symbol;
4041

4142
// Mask for permissions (discardable, writable, readable, executable, etc).
@@ -161,6 +162,8 @@ class SectionChunk final : public Chunk {
161162
void applyRelARM64(uint8_t *Off, uint16_t Type, OutputSection *OS, uint64_t S,
162163
uint64_t P) const;
163164

165+
void getRuntimePseudoRelocs(std::vector<RuntimePseudoReloc> &Res);
166+
164167
// Called if the garbage collector decides to not include this chunk
165168
// in a final output. It's supposed to print out a log message to stdout.
166169
void printDiscardedMessage() const;
@@ -418,6 +421,53 @@ class Baserel {
418421
uint8_t Type;
419422
};
420423

424+
// This is a placeholder Chunk, to allow attaching a DefinedSynthetic to a
425+
// specific place in a section, without any data. This is used for the MinGW
426+
// specific symbol __RUNTIME_PSEUDO_RELOC_LIST_END__, even though the concept
427+
// of an empty chunk isn't MinGW specific.
428+
class EmptyChunk : public Chunk {
429+
public:
430+
EmptyChunk() {}
431+
size_t getSize() const override { return 0; }
432+
void writeTo(uint8_t *Buf) const override {}
433+
};
434+
435+
// MinGW specific, for the "automatic import of variables from DLLs" feature.
436+
// This provides the table of runtime pseudo relocations, for variable
437+
// references that turned out to need to be imported from a DLL even though
438+
// the reference didn't use the dllimport attribute. The MinGW runtime will
439+
// process this table after loading, before handling control over to user
440+
// code.
441+
class PseudoRelocTableChunk : public Chunk {
442+
public:
443+
PseudoRelocTableChunk(std::vector<RuntimePseudoReloc> &Relocs)
444+
: Relocs(std::move(Relocs)) {
445+
Alignment = 4;
446+
}
447+
size_t getSize() const override;
448+
void writeTo(uint8_t *Buf) const override;
449+
450+
private:
451+
std::vector<RuntimePseudoReloc> Relocs;
452+
};
453+
454+
// MinGW specific; information about one individual location in the image
455+
// that needs to be fixed up at runtime after loading. This represents
456+
// one individual element in the PseudoRelocTableChunk table.
457+
class RuntimePseudoReloc {
458+
public:
459+
RuntimePseudoReloc(Defined *Sym, SectionChunk *Target, uint32_t TargetOffset,
460+
int Flags)
461+
: Sym(Sym), Target(Target), TargetOffset(TargetOffset), Flags(Flags) {}
462+
463+
Defined *Sym;
464+
SectionChunk *Target;
465+
uint32_t TargetOffset;
466+
// The Flags field contains the size of the relocation, in bits. No other
467+
// flags are currently defined.
468+
int Flags;
469+
};
470+
421471
void applyMOV32T(uint8_t *Off, uint32_t V);
422472
void applyBranch24T(uint8_t *Off, int32_t V);
423473

lld/COFF/Driver.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,11 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
13671367
// Needed for MSVC 2017 15.5 CRT.
13681368
Symtab->addAbsolute(mangle("__enclave_config"), 0);
13691369

1370+
if (Config->MinGW) {
1371+
Symtab->addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST__"), 0);
1372+
Symtab->addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST_END__"), 0);
1373+
}
1374+
13701375
// This code may add new undefined symbols to the link, which may enqueue more
13711376
// symbol resolution tasks, so we need to continue executing tasks until we
13721377
// converge.
@@ -1411,6 +1416,24 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
14111416
Symtab->addCombinedLTOObjects();
14121417
run();
14131418

1419+
if (Config->MinGW) {
1420+
// Load any further object files that might be needed for doing automatic
1421+
// imports.
1422+
//
1423+
// For cases with no automatically imported symbols, this iterates once
1424+
// over the symbol table and doesn't do anything.
1425+
//
1426+
// For the normal case with a few automatically imported symbols, this
1427+
// should only need to be run once, since each new object file imported
1428+
// is an import library and wouldn't add any new undefined references,
1429+
// but there's nothing stopping the __imp_ symbols from coming from a
1430+
// normal object file as well (although that won't be used for the
1431+
// actual autoimport later on). If this pass adds new undefined references,
1432+
// we won't iterate further to resolve them.
1433+
Symtab->loadMinGWAutomaticImports();
1434+
run();
1435+
}
1436+
14141437
// Make sure we have resolved all symbols.
14151438
Symtab->reportRemainingUndefines();
14161439
if (errorCount())

lld/COFF/SymbolTable.cpp

+50
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,53 @@ static std::string getSymbolLocations(ObjFile *File, uint32_t SymIndex) {
125125
return OS.str();
126126
}
127127

128+
void SymbolTable::loadMinGWAutomaticImports() {
129+
for (auto &I : SymMap) {
130+
Symbol *Sym = I.second;
131+
auto *Undef = dyn_cast<Undefined>(Sym);
132+
if (!Undef)
133+
continue;
134+
if (!Sym->IsUsedInRegularObj)
135+
continue;
136+
137+
StringRef Name = Undef->getName();
138+
139+
if (Name.startswith("__imp_"))
140+
continue;
141+
// If we have an undefined symbol, but we have a Lazy representing a
142+
// symbol we could load from file, make sure to load that.
143+
Lazy *L = dyn_cast_or_null<Lazy>(find(("__imp_" + Name).str()));
144+
if (!L || L->PendingArchiveLoad)
145+
continue;
146+
147+
log("Loading lazy " + L->getName() + " from " + L->File->getName() +
148+
" for automatic import");
149+
L->PendingArchiveLoad = true;
150+
L->File->addMember(&L->Sym);
151+
}
152+
}
153+
154+
bool SymbolTable::handleMinGWAutomaticImport(Symbol *Sym, StringRef Name) {
155+
if (Name.startswith("__imp_"))
156+
return false;
157+
DefinedImportData *Imp =
158+
dyn_cast_or_null<DefinedImportData>(find(("__imp_" + Name).str()));
159+
if (!Imp)
160+
return false;
161+
162+
log("Automatically importing " + Name + " from " + Imp->getDLLName());
163+
164+
// Replace the reference directly to a variable with a reference
165+
// to the import address table instead. This obviously isn't right,
166+
// but we mark the symbol as IsRuntimePseudoReloc, and a later pass
167+
// will add runtime pseudo relocations for every relocation against
168+
// this Symbol. The runtime pseudo relocation framework expects the
169+
// reference itself to point at the IAT entry.
170+
Sym->replaceKeepingName(Imp, sizeof(DefinedImportData));
171+
cast<DefinedImportData>(Sym)->IsRuntimePseudoReloc = true;
172+
return true;
173+
}
174+
128175
void SymbolTable::reportRemainingUndefines() {
129176
SmallPtrSet<Symbol *, 8> Undefs;
130177
DenseMap<Symbol *, Symbol *> LocalImports;
@@ -168,6 +215,9 @@ void SymbolTable::reportRemainingUndefines() {
168215
}
169216
}
170217

218+
if (Config->MinGW && handleMinGWAutomaticImport(Sym, Name))
219+
continue;
220+
171221
// Remaining undefined symbols are not fatal if /force is specified.
172222
// They are replaced with dummy defined symbols.
173223
if (Config->Force)

lld/COFF/SymbolTable.h

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ class SymbolTable {
5454
// symbols.
5555
void reportRemainingUndefines();
5656

57+
void loadMinGWAutomaticImports();
58+
bool handleMinGWAutomaticImport(Symbol *Sym, StringRef Name);
59+
5760
// Returns a list of chunks of selected symbols.
5861
std::vector<Chunk *> getChunks();
5962

lld/COFF/Symbols.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ bool Symbol::isLive() const {
6363
return true;
6464
}
6565

66+
// MinGW specific.
67+
void Symbol::replaceKeepingName(Symbol *Other, size_t Size) {
68+
StringRef OrigName = Name;
69+
memcpy(this, Other, Size);
70+
Name = OrigName;
71+
}
72+
6673
COFFSymbolRef DefinedCOFF::getCOFFSymbol() {
6774
size_t SymSize = cast<ObjFile>(File)->getCOFFObj()->getSymbolTableEntrySize();
6875
if (SymSize == sizeof(coff_symbol16))

lld/COFF/Symbols.h

+4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class Symbol {
6666
// Returns the symbol name.
6767
StringRef getName();
6868

69+
void replaceKeepingName(Symbol *Other, size_t Size);
70+
6971
// Returns the file from which this symbol was created.
7072
InputFile *getFile();
7173

@@ -307,6 +309,8 @@ class DefinedImportData : public Defined {
307309
uint16_t getOrdinal() { return File->Hdr->OrdinalHint; }
308310

309311
ImportFile *File;
312+
313+
bool IsRuntimePseudoReloc = false;
310314
};
311315

312316
// This class represents a symbol for a jump table entry which jumps

0 commit comments

Comments
 (0)