1 | //===----- x86.h - Generic JITLink x86 edge kinds, utilities ----*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // Generic utilities for graphs representing x86 objects. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #ifndef LLVM_EXECUTIONENGINE_JITLINK_X86_H |
14 | #define LLVM_EXECUTIONENGINE_JITLINK_X86_H |
15 | |
16 | #include "llvm/ExecutionEngine/JITLink/JITLink.h" |
17 | #include "llvm/ExecutionEngine/JITLink/TableManager.h" |
18 | #include "llvm/Support/Compiler.h" |
19 | |
20 | namespace llvm::jitlink::x86 { |
21 | |
22 | /// Represets x86 fixups |
23 | enum EdgeKind_x86 : Edge::Kind { |
24 | |
25 | /// A plain 32-bit pointer value relocation. |
26 | /// |
27 | /// Fixup expression: |
28 | /// Fixup <- Target + Addend : uint32 |
29 | /// |
30 | /// Errors: |
31 | /// - The target must reside in the low 32-bits of the address space, |
32 | /// otherwise an out-of-range error will be returned. |
33 | /// |
34 | Pointer32 = Edge::FirstRelocation, |
35 | |
36 | /// A 32-bit PC-relative relocation. |
37 | /// |
38 | /// Represents a data/control flow instruction using PC-relative addressing |
39 | /// to a target. |
40 | /// |
41 | /// Fixup expression: |
42 | /// Fixup <- Target - Fixup + Addend : int32 |
43 | /// |
44 | /// Errors: |
45 | /// - The result of the fixup expression must fit into an int32, otherwise |
46 | /// an out-of-range error will be returned. |
47 | /// |
48 | PCRel32, |
49 | |
50 | /// A plain 16-bit pointer value relocation. |
51 | /// |
52 | /// Fixup expression: |
53 | /// Fixup <- Target + Addend : uint16 |
54 | /// |
55 | /// Errors: |
56 | /// - The target must reside in the low 16-bits of the address space, |
57 | /// otherwise an out-of-range error will be returned. |
58 | /// |
59 | Pointer16, |
60 | |
61 | /// A 16-bit PC-relative relocation. |
62 | /// |
63 | /// Represents a data/control flow instruction using PC-relative addressing |
64 | /// to a target. |
65 | /// |
66 | /// Fixup expression: |
67 | /// Fixup <- Target - Fixup + Addend : int16 |
68 | /// |
69 | /// Errors: |
70 | /// - The result of the fixup expression must fit into an int16, otherwise |
71 | /// an out-of-range error will be returned. |
72 | /// |
73 | PCRel16, |
74 | |
75 | /// A 32-bit delta. |
76 | /// |
77 | /// Delta from the fixup to the target. |
78 | /// |
79 | /// Fixup expression: |
80 | /// Fixup <- Target - Fixup + Addend : int32 |
81 | /// |
82 | /// Errors: |
83 | /// - The result of the fixup expression must fit into an int32, otherwise |
84 | /// an out-of-range error will be returned. |
85 | Delta32, |
86 | |
87 | /// A 32-bit GOT delta. |
88 | /// |
89 | /// Delta from the global offset table to the target. |
90 | /// |
91 | /// Fixup expression: |
92 | /// Fixup <- Target - GOTSymbol + Addend : int32 |
93 | /// |
94 | /// Errors: |
95 | /// - *ASSERTION* Failure to a null pointer GOTSymbol, which the GOT section |
96 | /// symbol was not been defined. |
97 | Delta32FromGOT, |
98 | |
99 | /// A GOT entry offset within GOT getter/constructor, transformed to |
100 | /// Delta32FromGOT pointing at the GOT entry for the original target. |
101 | /// |
102 | /// Indicates that this edge should be transformed into a Delta32FromGOT |
103 | /// targeting the GOT entry for the edge's current target, maintaining the |
104 | /// same addend. |
105 | /// A GOT entry for the target should be created if one does not already |
106 | /// exist. |
107 | /// |
108 | /// Edges of this kind are usually handled by a GOT builder pass inserted by |
109 | /// default |
110 | /// |
111 | /// Fixup expression: |
112 | /// NONE |
113 | /// |
114 | /// Errors: |
115 | /// - *ASSERTION* Failure to handle edges of this kind prior to the fixup |
116 | /// phase will result in an assert/unreachable during the fixup phase |
117 | RequestGOTAndTransformToDelta32FromGOT, |
118 | |
119 | /// A 32-bit PC-relative branch. |
120 | /// |
121 | /// Represents a PC-relative call or branch to a target. This can be used to |
122 | /// identify, record, and/or patch call sites. |
123 | /// |
124 | /// Fixup expression: |
125 | /// Fixup <- Target - Fixup + Addend : int32 |
126 | /// |
127 | /// Errors: |
128 | /// - The result of the fixup expression must fit into an int32, otherwise |
129 | /// an out-of-range error will be returned. |
130 | /// |
131 | BranchPCRel32, |
132 | |
133 | /// A 32-bit PC-relative branch to a pointer jump stub. |
134 | /// |
135 | /// The target of this relocation should be a pointer jump stub of the form: |
136 | /// |
137 | /// \code{.s} |
138 | /// .text |
139 | /// jmp *tgtptr |
140 | /// ; ... |
141 | /// |
142 | /// .data |
143 | /// tgtptr: |
144 | /// .quad 0 |
145 | /// \endcode |
146 | /// |
147 | /// This edge kind has the same fixup expression as BranchPCRel32, but further |
148 | /// identifies the call/branch as being to a pointer jump stub. For edges of |
149 | /// this kind the jump stub should not be bypassed (use |
150 | /// BranchPCRel32ToPtrJumpStubBypassable for that), but the pointer location |
151 | /// target may be recorded to allow manipulation at runtime. |
152 | /// |
153 | /// Fixup expression: |
154 | /// Fixup <- Target - Fixup + Addend : int32 |
155 | /// |
156 | /// Errors: |
157 | /// - The result of the fixup expression must fit into an int32, otherwise |
158 | /// an out-of-range error will be returned. |
159 | /// |
160 | BranchPCRel32ToPtrJumpStub, |
161 | |
162 | /// A relaxable version of BranchPCRel32ToPtrJumpStub. |
163 | /// |
164 | /// The edge kind has the same fixup expression as BranchPCRel32ToPtrJumpStub, |
165 | /// but identifies the call/branch as being to a pointer jump stub that may be |
166 | /// bypassed with a direct jump to the ultimate target if the ultimate target |
167 | /// is within range of the fixup location. |
168 | /// |
169 | /// Fixup expression: |
170 | /// Fixup <- Target - Fixup + Addend : int32 |
171 | /// |
172 | /// Errors: |
173 | /// - The result of the fixup expression must fit into an int32, otherwise |
174 | /// an out-of-range error will be returned. |
175 | /// |
176 | BranchPCRel32ToPtrJumpStubBypassable, |
177 | }; |
178 | |
179 | /// Returns a string name for the given x86 edge. For debugging purposes |
180 | /// only |
181 | LLVM_ABI const char *getEdgeKindName(Edge::Kind K); |
182 | |
183 | /// Apply fixup expression for edge to block content. |
184 | inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E, |
185 | const Symbol *GOTSymbol) { |
186 | using namespace llvm::support; |
187 | |
188 | char *BlockWorkingMem = B.getAlreadyMutableContent().data(); |
189 | char *FixupPtr = BlockWorkingMem + E.getOffset(); |
190 | auto FixupAddress = B.getAddress() + E.getOffset(); |
191 | |
192 | switch (E.getKind()) { |
193 | case Pointer32: { |
194 | uint32_t Value = E.getTarget().getAddress().getValue() + E.getAddend(); |
195 | *(ulittle32_t *)FixupPtr = Value; |
196 | break; |
197 | } |
198 | |
199 | case PCRel32: { |
200 | int32_t Value = E.getTarget().getAddress() - FixupAddress + E.getAddend(); |
201 | *(little32_t *)FixupPtr = Value; |
202 | break; |
203 | } |
204 | |
205 | case Pointer16: { |
206 | uint32_t Value = E.getTarget().getAddress().getValue() + E.getAddend(); |
207 | if (LLVM_LIKELY(isUInt<16>(Value))) |
208 | *(ulittle16_t *)FixupPtr = Value; |
209 | else |
210 | return makeTargetOutOfRangeError(G, B, E); |
211 | break; |
212 | } |
213 | |
214 | case PCRel16: { |
215 | int32_t Value = E.getTarget().getAddress() - FixupAddress + E.getAddend(); |
216 | if (LLVM_LIKELY(isInt<16>(Value))) |
217 | *(little16_t *)FixupPtr = Value; |
218 | else |
219 | return makeTargetOutOfRangeError(G, B, E); |
220 | break; |
221 | } |
222 | |
223 | case Delta32: { |
224 | int32_t Value = E.getTarget().getAddress() - FixupAddress + E.getAddend(); |
225 | *(little32_t *)FixupPtr = Value; |
226 | break; |
227 | } |
228 | |
229 | case Delta32FromGOT: { |
230 | assert(GOTSymbol && "No GOT section symbol" ); |
231 | int32_t Value = |
232 | E.getTarget().getAddress() - GOTSymbol->getAddress() + E.getAddend(); |
233 | *(little32_t *)FixupPtr = Value; |
234 | break; |
235 | } |
236 | |
237 | case BranchPCRel32: |
238 | case BranchPCRel32ToPtrJumpStub: |
239 | case BranchPCRel32ToPtrJumpStubBypassable: { |
240 | int32_t Value = E.getTarget().getAddress() - FixupAddress + E.getAddend(); |
241 | *(little32_t *)FixupPtr = Value; |
242 | break; |
243 | } |
244 | |
245 | default: |
246 | return make_error<JITLinkError>( |
247 | Args: "In graph " + G.getName() + ", section " + B.getSection().getName() + |
248 | " unsupported edge kind " + getEdgeKindName(K: E.getKind())); |
249 | } |
250 | |
251 | return Error::success(); |
252 | } |
253 | |
254 | /// x86 pointer size. |
255 | constexpr uint32_t PointerSize = 4; |
256 | |
257 | /// x86 null pointer content. |
258 | LLVM_ABI extern const char NullPointerContent[PointerSize]; |
259 | |
260 | /// x86 pointer jump stub content. |
261 | /// |
262 | /// Contains the instruction sequence for an indirect jump via an in-memory |
263 | /// pointer: |
264 | /// jmpq *ptr |
265 | LLVM_ABI extern const char PointerJumpStubContent[6]; |
266 | |
267 | /// Creates a new pointer block in the given section and returns an anonymous |
268 | /// symbol pointing to it. |
269 | /// |
270 | /// If InitialTarget is given then an Pointer32 relocation will be added to the |
271 | /// block pointing at InitialTarget. |
272 | /// |
273 | /// The pointer block will have the following default values: |
274 | /// alignment: 32-bit |
275 | /// alignment-offset: 0 |
276 | /// address: highest allowable (~7U) |
277 | inline Symbol &createAnonymousPointer(LinkGraph &G, Section &PointerSection, |
278 | Symbol *InitialTarget = nullptr, |
279 | uint64_t InitialAddend = 0) { |
280 | auto &B = G.createContentBlock(Parent&: PointerSection, Content: NullPointerContent, |
281 | Address: orc::ExecutorAddr(), Alignment: 8, AlignmentOffset: 0); |
282 | if (InitialTarget) |
283 | B.addEdge(K: Pointer32, Offset: 0, Target&: *InitialTarget, Addend: InitialAddend); |
284 | return G.addAnonymousSymbol(Content&: B, Offset: 0, Size: PointerSize, IsCallable: false, IsLive: false); |
285 | } |
286 | |
287 | /// Create a jump stub block that jumps via the pointer at the given symbol. |
288 | /// |
289 | /// The stub block will have the following default values: |
290 | /// alignment: 8-bit |
291 | /// alignment-offset: 0 |
292 | /// address: highest allowable: (~5U) |
293 | inline Block &createPointerJumpStubBlock(LinkGraph &G, Section &StubSection, |
294 | Symbol &PointerSymbol) { |
295 | auto &B = G.createContentBlock(Parent&: StubSection, Content: PointerJumpStubContent, |
296 | Address: orc::ExecutorAddr(), Alignment: 8, AlignmentOffset: 0); |
297 | B.addEdge(K: Pointer32, |
298 | // Offset is 2 because the the first 2 bytes of the |
299 | // jump stub block are {0xff, 0x25} -- an indirect absolute |
300 | // jump. |
301 | Offset: 2, Target&: PointerSymbol, Addend: 0); |
302 | return B; |
303 | } |
304 | |
305 | /// Create a jump stub that jumps via the pointer at the given symbol and |
306 | /// an anonymous symbol pointing to it. Return the anonymous symbol. |
307 | /// |
308 | /// The stub block will be created by createPointerJumpStubBlock. |
309 | inline Symbol &createAnonymousPointerJumpStub(LinkGraph &G, |
310 | Section &StubSection, |
311 | Symbol &PointerSymbol) { |
312 | return G.addAnonymousSymbol( |
313 | Content&: createPointerJumpStubBlock(G, StubSection, PointerSymbol), Offset: 0, Size: 6, IsCallable: true, |
314 | IsLive: false); |
315 | } |
316 | |
317 | /// Global Offset Table Builder. |
318 | class GOTTableManager : public TableManager<GOTTableManager> { |
319 | public: |
320 | static StringRef getSectionName() { return "$__GOT" ; } |
321 | |
322 | bool visitEdge(LinkGraph &G, Block *B, Edge &E) { |
323 | Edge::Kind KindToSet = Edge::Invalid; |
324 | switch (E.getKind()) { |
325 | case Delta32FromGOT: { |
326 | // we need to make sure that the GOT section exists, but don't otherwise |
327 | // need to fix up this edge |
328 | getGOTSection(G); |
329 | return false; |
330 | } |
331 | case RequestGOTAndTransformToDelta32FromGOT: |
332 | KindToSet = Delta32FromGOT; |
333 | break; |
334 | default: |
335 | return false; |
336 | } |
337 | assert(KindToSet != Edge::Invalid && |
338 | "Fell through switch, but no new kind to set" ); |
339 | DEBUG_WITH_TYPE("jitlink" , { |
340 | dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at " |
341 | << B->getFixupAddress(E) << " (" << B->getAddress() << " + " |
342 | << formatv("{0:x}" , E.getOffset()) << ")\n" ; |
343 | }); |
344 | E.setKind(KindToSet); |
345 | E.setTarget(getEntryForTarget(G, Target&: E.getTarget())); |
346 | return true; |
347 | } |
348 | |
349 | Symbol &createEntry(LinkGraph &G, Symbol &Target) { |
350 | return createAnonymousPointer(G, PointerSection&: getGOTSection(G), InitialTarget: &Target); |
351 | } |
352 | |
353 | private: |
354 | Section &getGOTSection(LinkGraph &G) { |
355 | if (!GOTSection) |
356 | GOTSection = &G.createSection(Name: getSectionName(), Prot: orc::MemProt::Read); |
357 | return *GOTSection; |
358 | } |
359 | |
360 | Section *GOTSection = nullptr; |
361 | }; |
362 | |
363 | /// Procedure Linkage Table Builder. |
364 | class PLTTableManager : public TableManager<PLTTableManager> { |
365 | public: |
366 | PLTTableManager(GOTTableManager &GOT) : GOT(GOT) {} |
367 | |
368 | static StringRef getSectionName() { return "$__STUBS" ; } |
369 | |
370 | bool visitEdge(LinkGraph &G, Block *B, Edge &E) { |
371 | if (E.getKind() == BranchPCRel32 && !E.getTarget().isDefined()) { |
372 | DEBUG_WITH_TYPE("jitlink" , { |
373 | dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at " |
374 | << B->getFixupAddress(E) << " (" << B->getAddress() << " + " |
375 | << formatv("{0:x}" , E.getOffset()) << ")\n" ; |
376 | }); |
377 | // Set the edge kind to Branch32ToPtrJumpStubBypassable to enable it to |
378 | // be optimized when the target is in-range. |
379 | E.setKind(BranchPCRel32ToPtrJumpStubBypassable); |
380 | E.setTarget(getEntryForTarget(G, Target&: E.getTarget())); |
381 | return true; |
382 | } |
383 | return false; |
384 | } |
385 | |
386 | Symbol &createEntry(LinkGraph &G, Symbol &Target) { |
387 | return createAnonymousPointerJumpStub(G, StubSection&: getStubsSection(G), |
388 | PointerSymbol&: GOT.getEntryForTarget(G, Target)); |
389 | } |
390 | |
391 | public: |
392 | Section &getStubsSection(LinkGraph &G) { |
393 | if (!PLTSection) |
394 | PLTSection = &G.createSection(Name: getSectionName(), |
395 | Prot: orc::MemProt::Read | orc::MemProt::Exec); |
396 | return *PLTSection; |
397 | } |
398 | |
399 | GOTTableManager &GOT; |
400 | Section *PLTSection = nullptr; |
401 | }; |
402 | |
403 | /// Optimize the GOT and Stub relocations if the edge target address is in range |
404 | /// 1. PCRel32GOTLoadRelaxable. For this edge kind, if the target is in range, |
405 | /// then replace GOT load with lea. (THIS IS UNIMPLEMENTED RIGHT NOW!) |
406 | /// 2. BranchPCRel32ToPtrJumpStubRelaxable. For this edge kind, if the target is |
407 | /// in range, replace a indirect jump by plt stub with a direct jump to the |
408 | /// target |
409 | LLVM_ABI Error optimizeGOTAndStubAccesses(LinkGraph &G); |
410 | |
411 | } // namespace llvm::jitlink::x86 |
412 | |
413 | #endif // LLVM_EXECUTIONENGINE_JITLINK_X86_H |
414 | |