1 | //===-- RuntimeDyldCOFFAArch64.h --- COFF/AArch64 specific code ---*- 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 | // COFF AArch64 support for MC-JIT runtime dynamic linker. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #ifndef LLVM_LIB_EXECUTIONENGINE_RUNTIMEDYLD_TARGETS_RUNTIMEDYLDCOFFAARCH64_H |
14 | #define LLVM_LIB_EXECUTIONENGINE_RUNTIMEDYLD_TARGETS_RUNTIMEDYLDCOFFAARCH64_H |
15 | |
16 | #include "../RuntimeDyldCOFF.h" |
17 | #include "llvm/ADT/SmallString.h" |
18 | #include "llvm/BinaryFormat/COFF.h" |
19 | #include "llvm/Object/COFF.h" |
20 | #include "llvm/Support/Endian.h" |
21 | |
22 | #define DEBUG_TYPE "dyld" |
23 | |
24 | namespace llvm { |
25 | |
26 | // This relocation type is used for handling long branch instruction |
27 | // through the Stub. |
28 | enum InternalRelocationType : unsigned { |
29 | INTERNAL_REL_ARM64_LONG_BRANCH26 = 0x111, |
30 | }; |
31 | |
32 | static void add16(uint8_t *p, int16_t v) { |
33 | using namespace llvm::support::endian; |
34 | write16le(P: p, V: read16le(P: p) + v); |
35 | } |
36 | |
37 | static void or32le(void *P, int32_t V) { |
38 | using namespace llvm::support::endian; |
39 | |
40 | write32le(P, V: read32le(P) | V); |
41 | } |
42 | |
43 | static void write32AArch64Imm(uint8_t *T, uint64_t imm, uint32_t rangeLimit) { |
44 | using namespace llvm::support::endian; |
45 | |
46 | uint32_t orig = read32le(P: T); |
47 | orig &= ~(0xFFF << 10); |
48 | write32le(P: T, V: orig | ((imm & (0xFFF >> rangeLimit)) << 10)); |
49 | } |
50 | |
51 | static void write32AArch64Ldr(uint8_t *T, uint64_t imm) { |
52 | using namespace llvm::support::endian; |
53 | |
54 | uint32_t orig = read32le(P: T); |
55 | uint32_t size = orig >> 30; |
56 | // 0x04000000 indicates SIMD/FP registers |
57 | // 0x00800000 indicates 128 bit |
58 | if ((orig & 0x04800000) == 0x04800000) |
59 | size += 4; |
60 | if ((imm & ((1 << size) - 1)) != 0) |
61 | assert(0 && "misaligned ldr/str offset" ); |
62 | write32AArch64Imm(T, imm: imm >> size, rangeLimit: size); |
63 | } |
64 | |
65 | static void write32AArch64Addr(void *T, uint64_t s, uint64_t p, int shift) { |
66 | using namespace llvm::support::endian; |
67 | |
68 | uint64_t Imm = (s >> shift) - (p >> shift); |
69 | uint32_t ImmLo = (Imm & 0x3) << 29; |
70 | uint32_t ImmHi = (Imm & 0x1FFFFC) << 3; |
71 | uint64_t Mask = (0x3 << 29) | (0x1FFFFC << 3); |
72 | write32le(P: T, V: (read32le(P: T) & ~Mask) | ImmLo | ImmHi); |
73 | } |
74 | |
75 | class RuntimeDyldCOFFAArch64 : public RuntimeDyldCOFF { |
76 | |
77 | private: |
78 | // When a module is loaded we save the SectionID of the unwind |
79 | // sections in a table until we receive a request to register all |
80 | // unregisteredEH frame sections with the memory manager. |
81 | SmallVector<SID, 2> UnregisteredEHFrameSections; |
82 | SmallVector<SID, 2> RegisteredEHFrameSections; |
83 | uint64_t ImageBase; |
84 | |
85 | // Fake an __ImageBase pointer by returning the section with the lowest adress |
86 | uint64_t getImageBase() { |
87 | if (!ImageBase) { |
88 | ImageBase = std::numeric_limits<uint64_t>::max(); |
89 | for (const SectionEntry &Section : Sections) |
90 | // The Sections list may contain sections that weren't loaded for |
91 | // whatever reason: they may be debug sections, and ProcessAllSections |
92 | // is false, or they may be sections that contain 0 bytes. If the |
93 | // section isn't loaded, the load address will be 0, and it should not |
94 | // be included in the ImageBase calculation. |
95 | if (Section.getLoadAddress() != 0) |
96 | ImageBase = std::min(a: ImageBase, b: Section.getLoadAddress()); |
97 | } |
98 | return ImageBase; |
99 | } |
100 | |
101 | public: |
102 | RuntimeDyldCOFFAArch64(RuntimeDyld::MemoryManager &MM, |
103 | JITSymbolResolver &Resolver) |
104 | : RuntimeDyldCOFF(MM, Resolver, 8, COFF::IMAGE_REL_ARM64_ADDR64), |
105 | ImageBase(0) {} |
106 | |
107 | Align getStubAlignment() override { return Align(8); } |
108 | |
109 | unsigned getMaxStubSize() const override { return 20; } |
110 | |
111 | std::tuple<uint64_t, uint64_t, uint64_t> |
112 | generateRelocationStub(unsigned SectionID, StringRef TargetName, |
113 | uint64_t Offset, uint64_t RelType, uint64_t Addend, |
114 | StubMap &Stubs) { |
115 | uintptr_t StubOffset; |
116 | SectionEntry &Section = Sections[SectionID]; |
117 | |
118 | RelocationValueRef OriginalRelValueRef; |
119 | OriginalRelValueRef.SectionID = SectionID; |
120 | OriginalRelValueRef.Offset = Offset; |
121 | OriginalRelValueRef.Addend = Addend; |
122 | OriginalRelValueRef.SymbolName = TargetName.data(); |
123 | |
124 | auto [Stub, Inserted] = Stubs.try_emplace(k: OriginalRelValueRef); |
125 | if (Inserted) { |
126 | LLVM_DEBUG(dbgs() << " Create a new stub function for " |
127 | << TargetName.data() << "\n" ); |
128 | |
129 | StubOffset = Section.getStubOffset(); |
130 | Stub->second = StubOffset; |
131 | createStubFunction(Addr: Section.getAddressWithOffset(OffsetBytes: StubOffset)); |
132 | Section.advanceStubOffset(StubSize: getMaxStubSize()); |
133 | } else { |
134 | LLVM_DEBUG(dbgs() << " Stub function found for " << TargetName.data() |
135 | << "\n" ); |
136 | StubOffset = Stub->second; |
137 | } |
138 | |
139 | // Resolve original relocation to stub function. |
140 | const RelocationEntry RE(SectionID, Offset, RelType, Addend); |
141 | resolveRelocation(RE, Value: Section.getLoadAddressWithOffset(OffsetBytes: StubOffset)); |
142 | |
143 | // adjust relocation info so resolution writes to the stub function |
144 | // Here an internal relocation type is used for resolving long branch via |
145 | // stub instruction. |
146 | Addend = 0; |
147 | Offset = StubOffset; |
148 | RelType = INTERNAL_REL_ARM64_LONG_BRANCH26; |
149 | |
150 | return std::make_tuple(args&: Offset, args&: RelType, args&: Addend); |
151 | } |
152 | |
153 | Expected<object::relocation_iterator> |
154 | processRelocationRef(unsigned SectionID, object::relocation_iterator RelI, |
155 | const object::ObjectFile &Obj, |
156 | ObjSectionToIDMap &ObjSectionToID, |
157 | StubMap &Stubs) override { |
158 | using namespace llvm::support::endian; |
159 | |
160 | auto Symbol = RelI->getSymbol(); |
161 | if (Symbol == Obj.symbol_end()) |
162 | report_fatal_error(reason: "Unknown symbol in relocation" ); |
163 | |
164 | Expected<StringRef> TargetNameOrErr = Symbol->getName(); |
165 | if (!TargetNameOrErr) |
166 | return TargetNameOrErr.takeError(); |
167 | StringRef TargetName = *TargetNameOrErr; |
168 | |
169 | auto SectionOrErr = Symbol->getSection(); |
170 | if (!SectionOrErr) |
171 | return SectionOrErr.takeError(); |
172 | auto Section = *SectionOrErr; |
173 | |
174 | uint64_t RelType = RelI->getType(); |
175 | uint64_t Offset = RelI->getOffset(); |
176 | |
177 | // If there is no section, this must be an external reference. |
178 | bool IsExtern = Section == Obj.section_end(); |
179 | |
180 | // Determine the Addend used to adjust the relocation value. |
181 | uint64_t Addend = 0; |
182 | SectionEntry &AddendSection = Sections[SectionID]; |
183 | uintptr_t ObjTarget = AddendSection.getObjAddress() + Offset; |
184 | uint8_t *Displacement = (uint8_t *)ObjTarget; |
185 | |
186 | unsigned TargetSectionID = -1; |
187 | uint64_t TargetOffset = -1; |
188 | |
189 | if (TargetName.starts_with(Prefix: getImportSymbolPrefix())) { |
190 | TargetSectionID = SectionID; |
191 | TargetOffset = getDLLImportOffset(SectionID, Stubs, Name: TargetName); |
192 | TargetName = StringRef(); |
193 | IsExtern = false; |
194 | } else if (!IsExtern) { |
195 | if (auto TargetSectionIDOrErr = findOrEmitSection( |
196 | Obj, Section: *Section, IsCode: Section->isText(), LocalSections&: ObjSectionToID)) |
197 | TargetSectionID = *TargetSectionIDOrErr; |
198 | else |
199 | return TargetSectionIDOrErr.takeError(); |
200 | |
201 | TargetOffset = getSymbolOffset(Sym: *Symbol); |
202 | } |
203 | |
204 | switch (RelType) { |
205 | case COFF::IMAGE_REL_ARM64_ADDR32: |
206 | case COFF::IMAGE_REL_ARM64_ADDR32NB: |
207 | case COFF::IMAGE_REL_ARM64_REL32: |
208 | case COFF::IMAGE_REL_ARM64_SECREL: |
209 | Addend = read32le(P: Displacement); |
210 | break; |
211 | case COFF::IMAGE_REL_ARM64_BRANCH26: { |
212 | uint32_t orig = read32le(P: Displacement); |
213 | Addend = (orig & 0x03FFFFFF) << 2; |
214 | |
215 | if (IsExtern) |
216 | std::tie(args&: Offset, args&: RelType, args&: Addend) = generateRelocationStub( |
217 | SectionID, TargetName, Offset, RelType, Addend, Stubs); |
218 | break; |
219 | } |
220 | case COFF::IMAGE_REL_ARM64_BRANCH19: { |
221 | uint32_t orig = read32le(P: Displacement); |
222 | Addend = (orig & 0x00FFFFE0) >> 3; |
223 | break; |
224 | } |
225 | case COFF::IMAGE_REL_ARM64_BRANCH14: { |
226 | uint32_t orig = read32le(P: Displacement); |
227 | Addend = (orig & 0x000FFFE0) >> 3; |
228 | break; |
229 | } |
230 | case COFF::IMAGE_REL_ARM64_REL21: |
231 | case COFF::IMAGE_REL_ARM64_PAGEBASE_REL21: { |
232 | uint32_t orig = read32le(P: Displacement); |
233 | Addend = ((orig >> 29) & 0x3) | ((orig >> 3) & 0x1FFFFC); |
234 | break; |
235 | } |
236 | case COFF::IMAGE_REL_ARM64_PAGEOFFSET_12L: |
237 | case COFF::IMAGE_REL_ARM64_PAGEOFFSET_12A: { |
238 | uint32_t orig = read32le(P: Displacement); |
239 | Addend = ((orig >> 10) & 0xFFF); |
240 | break; |
241 | } |
242 | case COFF::IMAGE_REL_ARM64_ADDR64: { |
243 | Addend = read64le(P: Displacement); |
244 | break; |
245 | } |
246 | default: |
247 | break; |
248 | } |
249 | |
250 | #if !defined(NDEBUG) |
251 | SmallString<32> RelTypeName; |
252 | RelI->getTypeName(RelTypeName); |
253 | |
254 | LLVM_DEBUG(dbgs() << "\t\tIn Section " << SectionID << " Offset " << Offset |
255 | << " RelType: " << RelTypeName << " TargetName: " |
256 | << TargetName << " Addend " << Addend << "\n" ); |
257 | #endif |
258 | |
259 | if (IsExtern) { |
260 | RelocationEntry RE(SectionID, Offset, RelType, Addend); |
261 | addRelocationForSymbol(RE, SymbolName: TargetName); |
262 | } else { |
263 | RelocationEntry RE(SectionID, Offset, RelType, TargetOffset + Addend); |
264 | addRelocationForSection(RE, SectionID: TargetSectionID); |
265 | } |
266 | return ++RelI; |
267 | } |
268 | |
269 | void resolveRelocation(const RelocationEntry &RE, uint64_t Value) override { |
270 | using namespace llvm::support::endian; |
271 | |
272 | const auto Section = Sections[RE.SectionID]; |
273 | uint8_t *Target = Section.getAddressWithOffset(OffsetBytes: RE.Offset); |
274 | uint64_t FinalAddress = Section.getLoadAddressWithOffset(OffsetBytes: RE.Offset); |
275 | |
276 | switch (RE.RelType) { |
277 | default: |
278 | llvm_unreachable("unsupported relocation type" ); |
279 | case COFF::IMAGE_REL_ARM64_ABSOLUTE: { |
280 | // This relocation is ignored. |
281 | break; |
282 | } |
283 | case COFF::IMAGE_REL_ARM64_PAGEBASE_REL21: { |
284 | // The page base of the target, for ADRP instruction. |
285 | Value += RE.Addend; |
286 | write32AArch64Addr(T: Target, s: Value, p: FinalAddress, shift: 12); |
287 | break; |
288 | } |
289 | case COFF::IMAGE_REL_ARM64_REL21: { |
290 | // The 12-bit relative displacement to the target, for instruction ADR |
291 | Value += RE.Addend; |
292 | write32AArch64Addr(T: Target, s: Value, p: FinalAddress, shift: 0); |
293 | break; |
294 | } |
295 | case COFF::IMAGE_REL_ARM64_PAGEOFFSET_12A: { |
296 | // The 12-bit page offset of the target, |
297 | // for instructions ADD/ADDS (immediate) with zero shift. |
298 | Value += RE.Addend; |
299 | write32AArch64Imm(T: Target, imm: Value & 0xFFF, rangeLimit: 0); |
300 | break; |
301 | } |
302 | case COFF::IMAGE_REL_ARM64_PAGEOFFSET_12L: { |
303 | // The 12-bit page offset of the target, |
304 | // for instruction LDR (indexed, unsigned immediate). |
305 | Value += RE.Addend; |
306 | write32AArch64Ldr(T: Target, imm: Value & 0xFFF); |
307 | break; |
308 | } |
309 | case COFF::IMAGE_REL_ARM64_ADDR32: { |
310 | // The 32-bit VA of the target. |
311 | uint32_t VA = Value + RE.Addend; |
312 | write32le(P: Target, V: VA); |
313 | break; |
314 | } |
315 | case COFF::IMAGE_REL_ARM64_ADDR32NB: { |
316 | // The target's 32-bit RVA. |
317 | uint64_t RVA = Value + RE.Addend - getImageBase(); |
318 | write32le(P: Target, V: RVA); |
319 | break; |
320 | } |
321 | case INTERNAL_REL_ARM64_LONG_BRANCH26: { |
322 | // Encode the immadiate value for generated Stub instruction (MOVZ) |
323 | or32le(P: Target + 12, V: ((Value + RE.Addend) & 0xFFFF) << 5); |
324 | or32le(P: Target + 8, V: ((Value + RE.Addend) & 0xFFFF0000) >> 11); |
325 | or32le(P: Target + 4, V: ((Value + RE.Addend) & 0xFFFF00000000) >> 27); |
326 | or32le(P: Target + 0, V: ((Value + RE.Addend) & 0xFFFF000000000000) >> 43); |
327 | break; |
328 | } |
329 | case COFF::IMAGE_REL_ARM64_BRANCH26: { |
330 | // The 26-bit relative displacement to the target, for B and BL |
331 | // instructions. |
332 | uint64_t PCRelVal = Value + RE.Addend - FinalAddress; |
333 | assert(isInt<28>(PCRelVal) && "Branch target is out of range." ); |
334 | write32le(P: Target, V: (read32le(P: Target) & ~(0x03FFFFFF)) | |
335 | (PCRelVal & 0x0FFFFFFC) >> 2); |
336 | break; |
337 | } |
338 | case COFF::IMAGE_REL_ARM64_BRANCH19: { |
339 | // The 19-bit offset to the relocation target, |
340 | // for conditional B instruction. |
341 | uint64_t PCRelVal = Value + RE.Addend - FinalAddress; |
342 | assert(isInt<21>(PCRelVal) && "Branch target is out of range." ); |
343 | write32le(P: Target, V: (read32le(P: Target) & ~(0x00FFFFE0)) | |
344 | (PCRelVal & 0x001FFFFC) << 3); |
345 | break; |
346 | } |
347 | case COFF::IMAGE_REL_ARM64_BRANCH14: { |
348 | // The 14-bit offset to the relocation target, |
349 | // for instructions TBZ and TBNZ. |
350 | uint64_t PCRelVal = Value + RE.Addend - FinalAddress; |
351 | assert(isInt<16>(PCRelVal) && "Branch target is out of range." ); |
352 | write32le(P: Target, V: (read32le(P: Target) & ~(0x000FFFE0)) | |
353 | (PCRelVal & 0x0000FFFC) << 3); |
354 | break; |
355 | } |
356 | case COFF::IMAGE_REL_ARM64_ADDR64: { |
357 | // The 64-bit VA of the relocation target. |
358 | write64le(P: Target, V: Value + RE.Addend); |
359 | break; |
360 | } |
361 | case COFF::IMAGE_REL_ARM64_SECTION: { |
362 | // 16-bit section index of the section that contains the target. |
363 | assert(static_cast<uint32_t>(RE.SectionID) <= UINT16_MAX && |
364 | "relocation overflow" ); |
365 | add16(p: Target, v: RE.SectionID); |
366 | break; |
367 | } |
368 | case COFF::IMAGE_REL_ARM64_SECREL: { |
369 | // 32-bit offset of the target from the beginning of its section. |
370 | assert(static_cast<int64_t>(RE.Addend) <= INT32_MAX && |
371 | "Relocation overflow" ); |
372 | assert(static_cast<int64_t>(RE.Addend) >= INT32_MIN && |
373 | "Relocation underflow" ); |
374 | write32le(P: Target, V: RE.Addend); |
375 | break; |
376 | } |
377 | case COFF::IMAGE_REL_ARM64_REL32: { |
378 | // The 32-bit relative address from the byte following the relocation. |
379 | uint64_t Result = Value - FinalAddress - 4; |
380 | write32le(P: Target, V: Result + RE.Addend); |
381 | break; |
382 | } |
383 | } |
384 | } |
385 | |
386 | void registerEHFrames() override {} |
387 | }; |
388 | |
389 | } // End namespace llvm |
390 | |
391 | #undef DEBUG_TYPE |
392 | |
393 | #endif // LLVM_LIB_EXECUTIONENGINE_RUNTIMEDYLD_TARGETS_RUNTIMEDYLDCOFFAARCH64_H |
394 | |