Skip to content

Commit 70547a6

Browse files
committed
Support local-exec TLS access
This patch enhances OSv dynamic loader to support pies and position dependant executables that use TLS (Thread Local Storage) in local-exec mode. It does so by reserving an extra slot in kernel static TLS block at its end and designating it as user static TLS for the executable ELF. Any dependant ELF objects are still placed in the area before the kernel TLS. For the specifics please read comments added to arch-elf.cc and arch-switch.hh. Please note that this solution limits the size of the application TLS block to 64 bytes plus extra gap due to 64-bytes alignment of the kernel TLS. This should be sufficient for most applications which either use tiny TLS (Golang uses 8-bytes long) if at all. Rust ELFs tend to rely on quite large TLS in which case the limit in loader.ld needs to be increased accordingly and loader.elf relinked. Fixes #352 Signed-off-by: Waldemar Kozaczuk <[email protected]>
1 parent d44e7c6 commit 70547a6

File tree

8 files changed

+155
-25
lines changed

8 files changed

+155
-25
lines changed

arch/x64/arch-elf.cc

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,21 @@ bool object::arch_relocate_rela(u32 type, u32 sym, void *addr,
126126
// target thread-local variable
127127
if (sym) {
128128
auto sm = symbol(sym);
129-
sm.obj->alloc_static_tls();
130-
auto tls_offset = sm.obj->static_tls_end() + sched::kernel_tls_size();
129+
ulong tls_offset;
130+
if (sm.obj->is_executable()) {
131+
tls_offset = sm.obj->get_tls_size();
132+
// If this is an executable (pie or position-dependant one)
133+
// then the variable is located in the reserved slot of the TLS
134+
// right where the kernel TLS lives
135+
// So the offset is negative size of this ELF TLS block
136+
} else {
137+
// If shared library, the variable is located in one of TLS
138+
// blocks that are part of the static TLS before kernel part
139+
// so the offset needs to shift by sum of kernel and size of the user static
140+
// TLS so far
141+
sm.obj->alloc_static_tls();
142+
tls_offset = sm.obj->static_tls_end() + sched::kernel_tls_size();
143+
}
131144
*static_cast<u64*>(addr) = sm.symbol->st_value + addend - tls_offset;
132145
} else {
133146
// TODO: Which case does this handle?
@@ -166,7 +179,25 @@ void object::prepare_initial_tls(void* buffer, size_t size,
166179
memset(ptr + _tls_init_size, 0, _tls_uninit_size);
167180

168181
offsets.resize(std::max(_module_index + 1, offsets.size()));
169-
offsets[_module_index] = - _static_tls_offset - tls_size - sched::kernel_tls_size();
182+
auto offset = - _static_tls_offset - tls_size - sched::kernel_tls_size();
183+
offsets[_module_index] = offset;
184+
}
185+
186+
void object::prepare_local_tls(std::vector<ptrdiff_t>& offsets)
187+
{
188+
if (!_static_tls && !is_executable()) {
189+
return;
190+
}
191+
192+
offsets.resize(std::max(_module_index + 1, offsets.size()));
193+
auto offset = - get_tls_size();
194+
offsets[_module_index] = offset;
195+
}
196+
197+
void object::copy_local_tls(void* to_addr)
198+
{
199+
memcpy(to_addr, _tls_segment, _tls_init_size); //file size - 48 (0x30) for example and 80 (0x50) for httpserver
200+
memset(to_addr + _tls_init_size, 0, _tls_uninit_size);
170201
}
171202

172203
}

arch/x64/arch-switch.hh

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,33 +151,81 @@ void thread::init_stack()
151151
}
152152

153153
void thread::setup_tcb()
154-
{
155-
assert(tls.size);
154+
{ //
155+
// Most importantly this method allocates TLS memory region and
156+
// sets up TCB (Thread Control Block) that points to that allocated
157+
// memory region. The TLS memory region is designated to a specific thread
158+
// and holds thread local variables (with __thread modifier) defined
159+
// in OSv kernel and the application ELF objects including dependant ones
160+
// through DT_NEEDED tag.
161+
//
162+
// Each ELF object and OSv kernel gets its own TLS block with offsets
163+
// specified in DTV structure (the offsets get calculated as ELF is loaded and symbols
164+
// resolved before we get to this point).
165+
//
166+
// Because both OSv kernel and position-in-dependant (pie) or position-dependant
167+
// executable (non library) are compiled to use local-exec mode to access the thread
168+
// local variables, we need to setup the offsets and TLS blocks in a special way
169+
// to avoid any collisions. Specifically we define OSv TLS segment
170+
// (see arch/x64/loader.ld for specifics) with an extra buffer at
171+
// the end of the kernel TLS to accommodate TLS block of pies and
172+
// position-dependant executables.
173+
174+
// (1) - TLS memory area layout with app shared library
175+
// |-----|-----|-----|--------------|------|
176+
// |SO_3 |SO_2 |SO_1 |KERNEL |<NONE>|
177+
// |-----|-----|-----|--------------|------|
178+
179+
// (2) - TLS memory area layout with pie or
180+
// position dependant executable
181+
// |-----|-----|---------------------|
182+
// |SO_3 |SO_2 |KERNEL | EXE |
183+
// |-----|-----|--------------|------|
184+
185+
assert(sched::tls.size);
156186

157187
void* user_tls_data;
158188
size_t user_tls_size = 0;
189+
size_t executable_tls_size = 0;
159190
if (_app_runtime) {
160191
auto obj = _app_runtime->app.lib();
161192
assert(obj);
162193
user_tls_size = obj->initial_tls_size();
163194
user_tls_data = obj->initial_tls();
195+
if (obj->is_executable()) {
196+
executable_tls_size = obj->get_tls_size();
197+
}
164198
}
165199

166200
// In arch/x64/loader.ld, the TLS template segment is aligned to 64
167201
// bytes, and that's what the objects placed in it assume. So make
168202
// sure our copy is allocated with the same 64-byte alignment, and
169203
// verify that object::init_static_tls() ensured that user_tls_size
170-
// also doesn't break this alignment .
171-
assert(align_check(tls.size, (size_t)64));
204+
// also doesn't break this alignment.
205+
auto kernel_tls_size = sched::tls.size;
206+
assert(align_check(kernel_tls_size, (size_t)64));
172207
assert(align_check(user_tls_size, (size_t)64));
173-
void* p = aligned_alloc(64, sched::tls.size + user_tls_size + sizeof(*_tcb));
208+
209+
auto total_tls_size = kernel_tls_size + user_tls_size;
210+
void* p = aligned_alloc(64, total_tls_size + sizeof(*_tcb));
211+
// First goes user TLS data
174212
if (user_tls_size) {
175213
memcpy(p, user_tls_data, user_tls_size);
176214
}
177-
memcpy(p + user_tls_size, sched::tls.start, sched::tls.filesize);
178-
memset(p + user_tls_size + sched::tls.filesize, 0,
179-
sched::tls.size - sched::tls.filesize);
180-
_tcb = static_cast<thread_control_block*>(p + tls.size + user_tls_size);
215+
// Next goes kernel TLS data
216+
auto kernel_tls_offset = user_tls_size;
217+
memcpy(p + kernel_tls_offset, sched::tls.start, sched::tls.filesize);
218+
memset(p + kernel_tls_offset + sched::tls.filesize, 0,
219+
kernel_tls_size - sched::tls.filesize);
220+
221+
if (executable_tls_size) {
222+
// If executable copy its TLS block data at the designated offset
223+
// at the end of area as described in the ascii art for executables
224+
// TLS layout
225+
auto executable_tls_offset = total_tls_size - executable_tls_size;
226+
_app_runtime->app.lib()->copy_local_tls(p + executable_tls_offset);
227+
}
228+
_tcb = static_cast<thread_control_block*>(p + total_tls_size);
181229
_tcb->self = _tcb;
182230
_tcb->tls_base = p + user_tls_size;
183231

arch/x64/loader.ld

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ SECTIONS
9090
.tdata : AT(ADDR(.tdata) - OSV_KERNEL_VM_SHIFT) { *(.tdata .tdata.* .gnu.linkonce.td.*) } :tls :text
9191
.tbss : AT(ADDR(.tbss) - OSV_KERNEL_VM_SHIFT) {
9292
*(.tbss .tbss.* .gnu.linkonce.tb.*)
93+
_pie_static_tls_start = .;
94+
/* This is a reserve intended for executables' (pie or non-pie) TLS block */
95+
. = . + 64;
9396
. = ALIGN(64);
97+
_pie_static_tls_end = .;
9498
} :tls :text
9599
.tls_template_size = SIZEOF(.tdata) + SIZEOF(.tbss);
96100
.bss : AT(ADDR(.bss) - OSV_KERNEL_VM_SHIFT) { *(.bss .bss.*) } :text

core/elf.cc

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ Elf64_Note::Elf64_Note(void *_base, char *str)
404404
}
405405
}
406406

407+
extern "C" char _pie_static_tls_start, _pie_static_tls_end;
407408
void object::load_segments()
408409
{
409410
for (unsigned i = 0; i < _ehdr.e_phnum; ++i) {
@@ -471,9 +472,14 @@ void object::load_segments()
471472
// As explained in issue #352, we currently don't correctly support TLS
472473
// used in PIEs.
473474
if (_is_executable && _tls_segment) {
474-
std::cout << "WARNING: " << pathname() << " is a PIE using TLS. This "
475-
<< "is currently unsupported (see issue #352). Link with "
476-
<< "'-shared' instead of '-pie'.\n";
475+
auto tls_size = _tls_init_size + _tls_uninit_size;
476+
ulong pie_static_tls_maximum_size = &_pie_static_tls_end - &_pie_static_tls_start;
477+
if (tls_size > pie_static_tls_maximum_size) {
478+
std::cout << "WARNING: " << pathname() << " is a PIE using TLS of size " << tls_size
479+
<< " which is greater than " << pie_static_tls_maximum_size << " bytes limit. "
480+
<< "Either increase the size of TLS reserve in arch/x64/loader.ld or "
481+
<< "link with '-shared' instead of '-pie'.\n";
482+
}
477483
}
478484
}
479485

@@ -1110,8 +1116,13 @@ void object::init_static_tls()
11101116
if (obj->is_core()) {
11111117
continue;
11121118
}
1113-
obj->prepare_initial_tls(_initial_tls.get(), _initial_tls_size,
1114-
_initial_tls_offsets);
1119+
if (obj->is_executable()) {
1120+
obj->prepare_local_tls(_initial_tls_offsets);
1121+
}
1122+
else {
1123+
obj->prepare_initial_tls(_initial_tls.get(), _initial_tls_size,
1124+
_initial_tls_offsets);
1125+
}
11151126
}
11161127
}
11171128

include/osv/elf.hh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,12 @@ public:
366366
void init_static_tls();
367367
size_t initial_tls_size() { return _initial_tls_size; }
368368
void* initial_tls() { return _initial_tls.get(); }
369+
void* get_tls_segment() { return _tls_segment; }
369370
bool is_non_pie_executable() { return _ehdr.e_type == ET_EXEC; }
370371
std::vector<ptrdiff_t>& initial_tls_offsets() { return _initial_tls_offsets; }
372+
bool is_executable() { return _is_executable; }
373+
ulong get_tls_size();
374+
void copy_local_tls(void* to_addr);
371375
protected:
372376
virtual void load_segment(const Elf64_Phdr& segment) = 0;
373377
virtual void unload_segment(const Elf64_Phdr& segment) = 0;
@@ -392,9 +396,9 @@ private:
392396
void relocate_rela();
393397
void relocate_pltgot();
394398
unsigned symtab_len();
395-
ulong get_tls_size();
396399
void collect_dependencies(std::unordered_set<elf::object*>& ds);
397400
void prepare_initial_tls(void* buffer, size_t size, std::vector<ptrdiff_t>& offsets);
401+
void prepare_local_tls(std::vector<ptrdiff_t>& offsets);
398402
void alloc_static_tls();
399403
void make_text_writable(bool flag);
400404
protected:
@@ -441,7 +445,7 @@ protected:
441445
Elf64_Sxword addend);
442446
bool arch_relocate_jump_slot(u32 sym, void *addr, Elf64_Sxword addend, bool ignore_missing = false);
443447
size_t static_tls_end() {
444-
if (is_core()) {
448+
if (is_core() || is_executable()) {
445449
return 0;
446450
}
447451
return _static_tls_offset + get_tls_size();

modules/tests/Makefile

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ tests := tst-pthread.so misc-ramdisk.so tst-vblk.so tst-bsd-evh.so \
119119
tst-sendfile.so misc-lock-perf.so tst-uio.so tst-printf.so \
120120
tst-pthread-affinity.so tst-pthread-tsd.so tst-thread-local.so \
121121
tst-zfs-mount.so tst-regex.so tst-tcp-siocoutq.so \
122-
libtls.so tst-tls.so tst-select-timeout.so tst-faccessat.so \
122+
libtls.so tst-tls.so tst-tls-pie.so tst-select-timeout.so tst-faccessat.so \
123123
tst-fstatat.so misc-reboot.so tst-fcntl.so payload-namespace.so \
124124
tst-namespace.so tst-without-namespace.so payload-env.so \
125125
payload-merge-env.so misc-execve.so misc-execve-payload.so misc-mutex2.so \
@@ -150,7 +150,17 @@ $(out)/tests/tst-tls.so: \
150150
$(src)/tests/tst-tls.cc \
151151
$(out)/tests/libtls.so
152152
$(makedir)
153-
$(call quiet, cd $(out); $(CXX) $(CXXFLAGS) -shared -o $@ $< tests/libtls.so, CXX tst-tls.so)
153+
$(call quiet, cd $(out); $(CXX) $(CXXFLAGS) -D__SHARED_OBJECT__=1 -shared -o $@ $< tests/libtls.so, CXX tst-tls.so)
154+
155+
$(out)/tests/tst-tls-pie.o: CXXFLAGS:=$(subst -fPIC,-fpie,$(CXXFLAGS))
156+
$(out)/tests/tst-tls-pie.o: $(src)/tests/tst-tls.cc
157+
$(makedir)
158+
$(call quiet, $(CXX) $(CXXFLAGS) -c -o $@ $<, CXX $*.cc)
159+
$(out)/tests/tst-tls-pie.so: \
160+
$(out)/tests/tst-tls-pie.o \
161+
$(out)/tests/libtls.so
162+
$(makedir)
163+
$(call quiet, cd $(out); $(CXX) $(CXXFLAGS) -fuse-ld=bfd -pthread -pie -o $@ $< tests/libtls.so, LD tst-tls-pie.so)
154164

155165
boost-tests := tst-vfs.so tst-libc-locking.so misc-fs-stress.so \
156166
misc-bdev-write.so misc-bdev-wlatency.so misc-bdev-rw.so \

tests/libtls.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,16 @@ __thread int ex1 = 321;
1212
__thread int ex2 __attribute__ ((tls_model ("initial-exec"))) = 432;
1313
__thread int ex3 = 765;
1414

15+
extern __thread int v1;
16+
extern __thread int v5;
17+
1518
void external_library()
1619
{
20+
// ex1 and ex3 get accessed by _tls_get_addr()
1721
ex1++;
1822
ex2++;
1923
ex3++;
24+
// These 2 below get handled by get _tls_get_addr() function in core/elf.cc
25+
v1++;
26+
v5++;
2027
}

tests/tst-tls.cc

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ static __thread int v6 __attribute__ ((tls_model ("initial-exec"))) = 678;
3737

3838
extern __thread int ex3 __attribute__ ((tls_model ("initial-exec")));
3939

40-
#ifndef __OSV__
40+
#ifndef __SHARED_OBJECT__
4141
// We can also try to force the "Local Exec" TLS model, but OSv's makefile
4242
// builds all tests as shared objects (.so), and the linker will report an
4343
// error, because local-exec is not allowed in shared libraries, just in
4444
// executables (including PIE).
4545
__thread int v7 __attribute__ ((tls_model ("local-exec"))) = 789;
46+
__thread int v8 __attribute__ ((tls_model ("local-exec")));
4647
#endif
4748

4849
extern void external_library();
@@ -65,14 +66,16 @@ int main(int argc, char** argv)
6566
report(v5 == 567, "v5");
6667
report(v6 == 678, "v6");
6768
report(ex3 == 765, "ex3");
68-
#ifndef __OSV__
69+
#ifndef __SHARED_OBJECT__
6970
report(v7 == 789, "v7");
7071
#endif
7172

7273
external_library();
7374
report(ex1 == 322, "ex1 modified");
7475
report(ex2 == 433, "ex2 modified");
7576
report(ex3 == 766, "ex3 modified");
77+
report(v1 == 124, "v1 modified");
78+
report(v5 == 568, "v5 modified");
7679

7780
// Write on this thread's variables, and see a new thread gets
7881
// the original default values
@@ -82,7 +85,7 @@ int main(int argc, char** argv)
8285
v4 = 0;
8386
v5 = 0;
8487
v6 = 0;
85-
#ifndef __OSV__
88+
#ifndef __SHARED_OBJECT__
8689
v7 = 0;
8790
#endif
8891

@@ -97,16 +100,28 @@ int main(int argc, char** argv)
97100
report(v5 == 567, "v5 in new thread");
98101
report(v6 == 678, "v6 in new thread");
99102
report(ex3 == 765, "ex3 in new thread");
100-
#ifndef __OSV__
103+
#ifndef __SHARED_OBJECT__
101104
report(v7 == 789, "v7 in new thread");
102105
#endif
103106

104107
external_library();
105108
report(ex1 == 322, "ex1 modified in new thread");
106109
report(ex2 == 433, "ex2 modified in new thread");
107110
report(ex3 == 766, "ex3 modified in new thread");
111+
report(v1 == 124, "v1 modified in new thread");
112+
report(v5 == 568, "v5 modified");
108113
});
109114
t1.join();
110115

111116
std::cout << "SUMMARY: " << tests << " tests, " << fails << " failures\n";
112117
}
118+
119+
#ifndef __SHARED_OBJECT__
120+
static void before_main(void) __attribute__((constructor));
121+
static void before_main(void)
122+
{
123+
report(v7 == 789, "v7 in init function");
124+
report(v8 == 0, "v8 in init function");
125+
}
126+
#endif
127+

0 commit comments

Comments
 (0)