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 @@ -14,25 +14,155 @@ #define LLVM_EXECUTIONENGINE_JITLINK_I386_H #include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/ExecutionEngine/JITLink/TableManager.h" -namespace llvm { -namespace jitlink { -namespace i386 { - +namespace llvm::jitlink::i386 { /// Represets i386 fixups enum EdgeKind_i386 : Edge::Kind { /// None None = Edge::FirstRelocation, + /// A plain 32-bit pointer value relocation. + /// + /// Fixup expression: + /// Fixup <- Target + Addend : uint32 + /// + /// Errors: + /// - The target must reside in the low 32-bits of the address space, + /// otherwise an out-of-range error will be returned. + /// + Pointer32, + + /// A 32-bit PC-relative relocation. + /// + /// Represents a data/control flow instruction using PC-relative addressing + /// to a target. + /// + /// The fixup expression for this kind includes an implicit offset to account + /// for the PC (unlike the Delta edges) so that a PCRel32 with a target + /// T and addend zero is a call/branch to the start (offset zero) of T. + /// + /// Fixup expression: + /// Fixup <- Target - (Fixup + 4) + Addend : int32 + /// + /// Errors: + /// - The result of the fixup expression must fit into an int32, otherwise + /// an out-of-range error will be returned. + /// + PCRel32, + + /// A plain 16-bit pointer value relocation. + /// + /// Fixup expression: + /// Fixup <- Target + Addend : uint16 + /// + /// Errors: + /// - The target must reside in the low 16-bits of the address space, + /// otherwise an out-of-range error will be returned. + /// + Pointer16, + + /// A 16-bit PC-relative relocation. + /// + /// Represents a data/control flow instruction using PC-relative addressing + /// to a target. + /// + /// The fixup expression for this kind includes an implicit offset to account + /// for the PC (unlike the Delta edges) so that a PCRel16 with a target + /// T and addend zero is a call/branch to the start (offset zero) of T. + /// + /// Fixup expression: + /// Fixup <- Target - (Fixup + 4) + Addend : int16 + /// + /// Errors: + /// - The result of the fixup expression must fit into an int16, otherwise + /// an out-of-range error will be returned. + /// + PCRel16, + + /// A 64-bit GOT delta. + /// + /// Delta from the global offset table to the target. + /// + /// Fixup expression: + /// Fixup <- Target - GOTSymbol + Addend : int32 + /// + /// Errors: + /// - *ASSERTION* Failure to a null pointer GOTSymbol, which the GOT section + /// symbol was not been defined. + Delta32FromGOT, }; /// Returns a string name for the given i386 edge. For debugging purposes /// only const char *getEdgeKindName(Edge::Kind K); -} // namespace i386 -} // namespace jitlink -} // namespace llvm +/// Returns true if the given uint32_t value is in range for a uint16_t. +inline bool isInRangeForImmU16(uint32_t Value) { + return Value <= std::numeric_limits::max(); +} + +/// Returns true if the given int32_t value is in range for an int16_t. +inline bool isInRangeForImmS16(int32_t Value) { + return (Value >= std::numeric_limits::min() && + Value <= std::numeric_limits::max()); +} + +/// Apply fixup expression for edge to block content. +inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E) { + using namespace i386; + using namespace llvm::support; + + char *BlockWorkingMem = B.getAlreadyMutableContent().data(); + char *FixupPtr = BlockWorkingMem + E.getOffset(); + auto FixupAddress = B.getAddress() + E.getOffset(); + + switch (E.getKind()) { + case i386::None: { + break; + } + + case i386::Pointer32: { + uint32_t Value = E.getTarget().getAddress().getValue() + E.getAddend(); + *(ulittle32_t *)FixupPtr = Value; + break; + } + + case i386::PCRel32: { + int32_t Value = + E.getTarget().getAddress() - (FixupAddress + 4) + E.getAddend(); + *(little32_t *)FixupPtr = Value; + break; + } + + case i386::Pointer16: { + uint32_t Value = E.getTarget().getAddress().getValue() + E.getAddend(); + if (LLVM_LIKELY(isInRangeForImmU16(Value))) + *(ulittle16_t *)FixupPtr = Value; + else + return makeTargetOutOfRangeError(G, B, E); + break; + } + + case i386::PCRel16: { + int32_t Value = + E.getTarget().getAddress() - (FixupAddress + 4) + E.getAddend(); + if (LLVM_LIKELY(isInRangeForImmS16(Value))) + *(little16_t *)FixupPtr = Value; + else + return makeTargetOutOfRangeError(G, B, E); + break; + } + + default: + return make_error( + "In graph " + G.getName() + ", section " + B.getSection().getName() + + "unsupported edge kind" + getEdgeKindName(E.getKind())); + } + + return Error::success(); +} +} // namespace llvm::jitlink::i386 #endif // LLVM_EXECUTIONENGINE_JITLINK_I386_H diff --git a/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h b/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h --- a/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h +++ b/llvm/lib/ExecutionEngine/JITLink/ELFLinkGraphBuilder.h @@ -108,24 +108,49 @@ Error graphifySections(); Error graphifySymbols(); - /// Traverse all matching relocation records in the given section. The handler - /// function Func should be callable with this signature: + /// Traverse all matching ELFT::Rela relocation records in the given section. + /// The handler function Func should be callable with this signature: /// Error(const typename ELFT::Rela &, /// const typename ELFT::Shdr &, Section &) /// - template - Error forEachRelocation(const typename ELFT::Shdr &RelSect, - RelocHandlerFunction &&Func, - bool ProcessDebugSections = false); + template + Error forEachRelaRelocation(const typename ELFT::Shdr &RelSect, + RelocHandlerMethod &&Func, + bool ProcessDebugSections = false); + + /// Traverse all matching ELFT::Rel relocation records in the given section. + /// The handler function Func should be callable with this signature: + /// Error(const typename ELFT::Rel &, + /// const typename ELFT::Shdr &, Section &) + /// + template + Error forEachRelRelocation(const typename ELFT::Shdr &RelSect, + RelocHandlerMethod &&Func, + bool ProcessDebugSections = false); + + /// Traverse all matching rela relocation records in the given section. + /// Convenience wrapper to allow passing a member function for the handler. + /// + template + Error forEachRelaRelocation(const typename ELFT::Shdr &RelSect, + ClassT *Instance, RelocHandlerMethod &&Method, + bool ProcessDebugSections = false) { + return forEachRelaRelocation( + RelSect, + [Instance, Method](const auto &Rel, const auto &Target, auto &GS) { + return (Instance->*Method)(Rel, Target, GS); + }, + ProcessDebugSections); + } - /// Traverse all matching relocation records in the given section. Convenience - /// wrapper to allow passing a member function for the handler. + /// Traverse all matching rel relocation records in the given section. + /// Convenience wrapper to allow passing a member function for the handler. /// template - Error forEachRelocation(const typename ELFT::Shdr &RelSect, ClassT *Instance, - RelocHandlerMethod &&Method, - bool ProcessDebugSections = false) { - return forEachRelocation( + Error forEachRelRelocation(const typename ELFT::Shdr &RelSect, + ClassT *Instance, RelocHandlerMethod &&Method, + bool ProcessDebugSections = false) { + return forEachRelRelocation( RelSect, [Instance, Method](const auto &Rel, const auto &Target, auto &GS) { return (Instance->*Method)(Rel, Target, GS); @@ -487,13 +512,12 @@ } template -template -Error ELFLinkGraphBuilder::forEachRelocation( - const typename ELFT::Shdr &RelSect, RelocHandlerFunction &&Func, +template +Error ELFLinkGraphBuilder::forEachRelaRelocation( + const typename ELFT::Shdr &RelSect, RelocHandlerMethod &&Func, bool ProcessDebugSections) { - // Only look into sections that store relocation entries. - if (RelSect.sh_type != ELF::SHT_RELA && RelSect.sh_type != ELF::SHT_REL) + if (RelSect.sh_type != ELF::SHT_RELA) return Error::success(); // sh_info contains the section header index of the target (FixupSection), @@ -534,6 +558,53 @@ return Error::success(); } +template +template +Error ELFLinkGraphBuilder::forEachRelRelocation( + const typename ELFT::Shdr &RelSect, RelocHandlerMethod &&Func, + bool ProcessDebugSections) { + // Only look into sections that store relocation entries. + if (RelSect.sh_type != ELF::SHT_REL) + return Error::success(); + + // sh_info contains the section header index of the target (FixupSection), + // which is the section to which all relocations in RelSect apply. + auto FixupSection = Obj.getSection(RelSect.sh_info); + if (!FixupSection) + return FixupSection.takeError(); + + // Target sections have names in valid ELF object files. + Expected Name = Obj.getSectionName(**FixupSection); + if (!Name) + return Name.takeError(); + LLVM_DEBUG(dbgs() << " " << *Name << ":\n"); + + // Consider skipping these relocations. + if (!ProcessDebugSections && isDwarfSection(*Name)) { + LLVM_DEBUG(dbgs() << " skipped (dwarf section)\n\n"); + return Error::success(); + } + + // Lookup the link-graph node corresponding to the target section name. + auto *BlockToFix = getGraphBlock(RelSect.sh_info); + if (!BlockToFix) + return make_error( + "Refencing a section that wasn't added to the graph: " + *Name, + inconvertibleErrorCode()); + + auto RelEntries = Obj.rels(RelSect); + if (!RelEntries) + return RelEntries.takeError(); + + // Let the callee process relocation entries one by one. + for (const typename ELFT::Rel &R : *RelEntries) + if (Error Err = Func(R, **FixupSection, *BlockToFix)) + return Err; + + LLVM_DEBUG(dbgs() << "\n"); + return Error::success(); +} + } // end namespace jitlink } // end namespace llvm diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp --- a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp @@ -129,8 +129,8 @@ using Base = ELFLinkGraphBuilder; using Self = ELFLinkGraphBuilder_aarch64; for (const auto &RelSect : Base::Sections) - if (Error Err = Base::forEachRelocation(RelSect, this, - &Self::addSingleRelocation)) + if (Error Err = Base::forEachRelaRelocation(RelSect, this, + &Self::addSingleRelocation)) return Err; return Error::success(); 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 @@ -22,8 +22,7 @@ using namespace llvm; using namespace llvm::jitlink; -namespace llvm { -namespace jitlink { +namespace llvm::jitlink { class ELFJITLinker_i386 : public JITLinker { friend class JITLinker; @@ -35,15 +34,7 @@ private: Error applyFixup(LinkGraph &G, Block &B, const Edge &E) const { - using namespace i386; - using namespace llvm::support; - - switch (E.getKind()) { - case i386::None: { - break; - } - } - return Error::success(); + return i386::applyFixup(G, B, E); } }; @@ -55,6 +46,16 @@ switch (Type) { case ELF::R_386_NONE: return EdgeKind_i386::None; + case ELF::R_386_32: + return EdgeKind_i386::Pointer32; + case ELF::R_386_PC32: + return EdgeKind_i386::PCRel32; + case ELF::R_386_16: + return EdgeKind_i386::Pointer16; + case ELF::R_386_PC16: + return EdgeKind_i386::PCRel16; + case ELF::R_386_GOTPC: + return EdgeKind_i386::Delta32FromGOT; } return make_error("Unsupported i386 relocation:" + @@ -63,6 +64,64 @@ Error addRelocations() override { LLVM_DEBUG(dbgs() << "Adding relocations\n"); + using Base = ELFLinkGraphBuilder; + using Self = ELFLinkGraphBuilder_i386; + + for (const auto &RelSect : Base::Sections) { + // Validate the section to read relocation entries from. + if (RelSect.sh_type == ELF::SHT_RELA) + return make_error( + "No SHT_RELA in valid i386 ELF object files", + inconvertibleErrorCode()); + + if (Error Err = Base::forEachRelRelocation(RelSect, this, + &Self::addSingleRelocation)) + return Err; + } + + return Error::success(); + } + + Error addSingleRelocation(const typename ELFT::Rel &Rel, + const typename ELFT::Shdr &FixupSection, + Block &BlockToFix) { + using Base = ELFLinkGraphBuilder; + + uint32_t SymbolIndex = Rel.getSymbol(false); + auto ObjSymbol = Base::Obj.getRelocationSymbol(Rel, Base::SymTabSec); + if (!ObjSymbol) + return ObjSymbol.takeError(); + + Symbol *GraphSymbol = Base::getGraphSymbol(SymbolIndex); + if (!GraphSymbol) + return make_error( + formatv("Could not find symbol at given index, did you add it to " + "JITSymbolTable? index: {0}, shndx: {1} Size of table: {2}", + SymbolIndex, (*ObjSymbol)->st_shndx, + Base::GraphSymbols.size()), + inconvertibleErrorCode()); + + Expected Kind = getRelocationKind(Rel.getType(false)); + 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; + Edge::OffsetT Offset = FixupAddress - BlockToFix.getAddress(); + Edge GE(*Kind, Offset, *GraphSymbol, Addend); + LLVM_DEBUG({ + dbgs() << " "; + printEdge(dbgs(), BlockToFix, GE, i386::getEdgeKindName(*Kind)); + dbgs() << "\n"; + }); + + BlockToFix.addEdge(std::move(GE)); + return Error::success(); } @@ -110,5 +169,4 @@ ELFJITLinker_i386::link(std::move(Ctx), std::move(G), std::move(Config)); } -} // namespace jitlink -} // namespace llvm +} // namespace llvm::jitlink diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_riscv.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_riscv.cpp --- a/llvm/lib/ExecutionEngine/JITLink/ELF_riscv.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF_riscv.cpp @@ -482,8 +482,8 @@ using Base = ELFLinkGraphBuilder; using Self = ELFLinkGraphBuilder_riscv; for (const auto &RelSect : Base::Sections) - if (Error Err = Base::forEachRelocation(RelSect, this, - &Self::addSingleRelocation)) + if (Error Err = Base::forEachRelaRelocation(RelSect, this, + &Self::addSingleRelocation)) return Err; return Error::success(); diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp --- a/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp @@ -166,8 +166,8 @@ "No SHT_REL in valid x64 ELF object files", inconvertibleErrorCode()); - if (Error Err = Base::forEachRelocation(RelSect, this, - &Self::addSingleRelocation)) + if (Error Err = Base::forEachRelaRelocation(RelSect, this, + &Self::addSingleRelocation)) return 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 @@ -14,17 +14,22 @@ #define DEBUG_TYPE "jitlink" -namespace llvm { -namespace jitlink { -namespace i386 { +namespace llvm::jitlink::i386 { const char *getEdgeKindName(Edge::Kind K) { switch (K) { case None: return "None"; + case Pointer32: + return "Pointer32"; + case PCRel32: + return "PCRel32"; + case Pointer16: + return "Pointer16"; + case PCRel16: + return "PCRel16"; } return getGenericEdgeKindName(K); } -} // namespace i386 -} // namespace jitlink -} // namespace llvm + +} // namespace llvm::jitlink::i386 diff --git a/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_absolute_relocations_32.s b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_absolute_relocations_32.s new file mode 100644 --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_absolute_relocations_32.s @@ -0,0 +1,25 @@ +# RUN: llvm-mc -triple=i386-unknown-linux-gnu -filetype=obj -o %t.o %s +# RUN: llvm-jitlink -noexec %t.o + + .text + .globl main + .p2align 4 + .type main,@function +main: + pushl %ebp + movl %esp, %ebp + pushl %eax + movl $0, -4(%ebp) + movl a, %eax + addl $4, %esp + popl %ebp + retl + + .size main, .-main + + .data + .p2align 2 + .type a,@object +a: + .long 42 + .size a, 4 \ No newline at end of file diff --git a/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_pc_relative_relocations_32.s b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_pc_relative_relocations_32.s new file mode 100644 --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_pc_relative_relocations_32.s @@ -0,0 +1,31 @@ +# RUN: llvm-mc -triple=i386-unknown-linux-gnu -filetype=obj -o %t.o %s +# RUN: llvm-jitlink -noexec %t.o + + .text + .section .text.main + .globl main + .p2align 4 + .type main,@function +main: + pushl %ebp + movl %esp, %ebp + subl $8, %esp + movl $0, -4(%ebp) + calll foo + addl $8, %esp + popl %ebp + retl + + .size main, .-main + + .section .text.foo + .p2align 4 + .type foo,@function +foo: + pushl %ebp + movl %esp, %ebp + movl $42, %eax + popl %ebp + retl + + .size foo, .-foo \ No newline at end of file diff --git a/llvm/test/ExecutionEngine/JITLink/i386/lit.local.cfg b/llvm/test/ExecutionEngine/JITLink/i386/lit.local.cfg --- a/llvm/test/ExecutionEngine/JITLink/i386/lit.local.cfg +++ b/llvm/test/ExecutionEngine/JITLink/i386/lit.local.cfg @@ -1,2 +1,2 @@ -if not 'i386' in config.root.targets: - config.unsupported = True \ No newline at end of file +if not 'X86' in config.root.targets: + config.unsupported = True