Index: COFF/Chunks.h =================================================================== --- COFF/Chunks.h +++ COFF/Chunks.h @@ -36,6 +36,7 @@ class DefinedRegular; class ObjFile; class OutputSection; +class RuntimePseudoReloc; class Symbol; // Mask for permissions (discardable, writable, readable, executable, etc). @@ -157,6 +158,8 @@ void applyRelARM64(uint8_t *Off, uint16_t Type, OutputSection *OS, uint64_t S, uint64_t P) const; + void getRuntimePseudoRelocs(std::vector &Res); + // Called if the garbage collector decides to not include this chunk // in a final output. It's supposed to print out a log message to stdout. void printDiscardedMessage() const; @@ -414,6 +417,43 @@ uint8_t Type; }; +class PseudoRelocChunk : public Chunk { +public: + PseudoRelocChunk(const RuntimePseudoReloc *Reloc) : Reloc(Reloc) { + Alignment = 4; + } + size_t getSize() const override { return 12; } + void writeTo(uint8_t *Buf) const override; + +private: + const RuntimePseudoReloc *Reloc; +}; + +// This is a placeholder Chunk, to allow attaching a DefinedSynthetic to a +// specific place in a section, without any data. +class EmptyChunk : public Chunk { +public: + EmptyChunk() {} + size_t getSize() const override { return 0; } + void writeTo(uint8_t *Buf) const override {} +}; + +class RuntimePseudoReloc { +public: + RuntimePseudoReloc(Defined *Sym, SectionChunk *Target, uint32_t TargetOffset, + int Flags) + : Sym(Sym), Target(Target), TargetOffset(TargetOffset), Flags(Flags) {} + + static RuntimePseudoReloc makeListHeader() { + return RuntimePseudoReloc(nullptr, nullptr, 0, 1); + } + + Defined *Sym; + SectionChunk *Target; + uint32_t TargetOffset; + int Flags; +}; + void applyMOV32T(uint8_t *Off, uint32_t V); void applyBranch24T(uint8_t *Off, int32_t V); Index: COFF/Chunks.cpp =================================================================== --- COFF/Chunks.cpp +++ COFF/Chunks.cpp @@ -410,6 +410,97 @@ } } +static int getRuntimePseudoRelocSize(uint16_t Type) { + // Relocations that either contain an absolute address, or a plain + // relative offset, since the runtime pseudo reloc implementation + // adds 8/16/32/64 bit values to a memory address. + // + // Given a pseudo relocation entry, + // + // typedef struct { + // DWORD sym; + // DWORD target; + // DWORD flags; + // } runtime_pseudo_reloc_item_v2; + // + // the runtime relocation performs this adjustment: + // *(base + .target) += *(base + .sym) - (base + .sym) + // + // This works for both absolute addresses (IMAGE_REL_*_ADDR32/64, + // IMAGE_REL_I386_DIR32, where the memory location initially contains + // the address of the IAT slot, and for relative addresses (IMAGE_REL*_REL32), + // where the memory location originally contains the relative offset to the + // IAT slot. + // + // This requires the target address to be writable, either directly out of + // the image, or temporarily changed at runtime with VirtualProtect. + // Since this only operates on direct address values, it doesn't work for + // ARM/ARM64 relocations, other than the plain ADDR32/ADDR64 relocations. + switch (Config->Machine) { + case AMD64: + switch (Type) { + case IMAGE_REL_AMD64_ADDR64: + return 64; + case IMAGE_REL_AMD64_ADDR32: + case IMAGE_REL_AMD64_REL32: + case IMAGE_REL_AMD64_REL32_1: + case IMAGE_REL_AMD64_REL32_2: + case IMAGE_REL_AMD64_REL32_3: + case IMAGE_REL_AMD64_REL32_4: + case IMAGE_REL_AMD64_REL32_5: + return 32; + default: + return 0; + } + case I386: + switch (Type) { + case IMAGE_REL_I386_DIR32: + case IMAGE_REL_I386_REL32: + return 32; + default: + return 0; + } + case ARMNT: + switch (Type) { + case IMAGE_REL_ARM_ADDR32: + return 32; + default: + return 0; + } + case ARM64: + switch (Type) { + case IMAGE_REL_ARM64_ADDR64: + return 64; + case IMAGE_REL_ARM64_ADDR32: + return 32; + default: + return 0; + } + default: + llvm_unreachable("unknown machine type"); + } +} + +void SectionChunk::getRuntimePseudoRelocs( + std::vector &Res) { + for (const coff_relocation &Rel : Relocs) { + auto *Target = dyn_cast_or_null( + File->getSymbol(Rel.SymbolTableIndex)); + if (!Target || !Target->IsRuntimePseudoReloc) + continue; + int SizeInBits = getRuntimePseudoRelocSize(Rel.Type); + if (SizeInBits == 0) { + error("unable to automatically import from " + Target->getName() + + " with relocation type " + + File->getCOFFObj()->getRelocationTypeName(Rel.Type) + " in " + + toString(File)); + continue; + } + Res.emplace_back( + RuntimePseudoReloc(Target, this, Rel.VirtualAddress, SizeInBits)); + } +} + bool SectionChunk::hasData() const { return !(Header->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA); } @@ -528,6 +619,21 @@ "RVA tables should be de-duplicated"); } +void PseudoRelocChunk::writeTo(uint8_t *Buf) const { + ulittle32_t *Begin = reinterpret_cast(Buf + OutputSectionOff); + if (Reloc) { + Begin[0] = Reloc->Sym->getRVA(); + Begin[1] = Reloc->Target->getRVA() + Reloc->TargetOffset; + Begin[2] = Reloc->Flags; + } else { + // This is a list header, to signal the runtime pseudo relocation v2 + // format. + Begin[0] = 0; + Begin[1] = 0; + Begin[2] = 1; + } +} + // Windows-specific. This class represents a block in .reloc section. // The format is described here. // Index: COFF/Driver.cpp =================================================================== --- COFF/Driver.cpp +++ COFF/Driver.cpp @@ -1321,6 +1321,11 @@ // Needed for MSVC 2017 15.5 CRT. Symtab->addAbsolute(mangle("__enclave_config"), 0); + if (Config->MinGW) { + Symtab->addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST__"), 0); + Symtab->addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST_END__"), 0); + } + // This code may add new undefined symbols to the link, which may enqueue more // symbol resolution tasks, so we need to continue executing tasks until we // converge. @@ -1365,6 +1370,24 @@ Symtab->addCombinedLTOObjects(); run(); + if (Config->MinGW) { + // Load any further object files that might be needed for doing automatic + // imports. + // + // For cases with no automatically imported symbols, this iterates once + // over the symbol table and doesn't do anything. + // + // For the normal case with a few automatically imported symbols, this + // should only need to be run once, since each new object file imported + // is an import library and wouldn't add any new undefined references, + // but there's nothing stopping the __imp_ symbols from coming from a + // normal object file as well (although that won't be used for the + // actual autoimport later on). If this pass adds new undefined references, + // we won't iterate further to resolve them. + Symtab->loadMinGWAutomaticImports(); + run(); + } + // Make sure we have resolved all symbols. Symtab->reportRemainingUndefines(); if (errorCount()) Index: COFF/SymbolTable.h =================================================================== --- COFF/SymbolTable.h +++ COFF/SymbolTable.h @@ -54,6 +54,8 @@ // symbols. void reportRemainingUndefines(); + void loadMinGWAutomaticImports(); + // Returns a list of chunks of selected symbols. std::vector getChunks(); Index: COFF/SymbolTable.cpp =================================================================== --- COFF/SymbolTable.cpp +++ COFF/SymbolTable.cpp @@ -126,6 +126,32 @@ return OS.str(); } +void SymbolTable::loadMinGWAutomaticImports() { + for (auto &I : SymMap) { + Symbol *Sym = I.second; + auto *Undef = dyn_cast(Sym); + if (!Undef) + continue; + if (!Sym->IsUsedInRegularObj) + continue; + + StringRef Name = Undef->getName(); + + if (Name.startswith("__imp_")) + continue; + // If we have an undefined symbol, but we have a Lazy representing a + // symbol we could load from file, make sure to load that. + Lazy *L = dyn_cast_or_null(find(("__imp_" + Name).str())); + if (!L || L->PendingArchiveLoad) + continue; + + log("Loading lazy " + L->getName() + " from " + L->File->getName() + + " for automatic import"); + L->PendingArchiveLoad = true; + L->File->addMember(&L->Sym); + } +} + void SymbolTable::reportRemainingUndefines() { SmallPtrSet Undefs; DenseMap LocalImports; @@ -169,6 +195,22 @@ } } + if (Config->MinGW && !Name.startswith("__imp_")) { + DefinedImportData *Imp = + dyn_cast_or_null(find(("__imp_" + Name).str())); + if (Imp) { + log("Automatically importing " + Name + " from " + Imp->getDLLName()); + // Replace the reference directly to a variable with a reference + // to the import address table instead. This obviously isn't right, + // but we mark the symbol as IsRuntimePseudoReloc, and a later pass + // will add runtime pseudo relocations for every relocation against + // this Symbol. + Sym->replaceKeepingName(Imp, sizeof(DefinedImportData)); + cast(Sym)->IsRuntimePseudoReloc = true; + continue; + } + } + // Remaining undefined symbols are not fatal if /force is specified. // They are replaced with dummy defined symbols. if (Config->Force) Index: COFF/Symbols.h =================================================================== --- COFF/Symbols.h +++ COFF/Symbols.h @@ -66,6 +66,8 @@ // Returns the symbol name. StringRef getName(); + void replaceKeepingName(Symbol *Other, size_t Size); + // Returns the file from which this symbol was created. InputFile *getFile(); @@ -307,6 +309,8 @@ uint16_t getOrdinal() { return File->Hdr->OrdinalHint; } ImportFile *File; + + bool IsRuntimePseudoReloc = false; }; // This class represents a symbol for a jump table entry which jumps Index: COFF/Symbols.cpp =================================================================== --- COFF/Symbols.cpp +++ COFF/Symbols.cpp @@ -63,6 +63,12 @@ return true; } +void Symbol::replaceKeepingName(Symbol *Other, size_t Size) { + StringRef OrigName = Name; + memcpy(this, Other, Size); + Name = OrigName; +} + COFFSymbolRef DefinedCOFF::getCOFFSymbol() { size_t SymSize = cast(File)->getCOFFObj()->getSymbolTableEntrySize(); if (SymSize == sizeof(coff_symbol16)) Index: COFF/Writer.cpp =================================================================== --- COFF/Writer.cpp +++ COFF/Writer.cpp @@ -158,6 +158,7 @@ void openFile(StringRef OutputPath); template void writeHeader(); void createSEHTable(); + void createRuntimePseudoRelocs(); void createGuardCFTables(); void markSymbolsForRVATable(ObjFile *File, ArrayRef SymIdxChunks, @@ -187,6 +188,7 @@ DelayLoadContents DelayIdata; EdataContents Edata; bool SetNoSEHCharacteristic = false; + std::vector RuntimePseudoRelocs; DebugDirectoryChunk *DebugDirectory = nullptr; std::vector DebugRecords; @@ -524,6 +526,9 @@ // Create /guard:cf tables if requested. if (Config->GuardCF != GuardCFLevel::Off) createGuardCFTables(); + + if (Config->MinGW) + createRuntimePseudoRelocs(); } // Create .idata section for the DLL-imported symbol table. @@ -1134,6 +1139,35 @@ cast(C)->setVA(TableChunk->getSize() / 4); } +void Writer::createRuntimePseudoRelocs() { + for (Chunk *C : Symtab->getChunks()) { + auto *SC = dyn_cast(C); + if (!SC || !SC->isLive()) + continue; + SC->getRuntimePseudoRelocs(RuntimePseudoRelocs); + } + + Chunk *Header; + if (RuntimePseudoRelocs.empty()) { + Header = make(); + RdataSec->addChunk(Header); + } else { + log("Writing " + Twine(RuntimePseudoRelocs.size()) + + " runtime pseudo relocations"); + Header = make(nullptr); + RdataSec->addChunk(Header); + for (const auto &RPR : RuntimePseudoRelocs) + RdataSec->addChunk(make(&RPR)); + } + EmptyChunk *EndOfList = make(); + RdataSec->addChunk(EndOfList); + + Symbol *HeadSym = Symtab->findUnderscore("__RUNTIME_PSEUDO_RELOC_LIST__"); + Symbol *EndSym = Symtab->findUnderscore("__RUNTIME_PSEUDO_RELOC_LIST_END__"); + replaceSymbol(HeadSym, HeadSym->getName(), Header); + replaceSymbol(EndSym, EndSym->getName(), EndOfList); +} + // Handles /section options to allow users to overwrite // section attributes. void Writer::setSectionPermissions() { Index: test/COFF/autoimport-arm-code.s =================================================================== --- /dev/null +++ test/COFF/autoimport-arm-code.s @@ -0,0 +1,19 @@ +# REQUIRES: arm + +# RUN: echo -e ".global variable\n.global DllMainCRTStartup\n.thumb\n.text\nDllMainCRTStartup:\nbx lr\n.data\nvariable:\n.long 42" > %t-lib.s +# RUN: llvm-mc -triple=armv7-windows-gnu %t-lib.s -filetype=obj -o %t-lib.obj +# RUN: lld-link -out:%t-lib.dll -dll -entry:DllMainCRTStartup %t-lib.obj -lldmingw -implib:%t-lib.lib + +# RUN: llvm-mc -triple=armv7-windows-gnu %s -filetype=obj -o %t.obj +# RUN: not lld-link -lldmingw -out:%t.exe -entry:main %t.obj %t-lib.lib 2>&1 | FileCheck %s + +# CHECK: error: unable to automatically import from variable with relocation type IMAGE_REL_ARM_MOV32T + + .global main + .text + .thumb +main: + movw r0, :lower16:variable + movt r0, :upper16:variable + ldr r0, [r0] + bx lr Index: test/COFF/autoimport-arm-data.s =================================================================== --- /dev/null +++ test/COFF/autoimport-arm-data.s @@ -0,0 +1,42 @@ +# REQUIRES: arm + +# RUN: echo -e ".global variable\n.global DllMainCRTStartup\n.thumb\n.text\nDllMainCRTStartup:\nbx lr\n.data\nvariable:\n.long 42" > %t-lib.s +# RUN: llvm-mc -triple=armv7-windows-gnu %t-lib.s -filetype=obj -o %t-lib.obj +# RUN: lld-link -out:%t-lib.dll -dll -entry:DllMainCRTStartup %t-lib.obj -lldmingw -implib:%t-lib.lib + +# RUN: llvm-mc -triple=armv7-windows-gnu %s -filetype=obj -o %t.obj +# RUN: lld-link -lldmingw -out:%t.exe -entry:main %t.obj %t-lib.lib -verbose + +# RUN: llvm-readobj -coff-imports %t.exe | FileCheck -check-prefix=IMPORTS %s +# RUN: llvm-objdump -s %t.exe | FileCheck -check-prefix=CONTENTS %s + +# IMPORTS: Import { +# IMPORTS-NEXT: Name: autoimport-arm-data.s.tmp-lib.dll +# IMPORTS-NEXT: ImportLookupTableRVA: 0x2040 +# IMPORTS-NEXT: ImportAddressTableRVA: 0x2048 +# IMPORTS-NEXT: Symbol: variable (0) +# IMPORTS-NEXT: } + +# Runtime pseudo reloc list header consisting of 0x0, 0x0, 0x1. +# First runtime pseudo reloc, with import from 0x2048, +# applied at 0x3000, with a size of 32 bits. +# CONTENTS: Contents of section .rdata: +# CONTENTS: 402000 00000000 00000000 01000000 48200000 +# CONTENTS: 402010 00300000 20000000 +# ptr: pointing at the IAT RVA at 0x2048 +# relocs: pointing at the runtime pseudo reloc list at +# 0x2000 - 0x2018. +# CONTENTS: Contents of section .data: +# CONTENTS: 403000 48204000 00204000 18204000 + + .global main + .text + .thumb +main: + bx lr + .data +ptr: + .long variable +relocs: + .long __RUNTIME_PSEUDO_RELOC_LIST__ + .long __RUNTIME_PSEUDO_RELOC_LIST_END__ Index: test/COFF/autoimport-arm64-code.s =================================================================== --- /dev/null +++ test/COFF/autoimport-arm64-code.s @@ -0,0 +1,18 @@ +# REQUIRES: aarch64 + +# RUN: echo -e ".global variable\n.global DllMainCRTStartup\n.text\nDllMainCRTStartup:\nret\n.data\nvariable:\n.long 42" > %t-lib.s +# RUN: llvm-mc -triple=aarch64-windows-gnu %t-lib.s -filetype=obj -o %t-lib.obj +# RUN: lld-link -out:%t-lib.dll -dll -entry:DllMainCRTStartup %t-lib.obj -lldmingw -implib:%t-lib.lib + +# RUN: llvm-mc -triple=aarch64-windows-gnu %s -filetype=obj -o %t.obj +# RUN: not lld-link -lldmingw -out:%t.exe -entry:main %t.obj %t-lib.lib 2>&1 | FileCheck %s + +# CHECK: error: unable to automatically import from variable with relocation type IMAGE_REL_ARM64_PAGEBASE_REL21 +# CHECK: error: unable to automatically import from variable with relocation type IMAGE_REL_ARM64_PAGEOFFSET_12L + + .global main + .text +main: + adrp x0, variable + ldr w0, [x0, :lo12:variable] + ret Index: test/COFF/autoimport-arm64-data.s =================================================================== --- /dev/null +++ test/COFF/autoimport-arm64-data.s @@ -0,0 +1,42 @@ +# REQUIRES: aarch64 + +# RUN: echo -e ".global variable\n.global DllMainCRTStartup\n.text\nDllMainCRTStartup:\nret\n.data\nvariable:\n.long 42" > %t-lib.s +# RUN: llvm-mc -triple=aarch64-windows-gnu %t-lib.s -filetype=obj -o %t-lib.obj +# RUN: lld-link -out:%t-lib.dll -dll -entry:DllMainCRTStartup %t-lib.obj -lldmingw -implib:%t-lib.lib + +# RUN: llvm-mc -triple=aarch64-windows-gnu %s -filetype=obj -o %t.obj +# RUN: lld-link -lldmingw -out:%t.exe -entry:main %t.obj %t-lib.lib -verbose + +# RUN: llvm-readobj -coff-imports %t.exe | FileCheck -check-prefix=IMPORTS %s +# RUN: llvm-objdump -s %t.exe | FileCheck -check-prefix=CONTENTS %s + +# IMPORTS: Import { +# IMPORTS-NEXT: Name: autoimport-arm64-data.s.tmp-lib.dll +# IMPORTS-NEXT: ImportLookupTableRVA: 0x2040 +# IMPORTS-NEXT: ImportAddressTableRVA: 0x2050 +# IMPORTS-NEXT: Symbol: variable (0) +# IMPORTS-NEXT: } + +# Runtime pseudo reloc list header consisting of 0x0, 0x0, 0x1. +# First runtime pseudo reloc, with import from 0x2050, +# applied at 0x3000, with a size of 32 bits. +# CONTENTS: Contents of section .rdata: +# CONTENTS: 140002000 00000000 00000000 01000000 50200000 +# CONTENTS: 140002010 00300000 40000000 +# ptr: pointing at the IAT RVA at 0x2050 +# relocs: pointing at the runtime pseudo reloc list at +# 0x2000 - 0x2018. +# CONTENTS: Contents of section .data: +# CONTENTS: 140003000 50200040 01000000 00200040 01000000 +# CONTENTS: 140003010 18200040 01000000 + + .global main + .text +main: + ret + .data +ptr: + .quad variable +relocs: + .quad __RUNTIME_PSEUDO_RELOC_LIST__ + .quad __RUNTIME_PSEUDO_RELOC_LIST_END__ Index: test/COFF/autoimport-list-ptrs.s =================================================================== --- /dev/null +++ test/COFF/autoimport-list-ptrs.s @@ -0,0 +1,20 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -triple=x86_64-windows-gnu %s -filetype=obj -o %t.obj +# RUN: lld-link -lldmingw -out:%t.exe -entry:main %t.obj -verbose + +# RUN: llvm-objdump -s %t.exe | FileCheck -check-prefix=CONTENTS %s + +# Even if we didn't actually write any pseudo relocations, +# check that the synthetic pointers still are set to a non-null value +# CONTENTS: Contents of section .data: +# CONTENTS: 140002000 00200040 01000000 00200040 01000000 + + .global main + .text +main: + retq + .data +relocs: + .quad __RUNTIME_PSEUDO_RELOC_LIST__ + .quad __RUNTIME_PSEUDO_RELOC_LIST_END__ Index: test/COFF/autoimport-x86.s =================================================================== --- /dev/null +++ test/COFF/autoimport-x86.s @@ -0,0 +1,53 @@ +# REQUIRES: x86 + +# RUN: echo -e ".global variable\n.global DllMainCRTStartup\n.text\nDllMainCRTStartup:\nret\n.data\nvariable:\n.long 42" > %t-lib.s +# RUN: llvm-mc -triple=x86_64-windows-gnu %t-lib.s -filetype=obj -o %t-lib.obj +# RUN: lld-link -out:%t-lib.dll -dll -entry:DllMainCRTStartup %t-lib.obj -lldmingw -implib:%t-lib.lib + +# RUN: llvm-mc -triple=x86_64-windows-gnu %s -filetype=obj -o %t.obj +# RUN: lld-link -lldmingw -out:%t.exe -entry:main %t.obj %t-lib.lib -verbose + +# RUN: llvm-readobj -coff-imports %t.exe | FileCheck -check-prefix=IMPORTS %s +# RUN: llvm-objdump -d %t.exe | FileCheck -check-prefix=DISASM %s +# RUN: llvm-objdump -s %t.exe | FileCheck -check-prefix=CONTENTS %s + +# IMPORTS: Import { +# IMPORTS-NEXT: Name: autoimport-x86.s.tmp-lib.dll +# IMPORTS-NEXT: ImportLookupTableRVA: 0x2050 +# IMPORTS-NEXT: ImportAddressTableRVA: 0x2060 +# IMPORTS-NEXT: Symbol: variable (0) +# IMPORTS-NEXT: } + +# DISASM: Disassembly of section .text: +# DISASM: .text: +# Relative offset at 0x1002 pointing at the IAT at 0x2060. +# DISASM: 140001000: 8b 05 5a 10 00 00 movl 4186(%rip), %eax +# DISASM: 140001006: c3 retq + +# Runtime pseudo reloc list header consisting of 0x0, 0x0, 0x1. +# First runtime pseudo reloc, with import from 0x2060, +# applied at 0x1002, with a size of 32 bits. +# Second runtime pseudo reloc, with import from 0x2060, +# applied at 0x3000, with a size of 64 bits. +# CONTENTS: Contents of section .rdata: +# CONTENTS: 140002000 00000000 00000000 01000000 60200000 +# CONTENTS: 140002010 02100000 20000000 60200000 00300000 +# CONTENTS: 140002020 40000000 +# ptr: pointing at the IAT RVA at 0x2060 +# relocs: pointing at the runtime pseudo reloc list at +# 0x2000 - 0x2024. +# CONTENTS: Contents of section .data: +# CONTENTS: 140003000 60200040 01000000 00200040 01000000 +# CONTENTS: 140003010 24200040 01000000 + + .global main + .text +main: + movl variable(%rip), %eax + ret + .data +ptr: + .quad variable +relocs: + .quad __RUNTIME_PSEUDO_RELOC_LIST__ + .quad __RUNTIME_PSEUDO_RELOC_LIST_END__