Index: uspace/lib/c/Makefile
===================================================================
--- uspace/lib/c/Makefile	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/Makefile	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -77,4 +77,5 @@
 	generic/dnsr.c \
 	generic/dlfcn.c \
+	generic/elf/elf.c \
 	generic/elf/elf_load.c \
 	generic/elf/elf_mod.c \
Index: uspace/lib/c/arch/abs32le/_link.ld.in
===================================================================
--- uspace/lib/c/arch/abs32le/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/abs32le/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -8,4 +8,5 @@
 	text PT_LOAD FILEHDR PHDRS FLAGS(5);
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 }
 
@@ -40,14 +41,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
+	} :data :tls
+
+	.tbss : {
+		*(.tbss);
+		*(.tbss.*);
 		*(.gnu.linkonce.tb.*);
-		_tdata_end = .;
-		_tbss_start = .;
-		*(.tbss);
-		_tbss_end = .;
-	} :data
-
-	_tls_alignment = ALIGNOF(.tdata);
+	} :data :tls
 
 	.sbss : {
Index: uspace/lib/c/arch/amd64/_link.ld.in
===================================================================
--- uspace/lib/c/arch/amd64/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/amd64/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -8,4 +8,5 @@
 	text PT_LOAD FILEHDR PHDRS FLAGS(5);
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 	debug PT_NOTE;
 }
@@ -45,13 +46,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
-		_tdata_end = .;
-		_tbss_start = .;
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
+	} :data :tls
+
+	.tbss : {
 		*(.tbss);
-		_tbss_end = .;
-	} :data
-
-	_tls_alignment = ALIGNOF(.tdata);
+		*(.tbss.*);
+		*(.gnu.linkonce.tb.*);
+	} :data :tls
 
 	.bss : {
Index: uspace/lib/c/arch/arm32/_link.ld.in
===================================================================
--- uspace/lib/c/arch/arm32/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/arm32/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -8,4 +8,5 @@
 	text PT_LOAD FILEHDR PHDRS FLAGS(5);
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 }
 
@@ -45,13 +46,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
-		_tdata_end = .;
-		_tbss_start = .;
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
+	} :data :tls
+
+	.tbss : {
 		*(.tbss);
-		_tbss_end = .;
-	} :data
-
-	_tls_alignment = ALIGNOF(.tdata);
+		*(.tbss.*);
+		*(.gnu.linkonce.tb.*);
+	} :data :tls
 
 	.bss : {
Index: uspace/lib/c/arch/ia32/_link.ld.in
===================================================================
--- uspace/lib/c/arch/ia32/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/ia32/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -99,16 +99,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
-		*(.gnu.linkonce.tb.*);
-		_tdata_end = .;
-	} :data :tls
-	.tbss : {
-		_tbss_start = .;
-		*(.tbss);
-		_tbss_end = .;
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
 	} :data :tls
 
-	_tls_alignment = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss));
+	.tbss : {
+		*(.tbss);
+		*(.tbss.*);
+		*(.gnu.linkonce.tb.*);
+	} :data :tls
 
 	.bss : {
Index: uspace/lib/c/arch/ia64/_link.ld.in
===================================================================
--- uspace/lib/c/arch/ia64/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/ia64/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -8,4 +8,5 @@
 	text PT_LOAD FILEHDR PHDRS FLAGS(5);
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 }
 
@@ -54,13 +55,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
-		_tdata_end = .;
-		_tbss_start = .;
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
+	} :data :tls
+
+	.tbss : {
 		*(.tbss);
-		_tbss_end = .;
-	} :data
-
-	_tls_alignment = ALIGNOF(.tdata);
+		*(.tbss.*);
+		*(.gnu.linkonce.tb.*);
+	} :data :tls
 
 	.bss : {
Index: uspace/lib/c/arch/mips32/_link.ld.in
===================================================================
--- uspace/lib/c/arch/mips32/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/mips32/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -8,4 +8,5 @@
 	text PT_LOAD FILEHDR PHDRS FLAGS(5);
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 }
 
@@ -49,16 +50,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
-		_tdata_end = .;
-	} :data
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
+	} :data :tls
 
 	.tbss : {
-		_tbss_start = .;
 		*(.tbss);
-		_tbss_end = .;
-	} :data
-
-	_tls_alignment = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss));
+		*(.tbss.*);
+		*(.gnu.linkonce.tb.*);
+	} :data :tls
 
 	.sbss : {
Index: uspace/lib/c/arch/ppc32/_link.ld.in
===================================================================
--- uspace/lib/c/arch/ppc32/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/ppc32/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -8,4 +8,5 @@
 	text PT_LOAD FILEHDR PHDRS FLAGS(5);
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 	debug PT_NOTE;
 }
@@ -45,13 +46,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
-		_tdata_end = .;
-		_tbss_start = .;
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
+	} :data :tls
+
+	.tbss : {
 		*(.tbss);
-		_tbss_end = .;
-	} :data
-
-	_tls_alignment = ALIGNOF(.tdata);
+		*(.tbss.*);
+		*(.gnu.linkonce.tb.*);
+	} :data :tls
 
 	.bss : {
Index: uspace/lib/c/arch/riscv64/_link.ld.in
===================================================================
--- uspace/lib/c/arch/riscv64/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/riscv64/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -8,4 +8,5 @@
 	text PT_LOAD FILEHDR PHDRS FLAGS(5);
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 	debug PT_NOTE;
 }
@@ -43,14 +44,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
+	} :data :tls
+
+	.tbss : {
+		*(.tbss);
+		*(.tbss.*);
 		*(.gnu.linkonce.tb.*);
-		_tdata_end = .;
-		_tbss_start = .;
-		*(.tbss);
-		_tbss_end = .;
-	} :data
-
-	_tls_alignment = ALIGNOF(.tdata);
+	} :data :tls
 
 	.sbss : {
Index: uspace/lib/c/arch/sparc64/_link.ld.in
===================================================================
--- uspace/lib/c/arch/sparc64/_link.ld.in	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/arch/sparc64/_link.ld.in	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -8,4 +8,5 @@
 	text PT_LOAD FILEHDR PHDRS FLAGS(5);
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 	debug PT_NOTE;
 }
@@ -50,13 +51,14 @@
 
 	.tdata : {
-		_tdata_start = .;
 		*(.tdata);
-		_tdata_end = .;
-		_tbss_start = .;
+		*(.tdata.*);
+		*(.gnu.linkonce.td.*);
+	} :data :tls
+
+	.tbss : {
 		*(.tbss);
-		_tbss_end = .;
-	} :data
-
-	_tls_alignment = ALIGNOF(.tdata);
+		*(.tbss.*);
+		*(.gnu.linkonce.tb.*);
+	} :data :tls
 
 	.bss : {
Index: uspace/lib/c/generic/elf/elf.c
===================================================================
--- uspace/lib/c/generic/elf/elf.c	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
+++ uspace/lib/c/generic/elf/elf.c	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2018 CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <elf/elf.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+/** @addtogroup generic
+ * @{
+ */
+
+/**
+ * Checks that the ELF header is valid for the running system.
+ */
+static bool elf_is_valid(const elf_header_t *header)
+{
+	// TODO: check more things
+	// TODO: debug output
+
+	if (header->e_ident[EI_MAG0] != ELFMAG0 ||
+	    header->e_ident[EI_MAG1] != ELFMAG1 ||
+	    header->e_ident[EI_MAG2] != ELFMAG2 ||
+	    header->e_ident[EI_MAG3] != ELFMAG3) {
+		return false;
+	}
+
+	if (header->e_ident[EI_DATA] != ELF_DATA_ENCODING ||
+	    header->e_machine != ELF_MACHINE ||
+	    header->e_ident[EI_VERSION] != EV_CURRENT ||
+	    header->e_version != EV_CURRENT ||
+	    header->e_ident[EI_CLASS] != ELF_CLASS) {
+		return false;
+	}
+
+	if (header->e_phentsize != sizeof(elf_segment_header_t)) {
+		return false;
+	}
+
+	if (header->e_type != ET_EXEC && header->e_type != ET_DYN) {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Given the base of an ELF image in memory (i.e. pointer to the the file
+ * header at the beginning of the text segment), returns pointer to the
+ * first segment header with the given p_type.
+ */
+const elf_segment_header_t *elf_get_phdr(const void *base, unsigned p_type)
+{
+	const elf_header_t *hdr = base;
+	assert(elf_is_valid(hdr));
+
+	const elf_segment_header_t *phdr =
+	    (const elf_segment_header_t *) ((uintptr_t) base + hdr->e_phoff);
+
+	for (int i = 0; i < hdr->e_phnum; i++) {
+		if (phdr[i].p_type == p_type)
+			return &phdr[i];
+	}
+
+	return NULL;
+}
+
+uintptr_t elf_get_bias(const void *base)
+{
+	const elf_header_t *hdr = base;
+	assert(elf_is_valid(hdr));
+
+	/* There are two legal options for a HelenOS ELF file.
+	 * Either the file is ET_DYN (shared library or PIE), and the base is
+	 * (required to be) at vaddr 0. Or the file is ET_EXEC (non-relocatable)
+	 * and the bias is trivially 0.
+	 */
+
+	if (hdr->e_type == ET_DYN)
+		return (uintptr_t) base;
+
+	return 0;
+}
+
+/** @}
+ */
+
Index: uspace/lib/c/generic/rtld/module.c
===================================================================
--- uspace/lib/c/generic/rtld/module.c	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/generic/rtld/module.c	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -70,8 +70,12 @@
 	module->local = true;
 
-	module->tdata = &_tdata_start;
-	module->tdata_size = &_tdata_end - &_tdata_start;
-	module->tbss_size = &_tbss_end - &_tbss_start;
-	module->tls_align = (uintptr_t)&_tls_alignment;
+	const elf_segment_header_t *tls =
+	    elf_get_phdr(__executable_start, PT_TLS);
+	uintptr_t bias = elf_get_bias(__executable_start);
+
+	module->tdata = (void *) (tls->p_vaddr + bias);
+	module->tdata_size = tls->p_filesz;
+	module->tbss_size = tls->p_memsz - tls->p_filesz;
+	module->tls_align = tls->p_align;
 
 	list_append(&module->modules_link, &rtld->modules);
Index: uspace/lib/c/generic/tls.c
===================================================================
--- uspace/lib/c/generic/tls.c	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/generic/tls.c	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -41,4 +41,5 @@
 #include <stdlib.h>
 #include <str.h>
+#include <elf/elf.h>
 
 #ifdef CONFIG_RTLD
@@ -52,5 +53,8 @@
 		return runtime_env->tls_size;
 #endif
-	return &_tbss_end - &_tdata_start;
+
+	const elf_segment_header_t *tls =
+	    elf_get_phdr(__executable_start, PT_TLS);
+	return tls->p_memsz;
 }
 
@@ -73,5 +77,4 @@
 	void *data;
 	tcb_t *tcb;
-	size_t tls_size = &_tbss_end - &_tdata_start;
 
 #ifdef CONFIG_RTLD
@@ -80,17 +83,21 @@
 #endif
 
-	tcb = tls_alloc_arch(&data, tls_size);
-	if (!tcb)
+	const elf_segment_header_t *tls =
+	    elf_get_phdr(__executable_start, PT_TLS);
+	if (tls == NULL)
 		return NULL;
+
+	uintptr_t bias = elf_get_bias(__executable_start);
+
+	tcb = tls_alloc_arch(&data, tls->p_memsz);
 
 	/*
 	 * Copy thread local data from the initialization image.
 	 */
-	memcpy(data, &_tdata_start, &_tdata_end - &_tdata_start);
+	memcpy(data, (void *)(tls->p_vaddr + bias), tls->p_filesz);
 	/*
 	 * Zero out the thread local uninitialized data.
 	 */
-	memset(data + (&_tbss_start - &_tdata_start), 0,
-	    &_tbss_end - &_tbss_start);
+	memset(data + tls->p_filesz, 0, tls->p_memsz - tls->p_filesz);
 
 	return tcb;
@@ -151,6 +158,8 @@
 	tcb_t *tcb;
 
-	size = ALIGN_UP(size, &_tls_alignment);
-	*data = memalign((uintptr_t) &_tls_alignment, sizeof(tcb_t) + size);
+	uintptr_t align = elf_get_phdr(__executable_start, PT_TLS)->p_align;
+
+	size = ALIGN_UP(size, align);
+	*data = memalign(align, sizeof(tcb_t) + size);
 	if (*data == NULL)
 		return NULL;
@@ -171,5 +180,6 @@
 void tls_free_variant_2(tcb_t *tcb, size_t size)
 {
-	size = ALIGN_UP(size, &_tls_alignment);
+	uintptr_t align = elf_get_phdr(__executable_start, PT_TLS)->p_align;
+	size = ALIGN_UP(size, align);
 	void *start = ((void *) tcb) - size;
 	free(start);
Index: uspace/lib/c/include/elf/elf.h
===================================================================
--- uspace/lib/c/include/elf/elf.h	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/include/elf/elf.h	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -41,4 +41,9 @@
 #include <libarch/elf.h>
 
+extern const uint8_t __executable_start[];
+
+extern const elf_segment_header_t *elf_get_phdr(const void *, unsigned);
+extern uintptr_t elf_get_bias(const void *);
+
 #endif
 
Index: uspace/lib/c/include/tls.h
===================================================================
--- uspace/lib/c/include/tls.h	(revision 1433ecda9b732f3f185f902feb26826ec2496e03)
+++ uspace/lib/c/include/tls.h	(revision 2c4e1cc2aa4f1a4b249af5c661e93ac170ba5158)
@@ -43,13 +43,4 @@
 #define DTV_GN(dtv) (((uintptr_t *)(dtv))[0])
 
-/*
- * Symbols defined in the respective linker script.
- */
-extern char _tls_alignment;
-extern char _tdata_start;
-extern char _tdata_end;
-extern char _tbss_start;
-extern char _tbss_end;
-
 extern tcb_t *tls_make(void);
 extern tcb_t *tls_alloc_arch(void **, size_t);
