diff --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp --- a/lld/MachO/Arch/X86_64.cpp +++ b/lld/MachO/Arch/X86_64.cpp @@ -27,7 +27,7 @@ uint64_t getImplicitAddend(MemoryBufferRef, const section_64 &, const relocation_info &) const override; - void relocateOne(uint8_t *loc, uint8_t type, uint64_t val) const override; + void relocateOne(uint8_t *loc, const Reloc &, uint64_t val) const override; void writeStub(uint8_t *buf, const DylibSymbol &) const override; void writeStubHelperHeader(uint8_t *buf) const override; @@ -48,12 +48,35 @@ .str(); } +static void validateLength(MemoryBufferRef mb, const section_64 &sec, + const relocation_info &rel, + const std::vector &validLengths) { + if (std::find(validLengths.begin(), validLengths.end(), rel.r_length) != + validLengths.end()) + return; + + std::string msg = getErrorLocation(mb, sec, rel) + ": relocations of type " + + std::to_string(rel.r_type) + " must have r_length of "; + bool first = true; + for (uint8_t length : validLengths) { + if (!first) + msg += " or "; + first = false; + msg += std::to_string(length); + } + fatal(msg); +} + uint64_t X86_64::getImplicitAddend(MemoryBufferRef mb, const section_64 &sec, const relocation_info &rel) const { auto *buf = reinterpret_cast(mb.getBufferStart()); const uint8_t *loc = buf + sec.offset + rel.r_address; switch (rel.r_type) { case X86_64_RELOC_BRANCH: + // XXX: ld64 also supports r_length = 0 here but I'm not sure when such a + // relocation will actually be generated. + validateLength(mb, sec, rel, {2}); + break; case X86_64_RELOC_SIGNED: case X86_64_RELOC_SIGNED_1: case X86_64_RELOC_SIGNED_2: @@ -62,20 +85,35 @@ if (!rel.r_pcrel) fatal(getErrorLocation(mb, sec, rel) + ": relocations of type " + std::to_string(rel.r_type) + " must be pcrel"); - return read32le(loc); + validateLength(mb, sec, rel, {2}); + break; case X86_64_RELOC_UNSIGNED: if (rel.r_pcrel) fatal(getErrorLocation(mb, sec, rel) + ": relocations of type " + std::to_string(rel.r_type) + " must not be pcrel"); - return read64le(loc); + validateLength(mb, sec, rel, {2, 3}); + break; default: error("TODO: Unhandled relocation type " + std::to_string(rel.r_type)); return 0; } + + switch (rel.r_length) { + case 0: + return *loc; + case 1: + return read16le(loc); + case 2: + return read32le(loc); + case 3: + return read64le(loc); + default: + llvm_unreachable("invalid r_length"); + } } -void X86_64::relocateOne(uint8_t *loc, uint8_t type, uint64_t val) const { - switch (type) { +void X86_64::relocateOne(uint8_t *loc, const Reloc &r, uint64_t val) const { + switch (r.type) { case X86_64_RELOC_BRANCH: case X86_64_RELOC_SIGNED: case X86_64_RELOC_SIGNED_1: @@ -83,16 +121,33 @@ case X86_64_RELOC_SIGNED_4: case X86_64_RELOC_GOT_LOAD: // These types are only used for pc-relative relocations, so offset by 4 - // since the RIP has advanced by 4 at this point. - write32le(loc, val - 4); + // since the RIP has advanced by 4 at this point. This is only valid when + // r_length = 0, which is enforced by validateLength(). + val -= 4; break; case X86_64_RELOC_UNSIGNED: - write64le(loc, val); break; default: llvm_unreachable( "getImplicitAddend should have flagged all unhandled relocation types"); } + + switch (r.length) { + case 0: + *loc = val; + break; + case 1: + write16le(loc, val); + break; + case 2: + write32le(loc, val); + break; + case 3: + write64le(loc, val); + break; + default: + llvm_unreachable("invalid r_length"); + } } // The following methods emit a number of assembly sequences with RIP-relative diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -178,6 +178,7 @@ Reloc r; r.type = rel.r_type; r.pcrel = rel.r_pcrel; + r.length = rel.r_length; uint64_t rawAddend = target->getImplicitAddend(mb, sec, rel); if (rel.r_extern) { diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h --- a/lld/MachO/InputSection.h +++ b/lld/MachO/InputSection.h @@ -25,6 +25,7 @@ struct Reloc { uint8_t type; bool pcrel; + uint8_t length; // The offset from the start of the subsection that this relocation belongs // to. uint32_t offset; diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp --- a/lld/MachO/InputSection.cpp +++ b/lld/MachO/InputSection.cpp @@ -46,6 +46,6 @@ uint64_t val = va + r.addend; if (r.pcrel) val -= getVA() + r.offset; - target->relocateOne(buf + r.offset, r.type, val); + target->relocateOne(buf + r.offset, r, val); } } diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h --- a/lld/MachO/Target.h +++ b/lld/MachO/Target.h @@ -39,7 +39,7 @@ virtual uint64_t getImplicitAddend(llvm::MemoryBufferRef, const llvm::MachO::section_64 &, const llvm::MachO::relocation_info &) const = 0; - virtual void relocateOne(uint8_t *loc, uint8_t type, uint64_t val) const = 0; + virtual void relocateOne(uint8_t *loc, const Reloc &, uint64_t val) const = 0; // Write code for lazy binding. See the comments on StubsSection for more // details. diff --git a/lld/test/MachO/invalid/invalid-relocation.yaml b/lld/test/MachO/invalid/invalid-relocation-length.yaml rename from lld/test/MachO/invalid/invalid-relocation.yaml rename to lld/test/MachO/invalid/invalid-relocation-length.yaml --- a/lld/test/MachO/invalid/invalid-relocation.yaml +++ b/lld/test/MachO/invalid/invalid-relocation-length.yaml @@ -1,8 +1,8 @@ # REQUIRES: x86 # RUN: yaml2obj %s -o %t.o -# RUN: not lld -flavor darwinnew -o %t %t.o 2>&1 | FileCheck %s -DFILE=%t.o +# RUN: not lld -flavor darwinnew -arch x86_64 -o %t %t.o 2>&1 | FileCheck %s -DFILE=%t.o # -# CHECK: error: invalid relocation at offset 1 of __TEXT,__text in [[FILE]]: relocations of type 0 must not be pcrel +# CHECK: error: invalid relocation at offset 1 of __TEXT,__text in [[FILE]]: relocations of type 0 must have r_length of 2 or 3 !mach-o FileHeader: @@ -43,8 +43,8 @@ relocations: - address: 0x00000001 symbolnum: 1 - pcrel: true - length: 3 + pcrel: false + length: 1 extern: true type: 0 scattered: false diff --git a/lld/test/MachO/invalid/invalid-relocation.yaml b/lld/test/MachO/invalid/invalid-relocation-pcrel.yaml rename from lld/test/MachO/invalid/invalid-relocation.yaml rename to lld/test/MachO/invalid/invalid-relocation-pcrel.yaml diff --git a/lld/test/MachO/x86-64-reloc-unsigned.s b/lld/test/MachO/x86-64-reloc-unsigned.s --- a/lld/test/MachO/x86-64-reloc-unsigned.s +++ b/lld/test/MachO/x86-64-reloc-unsigned.s @@ -5,7 +5,7 @@ # CHECK: Contents of section foo: # CHECK: 100001000 08100000 01000000 # CHECK: Contents of section bar: -# CHECK: 100001008 11211111 02000000 +# CHECK: 100001008 011000f0 11211111 02000000 .globl _main, _foo, _bar @@ -15,7 +15,14 @@ .section __DATA,bar _bar: -## The unsigned relocation should support 64-bit addends +## We create a .int symbol reference here -- with non-zero data immediately +## after -- to check that lld reads precisely 32 bits (and not more) of the +## implicit addend when handling unsigned relocations of r_length = 2. +## Note that __PAGEZERO occupies the lower 32 bits, so all symbols are above +## that. To get a final relocated address that fits within 32 bits, we need to +## subtract an offset here. +.int _foo - 0x0fffffff +## The unsigned relocation should support 64-bit addends too (r_length = 3). .quad _foo + 0x111111111 .text