diff options
| author | Lang Hames <lhames@gmail.com> | 2019-04-20 17:10:34 +0000 |
|---|---|---|
| committer | Lang Hames <lhames@gmail.com> | 2019-04-20 17:10:34 +0000 |
| commit | 11c8dfa583f50edb7c81e5c84cc7797133e9c5c1 (patch) | |
| tree | 00883ae768b29dddf3148c867922514a0aad2ed7 /llvm/unittests | |
| parent | 3980d1ca6b39dd37b12b873a19847bbebd5a07bc (diff) | |
| download | bcm5719-llvm-11c8dfa583f50edb7c81e5c84cc7797133e9c5c1.tar.gz bcm5719-llvm-11c8dfa583f50edb7c81e5c84cc7797133e9c5c1.zip | |
Initial implementation of JITLink - A replacement for RuntimeDyld.
Summary:
JITLink is a jit-linker that performs the same high-level task as RuntimeDyld:
it parses relocatable object files and makes their contents runnable in a target
process.
JITLink aims to improve on RuntimeDyld in several ways:
(1) A clear design intended to maximize code-sharing while minimizing coupling.
RuntimeDyld has been developed in an ad-hoc fashion for a number of years and
this had led to intermingling of code for multiple architectures (e.g. in
RuntimeDyldELF::processRelocationRef) in a way that makes the code more
difficult to read, reason about, extend. JITLink is designed to isolate
format and architecture specific code, while still sharing generic code.
(2) Support for native code models.
RuntimeDyld required the use of large code models (where calls to external
functions are made indirectly via registers) for many of platforms due to its
restrictive model for stub generation (one "stub" per symbol). JITLink allows
arbitrary mutation of the atom graph, allowing both GOT and PLT atoms to be
added naturally.
(3) Native support for asynchronous linking.
JITLink uses asynchronous calls for symbol resolution and finalization: these
callbacks are passed a continuation function that they must call to complete the
linker's work. This allows for cleaner interoperation with the new concurrent
ORC JIT APIs, while still being easily implementable in synchronous style if
asynchrony is not needed.
To maximise sharing, the design has a hierarchy of common code:
(1) Generic atom-graph data structure and algorithms (e.g. dead stripping and
| memory allocation) that are intended to be shared by all architectures.
|
+ -- (2) Shared per-format code that utilizes (1), e.g. Generic MachO to
| atom-graph parsing.
|
+ -- (3) Architecture specific code that uses (1) and (2). E.g.
JITLinkerMachO_x86_64, which adds x86-64 specific relocation
support to (2) to build and patch up the atom graph.
To support asynchronous symbol resolution and finalization, the callbacks for
these operations take continuations as arguments:
using JITLinkAsyncLookupContinuation =
std::function<void(Expected<AsyncLookupResult> LR)>;
using JITLinkAsyncLookupFunction =
std::function<void(const DenseSet<StringRef> &Symbols,
JITLinkAsyncLookupContinuation LookupContinuation)>;
using FinalizeContinuation = std::function<void(Error)>;
virtual void finalizeAsync(FinalizeContinuation OnFinalize);
In addition to its headline features, JITLink also makes other improvements:
- Dead stripping support: symbols that are not used (e.g. redundant ODR
definitions) are discarded, and take up no memory in the target process
(In contrast, RuntimeDyld supported pointer equality for weak definitions,
but the redundant definitions stayed resident in memory).
- Improved exception handling support. JITLink provides a much more extensive
eh-frame parser than RuntimeDyld, and is able to correctly fix up many
eh-frame sections that RuntimeDyld currently (silently) fails on.
- More extensive validation and error handling throughout.
This initial patch supports linking MachO/x86-64 only. Work on support for
other architectures and formats will happen in-tree.
Differential Revision: https://reviews.llvm.org/D58704
llvm-svn: 358818
Diffstat (limited to 'llvm/unittests')
5 files changed, 668 insertions, 0 deletions
diff --git a/llvm/unittests/ExecutionEngine/CMakeLists.txt b/llvm/unittests/ExecutionEngine/CMakeLists.txt index 302de9943ff..1bf210556b6 100644 --- a/llvm/unittests/ExecutionEngine/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/CMakeLists.txt @@ -12,6 +12,7 @@ add_llvm_unittest(ExecutionEngineTests ExecutionEngineTest.cpp ) +add_subdirectory(JITLink) add_subdirectory(Orc) # Include MCJIT tests only if native arch is a built JIT target. diff --git a/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt b/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt new file mode 100644 index 00000000000..c424e684671 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + JITLink + MC + Object + RuntimeDyld + Support + ) + +add_llvm_unittest(JITLinkTests + JITLinkTestCommon.cpp + JITLinkTest_MachO_x86_64_Tests.cpp + ) + +target_link_libraries(JITLinkTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/ExecutionEngine/JITLink/JITLinkTestCommon.cpp b/llvm/unittests/ExecutionEngine/JITLink/JITLinkTestCommon.cpp new file mode 100644 index 00000000000..786b00e7985 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/JITLinkTestCommon.cpp @@ -0,0 +1,232 @@ +//===------- JITLinkTestCommon.cpp - Common code for JITLink tests --------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "JITLinkTestCommon.h" +#include "llvm/MC/MCCodeEmitter.h" +#include "llvm/MC/MCObjectWriter.h" +#include "llvm/MC/MCParser/MCTargetAsmParser.h" +#include "llvm/Support/TargetSelect.h" + +using namespace llvm::jitlink; +namespace llvm { + +JITLinkTestCommon::TestResources::TestResources(StringRef AsmSrc, + StringRef TripleStr, bool PIC, + bool LargeCodeModel, + MCTargetOptions Options) + : ObjStream(ObjBuffer), Options(std::move(Options)) { + Triple TT(Triple::normalize(TripleStr)); + initializeTripleSpecifics(TT); + initializeTestSpecifics(AsmSrc, TT, PIC, LargeCodeModel); +} + +MemoryBufferRef +JITLinkTestCommon::TestResources::getTestObjectBufferRef() const { + return MemoryBufferRef(StringRef(ObjBuffer.data(), ObjBuffer.size()), + "Test object"); +} + +void JITLinkTestCommon::TestResources::initializeTripleSpecifics(Triple &TT) { + std::string Error; + TheTarget = TargetRegistry::lookupTarget("", TT, Error); + + if (!TheTarget) + report_fatal_error(Error); + + MRI.reset(TheTarget->createMCRegInfo(TT.getTriple())); + if (!MRI) + report_fatal_error("Could not build MCRegisterInfo for triple"); + + MAI.reset(TheTarget->createMCAsmInfo(*MRI, TT.getTriple())); + if (!MAI) + report_fatal_error("Could not build MCAsmInfo for triple"); + + MCII.reset(TheTarget->createMCInstrInfo()); + if (!MCII) + report_fatal_error("Could not build MCInstrInfo for triple"); + + STI.reset(TheTarget->createMCSubtargetInfo(TT.getTriple(), "", "")); + if (!STI) + report_fatal_error("Could not build MCSubtargetInfo for triple"); + + DisCtx = llvm::make_unique<MCContext>(MAI.get(), MRI.get(), nullptr); + Dis.reset(TheTarget->createMCDisassembler(*STI, *DisCtx)); + + if (!Dis) + report_fatal_error("Could not build MCDisassembler"); +} + +void JITLinkTestCommon::TestResources::initializeTestSpecifics( + StringRef AsmSrc, const Triple &TT, bool PIC, bool LargeCodeModel) { + SrcMgr.AddNewSourceBuffer(MemoryBuffer::getMemBuffer(AsmSrc), SMLoc()); + AsCtx = llvm::make_unique<MCContext>(MAI.get(), MRI.get(), &MOFI, &SrcMgr); + MOFI.InitMCObjectFileInfo(TT, PIC, *AsCtx, LargeCodeModel); + + std::unique_ptr<MCCodeEmitter> CE( + TheTarget->createMCCodeEmitter(*MCII, *MRI, *AsCtx)); + if (!CE) + report_fatal_error("Could not build MCCodeEmitter"); + + std::unique_ptr<MCAsmBackend> MAB( + TheTarget->createMCAsmBackend(*STI, *MRI, Options)); + if (!MAB) + report_fatal_error("Could not build MCAsmBackend for test"); + + std::unique_ptr<MCObjectWriter> MOW(MAB->createObjectWriter(ObjStream)); + + MOS.reset(TheTarget->createMCObjectStreamer( + TT, *AsCtx, std::move(MAB), std::move(MOW), std::move(CE), *STI, + Options.MCRelaxAll, Options.MCIncrementalLinkerCompatible, false)); + + std::unique_ptr<MCAsmParser> MAP( + createMCAsmParser(SrcMgr, *AsCtx, *MOS, *MAI)); + std::unique_ptr<MCTargetAsmParser> TAP( + TheTarget->createMCAsmParser(*STI, *MAP, *MCII, Options)); + + if (!TAP) + report_fatal_error("Could not build MCTargetAsmParser for test"); + + MAP->setTargetParser(*TAP); + + if (MAP->Run(false)) + report_fatal_error("Failed to parse test case"); +} + +JITLinkTestCommon::TestJITLinkContext::TestJITLinkContext( + TestResources &TR, TestCaseFunction TestCase) + : TR(TR), TestCase(std::move(TestCase)) {} + +JITLinkTestCommon::TestJITLinkContext & +JITLinkTestCommon::TestJITLinkContext::setMemoryManager( + std::unique_ptr<JITLinkMemoryManager> MM) { + assert(!MemMgr && "Memory manager already set"); + MemMgr = std::move(MM); + return *this; +} + +JITLinkMemoryManager & +JITLinkTestCommon::TestJITLinkContext::getMemoryManager() { + if (!MemMgr) + MemMgr = llvm::make_unique<InProcessMemoryManager>(); + return *MemMgr; +} + +MemoryBufferRef JITLinkTestCommon::TestJITLinkContext::getObjectBuffer() const { + return TR.getTestObjectBufferRef(); +} + +void JITLinkTestCommon::TestJITLinkContext::notifyFailed(Error Err) { + ADD_FAILURE() << "Unexpected failure: " << toString(std::move(Err)); +} + +void JITLinkTestCommon::TestJITLinkContext::lookup( + const DenseSet<StringRef> &Symbols, + JITLinkAsyncLookupContinuation LookupContinuation) { + jitlink::AsyncLookupResult LookupResult; + DenseSet<StringRef> MissingSymbols; + for (const auto &Symbol : Symbols) { + auto I = Externals.find(Symbol); + if (I != Externals.end()) + LookupResult[Symbol] = I->second; + else + MissingSymbols.insert(Symbol); + } + + if (MissingSymbols.empty()) + LookupContinuation(std::move(LookupResult)); + else { + std::string ErrMsg; + { + raw_string_ostream ErrMsgStream(ErrMsg); + ErrMsgStream << "Failed to resolve external symbols: ["; + for (auto &Sym : MissingSymbols) + ErrMsgStream << " " << Sym; + ErrMsgStream << " ]\n"; + } + LookupContinuation( + make_error<StringError>(std::move(ErrMsg), inconvertibleErrorCode())); + } +} + +void JITLinkTestCommon::TestJITLinkContext::notifyResolved(AtomGraph &G) { + if (NotifyResolved) + NotifyResolved(G); +} + +void JITLinkTestCommon::TestJITLinkContext::notifyFinalized( + std::unique_ptr<JITLinkMemoryManager::Allocation> A) { + if (NotifyFinalized) + NotifyFinalized(std::move(A)); +} + +Error JITLinkTestCommon::TestJITLinkContext::modifyPassConfig( + const Triple &TT, PassConfiguration &Config) { + if (TestCase) + Config.PostFixupPasses.push_back([&](AtomGraph &G) -> Error { + TestCase(G); + return Error::success(); + }); + return Error::success(); +} + +JITLinkTestCommon::JITLinkTestCommon() { initializeLLVMTargets(); } + +Expected<std::pair<MCInst, size_t>> +JITLinkTestCommon::disassemble(const MCDisassembler &Dis, + jitlink::DefinedAtom &Atom, size_t Offset) { + ArrayRef<uint8_t> InstBuffer( + reinterpret_cast<const uint8_t *>(Atom.getContent().data()) + Offset, + Atom.getContent().size() - Offset); + + MCInst Inst; + uint64_t InstSize; + auto Status = + Dis.getInstruction(Inst, InstSize, InstBuffer, 0, nulls(), nulls()); + + if (Status != MCDisassembler::Success) + return make_error<StringError>("Could not disassemble instruction", + inconvertibleErrorCode()); + + return std::make_pair(Inst, InstSize); +} + +Expected<int64_t> +JITLinkTestCommon::decodeImmediateOperand(const MCDisassembler &Dis, + jitlink::DefinedAtom &Atom, + size_t OpIdx, size_t Offset) { + auto InstAndSize = disassemble(Dis, Atom, Offset); + if (!InstAndSize) + return InstAndSize.takeError(); + + if (OpIdx >= InstAndSize->first.getNumOperands()) + return make_error<StringError>("Invalid operand index", + inconvertibleErrorCode()); + + auto &Op = InstAndSize->first.getOperand(OpIdx); + + if (!Op.isImm()) + return make_error<StringError>("Operand at index is not immediate", + inconvertibleErrorCode()); + + return Op.getImm(); +} + +bool JITLinkTestCommon::AreTargetsInitialized = false; + +void JITLinkTestCommon::initializeLLVMTargets() { + if (!AreTargetsInitialized) { + InitializeAllTargets(); + InitializeAllTargetMCs(); + InitializeAllAsmParsers(); + InitializeAllAsmPrinters(); + InitializeAllDisassemblers(); + AreTargetsInitialized = true; + } +} + +} // end namespace llvm diff --git a/llvm/unittests/ExecutionEngine/JITLink/JITLinkTestCommon.h b/llvm/unittests/ExecutionEngine/JITLink/JITLinkTestCommon.h new file mode 100644 index 00000000000..c19232e7bbc --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/JITLinkTestCommon.h @@ -0,0 +1,196 @@ +//===---- JITLinkTestCommon.h - Utilities for Orc Unit Tests ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Common utilities for JITLink unit tests. +// +//===----------------------------------------------------------------------===// + + +#ifndef LLVM_UNITTESTS_EXECUTIONENGINE_JITLINK_JITLINKTESTCOMMON_H +#define LLVM_UNITTESTS_EXECUTIONENGINE_JITLINK_JITLINKTESTCOMMON_H + +#include "llvm/ADT/Triple.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/MC/MCAsmBackend.h" +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCDisassembler/MCDisassembler.h" +#include "llvm/MC/MCInstrInfo.h" +#include "llvm/MC/MCObjectFileInfo.h" +#include "llvm/MC/MCObjectStreamer.h" +#include "llvm/MC/MCParser/MCAsmParser.h" +#include "llvm/MC/MCRegisterInfo.h" +#include "llvm/MC/MCSubtargetInfo.h" +#include "llvm/MC/MCTargetOptions.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TargetRegistry.h" + +#include "gtest/gtest.h" + +namespace llvm { + +class JITLinkTestCommon { +public: + + class TestResources { + public: + TestResources(StringRef AsmSrc, StringRef TripleStr, bool PIC, + bool LargeCodeModel, MCTargetOptions Options); + + MemoryBufferRef getTestObjectBufferRef() const; + + const MCDisassembler &getDisassembler() const { return *Dis; } + + private: + void initializeTripleSpecifics(Triple &TT); + void initializeTestSpecifics(StringRef AsmSource, const Triple &TT, + bool PIC, bool LargeCodeModel); + + const Target *TheTarget = nullptr; + SourceMgr SrcMgr; + SmallVector<char, 0> ObjBuffer; + raw_svector_ostream ObjStream; + + MCTargetOptions Options; + std::unique_ptr<MCRegisterInfo> MRI; + std::unique_ptr<MCAsmInfo> MAI; + std::unique_ptr<MCInstrInfo> MCII; + std::unique_ptr<MCSubtargetInfo> STI; + + MCObjectFileInfo MOFI; + std::unique_ptr<MCContext> AsCtx; + std::unique_ptr<MCStreamer> MOS; + + std::unique_ptr<MCContext> DisCtx; + std::unique_ptr<const MCDisassembler> Dis; + }; + + class TestJITLinkContext : public jitlink::JITLinkContext { + public: + using TestCaseFunction = std::function<void(jitlink::AtomGraph &)>; + + using NotifyResolvedFunction = std::function<void(jitlink::AtomGraph &G)>; + + using NotifyFinalizedFunction = std::function<void( + std::unique_ptr<jitlink::JITLinkMemoryManager::Allocation>)>; + + TestJITLinkContext(TestResources &TR, TestCaseFunction TestCase); + + StringMap<JITEvaluatedSymbol> &externals() { return Externals; } + + TestJITLinkContext & + setNotifyResolved(NotifyResolvedFunction NotifyResolved); + + TestJITLinkContext & + setNotifyFinalized(NotifyFinalizedFunction NotifyFinalized); + + TestJITLinkContext & + setMemoryManager(std::unique_ptr<jitlink::JITLinkMemoryManager> MM); + + jitlink::JITLinkMemoryManager &getMemoryManager() override; + + MemoryBufferRef getObjectBuffer() const override; + + void notifyFailed(Error Err) override; + + void + lookup(const DenseSet<StringRef> &Symbols, + jitlink::JITLinkAsyncLookupContinuation LookupContinuation) override; + + void notifyResolved(jitlink::AtomGraph &G) override; + + void notifyFinalized( + std::unique_ptr<jitlink::JITLinkMemoryManager::Allocation> A) override; + + Error modifyPassConfig(const Triple &TT, + jitlink::PassConfiguration &Config) override; + + private: + TestResources &TR; + TestCaseFunction TestCase; + NotifyResolvedFunction NotifyResolved; + NotifyFinalizedFunction NotifyFinalized; + std::unique_ptr<MemoryBuffer> ObjBuffer; + std::unique_ptr<jitlink::JITLinkMemoryManager> MemMgr; + StringMap<JITEvaluatedSymbol> Externals; + }; + + JITLinkTestCommon(); + + std::unique_ptr<TestResources> + getTestResources(StringRef AsmSrc, StringRef Triple, bool PIC, + bool LargeCodeModel, MCTargetOptions Options) const { + return llvm::make_unique<TestResources>(AsmSrc, Triple, PIC, LargeCodeModel, + std::move(Options)); + } + + template <typename T> + static Expected<T> readInt(jitlink::AtomGraph &G, jitlink::DefinedAtom &A, + size_t Offset = 0) { + if (Offset + sizeof(T) > A.getContent().size()) + return make_error<StringError>("Reading past end of atom content", + inconvertibleErrorCode()); + return support::endian::read<T, 1>(A.getContent().data() + Offset, + G.getEndianness()); + } + + template <typename T> + static Expected<T> readInt(jitlink::AtomGraph &G, StringRef AtomName, + size_t Offset = 0) { + auto DA = G.findDefinedAtomByName(AtomName); + if (!DA) + return DA.takeError(); + return readInt<T>(G, *DA); + } + + static Expected<std::pair<MCInst, size_t>> + disassemble(const MCDisassembler &Dis, jitlink::DefinedAtom &Atom, + size_t Offset = 0); + + static Expected<int64_t> decodeImmediateOperand(const MCDisassembler &Dis, + jitlink::DefinedAtom &Atom, + size_t OpIdx, + size_t Offset = 0); + + static jitlink::Atom &atom(jitlink::AtomGraph &G, StringRef Name) { + return G.getAtomByName(Name); + } + + static jitlink::DefinedAtom &definedAtom(jitlink::AtomGraph &G, + StringRef Name) { + return G.getDefinedAtomByName(Name); + } + + static JITTargetAddress atomAddr(jitlink::AtomGraph &G, StringRef Name) { + return atom(G, Name).getAddress(); + } + + template <typename PredT> + static size_t countEdgesMatching(jitlink::DefinedAtom &DA, + const PredT &Pred) { + return std::count_if(DA.edges().begin(), DA.edges().end(), Pred); + } + + template <typename PredT> + static size_t countEdgesMatching(jitlink::AtomGraph &G, StringRef Name, + const PredT &Pred) { + return countEdgesMatching(definedAtom(G, Name), Pred); + } + +private: + + static bool AreTargetsInitialized; + void initializeLLVMTargets(); + + DenseMap<StringRef, JITEvaluatedSymbol> Externals; +}; + +} // end namespace llvm + +#endif diff --git a/llvm/unittests/ExecutionEngine/JITLink/JITLinkTest_MachO_x86_64_Tests.cpp b/llvm/unittests/ExecutionEngine/JITLink/JITLinkTest_MachO_x86_64_Tests.cpp new file mode 100644 index 00000000000..f8728c78a03 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/JITLinkTest_MachO_x86_64_Tests.cpp @@ -0,0 +1,224 @@ +//===---- JITLinkTest_MachO_x86_64.cpp - Tests for JITLink MachO/x86-64 ---===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "JITLinkTestCommon.h" + +#include "llvm/ADT/DenseSet.h" +#include "llvm/ExecutionEngine/JITLink/JITLink_MachO_x86_64.h" +#include "llvm/Testing/Support/Error.h" + +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::jitlink; +using namespace llvm::jitlink::MachO_x86_64_Edges; + +namespace { + +class JITLinkTest_MachO_x86_64 : public JITLinkTestCommon, + public testing::Test { +public: + using BasicVerifyGraphFunction = + std::function<void(AtomGraph &, const MCDisassembler &)>; + + void runBasicVerifyGraphTest(StringRef AsmSrc, StringRef Triple, + StringMap<JITEvaluatedSymbol> Externals, + bool PIC, bool LargeCodeModel, + MCTargetOptions Options, + BasicVerifyGraphFunction RunGraphTest) { + auto TR = getTestResources(AsmSrc, Triple, PIC, LargeCodeModel, + std::move(Options)); + + auto JTCtx = llvm::make_unique<TestJITLinkContext>( + *TR, [&](AtomGraph &G) { RunGraphTest(G, TR->getDisassembler()); }); + + JTCtx->externals() = std::move(Externals); + + jitLink_MachO_x86_64(std::move(JTCtx)); + } + +protected: + static void verifyIsPointerTo(AtomGraph &G, DefinedAtom &A, Atom &Target) { + EXPECT_EQ(A.edges_size(), 1U) << "Incorrect number of edges for pointer"; + if (A.edges_size() != 1U) + return; + auto &E = *A.edges().begin(); + EXPECT_EQ(E.getKind(), Pointer64) + << "Expected pointer to have a pointer64 relocation"; + EXPECT_EQ(&E.getTarget(), &Target) << "Expected edge to point at target"; + EXPECT_THAT_EXPECTED(readInt<uint64_t>(G, A), HasValue(Target.getAddress())) + << "Pointer does not point to target"; + } + + static void verifyGOTLoad(AtomGraph &G, DefinedAtom &A, Edge &E, + Atom &Target) { + EXPECT_EQ(E.getAddend(), 0U) << "Expected GOT load to have a zero addend"; + EXPECT_TRUE(E.getTarget().isDefined()) + << "GOT entry should be a defined atom"; + if (!E.getTarget().isDefined()) + return; + + verifyIsPointerTo(G, static_cast<DefinedAtom &>(E.getTarget()), Target); + } + + static void verifyCall(const MCDisassembler &Dis, AtomGraph &G, + DefinedAtom &Caller, Edge &E, Atom &Callee) { + EXPECT_EQ(E.getKind(), Branch32) << "Edge is not a Branch32"; + EXPECT_EQ(E.getAddend(), 0U) << "Expected no addend on stub call"; + EXPECT_EQ(&E.getTarget(), &Callee) + << "Edge does not point at expected callee"; + + JITTargetAddress FixupAddress = Caller.getAddress() + E.getOffset(); + uint64_t PCRelDelta = Callee.getAddress() - (FixupAddress + 4); + + EXPECT_THAT_EXPECTED( + decodeImmediateOperand(Dis, Caller, 0, E.getOffset() - 1), + HasValue(PCRelDelta)); + } + + static void verifyIndirectCall(const MCDisassembler &Dis, AtomGraph &G, + DefinedAtom &Caller, Edge &E, Atom &Callee) { + EXPECT_EQ(E.getKind(), PCRel32) << "Edge is not a PCRel32"; + EXPECT_EQ(E.getAddend(), 0) << "Expected no addend on stub cal"; + EXPECT_TRUE(E.getTarget().isDefined()) << "Target is not a defined atom"; + if (!E.getTarget().isDefined()) + return; + verifyIsPointerTo(G, static_cast<DefinedAtom &>(E.getTarget()), Callee); + + JITTargetAddress FixupAddress = Caller.getAddress() + E.getOffset(); + uint64_t PCRelDelta = E.getTarget().getAddress() - (FixupAddress + 4); + + EXPECT_THAT_EXPECTED( + decodeImmediateOperand(Dis, Caller, 3, E.getOffset() - 2), + HasValue(PCRelDelta)); + } + + static void verifyCallViaStub(const MCDisassembler &Dis, AtomGraph &G, + DefinedAtom &Caller, Edge &E, Atom &Callee) { + verifyCall(Dis, G, Caller, E, E.getTarget()); + + if (!E.getTarget().isDefined()) { + ADD_FAILURE() << "Edge target is not a stub"; + return; + } + + auto &StubAtom = static_cast<DefinedAtom &>(E.getTarget()); + EXPECT_EQ(StubAtom.edges_size(), 1U) + << "Expected one edge from stub to target"; + + auto &StubEdge = *StubAtom.edges().begin(); + + verifyIndirectCall(Dis, G, static_cast<DefinedAtom &>(StubAtom), StubEdge, + Callee); + } +}; + +} // end anonymous namespace + +// Test each operation on LegacyObjectTransformLayer. +TEST_F(JITLinkTest_MachO_x86_64, BasicRelocations) { + runBasicVerifyGraphTest( + R"( + .section __TEXT,__text,regular,pure_instructions + .build_version macos, 10, 14 + .globl _bar + .p2align 4, 0x90 + _bar: + callq _baz + + .globl _foo + .p2align 4, 0x90 + _foo: + callq _bar + _foo.1: + movq _y@GOTPCREL(%rip), %rcx + _foo.2: + movq _p(%rip), %rdx + + .section __DATA,__data + .globl _x + .p2align 2 + _x: + .long 42 + + .globl _p + .p2align 3 + _p: + .quad _x + + .subsections_via_symbols)", + "x86_64-apple-macosx10.14", + {{"_y", JITEvaluatedSymbol(0xdeadbeef, JITSymbolFlags::Exported)}, + {"_baz", JITEvaluatedSymbol(0xcafef00d, JITSymbolFlags::Exported)}}, + true, false, MCTargetOptions(), + [](AtomGraph &G, const MCDisassembler &Dis) { + // Name the atoms in the asm above. + auto &Baz = atom(G, "_baz"); + auto &Y = atom(G, "_y"); + + auto &Bar = definedAtom(G, "_bar"); + auto &Foo = definedAtom(G, "_foo"); + auto &Foo_1 = definedAtom(G, "_foo.1"); + auto &Foo_2 = definedAtom(G, "_foo.2"); + auto &X = definedAtom(G, "_x"); + auto &P = definedAtom(G, "_p"); + + // Check unsigned reloc for _p + { + EXPECT_EQ(P.edges_size(), 1U) << "Unexpected number of relocations"; + EXPECT_EQ(P.edges().begin()->getKind(), Pointer64) + << "Unexpected edge kind for _p"; + EXPECT_THAT_EXPECTED(readInt<uint64_t>(G, P), + HasValue(X.getAddress())) + << "Unsigned relocation did not apply correctly"; + } + + // Check that _bar is a call-via-stub to _baz. + // This will check that the call goes to a stub, that the stub is an + // indirect call, and that the pointer for the indirect call points to + // baz. + { + EXPECT_EQ(Bar.edges_size(), 1U) + << "Incorrect number of edges for bar"; + EXPECT_EQ(Bar.edges().begin()->getKind(), Branch32) + << "Unexpected edge kind for _bar"; + verifyCallViaStub(Dis, G, Bar, *Bar.edges().begin(), Baz); + } + + // Check that _foo is a direct call to _bar. + { + EXPECT_EQ(Foo.edges_size(), 1U) + << "Incorrect number of edges for foo"; + EXPECT_EQ(Foo.edges().begin()->getKind(), Branch32); + verifyCall(Dis, G, Foo, *Foo.edges().begin(), Bar); + } + + // Check .got load in _foo.1 + { + EXPECT_EQ(Foo_1.edges_size(), 1U) + << "Incorrect number of edges for foo_1"; + EXPECT_EQ(Foo_1.edges().begin()->getKind(), PCRel32); + verifyGOTLoad(G, Foo_1, *Foo_1.edges().begin(), Y); + } + + // Check PCRel ref to _p in _foo.2 + { + EXPECT_EQ(Foo_2.edges_size(), 1U) + << "Incorrect number of edges for foo_2"; + EXPECT_EQ(Foo_2.edges().begin()->getKind(), PCRel32); + + JITTargetAddress FixupAddress = + Foo_2.getAddress() + Foo_2.edges().begin()->getOffset(); + uint64_t PCRelDelta = P.getAddress() - (FixupAddress + 4); + + EXPECT_THAT_EXPECTED(decodeImmediateOperand(Dis, Foo_2, 4, 0), + HasValue(PCRelDelta)) + << "PCRel load does not reference expected target"; + } + }); +} |

