Index: COFF/Chunks.h =================================================================== --- COFF/Chunks.h +++ COFF/Chunks.h @@ -141,6 +141,7 @@ SectionChunk(ObjFile *File, const coff_section *Header); static bool classof(const Chunk *C) { return C->kind() == SectionKind; } + void finalizeContents() override; size_t getSize() const override { return Header->SizeOfRawData; } ArrayRef getContents() const; void writeTo(uint8_t *Buf) const override; @@ -223,6 +224,7 @@ DefinedRegular *Sym = nullptr; ArrayRef Relocs; + std::vector Symbols; private: StringRef SectionName; @@ -351,6 +353,16 @@ Defined *ImpSymbol; }; +class RangeExtensionThunkARM : public Chunk { +public: + explicit RangeExtensionThunkARM(Defined *T) : Target(T) {} + size_t getSize() const override; + void writeTo(uint8_t *Buf) const override; + +private: + Defined *Target; +}; + // Windows-specific. // See comments for DefinedLocalImport class. class LocalImportChunk : public Chunk { Index: COFF/Chunks.cpp =================================================================== --- COFF/Chunks.cpp +++ COFF/Chunks.cpp @@ -44,6 +44,16 @@ Live = !Config->DoGC || !isCOMDAT(); } +// Initialize the Symbols vector, to allow redirecting certain relocations +// to a thunk instead of the actual symbol the relocation's symbol table index +// indicates. +void SectionChunk::finalizeContents() { + if (!Symbols.empty()) + return; + for (const coff_relocation &Rel : Relocs) + Symbols.push_back(File->getSymbol(Rel.SymbolTableIndex)); +} + static void add16(uint8_t *P, int16_t V) { write16le(P, read16le(P) + V); } static void add32(uint8_t *P, int32_t V) { write32le(P, read32le(P) + V); } static void add64(uint8_t *P, int64_t V) { write64le(P, read64le(P) + V); } @@ -303,6 +313,7 @@ // Apply relocations. size_t InputSize = getSize(); + size_t Idx = 0; for (const coff_relocation &Rel : Relocs) { // Check for an invalid relocation offset. This check isn't perfect, because // we don't have the relocation size, which is only known after checking the @@ -315,8 +326,9 @@ uint8_t *Off = Buf + OutputSectionOff + Rel.VirtualAddress; - auto *Sym = - dyn_cast_or_null(File->getSymbol(Rel.SymbolTableIndex)); + // Use the potentially remapped Symbol instead of the one that the + // relocation points to. + auto *Sym = dyn_cast_or_null(Symbols[Idx++]); if (!Sym) { if (isCodeView() || isDWARF()) continue; @@ -408,11 +420,15 @@ // fixed by the loader if load-time relocation is needed. // Only called when base relocation is enabled. void SectionChunk::getBaserels(std::vector *Res) { + size_t Counter = 0; for (const coff_relocation &Rel : Relocs) { uint8_t Ty = getBaserelType(Rel); + size_t Idx = Counter++; if (Ty == IMAGE_REL_BASED_ABSOLUTE) continue; - Symbol *Target = File->getSymbol(Rel.SymbolTableIndex); + // Use the potentially remapped Symbol instead of the one that the + // relocation points to. + Symbol *Target = Symbols[Idx]; if (!Target || isa(Target)) continue; Res->emplace_back(RVA + Rel.VirtualAddress, Ty); @@ -618,6 +634,28 @@ applyArm64Ldr(Buf + OutputSectionOff + 4, Off); } +// A Thumb2, PIC range extension thunk. A non-PIC one would be 2 bytes +// shorter but would require a base relocation instead. +const uint8_t RangeExtensionThunkARMData[] = { + 0x40, 0xf2, 0x00, 0x0c, // P: movw ip,:lower16:S - (P + (L1-P) + 4) + 0xc0, 0xf2, 0x00, 0x0c, // movt ip,:upper16:S - (P + (L1-P) + 4) + 0xfc, 0x44, // L1: add ip, pc + 0x60, 0x47, // bx ip +}; + +size_t RangeExtensionThunkARM::getSize() const { + return sizeof(RangeExtensionThunkARMData); +} + +void RangeExtensionThunkARM::writeTo(uint8_t *Buf) const { + uint64_t Offset = Target->getRVA() - RVA - 12; + // The target address needs to have the Thumb bit set. + Offset |= 1; + memcpy(Buf + OutputSectionOff, RangeExtensionThunkARMData, + sizeof(RangeExtensionThunkARMData)); + applyMOV32T(Buf + OutputSectionOff, uint32_t(Offset)); +} + void LocalImportChunk::getBaserels(std::vector *Res) { Res->emplace_back(getRVA()); } Index: COFF/ICF.cpp =================================================================== --- COFF/ICF.cpp +++ COFF/ICF.cpp @@ -147,6 +147,9 @@ R1.VirtualAddress != R2.VirtualAddress) { return false; } + // This doesn't use the potentially remapped Symbol, but at this point, + // we shouldn't have done any remapping yet (and using the original + // Symbol is probably better in any case). Symbol *B1 = A->File->getSymbol(R1.SymbolTableIndex); Symbol *B2 = B->File->getSymbol(R2.SymbolTableIndex); if (B1 == B2) @@ -172,6 +175,9 @@ bool ICF::equalsVariable(const SectionChunk *A, const SectionChunk *B) { // Compare relocations. auto Eq = [&](const coff_relocation &R1, const coff_relocation &R2) { + // This doesn't use the potentially remapped Symbol, but at this point, + // we shouldn't have done any remapping yet (and using the original + // Symbol is probably better in any case). Symbol *B1 = A->File->getSymbol(R1.SymbolTableIndex); Symbol *B2 = B->File->getSymbol(R2.SymbolTableIndex); if (B1 == B2) Index: COFF/PDB.cpp =================================================================== --- COFF/PDB.cpp +++ COFF/PDB.cpp @@ -772,6 +772,7 @@ uint8_t *Buffer = Alloc.Allocate(DebugChunk->getSize()); assert(DebugChunk->OutputSectionOff == 0 && "debug sections should not be in output sections"); + DebugChunk->finalizeContents(); DebugChunk->writeTo(Buffer); return consumeDebugMagic(makeArrayRef(Buffer, DebugChunk->getSize()), ".debug$S"); Index: COFF/Writer.h =================================================================== --- COFF/Writer.h +++ COFF/Writer.h @@ -36,11 +36,13 @@ void addChunk(Chunk *C); void merge(OutputSection *Other); ArrayRef getChunks() { return Chunks; } + void clear() { Chunks.clear(); } void addPermissions(uint32_t C); void setPermissions(uint32_t C); uint64_t getRVA() { return Header.VirtualAddress; } uint64_t getFileOff() { return Header.PointerToRawData; } void writeHeaderTo(uint8_t *Buf); + bool createThunks(size_t &ThunkCounter); // Returns the size of this section in an executable memory image. // This may be smaller than the raw size (the raw size is multiple Index: COFF/Writer.cpp =================================================================== --- COFF/Writer.cpp +++ COFF/Writer.cpp @@ -154,6 +154,7 @@ void createExportTable(); void mergeSections(); void assignAddresses(); + void finalizeAddresses(); void removeEmptySections(); void createSymbolAndStringTable(); void openFile(StringRef OutputPath); @@ -331,6 +332,114 @@ return None; } +// Check whether the target address S is in range from a relocation +// of type RelType at address P. +static bool isInRange(uint16_t RelType, uint64_t S, uint64_t P) { + int64_t Diff = S - P - 4; + assert(Config->Machine == ARMNT); + switch (RelType) { + case IMAGE_REL_ARM_BRANCH20T: + return isInt<21>(Diff); + case IMAGE_REL_ARM_BRANCH24T: + case IMAGE_REL_ARM_BLX23T: + return isInt<25>(Diff); + default: + return true; + } +} + +// Return an existing thunk which is in range, or create a new one. +static std::pair +getThunk(std::map> &ExistingThunks, + Defined *Target, uint64_t P, uint16_t Type, size_t &ThunkCounter, + uint32_t EstimatedThunkRVA) { + std::vector &TargetThunks = ExistingThunks[Target->getRVA()]; + // TODO: Since we create thunks linearly in the same order as we iterate + // over the section, it should be enough to just check the last one of + // the existing thunks? + for (Defined *Sym : TargetThunks) { + if (isInRange(Type, Sym->getRVA(), P)) + return std::pair(Sym, false); + } + Chunk *C = make(Target); + C->setRVA(EstimatedThunkRVA); // Estimate of where it will be located. + Defined *D = dyn_cast_or_null(Symtab->addSynthetic( + Saver.save("__thunk_" + Target->getName() + "_" + Twine(ThunkCounter++)), + C)); + if (!D) + fatal("Thunk collision with other symbol?"); + TargetThunks.push_back(D); + return std::pair(D, true); +} + +bool OutputSection::createThunks(size_t &ThunkCounter) { + std::map> ExistingThunks; + bool Changed = false; + size_t ThunksSize = 0; + for (size_t I = 0; I != Chunks.size(); ++I) { + SectionChunk *SC = dyn_cast_or_null(Chunks[I]); + if (!SC) + continue; + size_t SymbolIdx = 0; + size_t ThunkInsertionSpot = I + 1; + // Try to get a good enough estimate of where new thunks will be placed. + // Offset this by the size of the new thunks added so far, to make the + // estimate slightly better. + size_t ThunkInsertionRVA = SC->getRVA() + SC->getSize() + ThunksSize; + for (const coff_relocation &Rel : SC->Relocs) { + Defined *Sym = dyn_cast_or_null(SC->Symbols[SymbolIdx]); + if (!Sym) { + SymbolIdx++; + continue; + } + // The estimate of the source address P should be pretty accurate, + // but we don't know whether the target Symbol address should be + // offset or not, giving us uncertainty once we have added one thunk. + uint64_t S = Sym->getRVA(); + uint64_t P = SC->getRVA() + Rel.VirtualAddress + ThunksSize; + if (!isInRange(Rel.Type, S, P)) { + Defined *Thunk; + bool WasNew; + std::tie(Thunk, WasNew) = getThunk(ExistingThunks, Sym, P, Rel.Type, + ThunkCounter, ThunkInsertionRVA); + if (WasNew && Thunk) { + // TODO: .insert() in a std::vector will move all later elements + // forward - is this a concern? + Chunks.insert(Chunks.begin() + ThunkInsertionSpot, Thunk->getChunk()); + ThunkInsertionSpot++; + ThunksSize += Thunk->getChunk()->getSize(); + ThunkInsertionRVA += Thunk->getChunk()->getSize(); + } + SC->Symbols[SymbolIdx] = Thunk; + Changed = true; + } + SymbolIdx++; + } + } + return Changed; +} + +// Assign addresses and add thunks if necessary. +void Writer::finalizeAddresses() { + int ThunkPass = 0; + size_t ThunkCounter = 0; + bool Changed; + do { + if (ThunkPass >= 10) + fatal("Adding thunks hasn't converged after " + Twine(ThunkPass) + + " passes"); + assignAddresses(); + // Only ARMNT requires range extension thunks at the moment. + if (Config->Machine != ARMNT) + break; + Changed = false; + for (OutputSection *Sec : OutputSections) + Changed |= Sec->createThunks(ThunkCounter); + ThunkPass++; + // Iterate until no new thunks are needed. + } while (Changed); +} + // The main function of the writer. void Writer::run() { ScopedTimer T1(CodeLayoutTimer); @@ -345,7 +454,7 @@ createImportTables(); createExportTable(); mergeSections(); - assignAddresses(); + finalizeAddresses(); removeEmptySections(); setSectionPermissions(); createSymbolAndStringTable(); @@ -1319,6 +1428,7 @@ void Writer::addBaserels() { if (!Config->Relocatable) return; + RelocSec->clear(); std::vector V; for (OutputSection *Sec : OutputSections) { if (Sec->Header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE) Index: test/COFF/arm-thumb-branch-error.s =================================================================== --- test/COFF/arm-thumb-branch-error.s +++ /dev/null @@ -1,10 +0,0 @@ -// RUN: llvm-mc -filetype=obj -triple=thumbv7a-windows-gnu %s -o %t -// RUN: llvm-mc -filetype=obj -triple=thumbv7a-windows-gnu %S/Inputs/far-arm-thumb-abs.s -o %tfar -// RUN: not lld-link -entry:_start -subsystem:console %t %tfar -out:%t2 2>&1 | FileCheck %s -// REQUIRES: arm - .syntax unified - .globl _start -_start: - bl too_far1 - -// CHECK: relocation out of range Index: test/COFF/arm-thumb-branch-thunk.s =================================================================== --- /dev/null +++ test/COFF/arm-thumb-branch-thunk.s @@ -0,0 +1,17 @@ +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-windows-gnu %s -o %t +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-windows-gnu %S/Inputs/far-arm-thumb-abs.s -o %tfar +// RUN: lld-link -entry:_start -subsystem:console %t %tfar -out:%t.exe +// RUN: llvm-objdump -d %t.exe | FileCheck %s +// REQUIRES: arm + .syntax unified + .globl _start +_start: + bl too_far1 + +// CHECK: Disassembly of section .text: +// CHECK: .text: +// CHECK: 401000: 00 f0 00 f8 bl #0 +// CHECK: 401004: 4f f6 f5 7c movw r12, #65525 +// CHECK: 401008: c0 f2 ff 0c movt r12, #255 +// CHECK: 40100c: fc 44 add r12, pc +// CHECK: 40100e: 60 47 bx r12 Index: test/COFF/arm-thumb-branch20-error.s =================================================================== --- test/COFF/arm-thumb-branch20-error.s +++ /dev/null @@ -1,10 +0,0 @@ -// REQUIRES: arm -// RUN: llvm-mc -filetype=obj -triple=thumbv7a-windows-gnu %s -o %t.obj -// RUN: llvm-mc -filetype=obj -triple=thumbv7a-windows-gnu %S/Inputs/far-arm-thumb-abs20.s -o %t.far.obj -// RUN: not lld-link -entry:_start -subsystem:console %t.obj %t.far.obj -out:%t.exe 2>&1 | FileCheck %s - .syntax unified - .globl _start -_start: - bne too_far20 - -// CHECK: relocation out of range Index: test/COFF/arm-thumb-branch20-thunk.s =================================================================== --- /dev/null +++ test/COFF/arm-thumb-branch20-thunk.s @@ -0,0 +1,17 @@ +// REQUIRES: arm +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-windows-gnu %s -o %t.obj +// RUN: llvm-mc -filetype=obj -triple=thumbv7a-windows-gnu %S/Inputs/far-arm-thumb-abs20.s -o %t.far.obj +// RUN: lld-link -entry:_start -subsystem:console %t.obj %t.far.obj -out:%t.exe +// RUN: llvm-objdump -d %t.exe | FileCheck %s + .syntax unified + .globl _start +_start: + bne too_far20 + +// CHECK: Disassembly of section .text: +// CHECK: .text: +// CHECK: 401000: 40 f0 00 80 bne.w #0 <.text+0x4> +// CHECK: 401004: 4f f6 f5 7c movw r12, #65525 +// CHECK: 401008: c0 f2 0f 0c movt r12, #15 +// CHECK: 40100c: fc 44 add r12, pc +// CHECK: 40100e: 60 47 bx r12