| //===- llvm-jitlink.cpp -- Command line interface/tester for llvm-jitlink -===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This utility provides a simple command line interface to the llvm jitlink | |
| // library, which makes relocatable object files executable in memory. Its | |
| // primary function is as a testing utility for the jitlink library. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm-jitlink.h" | |
| #include "ConnectionUtils.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/BinaryFormat/Magic.h" | |
| #include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX, LLVM_ENABLE_THREADS | |
| #include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h" | |
| #include "llvm/ExecutionEngine/Orc/BacktraceTools.h" | |
| #include "llvm/ExecutionEngine/Orc/COFFAutoImportGenerator.h" | |
| #include "llvm/ExecutionEngine/Orc/COFFPlatform.h" | |
| #include "llvm/ExecutionEngine/Orc/Debugging/DebugInfoSupport.h" | |
| #include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.h" | |
| #include "llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h" | |
| #include "llvm/ExecutionEngine/Orc/Debugging/PerfSupportPlugin.h" | |
| #include "llvm/ExecutionEngine/Orc/Debugging/VTuneSupportPlugin.h" | |
| #include "llvm/ExecutionEngine/Orc/EHFrameRegistrationPlugin.h" | |
| #include "llvm/ExecutionEngine/Orc/ELFNixPlatform.h" | |
| #include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" | |
| #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" | |
| #include "llvm/ExecutionEngine/Orc/IndirectionUtils.h" | |
| #include "llvm/ExecutionEngine/Orc/JITLinkRedirectableSymbolManager.h" | |
| #include "llvm/ExecutionEngine/Orc/JITLinkReentryTrampolines.h" | |
| #include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" | |
| #include "llvm/ExecutionEngine/Orc/LoadLinkableFile.h" | |
| #include "llvm/ExecutionEngine/Orc/MachO.h" | |
| #include "llvm/ExecutionEngine/Orc/MachOPlatform.h" | |
| #include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" | |
| #include "llvm/ExecutionEngine/Orc/ObjectFileInterface.h" | |
| #include "llvm/ExecutionEngine/Orc/SPSProxySpec.h" | |
| #include "llvm/ExecutionEngine/Orc/SectCreate.h" | |
| #include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h" | |
| #include "llvm/ExecutionEngine/Orc/Shared/ConnectionSpec.h" | |
| #include "llvm/ExecutionEngine/Orc/Shared/Mangler.h" | |
| #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" | |
| #include "llvm/ExecutionEngine/Orc/SharedMemoryMapSPS.h" | |
| #include "llvm/ExecutionEngine/Orc/SimpleMemoryMapSPS.h" | |
| #include "llvm/ExecutionEngine/Orc/SimpleRemoteMemoryMapper.h" | |
| #include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" | |
| #include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderPerf.h" | |
| #include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderVTune.h" | |
| #include "llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h" | |
| #include "llvm/ExecutionEngine/Orc/UnwindInfoRegistrationPlugin.h" | |
| #include "llvm/MC/MCAsmInfo.h" | |
| #include "llvm/MC/MCContext.h" | |
| #include "llvm/MC/MCDisassembler/MCDisassembler.h" | |
| #include "llvm/MC/MCInstPrinter.h" | |
| #include "llvm/MC/MCInstrAnalysis.h" | |
| #include "llvm/MC/MCInstrInfo.h" | |
| #include "llvm/MC/MCRegisterInfo.h" | |
| #include "llvm/MC/MCSubtargetInfo.h" | |
| #include "llvm/MC/MCTargetOptions.h" | |
| #include "llvm/MC/TargetRegistry.h" | |
| #include "llvm/Object/COFF.h" | |
| #include "llvm/Object/MachO.h" | |
| #include "llvm/Object/ObjectFile.h" | |
| #include "llvm/Object/TapiUniversal.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include "llvm/Support/Debug.h" | |
| #include "llvm/Support/ExponentialBackoff.h" | |
| #include "llvm/Support/InitLLVM.h" | |
| #include "llvm/Support/MemoryBuffer.h" | |
| #include "llvm/Support/Path.h" | |
| #include "llvm/Support/Process.h" | |
| #include "llvm/Support/TargetSelect.h" | |
| #include "llvm/Support/Timer.h" | |
| #include <chrono> | |
| #include <cstring> | |
| #include <deque> | |
| #include <string> | |
| #ifdef LLVM_ON_UNIX | |
| #include <fcntl.h> | |
| #include <sys/socket.h> | |
| #include <unistd.h> | |
| #endif // LLVM_ON_UNIX | |
| #define DEBUG_TYPE "llvm_jitlink" | |
| using namespace llvm; | |
| using namespace llvm::jitlink; | |
| using namespace llvm::orc; | |
| static cl::OptionCategory JITLinkCategory("JITLink Options"); | |
| static cl::list<std::string> InputFiles(cl::Positional, cl::desc("input files"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::list<bool> LazyLink("lazy", | |
| cl::desc("Link the following file lazily"), | |
| cl::cat(JITLinkCategory)); | |
| enum class SpeculateKind { None, Simple }; | |
| static cl::opt<SpeculateKind> Speculate( | |
| "speculate", cl::desc("Choose speculation scheme"), | |
| cl::init(SpeculateKind::None), | |
| cl::values(clEnumValN(SpeculateKind::None, "none", "No speculation"), | |
| clEnumValN(SpeculateKind::Simple, "simple", | |
| "Simple speculation")), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> SpeculateOrder( | |
| "speculate-order", | |
| cl::desc("A CSV file containing (JITDylib, Function) pairs to" | |
| "speculatively look up"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> RecordLazyExecs( | |
| "record-lazy-execs", | |
| cl::desc("Write lazy-function executions to a CSV file as (JITDylib, " | |
| "function) pairs"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<size_t> MaterializationThreads( | |
| "num-threads", cl::desc("Number of materialization threads to use"), | |
| cl::init(std::numeric_limits<size_t>::max()), cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| LibrarySearchPaths("L", | |
| cl::desc("Add dir to the list of library search paths"), | |
| cl::Prefix, cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| Libraries("l", | |
| cl::desc("Link against library X in the library search paths"), | |
| cl::Prefix, cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| LibrariesHidden("hidden-l", | |
| cl::desc("Link against library X in the library search " | |
| "paths with hidden visibility"), | |
| cl::Prefix, cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| LoadHidden("load_hidden", | |
| cl::desc("Link against library X with hidden visibility"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> | |
| WriteSymbolTableTo("write-symtab", | |
| cl::desc("Write the symbol table for the JIT'd program " | |
| "to the specified file"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> SymbolicateWith( | |
| "symbolicate-with", | |
| cl::desc("Given a path to a symbol table file, symbolicate the given " | |
| "backtrace(s)"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| LibrariesWeak("weak-l", | |
| cl::desc("Emulate weak link against library X. Must resolve " | |
| "to a TextAPI file, and all symbols in the " | |
| "interface will resolve to null."), | |
| cl::Prefix, cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> WeakLibraries( | |
| "weak_library", | |
| cl::desc("Emulate weak link against library X. X must point to a " | |
| "TextAPI file, and all symbols in the interface will " | |
| "resolve to null"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| LibrariesAuto("auto-l", | |
| cl::desc("Link against library X in the library search paths " | |
| "(auto-generate corresponding import library)"), | |
| cl::Prefix, cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> SearchSystemLibrary( | |
| "search-sys-lib", | |
| cl::desc("Add system library paths to library search paths"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> NoExec("noexec", cl::desc("Do not execute loaded code"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| CheckFiles("check", cl::desc("File containing verifier checks"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> | |
| CheckName("check-name", cl::desc("Name of checks to match against"), | |
| cl::init("jitlink-check"), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> | |
| ShowJITResult("show-jit-result", | |
| cl::desc("Print result of JIT'd main/entry to stdout"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> | |
| EntryPointName("entry", cl::desc("Symbol to call as main entry point"), | |
| cl::init(""), cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> JITDylibs( | |
| "jd", | |
| cl::desc("Specifies the JITDylib to be used for any subsequent " | |
| "input file, -L<seacrh-path>, and -l<library> arguments"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| Dylibs("preload", | |
| cl::desc("Pre-load dynamic libraries (e.g. language runtimes " | |
| "required by the ORC runtime)"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> InputArgv("args", cl::Positional, | |
| cl::desc("<program arguments>..."), | |
| cl::PositionalEatsArgs, | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> | |
| DebuggerSupport("debugger-support", | |
| cl::desc("Enable debugger suppport (default = !-noexec)"), | |
| cl::init(true), cl::Hidden, cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> PerfSupport("perf-support", | |
| cl::desc("Enable perf profiling support"), | |
| cl::init(false), cl::Hidden, | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> VTuneSupport("vtune-support", | |
| cl::desc("Enable vtune profiling support"), | |
| cl::init(false), cl::Hidden, | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> | |
| NoProcessSymbols("no-process-syms", | |
| cl::desc("Do not resolve to llvm-jitlink process symbols"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> AbsoluteDefs( | |
| "abs", | |
| cl::desc("Inject absolute symbol definitions (syntax: <name>=<addr>)"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| Aliases("alias", | |
| cl::desc("Inject symbol aliases (syntax: <alias-name>=<aliasee>)"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> | |
| SectCreate("sectcreate", | |
| cl::desc("given <sectname>,<filename>[@<sym>=<offset>,...] " | |
| "add the content of <filename> to <sectname>"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::list<std::string> TestHarnesses("harness", cl::Positional, | |
| cl::desc("Test harness files"), | |
| cl::PositionalEatsArgs, | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> | |
| ShowLinkedFiles("show-linked-files", | |
| cl::desc("List each file/graph name if/when it is linked"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> ShowInitialExecutionSessionState( | |
| "show-init-es", | |
| cl::desc("Print ExecutionSession state before resolving entry point"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> ShowEntryExecutionSessionState( | |
| "show-entry-es", | |
| cl::desc("Print ExecutionSession state after resolving entry point"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> ShowAddrs( | |
| "show-addrs", | |
| cl::desc("Print registered symbol, section, got and stub addresses"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> ShowLinkGraphs( | |
| "show-graphs", | |
| cl::desc("Takes a posix regex and prints the link graphs of all files " | |
| "matching that regex after fixups have been applied"), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> ShowTimes("show-times", | |
| cl::desc("Show times for llvm-jitlink phases"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> SlabAllocateSizeString( | |
| "slab-allocate", | |
| cl::desc("Allocate from a slab of the given size " | |
| "(allowable suffixes: Kb, Mb, Gb. default = " | |
| "Kb)"), | |
| cl::init(""), cl::cat(JITLinkCategory)); | |
| static cl::opt<uint64_t> SlabAddress( | |
| "slab-address", | |
| cl::desc("Set slab target address (requires -slab-allocate and -noexec)"), | |
| cl::init(~0ULL), cl::cat(JITLinkCategory)); | |
| static cl::opt<uint64_t> SlabPageSize( | |
| "slab-page-size", | |
| cl::desc("Set page size for slab (requires -slab-allocate and -noexec)"), | |
| cl::init(0), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> ShowRelocatedSectionContents( | |
| "show-relocated-section-contents", | |
| cl::desc("show section contents after fixups have been applied"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> PhonyExternals( | |
| "phony-externals", | |
| cl::desc("resolve all otherwise unresolved externals to null"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> OutOfProcessLaunch( | |
| "oop-launch", cl::desc("Launch an out-of-process executor to run code"), | |
| cl::ValueOptional, cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> OutOfProcessConnect( | |
| "oop-connect", cl::desc("How to connect to the out-of-process executor"), | |
| cl::cat(JITLinkCategory)); | |
| enum class PlatformKind { None, Native, MachO, ELFNix, COFF }; | |
| static cl::opt<PlatformKind> UsePlatform( | |
| "platform", cl::desc("Which ORC platform to use"), | |
| cl::init(PlatformKind::None), | |
| cl::values(clEnumValN(PlatformKind::None, "none", "no platform"), | |
| clEnumValN(PlatformKind::Native, "native", | |
| "Native platform for the executor"), | |
| clEnumValN(PlatformKind::MachO, "macho", "MachO platform"), | |
| clEnumValN(PlatformKind::ELFNix, "elfnix", "ELFNix platform"), | |
| clEnumValN(PlatformKind::COFF, "coff", "COFF platform")), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> | |
| OrcRuntime("orc-runtime", cl::desc("Use ORC runtime from given path"), | |
| cl::init(""), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> AddSelfRelocations( | |
| "add-self-relocations", | |
| cl::desc("Add relocations to function pointers to the current function"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> | |
| ShowErrFailedToMaterialize("show-err-failed-to-materialize", | |
| cl::desc("Show FailedToMaterialize errors"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| enum class MemMgr { Default, Generic, SimpleRemote, Shared }; | |
| static cl::opt<MemMgr> UseMemMgr( | |
| "use-memmgr", cl::desc("Choose memory manager"), cl::init(MemMgr::Generic), | |
| cl::values(clEnumValN(MemMgr::Default, "default", | |
| "Use setup default (InProcess or EPCGeneric)"), | |
| clEnumValN(MemMgr::Generic, "generic", | |
| "Generic remote memory manager"), | |
| clEnumValN(MemMgr::SimpleRemote, "simple-remote", | |
| "Mapper memory manager with simple-remote backend"), | |
| clEnumValN(MemMgr::Shared, "shared", | |
| "Mapper memory manager with shared-memory manager")), | |
| cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> | |
| OverrideTriple("triple", cl::desc("Override target triple detection"), | |
| cl::init(""), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> AllLoad("all_load", | |
| cl::desc("Load all members of static archives"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<bool> ForceLoadObjC( | |
| "ObjC", | |
| cl::desc("Load all members of static archives that implement " | |
| "Objective-C classes or categories, or Swift structs, " | |
| "classes or extensions"), | |
| cl::init(false), cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> WaitingOnGraphCapture( | |
| "waiting-on-graph-capture", | |
| cl::desc("Record WaitingOnGraph operations to the given file"), | |
| cl::init(""), cl::cat(JITLinkCategory)); | |
| static cl::opt<std::string> WaitingOnGraphReplay( | |
| "waiting-on-graph-replay", | |
| cl::desc("Replay WaitingOnGraph operations from the given file"), | |
| cl::init(""), cl::cat(JITLinkCategory)); | |
| static ExitOnError ExitOnErr; | |
| static LLVM_ATTRIBUTE_USED void linkComponents() { | |
| errs() << "Linking in runtime functions\n" | |
| << (void *)&llvm_orc_registerEHFrameSectionAllocAction << '\n' | |
| << (void *)&llvm_orc_deregisterEHFrameSectionAllocAction << '\n' | |
| << (void *)&llvm_orc_registerJITLoaderGDBAllocAction << '\n' | |
| << (void *)&llvm_orc_registerJITLoaderPerfStart << '\n' | |
| << (void *)&llvm_orc_registerJITLoaderPerfEnd << '\n' | |
| << (void *)&llvm_orc_registerJITLoaderPerfImpl << '\n' | |
| << (void *)&llvm_orc_registerVTuneImpl << '\n' | |
| << (void *)&llvm_orc_unregisterVTuneImpl << '\n' | |
| << (void *)&llvm_orc_test_registerVTuneImpl << '\n'; | |
| } | |
| static bool UseTestResultOverride = false; | |
| static int64_t TestResultOverride = 0; | |
| extern "C" LLVM_ATTRIBUTE_USED void | |
| llvm_jitlink_setTestResultOverride(int64_t Value) { | |
| TestResultOverride = Value; | |
| UseTestResultOverride = true; | |
| } | |
| static Error addSelfRelocations(LinkGraph &G); | |
| namespace { | |
| template <typename ErrT> class ConditionalPrintErr { | |
| public: | |
| ConditionalPrintErr(bool C) : C(C) {} | |
| void operator()(ErrT &EI) { | |
| if (C) { | |
| errs() << "llvm-jitlink error: "; | |
| EI.log(errs()); | |
| errs() << "\n"; | |
| } | |
| } | |
| private: | |
| bool C; | |
| }; | |
| Expected<std::unique_ptr<MemoryBuffer>> getFile(const Twine &FileName) { | |
| if (auto F = MemoryBuffer::getFile(FileName)) | |
| return std::move(*F); | |
| else | |
| return createFileError(FileName, F.getError()); | |
| } | |
| void reportLLVMJITLinkError(Error Err) { | |
| handleAllErrors( | |
| std::move(Err), | |
| ConditionalPrintErr<orc::FailedToMaterialize>(ShowErrFailedToMaterialize), | |
| ConditionalPrintErr<ErrorInfoBase>(true)); | |
| } | |
| } // end anonymous namespace | |
| namespace llvm { | |
| static raw_ostream & | |
| operator<<(raw_ostream &OS, const Session::MemoryRegionInfo &MRI) { | |
| OS << "target addr = " << format("0x%016" PRIx64, MRI.getTargetAddress()); | |
| if (MRI.isZeroFill()) | |
| OS << ", zero-fill: " << MRI.getZeroFillLength() << " bytes"; | |
| else | |
| OS << ", content: " << (const void *)MRI.getContent().data() << " -- " | |
| << (const void *)(MRI.getContent().data() + MRI.getContent().size()) | |
| << " (" << MRI.getContent().size() << " bytes)"; | |
| return OS; | |
| } | |
| static raw_ostream & | |
| operator<<(raw_ostream &OS, const Session::SymbolInfoMap &SIM) { | |
| OS << "Symbols:\n"; | |
| for (auto &SKV : SIM) | |
| OS << " \"" << SKV.first << "\" " << SKV.second << "\n"; | |
| return OS; | |
| } | |
| static raw_ostream & | |
| operator<<(raw_ostream &OS, const Session::FileInfo &FI) { | |
| for (auto &SIKV : FI.SectionInfos) | |
| OS << " Section \"" << SIKV.first() << "\": " << SIKV.second << "\n"; | |
| for (auto &GOTKV : FI.GOTEntryInfos) | |
| OS << " GOT \"" << GOTKV.first() << "\": " << GOTKV.second << "\n"; | |
| for (auto &StubKVs : FI.StubInfos) { | |
| OS << " Stubs \"" << StubKVs.first() << "\":"; | |
| for (auto MemRegion : StubKVs.second) | |
| OS << " " << MemRegion; | |
| OS << "\n"; | |
| } | |
| return OS; | |
| } | |
| static raw_ostream & | |
| operator<<(raw_ostream &OS, const Session::FileInfoMap &FIM) { | |
| for (auto &FIKV : FIM) | |
| OS << "File \"" << FIKV.first() << "\":\n" << FIKV.second; | |
| return OS; | |
| } | |
| bool lazyLinkingRequested() { | |
| for (auto LL : LazyLink) | |
| if (LL) | |
| return true; | |
| return false; | |
| } | |
| static Error applyLibraryLinkModifiers(Session &S, LinkGraph &G) { | |
| // If there are hidden archives and this graph is an archive | |
| // member then apply hidden modifier. | |
| if (!S.HiddenArchives.empty()) { | |
| StringRef ObjName(G.getName()); | |
| if (ObjName.ends_with(')')) { | |
| auto LibName = ObjName.split('[').first; | |
| if (S.HiddenArchives.count(LibName)) { | |
| for (auto *Sym : G.defined_symbols()) | |
| Sym->setScope(std::max(Sym->getScope(), Scope::Hidden)); | |
| } | |
| } | |
| } | |
| return Error::success(); | |
| } | |
| static Error applyHarnessPromotions(Session &S, LinkGraph &G) { | |
| std::lock_guard<std::mutex> Lock(S.M); | |
| // If this graph is part of the test harness there's nothing to do. | |
| if (S.HarnessFiles.empty() || S.HarnessFiles.count(G.getName())) | |
| return Error::success(); | |
| LLVM_DEBUG(dbgs() << "Applying promotions to graph " << G.getName() << "\n"); | |
| // If this graph is part of the test then promote any symbols referenced by | |
| // the harness to default scope, remove all symbols that clash with harness | |
| // definitions. | |
| std::vector<Symbol *> DefinitionsToRemove; | |
| for (auto *Sym : G.defined_symbols()) { | |
| if (!Sym->hasName()) | |
| continue; | |
| if (Sym->getLinkage() == Linkage::Weak) { | |
| auto It = S.CanonicalWeakDefs.find(*Sym->getName()); | |
| if (It == S.CanonicalWeakDefs.end() || It->second != G.getName()) { | |
| LLVM_DEBUG({ | |
| dbgs() << " Externalizing weak symbol " << Sym->getName() << "\n"; | |
| }); | |
| DefinitionsToRemove.push_back(Sym); | |
| } else { | |
| LLVM_DEBUG({ | |
| dbgs() << " Making weak symbol " << Sym->getName() << " strong\n"; | |
| }); | |
| if (S.HarnessExternals.count(*Sym->getName())) | |
| Sym->setScope(Scope::Default); | |
| else | |
| Sym->setScope(Scope::Hidden); | |
| Sym->setLinkage(Linkage::Strong); | |
| } | |
| } else if (S.HarnessExternals.count(*Sym->getName())) { | |
| LLVM_DEBUG(dbgs() << " Promoting " << Sym->getName() << "\n"); | |
| Sym->setScope(Scope::Default); | |
| Sym->setLive(true); | |
| continue; | |
| } else if (S.HarnessDefinitions.count(*Sym->getName())) { | |
| LLVM_DEBUG(dbgs() << " Externalizing " << Sym->getName() << "\n"); | |
| DefinitionsToRemove.push_back(Sym); | |
| } | |
| } | |
| for (auto *Sym : DefinitionsToRemove) | |
| G.makeExternal(*Sym); | |
| return Error::success(); | |
| } | |
| static void dumpSectionContents(raw_ostream &OS, Session &S, LinkGraph &G) { | |
| std::lock_guard<std::mutex> Lock(S.M); | |
| outs() << "Relocated section contents for " << G.getName() << ":\n"; | |
| constexpr orc::ExecutorAddrDiff DumpWidth = 16; | |
| static_assert(isPowerOf2_64(DumpWidth), "DumpWidth must be a power of two"); | |
| // Put sections in address order. | |
| std::vector<Section *> Sections; | |
| for (auto &S : G.sections()) | |
| Sections.push_back(&S); | |
| llvm::sort(Sections, [](const Section *LHS, const Section *RHS) { | |
| if (LHS->symbols().empty() && RHS->symbols().empty()) | |
| return false; | |
| if (LHS->symbols().empty()) | |
| return false; | |
| if (RHS->symbols().empty()) | |
| return true; | |
| SectionRange LHSRange(*LHS); | |
| SectionRange RHSRange(*RHS); | |
| return LHSRange.getStart() < RHSRange.getStart(); | |
| }); | |
| for (auto *S : Sections) { | |
| OS << S->getName() << " content:"; | |
| if (S->symbols().empty()) { | |
| OS << "\n section empty\n"; | |
| continue; | |
| } | |
| // Sort symbols into order, then render. | |
| std::vector<Symbol *> Syms(S->symbols().begin(), S->symbols().end()); | |
| llvm::sort(Syms, [](const Symbol *LHS, const Symbol *RHS) { | |
| return LHS->getAddress() < RHS->getAddress(); | |
| }); | |
| orc::ExecutorAddr NextAddr(Syms.front()->getAddress().getValue() & | |
| ~(DumpWidth - 1)); | |
| for (auto *Sym : Syms) { | |
| bool IsZeroFill = Sym->getBlock().isZeroFill(); | |
| auto SymStart = Sym->getAddress(); | |
| auto SymSize = Sym->getSize(); | |
| auto SymEnd = SymStart + SymSize; | |
| const uint8_t *SymData = IsZeroFill ? nullptr | |
| : reinterpret_cast<const uint8_t *>( | |
| Sym->getSymbolContent().data()); | |
| // Pad any space before the symbol starts. | |
| while (NextAddr != SymStart) { | |
| if (NextAddr % DumpWidth == 0) | |
| OS << formatv("\n{0:x16}:", NextAddr); | |
| OS << " "; | |
| ++NextAddr; | |
| } | |
| // Render the symbol content. | |
| while (NextAddr != SymEnd) { | |
| if (NextAddr % DumpWidth == 0) | |
| OS << formatv("\n{0:x16}:", NextAddr); | |
| if (IsZeroFill) | |
| OS << " 00"; | |
| else | |
| OS << formatv(" {0:x-2}", SymData[NextAddr - SymStart]); | |
| ++NextAddr; | |
| } | |
| } | |
| OS << "\n"; | |
| } | |
| } | |
| // A memory mapper with a fake offset applied only used for -noexec testing | |
| class InProcessDeltaMapper final : public InProcessMemoryMapper { | |
| public: | |
| InProcessDeltaMapper(size_t PageSize, uint64_t TargetAddr) | |
| : InProcessMemoryMapper(PageSize), TargetMapAddr(TargetAddr), | |
| DeltaAddr(0) {} | |
| static Expected<std::unique_ptr<InProcessDeltaMapper>> Create() { | |
| size_t PageSize = SlabPageSize; | |
| if (!PageSize) { | |
| if (auto PageSizeOrErr = sys::Process::getPageSize()) | |
| PageSize = *PageSizeOrErr; | |
| else | |
| return PageSizeOrErr.takeError(); | |
| } | |
| if (PageSize == 0) | |
| return make_error<StringError>("Page size is zero", | |
| inconvertibleErrorCode()); | |
| return std::make_unique<InProcessDeltaMapper>(PageSize, SlabAddress); | |
| } | |
| void reserve(size_t NumBytes, OnReservedFunction OnReserved) override { | |
| InProcessMemoryMapper::reserve( | |
| NumBytes, [this, OnReserved = std::move(OnReserved)]( | |
| Expected<ExecutorAddrRange> Result) mutable { | |
| if (!Result) | |
| return OnReserved(Result.takeError()); | |
| assert(DeltaAddr == 0 && "Overwriting previous offset"); | |
| if (TargetMapAddr != ~0ULL) | |
| DeltaAddr = TargetMapAddr - Result->Start.getValue(); | |
| auto OffsetRange = ExecutorAddrRange(Result->Start + DeltaAddr, | |
| Result->End + DeltaAddr); | |
| OnReserved(OffsetRange); | |
| }); | |
| } | |
| char *prepare(jitlink::LinkGraph &G, ExecutorAddr Addr, | |
| size_t ContentSize) override { | |
| return InProcessMemoryMapper::prepare(G, Addr - DeltaAddr, ContentSize); | |
| } | |
| void initialize(AllocInfo &AI, OnInitializedFunction OnInitialized) override { | |
| // Slide mapping based on delta, make all segments read-writable, and | |
| // discard allocation actions. | |
| auto FixedAI = std::move(AI); | |
| FixedAI.MappingBase -= DeltaAddr; | |
| for (auto &Seg : FixedAI.Segments) | |
| Seg.AG = {MemProt::Read | MemProt::Write, Seg.AG.getMemLifetime()}; | |
| FixedAI.Actions.clear(); | |
| InProcessMemoryMapper::initialize( | |
| FixedAI, [this, OnInitialized = std::move(OnInitialized)]( | |
| Expected<ExecutorAddr> Result) mutable { | |
| if (!Result) | |
| return OnInitialized(Result.takeError()); | |
| OnInitialized(ExecutorAddr(Result->getValue() + DeltaAddr)); | |
| }); | |
| } | |
| void deinitialize(ArrayRef<ExecutorAddr> Allocations, | |
| OnDeinitializedFunction OnDeInitialized) override { | |
| std::vector<ExecutorAddr> Addrs(Allocations.size()); | |
| for (const auto Base : Allocations) { | |
| Addrs.push_back(Base - DeltaAddr); | |
| } | |
| InProcessMemoryMapper::deinitialize(Addrs, std::move(OnDeInitialized)); | |
| } | |
| void release(ArrayRef<ExecutorAddr> Reservations, | |
| OnReleasedFunction OnRelease) override { | |
| std::vector<ExecutorAddr> Addrs(Reservations.size()); | |
| for (const auto Base : Reservations) { | |
| Addrs.push_back(Base - DeltaAddr); | |
| } | |
| InProcessMemoryMapper::release(Addrs, std::move(OnRelease)); | |
| } | |
| private: | |
| uint64_t TargetMapAddr; | |
| uint64_t DeltaAddr; | |
| }; | |
| Expected<uint64_t> getSlabAllocSize(StringRef SizeString) { | |
| SizeString = SizeString.trim(); | |
| uint64_t Units = 1024; | |
| if (SizeString.ends_with_insensitive("kb")) | |
| SizeString = SizeString.drop_back(2).rtrim(); | |
| else if (SizeString.ends_with_insensitive("mb")) { | |
| Units = 1024 * 1024; | |
| SizeString = SizeString.drop_back(2).rtrim(); | |
| } else if (SizeString.ends_with_insensitive("gb")) { | |
| Units = 1024 * 1024 * 1024; | |
| SizeString = SizeString.drop_back(2).rtrim(); | |
| } | |
| uint64_t SlabSize = 0; | |
| if (SizeString.getAsInteger(10, SlabSize)) | |
| return make_error<StringError>("Invalid numeric format for slab size", | |
| inconvertibleErrorCode()); | |
| return SlabSize * Units; | |
| } | |
| static std::unique_ptr<JITLinkMemoryManager> createInProcessMemoryManager() { | |
| uint64_t SlabSize; | |
| #ifdef _WIN32 | |
| SlabSize = 1024 * 1024; | |
| #else | |
| SlabSize = 1024 * 1024 * 1024; | |
| #endif | |
| if (!SlabAllocateSizeString.empty()) | |
| SlabSize = ExitOnErr(getSlabAllocSize(SlabAllocateSizeString)); | |
| // If this is a -no-exec case and we're tweaking the slab address or size then | |
| // use the delta mapper. | |
| if (NoExec && (SlabAddress || SlabPageSize)) | |
| return ExitOnErr( | |
| MapperJITLinkMemoryManager::CreateWithMapper<InProcessDeltaMapper>( | |
| SlabSize)); | |
| // Otherwise use the standard in-process mapper. | |
| return ExitOnErr( | |
| MapperJITLinkMemoryManager::CreateWithMapper<InProcessMemoryMapper>( | |
| SlabSize)); | |
| } | |
| Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>> | |
| createSimpleRemoteMemoryManager(ExecutorProcessControl &EPC) { | |
| auto &ES = EPC.getExecutionSession(); | |
| auto B = sps::createSimpleMemoryMapBindings(ES); | |
| if (!B) | |
| return B.takeError(); | |
| #ifdef _WIN32 | |
| size_t SlabSize = 1024 * 1024; | |
| #else | |
| size_t SlabSize = 1024 * 1024 * 1024; | |
| #endif | |
| return MapperJITLinkMemoryManager::CreateWithMapper<SimpleRemoteMemoryMapper>( | |
| SlabSize, ES, std::move(*B)); | |
| } | |
| Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>> | |
| createSharedMemoryManager(ExecutorProcessControl &EPC) { | |
| auto &ES = EPC.getExecutionSession(); | |
| auto B = sps::createSharedMemoryMapBindings(ES); | |
| if (!B) | |
| return B.takeError(); | |
| #ifdef _WIN32 | |
| size_t SlabSize = 1024 * 1024; | |
| #else | |
| size_t SlabSize = 1024 * 1024 * 1024; | |
| #endif | |
| if (!SlabAllocateSizeString.empty()) | |
| SlabSize = ExitOnErr(getSlabAllocSize(SlabAllocateSizeString)); | |
| return MapperJITLinkMemoryManager::CreateWithMapper<SharedMemoryMapper>( | |
| SlabSize, ES, std::move(*B)); | |
| } | |
| static Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>> | |
| createMemoryManager(ExecutorProcessControl &EPC) { | |
| if (OutOfProcessLaunch.getNumOccurrences() || | |
| OutOfProcessConnect.getNumOccurrences()) { | |
| switch (UseMemMgr) { | |
| case MemMgr::Default: | |
| case MemMgr::Generic: | |
| return EPC.createDefaultMemoryManager(); | |
| case MemMgr::SimpleRemote: | |
| return createSimpleRemoteMemoryManager(EPC); | |
| case MemMgr::Shared: | |
| return createSharedMemoryManager(EPC); | |
| } | |
| } | |
| return createInProcessMemoryManager(); | |
| } | |
| static Expected<MaterializationUnit::Interface> | |
| getTestObjectFileInterface(Session &S, MemoryBufferRef O) { | |
| // Get the standard interface for this object, but ignore the symbols field. | |
| // We'll handle that manually to include promotion. | |
| auto I = getObjectFileInterface(S.ES, O); | |
| if (!I) | |
| return I.takeError(); | |
| I->SymbolFlags.clear(); | |
| // If creating an object file was going to fail it would have happened above, | |
| // so we can 'cantFail' this. | |
| auto Obj = cantFail(object::ObjectFile::createObjectFile(O)); | |
| // The init symbol must be included in the SymbolFlags map if present. | |
| if (I->InitSymbol) | |
| I->SymbolFlags[I->InitSymbol] = | |
| JITSymbolFlags::MaterializationSideEffectsOnly; | |
| for (auto &Sym : Obj->symbols()) { | |
| Expected<uint32_t> SymFlagsOrErr = Sym.getFlags(); | |
| if (!SymFlagsOrErr) | |
| // TODO: Test this error. | |
| return SymFlagsOrErr.takeError(); | |
| // Skip symbols not defined in this object file. | |
| if ((*SymFlagsOrErr & object::BasicSymbolRef::SF_Undefined)) | |
| continue; | |
| auto Name = Sym.getName(); | |
| if (!Name) | |
| return Name.takeError(); | |
| // Skip symbols that have type SF_File. | |
| if (auto SymType = Sym.getType()) { | |
| if (*SymType == object::SymbolRef::ST_File) | |
| continue; | |
| } else | |
| return SymType.takeError(); | |
| auto SymFlags = JITSymbolFlags::fromObjectSymbol(Sym); | |
| if (!SymFlags) | |
| return SymFlags.takeError(); | |
| if (SymFlags->isWeak()) { | |
| // If this is a weak symbol that's not defined in the harness then we | |
| // need to either mark it as strong (if this is the first definition | |
| // that we've seen) or discard it. | |
| if (S.HarnessDefinitions.count(*Name) || S.CanonicalWeakDefs.count(*Name)) | |
| continue; | |
| S.CanonicalWeakDefs[*Name] = O.getBufferIdentifier(); | |
| *SymFlags &= ~JITSymbolFlags::Weak; | |
| if (!S.HarnessExternals.count(*Name)) | |
| *SymFlags &= ~JITSymbolFlags::Exported; | |
| } else if (S.HarnessExternals.count(*Name)) { | |
| *SymFlags |= JITSymbolFlags::Exported; | |
| } else if (S.HarnessDefinitions.count(*Name) || | |
| !(*SymFlagsOrErr & object::BasicSymbolRef::SF_Global)) | |
| continue; | |
| I->SymbolFlags[S.ES.intern(*Name)] = std::move(*SymFlags); | |
| } | |
| return I; | |
| } | |
| static Error loadProcessSymbols(Session &S) { | |
| S.ProcessSymsJD = &S.ES.createBareJITDylib("Process"); | |
| auto FilterMainEntryPoint = | |
| [EPName = S.ES.intern(EntryPointName)](SymbolStringPtr Name) { | |
| return Name != EPName; | |
| }; | |
| S.ProcessSymsJD->addGenerator( | |
| ExitOnErr(orc::EPCDynamicLibrarySearchGenerator::GetForTargetProcess( | |
| S.ES, *S.DylibMgr, std::move(FilterMainEntryPoint)))); | |
| return Error::success(); | |
| } | |
| static Error loadDylibs(Session &S) { | |
| LLVM_DEBUG(dbgs() << "Loading dylibs...\n"); | |
| for (const auto &Dylib : Dylibs) { | |
| LLVM_DEBUG(dbgs() << " " << Dylib << "\n"); | |
| auto DL = S.getOrLoadDynamicLibrary(Dylib); | |
| if (!DL) | |
| return DL.takeError(); | |
| } | |
| return Error::success(); | |
| } | |
| // maybe_unused because some configurations, e.g. LLVM_ENABLE_THREADS=Off, | |
| // eliminate all callsites. | |
| [[maybe_unused]] static Error launchExecutor(std::vector<std::string> Args) { | |
| #ifndef LLVM_ON_UNIX | |
| // FIXME: Add out-of-process support for Windows. | |
| return make_error<StringError>("-" + OutOfProcessLaunch.ArgStr + | |
| " not supported on non-unix platforms", | |
| inconvertibleErrorCode()); | |
| #else | |
| std::string ProgName = OutOfProcessLaunch; | |
| std::vector<char *> ExecvpArgs; | |
| ExecvpArgs.push_back(ProgName.data()); | |
| for (auto &Arg : Args) | |
| ExecvpArgs.push_back(Arg.data()); | |
| ExecvpArgs.push_back(nullptr); | |
| pid_t ChildPID; | |
| ChildPID = fork(); | |
| if (ChildPID == 0) { | |
| int RC = execvp(OutOfProcessLaunch.data(), ExecvpArgs.data()); | |
| if (RC != 0) { | |
| errs() << "Could not launch out-of-process executor " << ProgName << ": " | |
| << strerror(errno) << "\n"; | |
| exit(1); | |
| } | |
| } | |
| return Error::success(); | |
| #endif // LLVM_ON_UNIX | |
| } | |
| static Expected<std::unique_ptr<ExecutorProcessControl>> | |
| launchExecutorWithDefaultConnect() { | |
| #ifndef LLVM_ON_UNIX | |
| // FIXME: Add out-of-process support for Windows. | |
| return make_error<StringError>("-" + OutOfProcessLaunch.ArgStr + | |
| " not supported on non-unix platforms", | |
| inconvertibleErrorCode()); | |
| #elif !LLVM_ENABLE_THREADS | |
| // Out of process mode using SimpleRemoteEPC depends on threads. | |
| return make_error<StringError>( | |
| "-" + OutOfProcessLaunch.ArgStr + | |
| " requires threads, but LLVM was built with " | |
| "LLVM_ENABLE_THREADS=Off", | |
| inconvertibleErrorCode()); | |
| #else | |
| constexpr int ParentSocket = 0, ChildSocket = 1; | |
| int Sockets[2]; | |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, Sockets) == -1) | |
| return make_error<StringError>( | |
| Twine("Unable to create socket for executor: ") + strerror(errno), | |
| inconvertibleErrorCode()); | |
| { | |
| auto CloseChildSocket = scope_exit([&]() { close(Sockets[ChildSocket]); }); | |
| auto CloseParentSocket = | |
| scope_exit([&]() { close(Sockets[ParentSocket]); }); | |
| { | |
| // Set CLOEXEC on the parent socket. | |
| int Flags = fcntl(Sockets[ParentSocket], F_GETFD); | |
| if (Flags == -1) | |
| return make_error<StringError>( | |
| StringRef("Could not get flags for parent socket: ") + | |
| strerror(errno), | |
| inconvertibleErrorCode()); | |
| Flags |= FD_CLOEXEC; | |
| if (fcntl(Sockets[ParentSocket], F_SETFD, Flags) == -1) | |
| return make_error<StringError>( | |
| StringRef("Could not set flags for parent socket: ") + | |
| strerror(errno), | |
| inconvertibleErrorCode()); | |
| } | |
| std::string ConnSpec = "socket:adopt="; | |
| ConnSpec += std::to_string(Sockets[ChildSocket]); | |
| if (auto Err = launchExecutor({ConnSpec})) | |
| return std::move(Err); | |
| // Success. Hold on to the parent socket, let child socket close. | |
| CloseParentSocket.release(); | |
| } | |
| return SimpleRemoteEPC::Create<FDSimpleRemoteEPCTransport>( | |
| std::make_unique<DynamicThreadPoolTaskDispatcher>(MaterializationThreads), | |
| Sockets[ParentSocket], Sockets[ParentSocket]); | |
| #endif // LLVM_ON_UNIX | |
| } | |
| #if LLVM_ON_UNIX && LLVM_ENABLE_THREADS | |
| static Error createTCPSocketError(Twine Details) { | |
| return make_error<StringError>( | |
| formatv("Failed to set up TCP socket for '{0}': {1}", OutOfProcessConnect, | |
| Details), | |
| inconvertibleErrorCode()); | |
| } | |
| #endif | |
| static Expected<std::unique_ptr<ExecutorProcessControl>> | |
| connectToExecutorWithTCPConnect(const ConnectionSpec &CS) { | |
| #ifndef LLVM_ON_UNIX | |
| // FIXME: Add TCP support for Windows. | |
| return make_error<StringError>("-" + OutOfProcessConnect.ArgStr + | |
| " not supported on non-unix platforms", | |
| inconvertibleErrorCode()); | |
| #elif !LLVM_ENABLE_THREADS | |
| // Out of process mode using SimpleRemoteEPC depends on threads. | |
| return make_error<StringError>( | |
| "-" + OutOfProcessConnect.ArgStr + | |
| " requires threads, but LLVM was built with " | |
| "LLVM_ENABLE_THREADS=Off", | |
| inconvertibleErrorCode()); | |
| #else | |
| StringRef Host, PortStr; | |
| std::tie(Host, PortStr) = StringRef(CS.getDescriptor()).split(':'); | |
| if (Host.empty()) | |
| return createTCPSocketError("Host name for -" + OutOfProcessConnect.ArgStr + | |
| " can not be empty"); | |
| if (PortStr.empty()) | |
| return createTCPSocketError( | |
| "Port number in -" + OutOfProcessConnect.ArgStr + " can not be empty"); | |
| int Port = 0; | |
| if (PortStr.getAsInteger(10, Port)) | |
| return createTCPSocketError("Port number '" + PortStr + | |
| "' is not a valid integer"); | |
| std::unique_ptr<ExponentialBackoff> Retry; | |
| if (OutOfProcessLaunch.getNumOccurrences()) { | |
| // Launch the executor and attempt to connect with exponential backoff. | |
| if (auto Err = | |
| launchExecutor({{("tcp:listen=" + Host + ":" + PortStr).str()}})) | |
| return std::move(Err); | |
| using namespace std::chrono_literals; | |
| Retry = std::make_unique<ExponentialBackoff>(10s); | |
| } | |
| auto SockFD = connectWithRetry( | |
| [&]() { return connectTCPSocket(Host, PortStr); }, std::move(Retry)); | |
| if (!SockFD) | |
| return createTCPSocketError(toString(SockFD.takeError())); | |
| return SimpleRemoteEPC::Create<FDSimpleRemoteEPCTransport>( | |
| std::make_unique<DynamicThreadPoolTaskDispatcher>(MaterializationThreads), | |
| *SockFD, *SockFD); | |
| #endif | |
| } | |
| static Expected<std::unique_ptr<ExecutorProcessControl>> | |
| connectToExecutorWithTCPListen(const ConnectionSpec &CS) { | |
| #ifndef LLVM_ON_UNIX | |
| // FIXME: Add TCP support for Windows. | |
| return make_error<StringError>("-" + OutOfProcessConnect.ArgStr + | |
| " not supported on non-unix platforms", | |
| inconvertibleErrorCode()); | |
| #elif !LLVM_ENABLE_THREADS | |
| // Out of process mode using SimpleRemoteEPC depends on threads. | |
| return make_error<StringError>( | |
| "-" + OutOfProcessConnect.ArgStr + | |
| " requires threads, but LLVM was built with " | |
| "LLVM_ENABLE_THREADS=Off", | |
| inconvertibleErrorCode()); | |
| #else | |
| StringRef Host, PortStr; | |
| std::tie(Host, PortStr) = StringRef(CS.getDescriptor()).split(':'); | |
| if (PortStr.empty()) | |
| return createTCPSocketError( | |
| "Port number in -" + OutOfProcessConnect.ArgStr + " can not be empty"); | |
| int Port = 0; | |
| if (PortStr.getAsInteger(10, Port)) | |
| return createTCPSocketError("Port number '" + PortStr + | |
| "' is not a valid integer"); | |
| // Bind and listen before launching the executor. | |
| std::string ResolvedPortStr; | |
| auto ListenFD = listenTCPSocket(Host, PortStr, &ResolvedPortStr); | |
| if (!ListenFD) | |
| return createTCPSocketError(toString(ListenFD.takeError())); | |
| if (OutOfProcessLaunch.getNumOccurrences()) { | |
| // An empty host means we bound the wildcard address; loopback is always | |
| // reachable on that, so use it as the address the executor dials back | |
| // into. | |
| StringRef DialBackHost = Host.empty() ? StringRef("127.0.0.1") : Host; | |
| if (auto Err = launchExecutor( | |
| {("tcp:connect=" + DialBackHost + ":" + ResolvedPortStr).str()})) | |
| return std::move(Err); | |
| } | |
| auto SockFD = acceptTCPConnection(*ListenFD); | |
| if (!SockFD) | |
| return createTCPSocketError(toString(SockFD.takeError())); | |
| return SimpleRemoteEPC::Create<FDSimpleRemoteEPCTransport>( | |
| std::make_unique<DynamicThreadPoolTaskDispatcher>(MaterializationThreads), | |
| *SockFD, *SockFD); | |
| #endif | |
| } | |
| static Expected<std::unique_ptr<ExecutorProcessControl>> | |
| connectToExecutorWithTCP(const ConnectionSpec &CS) { | |
| if (CS.getAction() == "connect") | |
| return connectToExecutorWithTCPConnect(CS); | |
| if (CS.getAction() == "listen") | |
| return connectToExecutorWithTCPListen(CS); | |
| return make_error<StringError>(CS.getAction() + | |
| " is not a recognized action for tcp", | |
| inconvertibleErrorCode()); | |
| } | |
| static Expected<std::unique_ptr<ExecutorProcessControl>> | |
| connectToExecutor(const ConnectionSpec &CS) { | |
| if (CS.getTransport() == "tcp") | |
| return connectToExecutorWithTCP(CS); | |
| return make_error<StringError>(CS.getTransport() + | |
| " is not a recognized transport", | |
| inconvertibleErrorCode()); | |
| } | |
| static Expected<std::unique_ptr<ExecutorProcessControl>> | |
| setupOutOfProcessExecutor() { | |
| if (!OutOfProcessConnect.getNumOccurrences()) | |
| return launchExecutorWithDefaultConnect(); | |
| auto ConnSpec = ConnectionSpec::parse(OutOfProcessConnect); | |
| if (!ConnSpec) | |
| return ConnSpec.takeError(); | |
| return connectToExecutor(*ConnSpec); | |
| } | |
| static Expected<std::unique_ptr<ExecutorProcessControl>> | |
| createInProcessExecutorEPC(Triple TT) { | |
| /// Otherwise use SelfExecutorProcessControl to target the current process. | |
| auto PageSize = sys::Process::getPageSize(); | |
| if (!PageSize) | |
| return PageSize.takeError(); | |
| std::unique_ptr<TaskDispatcher> Dispatcher; | |
| if (MaterializationThreads == 0) | |
| Dispatcher = std::make_unique<InPlaceTaskDispatcher>(); | |
| else { | |
| #if LLVM_ENABLE_THREADS | |
| Dispatcher = std::make_unique<DynamicThreadPoolTaskDispatcher>( | |
| MaterializationThreads); | |
| #else | |
| llvm_unreachable("MaterializationThreads should be 0"); | |
| #endif | |
| } | |
| return std::make_unique<SelfExecutorProcessControl>( | |
| std::make_shared<SymbolStringPool>(), std::move(Dispatcher), | |
| std::move(TT), *PageSize); | |
| } | |
| static Expected<std::unique_ptr<ExecutorProcessControl>> | |
| createExecutorProcessControl(Triple TT) { | |
| if (OutOfProcessLaunch.getNumOccurrences() || | |
| OutOfProcessConnect.getNumOccurrences()) | |
| return setupOutOfProcessExecutor(); | |
| else | |
| return createInProcessExecutorEPC(TT); | |
| } | |
| class PhonyExternalsGenerator : public DefinitionGenerator { | |
| public: | |
| Error tryToGenerate(LookupState &LS, LookupKind K, JITDylib &JD, | |
| JITDylibLookupFlags JDLookupFlags, | |
| const SymbolLookupSet &LookupSet) override { | |
| SymbolMap PhonySymbols; | |
| for (auto &KV : LookupSet) | |
| PhonySymbols[KV.first] = {ExecutorAddr(), JITSymbolFlags::Exported}; | |
| return JD.define(absoluteSymbols(std::move(PhonySymbols))); | |
| } | |
| }; | |
| Expected<std::unique_ptr<Session::LazyLinkingSupport>> | |
| createLazyLinkingSupport(Session &S) { | |
| auto MemAccess = S.ES.getExecutorProcessControl().createDefaultMemoryAccess(); | |
| if (!MemAccess) | |
| return MemAccess.takeError(); | |
| auto RSMgr = | |
| JITLinkRedirectableSymbolManager::Create(*S.ObjLayer, **MemAccess); | |
| if (!RSMgr) | |
| return RSMgr.takeError(); | |
| std::shared_ptr<SimpleLazyReexportsSpeculator> Speculator; | |
| switch (Speculate) { | |
| case SpeculateKind::None: | |
| break; | |
| case SpeculateKind::Simple: | |
| SimpleLazyReexportsSpeculator::RecordExecutionFunction RecordExecs; | |
| if (!RecordLazyExecs.empty()) | |
| RecordExecs = [&S](const LazyReexportsManager::CallThroughInfo &CTI) { | |
| S.LazyFnExecOrder.push_back({CTI.JD->getName(), CTI.BodyName}); | |
| }; | |
| Speculator = | |
| SimpleLazyReexportsSpeculator::Create(S.ES, std::move(RecordExecs)); | |
| break; | |
| } | |
| auto LRMgr = createJITLinkLazyReexportsManager( | |
| *S.ObjLayer, **RSMgr, *S.PlatformJD, Speculator.get()); | |
| if (!LRMgr) | |
| return LRMgr.takeError(); | |
| return std::make_unique<Session::LazyLinkingSupport>( | |
| std::move(*MemAccess), std::move(*RSMgr), std::move(Speculator), | |
| std::move(*LRMgr), *S.ObjLayer); | |
| } | |
| static Error writeLazyExecOrder(Session &S) { | |
| if (RecordLazyExecs.empty()) | |
| return Error::success(); | |
| std::error_code EC; | |
| raw_fd_ostream ExecOrderOut(RecordLazyExecs, EC); | |
| if (EC) | |
| return createFileError(RecordLazyExecs, EC); | |
| for (auto &[JDName, FunctionName] : S.LazyFnExecOrder) | |
| ExecOrderOut << JDName << ", " << FunctionName << "\n"; | |
| return Error::success(); | |
| } | |
| Expected<std::unique_ptr<Session>> Session::Create(Triple TT, | |
| SubtargetFeatures Features) { | |
| auto EPC = createExecutorProcessControl(TT); | |
| if (!EPC) | |
| return EPC.takeError(); | |
| Error Err = Error::success(); | |
| std::unique_ptr<Session> S(new Session(std::move(*EPC), Err)); | |
| if (Err) | |
| return std::move(Err); | |
| S->Features = std::move(Features); | |
| if (lazyLinkingRequested()) { | |
| if (auto LazyLinking = createLazyLinkingSupport(*S)) | |
| S->LazyLinking = std::move(*LazyLinking); | |
| else | |
| return LazyLinking.takeError(); | |
| } | |
| return std::move(S); | |
| } | |
| Session::~Session() { | |
| if (auto Err = writeLazyExecOrder(*this)) | |
| ES.reportError(std::move(Err)); | |
| if (auto Err = ES.endSession()) | |
| ES.reportError(std::move(Err)); | |
| } | |
| Session::Session(std::unique_ptr<ExecutorProcessControl> EPC, Error &Err) | |
| : ES(std::move(EPC)) { | |
| /// Local ObjectLinkingLayer::Plugin class to forward modifyPassConfig to the | |
| /// Session. | |
| class JITLinkSessionPlugin : public ObjectLinkingLayer::Plugin { | |
| public: | |
| JITLinkSessionPlugin(Session &S) : S(S) {} | |
| void modifyPassConfig(MaterializationResponsibility &MR, LinkGraph &G, | |
| PassConfiguration &PassConfig) override { | |
| S.modifyPassConfig(G, PassConfig); | |
| } | |
| Error notifyFailed(MaterializationResponsibility &MR) override { | |
| return Error::success(); | |
| } | |
| Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { | |
| return Error::success(); | |
| } | |
| void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, | |
| ResourceKey SrcKey) override {} | |
| private: | |
| Session &S; | |
| }; | |
| ErrorAsOutParameter _(&Err); | |
| if (auto MM = createMemoryManager(ES.getExecutorProcessControl())) { | |
| MemoryMgr = std::move(*MM); | |
| ObjLayer = std::make_unique<orc::ObjectLinkingLayer>(ES, *MemoryMgr); | |
| } else { | |
| Err = MM.takeError(); | |
| return; | |
| } | |
| if (auto DM = ES.getExecutorProcessControl().createDefaultDylibMgr()) | |
| DylibMgr = std::move(*DM); | |
| else { | |
| Err = DM.takeError(); | |
| return; | |
| } | |
| ES.setErrorReporter(reportLLVMJITLinkError); | |
| // Attach WaitingOnGraph recorder if requested. | |
| if (!WaitingOnGraphCapture.empty()) { | |
| if (auto GRecorderOrErr = | |
| WaitingOnGraphOpRecorder::Create(WaitingOnGraphCapture)) { | |
| GOpRecorder = std::move(*GRecorderOrErr); | |
| ES.setWaitingOnGraphOpRecorder(*GOpRecorder); | |
| } else { | |
| Err = GRecorderOrErr.takeError(); | |
| return; | |
| } | |
| } | |
| if (!NoProcessSymbols) | |
| ExitOnErr(loadProcessSymbols(*this)); | |
| ExitOnErr(loadDylibs(*this)); | |
| auto &TT = ES.getTargetTriple(); | |
| if (!WriteSymbolTableTo.empty()) { | |
| if (auto STDump = SymbolTableDumpPlugin::Create(WriteSymbolTableTo)) | |
| ObjLayer->addPlugin(std::move(*STDump)); | |
| else { | |
| Err = STDump.takeError(); | |
| return; | |
| } | |
| } | |
| if (DebuggerSupport && TT.isOSBinFormatMachO()) { | |
| ObjLayer->addPlugin(ExitOnErr(GDBJITDebugInfoRegistrationPlugin::Create( | |
| this->ES, this->ES.getBootstrapJITDylib()))); | |
| } | |
| if (PerfSupport && TT.isOSBinFormatELF()) { | |
| if (!ProcessSymsJD) { | |
| Err = make_error<StringError>("MachO debugging requires process symbols", | |
| inconvertibleErrorCode()); | |
| return; | |
| } | |
| ObjLayer->addPlugin(ExitOnErr(DebugInfoPreservationPlugin::Create())); | |
| ObjLayer->addPlugin(ExitOnErr(PerfSupportPlugin::Create( | |
| this->ES.getExecutorProcessControl(), *ProcessSymsJD, true, true))); | |
| } | |
| if (VTuneSupport && TT.isOSBinFormatELF()) { | |
| ObjLayer->addPlugin(ExitOnErr(DebugInfoPreservationPlugin::Create())); | |
| ObjLayer->addPlugin(ExitOnErr( | |
| VTuneSupportPlugin::Create(this->ES.getExecutorProcessControl(), | |
| *ProcessSymsJD, /*EmitDebugInfo=*/true, | |
| /*TestMode=*/true))); | |
| } | |
| // Set up the platform. | |
| if (!OrcRuntime.empty() && UsePlatform != PlatformKind::None) { | |
| assert(ProcessSymsJD && "ProcessSymsJD should have been set"); | |
| PlatformJD = &ES.createBareJITDylib("Platform"); | |
| PlatformJD->addToLinkOrder(*ProcessSymsJD); | |
| // If the '-platform' option PlatformKind::Native then detect the | |
| // platform to use. | |
| if (UsePlatform == PlatformKind::Native) { | |
| if (TT.isOSBinFormatMachO()) | |
| UsePlatform = PlatformKind::MachO; | |
| else if (TT.isOSBinFormatELF()) | |
| UsePlatform = PlatformKind::ELFNix; | |
| else if (TT.isOSBinFormatCOFF()) | |
| UsePlatform = PlatformKind::COFF; | |
| else { | |
| Err = make_error<StringError>( | |
| "-" + OrcRuntime.ArgStr + " specified, but format " + | |
| Triple::getObjectFormatTypeName(TT.getObjectFormat()) + | |
| " not supported", | |
| inconvertibleErrorCode()); | |
| return; | |
| } | |
| } | |
| switch (UsePlatform) { | |
| case PlatformKind::MachO: | |
| if (auto P = | |
| MachOPlatform::Create(*ObjLayer, *PlatformJD, OrcRuntime.c_str())) | |
| ES.setPlatform(std::move(*P)); | |
| else { | |
| Err = P.takeError(); | |
| return; | |
| } | |
| break; | |
| case PlatformKind::ELFNix: | |
| if (auto P = ELFNixPlatform::Create(*ObjLayer, *PlatformJD, | |
| OrcRuntime.c_str())) | |
| ES.setPlatform(std::move(*P)); | |
| else { | |
| Err = P.takeError(); | |
| return; | |
| } | |
| break; | |
| case PlatformKind::COFF: { | |
| auto LoadDynLibrary = [&, this](JITDylib &JD, | |
| StringRef DLLName) -> Error { | |
| if (!DLLName.ends_with_insensitive(".dll")) | |
| return make_error<StringError>("DLLName not ending with .dll", | |
| inconvertibleErrorCode()); | |
| return loadAndLinkDynamicLibrary(JD, DLLName); | |
| }; | |
| if (auto P = | |
| COFFPlatform::Create(*ObjLayer, *PlatformJD, OrcRuntime.c_str(), | |
| std::move(LoadDynLibrary))) | |
| ES.setPlatform(std::move(*P)); | |
| else { | |
| Err = P.takeError(); | |
| return; | |
| } | |
| break; | |
| } | |
| default: | |
| llvm_unreachable("Invalid UsePlatform value"); | |
| } | |
| } else if (TT.isOSBinFormatMachO()) { | |
| if (!NoExec) { | |
| std::optional<bool> ForceEHFrames; | |
| if ((Err = ES.getBootstrapMapValue<bool, bool>("darwin-use-ehframes-only", | |
| ForceEHFrames))) | |
| return; | |
| bool UseEHFrames = ForceEHFrames.value_or(false); | |
| if (!UseEHFrames) | |
| ObjLayer->addPlugin( | |
| ExitOnErr(UnwindInfoRegistrationPlugin::Create(ES))); | |
| else | |
| ObjLayer->addPlugin(ExitOnErr(EHFrameRegistrationPlugin::Create(ES))); | |
| } | |
| } else if (TT.isOSBinFormatELF()) { | |
| if (!NoExec) | |
| ObjLayer->addPlugin(ExitOnErr(EHFrameRegistrationPlugin::Create(ES))); | |
| if (DebuggerSupport) { | |
| Error TargetSymErr = Error::success(); | |
| auto Plugin = | |
| std::make_unique<ELFDebugObjectPlugin>(ES, true, TargetSymErr); | |
| if (!TargetSymErr) | |
| ObjLayer->addPlugin(std::move(Plugin)); | |
| else | |
| logAllUnhandledErrors(std::move(TargetSymErr), errs(), | |
| "Debugger support not available: "); | |
| } | |
| } | |
| if (auto MainJDOrErr = ES.createJITDylib("main")) | |
| MainJD = &*MainJDOrErr; | |
| else { | |
| Err = MainJDOrErr.takeError(); | |
| return; | |
| } | |
| if (NoProcessSymbols) { | |
| // This symbol is used in testcases, but we're not reflecting process | |
| // symbols so we'll need to make it available some other way. | |
| auto &TestResultJD = ES.createBareJITDylib("<TestResultJD>"); | |
| ExitOnErr(TestResultJD.define(absoluteSymbols( | |
| {{ES.intern("llvm_jitlink_setTestResultOverride"), | |
| {ExecutorAddr::fromPtr(llvm_jitlink_setTestResultOverride), | |
| JITSymbolFlags::Exported}}}))); | |
| MainJD->addToLinkOrder(TestResultJD); | |
| } | |
| ObjLayer->addPlugin(std::make_unique<JITLinkSessionPlugin>(*this)); | |
| // Process any harness files. | |
| for (auto &HarnessFile : TestHarnesses) { | |
| HarnessFiles.insert(HarnessFile); | |
| auto ObjBuffer = | |
| ExitOnErr(loadLinkableFile(HarnessFile, ES.getTargetTriple(), | |
| LoadArchives::Never)) | |
| .first; | |
| auto ObjInterface = | |
| ExitOnErr(getObjectFileInterface(ES, ObjBuffer->getMemBufferRef())); | |
| for (auto &KV : ObjInterface.SymbolFlags) | |
| HarnessDefinitions.insert(*KV.first); | |
| auto Obj = ExitOnErr( | |
| object::ObjectFile::createObjectFile(ObjBuffer->getMemBufferRef())); | |
| for (auto &Sym : Obj->symbols()) { | |
| uint32_t SymFlags = ExitOnErr(Sym.getFlags()); | |
| auto Name = ExitOnErr(Sym.getName()); | |
| if (Name.empty()) | |
| continue; | |
| if (SymFlags & object::BasicSymbolRef::SF_Undefined) | |
| HarnessExternals.insert(Name); | |
| } | |
| } | |
| // If a name is defined by some harness file then it's a definition, not an | |
| // external. | |
| for (auto &DefName : HarnessDefinitions) | |
| HarnessExternals.erase(DefName.getKey()); | |
| if (!ShowLinkGraphs.empty()) | |
| ShowGraphsRegex = Regex(ShowLinkGraphs); | |
| } | |
| void Session::dumpSessionInfo(raw_ostream &OS) { | |
| OS << "Registered addresses:\n" << SymbolInfos << FileInfos; | |
| } | |
| void Session::modifyPassConfig(LinkGraph &G, PassConfiguration &PassConfig) { | |
| if (ShowLinkedFiles) | |
| outs() << "Linking " << G.getName() << "\n"; | |
| if (!CheckFiles.empty() || ShowAddrs) | |
| PassConfig.PostFixupPasses.push_back([this](LinkGraph &G) { | |
| if (ES.getTargetTriple().getObjectFormat() == Triple::ELF) | |
| return registerELFGraphInfo(*this, G); | |
| if (ES.getTargetTriple().getObjectFormat() == Triple::MachO) | |
| return registerMachOGraphInfo(*this, G); | |
| if (ES.getTargetTriple().getObjectFormat() == Triple::COFF) | |
| return registerCOFFGraphInfo(*this, G); | |
| return make_error<StringError>("Unsupported object format for GOT/stub " | |
| "registration", | |
| inconvertibleErrorCode()); | |
| }); | |
| if (ShowGraphsRegex) | |
| PassConfig.PostFixupPasses.push_back([this](LinkGraph &G) -> Error { | |
| std::lock_guard<std::mutex> Lock(M); | |
| // Print graph if ShowLinkGraphs is specified-but-empty, or if | |
| // it contains the given graph. | |
| if (ShowGraphsRegex->match(G.getName())) { | |
| outs() << "Link graph \"" << G.getName() << "\" post-fixup:\n"; | |
| G.dump(outs()); | |
| } | |
| return Error::success(); | |
| }); | |
| PassConfig.PrePrunePasses.push_back([this](LinkGraph &G) { | |
| std::lock_guard<std::mutex> Lock(M); | |
| ++ActiveLinks; | |
| return Error::success(); | |
| }); | |
| PassConfig.PrePrunePasses.push_back( | |
| [this](LinkGraph &G) { return applyLibraryLinkModifiers(*this, G); }); | |
| PassConfig.PrePrunePasses.push_back( | |
| [this](LinkGraph &G) { return applyHarnessPromotions(*this, G); }); | |
| if (ShowRelocatedSectionContents) | |
| PassConfig.PostFixupPasses.push_back([this](LinkGraph &G) -> Error { | |
| dumpSectionContents(outs(), *this, G); | |
| return Error::success(); | |
| }); | |
| if (AddSelfRelocations) | |
| PassConfig.PostPrunePasses.push_back(addSelfRelocations); | |
| PassConfig.PostFixupPasses.push_back([this](LinkGraph &G) { | |
| std::lock_guard<std::mutex> Lock(M); | |
| if (--ActiveLinks == 0) | |
| ActiveLinksCV.notify_all(); | |
| return Error::success(); | |
| }); | |
| } | |
| Expected<JITDylib *> Session::getOrLoadDynamicLibrary(StringRef LibPath) { | |
| auto It = DynLibJDs.find(LibPath); | |
| if (It != DynLibJDs.end()) | |
| return It->second; | |
| auto G = | |
| EPCDynamicLibrarySearchGenerator::Load(ES, *DylibMgr, LibPath.data()); | |
| if (!G) | |
| return G.takeError(); | |
| auto JD = &ES.createBareJITDylib(LibPath.str()); | |
| JD->addGenerator(std::move(*G)); | |
| DynLibJDs.emplace(LibPath.str(), JD); | |
| LLVM_DEBUG({ | |
| dbgs() << "Loaded dynamic library " << LibPath.data() << " for " << LibPath | |
| << "\n"; | |
| }); | |
| return JD; | |
| } | |
| Error Session::loadAndLinkDynamicLibrary(JITDylib &JD, StringRef LibPath) { | |
| auto DL = getOrLoadDynamicLibrary(LibPath); | |
| if (!DL) | |
| return DL.takeError(); | |
| JD.addToLinkOrder(**DL); | |
| LLVM_DEBUG({ | |
| dbgs() << "Linking dynamic library " << LibPath << " to " << JD.getName() | |
| << "\n"; | |
| }); | |
| return Error::success(); | |
| } | |
| Expected<JITDylib *> Session::getOrLoadAutoImportDLL(StringRef LibPath) { | |
| auto It = AutoImportJDs.find(LibPath); | |
| if (It != AutoImportJDs.end()) | |
| return It->second; | |
| auto G = orc::COFFAutoImportGenerator::Load(ES, *ObjLayer, *DylibMgr, | |
| LibPath.data()); | |
| if (!G) | |
| return G.takeError(); | |
| auto JD = &ES.createBareJITDylib(LibPath.str()); | |
| JD->addGenerator(std::move(*G)); | |
| AutoImportJDs.emplace(LibPath.str(), JD); | |
| LLVM_DEBUG({ | |
| dbgs() << "Loaded auto-import dynamic library " << LibPath.data() << " for " | |
| << LibPath << "\n"; | |
| }); | |
| return JD; | |
| } | |
| Error Session::loadAndLinkAutoImportDLL(JITDylib &JD, StringRef LibPath) { | |
| auto DL = getOrLoadAutoImportDLL(LibPath); | |
| if (!DL) | |
| return DL.takeError(); | |
| JD.addToLinkOrder(**DL); | |
| LLVM_DEBUG({ | |
| dbgs() << "Linking auto-import dynamic library " << LibPath << " to " | |
| << JD.getName() << "\n"; | |
| }); | |
| return Error::success(); | |
| } | |
| Error Session::FileInfo::registerGOTEntry( | |
| LinkGraph &G, Symbol &Sym, GetSymbolTargetFunction GetSymbolTarget) { | |
| if (Sym.isSymbolZeroFill()) | |
| return make_error<StringError>("Unexpected zero-fill symbol in section " + | |
| Sym.getBlock().getSection().getName(), | |
| inconvertibleErrorCode()); | |
| auto TS = GetSymbolTarget(G, Sym.getBlock()); | |
| if (!TS) | |
| return TS.takeError(); | |
| GOTEntryInfos[*TS->getName()] = {Sym.getSymbolContent(), | |
| Sym.getAddress().getValue(), | |
| Sym.getTargetFlags()}; | |
| return Error::success(); | |
| } | |
| Error Session::FileInfo::registerStubEntry( | |
| LinkGraph &G, Symbol &Sym, GetSymbolTargetFunction GetSymbolTarget) { | |
| if (Sym.isSymbolZeroFill()) | |
| return make_error<StringError>("Unexpected zero-fill symbol in section " + | |
| Sym.getBlock().getSection().getName(), | |
| inconvertibleErrorCode()); | |
| auto TS = GetSymbolTarget(G, Sym.getBlock()); | |
| if (!TS) | |
| return TS.takeError(); | |
| SmallVectorImpl<MemoryRegionInfo> &Entry = StubInfos[*TS->getName()]; | |
| Entry.insert(Entry.begin(), | |
| {Sym.getSymbolContent(), Sym.getAddress().getValue(), | |
| Sym.getTargetFlags()}); | |
| return Error::success(); | |
| } | |
| Error Session::FileInfo::registerMultiStubEntry( | |
| LinkGraph &G, Symbol &Sym, GetSymbolTargetFunction GetSymbolTarget) { | |
| if (Sym.isSymbolZeroFill()) | |
| return make_error<StringError>("Unexpected zero-fill symbol in section " + | |
| Sym.getBlock().getSection().getName(), | |
| inconvertibleErrorCode()); | |
| auto Target = GetSymbolTarget(G, Sym.getBlock()); | |
| if (!Target) | |
| return Target.takeError(); | |
| SmallVectorImpl<MemoryRegionInfo> &Entry = StubInfos[*Target->getName()]; | |
| Entry.emplace_back(Sym.getSymbolContent(), Sym.getAddress().getValue(), | |
| Sym.getTargetFlags()); | |
| // Let's keep stubs ordered by ascending address. | |
| std::sort(Entry.begin(), Entry.end(), | |
| [](const MemoryRegionInfo &L, const MemoryRegionInfo &R) { | |
| return L.getTargetAddress() < R.getTargetAddress(); | |
| }); | |
| return Error::success(); | |
| } | |
| Expected<Session::FileInfo &> Session::findFileInfo(StringRef FileName) { | |
| auto FileInfoItr = FileInfos.find(FileName); | |
| if (FileInfoItr == FileInfos.end()) | |
| return make_error<StringError>("file \"" + FileName + "\" not recognized", | |
| inconvertibleErrorCode()); | |
| return FileInfoItr->second; | |
| } | |
| Expected<Session::MemoryRegionInfo &> | |
| Session::findSectionInfo(StringRef FileName, StringRef SectionName) { | |
| auto FI = findFileInfo(FileName); | |
| if (!FI) | |
| return FI.takeError(); | |
| auto SecInfoItr = FI->SectionInfos.find(SectionName); | |
| if (SecInfoItr == FI->SectionInfos.end()) | |
| return make_error<StringError>("no section \"" + SectionName + | |
| "\" registered for file \"" + FileName + | |
| "\"", | |
| inconvertibleErrorCode()); | |
| return SecInfoItr->second; | |
| } | |
| class MemoryMatcher { | |
| public: | |
| MemoryMatcher(ArrayRef<char> Content) | |
| : Pos(Content.data()), End(Pos + Content.size()) {} | |
| template <typename MaskType> bool matchMask(MaskType Mask) { | |
| if (Mask == (Mask & *reinterpret_cast<const MaskType *>(Pos))) { | |
| Pos += sizeof(MaskType); | |
| return true; | |
| } | |
| return false; | |
| } | |
| template <typename ValueType> bool matchEqual(ValueType Value) { | |
| if (Value == *reinterpret_cast<const ValueType *>(Pos)) { | |
| Pos += sizeof(ValueType); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool done() const { return Pos == End; } | |
| private: | |
| const char *Pos; | |
| const char *End; | |
| }; | |
| static StringRef detectStubKind(const Session::MemoryRegionInfo &Stub) { | |
| using namespace support::endian; | |
| auto Armv7MovWTle = byte_swap<uint32_t>(0xe300c000, endianness::little); | |
| auto Armv7BxR12le = byte_swap<uint32_t>(0xe12fff1c, endianness::little); | |
| auto Thumbv7MovWTle = byte_swap<uint32_t>(0x0c00f240, endianness::little); | |
| auto Thumbv7BxR12le = byte_swap<uint16_t>(0x4760, endianness::little); | |
| MemoryMatcher M(Stub.getContent()); | |
| if (M.matchMask(Thumbv7MovWTle)) { | |
| if (M.matchMask(Thumbv7MovWTle)) | |
| if (M.matchEqual(Thumbv7BxR12le)) | |
| if (M.done()) | |
| return "thumbv7_abs_le"; | |
| } else if (M.matchMask(Armv7MovWTle)) { | |
| if (M.matchMask(Armv7MovWTle)) | |
| if (M.matchEqual(Armv7BxR12le)) | |
| if (M.done()) | |
| return "armv7_abs_le"; | |
| } | |
| return ""; | |
| } | |
| Expected<Session::MemoryRegionInfo &> | |
| Session::findStubInfo(StringRef FileName, StringRef TargetName, | |
| StringRef KindNameFilter) { | |
| auto FI = findFileInfo(FileName); | |
| if (!FI) | |
| return FI.takeError(); | |
| auto StubInfoItr = FI->StubInfos.find(TargetName); | |
| if (StubInfoItr == FI->StubInfos.end()) | |
| return make_error<StringError>("no stub for \"" + TargetName + | |
| "\" registered for file \"" + FileName + | |
| "\"", | |
| inconvertibleErrorCode()); | |
| auto &StubsForTarget = StubInfoItr->second; | |
| assert(!StubsForTarget.empty() && "At least 1 stub in each entry"); | |
| if (KindNameFilter.empty() && StubsForTarget.size() == 1) | |
| return StubsForTarget[0]; // Regular single-stub match | |
| std::string KindsStr; | |
| SmallVector<MemoryRegionInfo *, 1> Matches; | |
| Regex KindNameMatcher(KindNameFilter.empty() ? ".*" : KindNameFilter); | |
| for (MemoryRegionInfo &Stub : StubsForTarget) { | |
| StringRef Kind = detectStubKind(Stub); | |
| if (KindNameMatcher.match(Kind)) | |
| Matches.push_back(&Stub); | |
| KindsStr += "\"" + (Kind.empty() ? "<unknown>" : Kind.str()) + "\", "; | |
| } | |
| if (Matches.empty()) | |
| return make_error<StringError>( | |
| "\"" + TargetName + "\" has " + Twine(StubsForTarget.size()) + | |
| " stubs in file \"" + FileName + | |
| "\", but none of them matches the stub-kind filter \"" + | |
| KindNameFilter + "\" (all encountered kinds are " + | |
| StringRef(KindsStr.data(), KindsStr.size() - 2) + ").", | |
| inconvertibleErrorCode()); | |
| if (Matches.size() > 1) | |
| return make_error<StringError>( | |
| "\"" + TargetName + "\" has " + Twine(Matches.size()) + | |
| " candidate stubs in file \"" + FileName + | |
| "\". Please refine stub-kind filter \"" + KindNameFilter + | |
| "\" for disambiguation (encountered kinds are " + | |
| StringRef(KindsStr.data(), KindsStr.size() - 2) + ").", | |
| inconvertibleErrorCode()); | |
| return *Matches[0]; | |
| } | |
| Expected<Session::MemoryRegionInfo &> | |
| Session::findGOTEntryInfo(StringRef FileName, StringRef TargetName) { | |
| auto FI = findFileInfo(FileName); | |
| if (!FI) | |
| return FI.takeError(); | |
| auto GOTInfoItr = FI->GOTEntryInfos.find(TargetName); | |
| if (GOTInfoItr == FI->GOTEntryInfos.end()) | |
| return make_error<StringError>("no GOT entry for \"" + TargetName + | |
| "\" registered for file \"" + FileName + | |
| "\"", | |
| inconvertibleErrorCode()); | |
| return GOTInfoItr->second; | |
| } | |
| bool Session::isSymbolRegistered(const orc::SymbolStringPtr &SymbolName) { | |
| return SymbolInfos.count(SymbolName); | |
| } | |
| Expected<Session::MemoryRegionInfo &> | |
| Session::findSymbolInfo(const orc::SymbolStringPtr &SymbolName, | |
| Twine ErrorMsgStem) { | |
| auto SymInfoItr = SymbolInfos.find(SymbolName); | |
| if (SymInfoItr == SymbolInfos.end()) | |
| return make_error<StringError>(ErrorMsgStem + ": symbol " + *SymbolName + | |
| " not found", | |
| inconvertibleErrorCode()); | |
| return SymInfoItr->second; | |
| } | |
| } // end namespace llvm | |
| static std::pair<Triple, SubtargetFeatures> getFirstFileTripleAndFeatures() { | |
| // If we're running in symbolicate mode then just use the process triple. | |
| if (!SymbolicateWith.empty()) | |
| return std::make_pair(Triple(sys::getProcessTriple()), SubtargetFeatures()); | |
| // Otherwise we need to inspect the input files. | |
| static std::pair<Triple, SubtargetFeatures> FirstTTAndFeatures = []() { | |
| assert(!InputFiles.empty() && "InputFiles can not be empty"); | |
| if (!OverrideTriple.empty()) { | |
| LLVM_DEBUG({ | |
| dbgs() << "Triple from -triple override: " << OverrideTriple << "\n"; | |
| }); | |
| return std::make_pair(Triple(OverrideTriple), SubtargetFeatures()); | |
| } | |
| for (auto InputFile : InputFiles) { | |
| auto ObjBuffer = ExitOnErr(getFile(InputFile)); | |
| file_magic Magic = identify_magic(ObjBuffer->getBuffer()); | |
| switch (Magic) { | |
| case file_magic::coff_object: | |
| case file_magic::elf_relocatable: | |
| case file_magic::macho_object: { | |
| auto Obj = ExitOnErr( | |
| object::ObjectFile::createObjectFile(ObjBuffer->getMemBufferRef())); | |
| Triple TT; | |
| if (auto *MachOObj = dyn_cast<object::MachOObjectFile>(Obj.get())) | |
| TT = MachOObj->getArchTriple(); | |
| else | |
| TT = Obj->makeTriple(); | |
| if (Magic == file_magic::coff_object) { | |
| // TODO: Move this to makeTriple() if possible. | |
| TT.setObjectFormat(Triple::COFF); | |
| TT.setOS(Triple::OSType::Win32); | |
| } | |
| SubtargetFeatures Features; | |
| if (auto ObjFeatures = Obj->getFeatures()) | |
| Features = std::move(*ObjFeatures); | |
| LLVM_DEBUG({ | |
| dbgs() << "Triple from " << InputFile << ": " << TT.str() << "\n"; | |
| }); | |
| return std::make_pair(TT, Features); | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| // If no plain object file inputs exist to pin down the triple then detect | |
| // the host triple and default to that. | |
| auto JTMB = ExitOnErr(JITTargetMachineBuilder::detectHost()); | |
| LLVM_DEBUG({ | |
| dbgs() << "Triple from host-detection: " << JTMB.getTargetTriple().str() | |
| << "\n"; | |
| }); | |
| return std::make_pair(JTMB.getTargetTriple(), JTMB.getFeatures()); | |
| }(); | |
| return FirstTTAndFeatures; | |
| } | |
| static Error sanitizeArguments(const Triple &TT, const char *ArgV0) { | |
| if (InputFiles.empty()) | |
| return make_error<StringError>( | |
| "Not enough positional command line arguments specified! (see " | |
| "llvm-jitlink --help)", | |
| inconvertibleErrorCode()); | |
| // If we're in replay mode we should never get here. | |
| assert(WaitingOnGraphReplay.empty()); | |
| // -noexec and --args should not be used together. | |
| if (NoExec && !InputArgv.empty()) | |
| errs() << "Warning: --args passed to -noexec run will be ignored.\n"; | |
| // Set the entry point name if not specified. | |
| if (EntryPointName.empty()) | |
| EntryPointName = TT.getObjectFormat() == Triple::MachO ? "_main" : "main"; | |
| // Disable debugger support by default in noexec tests. | |
| if (DebuggerSupport.getNumOccurrences() == 0 && NoExec) | |
| DebuggerSupport = false; | |
| if (!OrcRuntime.empty() && NoProcessSymbols) | |
| return make_error<StringError>("-orc-runtime requires process symbols", | |
| inconvertibleErrorCode()); | |
| // If no -platform option was specified then pick a default: If an ORC | |
| // runtime path was given then use "native", otherwise use "none". | |
| if (UsePlatform.getNumOccurrences() == 0) | |
| UsePlatform = | |
| OrcRuntime.empty() ? PlatformKind::None : PlatformKind::Native; | |
| // If -slab-allocate is passed, check that we're not trying to use it in | |
| // -oop-launch or -oop-connect mode. | |
| // | |
| // FIXME: Remove once we enable remote slab allocation. | |
| if (SlabAllocateSizeString != "") { | |
| if (OutOfProcessLaunch.getNumOccurrences() || | |
| OutOfProcessConnect.getNumOccurrences()) | |
| return make_error<StringError>( | |
| "-slab-allocate cannot be used with -oop-launch or -oop-connect", | |
| inconvertibleErrorCode()); | |
| } | |
| // If -slab-address is passed, require -slab-allocate and -noexec | |
| if (SlabAddress != ~0ULL) { | |
| if (SlabAllocateSizeString == "" || !NoExec) | |
| return make_error<StringError>( | |
| "-slab-address requires -slab-allocate and -noexec", | |
| inconvertibleErrorCode()); | |
| if (SlabPageSize == 0) | |
| errs() << "Warning: -slab-address used without -slab-page-size.\n"; | |
| } | |
| if (SlabPageSize != 0) { | |
| // -slab-page-size requires slab alloc. | |
| if (SlabAllocateSizeString == "") | |
| return make_error<StringError>("-slab-page-size requires -slab-allocate", | |
| inconvertibleErrorCode()); | |
| // Check -slab-page-size / -noexec interactions. | |
| if (!NoExec) { | |
| if (auto RealPageSize = sys::Process::getPageSize()) { | |
| if (SlabPageSize % *RealPageSize) | |
| return make_error<StringError>( | |
| "-slab-page-size must be a multiple of real page size for exec " | |
| "tests (did you mean to use -noexec ?)\n", | |
| inconvertibleErrorCode()); | |
| } else { | |
| errs() << "Could not retrieve process page size:\n"; | |
| logAllUnhandledErrors(RealPageSize.takeError(), errs(), ""); | |
| errs() << "Executing with slab page size = " | |
| << formatv("{0:x}", SlabPageSize) << ".\n" | |
| << "Tool may crash if " << formatv("{0:x}", SlabPageSize) | |
| << " is not a multiple of the real process page size.\n" | |
| << "(did you mean to use -noexec ?)"; | |
| } | |
| } | |
| } | |
| #if LLVM_ENABLE_THREADS | |
| if (MaterializationThreads == std::numeric_limits<size_t>::max()) { | |
| if (auto HC = std::thread::hardware_concurrency()) | |
| MaterializationThreads = HC; | |
| else { | |
| errs() << "Warning: std::thread::hardware_concurrency() returned 0, " | |
| "defaulting to -num-threads=1.\n"; | |
| MaterializationThreads = 1; | |
| } | |
| } | |
| #else | |
| if (MaterializationThreads.getNumOccurrences() && | |
| MaterializationThreads != 0) { | |
| errs() << "Warning: -num-threads was set, but LLVM was built with threads " | |
| "disabled. Resetting to -num-threads=0\n"; | |
| } | |
| MaterializationThreads = 0; | |
| #endif | |
| if (!!OutOfProcessLaunch.getNumOccurrences() || | |
| !!OutOfProcessConnect.getNumOccurrences()) { | |
| if (NoExec) | |
| return make_error<StringError>("-noexec cannot be used with " + | |
| OutOfProcessLaunch.ArgStr + " or " + | |
| OutOfProcessConnect.ArgStr, | |
| inconvertibleErrorCode()); | |
| if (MaterializationThreads == 0) | |
| return make_error<StringError>("-threads=0 cannot be used with " + | |
| OutOfProcessLaunch.ArgStr + " or " + | |
| OutOfProcessConnect.ArgStr, | |
| inconvertibleErrorCode()); | |
| } | |
| #ifndef NDEBUG | |
| if (DebugFlag && MaterializationThreads != 0) | |
| errs() << "Warning: debugging output is not thread safe. " | |
| "Use -num-threads=0 to stabilize output.\n"; | |
| #endif // NDEBUG | |
| // If -oop-launch was used but no value was specified then use a sensible | |
| // default. | |
| if (!!OutOfProcessLaunch.getNumOccurrences() && OutOfProcessLaunch.empty()) { | |
| SmallString<256> OOPExecutorPath(sys::fs::getMainExecutable( | |
| ArgV0, reinterpret_cast<void *>(&sanitizeArguments))); | |
| sys::path::remove_filename(OOPExecutorPath); | |
| sys::path::append(OOPExecutorPath, "llvm-jitlink-executor"); | |
| OutOfProcessLaunch = OOPExecutorPath.str().str(); | |
| } | |
| // If lazy linking is requested then check compatibility with other options. | |
| if (lazyLinkingRequested()) { | |
| if (OrcRuntime.empty()) | |
| return make_error<StringError>("Lazy linking requries the ORC runtime", | |
| inconvertibleErrorCode()); | |
| if (!TestHarnesses.empty()) | |
| return make_error<StringError>( | |
| "Lazy linking cannot be used with -harness mode", | |
| inconvertibleErrorCode()); | |
| } else if (Speculate != SpeculateKind::None) { | |
| errs() << "Warning: -speculate ignored as there are no -lazy inputs\n"; | |
| Speculate = SpeculateKind::None; | |
| } | |
| if (Speculate == SpeculateKind::None) { | |
| if (!SpeculateOrder.empty()) { | |
| errs() << "Warning: -speculate-order ignored because speculation is " | |
| "disabled\n"; | |
| SpeculateOrder = ""; | |
| } | |
| if (!RecordLazyExecs.empty()) { | |
| errs() << "Warning: -record-lazy-execs ignored because speculation is " | |
| "disabled\n"; | |
| RecordLazyExecs = ""; | |
| } | |
| } | |
| if (!SymbolicateWith.empty()) { | |
| if (!WriteSymbolTableTo.empty()) | |
| errs() << WriteSymbolTableTo.ArgStr << " specified with " | |
| << SymbolicateWith.ArgStr << ", ignoring."; | |
| if (InputFiles.empty()) | |
| InputFiles.push_back("-"); | |
| } | |
| return Error::success(); | |
| } | |
| static void addPhonyExternalsGenerator(Session &S) { | |
| S.MainJD->addGenerator(std::make_unique<PhonyExternalsGenerator>()); | |
| } | |
| static Error createJITDylibs(Session &S, | |
| std::map<unsigned, JITDylib *> &IdxToJD) { | |
| // First, set up JITDylibs. | |
| LLVM_DEBUG(dbgs() << "Creating JITDylibs...\n"); | |
| { | |
| // Create a "main" JITLinkDylib. | |
| IdxToJD[0] = S.MainJD; | |
| S.JDSearchOrder.push_back({S.MainJD, JITDylibLookupFlags::MatchAllSymbols}); | |
| LLVM_DEBUG(dbgs() << " 0: " << S.MainJD->getName() << "\n"); | |
| // Add any extra JITDylibs from the command line. | |
| for (auto JDItr = JITDylibs.begin(), JDEnd = JITDylibs.end(); | |
| JDItr != JDEnd; ++JDItr) { | |
| auto JD = S.ES.createJITDylib(*JDItr); | |
| if (!JD) | |
| return JD.takeError(); | |
| unsigned JDIdx = JITDylibs.getPosition(JDItr - JITDylibs.begin()); | |
| IdxToJD[JDIdx] = &*JD; | |
| S.JDSearchOrder.push_back({&*JD, JITDylibLookupFlags::MatchAllSymbols}); | |
| LLVM_DEBUG(dbgs() << " " << JDIdx << ": " << JD->getName() << "\n"); | |
| } | |
| } | |
| if (S.PlatformJD) | |
| S.JDSearchOrder.push_back( | |
| {S.PlatformJD, JITDylibLookupFlags::MatchExportedSymbolsOnly}); | |
| if (S.ProcessSymsJD) | |
| S.JDSearchOrder.push_back( | |
| {S.ProcessSymsJD, JITDylibLookupFlags::MatchExportedSymbolsOnly}); | |
| LLVM_DEBUG({ | |
| dbgs() << "Dylib search order is [ "; | |
| for (auto &KV : S.JDSearchOrder) | |
| dbgs() << KV.first->getName() << " "; | |
| dbgs() << "]\n"; | |
| }); | |
| return Error::success(); | |
| } | |
| static Error addAbsoluteSymbols(Session &S, | |
| const std::map<unsigned, JITDylib *> &IdxToJD) { | |
| // Define absolute symbols. | |
| LLVM_DEBUG(dbgs() << "Defining absolute symbols...\n"); | |
| for (auto AbsDefItr = AbsoluteDefs.begin(), AbsDefEnd = AbsoluteDefs.end(); | |
| AbsDefItr != AbsDefEnd; ++AbsDefItr) { | |
| unsigned AbsDefArgIdx = | |
| AbsoluteDefs.getPosition(AbsDefItr - AbsoluteDefs.begin()); | |
| auto &JD = *std::prev(IdxToJD.lower_bound(AbsDefArgIdx))->second; | |
| StringRef AbsDefStmt = *AbsDefItr; | |
| size_t EqIdx = AbsDefStmt.find_first_of('='); | |
| if (EqIdx == StringRef::npos) | |
| return make_error<StringError>("Invalid absolute define \"" + AbsDefStmt + | |
| "\". Syntax: <name>=<addr>", | |
| inconvertibleErrorCode()); | |
| StringRef Name = AbsDefStmt.substr(0, EqIdx).trim(); | |
| StringRef AddrStr = AbsDefStmt.substr(EqIdx + 1).trim(); | |
| uint64_t Addr; | |
| if (AddrStr.getAsInteger(0, Addr)) | |
| return make_error<StringError>("Invalid address expression \"" + AddrStr + | |
| "\" in absolute symbol definition \"" + | |
| AbsDefStmt + "\"", | |
| inconvertibleErrorCode()); | |
| ExecutorSymbolDef AbsDef(ExecutorAddr(Addr), JITSymbolFlags::Exported); | |
| auto InternedName = S.ES.intern(Name); | |
| if (auto Err = JD.define(absoluteSymbols({{InternedName, AbsDef}}))) | |
| return Err; | |
| // Register the absolute symbol with the session symbol infos. | |
| S.SymbolInfos[std::move(InternedName)] = | |
| {ArrayRef<char>(), Addr, AbsDef.getFlags().getTargetFlags()}; | |
| } | |
| return Error::success(); | |
| } | |
| static Error addAliases(Session &S, | |
| const std::map<unsigned, JITDylib *> &IdxToJD) { | |
| // Define absolute symbols. | |
| LLVM_DEBUG(dbgs() << "Defining aliases...\n"); | |
| DenseMap<std::pair<JITDylib *, JITDylib *>, SymbolAliasMap> Reexports; | |
| for (auto AliasItr = Aliases.begin(), AliasEnd = Aliases.end(); | |
| AliasItr != AliasEnd; ++AliasItr) { | |
| auto BadExpr = [&]() { | |
| return make_error<StringError>( | |
| "Invalid alias definition \"" + *AliasItr + | |
| "\". Syntax: [<dst-jd>:]<alias>=[<src-jd>:]<aliasee>", | |
| inconvertibleErrorCode()); | |
| }; | |
| auto GetJD = [&](StringRef JDName) -> Expected<JITDylib *> { | |
| if (JDName.empty()) { | |
| unsigned AliasArgIdx = Aliases.getPosition(AliasItr - Aliases.begin()); | |
| return std::prev(IdxToJD.lower_bound(AliasArgIdx))->second; | |
| } | |
| auto *JD = S.ES.getJITDylibByName(JDName); | |
| if (!JD) | |
| return make_error<StringError>(StringRef("In alias definition \"") + | |
| *AliasItr + "\" no dylib named " + | |
| JDName, | |
| inconvertibleErrorCode()); | |
| return JD; | |
| }; | |
| { | |
| // First split on '=' to get alias and aliasee. | |
| StringRef AliasStmt = *AliasItr; | |
| auto [AliasExpr, AliaseeExpr] = AliasStmt.split('='); | |
| if (AliaseeExpr.empty()) | |
| return BadExpr(); | |
| auto [AliasJDName, Alias] = AliasExpr.split(':'); | |
| if (Alias.empty()) | |
| std::swap(AliasJDName, Alias); | |
| auto AliasJD = GetJD(AliasJDName); | |
| if (!AliasJD) | |
| return AliasJD.takeError(); | |
| auto [AliaseeJDName, Aliasee] = AliaseeExpr.split(':'); | |
| if (Aliasee.empty()) | |
| std::swap(AliaseeJDName, Aliasee); | |
| if (AliaseeJDName.empty() && !AliasJDName.empty()) | |
| AliaseeJDName = AliasJDName; | |
| auto AliaseeJD = GetJD(AliaseeJDName); | |
| if (!AliaseeJD) | |
| return AliaseeJD.takeError(); | |
| Reexports[{*AliasJD, *AliaseeJD}][S.ES.intern(Alias)] = { | |
| S.ES.intern(Aliasee), JITSymbolFlags::Exported}; | |
| } | |
| } | |
| for (auto &[JDs, AliasMap] : Reexports) { | |
| auto [DstJD, SrcJD] = JDs; | |
| if (auto Err = DstJD->define(reexports(*SrcJD, std::move(AliasMap)))) | |
| return Err; | |
| } | |
| return Error::success(); | |
| } | |
| static Error addSectCreates(Session &S, | |
| const std::map<unsigned, JITDylib *> &IdxToJD) { | |
| for (auto SCItr = SectCreate.begin(), SCEnd = SectCreate.end(); | |
| SCItr != SCEnd; ++SCItr) { | |
| unsigned SCArgIdx = SectCreate.getPosition(SCItr - SectCreate.begin()); | |
| auto &JD = *std::prev(IdxToJD.lower_bound(SCArgIdx))->second; | |
| StringRef SCArg(*SCItr); | |
| auto [SectAndFileName, ExtraSymbolsString] = SCArg.rsplit('@'); | |
| auto [SectName, FileName] = SectAndFileName.rsplit(','); | |
| if (SectName.empty()) | |
| return make_error<StringError>("In -sectcreate=" + SCArg + | |
| ", filename component cannot be empty", | |
| inconvertibleErrorCode()); | |
| if (FileName.empty()) | |
| return make_error<StringError>("In -sectcreate=" + SCArg + | |
| ", filename component cannot be empty", | |
| inconvertibleErrorCode()); | |
| auto Content = getFile(FileName); | |
| if (!Content) | |
| return Content.takeError(); | |
| SectCreateMaterializationUnit::ExtraSymbolsMap ExtraSymbols; | |
| while (!ExtraSymbolsString.empty()) { | |
| StringRef NextSymPair; | |
| std::tie(NextSymPair, ExtraSymbolsString) = ExtraSymbolsString.split(','); | |
| auto [Sym, OffsetString] = NextSymPair.split('='); | |
| size_t Offset; | |
| if (OffsetString.getAsInteger(0, Offset)) | |
| return make_error<StringError>("In -sectcreate=" + SCArg + ", " + | |
| OffsetString + | |
| " is not a valid integer", | |
| inconvertibleErrorCode()); | |
| ExtraSymbols[S.ES.intern(Sym)] = {JITSymbolFlags::Exported, Offset}; | |
| } | |
| if (auto Err = JD.define(std::make_unique<SectCreateMaterializationUnit>( | |
| *S.ObjLayer, SectName.str(), MemProt::Read, 16, std::move(*Content), | |
| std::move(ExtraSymbols)))) | |
| return Err; | |
| } | |
| return Error::success(); | |
| } | |
| static Error addTestHarnesses(Session &S) { | |
| LLVM_DEBUG(dbgs() << "Adding test harness objects...\n"); | |
| for (auto HarnessFile : TestHarnesses) { | |
| LLVM_DEBUG(dbgs() << " " << HarnessFile << "\n"); | |
| auto Linkable = loadLinkableFile(HarnessFile, S.ES.getTargetTriple(), | |
| LoadArchives::Never); | |
| if (!Linkable) | |
| return Linkable.takeError(); | |
| if (auto Err = S.ObjLayer->add(*S.MainJD, std::move(Linkable->first))) | |
| return Err; | |
| } | |
| return Error::success(); | |
| } | |
| static Error addObjects(Session &S, | |
| const std::map<unsigned, JITDylib *> &IdxToJD, | |
| const DenseSet<unsigned> &LazyLinkIdxs) { | |
| // Load each object into the corresponding JITDylib.. | |
| LLVM_DEBUG(dbgs() << "Adding objects...\n"); | |
| for (auto InputFileItr = InputFiles.begin(), InputFileEnd = InputFiles.end(); | |
| InputFileItr != InputFileEnd; ++InputFileItr) { | |
| unsigned InputFileArgIdx = | |
| InputFiles.getPosition(InputFileItr - InputFiles.begin()); | |
| const std::string &InputFile = *InputFileItr; | |
| if (StringRef(InputFile).ends_with(".a") || | |
| StringRef(InputFile).ends_with(".lib")) | |
| continue; | |
| auto &JD = *std::prev(IdxToJD.lower_bound(InputFileArgIdx))->second; | |
| bool AddLazy = LazyLinkIdxs.count(InputFileArgIdx); | |
| LLVM_DEBUG(dbgs() << " " << InputFileArgIdx << ": \"" << InputFile << "\" " | |
| << (AddLazy ? " (lazy-linked)" : "") << " to " | |
| << JD.getName() << "\n";); | |
| auto ObjBuffer = loadLinkableFile(InputFile, S.ES.getTargetTriple(), | |
| LoadArchives::Never); | |
| if (!ObjBuffer) | |
| return ObjBuffer.takeError(); | |
| if (S.HarnessFiles.empty()) { | |
| if (auto Err = | |
| S.getLinkLayer(AddLazy).add(JD, std::move(ObjBuffer->first))) | |
| return Err; | |
| } else { | |
| // We're in -harness mode. Use a custom interface for this | |
| // test object. | |
| auto ObjInterface = | |
| getTestObjectFileInterface(S, ObjBuffer->first->getMemBufferRef()); | |
| if (!ObjInterface) | |
| return ObjInterface.takeError(); | |
| if (auto Err = S.ObjLayer->add(JD, std::move(ObjBuffer->first), | |
| std::move(*ObjInterface))) | |
| return Err; | |
| } | |
| } | |
| return Error::success(); | |
| } | |
| static Expected<MaterializationUnit::Interface> | |
| getObjectFileInterfaceHidden(ExecutionSession &ES, MemoryBufferRef ObjBuffer) { | |
| auto I = getObjectFileInterface(ES, ObjBuffer); | |
| if (I) { | |
| for (auto &KV : I->SymbolFlags) | |
| KV.second &= ~JITSymbolFlags::Exported; | |
| } | |
| return I; | |
| } | |
| static SmallVector<StringRef, 5> getSearchPathsFromEnvVar(Session &S) { | |
| // FIXME: Handle EPC environment. | |
| SmallVector<StringRef, 5> PathVec; | |
| auto TT = S.ES.getTargetTriple(); | |
| if (TT.isOSBinFormatCOFF()) | |
| StringRef(getenv("PATH")).split(PathVec, ";"); | |
| else if (TT.isOSBinFormatELF()) | |
| StringRef(getenv("LD_LIBRARY_PATH")).split(PathVec, ":"); | |
| return PathVec; | |
| } | |
| static Expected<std::unique_ptr<DefinitionGenerator>> | |
| LoadLibraryWeak(Session &S, StringRef Path) { | |
| auto Symbols = getDylibInterface(S.ES, Path); | |
| if (!Symbols) | |
| return Symbols.takeError(); | |
| return std::make_unique<EPCDynamicLibrarySearchGenerator>( | |
| S.ES, *S.DylibMgr, | |
| [Symbols = std::move(*Symbols)](const SymbolStringPtr &Sym) { | |
| return Symbols.count(Sym); | |
| }); | |
| } | |
| static Error addLibraries(Session &S, | |
| const std::map<unsigned, JITDylib *> &IdxToJD, | |
| const DenseSet<unsigned> &LazyLinkIdxs) { | |
| // 1. Collect search paths for each JITDylib. | |
| DenseMap<const JITDylib *, SmallVector<StringRef, 2>> JDSearchPaths; | |
| for (auto LSPItr = LibrarySearchPaths.begin(), | |
| LSPEnd = LibrarySearchPaths.end(); | |
| LSPItr != LSPEnd; ++LSPItr) { | |
| unsigned LibrarySearchPathIdx = | |
| LibrarySearchPaths.getPosition(LSPItr - LibrarySearchPaths.begin()); | |
| auto &JD = *std::prev(IdxToJD.lower_bound(LibrarySearchPathIdx))->second; | |
| StringRef LibrarySearchPath = *LSPItr; | |
| if (sys::fs::get_file_type(LibrarySearchPath) != | |
| sys::fs::file_type::directory_file) | |
| return make_error<StringError>("While linking " + JD.getName() + ", -L" + | |
| LibrarySearchPath + | |
| " does not point to a directory", | |
| inconvertibleErrorCode()); | |
| JDSearchPaths[&JD].push_back(*LSPItr); | |
| } | |
| LLVM_DEBUG({ | |
| if (!JDSearchPaths.empty()) | |
| dbgs() << "Search paths:\n"; | |
| for (auto &KV : JDSearchPaths) { | |
| dbgs() << " " << KV.first->getName() << ": ["; | |
| for (auto &LibSearchPath : KV.second) | |
| dbgs() << " \"" << LibSearchPath << "\""; | |
| dbgs() << " ]\n"; | |
| } | |
| }); | |
| // 2. Collect library loads | |
| struct LibraryLoad { | |
| std::string LibName; | |
| bool IsPath = false; | |
| unsigned Position; | |
| ArrayRef<StringRef> CandidateExtensions; | |
| enum { Standard, Hidden, Weak, Auto } Modifier; | |
| }; | |
| // Queue to load library as in the order as it appears in the argument list. | |
| std::deque<LibraryLoad> LibraryLoadQueue; | |
| // Add archive files from the inputs to LibraryLoads. | |
| for (auto InputFileItr = InputFiles.begin(), InputFileEnd = InputFiles.end(); | |
| InputFileItr != InputFileEnd; ++InputFileItr) { | |
| StringRef InputFile = *InputFileItr; | |
| if (!InputFile.ends_with(".a") && !InputFile.ends_with(".lib")) | |
| continue; | |
| LibraryLoad LL; | |
| LL.LibName = InputFile.str(); | |
| LL.IsPath = true; | |
| LL.Position = InputFiles.getPosition(InputFileItr - InputFiles.begin()); | |
| LL.CandidateExtensions = {}; | |
| LL.Modifier = LibraryLoad::Standard; | |
| LibraryLoadQueue.push_back(std::move(LL)); | |
| } | |
| // Add -load_hidden arguments to LibraryLoads. | |
| for (auto LibItr = LoadHidden.begin(), LibEnd = LoadHidden.end(); | |
| LibItr != LibEnd; ++LibItr) { | |
| LibraryLoad LL; | |
| LL.LibName = *LibItr; | |
| LL.IsPath = true; | |
| LL.Position = LoadHidden.getPosition(LibItr - LoadHidden.begin()); | |
| LL.CandidateExtensions = {}; | |
| LL.Modifier = LibraryLoad::Hidden; | |
| LibraryLoadQueue.push_back(std::move(LL)); | |
| } | |
| // Add -weak_library arguments to LibraryLoads. | |
| for (auto LibItr = WeakLibraries.begin(), LibEnd = WeakLibraries.end(); | |
| LibItr != LibEnd; ++LibItr) { | |
| LibraryLoad LL; | |
| LL.LibName = *LibItr; | |
| LL.IsPath = true; | |
| LL.Position = WeakLibraries.getPosition(LibItr - WeakLibraries.begin()); | |
| LL.CandidateExtensions = {}; | |
| LL.Modifier = LibraryLoad::Weak; | |
| LibraryLoadQueue.push_back(std::move(LL)); | |
| } | |
| StringRef StandardExtensions[] = {".so", ".dylib", ".dll", ".a", ".lib"}; | |
| StringRef DynLibExtensionsOnly[] = {".so", ".dylib", ".dll"}; | |
| StringRef ArchiveExtensionsOnly[] = {".a", ".lib"}; | |
| StringRef WeakLinkExtensionsOnly[] = {".dylib", ".tbd"}; | |
| // Add -lx arguments to LibraryLoads. | |
| for (auto LibItr = Libraries.begin(), LibEnd = Libraries.end(); | |
| LibItr != LibEnd; ++LibItr) { | |
| LibraryLoad LL; | |
| LL.LibName = *LibItr; | |
| LL.Position = Libraries.getPosition(LibItr - Libraries.begin()); | |
| LL.CandidateExtensions = StandardExtensions; | |
| LL.Modifier = LibraryLoad::Standard; | |
| LibraryLoadQueue.push_back(std::move(LL)); | |
| } | |
| // Add -hidden-lx arguments to LibraryLoads. | |
| for (auto LibHiddenItr = LibrariesHidden.begin(), | |
| LibHiddenEnd = LibrariesHidden.end(); | |
| LibHiddenItr != LibHiddenEnd; ++LibHiddenItr) { | |
| LibraryLoad LL; | |
| LL.LibName = *LibHiddenItr; | |
| LL.Position = | |
| LibrariesHidden.getPosition(LibHiddenItr - LibrariesHidden.begin()); | |
| LL.CandidateExtensions = ArchiveExtensionsOnly; | |
| LL.Modifier = LibraryLoad::Hidden; | |
| LibraryLoadQueue.push_back(std::move(LL)); | |
| } | |
| // Add -weak-lx arguments to LibraryLoads. | |
| for (auto LibWeakItr = LibrariesWeak.begin(), | |
| LibWeakEnd = LibrariesWeak.end(); | |
| LibWeakItr != LibWeakEnd; ++LibWeakItr) { | |
| LibraryLoad LL; | |
| LL.LibName = *LibWeakItr; | |
| LL.Position = LibrariesWeak.getPosition(LibWeakItr - LibrariesWeak.begin()); | |
| LL.CandidateExtensions = WeakLinkExtensionsOnly; | |
| LL.Modifier = LibraryLoad::Weak; | |
| LibraryLoadQueue.push_back(std::move(LL)); | |
| } | |
| // Add -auto-lx arguments to LibraryLoads. | |
| for (auto LibAutoItr = LibrariesAuto.begin(), | |
| LibAutoEnd = LibrariesAuto.end(); | |
| LibAutoItr != LibAutoEnd; ++LibAutoItr) { | |
| LibraryLoad LL; | |
| LL.LibName = *LibAutoItr; | |
| LL.Position = LibrariesAuto.getPosition(LibAutoItr - LibrariesAuto.begin()); | |
| LL.CandidateExtensions = DynLibExtensionsOnly; | |
| LL.Modifier = LibraryLoad::Auto; | |
| LibraryLoadQueue.push_back(std::move(LL)); | |
| } | |
| // Sort library loads by position in the argument list. | |
| llvm::sort(LibraryLoadQueue, | |
| [](const LibraryLoad &LHS, const LibraryLoad &RHS) { | |
| return LHS.Position < RHS.Position; | |
| }); | |
| // 3. Process library loads. | |
| auto AddArchive = [&](JITDylib &JD, const char *Path, const LibraryLoad &LL) | |
| -> Expected<std::unique_ptr<StaticLibraryDefinitionGenerator>> { | |
| StaticLibraryDefinitionGenerator::GetObjectFileInterface | |
| GetObjFileInterface; | |
| switch (LL.Modifier) { | |
| case LibraryLoad::Standard: | |
| GetObjFileInterface = getObjectFileInterface; | |
| break; | |
| case LibraryLoad::Hidden: | |
| GetObjFileInterface = getObjectFileInterfaceHidden; | |
| S.HiddenArchives.insert(Path); | |
| break; | |
| case LibraryLoad::Weak: | |
| case LibraryLoad::Auto: | |
| llvm_unreachable("Unsupported"); | |
| break; | |
| } | |
| auto &LinkLayer = S.getLinkLayer(LazyLinkIdxs.count(LL.Position)); | |
| std::set<std::string> ImportedDynamicLibraries; | |
| StaticLibraryDefinitionGenerator::VisitMembersFunction VisitMembers; | |
| // COFF gets special handling due to import libraries. | |
| if (S.ES.getTargetTriple().isOSBinFormatCOFF()) { | |
| if (AllLoad) { | |
| VisitMembers = | |
| [ImportScanner = COFFImportFileScanner(ImportedDynamicLibraries), | |
| LoadAll = | |
| StaticLibraryDefinitionGenerator::loadAllObjectFileMembers( | |
| LinkLayer, JD)](object::Archive &A, | |
| MemoryBufferRef MemberBuf, | |
| size_t Index) mutable -> Expected<bool> { | |
| if (!ImportScanner(A, MemberBuf, Index)) | |
| return false; | |
| return LoadAll(A, MemberBuf, Index); | |
| }; | |
| } else | |
| VisitMembers = COFFImportFileScanner(ImportedDynamicLibraries); | |
| } else if (AllLoad) | |
| VisitMembers = StaticLibraryDefinitionGenerator::loadAllObjectFileMembers( | |
| LinkLayer, JD); | |
| else if (S.ES.getTargetTriple().isOSBinFormatMachO() && ForceLoadObjC) | |
| VisitMembers = ForceLoadMachOArchiveMembers(LinkLayer, JD, true); | |
| auto G = StaticLibraryDefinitionGenerator::Load( | |
| LinkLayer, Path, std::move(VisitMembers), | |
| std::move(GetObjFileInterface)); | |
| if (!G) | |
| return G.takeError(); | |
| // Push additional dynamic libraries to search. | |
| // Note that this mechanism only happens in COFF. | |
| for (auto FileName : ImportedDynamicLibraries) { | |
| LibraryLoad NewLL; | |
| auto FileNameRef = StringRef(FileName); | |
| if (!FileNameRef.ends_with_insensitive(".dll")) | |
| return make_error<StringError>( | |
| "COFF Imported library not ending with dll extension?", | |
| inconvertibleErrorCode()); | |
| NewLL.LibName = FileNameRef.drop_back(strlen(".dll")).str(); | |
| NewLL.Position = LL.Position; | |
| NewLL.CandidateExtensions = DynLibExtensionsOnly; | |
| NewLL.Modifier = LibraryLoad::Standard; | |
| LibraryLoadQueue.push_front(std::move(NewLL)); | |
| } | |
| return G; | |
| }; | |
| SmallVector<StringRef, 5> SystemSearchPaths; | |
| if (SearchSystemLibrary.getValue()) | |
| SystemSearchPaths = getSearchPathsFromEnvVar(S); | |
| while (!LibraryLoadQueue.empty()) { | |
| bool LibFound = false; | |
| auto LL = LibraryLoadQueue.front(); | |
| LibraryLoadQueue.pop_front(); | |
| auto &JD = *std::prev(IdxToJD.lower_bound(LL.Position))->second; | |
| // If this is the name of a JITDylib then link against that. | |
| if (auto *LJD = S.ES.getJITDylibByName(LL.LibName)) { | |
| if (LL.Modifier == LibraryLoad::Weak) | |
| return make_error<StringError>( | |
| "Can't use -weak-lx or -weak_library to load JITDylib " + | |
| LL.LibName, | |
| inconvertibleErrorCode()); | |
| if (LL.Modifier == LibraryLoad::Auto) | |
| return make_error<StringError>("Can't use -auto-lx to load JITDylib " + | |
| LL.LibName, | |
| inconvertibleErrorCode()); | |
| JD.addToLinkOrder(*LJD); | |
| continue; | |
| } | |
| if (LL.IsPath) { | |
| // Must be -weak_library. | |
| if (LL.Modifier == LibraryLoad::Weak) { | |
| if (auto G = LoadLibraryWeak(S, LL.LibName)) { | |
| JD.addGenerator(std::move(*G)); | |
| continue; | |
| } else | |
| return G.takeError(); | |
| } | |
| // Otherwise handle archive. | |
| auto G = AddArchive(JD, LL.LibName.c_str(), LL); | |
| if (!G) | |
| return createFileError(LL.LibName, G.takeError()); | |
| JD.addGenerator(std::move(*G)); | |
| LLVM_DEBUG({ | |
| dbgs() << "Adding generator for static library " << LL.LibName << " to " | |
| << JD.getName() << "\n"; | |
| }); | |
| continue; | |
| } | |
| // Otherwise look through the search paths. | |
| auto CurJDSearchPaths = JDSearchPaths[&JD]; | |
| for (StringRef SearchPath : | |
| concat<StringRef>(CurJDSearchPaths, SystemSearchPaths)) { | |
| for (auto LibExt : LL.CandidateExtensions) { | |
| SmallVector<char, 256> LibPath; | |
| LibPath.reserve(SearchPath.size() + strlen("lib") + LL.LibName.size() + | |
| LibExt.size() + 2); // +2 for pathsep, null term. | |
| llvm::append_range(LibPath, SearchPath); | |
| if (LibExt != ".lib" && LibExt != ".dll") | |
| sys::path::append(LibPath, "lib" + LL.LibName + LibExt); | |
| else | |
| sys::path::append(LibPath, LL.LibName + LibExt); | |
| LibPath.push_back('\0'); | |
| // Skip missing or non-regular paths. | |
| if (sys::fs::get_file_type(LibPath.data()) != | |
| sys::fs::file_type::regular_file) { | |
| continue; | |
| } | |
| file_magic Magic; | |
| if (auto EC = identify_magic(LibPath, Magic)) { | |
| // If there was an error loading the file then skip it. | |
| LLVM_DEBUG({ | |
| dbgs() << "Library search found \"" << LibPath | |
| << "\", but could not identify file type (" << EC.message() | |
| << "). Skipping.\n"; | |
| }); | |
| continue; | |
| } | |
| // We identified the magic. Assume that we can load it -- we'll reset | |
| // in the default case. | |
| LibFound = true; | |
| switch (Magic) { | |
| case file_magic::pecoff_executable: | |
| case file_magic::elf_shared_object: | |
| case file_magic::macho_dynamically_linked_shared_lib: { | |
| if (LL.Modifier == LibraryLoad::Weak) { | |
| if (auto G = LoadLibraryWeak(S, LibPath.data())) | |
| JD.addGenerator(std::move(*G)); | |
| else | |
| return G.takeError(); | |
| } else if (LL.Modifier == LibraryLoad::Auto) { | |
| if (auto Err = S.loadAndLinkAutoImportDLL(JD, LibPath.data())) | |
| return Err; | |
| } else { | |
| if (auto Err = S.loadAndLinkDynamicLibrary(JD, LibPath.data())) | |
| return Err; | |
| } | |
| break; | |
| } | |
| case file_magic::archive: | |
| case file_magic::macho_universal_binary: { | |
| auto G = AddArchive(JD, LibPath.data(), LL); | |
| if (!G) | |
| return G.takeError(); | |
| JD.addGenerator(std::move(*G)); | |
| LLVM_DEBUG({ | |
| dbgs() << "Adding generator for static library " << LibPath.data() | |
| << " to " << JD.getName() << "\n"; | |
| }); | |
| break; | |
| } | |
| case file_magic::tapi_file: | |
| assert(LL.Modifier == LibraryLoad::Weak && | |
| "TextAPI file not being loaded as weak?"); | |
| if (auto G = LoadLibraryWeak(S, LibPath.data())) | |
| JD.addGenerator(std::move(*G)); | |
| else | |
| return G.takeError(); | |
| break; | |
| default: | |
| // This file isn't a recognized library kind. | |
| LLVM_DEBUG({ | |
| dbgs() << "Library search found \"" << LibPath | |
| << "\", but file type is not supported. Skipping.\n"; | |
| }); | |
| LibFound = false; | |
| break; | |
| } | |
| if (LibFound) | |
| break; | |
| } | |
| if (LibFound) | |
| break; | |
| } | |
| if (!LibFound) | |
| return make_error<StringError>("While linking " + JD.getName() + | |
| ", could not find library for -l" + | |
| LL.LibName, | |
| inconvertibleErrorCode()); | |
| } | |
| // Add platform and process symbols if available. | |
| for (auto &[Idx, JD] : IdxToJD) { | |
| if (S.PlatformJD) | |
| JD->addToLinkOrder(*S.PlatformJD); | |
| if (S.ProcessSymsJD) | |
| JD->addToLinkOrder(*S.ProcessSymsJD); | |
| } | |
| return Error::success(); | |
| } | |
| static Error addSpeculationOrder(Session &S) { | |
| if (SpeculateOrder.empty()) | |
| return Error::success(); | |
| assert(S.LazyLinking && "SpeculateOrder set, but lazy linking not enabled"); | |
| assert(S.LazyLinking->Speculator && "SpeculatoOrder set, but no speculator"); | |
| auto SpecOrderBuffer = getFile(SpeculateOrder); | |
| if (!SpecOrderBuffer) | |
| return SpecOrderBuffer.takeError(); | |
| StringRef LineStream((*SpecOrderBuffer)->getBuffer()); | |
| std::vector<std::pair<std::string, SymbolStringPtr>> SpecOrder; | |
| size_t LineNumber = 0; | |
| while (!LineStream.empty()) { | |
| ++LineNumber; | |
| auto MakeSpecOrderErr = [&](StringRef Reason) { | |
| return make_error<StringError>("Error in speculation order file \"" + | |
| SpeculateOrder + "\" on line " + | |
| Twine(LineNumber) + ": " + Reason, | |
| inconvertibleErrorCode()); | |
| }; | |
| StringRef CurLine; | |
| std::tie(CurLine, LineStream) = LineStream.split('\n'); | |
| CurLine = CurLine.trim(); | |
| if (CurLine.empty()) | |
| continue; | |
| auto [JDName, FuncName] = CurLine.split(','); | |
| if (FuncName.empty()) | |
| return MakeSpecOrderErr("missing ',' separator"); | |
| JDName = JDName.trim(); | |
| if (JDName.empty()) | |
| return MakeSpecOrderErr("no value for column 1 (JIT Dylib name)"); | |
| FuncName = FuncName.trim(); | |
| if (FuncName.empty()) | |
| return MakeSpecOrderErr("no value for column 2 (function name)"); | |
| SpecOrder.push_back({JDName.str(), S.ES.intern(FuncName)}); | |
| } | |
| S.LazyLinking->Speculator->addSpeculationSuggestions(std::move(SpecOrder)); | |
| return Error::success(); | |
| } | |
| static Error addSessionInputs(Session &S) { | |
| std::map<unsigned, JITDylib *> IdxToJD; | |
| DenseSet<unsigned> LazyLinkIdxs; | |
| for (auto LLItr = LazyLink.begin(), LLEnd = LazyLink.end(); LLItr != LLEnd; | |
| ++LLItr) { | |
| if (*LLItr) | |
| LazyLinkIdxs.insert(LazyLink.getPosition(LLItr - LazyLink.begin()) + 1); | |
| } | |
| if (auto Err = createJITDylibs(S, IdxToJD)) | |
| return Err; | |
| if (auto Err = addAbsoluteSymbols(S, IdxToJD)) | |
| return Err; | |
| if (auto Err = addAliases(S, IdxToJD)) | |
| return Err; | |
| if (auto Err = addSectCreates(S, IdxToJD)) | |
| return Err; | |
| if (!TestHarnesses.empty()) | |
| if (auto Err = addTestHarnesses(S)) | |
| return Err; | |
| if (auto Err = addObjects(S, IdxToJD, LazyLinkIdxs)) | |
| return Err; | |
| if (auto Err = addLibraries(S, IdxToJD, LazyLinkIdxs)) | |
| return Err; | |
| if (auto Err = addSpeculationOrder(S)) | |
| return Err; | |
| return Error::success(); | |
| } | |
| namespace { | |
| struct TargetInfo { | |
| const Target *TheTarget; | |
| std::unique_ptr<MCSubtargetInfo> STI; | |
| std::unique_ptr<MCRegisterInfo> MRI; | |
| std::unique_ptr<MCAsmInfo> MAI; | |
| std::unique_ptr<MCContext> Ctx; | |
| std::unique_ptr<MCDisassembler> Disassembler; | |
| std::unique_ptr<MCInstrInfo> MII; | |
| std::unique_ptr<MCInstrAnalysis> MIA; | |
| std::unique_ptr<MCInstPrinter> InstPrinter; | |
| }; | |
| } // anonymous namespace | |
| static TargetInfo | |
| getTargetInfo(const Triple &TT, | |
| const SubtargetFeatures &TF = SubtargetFeatures()) { | |
| std::string ErrorStr; | |
| const Target *TheTarget = TargetRegistry::lookupTarget(TT, ErrorStr); | |
| if (!TheTarget) | |
| ExitOnErr(make_error<StringError>("Error accessing target '" + TT.str() + | |
| "': " + ErrorStr, | |
| inconvertibleErrorCode())); | |
| std::unique_ptr<MCSubtargetInfo> STI( | |
| TheTarget->createMCSubtargetInfo(TT, "", TF.getString())); | |
| if (!STI) | |
| ExitOnErr( | |
| make_error<StringError>("Unable to create subtarget for " + TT.str(), | |
| inconvertibleErrorCode())); | |
| std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(TT)); | |
| if (!MRI) | |
| ExitOnErr(make_error<StringError>("Unable to create target register info " | |
| "for " + | |
| TT.str(), | |
| inconvertibleErrorCode())); | |
| MCTargetOptions MCOptions; | |
| std::unique_ptr<MCAsmInfo> MAI( | |
| TheTarget->createMCAsmInfo(*MRI, TT, MCOptions)); | |
| if (!MAI) | |
| ExitOnErr( | |
| make_error<StringError>("Unable to create target asm info " + TT.str(), | |
| inconvertibleErrorCode())); | |
| auto Ctx = std::make_unique<MCContext>(Triple(TT.str()), *MAI, *MRI, *STI); | |
| std::unique_ptr<MCDisassembler> Disassembler( | |
| TheTarget->createMCDisassembler(*STI, *Ctx)); | |
| if (!Disassembler) | |
| ExitOnErr( | |
| make_error<StringError>("Unable to create disassembler for " + TT.str(), | |
| inconvertibleErrorCode())); | |
| std::unique_ptr<MCInstrInfo> MII(TheTarget->createMCInstrInfo()); | |
| if (!MII) | |
| ExitOnErr(make_error<StringError>("Unable to create instruction info for" + | |
| TT.str(), | |
| inconvertibleErrorCode())); | |
| std::unique_ptr<MCInstrAnalysis> MIA( | |
| TheTarget->createMCInstrAnalysis(MII.get())); | |
| if (!MIA) | |
| ExitOnErr(make_error<StringError>( | |
| "Unable to create instruction analysis for" + TT.str(), | |
| inconvertibleErrorCode())); | |
| std::unique_ptr<MCInstPrinter> InstPrinter( | |
| TheTarget->createMCInstPrinter(Triple(TT.str()), 0, *MAI, *MII, *MRI)); | |
| if (!InstPrinter) | |
| ExitOnErr(make_error<StringError>( | |
| "Unable to create instruction printer for" + TT.str(), | |
| inconvertibleErrorCode())); | |
| return {TheTarget, std::move(STI), std::move(MRI), | |
| std::move(MAI), std::move(Ctx), std::move(Disassembler), | |
| std::move(MII), std::move(MIA), std::move(InstPrinter)}; | |
| } | |
| static Error runChecks(Session &S, Triple TT, SubtargetFeatures Features) { | |
| if (CheckFiles.empty()) | |
| return Error::success(); | |
| S.waitForFilesLinkedFromEntryPointFile(); | |
| LLVM_DEBUG(dbgs() << "Running checks...\n"); | |
| auto IsSymbolValid = [&S](StringRef Symbol) { | |
| auto InternedSymbol = S.ES.intern(Symbol); | |
| return S.isSymbolRegistered(InternedSymbol); | |
| }; | |
| auto GetSymbolInfo = [&S](StringRef Symbol) { | |
| auto InternedSymbol = S.ES.intern(Symbol); | |
| return S.findSymbolInfo(InternedSymbol, "Can not get symbol info"); | |
| }; | |
| auto GetSectionInfo = [&S](StringRef FileName, StringRef SectionName) { | |
| return S.findSectionInfo(FileName, SectionName); | |
| }; | |
| auto GetStubInfo = [&S](StringRef FileName, StringRef SectionName, | |
| StringRef KindNameFilter) { | |
| return S.findStubInfo(FileName, SectionName, KindNameFilter); | |
| }; | |
| auto GetGOTInfo = [&S](StringRef FileName, StringRef SectionName) { | |
| return S.findGOTEntryInfo(FileName, SectionName); | |
| }; | |
| RuntimeDyldChecker Checker( | |
| IsSymbolValid, GetSymbolInfo, GetSectionInfo, GetStubInfo, GetGOTInfo, | |
| S.ES.getTargetTriple().isLittleEndian() ? llvm::endianness::little | |
| : llvm::endianness::big, | |
| TT, StringRef(), Features, dbgs()); | |
| std::string CheckLineStart = "# " + CheckName + ":"; | |
| for (auto &CheckFile : CheckFiles) { | |
| auto CheckerFileBuf = ExitOnErr(getFile(CheckFile)); | |
| if (!Checker.checkAllRulesInBuffer(CheckLineStart, &*CheckerFileBuf)) | |
| ExitOnErr(make_error<StringError>( | |
| "Some checks in " + CheckFile + " failed", inconvertibleErrorCode())); | |
| } | |
| return Error::success(); | |
| } | |
| static Error addSelfRelocations(LinkGraph &G) { | |
| auto TI = getTargetInfo(G.getTargetTriple()); | |
| for (auto *Sym : G.defined_symbols()) | |
| if (Sym->isCallable()) | |
| if (auto Err = addFunctionPointerRelocationsToCurrentSymbol( | |
| *Sym, G, *TI.Disassembler, *TI.MIA)) | |
| return Err; | |
| return Error::success(); | |
| } | |
| // Controller-interface descriptor for the ORC runtime's run-program wrapper. | |
| namespace llvm::orc::run_program_sps_ci { | |
| struct RunProgram { | |
| static constexpr SymbolNameSpec Name = | |
| SymbolNameSpec::c("__orc_rt_run_program_wrapper"); | |
| using SPSSig = int64_t(shared::SPSString, shared::SPSString, | |
| shared::SPSSequence<shared::SPSString>); | |
| }; | |
| } // namespace llvm::orc::run_program_sps_ci | |
| static Expected<ExecutorSymbolDef> getMainEntryPoint(Session &S) { | |
| return S.ES.lookup(S.JDSearchOrder, S.ES.intern(EntryPointName)); | |
| } | |
| static Expected<ExecutorSymbolDef> getOrcRuntimeEntryPoint(Session &S) { | |
| orc::Mangler Mangle(S.ES.getTargetTriple()); | |
| return S.ES.lookup( | |
| S.JDSearchOrder, | |
| S.ES.intern(Mangle.mangledCopy(run_program_sps_ci::RunProgram::Name))); | |
| } | |
| static Expected<ExecutorSymbolDef> getEntryPoint(Session &S) { | |
| ExecutorSymbolDef EntryPoint; | |
| // Find the entry-point function unconditionally, since we want to force | |
| // it to be materialized to collect stats. | |
| if (auto EP = getMainEntryPoint(S)) | |
| EntryPoint = *EP; | |
| else | |
| return EP.takeError(); | |
| LLVM_DEBUG({ | |
| dbgs() << "Using entry point \"" << EntryPointName | |
| << "\": " << formatv("{0:x16}", EntryPoint.getAddress()) << "\n"; | |
| }); | |
| // If we're running with the ORC runtime then replace the entry-point | |
| // with the __orc_rt_run_program symbol. | |
| if (!OrcRuntime.empty()) { | |
| if (auto EP = getOrcRuntimeEntryPoint(S)) | |
| EntryPoint = *EP; | |
| else | |
| return EP.takeError(); | |
| LLVM_DEBUG({ | |
| dbgs() << "(called via __orc_rt_run_program_wrapper at " | |
| << formatv("{0:x16}", EntryPoint.getAddress()) << ")\n"; | |
| }); | |
| } | |
| return EntryPoint; | |
| } | |
| static Expected<int> runWithRuntime(Session &S, ExecutorAddr EntryPointAddr) { | |
| StringRef DemangledEntryPoint = EntryPointName; | |
| if (S.ES.getTargetTriple().getObjectFormat() == Triple::MachO && | |
| DemangledEntryPoint.front() == '_') | |
| DemangledEntryPoint = DemangledEntryPoint.drop_front(); | |
| using RunProgramProxy = | |
| Proxy<int64_t(StringRef, StringRef, ArrayRef<std::string>)>; | |
| RunProgramProxy RunProgram( | |
| sps::ProxySpec<RunProgramProxy, run_program_sps_ci::RunProgram>::dispatch, | |
| EntryPointAddr); | |
| auto Result = | |
| RunProgram(S.ES, S.MainJD->getName(), DemangledEntryPoint, | |
| ArrayRef<std::string>( | |
| static_cast<std::vector<std::string> &>(InputArgv))); | |
| if (!Result) | |
| return Result.takeError(); | |
| return *Result; | |
| } | |
| static Expected<int> runWithoutRuntime(Session &S, | |
| ExecutorAddr EntryPointAddr) { | |
| return S.ES.getExecutorProcessControl().runAsMain(EntryPointAddr, InputArgv); | |
| } | |
| static Error symbolicateBacktraces() { | |
| auto Symtab = DumpedSymbolTable::Create(SymbolicateWith); | |
| if (!Symtab) | |
| return Symtab.takeError(); | |
| for (auto InputFile : InputFiles) { | |
| auto BacktraceBuffer = MemoryBuffer::getFileOrSTDIN(InputFile); | |
| if (!BacktraceBuffer) | |
| return createFileError(InputFile, BacktraceBuffer.getError()); | |
| outs() << Symtab->symbolicate((*BacktraceBuffer)->getBuffer()); | |
| } | |
| return Error::success(); | |
| } | |
| static Error waitingOnGraphReplay() { | |
| // Warn about ignored options. | |
| { | |
| bool PrintedHeader = false; | |
| for (auto &[OptName, Opt] : cl::getRegisteredOptions()) { | |
| if (Opt == &WaitingOnGraphReplay) | |
| continue; | |
| if (Opt->getNumOccurrences()) { | |
| if (!PrintedHeader) { | |
| errs() << "Warning: Running in -waiting-on-graph-replay mode. " | |
| "The following options will be ignored:\n"; | |
| PrintedHeader = true; | |
| } | |
| errs() << " " << OptName << "\n"; | |
| } | |
| } | |
| } | |
| // Read the replay buffer file. | |
| auto GraphOpsBuffer = getFile(WaitingOnGraphReplay); | |
| if (!GraphOpsBuffer) | |
| return GraphOpsBuffer.takeError(); | |
| using Replay = orc::detail::WaitingOnGraphOpReplay<uintptr_t, uintptr_t>; | |
| using Graph = typename Replay::Graph; | |
| using Replayer = typename Replay::Replayer; | |
| std::vector<typename Replay::Op> RecordedOps; | |
| // First read the buffer to build the Ops vector. Doing this up-front allows | |
| // us to avoid polluting the timings below with the cost of parsing. | |
| Error Err = Error::success(); | |
| for (auto &Op : | |
| orc::detail::readWaitingOnGraphOpsFromBuffer<uintptr_t, uintptr_t>( | |
| (*GraphOpsBuffer)->getBuffer(), Err)) | |
| RecordedOps.push_back(std::move(Op)); | |
| if (Err) | |
| return Err; | |
| // Now replay the Ops: | |
| Graph G; | |
| Replayer R(G); | |
| outs() << "Replaying WaitingOnGraph operations from " << WaitingOnGraphReplay | |
| << "...\n"; | |
| auto ReplayStart = std::chrono::high_resolution_clock::now(); | |
| for (auto &Op : RecordedOps) | |
| R.replay(std::move(Op)); | |
| auto ReplayEnd = std::chrono::high_resolution_clock::now(); | |
| std::chrono::duration<double> ReplayDiff = ReplayEnd - ReplayStart; | |
| outs() << ReplayDiff.count() << "s to replay " << RecordedOps.size() | |
| << " ops (wall-clock time)\n"; | |
| return Error::success(); | |
| } | |
| namespace { | |
| struct JITLinkTimers { | |
| TimerGroup JITLinkTG{"llvm-jitlink timers", "timers for llvm-jitlink phases"}; | |
| Timer LoadObjectsTimer{"load", "time to load/add object files", JITLinkTG}; | |
| Timer LinkTimer{"link", "time to link object files", JITLinkTG}; | |
| Timer RunTimer{"run", "time to execute jitlink'd code", JITLinkTG}; | |
| }; | |
| } // namespace | |
| int main(int argc, char *argv[]) { | |
| InitLLVM X(argc, argv); | |
| InitializeAllTargetInfos(); | |
| InitializeAllTargetMCs(); | |
| InitializeAllDisassemblers(); | |
| cl::HideUnrelatedOptions({&JITLinkCategory, &getColorCategory()}); | |
| cl::ParseCommandLineOptions(argc, argv, "llvm jitlink tool"); | |
| ExitOnErr.setBanner(std::string(argv[0]) + ": "); | |
| // Check for WaitingOnGraph replay mode. | |
| if (!WaitingOnGraphReplay.empty()) { | |
| ExitOnErr(waitingOnGraphReplay()); | |
| return 0; | |
| } | |
| /// If timers are enabled, create a JITLinkTimers instance. | |
| std::unique_ptr<JITLinkTimers> Timers = | |
| ShowTimes ? std::make_unique<JITLinkTimers>() : nullptr; | |
| auto [TT, Features] = getFirstFileTripleAndFeatures(); | |
| ExitOnErr(sanitizeArguments(TT, argv[0])); | |
| if (!SymbolicateWith.empty()) { | |
| ExitOnErr(symbolicateBacktraces()); | |
| return 0; | |
| } | |
| auto S = ExitOnErr(Session::Create(TT, Features)); | |
| enableStatistics(*S, !OrcRuntime.empty()); | |
| { | |
| TimeRegion TR(Timers ? &Timers->LoadObjectsTimer : nullptr); | |
| ExitOnErr(addSessionInputs(*S)); | |
| } | |
| if (PhonyExternals) | |
| addPhonyExternalsGenerator(*S); | |
| if (ShowInitialExecutionSessionState) | |
| S->ES.dump(outs()); | |
| Expected<ExecutorSymbolDef> EntryPoint((ExecutorSymbolDef())); | |
| { | |
| ExpectedAsOutParameter<ExecutorSymbolDef> _(&EntryPoint); | |
| TimeRegion TR(Timers ? &Timers->LinkTimer : nullptr); | |
| EntryPoint = getEntryPoint(*S); | |
| } | |
| // Print any reports regardless of whether we succeeded or failed. | |
| if (ShowEntryExecutionSessionState) | |
| S->ES.dump(outs()); | |
| if (ShowAddrs) | |
| S->dumpSessionInfo(outs()); | |
| if (!EntryPoint) { | |
| if (Timers) | |
| Timers->JITLinkTG.printAll(errs()); | |
| reportLLVMJITLinkError(EntryPoint.takeError()); | |
| ExitOnErr(S->ES.endSession()); | |
| return 1; | |
| } | |
| ExitOnErr(runChecks(*S, std::move(TT), std::move(Features))); | |
| int Result = 0; | |
| if (!NoExec) { | |
| LLVM_DEBUG(dbgs() << "Running \"" << EntryPointName << "\"...\n"); | |
| TimeRegion TR(Timers ? &Timers->RunTimer : nullptr); | |
| if (!OrcRuntime.empty()) | |
| Result = ExitOnErr(runWithRuntime(*S, EntryPoint->getAddress())); | |
| else | |
| Result = ExitOnErr(runWithoutRuntime(*S, EntryPoint->getAddress())); | |
| if (ShowJITResult) | |
| outs() << "JIT result: " << Result << "\n"; | |
| } | |
| // Destroy the session. | |
| ExitOnErr(S->ES.endSession()); | |
| S.reset(); | |
| if (Timers) | |
| Timers->JITLinkTG.printAll(errs()); | |
| // If the executing code set a test result override then use that. | |
| if (UseTestResultOverride) | |
| Result = TestResultOverride; | |
| return Result; | |
| } |