[lld-macho] Fix TLV data initialization

We were mishandling the case where both `__tbss` and `__thread_data` sections were
present.

TLVP relocations should be encoded as offsets from the start of `__thread_data`,
even if the symbol is actually located in `__thread_bss`. Previously, we were
writing the offset from the start of the containing section, which doesn't
really make sense since there's no way `tlv_get_addr()` can know which section a
given `tlv$init` symbol is in at runtime.

In addition, this patch ensures that we place `__thread_data` immediately before
`__thread_bss`. This is what ld64 does, likely for performance reasons. Zerofill
sections must also be at the end of their segments; we were already doing this,
but now we ensure that `__thread_bss` occurs before `__bss`, so that it's always
possible to have it contiguous with `__thread_data`.

Fixes llvm.org/PR48657.

Reviewed By: #lld-macho, thakis

Differential Revision: https://reviews.llvm.org/D94329
This commit is contained in:
Jez Ng 2021-01-08 18:47:40 -05:00
parent 756dd70766
commit daaaed6bb8
6 changed files with 189 additions and 59 deletions

View File

@ -11,6 +11,7 @@
#include "OutputSegment.h"
#include "Symbols.h"
#include "Target.h"
#include "Writer.h"
#include "lld/Common/Memory.h"
#include "llvm/Support/Endian.h"
@ -45,11 +46,12 @@ void InputSection::writeTo(uint8_t *buf) {
target->resolveSymbolVA(buf + r.offset, *referentSym, r.type);
if (isThreadLocalVariables(flags)) {
// References from thread-local variable sections are treated
// as offsets relative to the start of the referent section,
// instead of as absolute addresses.
// References from thread-local variable sections are treated as offsets
// relative to the start of the thread-local data memory area, which
// is initialized via copying all the TLV data sections (which are all
// contiguous).
if (auto *defined = dyn_cast<Defined>(referentSym))
referentVA -= defined->isec->parent->addr;
referentVA -= firstTLVDataSection->addr;
}
} else if (auto *referentIsec = r.referent.dyn_cast<InputSection *>()) {
referentVA = referentIsec->getVA();

View File

@ -60,13 +60,22 @@ public:
std::vector<Reloc> relocs;
};
inline uint8_t sectionType(uint32_t flags) {
return flags & llvm::MachO::SECTION_TYPE;
}
inline bool isZeroFill(uint32_t flags) {
return llvm::MachO::isVirtualSection(flags & llvm::MachO::SECTION_TYPE);
return llvm::MachO::isVirtualSection(sectionType(flags));
}
inline bool isThreadLocalVariables(uint32_t flags) {
return (flags & llvm::MachO::SECTION_TYPE) ==
llvm::MachO::S_THREAD_LOCAL_VARIABLES;
return sectionType(flags) == llvm::MachO::S_THREAD_LOCAL_VARIABLES;
}
// These sections contain the data for initializing thread-local variables.
inline bool isThreadLocalData(uint32_t flags) {
return sectionType(flags) == llvm::MachO::S_THREAD_LOCAL_REGULAR ||
sectionType(flags) == llvm::MachO::S_THREAD_LOCAL_ZEROFILL;
}
inline bool isDebugSection(uint32_t flags) {

View File

@ -557,6 +557,24 @@ static int sectionOrder(OutputSection *osec) {
.Case(section_names::unwindInfo, std::numeric_limits<int>::max() - 1)
.Case(section_names::ehFrame, std::numeric_limits<int>::max())
.Default(0);
} else if (segname == segment_names::data) {
// For each thread spawned, dyld will initialize its TLVs by copying the
// address range from the start of the first thread-local data section to
// the end of the last one. We therefore arrange these sections contiguously
// to minimize the amount of memory used. Additionally, since zerofill
// sections must be at the end of their segments, and since TLV data
// sections can be zerofills, we end up putting all TLV data sections at the
// end of the segment.
switch (sectionType(osec->flags)) {
case S_THREAD_LOCAL_REGULAR:
return std::numeric_limits<int>::max() - 2;
case S_THREAD_LOCAL_ZEROFILL:
return std::numeric_limits<int>::max() - 1;
case S_ZEROFILL:
return std::numeric_limits<int>::max();
default:
return 0;
}
} else if (segname == segment_names::linkEdit) {
return StringSwitch<int>(osec->name)
.Case(section_names::rebase, -8)
@ -571,7 +589,7 @@ static int sectionOrder(OutputSection *osec) {
}
// ZeroFill sections must always be the at the end of their segments,
// otherwise subsequent sections may get overwritten with zeroes at runtime.
if (isZeroFill(osec->flags))
if (sectionType(osec->flags) == S_ZEROFILL)
return std::numeric_limits<int>::max();
return 0;
}
@ -600,6 +618,9 @@ static void sortSegmentsAndSections() {
if (!osec->isHidden())
osec->index = ++sectionIndex;
if (!firstTLVDataSection && isThreadLocalData(osec->flags))
firstTLVDataSection = osec;
if (!isecPriorities.empty()) {
if (auto *merged = dyn_cast<MergedOutputSection>(osec)) {
llvm::stable_sort(merged->inputs,
@ -777,3 +798,5 @@ void macho::createSyntheticSections() {
in.stubHelper = make<StubHelperSection>();
in.imageLoaderCache = make<ImageLoaderCacheSection>();
}
OutputSection *macho::firstTLVDataSection = nullptr;

View File

@ -14,6 +14,8 @@
namespace lld {
namespace macho {
class OutputSection;
class LoadCommand {
public:
virtual ~LoadCommand() = default;
@ -25,6 +27,8 @@ void writeResult();
void createSyntheticSections();
extern OutputSection *firstTLVDataSection;
} // namespace macho
} // namespace lld

View File

@ -3,8 +3,11 @@
# RUN: %lld -o %t %t.o
# RUN: llvm-readobj --section-headers --macho-segment %t | FileCheck %s
## Check that __bss takes up zero file size, is at file offset zero, and
## appears at the end of its segment.
## Check that __bss takes up zero file size, is at file offset zero, and appears
## at the end of its segment. Also check that __tbss is placed immediately
## before it.
## Zerofill sections in other segments (i.e. not __DATA) should also be placed
## at the end.
# CHECK: Index: 1
# CHECK-NEXT: Name: __data
@ -23,6 +26,22 @@
# CHECK-NEXT: Reserved3: 0x0
# CHECK: Index: 2
# CHECK-NEXT: Name: __thread_bss
# CHECK-NEXT: Segment: __DATA
# CHECK-NEXT: Address:
# CHECK-NEXT: Size: 0x4
# CHECK-NEXT: Offset: 0
# CHECK-NEXT: Alignment: 0
# CHECK-NEXT: RelocationOffset: 0x0
# CHECK-NEXT: RelocationCount: 0
# CHECK-NEXT: Type: ThreadLocalZerofill (0x12)
# CHECK-NEXT: Attributes [ (0x0)
# CHECK-NEXT: ]
# CHECK-NEXT: Reserved1: 0x0
# CHECK-NEXT: Reserved2: 0x0
# CHECK-NEXT: Reserved3: 0x0
# CHECK: Index: 3
# CHECK-NEXT: Name: __bss
# CHECK-NEXT: Segment: __DATA
# CHECK-NEXT: Address:
@ -38,16 +57,32 @@
# CHECK-NEXT: Reserved2: 0x0
# CHECK-NEXT: Reserved3: 0x0
# CHECK: Index: 3
# CHECK-NEXT: Name: __thread_bss
# CHECK-NEXT: Segment: __DATA
# CHECK-NEXT: Address: 0x100001010
# CHECK-NEXT: Size: 0x4
# CHECK: Index: 4
# CHECK-NEXT: Name: foo
# CHECK-NEXT: Segment: FOO
# CHECK-NEXT: Address:
# CHECK-NEXT: Size: 0x8
# CHECK-NEXT: Offset: 8192
# CHECK-NEXT: Alignment: 0
# CHECK-NEXT: RelocationOffset: 0x0
# CHECK-NEXT: RelocationCount: 0
# CHECK-NEXT: Type: Regular (0x0)
# CHECK-NEXT: Attributes [ (0x0)
# CHECK-NEXT: ]
# CHECK-NEXT: Reserved1: 0x0
# CHECK-NEXT: Reserved2: 0x0
# CHECK-NEXT: Reserved3: 0x0
# CHECK: Index: 5
# CHECK-NEXT: Name: bss
# CHECK-NEXT: Segment: FOO
# CHECK-NEXT: Address:
# CHECK-NEXT: Size: 0x8
# CHECK-NEXT: Offset: 0
# CHECK-NEXT: Alignment: 0
# CHECK-NEXT: RelocationOffset: 0x0
# CHECK-NEXT: RelocationCount: 0
# CHECK-NEXT: Type: ThreadLocalZerofill (0x12)
# CHECK-NEXT: Type: ZeroFill (0x1)
# CHECK-NEXT: Attributes [ (0x0)
# CHECK-NEXT: ]
# CHECK-NEXT: Reserved1: 0x0
@ -61,6 +96,13 @@
# CHECK-NEXT: fileoff:
# CHECK-NEXT: filesize: 8
# CHECK: Name: FOO
# CHECK-NEXT: Size:
# CHECK-NEXT: vmaddr:
# CHECK-NEXT: vmsize: 0x10
# CHECK-NEXT: fileoff:
# CHECK-NEXT: filesize: 8
.globl _main
.text
@ -76,3 +118,8 @@ _main:
.data
.quad 0x1234
.zerofill FOO,bss,_zero_foo,0x8
.section FOO,foo
.quad 123

View File

@ -1,53 +1,75 @@
# REQUIRES: x86
# RUN: mkdir -p %t
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/test.o
# RUN: rm -rf %t; split-file %s %t
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/regular.s -o %t/regular.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/tbss.s -o %t/tbss.o
# RUN: %lld -lSystem -o %t/test %t/test.o
# RUN: llvm-readobj --file-headers %t/test | FileCheck %s --check-prefix=HEADER
# RUN: llvm-objdump -D --bind --rebase %t/test | FileCheck %s
# RUN: %lld -lSystem -no_pie -o %t/regular %t/regular.o
# RUN: llvm-readobj --file-headers %t/regular | FileCheck %s --check-prefix=HEADER
# RUN: llvm-objdump -d --bind --rebase %t/regular | FileCheck %s --check-prefixes=REG,LINKEDIT
# RUN: llvm-objdump --macho --section=__DATA,__thread_vars %t/regular | \
# RUN: FileCheck %s --check-prefix=REG-TLVP
# RUN: %lld -lSystem -pie -o %t/test %t/test.o
# RUN: llvm-readobj --file-headers %t/test | FileCheck %s --check-prefix=HEADER
# RUN: llvm-objdump -D --bind --rebase %t/test | FileCheck %s
# RUN: %lld -lSystem -pie %t/regular.o -o %t/regular-pie
# RUN: llvm-readobj --file-headers %t/regular-pie | FileCheck %s --check-prefix=HEADER
# RUN: llvm-objdump -d --bind --rebase %t/regular-pie | FileCheck %s --check-prefixes=REG,LINKEDIT
# RUN: llvm-objdump --macho --section=__DATA,__thread_vars %t/regular-pie | \
# RUN: FileCheck %s --check-prefix=REG-TLVP
# RUN: %lld -lSystem %t/tbss.o -o %t/tbss -e _f
# RUN: llvm-objdump -d --bind --rebase %t/tbss | FileCheck %s --check-prefixes=TBSS,LINKEDIT
# RUN: llvm-objdump --macho --section=__DATA,__thread_vars %t/tbss | \
# RUN: FileCheck %s --check-prefix=TBSS-TLVP
# RUN: %lld -lSystem %t/regular.o %t/tbss.o -o %t/regular-and-tbss
# RUN: llvm-objdump -d --bind --rebase %t/regular-and-tbss | FileCheck %s --check-prefixes=REG,TBSS,LINKEDIT
# RUN: llvm-objdump --macho --section=__DATA,__thread_vars %t/regular-and-tbss | \
# RUN: FileCheck %s --check-prefix=REG-TBSS-TLVP
# RUN: llvm-objdump --section-headers %t/regular-and-tbss | FileCheck %s --check-prefix=SECTION-ORDER
## Check that we always put __thread_bss immediately after __thread_data,
## regardless of the order of the input files.
# RUN: %lld -lSystem %t/tbss.o %t/regular.o -o %t/regular-and-tbss
# RUN: llvm-objdump --section-headers %t/regular-and-tbss | FileCheck %s --check-prefix=SECTION-ORDER
# 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: <_foo$tlv$init>:
# CHECK-NEXT: 00 00
# CHECK-NEXT: 00 00
# CHECK-EMPTY:
# CHECK-NEXT: <_bar$tlv$init>:
# CHECK-NEXT: 00 00
# CHECK-NEXT: 00 00
# 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
# REG: <_main>:
# REG-NEXT: leaq {{.*}}(%rip), %rax # {{.*}} <_foo>
# REG-NEXT: leaq {{.*}}(%rip), %rax # {{.*}} <_bar>
# REG-NEXT: retq
# TBSS: <_f>:
# TBSS-NEXT: leaq {{.*}}(%rip), %rax # {{.*}} <_baz>
# TBSS-NEXT: leaq {{.*}}(%rip), %rax # {{.*}} <_qux>
# TBSS-NEXT: retq
# REG-TLVP: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# REG-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# REG-TLVP-NEXT: 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00
# TBSS-TLVP: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00
# REG-TBSS-TLVP: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00
# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# REG-TBSS-TLVP-NEXT: 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00
## Make sure we don't emit rebase opcodes for relocations in __thread_vars.
# CHECK: Rebase table:
# CHECK-NEXT: segment section address type
# CHECK-NEXT: Bind table:
# CHECK: __DATA __thread_vars 0x{{[0-9a-f]*}} pointer 0 libSystem __tlv_bootstrap
# CHECK: __DATA __thread_vars 0x{{[0-9a-f]*}} pointer 0 libSystem __tlv_bootstrap
# LINKEDIT: Rebase table:
# LINKEDIT-NEXT: segment section address type
# LINKEDIT-NEXT: Bind table:
# LINKEDIT: __DATA __thread_vars 0x{{[0-9a-f]*}} pointer 0 libSystem __tlv_bootstrap
# LINKEDIT: __DATA __thread_vars 0x{{[0-9a-f]*}} pointer 0 libSystem __tlv_bootstrap
# SECTION-ORDER: __thread_data
# SECTION-ORDER: more_thread_data
# SECTION-ORDER-NEXT: __thread_bss
#--- regular.s
.globl _main
_main:
mov _foo@TLVP(%rip), %rax
@ -56,9 +78,11 @@ _main:
.section __DATA,__thread_data,thread_local_regular
_foo$tlv$init:
.space 4
.quad 123
.section __DATA,more_thread_data,thread_local_regular
_bar$tlv$init:
.space 4
.quad 123
.section __DATA,__thread_vars,thread_local_variables
.globl _foo, _bar
@ -70,3 +94,24 @@ _bar:
.quad __tlv_bootstrap
.quad 0
.quad _bar$tlv$init
#--- tbss.s
.globl _f
_f:
mov _baz@TLVP(%rip), %rax
mov _qux@TLVP(%rip), %rax
ret
.tbss _baz$tlv$init, 8, 3
.tbss _qux$tlv$init, 8, 3
.section __DATA,__thread_vars,thread_local_variables
_baz:
.quad __tlv_bootstrap
.quad 0
.quad _baz$tlv$init
_qux:
.quad __tlv_bootstrap
.quad 0
.quad _qux$tlv$init