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
24namespace llvm {
25
26// This relocation type is used for handling long branch instruction
27// through the Stub.
28enum InternalRelocationType : unsigned {
29 INTERNAL_REL_ARM64_LONG_BRANCH26 = 0x111,
30};
31
32static 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
37static void or32le(void *P, int32_t V) {
38 using namespace llvm::support::endian;
39
40 write32le(P, V: read32le(P) | V);
41}
42
43static 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
51static 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
65static 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
75class RuntimeDyldCOFFAArch64 : public RuntimeDyldCOFF {
76
77private:
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
101public:
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