From 88181375a3db11687ec318b4518fe64dbbccf5e7 Mon Sep 17 00:00:00 2001 From: Sunho Kim Date: Sun, 31 Jul 2022 07:49:37 +0900 Subject: [PATCH] [JITLink][COFF] Implement include/alternatename linker directive. Implements include/alternatename linker directive. Alternatename is used by static msvc runtime library. Alias symbol is technically incorrect (we have to search for external definition) but we don't have a way to represent this in jitlink/orc yet, this is solved in the following up patch. Inlcude linker directive is used in ucrt to forcelly lookup the static initializer symbols so that they will be emitted. It's implemented as extenral symbols with live flag on that cause the lookup of these symbols. Reviewed By: lhames Differential Revision: https://reviews.llvm.org/D130276 --- .../ExecutionEngine/JITLink/CMakeLists.txt | 7 + .../JITLink/COFFDirectiveParser.cpp | 77 ++++++++++ .../JITLink/COFFDirectiveParser.h | 48 ++++++ .../JITLink/COFFLinkGraphBuilder.cpp | 141 +++++++++++++----- .../JITLink/COFFLinkGraphBuilder.h | 13 ++ .../ExecutionEngine/JITLink/COFFOptions.td | 21 +++ .../X86/COFF_directive_alternatename.s | 30 ++++ .../X86/COFF_directive_alternatename_fail.s | 27 ++++ .../JITLink/X86/COFF_directive_include.s | 21 +++ 9 files changed, 351 insertions(+), 34 deletions(-) create mode 100644 llvm/lib/ExecutionEngine/JITLink/COFFDirectiveParser.cpp create mode 100644 llvm/lib/ExecutionEngine/JITLink/COFFDirectiveParser.h create mode 100644 llvm/lib/ExecutionEngine/JITLink/COFFOptions.td create mode 100644 llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_alternatename.s create mode 100644 llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_alternatename_fail.s create mode 100644 llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_include.s diff --git a/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt b/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt index 87892c080af2..a1dbb8a90875 100644 --- a/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt @@ -1,3 +1,7 @@ +set(LLVM_TARGET_DEFINITIONS COFFOptions.td) +tablegen(LLVM COFFOptions.inc -gen-opt-parser-defs) +add_public_tablegen_target(JITLinkTableGen) + add_llvm_component_library(LLVMJITLink DWARFRecordSectionSplitter.cpp EHFrameSupport.cpp @@ -23,6 +27,7 @@ add_llvm_component_library(LLVMJITLink # COFF COFF.cpp + COFFDirectiveParser.cpp COFFLinkGraphBuilder.cpp COFF_x86_64.cpp @@ -36,10 +41,12 @@ add_llvm_component_library(LLVMJITLink DEPENDS intrinsics_gen + JITLinkTableGen LINK_COMPONENTS BinaryFormat Object + Option OrcTargetProcess Support ) diff --git a/llvm/lib/ExecutionEngine/JITLink/COFFDirectiveParser.cpp b/llvm/lib/ExecutionEngine/JITLink/COFFDirectiveParser.cpp new file mode 100644 index 000000000000..0934f3418804 --- /dev/null +++ b/llvm/lib/ExecutionEngine/JITLink/COFFDirectiveParser.cpp @@ -0,0 +1,77 @@ +//===-- COFFDirectiveParser.cpp - JITLink coff directive parser --*- C++ -*===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// MSVC COFF directive parser +// +//===----------------------------------------------------------------------===// + +#include "COFFDirectiveParser.h" + +using namespace llvm; +using namespace jitlink; + +#define DEBUG_TYPE "jitlink" + +// Create prefix string literals used in Options.td +#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; +#include "COFFOptions.inc" +#undef PREFIX + +// Create table mapping all options defined in COFFOptions.td +static const opt::OptTable::Info infoTable[] = { +#define OPTION(X1, X2, ID, KIND, GROUP, ALIAS, X7, X8, X9, X10, X11, X12) \ + {X1, \ + X2, \ + X10, \ + X11, \ + COFF_OPT_##ID, \ + opt::Option::KIND##Class, \ + X9, \ + X8, \ + COFF_OPT_##GROUP, \ + COFF_OPT_##ALIAS, \ + X7, \ + X12}, +#include "COFFOptions.inc" +#undef OPTION +}; + +class COFFOptTable : public opt::OptTable { +public: + COFFOptTable() : OptTable(infoTable, true) {} +}; + +static COFFOptTable optTable; + +Expected> +COFFDirectiveParser::parse(StringRef Str) { + SmallVector Tokens; + SmallVector Buffer; + cl::TokenizeWindowsCommandLineNoCopy(Str, saver, Tokens); + for (StringRef Tok : Tokens) { + bool HasNul = Tok.end() != Str.end() && Tok.data()[Tok.size()] == '\0'; + Buffer.push_back(HasNul ? Tok.data() : saver.save(Tok).data()); + } + + unsigned missingIndex; + unsigned missingCount; + + auto Result = std::make_unique( + optTable.ParseArgs(Buffer, missingIndex, missingCount)); + + if (missingCount) + return make_error(Twine("COFF directive parsing failed: ") + + Result->getArgString(missingIndex) + + " missing argument"); + LLVM_DEBUG({ + for (auto *arg : Result->filtered(COFF_OPT_UNKNOWN)) + dbgs() << "Unknown coff option argument: " << arg->getAsString(*Result) + << "\n"; + }); + return Result; +} diff --git a/llvm/lib/ExecutionEngine/JITLink/COFFDirectiveParser.h b/llvm/lib/ExecutionEngine/JITLink/COFFDirectiveParser.h new file mode 100644 index 000000000000..bd3b6cf3e91f --- /dev/null +++ b/llvm/lib/ExecutionEngine/JITLink/COFFDirectiveParser.h @@ -0,0 +1,48 @@ +//===--- COFFDirectiveParser.h - JITLink coff directive parser --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// MSVC COFF directive parser +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_JITLINK_COFFDIRECTIVEPARSER_H +#define LLVM_EXECUTIONENGINE_JITLINK_COFFDIRECTIVEPARSER_H + +#include "llvm/ADT/Triple.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/StringSaver.h" + +namespace llvm { +namespace jitlink { + +enum { + COFF_OPT_INVALID = 0, +#define OPTION(_1, _2, ID, _4, _5, _6, _7, _8, _9, _10, _11, _12) COFF_OPT_##ID, +#include "COFFOptions.inc" +#undef OPTION +}; + +/// Parser for the MSVC specific preprocessor directives. +/// https://docs.microsoft.com/en-us/cpp/preprocessor/comment-c-cpp?view=msvc-160 +class COFFDirectiveParser { +public: + Expected> parse(StringRef Str); + +private: + llvm::BumpPtrAllocator bAlloc; + llvm::StringSaver saver{bAlloc}; +}; + +} // end namespace jitlink +} // end namespace llvm + +#endif // LLVM_EXECUTIONENGINE_JITLINK_COFFDIRECTIVEPARSER_H \ No newline at end of file diff --git a/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp b/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp index 8fd644d3c3b0..36b25c3ccc18 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp @@ -170,11 +170,16 @@ Error COFFLinkGraphBuilder::graphifySections() { if (auto Err = Obj.getSectionContents(*Sec, Data)) return Err; + auto CharData = ArrayRef( + reinterpret_cast(Data.data()), Data.size()); + + if (SectionName == getDirectiveSectionName()) + if (auto Err = handleDirectiveSection( + StringRef(CharData.data(), CharData.size()))) + return Err; + B = &G->createContentBlock( - *GraphSec, - ArrayRef(reinterpret_cast(Data.data()), - Data.size()), - orc::ExecutorAddr(getSectionAddress(Obj, *Sec)), + *GraphSec, CharData, orc::ExecutorAddr(getSectionAddress(Obj, *Sec)), (*Sec)->getAlignment(), 0); } @@ -224,30 +229,17 @@ Error COFFLinkGraphBuilder::graphifySymbols() { << " (index: " << SectionIndex << ") \n"; }); else if (Sym->isUndefined()) { - auto CreateExternalSymbol = [&](StringRef SymbolName) { - if (!ExternalSymbols.count(SymbolName)) - ExternalSymbols[SymbolName] = &G->addExternalSymbol( - SymbolName, Sym->getValue(), Linkage::Strong); - - LLVM_DEBUG({ - dbgs() << " " << SymIndex - << ": Creating external graph symbol for COFF symbol \"" - << SymbolName << "\" in " - << getCOFFSectionName(SectionIndex, Sec, *Sym) - << " (index: " << SectionIndex << ") \n"; - }); - return ExternalSymbols[SymbolName]; - }; if (SymbolName.startswith(getDLLImportStubPrefix())) { if (Sym->getValue() != 0) return make_error( "DLL import symbol has non-zero offset"); - auto ExternalSym = CreateExternalSymbol( - SymbolName.drop_front(getDLLImportStubPrefix().size())); + auto ExternalSym = createExternalSymbol( + SymIndex, SymbolName.drop_front(getDLLImportStubPrefix().size()), + *Sym, Sec); GSym = &createDLLImportEntry(SymbolName, *ExternalSym); } else - GSym = CreateExternalSymbol(SymbolName); + GSym = createExternalSymbol(SymIndex, SymbolName, *Sym, Sec); } else if (Sym->isWeakExternal()) { auto *WeakExternal = Sym->getAux(); COFFSymbolIndex TagIndex = WeakExternal->TagIndex; @@ -281,12 +273,52 @@ Error COFFLinkGraphBuilder::graphifySymbols() { if (auto Err = flushWeakAliasRequests()) return Err; + if (auto Err = handleAlternateNames()) + return Err; + if (auto Err = calculateImplicitSizeOfSymbols()) return Err; return Error::success(); } +Error COFFLinkGraphBuilder::handleDirectiveSection(StringRef Str) { + auto Parsed = DirectiveParser.parse(Str); + if (!Parsed) + return Parsed.takeError(); + for (auto *Arg : **Parsed) { + StringRef S = Arg->getValue(); + switch (Arg->getOption().getID()) { + case COFF_OPT_alternatename: { + StringRef From, To; + std::tie(From, To) = S.split('='); + if (From.empty() || To.empty()) + return make_error( + "Invalid COFF /alternatename directive"); + AlternateNames[From] = To; + break; + } + case COFF_OPT_incl: { + auto DataCopy = G->allocateString(S); + StringRef StrCopy(DataCopy.data(), DataCopy.size()); + ExternalSymbols[StrCopy] = + &G->addExternalSymbol(StrCopy, 0, Linkage::Strong); + ExternalSymbols[StrCopy]->setLive(true); + break; + } + case COFF_OPT_export: + break; + default: { + LLVM_DEBUG({ + dbgs() << "Unknown coff directive: " << Arg->getSpelling() << "\n"; + }); + break; + } + } + } + return Error::success(); +} + Error COFFLinkGraphBuilder::flushWeakAliasRequests() { // Export the weak external symbols and alias it for (auto &WeakExternal : WeakExternalRequests) { @@ -303,22 +335,18 @@ Error COFFLinkGraphBuilder::flushWeakAliasRequests() { ? Scope::Default : Scope::Local; - // FIXME: Support this when there's a way to handle this. - if (!Target->isDefined()) - return make_error("Weak external symbol with external " - "symbol as alternative not supported."); - - jitlink::Symbol *NewSymbol = &G->addDefinedSymbol( - Target->getBlock(), Target->getOffset(), WeakExternal.SymbolName, - Target->getSize(), Linkage::Weak, S, Target->isCallable(), false); + auto NewSymbol = + createAliasSymbol(WeakExternal.SymbolName, Linkage::Weak, S, *Target); + if (!NewSymbol) + return NewSymbol.takeError(); setGraphSymbol(AliasSymbol->getSectionNumber(), WeakExternal.Alias, - *NewSymbol); + **NewSymbol); LLVM_DEBUG({ dbgs() << " " << WeakExternal.Alias << ": Creating weak external symbol for COFF symbol \"" << WeakExternal.SymbolName << "\" in section " << AliasSymbol->getSectionNumber() << "\n"; - dbgs() << " " << *NewSymbol << "\n"; + dbgs() << " " << **NewSymbol << "\n"; }); } else return make_error("Weak symbol alias requested but actual " @@ -328,6 +356,49 @@ Error COFFLinkGraphBuilder::flushWeakAliasRequests() { return Error::success(); } +Error COFFLinkGraphBuilder::handleAlternateNames() { + // FIXME: Use proper alias + for (auto &KeyValue : AlternateNames) + if (DefinedSymbols.count(KeyValue.second) && + ExternalSymbols.count(KeyValue.first)) { + auto *Target = DefinedSymbols[KeyValue.second]; + auto *Alias = ExternalSymbols[KeyValue.first]; + G->makeDefined(*Alias, Target->getBlock(), Target->getOffset(), + Target->getSize(), Linkage::Weak, Scope::Local, false); + } + return Error::success(); +} + +Symbol *COFFLinkGraphBuilder::createExternalSymbol( + COFFSymbolIndex SymIndex, StringRef SymbolName, + object::COFFSymbolRef Symbol, const object::coff_section *Section) { + if (!ExternalSymbols.count(SymbolName)) + ExternalSymbols[SymbolName] = + &G->addExternalSymbol(SymbolName, Symbol.getValue(), Linkage::Strong); + + LLVM_DEBUG({ + dbgs() << " " << SymIndex + << ": Creating external graph symbol for COFF symbol \"" + << SymbolName << "\" in " + << getCOFFSectionName(Symbol.getSectionNumber(), Section, Symbol) + << " (index: " << Symbol.getSectionNumber() << ") \n"; + }); + return ExternalSymbols[SymbolName]; +} + +Expected COFFLinkGraphBuilder::createAliasSymbol(StringRef SymbolName, + Linkage L, Scope S, + Symbol &Target) { + if (!Target.isDefined()) { + // FIXME: Support this when there's a way to handle this. + return make_error("Weak external symbol with external " + "symbol as alternative not supported."); + } + return &G->addDefinedSymbol(Target.getBlock(), Target.getOffset(), SymbolName, + Target.getSize(), L, S, Target.isCallable(), + false); +} + // In COFF, most of the defined symbols don't contain the size information. // Hence, we calculate the "implicit" size of symbol by taking the delta of // offsets of consecutive symbols within a block. We maintain a balanced tree @@ -426,10 +497,11 @@ Expected COFFLinkGraphBuilder::createDefinedSymbol( if (Symbol.isExternal()) { // This is not a comdat sequence, export the symbol as it is if (!isComdatSection(Section)) { - - return &G->addDefinedSymbol( + auto GSym = &G->addDefinedSymbol( *B, Symbol.getValue(), SymbolName, 0, Linkage::Strong, Scope::Default, Symbol.getComplexType() == COFF::IMAGE_SYM_DTYPE_FUNCTION, false); + DefinedSymbols[SymbolName] = GSym; + return GSym; } else { if (!PendingComdatExports[Symbol.getSectionNumber()]) return make_error("No pending COMDAT export for symbol " + @@ -557,8 +629,9 @@ COFFLinkGraphBuilder::exportCOMDATSymbol(COFFSymbolIndex SymIndex, dbgs() << " " << *Target << "\n"; }); PendingComdatExport = None; + DefinedSymbols[SymbolName] = Target; return Target; } } // namespace jitlink -} // namespace llvm +} // namespace llvm \ No newline at end of file diff --git a/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h b/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h index a571e5e6f4fe..1fd881c59558 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h +++ b/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h @@ -18,6 +18,7 @@ #include "llvm/ExecutionEngine/JITLink/JITLink.h" #include "llvm/Object/COFF.h" +#include "COFFDirectiveParser.h" #include "EHFrameSupportImpl.h" #include "JITLinkGeneric.h" @@ -133,6 +134,11 @@ private: Section &getCommonSection(); + Symbol *createExternalSymbol(COFFSymbolIndex SymIndex, StringRef SymbolName, + object::COFFSymbolRef Symbol, + const object::coff_section *Section); + Expected createAliasSymbol(StringRef SymbolName, Linkage L, Scope S, + Symbol &Target); Expected createDefinedSymbol(COFFSymbolIndex SymIndex, StringRef SymbolName, object::COFFSymbolRef Symbol, @@ -143,7 +149,10 @@ private: Expected exportCOMDATSymbol(COFFSymbolIndex SymIndex, StringRef SymbolName, object::COFFSymbolRef Symbol); + + Error handleDirectiveSection(StringRef Str); Error flushWeakAliasRequests(); + Error handleAlternateNames(); Error calculateImplicitSizeOfSymbols(); static uint64_t getSectionAddress(const object::COFFObjectFile &Obj, @@ -154,18 +163,22 @@ private: static unsigned getPointerSize(const object::COFFObjectFile &Obj); static support::endianness getEndianness(const object::COFFObjectFile &Obj); static StringRef getDLLImportStubPrefix() { return "__imp_"; } + static StringRef getDirectiveSectionName() { return ".drectve"; } StringRef getCOFFSectionName(COFFSectionIndex SectionIndex, const object::coff_section *Sec, object::COFFSymbolRef Sym); const object::COFFObjectFile &Obj; std::unique_ptr G; + COFFDirectiveParser DirectiveParser; Section *CommonSection = nullptr; std::vector GraphBlocks; std::vector GraphSymbols; + DenseMap AlternateNames; DenseMap ExternalSymbols; + DenseMap DefinedSymbols; }; template diff --git a/llvm/lib/ExecutionEngine/JITLink/COFFOptions.td b/llvm/lib/ExecutionEngine/JITLink/COFFOptions.td new file mode 100644 index 000000000000..0a0ce2fc76dd --- /dev/null +++ b/llvm/lib/ExecutionEngine/JITLink/COFFOptions.td @@ -0,0 +1,21 @@ +include "llvm/Option/OptParser.td" + +// link.exe accepts options starting with either a dash or a slash. + +// Flag that takes no arguments. +class F : Flag<["/", "-", "/?", "-?"], name>; + +// Flag that takes one argument after ":". +class P : + Joined<["/", "-", "/?", "-?"], name#":">; + +// Boolean flag which can be suffixed by ":no". Using it unsuffixed turns the +// flag on and using it suffixed by ":no" turns it off. +multiclass B_priv { + def "" : F; + def _no : F; +} + +def export : P<"export">; +def alternatename : P<"alternatename">; +def incl : Joined<["/", "-", "/?", "-?"], "include:">; \ No newline at end of file diff --git a/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_alternatename.s b/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_alternatename.s new file mode 100644 index 000000000000..024d066a5cec --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_alternatename.s @@ -0,0 +1,30 @@ +# RUN: llvm-mc -filetype=obj -triple=x86_64-windows-msvc %s -o %t +# RUN: llvm-jitlink -noexec %t +# +# Check object with alternatename directive does not fail, because +# foo external symbol was resolved to foo_def. +# + + .text + + .def foo_def; + .scl 2; + .type 32; + .endef + .globl foo_def + .p2align 4, 0x90 +foo_def: + retq + + .def main; + .scl 2; + .type 32; + .endef + .globl main + .p2align 4, 0x90 +main: + callq foo + retq + +.section .drectve,"yn" +.ascii "/alternatename:foo=foo_def" diff --git a/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_alternatename_fail.s b/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_alternatename_fail.s new file mode 100644 index 000000000000..d513263b279d --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_alternatename_fail.s @@ -0,0 +1,27 @@ +# RUN: llvm-mc -filetype=obj -triple=x86_64-windows-msvc %s -o %t +# RUN: not llvm-jitlink -noexec %t 2>&1 | FileCheck %s +# +# Check object without alternatename directive fails because of +# external symbol not found error. +# +# CHECK: error: Symbols not found: [ foo ] + .text + + .def foo_def; + .scl 2; + .type 32; + .endef + .globl foo_def + .p2align 4, 0x90 +foo_def: + retq + + .def main; + .scl 2; + .type 32; + .endef + .globl main + .p2align 4, 0x90 +main: + callq foo + retq diff --git a/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_include.s b/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_include.s new file mode 100644 index 000000000000..d3ddbf95e30c --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/X86/COFF_directive_include.s @@ -0,0 +1,21 @@ +# RUN: llvm-mc -filetype=obj -triple=x86_64-windows-msvc %s -o %t +# RUN: not llvm-jitlink -noexec %t 2>&1 | FileCheck %s +# +# Check an external symbol "foo" is generated and not dead-stripped +# because of include directive which turned into symbol not found error. +# +# CHECK: error: Symbols not found: [ foo ] + + .text + + .def main; + .scl 2; + .type 32; + .endef + .globl main + .p2align 4, 0x90 +main: + retq + + .section .drectve,"yn" + .ascii "/include:foo"