diff options
Diffstat (limited to 'lld/COFF')
-rw-r--r-- | lld/COFF/Chunks.cpp | 22 | ||||
-rw-r--r-- | lld/COFF/Chunks.h | 32 | ||||
-rw-r--r-- | lld/COFF/Config.h | 3 | ||||
-rw-r--r-- | lld/COFF/Driver.cpp | 88 | ||||
-rw-r--r-- | lld/COFF/Driver.h | 2 | ||||
-rw-r--r-- | lld/COFF/DriverUtils.cpp | 16 | ||||
-rw-r--r-- | lld/COFF/MarkLive.cpp | 5 | ||||
-rw-r--r-- | lld/COFF/Options.td | 4 | ||||
-rw-r--r-- | lld/COFF/SymbolTable.cpp | 42 | ||||
-rw-r--r-- | lld/COFF/SymbolTable.h | 6 | ||||
-rw-r--r-- | lld/COFF/Symbols.cpp | 8 | ||||
-rw-r--r-- | lld/COFF/Symbols.h | 4 | ||||
-rw-r--r-- | lld/COFF/Writer.cpp | 29 |
13 files changed, 199 insertions, 62 deletions
diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp index 01752cd..39fc250 100644 --- a/lld/COFF/Chunks.cpp +++ b/lld/COFF/Chunks.cpp @@ -422,12 +422,6 @@ void SectionChunk::writeTo(uint8_t *buf) const { applyRelocation(buf + rel.VirtualAddress, rel); } - - // Write the offset to EC entry thunk preceding section contents. The low bit - // is always set, so it's effectively an offset from the last byte of the - // offset. - if (Defined *entryThunk = getEntryThunk()) - write32le(buf - sizeof(uint32_t), entryThunk->getRVA() - rva + 1); } void SectionChunk::applyRelocation(uint8_t *off, @@ -881,6 +875,19 @@ void RangeExtensionThunkARM64::writeTo(uint8_t *buf) const { applyArm64Imm(buf + 4, target->getRVA() & 0xfff, 0); } +void SameAddressThunkARM64EC::setDynamicRelocs(COFFLinkerContext &ctx) const { + // Add ARM64X relocations replacing adrp/add instructions with a version using + // the hybrid target. + RangeExtensionThunkARM64 hybridView(ARM64EC, hybridTarget); + uint8_t buf[sizeof(arm64Thunk)]; + hybridView.setRVA(rva); + hybridView.writeTo(buf); + uint32_t addrp = *reinterpret_cast<ulittle32_t *>(buf); + uint32_t add = *reinterpret_cast<ulittle32_t *>(buf + sizeof(uint32_t)); + ctx.dynamicRelocs->set(this, addrp); + ctx.dynamicRelocs->set(Arm64XRelocVal(this, sizeof(uint32_t)), add); +} + LocalImportChunk::LocalImportChunk(COFFLinkerContext &c, Defined *s) : sym(s), ctx(c) { setAlignment(ctx.config.wordsize); @@ -1264,7 +1271,8 @@ void DynamicRelocsChunk::finalize() { } // Set the reloc value. The reloc entry must be allocated beforehand. -void DynamicRelocsChunk::set(uint32_t rva, Arm64XRelocVal value) { +void DynamicRelocsChunk::set(Arm64XRelocVal offset, Arm64XRelocVal value) { + uint32_t rva = offset.get(); auto entry = llvm::find_if(arm64xRelocs, [rva](const Arm64XDynamicRelocEntry &e) { return e.offset.get() == rva; diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h index d03a64c..6d88f5e 100644 --- a/lld/COFF/Chunks.h +++ b/lld/COFF/Chunks.h @@ -193,6 +193,8 @@ public: // allowed ranges. Return the additional space required for the extension. virtual uint32_t extendRanges() { return 0; }; + virtual Defined *getEntryThunk() const { return nullptr; }; + static bool classof(const Chunk *c) { return c->kind() >= OtherKind; } protected: @@ -633,7 +635,7 @@ public: bool verifyRanges() override; uint32_t extendRanges() override; - Defined *exitThunk; + Defined *exitThunk = nullptr; Defined *sym = nullptr; bool extended = false; @@ -675,6 +677,26 @@ private: MachineTypes machine; }; +// A chunk used to guarantee the same address for a function in both views of +// a hybrid image. Similar to RangeExtensionThunkARM64 chunks, it calls the +// target symbol using a BR instruction. It also contains an entry thunk for EC +// compatibility and additional ARM64X relocations that swap targets between +// views. +class SameAddressThunkARM64EC : public RangeExtensionThunkARM64 { +public: + explicit SameAddressThunkARM64EC(Defined *t, Defined *hybridTarget, + Defined *entryThunk) + : RangeExtensionThunkARM64(ARM64EC, t), hybridTarget(hybridTarget), + entryThunk(entryThunk) {} + + Defined *getEntryThunk() const override { return entryThunk; } + void setDynamicRelocs(COFFLinkerContext &ctx) const; + +private: + Defined *hybridTarget; + Defined *entryThunk; +}; + // Windows-specific. // See comments for DefinedLocalImport class. class LocalImportChunk : public NonSectionChunk { @@ -843,13 +865,13 @@ class Arm64XRelocVal { public: Arm64XRelocVal(uint64_t value = 0) : value(value) {} Arm64XRelocVal(Defined *sym, int32_t offset = 0) : sym(sym), value(offset) {} - Arm64XRelocVal(Chunk *chunk, int32_t offset = 0) + Arm64XRelocVal(const Chunk *chunk, int32_t offset = 0) : chunk(chunk), value(offset) {} uint64_t get() const; private: Defined *sym = nullptr; - Chunk *chunk = nullptr; + const Chunk *chunk = nullptr; uint64_t value; }; @@ -884,7 +906,7 @@ public: arm64xRelocs.emplace_back(type, size, offset, value); } - void set(uint32_t rva, Arm64XRelocVal value); + void set(Arm64XRelocVal offset, Arm64XRelocVal value); private: std::vector<Arm64XDynamicRelocEntry> arm64xRelocs; @@ -940,6 +962,8 @@ inline bool Chunk::isHotPatchable() const { inline Defined *Chunk::getEntryThunk() const { if (auto *c = dyn_cast<const SectionChunkEC>(this)) return c->entryThunk; + if (auto *c = dyn_cast<const NonSectionChunk>(this)) + return c->getEntryThunk(); return nullptr; } diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h index 91b6e63..a03bb57 100644 --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -223,6 +223,9 @@ struct Configuration { StringRef manifestUIAccess = "'false'"; StringRef manifestFile; + // used for /arm64xsameaddress + std::vector<std::pair<Symbol *, Symbol *>> sameAddresses; + // used for /dwodir StringRef dwoDir; diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 570b8f9..7580b469 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -500,7 +500,9 @@ void LinkerDriver::parseDirectives(InputFile *file) { file->symtab.parseAlternateName(arg->getValue()); break; case OPT_arm64xsameaddress: - if (!file->symtab.isEC()) + if (file->symtab.isEC()) + parseSameAddress(arg->getValue()); + else Warn(ctx) << arg->getSpelling() << " is not allowed in non-ARM64EC files (" << toString(file) << ")"; @@ -1318,13 +1320,9 @@ void LinkerDriver::convertResources() { } void LinkerDriver::maybeCreateECExportThunk(StringRef name, Symbol *&sym) { - Defined *def; if (!sym) return; - if (auto undef = dyn_cast<Undefined>(sym)) - def = undef->getDefinedWeakAlias(); - else - def = dyn_cast<Defined>(sym); + Defined *def = sym->getDefined(); if (!def) return; @@ -1356,11 +1354,7 @@ void LinkerDriver::createECExportThunks() { Symbol *sym = ctx.symtab.find(targetName); if (!sym) continue; - Defined *targetSym; - if (auto undef = dyn_cast<Undefined>(sym)) - targetSym = undef->getDefinedWeakAlias(); - else - targetSym = dyn_cast<Defined>(sym); + Defined *targetSym = sym->getDefined(); if (!targetSym) continue; @@ -2303,6 +2297,13 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) { args.filtered(OPT_dependentloadflag, OPT_dependentloadflag_opt)) parseDependentLoadFlags(arg); + for (auto *arg : args.filtered(OPT_arm64xsameaddress)) { + if (ctx.hybridSymtab) + parseSameAddress(arg->getValue()); + else + Warn(ctx) << arg->getSpelling() << " is allowed only on EC targets"; + } + if (tar) { llvm::TimeTraceScope timeScope("Reproducer: response file"); tar->append( @@ -2676,12 +2677,46 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) { createECExportThunks(); // Resolve remaining undefined symbols and warn about imported locals. + std::vector<Undefined *> aliases; ctx.forEachSymtab( - [&](SymbolTable &symtab) { symtab.resolveRemainingUndefines(); }); + [&](SymbolTable &symtab) { symtab.resolveRemainingUndefines(aliases); }); if (errorCount()) return; + ctx.forEachActiveSymtab([](SymbolTable &symtab) { + symtab.initializeECThunks(); + symtab.initializeLoadConfig(); + }); + + // Identify unreferenced COMDAT sections. + if (config->doGC) { + if (config->mingw) { + // markLive doesn't traverse .eh_frame, but the personality function is + // only reached that way. The proper solution would be to parse and + // traverse the .eh_frame section, like the ELF linker does. + // For now, just manually try to retain the known possible personality + // functions. This doesn't bring in more object files, but only marks + // functions that already have been included to be retained. + ctx.forEachSymtab([&](SymbolTable &symtab) { + for (const char *n : {"__gxx_personality_v0", "__gcc_personality_v0", + "rust_eh_personality"}) { + Defined *d = dyn_cast_or_null<Defined>(symtab.findUnderscore(n)); + if (d && !d->isGCRoot) { + d->isGCRoot = true; + config->gcroot.push_back(d); + } + } + }); + } + + markLive(ctx); + } + + ctx.symtab.initializeSameAddressThunks(); + for (auto alias : aliases) + alias->resolveWeakAlias(); + if (config->mingw) { // Make sure the crtend.o object is the last object file. This object // file can contain terminating section chunks that need to be placed @@ -2773,35 +2808,6 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) { if (auto *arg = args.getLastArg(OPT_print_symbol_order)) config->printSymbolOrder = arg->getValue(); - if (ctx.symtab.isEC()) - ctx.symtab.initializeECThunks(); - ctx.forEachActiveSymtab( - [](SymbolTable &symtab) { symtab.initializeLoadConfig(); }); - - // Identify unreferenced COMDAT sections. - if (config->doGC) { - if (config->mingw) { - // markLive doesn't traverse .eh_frame, but the personality function is - // only reached that way. The proper solution would be to parse and - // traverse the .eh_frame section, like the ELF linker does. - // For now, just manually try to retain the known possible personality - // functions. This doesn't bring in more object files, but only marks - // functions that already have been included to be retained. - ctx.forEachSymtab([&](SymbolTable &symtab) { - for (const char *n : {"__gxx_personality_v0", "__gcc_personality_v0", - "rust_eh_personality"}) { - Defined *d = dyn_cast_or_null<Defined>(symtab.findUnderscore(n)); - if (d && !d->isGCRoot) { - d->isGCRoot = true; - config->gcroot.push_back(d); - } - } - }); - } - - markLive(ctx); - } - // Needs to happen after the last call to addFile(). convertResources(); diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index 5a9bd5c..b500ac8 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -214,6 +214,8 @@ private: void parsePDBPageSize(StringRef); void parseSection(StringRef); + void parseSameAddress(StringRef); + // Parses a MS-DOS stub file void parseDosStub(StringRef path); diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp index d8b41c7..dc4039f 100644 --- a/lld/COFF/DriverUtils.cpp +++ b/lld/COFF/DriverUtils.cpp @@ -328,6 +328,22 @@ void LinkerDriver::parseSwaprun(StringRef arg) { } while (!arg.empty()); } +void LinkerDriver::parseSameAddress(StringRef arg) { + auto mangledName = getArm64ECMangledFunctionName(arg); + Symbol *sym = ctx.symtab.addUndefined(mangledName ? *mangledName : arg); + + // MSVC appears to generate thunks even for non-hybrid ARM64EC images. + // As a side effect, the native symbol is pulled in. Since this is used + // in the CRT for thread-local constructors, it results in the image + // containing unnecessary native code. As these thunks don't appear to + // be useful, we limit this behavior to actual hybrid targets. This may + // change if compatibility becomes necessary. + if (ctx.config.machine != ARM64X) + return; + Symbol *nativeSym = ctx.hybridSymtab->addUndefined(arg); + ctx.config.sameAddresses.emplace_back(sym, nativeSym); +} + // An RAII temporary file class that automatically removes a temporary file. namespace { class TemporaryFile { diff --git a/lld/COFF/MarkLive.cpp b/lld/COFF/MarkLive.cpp index f40810c..78f5030 100644 --- a/lld/COFF/MarkLive.cpp +++ b/lld/COFF/MarkLive.cpp @@ -49,7 +49,10 @@ void markLive(COFFLinkerContext &ctx) { addSym(file->impchkThunk->exitThunk); }; - addSym = [&](Symbol *b) { + addSym = [&](Symbol *s) { + Defined *b = s->getDefined(); + if (!b) + return; if (auto *sym = dyn_cast<DefinedRegular>(b)) { enqueue(sym->getChunk()); } else if (auto *sym = dyn_cast<DefinedImportData>(b)) { diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td index 0d66b49..2c393cc 100644 --- a/lld/COFF/Options.td +++ b/lld/COFF/Options.td @@ -31,6 +31,9 @@ multiclass B_priv<string name> { def align : P<"align", "Section alignment">; def aligncomm : P<"aligncomm", "Set common symbol alignment">; def alternatename : P<"alternatename", "Define weak alias">; +def arm64xsameaddress + : P<"arm64xsameaddress", "Generate a thunk for the symbol with the same " + "address in both native and EC views on ARM64X.">; def base : P<"base", "Base address of the program">; def color_diagnostics: Flag<["--"], "color-diagnostics">, HelpText<"Alias for --color-diagnostics=always">; @@ -373,4 +376,3 @@ def tlbid : P_priv<"tlbid">; def tlbout : P_priv<"tlbout">; def verbose_all : P_priv<"verbose">; def guardsym : P_priv<"guardsym">; -def arm64xsameaddress : P_priv<"arm64xsameaddress">; diff --git a/lld/COFF/SymbolTable.cpp b/lld/COFF/SymbolTable.cpp index 189e75d..de04cdf 100644 --- a/lld/COFF/SymbolTable.cpp +++ b/lld/COFF/SymbolTable.cpp @@ -452,7 +452,7 @@ void SymbolTable::reportUnresolvable() { reportProblemSymbols(undefs, /*localImports=*/nullptr, true); } -void SymbolTable::resolveRemainingUndefines() { +void SymbolTable::resolveRemainingUndefines(std::vector<Undefined *> &aliases) { llvm::TimeTraceScope timeScope("Resolve remaining undefined symbols"); SmallPtrSet<Symbol *, 8> undefs; DenseMap<Symbol *, Symbol *> localImports; @@ -468,8 +468,10 @@ void SymbolTable::resolveRemainingUndefines() { StringRef name = undef->getName(); // A weak alias may have been resolved, so check for that. - if (undef->resolveWeakAlias()) + if (undef->getWeakAlias()) { + aliases.push_back(undef); continue; + } // If we can resolve a symbol by removing __imp_ prefix, do that. // This odd rule is for compatibility with MSVC linker. @@ -620,10 +622,10 @@ void SymbolTable::initializeECThunks() { return; for (auto it : entryThunks) { - auto *to = dyn_cast<Defined>(it.second); + Defined *to = it.second->getDefined(); if (!to) continue; - auto *from = dyn_cast<DefinedRegular>(it.first); + auto *from = dyn_cast_or_null<DefinedRegular>(it.first->getDefined()); // We need to be able to add padding to the function and fill it with an // offset to its entry thunks. To ensure that padding the function is // feasible, functions are required to be COMDAT symbols with no offset. @@ -642,7 +644,8 @@ void SymbolTable::initializeECThunks() { Symbol *sym = exitThunks.lookup(file->thunkSym); if (!sym) sym = exitThunks.lookup(file->impECSym); - file->impchkThunk->exitThunk = dyn_cast_or_null<Defined>(sym); + if (sym) + file->impchkThunk->exitThunk = sym->getDefined(); } // On ARM64EC, the __imp_ symbol references the auxiliary IAT, while the @@ -659,6 +662,35 @@ void SymbolTable::initializeECThunks() { }); } +void SymbolTable::initializeSameAddressThunks() { + for (auto iter : ctx.config.sameAddresses) { + auto sym = dyn_cast_or_null<DefinedRegular>(iter.first->getDefined()); + if (!sym || !sym->isLive()) + continue; + auto nativeSym = + dyn_cast_or_null<DefinedRegular>(iter.second->getDefined()); + if (!nativeSym || !nativeSym->isLive()) + continue; + Defined *entryThunk = sym->getChunk()->getEntryThunk(); + if (!entryThunk) + continue; + + // Replace symbols with symbols referencing the thunk. Store the original + // symbol as equivalent DefinedSynthetic instances for use in the thunk + // itself. + auto symClone = make<DefinedSynthetic>(sym->getName(), sym->getChunk(), + sym->getValue()); + auto nativeSymClone = make<DefinedSynthetic>( + nativeSym->getName(), nativeSym->getChunk(), nativeSym->getValue()); + SameAddressThunkARM64EC *thunk = + make<SameAddressThunkARM64EC>(nativeSymClone, symClone, entryThunk); + sameAddressThunks.push_back(thunk); + + replaceSymbol<DefinedSynthetic>(sym, sym->getName(), thunk); + replaceSymbol<DefinedSynthetic>(nativeSym, nativeSym->getName(), thunk); + } +} + Symbol *SymbolTable::addUndefined(StringRef name, InputFile *f, bool overrideLazy) { auto [s, wasInserted] = insert(name, f); diff --git a/lld/COFF/SymbolTable.h b/lld/COFF/SymbolTable.h index 7eb0676..aadd366 100644 --- a/lld/COFF/SymbolTable.h +++ b/lld/COFF/SymbolTable.h @@ -31,6 +31,7 @@ class DefinedAbsolute; class DefinedRegular; class ImportThunkChunk; class LazyArchive; +class SameAddressThunkARM64EC; class SectionChunk; class Symbol; @@ -67,7 +68,7 @@ public: // Try to resolve any undefined symbols and update the symbol table // accordingly, then print an error message for any remaining undefined // symbols and warn about imported local symbols. - void resolveRemainingUndefines(); + void resolveRemainingUndefines(std::vector<Undefined *> &aliases); // Try to resolve undefined symbols with alternate names. void resolveAlternateNames(); @@ -140,6 +141,7 @@ public: void addEntryThunk(Symbol *from, Symbol *to); void addExitThunk(Symbol *from, Symbol *to); void initializeECThunks(); + void initializeSameAddressThunks(); void reportDuplicate(Symbol *existing, InputFile *newFile, SectionChunk *newSc = nullptr, @@ -159,6 +161,8 @@ public: // A list of EC EXP+ symbols. std::vector<Symbol *> expSymbols; + std::vector<SameAddressThunkARM64EC *> sameAddressThunks; + // A list of DLL exports. std::vector<Export> exports; llvm::DenseSet<StringRef> directivesExports; diff --git a/lld/COFF/Symbols.cpp b/lld/COFF/Symbols.cpp index b571ce9..ba4f95d1 100644 --- a/lld/COFF/Symbols.cpp +++ b/lld/COFF/Symbols.cpp @@ -91,6 +91,14 @@ bool Symbol::isLive() const { return true; } +Defined *Symbol::getDefined() { + if (auto d = dyn_cast<Defined>(this)) + return d; + if (auto u = dyn_cast<Undefined>(this)) + return u->getDefinedWeakAlias(); + return nullptr; +} + void Symbol::replaceKeepingName(Symbol *other, size_t size) { StringRef origName = getName(); memcpy(this, other, size); diff --git a/lld/COFF/Symbols.h b/lld/COFF/Symbols.h index fd3d8ce..c86ded8 100644 --- a/lld/COFF/Symbols.h +++ b/lld/COFF/Symbols.h @@ -95,6 +95,10 @@ public: symbolKind == LazyDLLSymbolKind; } + // Get the Defined symbol associated with this symbol, either itself or its + // weak alias. + Defined *getDefined(); + private: void computeName(); diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp index 0765618..21ab9d1 100644 --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -314,6 +314,7 @@ private: uint32_t dataDirOffset64; OutputSection *textSec; + OutputSection *wowthkSec; OutputSection *hexpthkSec; OutputSection *bssSec; OutputSection *rdataSec; @@ -1076,8 +1077,10 @@ void Writer::createSections() { // Try to match the section order used by link.exe. textSec = createSection(".text", code | r | x); - if (isArm64EC(ctx.config.machine)) + if (isArm64EC(ctx.config.machine)) { + wowthkSec = createSection(".wowthk", code | r | x); hexpthkSec = createSection(".hexpthk", code | r | x); + } bssSec = createSection(".bss", bss | r | w); rdataSec = createSection(".rdata", data | r); buildidSec = createSection(".buildid", data | r); @@ -1129,6 +1132,9 @@ void Writer::createSections() { if (hasIdata) locateImportTables(); + for (auto thunk : ctx.symtab.sameAddressThunks) + wowthkSec->addChunk(thunk); + // Then create an OutputSection for each section. // '$' and all following characters in input section names are // discarded when determining output section. So, .text$foo @@ -2310,6 +2316,14 @@ void Writer::createECChunks() { ctx.symtab.findUnderscore("__arm64x_redirection_metadata"); replaceSymbol<DefinedSynthetic>(entryPointsSym, entryPointsSym->getName(), entryPoints); + + for (auto thunk : ctx.symtab.sameAddressThunks) { + // Relocation values are set later in setECSymbols. + ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE, sizeof(uint32_t), + thunk); + ctx.dynamicRelocs->add(IMAGE_DVRT_ARM64X_FIXUP_TYPE_VALUE, sizeof(uint32_t), + Arm64XRelocVal(thunk, sizeof(uint32_t))); + } } // MinGW specific. Gather all relocations that are imported from a DLL even @@ -2519,6 +2533,9 @@ void Writer::setECSymbols() { chpeSym->getRVA() + offsetof(chpe_metadata, ExtraRFETableSize), pdata.last->getRVA() + pdata.last->getSize() - pdata.first->getRVA()); } + + for (SameAddressThunkARM64EC *thunk : ctx.symtab.sameAddressThunks) + thunk->setDynamicRelocs(ctx); } // Write section contents to a mmap'ed file. @@ -2544,7 +2561,15 @@ void Writer::writeSections() { } parallelForEach(sec->chunks, [&](Chunk *c) { - c->writeTo(secBuf + c->getRVA() - sec->getRVA()); + uint8_t *buf = secBuf + c->getRVA() - sec->getRVA(); + c->writeTo(buf); + + // Write the offset to EC entry thunk preceding section contents. The low + // bit is always set, so it's effectively an offset from the last byte of + // the offset. + if (Defined *entryThunk = c->getEntryThunk()) + write32le(buf - sizeof(uint32_t), + entryThunk->getRVA() - c->getRVA() + 1); }); } } |