[lld-macho] Support static linking of thread-locals

Note: What ELF refers to as "TLS", Mach-O seems to refer to as "TLV", i.e.
thread-local variables.

This diff implements support for TLV relocations that reference defined
symbols. On x86_64, TLV relocations are always used with movq opcodes, so for
defined TLVs, we don't need to create a synthetic section to store the
addresses of the symbols -- we can just convert the `movq` to a `leaq`.

One notable quirk of Mach-O's TLVs is that absolute-address relocations
inside TLV-defining sections behave differently -- their addresses are
no longer absolute, but relative to the start of the target section.
(AFAICT, RIP-relative relocations are not allowed in these sections.)

Reviewed By: #lld-macho, compnerd, smeenai

Differential Revision: https://reviews.llvm.org/D85080
This commit is contained in:
Jez Ng 2020-08-07 11:04:52 -07:00
parent 4e43f18048
commit ca85e37338
9 changed files with 146 additions and 12 deletions

View File

@ -36,7 +36,8 @@ struct X86_64 : TargetInfo {
void prepareSymbolRelocation(lld::macho::Symbol &, const InputSection *,
const Reloc &) override;
uint64_t getSymbolVA(const lld::macho::Symbol &, uint8_t type) const override;
uint64_t resolveSymbolVA(uint8_t *buf, const lld::macho::Symbol &,
uint8_t type) const override;
};
} // namespace
@ -72,6 +73,11 @@ uint64_t X86_64::getImplicitAddend(MemoryBufferRef mb, const section_64 &sec,
const relocation_info &rel) const {
auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
const uint8_t *loc = buf + sec.offset + rel.r_address;
if (isThreadLocalVariables(sec.flags) && rel.r_type != X86_64_RELOC_UNSIGNED)
error("relocations in thread-local variable sections must be "
"X86_64_RELOC_UNSIGNED");
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
@ -84,6 +90,7 @@ uint64_t X86_64::getImplicitAddend(MemoryBufferRef mb, const section_64 &sec,
case X86_64_RELOC_SIGNED_4:
case X86_64_RELOC_GOT_LOAD:
case X86_64_RELOC_GOT:
case X86_64_RELOC_TLV:
if (!rel.r_pcrel)
fatal(getErrorLocation(mb, sec, rel) + ": relocations of type " +
std::to_string(rel.r_type) + " must be pcrel");
@ -123,6 +130,7 @@ void X86_64::relocateOne(uint8_t *loc, const Reloc &r, uint64_t val) const {
case X86_64_RELOC_SIGNED_4:
case X86_64_RELOC_GOT_LOAD:
case X86_64_RELOC_GOT:
case X86_64_RELOC_TLV:
// These types are only used for pc-relative relocations, so offset by 4
// since the RIP has advanced by 4 at this point. This is only valid when
// r_length = 2, which is enforced by validateLength().
@ -239,8 +247,13 @@ void X86_64::prepareSymbolRelocation(lld::macho::Symbol &sym,
case X86_64_RELOC_SIGNED_2:
case X86_64_RELOC_SIGNED_4:
break;
case X86_64_RELOC_SUBTRACTOR:
case X86_64_RELOC_TLV:
if (auto *dysym = dyn_cast<DylibSymbol>(&sym))
error("relocations to thread-local dylib symbols not yet implemented");
else
assert(isa<Defined>(&sym));
break;
case X86_64_RELOC_SUBTRACTOR:
fatal("TODO: handle relocation type " + std::to_string(r.type));
break;
default:
@ -248,8 +261,8 @@ void X86_64::prepareSymbolRelocation(lld::macho::Symbol &sym,
}
}
uint64_t X86_64::getSymbolVA(const lld::macho::Symbol &sym,
uint8_t type) const {
uint64_t X86_64::resolveSymbolVA(uint8_t *buf, const lld::macho::Symbol &sym,
uint8_t type) const {
switch (type) {
case X86_64_RELOC_GOT_LOAD:
case X86_64_RELOC_GOT:
@ -264,8 +277,18 @@ uint64_t X86_64::getSymbolVA(const lld::macho::Symbol &sym,
case X86_64_RELOC_SIGNED_2:
case X86_64_RELOC_SIGNED_4:
return sym.getVA();
case X86_64_RELOC_TLV: {
if (auto *dysym = dyn_cast<DylibSymbol>(&sym))
error("relocations to thread-local dylib symbols not yet implemented");
// Convert the movq to a leaq.
assert(isa<Defined>(&sym));
if (buf[-2] != 0x8b)
error("X86_64_RELOC_TLV must be used with movq instructions");
buf[-2] = 0x8d;
return sym.getVA();
}
case X86_64_RELOC_SUBTRACTOR:
case X86_64_RELOC_TLV:
fatal("TODO: handle relocation type " + std::to_string(type));
default:
llvm_unreachable("Unexpected relocation type");

View File

@ -35,10 +35,19 @@ void InputSection::writeTo(uint8_t *buf) {
for (Reloc &r : relocs) {
uint64_t va = 0;
if (auto *s = r.target.dyn_cast<Symbol *>())
va = target->getSymbolVA(*s, r.type);
else if (auto *isec = r.target.dyn_cast<InputSection *>())
if (auto *s = r.target.dyn_cast<Symbol *>()) {
va = target->resolveSymbolVA(buf + r.offset, *s, r.type);
if (isThreadLocalVariables(flags)) {
// References from thread-local variable sections are treated as
// offsets relative to the start of the target section, instead of as
// absolute addresses.
if (auto *defined = dyn_cast<Defined>(s))
va -= defined->isec->parent->addr;
}
} else if (auto *isec = r.target.dyn_cast<InputSection *>()) {
va = isec->getVA();
}
uint64_t val = va + r.addend;
if (r.pcrel)

View File

@ -38,6 +38,10 @@ struct Reloc {
inline bool isZeroFill(uint8_t flags) {
return llvm::MachO::isVirtualSection(flags & llvm::MachO::SECTION_TYPE);
}
inline bool isThreadLocalVariables(uint8_t flags) {
return (flags & llvm::MachO::SECTION_TYPE) ==
llvm::MachO::S_THREAD_LOCAL_VARIABLES;
}
class InputSection {

View File

@ -59,9 +59,19 @@ void MachHeaderSection::writeTo(uint8_t *buf) const {
hdr->ncmds = loadCommands.size();
hdr->sizeofcmds = sizeOfCmds;
hdr->flags = MachO::MH_NOUNDEFS | MachO::MH_DYLDLINK | MachO::MH_TWOLEVEL;
if (config->outputType == MachO::MH_DYLIB && !config->hasReexports)
hdr->flags |= MachO::MH_NO_REEXPORTED_DYLIBS;
for (OutputSegment *seg : outputSegments) {
for (OutputSection *osec : seg->getSections()) {
if (isThreadLocalVariables(osec->flags)) {
hdr->flags |= MachO::MH_HAS_TLV_DESCRIPTORS;
break;
}
}
}
uint8_t *p = reinterpret_cast<uint8_t *>(hdr + 1);
for (LoadCommand *lc : loadCommands) {
lc->writeTo(p);

View File

@ -51,11 +51,13 @@ public:
// Symbols may be referenced via either the GOT or the stubs section,
// depending on the relocation type. prepareSymbolRelocation() will set up the
// GOT/stubs entries, and getSymbolVA() will return the addresses of those
// entries.
// GOT/stubs entries, and resolveSymbolVA() will return the addresses of those
// entries. resolveSymbolVA() may also relax the target instructions to save
// on a level of address indirection.
virtual void prepareSymbolRelocation(Symbol &, const InputSection *,
const Reloc &) = 0;
virtual uint64_t getSymbolVA(const Symbol &, uint8_t type) const = 0;
virtual uint64_t resolveSymbolVA(uint8_t *buf, const Symbol &,
uint8_t type) const = 0;
uint32_t cpuType;
uint32_t cpuSubtype;

View File

@ -18,7 +18,7 @@ current-version: 0001.001.1
parent-umbrella: System
exports:
- archs: [ 'x86_64' ]
symbols: [ dyld_stub_binder ]
symbols: [ dyld_stub_binder, __tlv_bootstrap ]
--- !tapi-tbd-v3
archs: [ x86_64 ]
uuids: [ 'x86_64: 00000000-0000-0000-0000-000000000002' ]

View File

@ -0,0 +1,15 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
# RUN: not lld -flavor darwinnew -o /dev/null %t.o 2>&1 | FileCheck %s
# CHECK: error: relocations in thread-local variable sections must be X86_64_RELOC_UNSIGNED
.text
.globl _main
_main:
ret
.section __DATA,__thread_vars,thread_local_variables
.globl _foo, _bar
_foo:
movq _bar@GOTPCREL(%rip), %rax

View File

@ -0,0 +1,14 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
# RUN: not lld -flavor darwinnew -o /dev/null %t.o 2>&1 | FileCheck %s
# CHECK: error: X86_64_RELOC_TLV must be used with movq instructions
.text
.globl _main
_main:
leaq _foo@TLVP(%rip), %rax
ret
.section __DATA,__thread_vars,thread_local_variables
_foo:

57
lld/test/MachO/tlv.s Normal file
View File

@ -0,0 +1,57 @@
# REQUIRES: x86
# RUN: mkdir -p %t
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/test.o
# RUN: lld -flavor darwinnew -L%S/Inputs/MacOSX.sdk/usr/lib -lSystem -o %t/test %t/test.o
# RUN: llvm-readobj --file-headers %t/test | FileCheck %s --check-prefix=HEADER
# RUN: llvm-objdump -D %t/test | FileCheck %s
# HEADER: MH_HAS_TLV_DESCRIPTORS
# CHECK: Disassembly of section __TEXT,__text:
# CHECK-EMPTY:
# CHECK-NEXT: <_main>:
# CHECK-NEXT: leaq {{.*}}(%rip), %rax # {{.*}} <_foo>
# CHECK-NEXT: leaq {{.*}}(%rip), %rax # {{.*}} <_bar>
# CHECK-NEXT: retq
# CHECK-EMPTY:
# CHECK-NEXT: Disassembly of section __DATA,__thread_data:
# CHECK-EMPTY:
# CHECK-NEXT: <__thread_data>:
# CHECK-NEXT: ef
# CHECK-NEXT: be ad de be ba
# CHECK-NEXT: fe ca
# CHECK-EMPTY:
# CHECK-NEXT: Disassembly of section __DATA,__thread_vars:
# CHECK-EMPTY:
# CHECK-NEXT: <_foo>:
# CHECK-NEXT: ...
# CHECK-EMPTY:
# CHECK-NEXT: <_bar>:
# CHECK-NEXT: ...
# CHECK-NEXT: 04 00
# CHECK-NEXT: 00 00
# CHECK-NEXT: 00 00
# CHECK-NEXT: 00 00
.globl _main
_main:
mov _foo@TLVP(%rip), %rax
mov _bar@TLVP(%rip), %rax
ret
.section __DATA,__thread_data,thread_local_regular
_foo$tlv$init:
.long 0xdeadbeef
_bar$tlv$init:
.long 0xcafebabe
.section __DATA,__thread_vars,thread_local_variables
.globl _foo, _bar
_foo:
.quad __tlv_bootstrap
.quad 0
.quad _foo$tlv$init
_bar:
.quad __tlv_bootstrap
.quad 0
.quad _bar$tlv$init