Index: .bzrignore
===================================================================
--- .bzrignore	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ .bzrignore	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -35,4 +35,5 @@
 uspace/app/devctl/devctl
 uspace/app/dltest/dltest
+uspace/app/dltests/dltests
 uspace/app/dnscfg/dnscfg
 uspace/app/dnsres/dnsres
@@ -96,4 +97,5 @@
 uspace/dist/app/devctl
 uspace/dist/app/dltest
+uspace/dist/app/dltests
 uspace/dist/app/dnscfg
 uspace/dist/app/dnsres
Index: abi/include/abi/elf.h
===================================================================
--- abi/include/abi/elf.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ abi/include/abi/elf.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -157,4 +157,5 @@
 #define STT_SECTION  3
 #define STT_FILE     4
+#define STT_TLS      6
 #define STT_LOPROC   13
 #define STT_HIPROC   15
@@ -170,4 +171,5 @@
 #define PT_SHLIB    5
 #define PT_PHDR     6
+#define PT_TLS      7
 #define PT_LOPROC   0x70000000
 #define PT_HIPROC   0x7fffffff
Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ boot/Makefile.common	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -226,5 +226,6 @@
 ifeq ($(CONFIG_BUILD_SHARED_LIBS), y)
 	RD_APPS_NON_ESSENTIAL += \
-		$(USPACE_PATH)/app/dltest/dltest
+		$(USPACE_PATH)/app/dltest/dltest \
+		$(USPACE_PATH)/app/dltests/dltests
 endif
 
Index: kernel/generic/src/lib/elf.c
===================================================================
--- kernel/generic/src/lib/elf.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ kernel/generic/src/lib/elf.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -163,4 +163,6 @@
 	case PT_LOAD:
 		return load_segment(entry, elf, as);
+	case PT_TLS:
+		break;
 	case PT_DYNAMIC:
 	case PT_INTERP:
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/Makefile	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -41,5 +41,4 @@
 	app/corecfg \
 	app/devctl \
-	app/dltest \
 	app/dnscfg \
 	app/dnsres \
@@ -167,4 +166,5 @@
 	drv/platform/icp
 
+
 ## Platform-specific hardware support
 #
@@ -210,4 +210,12 @@
 		drv/fb/amdm37x_dispc \
 		srv/hw/irc/icp-ic
+endif
+
+## Dynamic linking tests
+#
+ifeq ($(CONFIG_BUILD_SHARED_LIBS),y)
+	DIRS += \
+		app/dltest \
+		app/dltests
 endif
 
Index: uspace/Makefile.common
===================================================================
--- uspace/Makefile.common	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/Makefile.common	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -191,5 +191,4 @@
 ifeq ($(STATIC_BUILD),y)
 	BASE_LIBS = $(LIBC_PREFIX)/libc.a $(LIBSOFTINT_PREFIX)/libsoftint.a
-	LINKER_SCRIPT ?= $(LIBC_PREFIX)/arch/$(UARCH)/_link.ld
 	ifeq ($(MATH),y)
 		BASE_LIBS += $(LIBMATH_PREFIX)/libmath.a
@@ -197,9 +196,15 @@
 else
 	BASE_LIBS = $(LIBC_PREFIX)/libc.so.0 $(LIBSOFTINT_PREFIX)/libsoftint.so.0
-	LFLAGS += -Bdynamic
-	LINKER_SCRIPT ?= $(LIBC_PREFIX)/arch/$(UARCH)/_link-dlexe.ld
+	LINK_DYNAMIC = y
 	ifeq ($(MATH),y)
 		BASE_LIBS += $(LIBMATH_PREFIX)/libmath.so.0
 	endif
+endif
+
+ifeq ($(LINK_DYNAMIC),y)
+	LFLAGS += -Bdynamic
+	LINKER_SCRIPT ?= $(LIBC_PREFIX)/arch/$(UARCH)/_link-dlexe.ld
+else
+	LINKER_SCRIPT ?= $(LIBC_PREFIX)/arch/$(UARCH)/_link.ld
 endif
 
Index: uspace/app/dltest/Makefile
===================================================================
--- uspace/app/dltest/Makefile	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/app/dltest/Makefile	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -28,5 +28,9 @@
 
 USPACE_PREFIX = ../..
-EXTRA_CFLAGS = -I$(LIBDLTEST_PREFIX)
+EXTRA_CFLAGS = -I$(LIBDLTEST_PREFIX) -DDLTEST_LINKED
+LIBS = $(LIBDLTEST_PREFIX)/libdltest.so.0.0
+# Need a dynamic link, but possibly still use static libc
+LINK_DYNAMIC = y
+
 BINARY = dltest
 
Index: uspace/app/dltest/dltest.c
===================================================================
--- uspace/app/dltest/dltest.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/app/dltest/dltest.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -43,5 +43,8 @@
 
 /** libdltest library handle */
-void *handle;
+static void *handle;
+
+/** If true, do not run dlfcn tests */
+static bool no_dlfcn = false;
 
 /** Test dlsym() function */
@@ -93,4 +96,5 @@
 {
 	int (*p_dl_get_private_var)(void);
+	int *(*p_dl_get_private_var_addr)(void);
 	int val;
 
@@ -103,8 +107,16 @@
 	}
 
+	p_dl_get_private_var_addr = dlsym(handle, "dl_get_private_var_addr");
+	if (p_dl_get_private_var_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
 	val = p_dl_get_private_var();
 
 	printf("Got %d, expected %d... ", val, dl_private_var_val);
 	if (val != dl_private_var_val) {
+		printf("dl_get_private_var_addr -> %p\n",
+		    p_dl_get_private_var_addr());
 		printf("FAILED\n");
 		return false;
@@ -121,4 +133,5 @@
 {
 	int (*p_dl_get_private_uvar)(void);
+	int *(*p_dl_get_private_uvar_addr)(void);
 	int val;
 
@@ -131,8 +144,16 @@
 	}
 
+	p_dl_get_private_uvar_addr = dlsym(handle, "dl_get_private_uvar_addr");
+	if (p_dl_get_private_uvar_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
 	val = p_dl_get_private_uvar();
 
 	printf("Got %d, expected %d... ", val, 0);
 	if (val != 0) {
+		printf("dl_get_private_uvar_addr -> %p\n",
+		    p_dl_get_private_uvar_addr());
 		printf("FAILED\n");
 		return false;
@@ -149,4 +170,5 @@
 {
 	int (*p_dl_get_public_var)(void);
+	int *(*p_dl_get_public_var_addr)(void);
 	int val;
 
@@ -159,8 +181,16 @@
 	}
 
+	p_dl_get_public_var_addr = dlsym(handle, "dl_get_public_var_addr");
+	if (p_dl_get_public_var_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
 	val = p_dl_get_public_var();
 
 	printf("Got %d, expected %d... ", val, dl_public_var_val);
 	if (val != dl_public_var_val) {
+		printf("dl_get_public_var_addr -> %p\n",
+		    p_dl_get_public_var_addr());
 		printf("FAILED\n");
 		return false;
@@ -177,4 +207,5 @@
 {
 	int (*p_dl_get_public_uvar)(void);
+	int *(*p_dl_get_public_uvar_addr)(void);
 	int val;
 
@@ -187,8 +218,16 @@
 	}
 
+	p_dl_get_public_uvar_addr = dlsym(handle, "dl_get_public_uvar_addr");
+	if (p_dl_get_public_uvar_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
 	val = p_dl_get_public_uvar();
 
 	printf("Got %d, expected %d... ", val, 0);
 	if (val != 0) {
+		printf("dl_get_public_uvar_addr -> %p\n",
+		    p_dl_get_public_uvar_addr());
 		printf("FAILED\n");
 		return false;
@@ -205,4 +244,5 @@
 {
 	int *p_dl_public_var;
+	int *(*p_dl_get_public_var_addr)(void);
 	int val;
 
@@ -215,8 +255,17 @@
 	}
 
+	p_dl_get_public_var_addr = dlsym(handle, "dl_get_public_var_addr");
+	if (p_dl_get_public_var_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
 	val = *p_dl_public_var;
 
 	printf("Got %d, expected %d... ", val, dl_public_var_val);
 	if (val != dl_public_var_val) {
+		printf("&dl_public_var = %p, "
+		    "dl_get_public_var_addr -> %p\n",
+		    p_dl_public_var, p_dl_get_public_var_addr());
 		printf("FAILED\n");
 		return false;
@@ -233,4 +282,5 @@
 {
 	int *p_dl_public_uvar;
+	int *(*p_dl_get_public_uvar_addr)(void);
 	int val;
 
@@ -243,21 +293,548 @@
 	}
 
+	p_dl_get_public_uvar_addr = dlsym(handle, "dl_get_public_uvar_addr");
+	if (p_dl_get_public_uvar_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
 	val = *p_dl_public_uvar;
 
 	printf("Got %d, expected %d... ", val, 0);
 	if (val != 0) {
-		printf("FAILED\n");
-		return false;
-	}
-
-	printf("Passed\n");
-	return true;
-}
-
-int main(int argc, char *argv[])
-{
-
-	printf("Dynamic linking test\n");
-
+		printf("&dl_public_uvar = %p, "
+		    "dl_get_public_uvar_addr -> %p\n",
+		    p_dl_public_uvar, p_dl_get_public_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+#ifndef STATIC_EXE
+
+/** Test calling a function that returns contents of a private initialized
+ * fibril-local variable.
+ */
+static bool test_dlfcn_dl_get_private_fib_var(void)
+{
+	int (*p_dl_get_private_fib_var)(void);
+	int *(*p_dl_get_private_fib_var_addr)(void);
+	int val;
+
+	printf("Call dlsym/dl_get_private_fib_var...\n");
+
+	p_dl_get_private_fib_var = dlsym(handle, "dl_get_private_fib_var");
+	if (p_dl_get_private_fib_var == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	p_dl_get_private_fib_var_addr = dlsym(handle, "dl_get_private_fib_var_addr");
+	if (p_dl_get_private_fib_var_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	val = p_dl_get_private_fib_var();
+
+	printf("Got %d, expected %d... ", val, dl_private_fib_var_val);
+	if (val != dl_private_fib_var_val) {
+		printf("dl_get_private_fib_var_addr -> %p\n",
+		    p_dl_get_private_fib_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test calling a function that returns contents of a private uninitialized
+ * fibril-local variable.
+ */
+static bool test_dlfcn_dl_get_private_fib_uvar(void)
+{
+	int (*p_dl_get_private_fib_uvar)(void);
+	int *(*p_dl_get_private_fib_uvar_addr)(void);
+	int val;
+
+	printf("Call dlsym/dl_get_private_fib_uvar...\n");
+
+	p_dl_get_private_fib_uvar = dlsym(handle, "dl_get_private_fib_uvar");
+	if (p_dl_get_private_fib_uvar == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	p_dl_get_private_fib_uvar_addr = dlsym(handle, "dl_get_private_fib_uvar_addr");
+	if (p_dl_get_private_fib_uvar_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	val = p_dl_get_private_fib_uvar();
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("dl_get_private_fib_uvar_addr -> %p\n",
+		    p_dl_get_private_fib_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test calling a function that returns the contents of a public initialized
+ * fibril-local variable.
+ */
+static bool test_dlfcn_dl_get_public_fib_var(void)
+{
+	int (*p_dl_get_public_fib_var)(void);
+	int *(*p_dl_get_public_fib_var_addr)(void);
+	int val;
+
+	printf("Call dlsym/dl_get_public_fib_var...\n");
+
+	p_dl_get_public_fib_var = dlsym(handle, "dl_get_public_fib_var");
+	if (p_dl_get_public_fib_var == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	p_dl_get_public_fib_var_addr = dlsym(handle, "dl_get_public_fib_var_addr");
+	if (p_dl_get_public_fib_var_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	val = p_dl_get_public_fib_var();
+
+	printf("Got %d, expected %d... ", val, dl_public_fib_var_val);
+	if (val != dl_public_fib_var_val) {
+		printf("dl_get_public_fib_var_addr -> %p\n",
+		    p_dl_get_public_fib_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test calling a function that returns the contents of a public uninitialized
+ * fibril-local variable.
+ */
+static bool test_dlfcn_dl_get_public_fib_uvar(void)
+{
+	int (*p_dl_get_public_fib_uvar)(void);
+	int *(*p_dl_get_public_fib_uvar_addr)(void);
+	int val;
+
+	printf("Call dlsym/dl_get_public_fib_uvar...\n");
+
+	p_dl_get_public_fib_uvar = dlsym(handle, "dl_get_public_fib_uvar");
+	if (p_dl_get_public_fib_uvar == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	p_dl_get_public_fib_uvar_addr = dlsym(handle, "dl_get_public_fib_uvar_addr");
+	if (p_dl_get_public_fib_uvar_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	val = p_dl_get_public_fib_uvar();
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("dl_get_public_fib_uvar_addr -> %p\n",
+		    p_dl_get_public_fib_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly reading a public initialized fibril-local variable
+ * whose address was obtained using dlsym.
+ */
+static bool test_dlfcn_read_public_fib_var(void)
+{
+	int *p_dl_public_fib_var;
+	int *(*p_dl_get_public_fib_var_addr)(void);
+	int val;
+
+	printf("Read dlsym/dl_public_fib_var...\n");
+
+	p_dl_public_fib_var = dlsym(handle, "dl_public_fib_var");
+	if (p_dl_public_fib_var == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	p_dl_get_public_fib_var_addr = dlsym(handle, "dl_get_public_fib_var_addr");
+	if (p_dl_get_public_fib_var_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	val = *p_dl_public_fib_var;
+
+	printf("Got %d, expected %d... ", val, dl_public_fib_var_val);
+	if (val != dl_public_fib_var_val) {
+		printf("&dl_public_fib_var = %p, "
+		    "dl_get_public_fib_var_addr -> %p\n",
+		    p_dl_public_fib_var, p_dl_get_public_fib_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly reading a public uninitialized fibril-local variable
+ * whose address was obtained using dlsym.
+ */
+static bool test_dlfcn_read_public_fib_uvar(void)
+{
+	int *p_dl_public_fib_uvar;
+	int *(*p_dl_get_public_fib_uvar_addr)(void);
+	int val;
+
+	printf("Read dlsym/dl_public_fib_uvar...\n");
+
+	p_dl_public_fib_uvar = dlsym(handle, "dl_public_fib_uvar");
+	if (p_dl_public_fib_uvar == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	p_dl_get_public_fib_uvar_addr = dlsym(handle, "dl_get_public_fib_uvar_addr");
+	if (p_dl_get_public_fib_uvar_addr == NULL) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	val = *p_dl_public_fib_uvar;
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("&dl_public_fib_uvar = %p, "
+		    "dl_get_public_fib_uvar_addr -> %p\n",
+		    p_dl_public_fib_uvar, p_dl_get_public_fib_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+#endif /* STATIC_EXE */
+
+#ifdef DLTEST_LINKED
+
+/** Test directly calling function that returns a constant */
+static bool test_lnk_dl_get_constant(void)
+{
+	int val;
+
+	printf("Call linked dl_get_constant...\n");
+
+	val = dl_get_constant();
+
+	printf("Got %d, expected %d... ", val, dl_constant);
+	if (val != dl_constant) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test dircetly calling a function that returns contents of a private
+ * initialized variable.
+ */
+static bool test_lnk_dl_get_private_var(void)
+{
+	int val;
+
+	printf("Call linked dl_get_private_var...\n");
+
+	val = dl_get_private_var();
+
+	printf("Got %d, expected %d... ", val, dl_private_var_val);
+	if (val != dl_private_var_val) {
+		printf("dl_get_private_var_addr -> %p\n",
+		    dl_get_private_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test dircetly calling a function that returns contents of a private
+ * uninitialized variable.
+ */
+static bool test_lnk_dl_get_private_uvar(void)
+{
+	int val;
+
+	printf("Call linked dl_get_private_uvar...\n");
+
+	val = dl_get_private_uvar();
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("dl_get_private_uvar_addr -> %p\n",
+		    dl_get_private_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly calling a function that returns the contents of a public
+ * initialized variable.
+ */
+static bool test_lnk_dl_get_public_var(void)
+{
+	int val;
+
+	printf("Call linked dl_get_public_var...\n");
+
+	val = dl_get_public_var();
+
+	printf("Got %d, expected %d... ", val, dl_public_var_val);
+	if (val != dl_public_var_val) {
+		printf("dl_get_public_var_addr -> %p\n",
+		    dl_get_public_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly calling a function that returns the contents of a public
+ * uninitialized variable.
+ */
+static bool test_lnk_dl_get_public_uvar(void)
+{
+	int val;
+
+	printf("Call linked dl_get_public_uvar...\n");
+
+	val = dl_get_public_uvar();
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("dl_get_public_uvar_addr -> %p\n",
+		    dl_get_public_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly reading a public initialized variable. */
+static bool test_lnk_read_public_var(void)
+{
+	int val;
+
+	printf("Read linked dl_public_var...\n");
+
+	val = dl_public_var;
+
+	printf("Got %d, expected %d... ", val, dl_public_var_val);
+	if (val != dl_public_var_val) {
+		printf("&dl_public_var = %p, dl_get_public_var_addr -> %p\n",
+		    &dl_public_var, dl_get_public_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly reading a public uninitialized variable. */
+static bool test_lnk_read_public_uvar(void)
+{
+	int val;
+
+	printf("Read linked dl_public_uvar...\n");
+
+	val = dl_public_uvar;
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("&dl_public_uvar = %p, dl_get_public_uvar_addr -> %p\n",
+		    &dl_public_uvar, dl_get_public_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test dircetly calling a function that returns contents of a private
+ * initialized fibril-local variable.
+ */
+static bool test_lnk_dl_get_private_fib_var(void)
+{
+	int val;
+
+	printf("Call linked dl_get_private_fib_var...\n");
+
+	val = dl_get_private_fib_var();
+
+	printf("Got %d, expected %d... ", val, dl_private_fib_var_val);
+	if (val != dl_private_fib_var_val) {
+		printf("dl_get_private_fib_var_addr -> %p\n",
+		    dl_get_private_fib_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test dircetly calling a function that returns contents of a private
+ * uninitialized fibril-local variable.
+ */
+static bool test_lnk_dl_get_private_fib_uvar(void)
+{
+	int val;
+
+	printf("Call linked dl_get_private_fib_uvar...\n");
+
+	val = dl_get_private_fib_uvar();
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("dl_get_private_fib_uvar_addr -> %p\n",
+		    dl_get_private_fib_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly calling a function that returns the contents of a public
+ * initialized fibril-local variable.
+ */
+static bool test_lnk_dl_get_public_fib_var(void)
+{
+	int val;
+
+	printf("Call linked dl_get_public_fib_var...\n");
+
+	val = dl_get_public_fib_var();
+
+	printf("Got %d, expected %d... ", val, dl_public_fib_var_val);
+	if (val != dl_public_fib_var_val) {
+		printf("dl_get_public_fib_var_addr -> %p\n",
+		    dl_get_public_fib_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly calling a function that returns the contents of a public
+ * uninitialized fibril-local variable.
+ */
+static bool test_lnk_dl_get_public_fib_uvar(void)
+{
+	int val;
+
+	printf("Call linked dl_get_public_fib_uvar...\n");
+
+	val = dl_get_public_fib_uvar();
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("dl_get_public_fib_uvar_addr -> %p\n",
+		    dl_get_public_fib_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly reading a public initialized fibril-local variable. */
+static bool test_lnk_read_public_fib_var(void)
+{
+	int val;
+
+	printf("Read linked dl_public_fib_var...\n");
+
+	val = dl_public_fib_var;
+
+	printf("Got %d, expected %d... ", val, dl_public_fib_var_val);
+	if (val != dl_public_fib_var_val) {
+		printf("&dl_public_fib_var = %p, "
+		    "dl_get_public_fib_var_addr -> %p\n",
+		    &dl_public_fib_var, dl_get_public_fib_var_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+/** Test directly reading a public uninitialized fibril-local variable. */
+static bool test_lnk_read_public_fib_uvar(void)
+{
+	int val;
+
+	printf("Read linked dl_public_fib_uvar...\n");
+
+	val = dl_public_fib_uvar;
+
+	printf("Got %d, expected %d... ", val, 0);
+	if (val != 0) {
+		printf("&dl_public_fib_uvar = %p, "
+		    "dl_get_public_fib_uvar_addr -> %p\n",
+		    &dl_public_fib_uvar, dl_get_public_fib_uvar_addr());
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
+#endif /* DLTEST_LINKED */
+
+static int test_dlfcn(void)
+{
 	printf("dlopen()... ");
 	handle = dlopen("libdltest.so.0", 0);
@@ -293,8 +870,113 @@
 		return 1;
 
+#ifndef STATIC_EXE
+	if (!test_dlfcn_dl_get_private_fib_var())
+		return 1;
+
+	if (!test_dlfcn_dl_get_private_fib_uvar())
+		return 1;
+
+	if (!test_dlfcn_dl_get_public_fib_var())
+		return 1;
+
+	if (!test_dlfcn_dl_get_public_fib_uvar())
+		return 1;
+
+	if (!test_dlfcn_read_public_fib_var())
+		return 1;
+
+	if (!test_dlfcn_read_public_fib_uvar())
+		return 1;
+#endif /* STATIC_EXE */
+
 //	printf("dlclose()... ");
 //	dlclose(handle);
 //	printf("Passed\n");
 
+	return 0;
+}
+
+#ifdef DLTEST_LINKED
+
+static int test_lnk(void)
+{
+	if (!test_lnk_dl_get_constant())
+		return 1;
+
+	if (!test_lnk_dl_get_private_var())
+		return 1;
+
+	if (!test_lnk_dl_get_private_uvar())
+		return 1;
+
+	if (!test_lnk_dl_get_public_var())
+		return 1;
+
+	if (!test_lnk_dl_get_public_uvar())
+		return 1;
+
+	if (!test_lnk_read_public_var())
+		return 1;
+
+	if (!test_lnk_read_public_uvar())
+		return 1;
+
+	if (!test_lnk_dl_get_private_fib_var())
+		return 1;
+
+	if (!test_lnk_dl_get_private_fib_uvar())
+		return 1;
+
+	if (!test_lnk_dl_get_public_fib_var())
+		return 1;
+
+	if (!test_lnk_dl_get_public_fib_uvar())
+		return 1;
+
+	if (!test_lnk_read_public_fib_var())
+		return 1;
+
+	if (!test_lnk_read_public_fib_uvar())
+		return 1;
+
+	return 0;
+}
+
+#endif /* DLTEST_LINKED */
+
+static void print_syntax(void)
+{
+	fprintf(stderr, "syntax: dltest [-n]\n");
+	fprintf(stderr, "\t-n Do not run dlfcn tests\n");
+}
+
+int main(int argc, char *argv[])
+{
+	printf("Dynamic linking test\n");
+
+	if (argc > 1) {
+		if (argc > 2) {
+			print_syntax();
+			return 1;
+		}
+
+		if (str_cmp(argv[1], "-n") == 0) {
+			no_dlfcn = true;
+		} else {
+			print_syntax();
+			return 1;
+		}
+	}
+
+	if (!no_dlfcn) {
+		if (test_dlfcn() != 0)
+			return 1;
+	}
+
+#ifdef DLTEST_LINKED
+	if (test_lnk() != 0)
+		return 1;
+#endif
+
 	printf("All passed.\n");
 	return 0;
Index: uspace/app/dltests/Makefile
===================================================================
--- uspace/app/dltests/Makefile	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
+++ uspace/app/dltests/Makefile	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2016 Jiri Svoboda
+# 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.
+#
+
+USPACE_PREFIX = ../..
+EXTRA_CFLAGS = -I$(LIBDLTEST_PREFIX)
+BINARY = dltests
+
+SOURCES = \
+	dltests.c
+
+include $(USPACE_PREFIX)/Makefile.common
+
+ifeq ($(STATIC_BUILD), y)
+	EXTRA_CFLAGS += -DSTATIC_EXE
+endif
Index: uspace/app/dltests/dltests.c
===================================================================
--- uspace/app/dltests/dltests.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
+++ uspace/app/dltests/dltests.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -0,0 +1,1 @@
+../dltest/dltest.c
Index: uspace/lib/c/arch/ia32/_link.ld.in
===================================================================
--- uspace/lib/c/arch/ia32/_link.ld.in	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/arch/ia32/_link.ld.in	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -12,4 +12,5 @@
 #endif
 	data PT_LOAD FLAGS(6);
+	tls PT_TLS;
 #if defined(SHLIB) || defined(DLEXE)
 	dynamic PT_DYNAMIC;
@@ -95,15 +96,25 @@
 #endif
 	
+	.tdata : {
 #ifndef DLEXE
-	.tdata : {
 		_tdata_start = .;
+#endif
 		*(.tdata);
 		*(.gnu.linkonce.tb.*);
+#ifndef DLEXE
 		_tdata_end = .;
+#endif
+	} :data :tls
+	.tbss : {
+#ifndef DLEXE
 		_tbss_start = .;
+#endif
 		*(.tbss);
+#ifndef DLEXE
 		_tbss_end = .;
-	} :data
+#endif
+	} :data :tls
 	
+#ifndef DLEXE
 	_tls_alignment = ALIGNOF(.tdata);
 #endif
Index: uspace/lib/c/arch/ia32/include/libarch/rtld/elf_dyn.h
===================================================================
--- uspace/lib/c/arch/ia32/include/libarch/rtld/elf_dyn.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/arch/ia32/include/libarch/rtld/elf_dyn.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -36,5 +36,5 @@
 #define LIBC_ia32_RTLD_ELF_DYN_H_
 
-/* 
+/*
  * ia32 dynamic relocation types
  */
@@ -47,5 +47,7 @@
 #define R_386_RELATIVE	8
 
+#define R_386_TLS_TPOFF    14
 #define R_386_TLS_DTPMOD32 35
+#define R_386_TLS_DTPOFF32 36
 
 #endif
Index: uspace/lib/c/arch/ia32/include/libarch/tls.h
===================================================================
--- uspace/lib/c/arch/ia32/include/libarch/tls.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/arch/ia32/include/libarch/tls.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -43,4 +43,5 @@
 	void *self;
 	void *fibril_data;
+	void **dtv;
 } tcb_t;
 
Index: uspace/lib/c/arch/ia32/src/rtld/reloc.c
===================================================================
--- uspace/lib/c/arch/ia32/src/rtld/reloc.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/arch/ia32/src/rtld/reloc.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -101,9 +101,9 @@
 			sym_def = symbol_def_find(str_tab + sym->st_name,
 			    m, ssf_none, &dest);
-//			DPRINTF("dest name: '%s'\n", dest->dyn.soname);
+			DPRINTF("dest name: '%s'\n", dest->dyn.soname);
 //			DPRINTF("dest bias: 0x%x\n", dest->bias);
 			if (sym_def) {
 				sym_addr = (uint32_t)
-				    symbol_get_addr(sym_def, dest);
+				    symbol_get_addr(sym_def, dest, NULL);
 //				DPRINTF("symbol definition found, addr=0x%x\n", sym_addr);
 			} else {
@@ -115,4 +115,10 @@
 			sym_addr = 0;
 			sym_def = NULL;
+
+			/*
+			 * DTPMOD with null st_name should return the index
+			 * of the current module.
+			 */
+			dest = m;
 		}
 
@@ -148,5 +154,5 @@
 			if (sym_def) {
 				sym_addr = (uint32_t)
-				    symbol_get_addr(sym_def, dest);
+				    symbol_get_addr(sym_def, dest, NULL);
 			} else {
 				printf("Source definition of '%s' not found.\n",
@@ -171,10 +177,17 @@
 			break;
 
+		case R_386_TLS_TPOFF:
+			DPRINTF("fixup R_386_TLS_TPOFF\n");
+			*r_ptr = (dest->ioffs + sym_def->st_value) - dest->rtld->tls_size;
+			break;
+
+		case R_386_TLS_DTPOFF32:
+			DPRINTF("fixup R_386_TLS_DTPOFF32\n");
+			*r_ptr = sym_def->st_value;
+			break;
+
 		case R_386_TLS_DTPMOD32:
-			/*
-			 * We can ignore this as long as the only module
-			 * with TLS variables is libc.so.
-			 */
-			DPRINTF("Ignoring R_386_TLS_DTPMOD32\n");
+			DPRINTF("fixup R_386_TLS_DTPMOD32\n");
+			*r_ptr = dest->id;
 			break;
 
Index: uspace/lib/c/arch/ia32/src/tls.c
===================================================================
--- uspace/lib/c/arch/ia32/src/tls.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/arch/ia32/src/tls.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2006 Ondrej Palkovsky
+ * Copyright (c) 2016 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,4 +40,8 @@
 #include <align.h>
 
+#ifdef CONFIG_RTLD
+#include <rtld/rtld.h>
+#endif
+
 tcb_t *tls_alloc_arch(void **data, size_t size)
 {
@@ -64,13 +69,14 @@
     *___tls_get_addr(tls_index *ti)
 {
-	size_t tls_size;
 	uint8_t *tls;
 
-	/* Calculate size of TLS block */
-	tls_size = ALIGN_UP(&_tbss_end - &_tdata_start, &_tls_alignment);
-
-	/* The TLS block is just before TCB */
-	tls = (uint8_t *)__tcb_get() - tls_size;
-
+#ifdef CONFIG_RTLD
+	if (runtime_env != NULL) {
+		return rtld_tls_get_addr(runtime_env, __tcb_get(),
+		    ti->ti_module, ti->ti_offset);
+	}
+#endif
+	/* Get address of static TLS block */
+	tls = tls_get();
 	return tls + ti->ti_offset;
 }
Index: uspace/lib/c/generic/dlfcn.c
===================================================================
--- uspace/lib/c/generic/dlfcn.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/generic/dlfcn.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -49,23 +49,10 @@
 	module_t *m;
 
-	if (runtime_env == NULL) {
-		printf("Dynamic linker not set up -- initializing.\n");
-		rtld_init_static();
-	}
-
-	printf("dlopen(\"%s\", %d)\n", path, flag);
-
-	printf("module_find('%s')\n", path);
 	m = module_find(runtime_env, path);
 	if (m == NULL) {
-		printf("NULL. module_load('%s')\n", path);
 		m = module_load(runtime_env, path, mlf_local);
-		printf("module_load_deps(m)\n");
 		module_load_deps(m, mlf_local);
 		/* Now relocate. */
-		printf("module_process_relocs(m)\n");
 		module_process_relocs(m);
-	} else {
-		printf("not NULL\n");
 	}
 
@@ -81,8 +68,7 @@
 	module_t *sm;
 
-	printf("dlsym(0x%lx, \"%s\")\n", (long)mod, sym_name);
 	sd = symbol_bfs_find(sym_name, (module_t *) mod, &sm);
 	if (sd != NULL) {
-		return symbol_get_addr(sd, sm);
+		return symbol_get_addr(sd, sm, __tcb_get());
 	}
 
Index: uspace/lib/c/generic/elf/elf_mod.c
===================================================================
--- uspace/lib/c/generic/elf/elf_mod.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/generic/elf/elf_mod.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -248,6 +248,22 @@
 }
 
+/** Process TLS program header.
+ *
+ * @param elf  Pointer to loader state buffer.
+ * @param hdr  TLS program header
+ * @param info Place to store TLS info
+ */
+static void tls_program_header(elf_ld_t *elf, elf_segment_header_t *hdr,
+    elf_tls_info_t *info)
+{
+	info->tdata = (void *)((uint8_t *)hdr->p_vaddr + elf->bias);
+	info->tdata_size = hdr->p_filesz;
+	info->tbss_size = hdr->p_memsz - hdr->p_filesz;
+	info->tls_align = hdr->p_align;
+}
+
 /** Process segment header.
  *
+ * @param elf   Pointer to loader state buffer.
  * @param entry	Segment header.
  *
@@ -277,4 +293,10 @@
 	case 0x70000000:
 		/* FIXME: MIPS reginfo */
+		break;
+	case PT_TLS:
+		/* Parse TLS program header */
+		tls_program_header(elf, entry, &elf->info->tls);
+		DPRINTF("TLS header found at %p\n",
+		    (void *)((uint8_t *)entry->p_vaddr + elf->bias));
 		break;
 	case PT_SHLIB:
Index: uspace/lib/c/generic/libc.c
===================================================================
--- uspace/lib/c/generic/libc.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/generic/libc.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -41,4 +41,5 @@
  */
 
+#include <errno.h>
 #include <libc.h>
 #include <stdlib.h>
@@ -68,4 +69,16 @@
 	__malloc_init();
 	
+	/* Save the PCB pointer */
+	__pcb = (pcb_t *) pcb_ptr;
+	
+#ifdef CONFIG_RTLD
+	if (__pcb != NULL && __pcb->rtld_runtime != NULL) {
+		runtime_env = (rtld_t *) __pcb->rtld_runtime;
+	} else {
+		if (rtld_init_static() != EOK)
+			abort();
+	}
+#endif
+	
 	fibril_t *fibril = fibril_setup();
 	if (fibril == NULL)
@@ -74,6 +87,4 @@
 	__tcb_set(fibril->tcb);
 	
-	/* Save the PCB pointer */
-	__pcb = (pcb_t *) pcb_ptr;
 	
 #ifdef FUTEX_UPGRADABLE
@@ -89,9 +100,4 @@
 	char **argv;
 	
-#ifdef CONFIG_RTLD
-	if (__pcb != NULL && __pcb->rtld_runtime != NULL) {
-		runtime_env = (rtld_t *) __pcb->rtld_runtime;
-	}
-#endif
 	/*
 	 * Get command line arguments and initialize
Index: uspace/lib/c/generic/rtld/module.c
===================================================================
--- uspace/lib/c/generic/rtld/module.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/generic/rtld/module.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -37,4 +37,5 @@
 #include <adt/list.h>
 #include <elf/elf_load.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <loader/pcb.h>
@@ -48,4 +49,37 @@
 #include <rtld/rtld_arch.h>
 #include <rtld/module.h>
+
+/** Create module for static executable.
+ *
+ * @param rtld Run-time dynamic linker
+ * @param rmodule Place to store pointer to new module or @c NULL
+ * @return EOK on success, ENOMEM if out of memory
+ */
+int module_create_static_exec(rtld_t *rtld, module_t **rmodule)
+{
+	module_t *module;
+
+	module = calloc(1, sizeof(module_t));
+	if (module == NULL)
+		return ENOMEM;
+
+	module->id = rtld_get_next_id(rtld);
+	module->dyn.soname = "[program]";
+
+	module->rtld = rtld;
+	module->exec = true;
+	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;
+
+	list_append(&module->modules_link, &rtld->modules);
+
+	if (rmodule != NULL)
+		*rmodule = module;
+	return EOK;
+}
 
 /** (Eagerly) process all relocation tables in a module.
@@ -135,5 +169,5 @@
 
 	m = calloc(1, sizeof(module_t));
-	if (!m) {
+	if (m == NULL) {
 		printf("malloc failed\n");
 		exit(1);
@@ -141,4 +175,6 @@
 
 	m->rtld = rtld;
+	m->id = rtld_get_next_id(rtld);
+
 	if ((flags & mlf_local) != 0)
 		m->local = true;
@@ -181,4 +217,13 @@
 	/* Insert into the list of loaded modules */
 	list_append(&m->modules_link, &rtld->modules);
+
+	/* Copy TLS info */
+	m->tdata = info.tls.tdata;
+	m->tdata_size = info.tls.tdata_size;
+	m->tbss_size = info.tls.tbss_size;
+	m->tls_align = info.tls.tls_align;
+
+	DPRINTF("tdata at %p size %zu, tbss size %zu\n",
+	    m->tdata, m->tdata_size, m->tbss_size);
 
 	return m;
@@ -243,4 +288,15 @@
 }
 
+/** Find module structure by ID. */
+module_t *module_by_id(rtld_t *rtld, unsigned long id)
+{
+	list_foreach(rtld->modules, modules_link, module_t, m) {
+		if (m->id == id)
+			return m;
+	}
+
+	return NULL;
+}
+
 /** Process relocations in modules.
  *
@@ -260,4 +316,28 @@
 }
 
+void modules_process_tls(rtld_t *rtld)
+{
+#ifdef CONFIG_TLS_VARIANT_1
+	list_foreach(rtld->modules, modules_link, module_t, m) {
+		m->ioffs = rtld->tls_size;
+		list_append(&m->imodules_link, &rtmd->imodules);
+		rtld->tls_size += m->tdata_size + m->tbss_size;
+	}
+#else /* CONFIG_TLS_VARIANT_2 */
+	size_t offs;
+
+	list_foreach(rtld->modules, modules_link, module_t, m) {
+		rtld->tls_size += m->tdata_size + m->tbss_size;
+	}
+
+	offs = 0;
+	list_foreach(rtld->modules, modules_link, module_t, m) {
+		offs += m->tdata_size + m->tbss_size;
+		m->ioffs = rtld->tls_size - offs;
+		list_append(&m->imodules_link, &rtld->imodules);
+	}
+#endif
+}
+
 /** Clear BFS tags of all modules.
  */
Index: uspace/lib/c/generic/rtld/rtld.c
===================================================================
--- uspace/lib/c/generic/rtld/rtld.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/generic/rtld/rtld.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -43,13 +43,24 @@
 rtld_t *runtime_env;
 static rtld_t rt_env_static;
-static module_t prog_mod;
 
 /** Initialize the runtime linker for use in a statically-linked executable. */
-void rtld_init_static(void)
-{
+int rtld_init_static(void)
+{
+	int rc;
+
 	runtime_env = &rt_env_static;
 	list_initialize(&runtime_env->modules);
+	list_initialize(&runtime_env->imodules);
 	runtime_env->next_bias = 0x2000000;
 	runtime_env->program = NULL;
+	runtime_env->next_id = 1;
+
+	rc = module_create_static_exec(runtime_env, NULL);
+	if (rc != EOK)
+		return rc;
+
+	modules_process_tls(runtime_env);
+
+	return EOK;
 }
 
@@ -62,4 +73,5 @@
 {
 	rtld_t *env;
+	module_t *prog;
 
 	DPRINTF("Load dynamically linked program.\n");
@@ -70,4 +82,12 @@
 		return ENOMEM;
 
+	env->next_id = 1;
+
+	prog = calloc(1, sizeof(module_t));
+	if (prog == NULL) {
+		free(env);
+		return ENOMEM;
+	}
+
 	/*
 	 * First we need to process dynamic sections of the executable
@@ -76,17 +96,27 @@
 
 	DPRINTF("Parse program .dynamic section at %p\n", p_info->dynamic);
-	dynamic_parse(p_info->dynamic, 0, &prog_mod.dyn);
-	prog_mod.bias = 0;
-	prog_mod.dyn.soname = "[program]";
-	prog_mod.rtld = env;
-	prog_mod.exec = true;
-	prog_mod.local = false;
+	dynamic_parse(p_info->dynamic, 0, &prog->dyn);
+	prog->bias = 0;
+	prog->dyn.soname = "[program]";
+	prog->rtld = env;
+	prog->id = rtld_get_next_id(env);
+	prog->exec = true;
+	prog->local = false;
+
+	prog->tdata = p_info->tls.tdata;
+	prog->tdata_size = p_info->tls.tdata_size;
+	prog->tbss_size = p_info->tls.tbss_size;
+	prog->tls_align = p_info->tls.tls_align;
+
+	DPRINTF("prog tdata at %p size %zu, tbss size %zu\n",
+	    prog->tdata, prog->tdata_size, prog->tbss_size);
 
 	/* Initialize list of loaded modules */
 	list_initialize(&env->modules);
-	list_append(&prog_mod.modules_link, &env->modules);
+	list_initialize(&env->imodules);
+	list_append(&prog->modules_link, &env->modules);
 
 	/* Pointer to program module. Used as root of the module graph. */
-	env->program = &prog_mod;
+	env->program = prog;
 
 	/* Work around non-existent memory space allocation. */
@@ -98,5 +128,8 @@
 
 	DPRINTF("Load all program dependencies\n");
-	module_load_deps(&prog_mod, 0);
+	module_load_deps(prog, 0);
+
+	/* Compute static TLS size */
+	modules_process_tls(env);
 
 	/*
@@ -106,5 +139,5 @@
 	/* Process relocations in all modules */
 	DPRINTF("Relocate all modules\n");
-	modules_process_relocs(env, &prog_mod);
+	modules_process_relocs(env, prog);
 
 	*rre = env;
@@ -112,4 +145,130 @@
 }
 
+/** Create TLS (Thread Local Storage) data structures.
+ *
+ * @return Pointer to TCB.
+ */
+tcb_t *rtld_tls_make(rtld_t *rtld)
+{
+	void *data;
+	tcb_t *tcb;
+	size_t offset;
+	void **dtv;
+	size_t nmods;
+	size_t i;
+
+	tcb = tls_alloc_arch(&data, rtld->tls_size);
+	if (tcb == NULL)
+		return NULL;
+
+	/** Allocate dynamic thread vector */
+	nmods = list_count(&rtld->imodules);
+	dtv = malloc((nmods + 1) * sizeof(void *));
+	if (dtv == NULL) {
+		tls_free(tcb);
+		return NULL;
+	}
+
+	/*
+	 * We define generation number to be equal to vector length.
+	 * We start with a vector covering the initially loaded modules.
+	 */
+	DTV_GN(dtv) = nmods;
+
+	/*
+	 * Copy thread local data from the initialization images of initial
+	 * modules. Zero out thread-local uninitialized data.
+	 */
+
+#ifdef CONFIG_TLS_VARIANT_1
+	/*
+	 * Ascending addresses
+	 */
+	offset = 0; i = 1;
+	list_foreach(rtld->imodules, imodules_link, module_t, m) {
+		assert(i == m->id);
+		assert(offset + m->tdata_size + m->tbss_size <= rtld->tls_size);
+		dtv[i++] = data + offset;
+		memcpy(data + offset, m->tdata, m->tdata_size);
+		offset += m->tdata_size;
+		memset(data + offset, 0, m->tbss_size);
+		offset += m->tbss_size;
+	}
+#else /* CONFIG_TLS_VARIANT_2 */
+	/*
+	 * Descending addresses
+	 */
+	offset = 0; i = 1;
+	list_foreach(rtld->imodules, imodules_link, module_t, m) {
+		assert(i == m->id);
+		assert(offset + m->tdata_size + m->tbss_size <= rtld->tls_size);
+		offset += m->tbss_size;
+		memset(data + rtld->tls_size - offset, 0, m->tbss_size);
+		offset += m->tdata_size;
+		memcpy(data + rtld->tls_size - offset, m->tdata, m->tdata_size);
+		dtv[i++] = data + rtld->tls_size - offset;
+	}
+#endif
+
+	tcb->dtv = dtv;
+	return tcb;
+}
+
+unsigned long rtld_get_next_id(rtld_t *rtld)
+{
+	return rtld->next_id++;
+}
+
+/** Get address of thread-local variable.
+ *
+ * @param rtld RTLD instance
+ * @param tcb TCB of the thread whose instance to return
+ * @param mod_id Module ID
+ * @param offset Offset within TLS block of the module
+ *
+ * @return Address of thread-local variable
+ */
+void *rtld_tls_get_addr(rtld_t *rtld, tcb_t *tcb, unsigned long mod_id,
+    unsigned long offset)
+{
+	module_t *m;
+	size_t dtv_len;
+	void *tls_block;
+
+	dtv_len = DTV_GN(tcb->dtv);
+	if (dtv_len < mod_id) {
+		/* Vector is short */
+
+		tcb->dtv = realloc(tcb->dtv, (1 + mod_id) * sizeof(void *));
+		/* XXX This can fail if OOM */
+		assert(tcb->dtv != NULL);
+		/* Zero out new part of vector */
+		memset(tcb->dtv + (1 + dtv_len), 0, (mod_id - dtv_len) *
+		    sizeof(void *));
+	}
+
+	if (tcb->dtv[mod_id] == NULL) {
+		/* TLS block is not allocated */
+
+		m = module_by_id(rtld, mod_id);
+		assert(m != NULL);
+		/* Should not be initial module, those have TLS pre-allocated */
+		assert(!link_used(&m->imodules_link));
+
+		tls_block = malloc(m->tdata_size + m->tbss_size);
+		/* XXX This can fail if OOM */
+		assert(tls_block != NULL);
+
+		/* Copy tdata */
+		memcpy(tls_block, m->tdata, m->tdata_size);
+		/* Zero out tbss */
+		memset(tls_block + m->tdata_size, 0, m->tbss_size);
+
+		tcb->dtv[mod_id] = tls_block;
+	}
+
+	return (uint8_t *)(tcb->dtv[mod_id]) + offset;
+}
+
 /** @}
  */
Index: uspace/lib/c/generic/rtld/symbol.c
===================================================================
--- uspace/lib/c/generic/rtld/symbol.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/generic/rtld/symbol.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -249,7 +249,21 @@
 }
 
-void *symbol_get_addr(elf_symbol_t *sym, module_t *m)
-{
-	if (sym->st_shndx == SHN_ABS) {
+/** Get symbol address.
+ *
+ * @param sym Symbol
+ * @param m Module contaning the symbol
+ * @param tcb TCB of the thread whose thread-local variable instance should
+ *            be returned. If @a tcb is @c NULL then @c NULL is returned for
+ *            thread-local variables.
+ *
+ * @return Symbol address
+ */
+void *symbol_get_addr(elf_symbol_t *sym, module_t *m, tcb_t *tcb)
+{
+	if (ELF_ST_TYPE(sym->st_info) == STT_TLS) {
+		if (tcb == NULL)
+			return NULL;
+		return rtld_tls_get_addr(m->rtld, tcb, m->id, sym->st_value);
+	} else if (sym->st_shndx == SHN_ABS) {
 		/* Do not add bias to absolute symbols */
 		return (void *) sym->st_value;
Index: uspace/lib/c/generic/tls.c
===================================================================
--- uspace/lib/c/generic/tls.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/generic/tls.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -34,18 +34,35 @@
  * Support for thread-local storage, as described in:
  * 	Drepper U.: ELF Handling For Thread-Local Storage, 2005
- *
- * Only static model is supported.
- */ 
+ */
 
 #include <tls.h>
 #include <malloc.h>
 #include <str.h>
-#include <align.h>
 #include <unistd.h>
 
+#ifdef CONFIG_RTLD
+#include <rtld/rtld.h>
+#endif
+
+size_t tls_get_size(void)
+{
+#ifdef CONFIG_RTLD
+	if (runtime_env != NULL)
+		return runtime_env->tls_size;
+#endif
+	return &_tbss_end - &_tdata_start;
+}
+
+/** Get address of static TLS block */
+void *tls_get(void)
+{
+#ifdef CONFIG_TLS_VARIANT_1
+	return (uint8_t *)__tcb_get() + sizeof(tcb_t);
+#else /* CONFIG_TLS_VARIANT_2 */
+	return (uint8_t *)__tcb_get() - tls_get_size();
+#endif
+}
+
 /** Create TLS (Thread Local Storage) data structures.
- *
- * The code requires, that sections .tdata and .tbss are adjacent. It may be
- * changed in the future.
  *
  * @return Pointer to TCB.
@@ -56,9 +73,13 @@
 	tcb_t *tcb;
 	size_t tls_size = &_tbss_end - &_tdata_start;
-	
+
+#ifdef CONFIG_RTLD
+	if (runtime_env != NULL)
+		return rtld_tls_make(runtime_env);
+#endif
 	tcb = tls_alloc_arch(&data, tls_size);
 	if (!tcb)
 		return NULL;
-	
+
 	/*
 	 * Copy thread local data from the initialization image.
@@ -76,6 +97,6 @@
 void tls_free(tcb_t *tcb)
 {
-	size_t tls_size = &_tbss_end - &_tdata_start;
-	tls_free_arch(tcb, tls_size);
+	free(tcb->dtv);
+	tls_free_arch(tcb, tls_get_size());
 }
 
@@ -89,12 +110,13 @@
 tcb_t *tls_alloc_variant_1(void **data, size_t size)
 {
-	tcb_t *result;
+	tcb_t *tcb;
 
-	result = malloc(sizeof(tcb_t) + size);
-	if (!result)
+	tcb = malloc(sizeof(tcb_t) + size);
+	if (!tcb)
 		return NULL;
-	*data = ((void *)result) + sizeof(tcb_t);
+	*data = ((void *)tcb) + sizeof(tcb_t);
+	tcb->dtv = NULL;
 
-	return result;
+	return tcb;
 }
 
@@ -121,11 +143,11 @@
 {
 	tcb_t *tcb;
-	
-	size = ALIGN_UP(size, &_tls_alignment);
-	*data = memalign((uintptr_t) &_tls_alignment, sizeof(tcb_t) + size);
-	if (!*data)
+
+	*data = malloc(sizeof(tcb_t) + size);
+	if (*data == NULL)
 		return NULL;
 	tcb = (tcb_t *) (*data + size);
 	tcb->self = tcb;
+	tcb->dtv = NULL;
 
 	return tcb;
@@ -139,5 +161,4 @@
 void tls_free_variant_2(tcb_t *tcb, size_t size)
 {
-	size = ALIGN_UP(size, &_tls_alignment);
 	void *start = ((void *) tcb) - size;
 	free(start);
Index: uspace/lib/c/include/elf/elf_mod.h
===================================================================
--- uspace/lib/c/include/elf/elf_mod.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/include/elf/elf_mod.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -58,4 +58,16 @@
 } eld_flags_t;
 
+/** TLS info for a module */
+typedef struct {
+	/** tdata section image */
+	void *tdata;
+	/** Size of tdata section image in bytes */
+	size_t tdata_size;
+	/** Size of tbss section */
+	size_t tbss_size;
+	/** Alignment of TLS initialization image */
+	size_t tls_align;
+} elf_tls_info_t;
+
 /**
  * Some data extracted from the headers are stored here
@@ -70,4 +82,7 @@
 	/** Pointer to the dynamic section */
 	void *dynamic;
+
+	/** TLS info */
+	elf_tls_info_t tls;
 } elf_finfo_t;
 
Index: uspace/lib/c/include/rtld/module.h
===================================================================
--- uspace/lib/c/include/rtld/module.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/include/rtld/module.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -42,10 +42,13 @@
 #include <types/rtld/rtld.h>
 
+extern int module_create_static_exec(rtld_t *, module_t **);
 extern void module_process_relocs(module_t *);
 extern module_t *module_find(rtld_t *, const char *);
 extern module_t *module_load(rtld_t *, const char *, mlflags_t);
 extern void module_load_deps(module_t *, mlflags_t);
+extern module_t *module_by_id(rtld_t *, unsigned long);
 
 extern void modules_process_relocs(rtld_t *, module_t *);
+extern void modules_process_tls(rtld_t *);
 extern void modules_untag(rtld_t *);
 
Index: uspace/lib/c/include/rtld/rtld.h
===================================================================
--- uspace/lib/c/include/rtld/rtld.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/include/rtld/rtld.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -41,10 +41,14 @@
 
 #include <rtld/dynamic.h>
+#include <tls.h>
 #include <types/rtld/rtld.h>
 
 extern rtld_t *runtime_env;
 
-extern void rtld_init_static(void);
+extern int rtld_init_static(void);
 extern int rtld_prog_process(elf_finfo_t *, rtld_t **);
+extern tcb_t *rtld_tls_make(rtld_t *);
+extern unsigned long rtld_get_next_id(rtld_t *);
+extern void *rtld_tls_get_addr(rtld_t *, tcb_t *, unsigned long, unsigned long);
 
 #endif
Index: uspace/lib/c/include/rtld/symbol.h
===================================================================
--- uspace/lib/c/include/rtld/symbol.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/include/rtld/symbol.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -38,4 +38,5 @@
 #include <elf/elf.h>
 #include <rtld/rtld.h>
+#include <tls.h>
 
 /** Symbol search flags */
@@ -50,5 +51,5 @@
 extern elf_symbol_t *symbol_def_find(const char *, module_t *,
     symbol_search_flags_t, module_t **);
-extern void *symbol_get_addr(elf_symbol_t *, module_t *);
+extern void *symbol_get_addr(elf_symbol_t *, module_t *, tcb_t *);
 
 #endif
Index: uspace/lib/c/include/tls.h
===================================================================
--- uspace/lib/c/include/tls.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/include/tls.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -39,4 +39,7 @@
 #include <sys/types.h>
 
+/** DTV Generation number - equals vector length */
+#define DTV_GN(dtv) (((uintptr_t *)(dtv))[0])
+
 /*
  * Symbols defined in the respective linker script.
@@ -52,4 +55,6 @@
 extern void tls_free(tcb_t *);
 extern void tls_free_arch(tcb_t *, size_t);
+extern size_t tls_get_size(void);
+extern void *tls_get(void);
 
 #ifdef CONFIG_TLS_VARIANT_1
Index: uspace/lib/c/include/types/rtld/module.h
===================================================================
--- uspace/lib/c/include/types/rtld/module.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/include/types/rtld/module.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -46,6 +46,21 @@
 /** Dynamically linked module */
 typedef struct module {
+	/** Module ID */
+	unsigned long id;
+	/** Dynamic info for this module */
 	dyn_info_t dyn;
+	/** Load bias */
 	size_t bias;
+
+	/** tdata image start */
+	void *tdata;
+	/** tdata image size */
+	size_t tdata_size;
+	/** tbss size */
+	size_t tbss_size;
+	/** TLS alignment */
+	size_t tls_align;
+
+	size_t ioffs;
 
 	/** Containing rtld */
@@ -61,4 +76,6 @@
 	/** Link to list of all modules in runtime environment */
 	link_t modules_link;
+	/** Link to list of initial modules */
+	link_t imodules_link;
 
 	/** Link to BFS queue. Only used when doing a BFS of the module graph */
Index: uspace/lib/c/include/types/rtld/rtld.h
===================================================================
--- uspace/lib/c/include/types/rtld/rtld.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/c/include/types/rtld/rtld.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -48,6 +48,15 @@
 	module_t *program;
 
+	/** Next module ID */
+	unsigned long next_id;
+
+	/** Size of initial TLS tdata + tbss */
+	size_t tls_size;
+
 	/** List of all loaded modules including rtld and the program */
 	list_t modules;
+
+	/** List of initial modules */
+	list_t imodules;
 
 	/** Temporary hack to place each module at different address. */
Index: uspace/lib/dltest/dltest.c
===================================================================
--- uspace/lib/dltest/dltest.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/dltest/dltest.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -34,4 +34,5 @@
  */
 
+#include <fibril.h>
 #include "libdltest.h"
 
@@ -45,4 +46,14 @@
 /** Public uninitialized variable */
 int dl_public_uvar;
+
+/** Private initialized fibril-local variable */
+static fibril_local int dl_private_fib_var = dl_private_fib_var_val;
+/** Private uninitialized fibril-local variable */
+static fibril_local int dl_private_fib_uvar;
+
+/** Public initialized fibril-local variable */
+fibril_local int dl_public_fib_var = dl_public_fib_var_val;
+/** Public uninitialized fibril-local variable */
+fibril_local int dl_public_fib_uvar;
 
 /** Return constant value. */
@@ -58,8 +69,20 @@
 }
 
+/** Return address of private initialized variable */
+int *dl_get_private_var_addr(void)
+{
+	return &private_var;
+}
+
 /** Return value of private uninitialized variable */
 int dl_get_private_uvar(void)
 {
 	return private_uvar;
+}
+
+/** Return vaddress of private uninitialized variable */
+int *dl_get_private_uvar_addr(void)
+{
+	return &private_uvar;
 }
 
@@ -70,4 +93,10 @@
 }
 
+/** Return address of public initialized variable */
+int *dl_get_public_var_addr(void)
+{
+	return &dl_public_var;
+}
+
 /** Return value of public uninitialized variable */
 int dl_get_public_uvar(void)
@@ -76,4 +105,58 @@
 }
 
+/** Return address of public uninitialized variable */
+int *dl_get_public_uvar_addr(void)
+{
+	return &dl_public_uvar;
+}
+
+/** Return value of private initialized fibril-local variable */
+int dl_get_private_fib_var(void)
+{
+	return dl_private_fib_var;
+}
+
+/** Return address of private initialized fibril-local variable */
+int *dl_get_private_fib_var_addr(void)
+{
+	return &dl_private_fib_var;
+}
+
+/** Return value of private uninitialized fibril-local variable */
+int dl_get_private_fib_uvar(void)
+{
+	return dl_private_fib_uvar;
+}
+
+/** Return address of private uninitialized fibril-local variable */
+int *dl_get_private_fib_uvar_addr(void)
+{
+	return &dl_private_fib_uvar;
+}
+
+/** Return value of public initialized fibril-local variable */
+int dl_get_public_fib_var(void)
+{
+	return dl_public_fib_var;
+}
+
+/** Return value of public initialized fibril-local variable */
+int *dl_get_public_fib_var_addr(void)
+{
+	return &dl_public_fib_var;
+}
+
+/** Return value of public uninitialized fibril-local variable */
+int dl_get_public_fib_uvar(void)
+{
+	return dl_public_fib_uvar;
+}
+
+/** Return value of public uninitialized fibril-local variable */
+int *dl_get_public_fib_uvar_addr(void)
+{
+	return &dl_public_fib_uvar;
+}
+
 /**
  * @}
Index: uspace/lib/dltest/libdltest.h
===================================================================
--- uspace/lib/dltest/libdltest.h	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/lib/dltest/libdltest.h	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -37,18 +37,36 @@
 #define LIBDLTEST_H
 
+#include <fibril.h>
+
 enum {
 	dl_constant = 110011,
 	dl_private_var_val = 220022,
-	dl_public_var_val = 330033
+	dl_public_var_val = 330033,
+	dl_private_fib_var_val = 440044,
+	dl_public_fib_var_val = 550055
 };
 
 extern int dl_get_constant(void);
 extern int dl_get_private_var(void);
+extern int *dl_get_private_var_addr(void);
 extern int dl_get_private_uvar(void);
+extern int *dl_get_private_uvar_addr(void);
 extern int dl_get_public_var(void);
+extern int *dl_get_public_var_addr(void);
 extern int dl_get_public_uvar(void);
+extern int *dl_get_public_uvar_addr(void);
+extern int dl_get_private_fib_var(void);
+extern int *dl_get_private_fib_var_addr(void);
+extern int dl_get_private_fib_uvar(void);
+extern int *dl_get_private_fib_uvar_addr(void);
+extern int dl_get_public_fib_var(void);
+extern int *dl_get_public_fib_var_addr(void);
+extern int dl_get_public_fib_uvar(void);
+extern int *dl_get_public_fib_uvar_addr(void);
 
 extern int dl_public_var;
 extern int dl_public_uvar;
+extern fibril_local int dl_public_fib_var;
+extern fibril_local int dl_public_fib_uvar;
 
 #endif
Index: uspace/srv/loader/main.c
===================================================================
--- uspace/srv/loader/main.c	(revision af2254ece47fffd627e909e8547dca085773c990)
+++ uspace/srv/loader/main.c	(revision 0dc2fec8ad4b9dd2ceae65173853e10062135c7b)
@@ -269,5 +269,4 @@
 	
 	pcb.filc = filc;
-	printf("dynamic=%p rtld_env=%p\n", pcb.dynamic, pcb.rtld_runtime);
 	
 	async_answer_0(rid, rc);
