StaticTlsLayout: add exe/tcb and solib layout

Replace reserve_tcb with reserve_exe_segment_and_tcb, which lays out both
the TCB and the executable's TLS segment, accounting for the difference in
layout between variant 1 and variant 2 targets.

The function isn't actually called with a non-null TlsSegment* yet.

Bug: http://b/78026329
Test: bionic unit tests
Change-Id: Ibd6238577423a7d0451f36da7e64912046959796
diff --git a/libc/bionic/bionic_elf_tls.cpp b/libc/bionic/bionic_elf_tls.cpp
index 2d2cbe3..a54f29e 100644
--- a/libc/bionic/bionic_elf_tls.cpp
+++ b/libc/bionic/bionic_elf_tls.cpp
@@ -65,8 +65,57 @@
   return false;
 }
 
-void StaticTlsLayout::reserve_tcb() {
-  offset_bionic_tcb_ = reserve_type<bionic_tcb>();
+// Reserves space for the Bionic TCB and the executable's TLS segment. Returns
+// the offset of the executable's TLS segment.
+size_t StaticTlsLayout::reserve_exe_segment_and_tcb(const TlsSegment* exe_segment,
+                                                    const char* progname __attribute__((unused))) {
+  // Special case: if the executable has no TLS segment, then just allocate a
+  // TCB and skip the minimum alignment check on ARM.
+  if (exe_segment == nullptr) {
+    offset_bionic_tcb_ = reserve_type<bionic_tcb>();
+    return 0;
+  }
+
+#if defined(__arm__) || defined(__aarch64__)
+
+  // First reserve enough space for the TCB before the executable segment.
+  reserve(sizeof(bionic_tcb), 1);
+
+  // Then reserve the segment itself.
+  const size_t result = reserve(exe_segment->size, exe_segment->alignment);
+
+  // The variant 1 ABI that ARM linkers follow specifies a 2-word TCB between
+  // the thread pointer and the start of the executable's TLS segment, but both
+  // the thread pointer and the TLS segment are aligned appropriately for the
+  // TLS segment. Calculate the distance between the thread pointer and the
+  // EXE's segment.
+  const size_t exe_tpoff = __BIONIC_ALIGN(sizeof(void*) * 2, exe_segment->alignment);
+
+  const size_t min_bionic_alignment = BIONIC_ROUND_UP_POWER_OF_2(MAX_TLS_SLOT) * sizeof(void*);
+  if (exe_tpoff < min_bionic_alignment) {
+    async_safe_fatal("error: \"%s\": executable's TLS segment is underaligned: "
+                     "alignment is %zu, needs to be at least %zu for %s Bionic",
+                     progname, exe_segment->alignment, min_bionic_alignment,
+                     (sizeof(void*) == 4 ? "ARM" : "ARM64"));
+  }
+
+  offset_bionic_tcb_ = result - exe_tpoff - (-MIN_TLS_SLOT * sizeof(void*));
+  return result;
+
+#elif defined(__i386__) || defined(__x86_64__)
+
+  // x86 uses variant 2 TLS layout. The executable's segment is located just
+  // before the TCB.
+  static_assert(MIN_TLS_SLOT == 0, "First slot of bionic_tcb must be slot #0 on x86");
+  const size_t exe_size = round_up_with_overflow_check(exe_segment->size, exe_segment->alignment);
+  reserve(exe_size, 1);
+  const size_t max_align = MAX(alignof(bionic_tcb), exe_segment->alignment);
+  offset_bionic_tcb_ = reserve(sizeof(bionic_tcb), max_align);
+  return offset_bionic_tcb_ - exe_size;
+
+#else
+#error "Unrecognized architecture"
+#endif
 }
 
 void StaticTlsLayout::reserve_bionic_tls() {
@@ -76,6 +125,10 @@
 void StaticTlsLayout::finish_layout() {
   // Round the offset up to the alignment.
   offset_ = round_up_with_overflow_check(offset_, alignment_);
+
+  if (overflowed_) {
+    async_safe_fatal("error: TLS segments in static TLS overflowed");
+  }
 }
 
 // The size is not required to be a multiple of the alignment. The alignment
diff --git a/libc/bionic/libc_init_static.cpp b/libc/bionic/libc_init_static.cpp
index 68650ed..6ba6583 100644
--- a/libc/bionic/libc_init_static.cpp
+++ b/libc/bionic/libc_init_static.cpp
@@ -86,7 +86,7 @@
 static void layout_static_tls() {
   StaticTlsLayout& layout = __libc_shared_globals()->static_tls_layout;
   layout.reserve_bionic_tls();
-  layout.reserve_tcb();
+  layout.reserve_exe_segment_and_tcb(nullptr);
   layout.finish_layout();
 }
 
diff --git a/libc/private/bionic_elf_tls.h b/libc/private/bionic_elf_tls.h
index 48a4d25..c67c221 100644
--- a/libc/private/bionic_elf_tls.h
+++ b/libc/private/bionic_elf_tls.h
@@ -63,8 +63,11 @@
   size_t alignment() const { return alignment_; }
   bool overflowed() const { return overflowed_; }
 
-  void reserve_tcb();
+  size_t reserve_exe_segment_and_tcb(const TlsSegment* exe_segment, const char* progname);
   void reserve_bionic_tls();
+  size_t reserve_solib_segment(const TlsSegment& segment) {
+    return reserve(segment.size, segment.alignment);
+  }
   void finish_layout();
 
 private:
diff --git a/linker/linker_tls.cpp b/linker/linker_tls.cpp
index 3327453..c0ebf25 100644
--- a/linker/linker_tls.cpp
+++ b/linker/linker_tls.cpp
@@ -41,7 +41,7 @@
 // Stub for linker static TLS layout.
 void layout_linker_static_tls() {
   StaticTlsLayout& layout = __libc_shared_globals()->static_tls_layout;
-  layout.reserve_tcb();
+  layout.reserve_exe_segment_and_tcb(nullptr);
 
   // The pthread key data is located at the very front of bionic_tls. As a
   // temporary workaround, allocate bionic_tls just after the thread pointer so