diff --git a/lld/ELF/Arch/X86_64.cpp b/lld/ELF/Arch/X86_64.cpp --- a/lld/ELF/Arch/X86_64.cpp +++ b/lld/ELF/Arch/X86_64.cpp @@ -7,12 +7,14 @@ //===----------------------------------------------------------------------===// #include "OutputSections.h" +#include "Relocations.h" #include "Symbols.h" #include "SyntheticSections.h" #include "Target.h" #include "lld/Common/ErrorHandler.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/Support/Endian.h" +#include "llvm/Support/MathExtras.h" using namespace llvm; using namespace llvm::object; @@ -47,6 +49,7 @@ uint8_t stOther) const override; bool deleteFallThruJmpInsn(InputSection &is, InputFile *file, InputSection *nextIS) const override; + bool relaxOnce(int pass) const override; }; } // namespace @@ -305,6 +308,43 @@ return true; } +bool X86_64::relaxOnce(int pass) const { + uint64_t minVA = UINT64_MAX, maxVA = 0; + for (OutputSection *osec : outputSections) { + minVA = std::min(minVA, osec->addr); + maxVA = std::max(maxVA, osec->addr + osec->size); + } + // If the max VA difference is under 2^31, GOT-generating relocations with a 32-bit range cannot overflow. + if (isUInt<31>(maxVA - minVA)) + return false; + + SmallVector storage; + bool changed = false; + for (OutputSection *osec : outputSections) { + if (!(osec->flags & SHF_EXECINSTR)) + continue; + for (InputSection *sec : getInputSections(*osec, storage)) { + for (Relocation &rel : sec->relocs()) { + if (rel.expr != R_RELAX_GOT_PC) + continue; + + uint64_t v = sec->getRelocTargetVA( + sec->file, rel.type, rel.addend, + sec->getOutputSection()->addr + rel.offset, *rel.sym, rel.expr); + if (isInt<32>(v)) + continue; + if (rel.sym->auxIdx == 0) { + rel.sym->allocateAux(); + addGotEntry(*rel.sym); + changed = true; + } + rel.expr = R_GOT_PC; + } + } + } + return changed; +} + RelExpr X86_64::getRelExpr(RelType type, const Symbol &s, const uint8_t *loc) const { switch (type) { @@ -912,7 +952,8 @@ } static void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) { - checkInt(loc, val, 32, rel); + assert(isInt<32>(val) && + "GOTPCRELX should not have been relaxed if it overflows"); const uint8_t op = loc[-2]; const uint8_t modRm = loc[-1]; diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h --- a/lld/ELF/Relocations.h +++ b/lld/ELF/Relocations.h @@ -137,6 +137,7 @@ template void scanRelocations(); void reportUndefinedSymbols(); void postScanRelocations(); +void addGotEntry(Symbol &sym); void hexagonTLSSymbolUpdate(ArrayRef outputSections); bool hexagonNeedsTLSSymbol(ArrayRef outputSections); diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -904,7 +904,7 @@ sym, 0, R_ABS}); } -static void addGotEntry(Symbol &sym) { +void elf::addGotEntry(Symbol &sym) { in.got->addEntry(sym); uint64_t off = sym.getGotOffset(); @@ -1055,6 +1055,10 @@ } else if (!isAbsoluteValue(sym)) { expr = target->adjustGotPcExpr(type, addend, sec->content().data() + offset); + // If the target adjusted the expression to R_RELAX_GOT_PC, we may end up + // needing the GOT if we can't relax everything. + if (expr == R_RELAX_GOT_PC) + in.got->hasGotOffRel.store(true, std::memory_order_relaxed); } } diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -1672,6 +1672,7 @@ changed |= a32p.createFixes(); } + finalizeSynthetic(in.got.get()); if (in.mipsGot) in.mipsGot->updateAllocSize(); diff --git a/lld/test/ELF/x86-64-gotpc-err.s b/lld/test/ELF/x86-64-gotpc-err.s deleted file mode 100644 --- a/lld/test/ELF/x86-64-gotpc-err.s +++ /dev/null @@ -1,26 +0,0 @@ -# REQUIRES: x86 -# RUN: split-file %s %t -# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/a.s -o %t/a.o -# RUN: not ld.lld -T %t/lds %t/a.o -o /dev/null 2>&1 | FileCheck %s - -## Test diagnostics for GOTPCRELX overflows. In addition, test that there is no -## `>>> defined in` for linker synthesized __stop_* symbols (there is no -## associated file or linker script line number). - -# CHECK: error: {{.*}}:(.text+0x2): relocation R_X86_64_GOTPCRELX out of range: 2147483655 is not in [-2147483648, 2147483647]; references '__stop_data' -# CHECK-NEXT: error: {{.*}}:(.text+0x9): relocation R_X86_64_REX_GOTPCRELX out of range: 2147483648 is not in [-2147483648, 2147483647]; references '__stop_data' -# CHECK-NOT: error: - -#--- a.s - movl __stop_data@GOTPCREL(%rip), %eax # out of range - movq __stop_data@GOTPCREL(%rip), %rax # out of range - movq __stop_data@GOTPCREL(%rip), %rax # in range - -.section data,"aw",@progbits -.space 13 - -#--- lds -SECTIONS { - .text 0x200000 : { *(.text) } - data 0x80200000 : { *(data) } -} diff --git a/lld/test/ELF/x86-64-gotpc-relax-too-far.s b/lld/test/ELF/x86-64-gotpc-relax-too-far.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/x86-64-gotpc-relax-too-far.s @@ -0,0 +1,55 @@ +# REQUIRES: x86 +# RUN: split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/a.s -o %t/a.o +# RUN: ld.lld -T %t/lds1 %t/a.o -o %t/bin +# RUN: llvm-objdump --no-print-imm-hex -d %t/bin | FileCheck --check-prefix=DISASM %s +# RUN: llvm-readelf -S %t/bin | FileCheck --check-prefixes=GOT %s +# RUN: ld.lld -T %t/lds2 %t/a.o -o %t/bin2 +# RUN: llvm-readelf -S %t/bin2 | FileCheck --check-prefixes=UNNECESSARY-GOT %s + +# DISASM: <_foo>: +# DISASM-NEXT: movl 2097146(%rip), %eax +# DISASM: <_start>: +# DISASM-NEXT: movl 1048578(%rip), %eax +# DISASM-NEXT: movq 1048571(%rip), %rax +# DISASM-NEXT: leaq 2147483641(%rip), %rax +# DISASM-NEXT: leal 2147483635(%rip), %eax + +# In our implementation, .got is retained even if all GOT-generating relocations are optimized. +# Make sure .got still exists with the right size. +# UNNECESSARY-GOT: .got PROGBITS 0000000000300000 101020 000000 00 WA 0 0 8 +# GOT: .got PROGBITS 0000000000300000 102000 000010 00 WA 0 0 8 + +#--- a.s +.section .text.foo,"ax" +.globl _foo +.type _foo, @function +_foo: + movl __start_data@GOTPCREL(%rip), %eax # out of range + +.section .text,"ax" +.globl _start +.type _start, @function +_start: + movl __stop_data@GOTPCREL(%rip), %eax # out of range + movq __stop_data@GOTPCREL(%rip), %rax # out of range + movq __stop_data@GOTPCREL(%rip), %rax # in range + movl __stop_data@GOTPCREL(%rip), %eax # in range + +.section data,"aw",@progbits +.space 13 + +#--- lds1 +SECTIONS { + .text.foo 0x100000 : { *(.text.foo) } + .text 0x200000 : { *(.text) } + .got 0x300000 : { *(.got) } + data 0x80200000 : { *(data) } +} +#--- lds2 +SECTIONS { + .text.foo 0x100000 : { *(.text.foo) } + .text 0x200000 : { *(.text) } + .got 0x300000 : { *(.got) } + data 0x400000 : { *(data) } +} diff --git a/lld/test/ELF/x86-64-gotpc-relax.s b/lld/test/ELF/x86-64-gotpc-relax.s --- a/lld/test/ELF/x86-64-gotpc-relax.s +++ b/lld/test/ELF/x86-64-gotpc-relax.s @@ -13,18 +13,18 @@ # RUN: ld.lld --no-relax %t.o -o %t2 # RUN: llvm-objdump --no-print-imm-hex -d %t2 | FileCheck --check-prefix=NORELAX %s -## .got is removed as all GOT-generating relocations are optimized. +## In our implementation, .got is retained even if all GOT-generating relocations are optimized. # CHECK: Name Type Address Off Size ES Flg Lk Inf Al -# CHECK: .iplt PROGBITS 0000000000201210 000210 000010 00 AX 0 0 16 -# CHECK-NEXT: .got.plt PROGBITS 0000000000202220 000220 000008 00 WA 0 0 8 +# CHECK: .iplt PROGBITS 0000000000201280 000280 000010 00 AX 0 0 16 +# CHECK-NEXT: .got PROGBITS 0000000000202290 000290 000000 00 WA 0 0 8 ## There is one R_X86_64_IRELATIVE relocations. # RELOC-LABEL: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entry: # CHECK: Offset Info Type Symbol's Value Symbol's Name + Addend -# CHECK: 0000000000202220 0000000000000025 R_X86_64_IRELATIVE 201172 +# CHECK: 0000000000203290 0000000000000025 R_X86_64_IRELATIVE 2011e2 # CHECK-LABEL: Hex dump of section '.got.plt': -# NOAPPLY-NEXT: 0x00202220 00000000 00000000 -# APPLY-NEXT: 0x00202220 72112000 00000000 +# NOAPPLY-NEXT: 0x00203290 00000000 00000000 +# APPLY-NEXT: 0x00203290 e2112000 00000000 # 0x201173 + 7 - 10 = 0x201170 # 0x20117a + 7 - 17 = 0x201170 @@ -33,40 +33,40 @@ # DISASM: Disassembly of section .text: # DISASM-EMPTY: # DISASM-NEXT: : -# DISASM-NEXT: 201170: 90 nop +# DISASM-NEXT: 2011e0: 90 nop # DISASM: : -# DISASM-NEXT: 201171: 90 nop +# DISASM-NEXT: 2011e1: 90 nop # DISASM: : -# DISASM-NEXT: 201172: c3 retq +# DISASM-NEXT: 2011e2: c3 retq # DISASM: <_start>: # DISASM-NEXT: leaq -10(%rip), %rax # DISASM-NEXT: leaq -17(%rip), %rax # DISASM-NEXT: leaq -23(%rip), %rax # DISASM-NEXT: leaq -30(%rip), %rax -# DISASM-NEXT: movq 4234(%rip), %rax -# DISASM-NEXT: movq 4227(%rip), %rax +# DISASM-NEXT: movq 8330(%rip), %rax +# DISASM-NEXT: movq 8323(%rip), %rax # DISASM-NEXT: leaq -52(%rip), %rax # DISASM-NEXT: leaq -59(%rip), %rax # DISASM-NEXT: leaq -65(%rip), %rax # DISASM-NEXT: leaq -72(%rip), %rax -# DISASM-NEXT: movq 4192(%rip), %rax -# DISASM-NEXT: movq 4185(%rip), %rax -# DISASM-NEXT: callq 0x201170 -# DISASM-NEXT: callq 0x201170 -# DISASM-NEXT: callq 0x201171 -# DISASM-NEXT: callq 0x201171 -# DISASM-NEXT: callq *4155(%rip) -# DISASM-NEXT: callq *4149(%rip) -# DISASM-NEXT: jmp 0x201170 +# DISASM-NEXT: movq 8288(%rip), %rax +# DISASM-NEXT: movq 8281(%rip), %rax +# DISASM-NEXT: callq 0x2011e0 +# DISASM-NEXT: callq 0x2011e0 +# DISASM-NEXT: callq 0x2011e1 +# DISASM-NEXT: callq 0x2011e1 +# DISASM-NEXT: callq *8251(%rip) +# DISASM-NEXT: callq *8245(%rip) +# DISASM-NEXT: jmp 0x2011e0 # DISASM-NEXT: nop -# DISASM-NEXT: jmp 0x201170 +# DISASM-NEXT: jmp 0x2011e0 # DISASM-NEXT: nop -# DISASM-NEXT: jmp 0x201171 +# DISASM-NEXT: jmp 0x2011e1 # DISASM-NEXT: nop -# DISASM-NEXT: jmp 0x201171 +# DISASM-NEXT: jmp 0x2011e1 # DISASM-NEXT: nop -# DISASM-NEXT: jmpq *4119(%rip) -# DISASM-NEXT: jmpq *4113(%rip) +# DISASM-NEXT: jmpq *8215(%rip) +# DISASM-NEXT: jmpq *8209(%rip) # NORELAX-LABEL: <_start>: # NORELAX-COUNT-12: movq