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
#include "OutputSegment.h"
#include "Symbols.h"
#include "Target.h"
+#include "Writer.h"
#include "lld/Common/Memory.h"
#include "llvm/Support/Endian.h"
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();
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) {
.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)
}
// 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;
}
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,
in.stubHelper = make<StubHelperSection>();
in.imageLoaderCache = make<ImageLoaderCacheSection>();
}
+
+OutputSection *macho::firstTLVDataSection = nullptr;
namespace lld {
namespace macho {
+class OutputSection;
+
class LoadCommand {
public:
virtual ~LoadCommand() = default;
void createSyntheticSections();
+extern OutputSection *firstTLVDataSection;
+
} // namespace macho
} // namespace lld
# 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
# 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:
# 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
# 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
.data
.quad 0x1234
+
+.zerofill FOO,bss,_zero_foo,0x8
+
+.section FOO,foo
+.quad 123
# 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
.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
.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