[PATCH] Fix TLS allocation static binaries on on TLS_DTV_AT_TP architectures
Florian Weimer
fweimer@redhat.com
Sun Jan 12 17:20:37 GMT 2025
The TLS block effectively starts at tcb_offset, and this needs to be
reflected in the TLS block size. With large align values, tcb_offset
can be much larger than TLS_INIT_TCB_SIZE.
Fixes commit 0e411c5d3098982d67cd2d7a233eaa6c977a1869 ("Add generic
'extra TLS'").
While this fixes the elf/tst-tlsalign-static test case, the new
elf/tst-rseq-tls-range-4096-static.c test case fails with a crash in
dlopen (in TLS image initialization) *before* the rseq changes are
applied, so this is clearly not the complete fix. I suspect that the
amount of TLS usage is not tracked correctly before and after the rseq
changes. I'm posting this so that others can have a look at this
minimal change.
I'm also exploring a completely different approach.
Thanks,
Florian
---
csu/libc-tls.c | 19 ++-
sysdeps/unix/sysv/linux/Makefile | 14 ++
.../sysv/linux/tst-rseq-tls-range-4096-static.c | 1 +
sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096.c | 1 +
sysdeps/unix/sysv/linux/tst-rseq-tls-range-mod.c | 1 +
.../unix/sysv/linux/tst-rseq-tls-range-static.c | 1 +
sysdeps/unix/sysv/linux/tst-rseq-tls-range.c | 168 +++++++++++++++++++++
7 files changed, 197 insertions(+), 8 deletions(-)
diff --git a/csu/libc-tls.c b/csu/libc-tls.c
index 5ffebc6fc2..0e239f8619 100644
--- a/csu/libc-tls.c
+++ b/csu/libc-tls.c
@@ -192,18 +192,21 @@ __libc_setup_tls (void)
/* In this layout the TLS blocks are located after the thread pointer. */
/* Record the tcb_offset including the aligment requirements of 'memsz'
- that comes after it. */
+ that comes after it. The link editor knows about this offset
+ and adds it to all offsets of TLS variables. */
tcb_offset = roundup (TLS_INIT_TCB_SIZE, align ?: 1);
/* Record the size of the combined TLS blocks.
- First reserve space for TLS_INIT_TCB_SIZE and 'memsz' while respecting
- both its alignment requirements and those of the extra TLS blocks. Then
- add the size of the extra TLS block. Both values respect the extra TLS
- alignment requirements and so does the resulting size and the offset that
- will be derived from it. */
- tls_blocks_size = roundup (TLS_INIT_TCB_SIZE + memsz,
- MAX (align, extra_tls_align) ?: 1) + extra_tls_size;
+ First reserve space for tcb_offset (the adjusted
+ TLS_INIT_TCB_SIZE) and 'memsz' while respecting both its
+ alignment requirements and those of the extra TLS blocks. Then
+ add the size of the extra TLS block. Both values respect the
+ extra TLS alignment requirements and so does the resulting size
+ and the offset that will be derived from it. */
+ tls_blocks_size = (roundup (tcb_offset + memsz,
+ MAX (align, extra_tls_align) ?: 1)
+ + extra_tls_size);
/* Record the extra TLS block offset from the thread pointer.
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index c8d30cc405..395d2d6593 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -660,6 +660,20 @@ install-bin += \
$(objpfx)pldd: $(objpfx)xmalloc.o
+tests += tst-rseq-tls-range tst-rseq-tls-range-4096
+tests-static += tst-rseq-tls-range-static tst-rseq-tls-range-4096-static
+modules-names += tst-rseq-tls-range-mod
+CFLAGS-tst-rseq-tls-range.c += -DMAIN_TLS_ALIGN=4
+CFLAGS-tst-rseq-tls-range-4096.c += -DMAIN_TLS_ALIGN=4096
+CFLAGS-tst-rseq-tls-range-static.c += -DMAIN_TLS_ALIGN=4
+CFLAGS-tst-rseq-tls-range-4096-static.c += -DMAIN_TLS_ALIGN=4096
+$(objpfx)tst-rseq-tls-range.out: $(objpfx)tst-rseq-tls-range-mod.so
+$(objpfx)tst-rseq-tls-range-4096.out: $(objpfx)tst-rseq-tls-range-mod.so
+$(objpfx)tst-rseq-tls-range-static.out: $(objpfx)tst-rseq-tls-range-mod.so
+$(objpfx)tst-rseq-tls-range-4096-static.out: $(objpfx)tst-rseq-tls-range-mod.so
+tst-rseq-tls-range-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
+tst-rseq-tls-range-4096-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
+
test-internal-extras += tst-nolink-libc
ifeq ($(run-built-tests),yes)
tests-special += \
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096-static.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096-static.c
new file mode 100644
index 0000000000..5ad2853847
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096-static.c
@@ -0,0 +1 @@
+#include "tst-rseq-tls-range.c"
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096.c
new file mode 100644
index 0000000000..5ad2853847
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096.c
@@ -0,0 +1 @@
+#include "tst-rseq-tls-range.c"
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range-mod.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-mod.c
new file mode 100644
index 0000000000..165e468eaa
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-mod.c
@@ -0,0 +1 @@
+__thread int mod_thread_var __attribute__ ((tls_model ("initial-exec")));
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range-static.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-static.c
new file mode 100644
index 0000000000..5ad2853847
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-static.c
@@ -0,0 +1 @@
+#include "tst-rseq-tls-range.c"
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range.c
new file mode 100644
index 0000000000..00a2cabe94
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range.c
@@ -0,0 +1,168 @@
+#include <array_length.h>
+#include <elf.h>
+#include <link.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <sys/rseq.h>
+#include <thread_pointer.h>
+#include <unistd.h>
+
+/* Used to keep track of address ranges. The ranges are sorted and
+ then checked for overlap. */
+
+struct address_range
+{
+ const char *prefix;
+ const char *label;
+ uintptr_t start;
+ size_t length;
+};
+
+struct address_range ranges[20];
+size_t range_count;
+
+static void
+add_range (const char *prefix, const char *label,
+ const void *start, size_t length)
+{
+ TEST_VERIFY (start != NULL);
+ TEST_VERIFY (length > 0);
+ TEST_VERIFY_EXIT (range_count < array_length (ranges));
+ ranges[range_count].prefix = prefix;
+ ranges[range_count].label = label;
+ ranges[range_count].start = (uintptr_t) start;
+ ranges[range_count].length = length;
+ ++range_count;
+}
+
+static int
+range_compare (const void *a1, const void *b1)
+{
+ const struct address_range *a = a1;
+ const struct address_range *b = b1;
+ if (a->start < b->start)
+ return -1;
+ if (a->start > b->start)
+ return 1;
+ return 0;
+}
+
+static void
+check_for_overlap (void)
+{
+ qsort (ranges, range_count, sizeof (ranges[0]), range_compare);
+ uintptr_t previous_end = ranges[0].start + ranges[0].length - 1;
+ for (size_t i = 1; i < range_count; ++i)
+ {
+ uintptr_t this_end = ranges[i].start + ranges[i].length - 1;
+ if (ranges[i].start <= previous_end)
+ {
+ puts ("error: overlap between address ranges");
+ printf (" %s%s: [0x%lx, 0x%lx)\n",
+ ranges[i - 1].prefix, ranges[i - 1].label,
+ (unsigned long int) ranges[i - 1].start,
+ (unsigned long int) previous_end);
+ printf (" %s%s: [0x%lx, 0x%lx)\n",
+ ranges[i].prefix, ranges[i].label,
+ (unsigned long int) ranges[i].start,
+ (unsigned long int) this_end);
+ }
+ previous_end = this_end;
+ }
+}
+
+static void
+add_rseq (void)
+{
+ if (__rseq_size > 0)
+ add_range ("", "rseq area",
+ (char *) __thread_pointer () + __rseq_offset, __rseq_size);
+}
+
+/* These functions add the TLS data for all loaded modules to the
+ recorded address ranges. */
+
+static int
+dlip_callback (struct dl_phdr_info *info, size_t size, void *ignored)
+{
+ /* If the dynamic linker does not provide TLS address information,
+ there is nothing to register. */
+ if (info->dlpi_tls_data == NULL)
+ return 0;
+
+ for (int i = 0; i < info->dlpi_phnum; ++i)
+ {
+ if (info->dlpi_phdr[i].p_type == PT_TLS)
+ {
+ printf ("info: adding TLS range for \"%s\" (%zu bytes)\n",
+ info->dlpi_name, info->dlpi_phdr[i].p_memsz);
+ add_range ("TLS for ",
+ info->dlpi_name[0] != '\0' ? info->dlpi_name : "main",
+ info->dlpi_tls_data, info->dlpi_phdr[i].p_memsz);
+ break;
+ }
+ }
+ return 0;
+}
+
+/* Returns true if any TLS ranges were found. */
+static void
+add_tls_ranges (void)
+{
+ dl_iterate_phdr (dlip_callback, NULL);
+}
+
+volatile __thread int thread_var __attribute__ ((aligned (MAIN_TLS_ALIGN)));
+
+static int
+do_test (void)
+{
+ void *original_brk = sbrk (0);
+ void *initial_allocation = malloc (16);
+
+ /* Ensure that the variable is not optimized away. */
+ thread_var = 0;
+
+ printf ("info: rseq area size: %u\n", __rseq_size);
+
+ puts ("info: checking address ranges with initially loaded modules");
+ add_range ("", "program break", original_brk, 1);
+ add_range ("", "malloc allocation", initial_allocation, 16);
+ add_rseq ();
+ add_tls_ranges ();
+ printf ("info: %zu ranges found\n", range_count);
+ check_for_overlap ();
+ range_count = 0;
+
+ puts ("info: checking address ranges after dlopen");
+ void *handle = xdlopen ("tst-rseq-tls-range-mod.so", RTLD_NOW);
+ int *mod_thread_var = xdlsym (handle, "mod_thread_var");
+ add_range ("", "program break", original_brk, 1);
+ add_range ("", "malloc allocation", initial_allocation, 16);
+ add_rseq ();
+ add_tls_ranges ();
+ {
+ bool found_objects = false;
+ for (size_t i = 0; i < range_count; ++i)
+ if (strchr (ranges[i].label, '/') != NULL)
+ found_objects = true;
+ if (!found_objects)
+ /* __tls_get_addr does not fully work with static dlopen.
+ Add some fall-back test data. */
+ add_range ("", "mod_thread_var",
+ mod_thread_var, sizeof (*mod_thread_var));
+ }
+ printf ("info: %zu ranges found\n", range_count);
+ check_for_overlap ();
+ xdlclose (handle);
+
+ free (initial_allocation);
+ return 0;
+}
+
+#include <support/test-driver.c>
base-commit: 53a71b9f66dbc7f0ce44ec95bff7caa31fa0374b
More information about the Libc-alpha
mailing list