377 lines
13 KiB
C++
377 lines
13 KiB
C++
//===-- RuntimeDyldCOFFAArch64.h --- COFF/AArch64 specific code ---*- C++
|
|
//-*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// COFF AArch64 support for MC-JIT runtime dynamic linker.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef LLVM_LIB_EXECUTIONENGINE_RUNTIMEDYLD_TARGETS_RUNTIMEDYLDCOFFAARCH64_H
|
|
#define LLVM_LIB_EXECUTIONENGINE_RUNTIMEDYLD_TARGETS_RUNTIMEDYLDCOFFAARCH64_H
|
|
|
|
#include "../RuntimeDyldCOFF.h"
|
|
#include "llvm/BinaryFormat/COFF.h"
|
|
#include "llvm/Object/COFF.h"
|
|
#include "llvm/Support/Endian.h"
|
|
|
|
#define DEBUG_TYPE "dyld"
|
|
|
|
using namespace llvm::support::endian;
|
|
|
|
namespace llvm {
|
|
|
|
// This relocation type is used for handling long branch instruction
|
|
// throught the Stub.
|
|
enum InternalRelocationType : unsigned {
|
|
INTERNAL_REL_ARM64_LONG_BRANCH26 = 0x111,
|
|
};
|
|
|
|
static void add16(uint8_t *p, int16_t v) { write16le(p, read16le(p) + v); }
|
|
static void or32le(void *P, int32_t V) { write32le(P, read32le(P) | V); }
|
|
|
|
static void write32AArch64Imm(uint8_t *T, uint64_t imm, uint32_t rangeLimit) {
|
|
uint32_t orig = read32le(T);
|
|
orig &= ~(0xFFF << 10);
|
|
write32le(T, orig | ((imm & (0xFFF >> rangeLimit)) << 10));
|
|
}
|
|
|
|
static void write32AArch64Ldr(uint8_t *T, uint64_t imm) {
|
|
uint32_t orig = read32le(T);
|
|
uint32_t size = orig >> 30;
|
|
// 0x04000000 indicates SIMD/FP registers
|
|
// 0x00800000 indicates 128 bit
|
|
if ((orig & 0x04800000) == 0x04800000)
|
|
size += 4;
|
|
if ((imm & ((1 << size) - 1)) != 0)
|
|
assert(0 && "misaligned ldr/str offset");
|
|
write32AArch64Imm(T, imm >> size, size);
|
|
}
|
|
|
|
static void write32AArch64Addr(void *T, uint64_t s, uint64_t p, int shift) {
|
|
uint64_t Imm = (s >> shift) - (p >> shift);
|
|
uint32_t ImmLo = (Imm & 0x3) << 29;
|
|
uint32_t ImmHi = (Imm & 0x1FFFFC) << 3;
|
|
uint64_t Mask = (0x3 << 29) | (0x1FFFFC << 3);
|
|
write32le(T, (read32le(T) & ~Mask) | ImmLo | ImmHi);
|
|
}
|
|
|
|
class RuntimeDyldCOFFAArch64 : public RuntimeDyldCOFF {
|
|
|
|
private:
|
|
// When a module is loaded we save the SectionID of the unwind
|
|
// sections in a table until we receive a request to register all
|
|
// unregisteredEH frame sections with the memory manager.
|
|
SmallVector<SID, 2> UnregisteredEHFrameSections;
|
|
SmallVector<SID, 2> RegisteredEHFrameSections;
|
|
uint64_t ImageBase;
|
|
|
|
// Fake an __ImageBase pointer by returning the section with the lowest adress
|
|
uint64_t getImageBase() {
|
|
if (!ImageBase) {
|
|
ImageBase = std::numeric_limits<uint64_t>::max();
|
|
for (const SectionEntry &Section : Sections)
|
|
// The Sections list may contain sections that weren't loaded for
|
|
// whatever reason: they may be debug sections, and ProcessAllSections
|
|
// is false, or they may be sections that contain 0 bytes. If the
|
|
// section isn't loaded, the load address will be 0, and it should not
|
|
// be included in the ImageBase calculation.
|
|
if (Section.getLoadAddress() != 0)
|
|
ImageBase = std::min(ImageBase, Section.getLoadAddress());
|
|
}
|
|
return ImageBase;
|
|
}
|
|
|
|
public:
|
|
RuntimeDyldCOFFAArch64(RuntimeDyld::MemoryManager &MM,
|
|
JITSymbolResolver &Resolver)
|
|
: RuntimeDyldCOFF(MM, Resolver, 8, COFF::IMAGE_REL_ARM64_ADDR64),
|
|
ImageBase(0) {}
|
|
|
|
unsigned getStubAlignment() override { return 8; }
|
|
|
|
unsigned getMaxStubSize() const override { return 20; }
|
|
|
|
std::tuple<uint64_t, uint64_t, uint64_t>
|
|
generateRelocationStub(unsigned SectionID, StringRef TargetName,
|
|
uint64_t Offset, uint64_t RelType, uint64_t Addend,
|
|
StubMap &Stubs) {
|
|
uintptr_t StubOffset;
|
|
SectionEntry &Section = Sections[SectionID];
|
|
|
|
RelocationValueRef OriginalRelValueRef;
|
|
OriginalRelValueRef.SectionID = SectionID;
|
|
OriginalRelValueRef.Offset = Offset;
|
|
OriginalRelValueRef.Addend = Addend;
|
|
OriginalRelValueRef.SymbolName = TargetName.data();
|
|
|
|
auto Stub = Stubs.find(OriginalRelValueRef);
|
|
if (Stub == Stubs.end()) {
|
|
LLVM_DEBUG(dbgs() << " Create a new stub function for "
|
|
<< TargetName.data() << "\n");
|
|
|
|
StubOffset = Section.getStubOffset();
|
|
Stubs[OriginalRelValueRef] = StubOffset;
|
|
createStubFunction(Section.getAddressWithOffset(StubOffset));
|
|
Section.advanceStubOffset(getMaxStubSize());
|
|
} else {
|
|
LLVM_DEBUG(dbgs() << " Stub function found for " << TargetName.data()
|
|
<< "\n");
|
|
StubOffset = Stub->second;
|
|
}
|
|
|
|
// Resolve original relocation to stub function.
|
|
const RelocationEntry RE(SectionID, Offset, RelType, Addend);
|
|
resolveRelocation(RE, Section.getLoadAddressWithOffset(StubOffset));
|
|
|
|
// adjust relocation info so resolution writes to the stub function
|
|
// Here an internal relocation type is used for resolving long branch via
|
|
// stub instruction.
|
|
Addend = 0;
|
|
Offset = StubOffset;
|
|
RelType = INTERNAL_REL_ARM64_LONG_BRANCH26;
|
|
|
|
return std::make_tuple(Offset, RelType, Addend);
|
|
}
|
|
|
|
Expected<object::relocation_iterator>
|
|
processRelocationRef(unsigned SectionID, object::relocation_iterator RelI,
|
|
const object::ObjectFile &Obj,
|
|
ObjSectionToIDMap &ObjSectionToID,
|
|
StubMap &Stubs) override {
|
|
|
|
auto Symbol = RelI->getSymbol();
|
|
if (Symbol == Obj.symbol_end())
|
|
report_fatal_error("Unknown symbol in relocation");
|
|
|
|
Expected<StringRef> TargetNameOrErr = Symbol->getName();
|
|
if (!TargetNameOrErr)
|
|
return TargetNameOrErr.takeError();
|
|
StringRef TargetName = *TargetNameOrErr;
|
|
|
|
auto SectionOrErr = Symbol->getSection();
|
|
if (!SectionOrErr)
|
|
return SectionOrErr.takeError();
|
|
auto Section = *SectionOrErr;
|
|
|
|
uint64_t RelType = RelI->getType();
|
|
uint64_t Offset = RelI->getOffset();
|
|
|
|
// If there is no section, this must be an external reference.
|
|
bool IsExtern = Section == Obj.section_end();
|
|
|
|
// Determine the Addend used to adjust the relocation value.
|
|
uint64_t Addend = 0;
|
|
SectionEntry &AddendSection = Sections[SectionID];
|
|
uintptr_t ObjTarget = AddendSection.getObjAddress() + Offset;
|
|
uint8_t *Displacement = (uint8_t *)ObjTarget;
|
|
|
|
unsigned TargetSectionID = -1;
|
|
uint64_t TargetOffset = -1;
|
|
|
|
if (TargetName.startswith(getImportSymbolPrefix())) {
|
|
TargetSectionID = SectionID;
|
|
TargetOffset = getDLLImportOffset(SectionID, Stubs, TargetName);
|
|
TargetName = StringRef();
|
|
IsExtern = false;
|
|
} else if (!IsExtern) {
|
|
if (auto TargetSectionIDOrErr = findOrEmitSection(
|
|
Obj, *Section, Section->isText(), ObjSectionToID))
|
|
TargetSectionID = *TargetSectionIDOrErr;
|
|
else
|
|
return TargetSectionIDOrErr.takeError();
|
|
|
|
TargetOffset = getSymbolOffset(*Symbol);
|
|
}
|
|
|
|
switch (RelType) {
|
|
case COFF::IMAGE_REL_ARM64_ADDR32:
|
|
case COFF::IMAGE_REL_ARM64_ADDR32NB:
|
|
case COFF::IMAGE_REL_ARM64_REL32:
|
|
case COFF::IMAGE_REL_ARM64_SECREL:
|
|
Addend = read32le(Displacement);
|
|
break;
|
|
case COFF::IMAGE_REL_ARM64_BRANCH26: {
|
|
uint32_t orig = read32le(Displacement);
|
|
Addend = (orig & 0x03FFFFFF) << 2;
|
|
|
|
if (IsExtern)
|
|
std::tie(Offset, RelType, Addend) = generateRelocationStub(
|
|
SectionID, TargetName, Offset, RelType, Addend, Stubs);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_BRANCH19: {
|
|
uint32_t orig = read32le(Displacement);
|
|
Addend = (orig & 0x00FFFFE0) >> 3;
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_BRANCH14: {
|
|
uint32_t orig = read32le(Displacement);
|
|
Addend = (orig & 0x000FFFE0) >> 3;
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_REL21:
|
|
case COFF::IMAGE_REL_ARM64_PAGEBASE_REL21: {
|
|
uint32_t orig = read32le(Displacement);
|
|
Addend = ((orig >> 29) & 0x3) | ((orig >> 3) & 0x1FFFFC);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_PAGEOFFSET_12L:
|
|
case COFF::IMAGE_REL_ARM64_PAGEOFFSET_12A: {
|
|
uint32_t orig = read32le(Displacement);
|
|
Addend = ((orig >> 10) & 0xFFF);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_ADDR64: {
|
|
Addend = read64le(Displacement);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
#if !defined(NDEBUG)
|
|
SmallString<32> RelTypeName;
|
|
RelI->getTypeName(RelTypeName);
|
|
|
|
LLVM_DEBUG(dbgs() << "\t\tIn Section " << SectionID << " Offset " << Offset
|
|
<< " RelType: " << RelTypeName << " TargetName: "
|
|
<< TargetName << " Addend " << Addend << "\n");
|
|
#endif
|
|
|
|
if (IsExtern) {
|
|
RelocationEntry RE(SectionID, Offset, RelType, Addend);
|
|
addRelocationForSymbol(RE, TargetName);
|
|
} else {
|
|
RelocationEntry RE(SectionID, Offset, RelType, TargetOffset + Addend);
|
|
addRelocationForSection(RE, TargetSectionID);
|
|
}
|
|
return ++RelI;
|
|
}
|
|
|
|
void resolveRelocation(const RelocationEntry &RE, uint64_t Value) override {
|
|
const auto Section = Sections[RE.SectionID];
|
|
uint8_t *Target = Section.getAddressWithOffset(RE.Offset);
|
|
uint64_t FinalAddress = Section.getLoadAddressWithOffset(RE.Offset);
|
|
|
|
switch (RE.RelType) {
|
|
default:
|
|
llvm_unreachable("unsupported relocation type");
|
|
case COFF::IMAGE_REL_ARM64_ABSOLUTE: {
|
|
// This relocation is ignored.
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_PAGEBASE_REL21: {
|
|
// The page base of the target, for ADRP instruction.
|
|
Value += RE.Addend;
|
|
write32AArch64Addr(Target, Value, FinalAddress, 12);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_REL21: {
|
|
// The 12-bit relative displacement to the target, for instruction ADR
|
|
Value += RE.Addend;
|
|
write32AArch64Addr(Target, Value, FinalAddress, 0);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_PAGEOFFSET_12A: {
|
|
// The 12-bit page offset of the target,
|
|
// for instructions ADD/ADDS (immediate) with zero shift.
|
|
Value += RE.Addend;
|
|
write32AArch64Imm(Target, Value & 0xFFF, 0);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_PAGEOFFSET_12L: {
|
|
// The 12-bit page offset of the target,
|
|
// for instruction LDR (indexed, unsigned immediate).
|
|
Value += RE.Addend;
|
|
write32AArch64Ldr(Target, Value & 0xFFF);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_ADDR32: {
|
|
// The 32-bit VA of the target.
|
|
uint32_t VA = Value + RE.Addend;
|
|
write32le(Target, VA);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_ADDR32NB: {
|
|
// The target's 32-bit RVA.
|
|
uint64_t RVA = Value + RE.Addend - getImageBase();
|
|
write32le(Target, RVA);
|
|
break;
|
|
}
|
|
case INTERNAL_REL_ARM64_LONG_BRANCH26: {
|
|
// Encode the immadiate value for generated Stub instruction (MOVZ)
|
|
or32le(Target + 12, ((Value + RE.Addend) & 0xFFFF) << 5);
|
|
or32le(Target + 8, ((Value + RE.Addend) & 0xFFFF0000) >> 11);
|
|
or32le(Target + 4, ((Value + RE.Addend) & 0xFFFF00000000) >> 27);
|
|
or32le(Target + 0, ((Value + RE.Addend) & 0xFFFF000000000000) >> 43);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_BRANCH26: {
|
|
// The 26-bit relative displacement to the target, for B and BL
|
|
// instructions.
|
|
uint64_t PCRelVal = Value + RE.Addend - FinalAddress;
|
|
assert(isInt<28>(PCRelVal) && "Branch target is out of range.");
|
|
write32le(Target, (read32le(Target) & ~(0x03FFFFFF)) |
|
|
(PCRelVal & 0x0FFFFFFC) >> 2);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_BRANCH19: {
|
|
// The 19-bit offset to the relocation target,
|
|
// for conditional B instruction.
|
|
uint64_t PCRelVal = Value + RE.Addend - FinalAddress;
|
|
assert(isInt<21>(PCRelVal) && "Branch target is out of range.");
|
|
write32le(Target, (read32le(Target) & ~(0x00FFFFE0)) |
|
|
(PCRelVal & 0x001FFFFC) << 3);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_BRANCH14: {
|
|
// The 14-bit offset to the relocation target,
|
|
// for instructions TBZ and TBNZ.
|
|
uint64_t PCRelVal = Value + RE.Addend - FinalAddress;
|
|
assert(isInt<16>(PCRelVal) && "Branch target is out of range.");
|
|
write32le(Target, (read32le(Target) & ~(0x000FFFE0)) |
|
|
(PCRelVal & 0x0000FFFC) << 3);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_ADDR64: {
|
|
// The 64-bit VA of the relocation target.
|
|
write64le(Target, Value + RE.Addend);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_SECTION: {
|
|
// 16-bit section index of the section that contains the target.
|
|
assert(static_cast<uint32_t>(RE.SectionID) <= UINT16_MAX &&
|
|
"relocation overflow");
|
|
add16(Target, RE.SectionID);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_SECREL: {
|
|
// 32-bit offset of the target from the beginning of its section.
|
|
assert(static_cast<int64_t>(RE.Addend) <= INT32_MAX &&
|
|
"Relocation overflow");
|
|
assert(static_cast<int64_t>(RE.Addend) >= INT32_MIN &&
|
|
"Relocation underflow");
|
|
write32le(Target, RE.Addend);
|
|
break;
|
|
}
|
|
case COFF::IMAGE_REL_ARM64_REL32: {
|
|
// The 32-bit relative address from the byte following the relocation.
|
|
uint64_t Result = Value - FinalAddress - 4;
|
|
write32le(Target, Result + RE.Addend);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void registerEHFrames() override {}
|
|
};
|
|
|
|
} // End namespace llvm
|
|
|
|
#endif
|