diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/i386.h b/llvm/include/llvm/ExecutionEngine/JITLink/i386.h --- a/llvm/include/llvm/ExecutionEngine/JITLink/i386.h +++ b/llvm/include/llvm/ExecutionEngine/JITLink/i386.h @@ -81,7 +81,19 @@ /// PCRel16, - /// A 64-bit GOT delta. + /// A 32-bit delta. + /// + /// Delta from the fixup to the target. + /// + /// Fixup expression: + /// Fixup <- Target - Fixup + Addend : int64 + /// + /// Errors: + /// - The result of the fixup expression must fit into an int32, otherwise + /// an out-of-range error will be returned. + Delta32, + + /// A 32-bit GOT delta. /// /// Delta from the global offset table to the target. /// @@ -92,6 +104,26 @@ /// - *ASSERTION* Failure to a null pointer GOTSymbol, which the GOT section /// symbol was not been defined. Delta32FromGOT, + + /// A GOT entry offset within GOT getter/constructor, transformed to + /// Delta32FromGOT pointing at the GOT entry for the original target. + /// + /// Indicates that this edge should be transformed into a Delta32FromGOT + /// targeting the GOT entry for the edge's current target, maintaining the + /// same addend. + /// A GOT entry for the target should be created if one does not already + /// exist. + /// + /// Edges of this kind are usually handled by a GOT builder pass inserted by + /// default + /// + /// Fixup expression: + /// NONE + /// + /// Errors: + /// - *ASSERTION* Failure to handle edges of this kind prior to the fixup + /// phase will result in an assert/unreachable during the fixup phase + RequestGOTAndTransformToDelta32FromGOT, }; /// Returns a string name for the given i386 edge. For debugging purposes @@ -110,7 +142,8 @@ } /// Apply fixup expression for edge to block content. -inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E) { +inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E, + const Symbol *GOTSymbol) { using namespace i386; using namespace llvm::support; @@ -155,6 +188,20 @@ break; } + case i386::Delta32: { + int32_t Value = E.getTarget().getAddress() - FixupAddress + E.getAddend(); + *(little32_t *)FixupPtr = Value; + break; + } + + case i386::Delta32FromGOT: { + assert(GOTSymbol && "No GOT section symbol"); + int32_t Value = + E.getTarget().getAddress() - GOTSymbol->getAddress() + E.getAddend(); + *(little32_t *)FixupPtr = Value; + break; + } + default: return make_error( "In graph " + G.getName() + ", section " + B.getSection().getName() + @@ -163,6 +210,79 @@ return Error::success(); } + +/// i386 pointer size. +constexpr uint32_t PointerSize = 4; + +/// i386 null pointer content. +extern const char NullPointerContent[PointerSize]; + +/// Creates a new pointer block in the given section and returns an anonymous +/// symbol pointing to it. +/// +/// If InitialTarget is given then an Pointer32 relocation will be added to the +/// block pointing at InitialTarget. +/// +/// The pointer block will have the following default values: +/// alignment: 32-bit +/// alignment-offset: 0 +/// address: highest allowable (~7U) +inline Symbol &createAnonymousPointer(LinkGraph &G, Section &PointerSection, + Symbol *InitialTarget = nullptr, + uint64_t InitialAddend = 0) { + auto &B = G.createContentBlock(PointerSection, NullPointerContent, + orc::ExecutorAddr(), 8, 0); + if (InitialTarget) + B.addEdge(Pointer32, 0, *InitialTarget, InitialAddend); + return G.addAnonymousSymbol(B, 0, PointerSize, false, false); +} + +/// Global Offset Table Builder. +class GOTTableManager : public TableManager { +public: + static StringRef getSectionName() { return "$__GOT"; } + + bool visitEdge(LinkGraph &G, Block *B, Edge &E) { + Edge::Kind KindToSet = Edge::Invalid; + switch (E.getKind()) { + case i386::Delta32FromGOT: { + // we need to make sure that the GOT section exists, but don't otherwise + // need to fix up this edge + getGOTSection(G); + return false; + } + case i386::RequestGOTAndTransformToDelta32FromGOT: + KindToSet = i386::Delta32FromGOT; + break; + default: + return false; + } + assert(KindToSet != Edge::Invalid && + "Fell through switch, but no new kind to set"); + DEBUG_WITH_TYPE("jitlink", { + dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at " + << B->getFixupAddress(E) << " (" << B->getAddress() << " + " + << formatv("{0:x}", E.getOffset()) << ")\n"; + }); + E.setKind(KindToSet); + E.setTarget(getEntryForTarget(G, E.getTarget())); + return true; + } + + Symbol &createEntry(LinkGraph &G, Symbol &Target) { + return createAnonymousPointer(G, getGOTSection(G), &Target); + } + +private: + Section &getGOTSection(LinkGraph &G) { + if (!GOTSection) + GOTSection = &G.createSection(getSectionName(), orc::MemProt::Read); + return *GOTSection; + } + + Section *GOTSection = nullptr; +}; + } // namespace llvm::jitlink::i386 #endif // LLVM_EXECUTIONENGINE_JITLINK_I386_H diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_i386.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_i386.cpp --- a/llvm/lib/ExecutionEngine/JITLink/ELF_i386.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF_i386.cpp @@ -17,11 +17,25 @@ #include "llvm/ExecutionEngine/JITLink/i386.h" #include "llvm/Object/ELFObjectFile.h" +#include "DefineExternalSectionStartAndEndSymbols.h" + #define DEBUG_TYPE "jitlink" using namespace llvm; using namespace llvm::jitlink; +namespace { +constexpr StringRef ELFGOTSymbolName = "_GLOBAL_OFFSET_TABLE_"; + +Error buildTables_ELF_i386(LinkGraph &G) { + LLVM_DEBUG(dbgs() << "Visiting edges in graph:\n"); + + i386::GOTTableManager GOT; + visitExistingEdges(G, GOT); + return Error::success(); +} +} // namespace + namespace llvm::jitlink { class ELFJITLinker_i386 : public JITLinker { @@ -30,11 +44,68 @@ public: ELFJITLinker_i386(std::unique_ptr Ctx, std::unique_ptr G, PassConfiguration PassConfig) - : JITLinker(std::move(Ctx), std::move(G), std::move(PassConfig)) {} + : JITLinker(std::move(Ctx), std::move(G), std::move(PassConfig)) { + getPassConfig().PostAllocationPasses.push_back( + [this](LinkGraph &G) { return getOrCreateGOTSymbol(G); }); + } private: + Symbol *GOTSymbol = nullptr; + + Error getOrCreateGOTSymbol(LinkGraph &G) { + auto DefineExternalGOTSymbolIfPresent = + createDefineExternalSectionStartAndEndSymbolsPass( + [&](LinkGraph &LG, Symbol &Sym) -> SectionRangeSymbolDesc { + if (Sym.getName() == ELFGOTSymbolName) + if (auto *GOTSection = G.findSectionByName( + i386::GOTTableManager::getSectionName())) { + GOTSymbol = &Sym; + return {*GOTSection, true}; + } + return {}; + }); + + // Try to attach _GLOBAL_OFFSET_TABLE_ to the GOT if it's defined as an + // external. + if (auto Err = DefineExternalGOTSymbolIfPresent(G)) + return Err; + + // If we succeeded then we're done. + if (GOTSymbol) + return Error::success(); + + // Otherwise look for a GOT section: If it already has a start symbol we'll + // record it, otherwise we'll create our own. + // If there's a GOT section but we didn't find an external GOT symbol... + if (auto *GOTSection = + G.findSectionByName(i386::GOTTableManager::getSectionName())) { + + // Check for an existing defined symbol. + for (auto *Sym : GOTSection->symbols()) + if (Sym->getName() == ELFGOTSymbolName) { + GOTSymbol = Sym; + return Error::success(); + } + + // If there's no defined symbol then create one. + SectionRange SR(*GOTSection); + + if (SR.empty()) { + GOTSymbol = + &G.addAbsoluteSymbol(ELFGOTSymbolName, orc::ExecutorAddr(), 0, + Linkage::Strong, Scope::Local, true); + } else { + GOTSymbol = + &G.addDefinedSymbol(*SR.getFirstBlock(), 0, ELFGOTSymbolName, 0, + Linkage::Strong, Scope::Local, false, true); + } + } + + return Error::success(); + } + Error applyFixup(LinkGraph &G, Block &B, const Edge &E) const { - return i386::applyFixup(G, B, E); + return i386::applyFixup(G, B, E, GOTSymbol); } }; @@ -54,7 +125,11 @@ return EdgeKind_i386::Pointer16; case ELF::R_386_PC16: return EdgeKind_i386::PCRel16; + case ELF::R_386_GOT32: + return EdgeKind_i386::RequestGOTAndTransformToDelta32FromGOT; case ELF::R_386_GOTPC: + return EdgeKind_i386::Delta32; + case ELF::R_386_GOTOFF: return EdgeKind_i386::Delta32FromGOT; } @@ -105,10 +180,6 @@ if (!Kind) return Kind.takeError(); - // TODO: To be removed when GOT relative relocations are supported. - if (*Kind == i386::EdgeKind_i386::Delta32FromGOT) - return Error::success(); - int64_t Addend = 0; auto FixupAddress = orc::ExecutorAddr(FixupSection.sh_addr) + Rel.r_offset; @@ -121,7 +192,6 @@ }); BlockToFix.addEdge(std::move(GE)); - return Error::success(); } @@ -162,6 +232,9 @@ Config.PrePrunePasses.push_back(std::move(MarkLive)); else Config.PrePrunePasses.push_back(markAllSymbolsLive); + + // Add an in-place GOT build pass. + Config.PostPrunePasses.push_back(buildTables_ELF_i386); } if (auto Err = Ctx->modifyPassConfig(*G, Config)) return Ctx->notifyFailed(std::move(Err)); diff --git a/llvm/lib/ExecutionEngine/JITLink/i386.cpp b/llvm/lib/ExecutionEngine/JITLink/i386.cpp --- a/llvm/lib/ExecutionEngine/JITLink/i386.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/i386.cpp @@ -28,8 +28,16 @@ return "Pointer16"; case PCRel16: return "PCRel16"; + case Delta32: + return "Delta32"; + case Delta32FromGOT: + return "Delta32FromGOT"; + case RequestGOTAndTransformToDelta32FromGOT: + return "RequestGOTAndTransformToDelta32FromGOT"; } + return getGenericEdgeKindName(K); } +const char NullPointerContent[PointerSize] = {0x00, 0x00, 0x00, 0x00}; } // namespace llvm::jitlink::i386 diff --git a/llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s b/llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s new file mode 100644 --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s @@ -0,0 +1,41 @@ +# RUN: llvm-mc -triple=i386-unknown-linux-gnu -filetype=obj -o %t %s +# RUN: llvm-jitlink -noexec %t +# +# Check that symbol scope is demoted to local when external symbols are +# converted to absolutes. This is demotion is necessary to avoid "unexpected +# definition" errors. +# +# The reference to _GLOBAL_OFFSET_TABLE_ will trigger creation of an external +# _GLOBAL_OFFSET_TABLE_ symbol, and the GOTOFF relocation will force creation +# of a GOT symbol without actually introducing any GOT entries. Together these +# should cause the external _GLOBAL_OFFSET_TABLE_ symbol to be converted to an +# absolute symbol with address zero. If the scope is not demoted correctly this +# will trigger an "unexpected definition" error. + + .text + .globl main + .p2align 4, 0x90 + .type main,@function +main: + pushl %ebp + movl %esp, %ebp + pushl %eax + calll .L0$pb +.L0$pb: + popl %eax +.Ltmp0: + addl $_GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb), %eax + movl $0, -4(%ebp) + movl a@GOTOFF(%eax), %eax + addl $4, %esp + popl %ebp + retl + .size main, .-main + + + .type a,@object # @a + .data + .p2align 2 +a: + .long 42 # 0x2a + .size a, 4 \ No newline at end of file diff --git a/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations.s b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations.s new file mode 100644 --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations.s @@ -0,0 +1,68 @@ +# RUN: rm -rf %t && mkdir -p %t +# RUN: llvm-mc -triple=i386-unknown-linux-gnu -position-independent \ +# RUN: -filetype=obj -o %t/elf_sm_pic_reloc.o %s +# RUN: llvm-jitlink -noexec \ +# RUN: -slab-allocate 100Kb -slab-address 0xfff00000 -slab-page-size 4096 \ +# RUN: -check %s %t/elf_sm_pic_reloc.o +# +# Test ELF small/PIC relocations. + + .text + .globl main + .p2align 4, 0x90 + .type main,@function +main: + ret + .size main, .-main + + +# Test GOT32 handling. +# +# We want to check both the offset to the GOT entry and its contents. +# jitlink-check: decode_operand(test_got, 4) = got_addr(elf_sm_pic_reloc.o, named_data1) - _GLOBAL_OFFSET_TABLE_ +# jitlink-check: *{4}(got_addr(elf_sm_pic_reloc.o, named_data1)) = named_data1 +# +# jitlink-check: decode_operand(test_got+6, 4) = got_addr(elf_sm_pic_reloc.o, named_data2) - _GLOBAL_OFFSET_TABLE_ +# jitlink-check: *{4}(got_addr(elf_sm_pic_reloc.o, named_data2)) = named_data2 + + .globl test_got + .p2align 4, 0x90 + .type test_got,@function +test_got: + leal named_data1@GOT, %eax + leal named_data2@GOT, %eax + .size test_got, .-test_got + + + +# Test GOTOFF64 handling. +# jitlink-check: decode_operand(test_gotoff, 1) = named_func - _GLOBAL_OFFSET_TABLE_ + .globl test_gotoff + .p2align 4, 0x90 + .type test_gotoff,@function +test_gotoff: + mov $named_func@GOTOFF, %eax + .size test_gotoff, .-test_gotoff + + + .globl named_func + .p2align 4, 0x90 + .type named_func,@function +named_func: + xor %eax, %eax + .size named_func, .-named_func + + + .data + + .type named_data1,@object + .p2align 3 +named_data1: + .quad 42 + .size named_data1, 8 + + .type named_data2,@object + .p2align 3 +named_data2: + .quad 42 + .size named_data2, 8