[llvm] cd9fd42 - [JITLink] Adds support for PLT based relocations to the ELF/i386 JITLink backend
Kshitij Jain via llvm-commits
llvm-commits at lists.llvm.org
Sun Jan 29 17:16:29 PST 2023
Author: Kshitij Jain
Date: 2023-01-30T01:15:34Z
New Revision: cd9fd4255b48e6c759aa09a4d91249def330b835
URL: https://github.com/llvm/llvm-project/commit/cd9fd4255b48e6c759aa09a4d91249def330b835
DIFF: https://github.com/llvm/llvm-project/commit/cd9fd4255b48e6c759aa09a4d91249def330b835.diff
LOG: [JITLink] Adds support for PLT based relocations to the ELF/i386 JITLink backend
This commit adds support for PLT based relocations. Specifically -
1. It adds logic to create a `PLTTableManager` in the `buildTables_ELF_i386`
function, which is called as part of the post-prune JITLink passes. The `PLTTableManager`
handles creating pointer jump stubs and related GOT entries for position independent
code.
2. It also adds a pre-fixup pass to optimize away PLT based calls in position independent
code, when possible.
Reviewed By: lhames
Differential Revision: https://reviews.llvm.org/D142846
Added:
llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations_got.s
llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations_plt.s
Modified:
llvm/include/llvm/ExecutionEngine/JITLink/i386.h
llvm/lib/ExecutionEngine/JITLink/ELF_i386.cpp
llvm/lib/ExecutionEngine/JITLink/i386.cpp
Removed:
llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations.s
################################################################################
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/i386.h b/llvm/include/llvm/ExecutionEngine/JITLink/i386.h
index a590713625d29..1499b402cbbb9 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/i386.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/i386.h
@@ -124,6 +124,69 @@ enum EdgeKind_i386 : Edge::Kind {
/// - *ASSERTION* Failure to handle edges of this kind prior to the fixup
/// phase will result in an assert/unreachable during the fixup phase
RequestGOTAndTransformToDelta32FromGOT,
+
+ /// A 32-bit PC-relative branch.
+ ///
+ /// Represents a PC-relative call or branch to a target. This can be used to
+ /// identify, record, and/or patch call sites.
+ ///
+ /// The fixup expression for this kind includes an implicit offset to account
+ /// for the PC (unlike the Delta edges) so that a Branch32PCRel 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.
+ ///
+ BranchPCRel32,
+
+ /// A 32-bit PC-relative branch to a pointer jump stub.
+ ///
+ /// The target of this relocation should be a pointer jump stub of the form:
+ ///
+ /// \code{.s}
+ /// .text
+ /// jmp *tgtptr
+ /// ; ...
+ ///
+ /// .data
+ /// tgtptr:
+ /// .quad 0
+ /// \endcode
+ ///
+ /// This edge kind has the same fixup expression as BranchPCRel32, but further
+ /// identifies the call/branch as being to a pointer jump stub. For edges of
+ /// this kind the jump stub should not be bypassed (use
+ /// BranchPCRel32ToPtrJumpStubBypassable for that), but the pointer location
+ /// target may be recorded to allow manipulation at runtime.
+ ///
+ /// Fixup expression:
+ /// Fixup <- Target - Fixup + Addend - 4 : int32
+ ///
+ /// Errors:
+ /// - The result of the fixup expression must fit into an int32, otherwise
+ /// an out-of-range error will be returned.
+ ///
+ BranchPCRel32ToPtrJumpStub,
+
+ /// A relaxable version of BranchPCRel32ToPtrJumpStub.
+ ///
+ /// The edge kind has the same fixup expression as BranchPCRel32ToPtrJumpStub,
+ /// but identifies the call/branch as being to a pointer jump stub that may be
+ /// bypassed with a direct jump to the ultimate target if the ultimate target
+ /// is within range of the fixup location.
+ ///
+ /// Fixup expression:
+ /// Fixup <- Target - Fixup + Addend - 4: int32
+ ///
+ /// Errors:
+ /// - The result of the fixup expression must fit into an int32, otherwise
+ /// an out-of-range error will be returned.
+ ///
+ BranchPCRel32ToPtrJumpStubBypassable,
};
/// Returns a string name for the given i386 edge. For debugging purposes
@@ -141,6 +204,12 @@ inline bool isInRangeForImmS16(int32_t Value) {
Value <= std::numeric_limits<int16_t>::max());
}
+/// Returns true if the given int64_t value is in range for an int32_t.
+inline bool isInRangeForImmS32(int64_t Value) {
+ return (Value >= std::numeric_limits<int32_t>::min() &&
+ Value <= std::numeric_limits<int32_t>::max());
+}
+
/// Apply fixup expression for edge to block content.
inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
const Symbol *GOTSymbol) {
@@ -202,6 +271,15 @@ inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
break;
}
+ case i386::BranchPCRel32:
+ case i386::BranchPCRel32ToPtrJumpStub:
+ case i386::BranchPCRel32ToPtrJumpStubBypassable: {
+ int32_t Value =
+ E.getTarget().getAddress() - (FixupAddress + 4) + E.getAddend();
+ *(little32_t *)FixupPtr = Value;
+ break;
+ }
+
default:
return make_error<JITLinkError>(
"In graph " + G.getName() + ", section " + B.getSection().getName() +
@@ -217,6 +295,13 @@ constexpr uint32_t PointerSize = 4;
/// i386 null pointer content.
extern const char NullPointerContent[PointerSize];
+/// i386 pointer jump stub content.
+///
+/// Contains the instruction sequence for an indirect jump via an in-memory
+/// pointer:
+/// jmpq *ptr
+extern const char PointerJumpStubContent[6];
+
/// Creates a new pointer block in the given section and returns an anonymous
/// symbol pointing to it.
///
@@ -237,6 +322,36 @@ inline Symbol &createAnonymousPointer(LinkGraph &G, Section &PointerSection,
return G.addAnonymousSymbol(B, 0, PointerSize, false, false);
}
+/// Create a jump stub block that jumps via the pointer at the given symbol.
+///
+/// The stub block will have the following default values:
+/// alignment: 8-bit
+/// alignment-offset: 0
+/// address: highest allowable: (~5U)
+inline Block &createPointerJumpStubBlock(LinkGraph &G, Section &StubSection,
+ Symbol &PointerSymbol) {
+ auto &B = G.createContentBlock(StubSection, PointerJumpStubContent,
+ orc::ExecutorAddr(), 8, 0);
+ B.addEdge(Pointer32,
+ // Offset is 2 because the the first 2 bytes of the
+ // jump stub block are {0xff, 0x25} -- an indirect absolute
+ // jump.
+ 2, PointerSymbol, 0);
+ return B;
+}
+
+/// Create a jump stub that jumps via the pointer at the given symbol and
+/// an anonymous symbol pointing to it. Return the anonymous symbol.
+///
+/// The stub block will be created by createPointerJumpStubBlock.
+inline Symbol &createAnonymousPointerJumpStub(LinkGraph &G,
+ Section &StubSection,
+ Symbol &PointerSymbol) {
+ return G.addAnonymousSymbol(
+ createPointerJumpStubBlock(G, StubSection, PointerSymbol), 0, 6, true,
+ false);
+}
+
/// Global Offset Table Builder.
class GOTTableManager : public TableManager<GOTTableManager> {
public:
@@ -283,6 +398,54 @@ class GOTTableManager : public TableManager<GOTTableManager> {
Section *GOTSection = nullptr;
};
+/// Procedure Linkage Table Builder.
+class PLTTableManager : public TableManager<PLTTableManager> {
+public:
+ PLTTableManager(GOTTableManager &GOT) : GOT(GOT) {}
+
+ static StringRef getSectionName() { return "$__STUBS"; }
+
+ bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
+ if (E.getKind() == i386::BranchPCRel32 && !E.getTarget().isDefined()) {
+ DEBUG_WITH_TYPE("jitlink", {
+ dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at "
+ << B->getFixupAddress(E) << " (" << B->getAddress() << " + "
+ << formatv("{0:x}", E.getOffset()) << ")\n";
+ });
+ // Set the edge kind to Branch32ToPtrJumpStubBypassable to enable it to
+ // be optimized when the target is in-range.
+ E.setKind(i386::BranchPCRel32ToPtrJumpStubBypassable);
+ E.setTarget(getEntryForTarget(G, E.getTarget()));
+ return true;
+ }
+ return false;
+ }
+
+ Symbol &createEntry(LinkGraph &G, Symbol &Target) {
+ return createAnonymousPointerJumpStub(G, getStubsSection(G),
+ GOT.getEntryForTarget(G, Target));
+ }
+
+public:
+ Section &getStubsSection(LinkGraph &G) {
+ if (!PLTSection)
+ PLTSection = &G.createSection(getSectionName(),
+ orc::MemProt::Read | orc::MemProt::Exec);
+ return *PLTSection;
+ }
+
+ GOTTableManager &GOT;
+ Section *PLTSection = nullptr;
+};
+
+/// Optimize the GOT and Stub relocations if the edge target address is in range
+/// 1. PCRel32GOTLoadRelaxable. For this edge kind, if the target is in range,
+/// then replace GOT load with lea. (THIS IS UNIMPLEMENTED RIGHT NOW!)
+/// 2. BranchPCRel32ToPtrJumpStubRelaxable. For this edge kind, if the target is
+/// in range, replace a indirect jump by plt stub with a direct jump to the
+/// target
+Error optimizeGOTAndStubAccesses(LinkGraph &G);
+
} // 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
index 1fee1b24b6bda..d1d390d3eefcf 100644
--- a/llvm/lib/ExecutionEngine/JITLink/ELF_i386.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/ELF_i386.cpp
@@ -30,7 +30,8 @@ Error buildTables_ELF_i386(LinkGraph &G) {
LLVM_DEBUG(dbgs() << "Visiting edges in graph:\n");
i386::GOTTableManager GOT;
- visitExistingEdges(G, GOT);
+ i386::PLTTableManager PLT(GOT);
+ visitExistingEdges(G, GOT, PLT);
return Error::success();
}
} // namespace
@@ -130,6 +131,8 @@ class ELFLinkGraphBuilder_i386 : public ELFLinkGraphBuilder<ELFT> {
return EdgeKind_i386::Delta32;
case ELF::R_386_GOTOFF:
return EdgeKind_i386::Delta32FromGOT;
+ case ELF::R_386_PLT32:
+ return EdgeKind_i386::BranchPCRel32;
}
return make_error<JITLinkError>("Unsupported i386 relocation:" +
@@ -243,8 +246,11 @@ void link_ELF_i386(std::unique_ptr<LinkGraph> G,
else
Config.PrePrunePasses.push_back(markAllSymbolsLive);
- // Add an in-place GOT build pass.
+ // Add an in-place GOT and PLT build pass.
Config.PostPrunePasses.push_back(buildTables_ELF_i386);
+
+ // Add GOT/Stubs optimizer pass.
+ Config.PreFixupPasses.push_back(i386::optimizeGOTAndStubAccesses);
}
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
index c2c5761cd2723..5561a5fd10495 100644
--- a/llvm/lib/ExecutionEngine/JITLink/i386.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/i386.cpp
@@ -34,10 +34,58 @@ const char *getEdgeKindName(Edge::Kind K) {
return "Delta32FromGOT";
case RequestGOTAndTransformToDelta32FromGOT:
return "RequestGOTAndTransformToDelta32FromGOT";
+ case BranchPCRel32:
+ return "BranchPCRel32";
+ case BranchPCRel32ToPtrJumpStub:
+ return "BranchPCRel32ToPtrJumpStub";
+ case BranchPCRel32ToPtrJumpStubBypassable:
+ return "BranchPCRel32ToPtrJumpStubBypassable";
}
return getGenericEdgeKindName(K);
}
const char NullPointerContent[PointerSize] = {0x00, 0x00, 0x00, 0x00};
+
+const char PointerJumpStubContent[6] = {
+ static_cast<char>(0xFFu), 0x25, 0x00, 0x00, 0x00, 0x00};
+
+Error optimizeGOTAndStubAccesses(LinkGraph &G) {
+ LLVM_DEBUG(dbgs() << "Optimizing GOT entries and stubs:\n");
+
+ for (auto *B : G.blocks())
+ for (auto &E : B->edges()) {
+ if (E.getKind() == i386::BranchPCRel32ToPtrJumpStubBypassable) {
+ auto &StubBlock = E.getTarget().getBlock();
+ assert(StubBlock.getSize() == sizeof(PointerJumpStubContent) &&
+ "Stub block should be stub sized");
+ assert(StubBlock.edges_size() == 1 &&
+ "Stub block should only have one outgoing edge");
+
+ auto &GOTBlock = StubBlock.edges().begin()->getTarget().getBlock();
+ assert(GOTBlock.getSize() == G.getPointerSize() &&
+ "GOT block should be pointer sized");
+ assert(GOTBlock.edges_size() == 1 &&
+ "GOT block should only have one outgoing edge");
+
+ auto &GOTTarget = GOTBlock.edges().begin()->getTarget();
+ orc::ExecutorAddr EdgeAddr = B->getAddress() + E.getOffset();
+ orc::ExecutorAddr TargetAddr = GOTTarget.getAddress();
+
+ int64_t Displacement = TargetAddr - EdgeAddr + 4;
+ if (isInRangeForImmS32(Displacement)) {
+ E.setKind(i386::BranchPCRel32);
+ E.setTarget(GOTTarget);
+ LLVM_DEBUG({
+ dbgs() << " Replaced stub branch with direct branch:\n ";
+ printEdge(dbgs(), *B, E, getEdgeKindName(E.getKind()));
+ dbgs() << "\n";
+ });
+ }
+ }
+ }
+
+ return Error::success();
+}
+
} // namespace llvm::jitlink::i386
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_got.s
similarity index 78%
rename from llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations.s
rename to llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations_got.s
index cb379d51f5d01..91049a8a87a55 100644
--- a/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations.s
+++ b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations_got.s
@@ -1,11 +1,11 @@
# 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: -filetype=obj -o %t/elf_sm_pic_reloc_got.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
+# RUN: -check %s %t/elf_sm_pic_reloc_got.o
#
-# Test ELF small/PIC relocations.
+# Test ELF small/PIC GOT relocations.
.text
.globl main
@@ -19,11 +19,11 @@ 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, 4) = got_addr(elf_sm_pic_reloc_got.o, named_data1) - _GLOBAL_OFFSET_TABLE_
+# jitlink-check: *{4}(got_addr(elf_sm_pic_reloc_got.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
+# jitlink-check: decode_operand(test_got+6, 4) = got_addr(elf_sm_pic_reloc_got.o, named_data2) - _GLOBAL_OFFSET_TABLE_
+# jitlink-check: *{4}(got_addr(elf_sm_pic_reloc_got.o, named_data2)) = named_data2
.globl test_got
.p2align 4, 0x90
diff --git a/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations_plt.s b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations_plt.s
new file mode 100644
index 0000000000000..24d9751b1631e
--- /dev/null
+++ b/llvm/test/ExecutionEngine/JITLink/i386/ELF_i386_small_pic_relocations_plt.s
@@ -0,0 +1,38 @@
+# 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_plt.o %s
+# RUN: /home/ec2-user/llvm-project/build-32/bin/llvm-jitlink -noexec \
+# RUN: -slab-allocate 100Kb -slab-address 0xfff00000 -slab-page-size 4096 \
+# RUN: -abs external_func=0xffff0010 \
+# RUN: -check %s %t/elf_sm_pic_reloc_plt.o
+#
+# Test ELF small/PIC PLT relocations.
+
+# Empty main entry point.
+ .text
+ .globl main
+ .p2align 4, 0x90
+ .type main, at function
+main:
+ ret
+ .size main, .-main
+
+# Check R_386_PLT32 handling with a call to an external function via PLT.
+# This produces a Branch32 edge that is resolved like a regular PCRel32
+# (no PLT entry created).
+#
+# NOTE - For ELF/i386 we always optimize away the PLT calls as the
+# displacement between the target address and the edge address always
+# fits in an int32_t. Regardless, we always create the PLT stub and GOT entry
+# for position independent code, first, as there may be future use-cases
+# where we would want to disable the optimization.
+#
+# jitlink-check: decode_operand(test_call_extern_plt, 0) = external_func - next_pc(test_call_extern_plt)
+# jitlink-check: *{4}(got_addr(elf_sm_pic_reloc_plt.o, external_func))= external_func
+ .globl test_call_extern_plt
+ .p2align 4, 0x90
+ .type test_call_extern_plt, at function
+test_call_extern_plt:
+ call external_func at plt
+
+ .size test_call_extern_plt, .-test_call_extern_plt
\ No newline at end of file
More information about the llvm-commits
mailing list