This CR adds support for GOT relocations to the JITLink i386/ELF backend.
Reviewed By: lhames
Differential Revision: https://reviews.llvm.org/D140279
///
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.
///
/// - *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
}
/// 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;
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<JITLinkError>(
"In graph " + G.getName() + ", section " + B.getSection().getName() +
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<GOTTableManager> {
+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
#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<ELFJITLinker_i386> {
public:
ELFJITLinker_i386(std::unique_ptr<JITLinkContext> Ctx,
std::unique_ptr<LinkGraph> 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);
}
};
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;
}
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;
});
BlockToFix.addEdge(std::move(GE));
-
return Error::success();
}
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));
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
--- /dev/null
+# 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
--- /dev/null
+# 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