Skip to content

Commit

Permalink
[ELF] Align the first TLS section with largest required TLS alignment.
Browse files Browse the repository at this point in the history
TLS chunks alignments are special: in addition to having their virtual
addresses aligned, they also have to be aligned when the region of
tls_begin is copied to a new thread's storage area. In other words, their
offset against tls_begin also has to be aligned.

A good way to achieve this is to take the largest alignment requirement of
all TLS sections and make tls_begin also aligned to that.

Signed-off-by: Tatsuyuki Ishi <ishitatsuyuki@gmail.com>
  • Loading branch information
ishitatsuyuki committed Nov 30, 2022
1 parent 48b2c0f commit bd46edf
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 2 deletions.
27 changes: 25 additions & 2 deletions elf/passes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,29 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
std::vector<Chunk<E> *> &chunks = ctx.chunks;
u64 addr = ctx.arg.image_base;

// TLS chunks alignments are special: in addition to having their virtual
// addresses aligned, they also have to be aligned when the region of
// tls_begin is copied to a new thread's storage area. In other words, their
// offset against tls_begin also has to be aligned.
//
// A good way to achieve this is to take the largest alignment requirement
// of all TLS sections and make tls_begin also aligned to that.
Chunk<E> *first_tls_chunk = nullptr;
u64 first_tls_chunk_alignment = 1;
for (Chunk<E> *chunk : chunks) {
if (chunk->shdr.sh_flags & SHF_TLS) {
if (!first_tls_chunk)
first_tls_chunk = chunk;
first_tls_chunk_alignment
= std::max(first_tls_chunk_alignment, (u64)chunk->shdr.sh_addralign);
}
}

auto alignment = [&](Chunk<E> *chunk) {
return chunk == first_tls_chunk ? first_tls_chunk_alignment
: (u64)chunk->shdr.sh_addralign;
};

for (i64 i = 0; i < chunks.size(); i++) {
if (!(chunks[i]->shdr.sh_flags & SHF_ALLOC))
continue;
Expand Down Expand Up @@ -1835,7 +1858,7 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
if (is_tbss(chunks[i])) {
u64 addr2 = addr;
for (;;) {
addr2 = align_to(addr2, chunks[i]->shdr.sh_addralign);
addr2 = align_to(addr2, alignment(chunks[i]));
chunks[i]->shdr.sh_addr = addr2;
addr2 += chunks[i]->shdr.sh_size;
if (i + 2 == chunks.size() || !is_tbss(chunks[i + 1]))
Expand All @@ -1845,7 +1868,7 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
continue;
}

addr = align_to(addr, chunks[i]->shdr.sh_addralign);
addr = align_to(addr, alignment(chunks[i]));
chunks[i]->shdr.sh_addr = addr;
addr += chunks[i]->shdr.sh_size;
}
Expand Down
50 changes: 50 additions & 0 deletions test/elf/tls-alignment-multi.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash
. $(dirname $0)/common.inc

# Test a tricky case of TLS alignment requirement where not only the virtual
# address of a symbol but also its offset against the TLS base address has to
# be aligned.
#
# On glibc, this issue requires a TLS model equivalent to global-dynamic in
# order to be triggered.

cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
#include <assert.h>
#include <stdlib.h>
// .tdata
_Thread_local int x = 42;
// .tbss
__attribute__ ((aligned(64)))
_Thread_local int y = 0;
void *verify(void *unused) {
assert((unsigned long)(&y) % 64 == 0);
return NULL;
}
EOF

cat <<EOF | $CC -fPIC -c -o $t/b.o -xc -
#include <pthread.h>
#include <dlfcn.h>
#include <assert.h>
void *(*verify)(void *);
int main() {
void *handle = dlopen("a.so", RTLD_NOW);
assert(handle);
*(void**)(&verify) = dlsym(handle, "verify");
assert(verify);
pthread_t thread;
verify(NULL);
pthread_create(&thread, NULL, verify, NULL);
pthread_join(thread, NULL);
}
EOF

$CC -B. -shared -o $t/a.so $t/a.o
$CC -B. -ldl -pthread -o $t/exe $t/b.o -Wl,-rpath,$t
$QEMU $t/exe

0 comments on commit bd46edf

Please sign in to comment.