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]> --- arch/x64/arch-elf.cc | 31 +++++++++++++++++-- arch/x64/arch-switch.hh | 68 +++++++++++++++++++++++++++++++++++------ arch/x64/loader.ld | 4 +++ core/elf.cc | 20 +++++++++--- include/osv/elf.hh | 7 +++-- modules/tests/Makefile | 14 +++++++-- tests/libtls.cc | 7 +++++ tests/tst-tls.cc | 12 +++++--- 8 files changed, 138 insertions(+), 25 deletions(-) diff --git a/arch/x64/arch-elf.cc b/arch/x64/arch-elf.cc index ebe40996..4f3df376 100644 --- a/arch/x64/arch-elf.cc +++ b/arch/x64/arch-elf.cc @@ -114,8 +114,21 @@ bool object::arch_relocate_rela(u32 type, u32 sym, void *addr, // target thread-local variable if (sym) { auto sm = symbol(sym); - sm.obj->alloc_static_tls(); - auto tls_offset = sm.obj->static_tls_end() + sched::kernel_tls_size(); + ulong tls_offset; + if (sm.obj->is_executable()) { + tls_offset = sm.obj->get_tls_size(); + // If this is an executable (pie or position-dependant one) + // then the variable is located in the reserved slot of the TLS + // right where the kernel TLS lives + // So the offset is negative size of this ELF TLS block + } else { + // If shared library, the variable is located in one of TLS + // blocks that are part of the static TLS before kernel part + // so the offset needs to shift by sum of kernel and size of the user static + // TLS so far + sm.obj->alloc_static_tls(); + tls_offset = sm.obj->static_tls_end() + sched::kernel_tls_size(); + } *static_cast<u64*>(addr) = sm.symbol->st_value + addend - tls_offset; } else { // TODO: Which case does this handle? @@ -154,7 +167,19 @@ void object::prepare_initial_tls(void* buffer, size_t size, memset(ptr + _tls_init_size, 0, _tls_uninit_size); offsets.resize(std::max(_module_index + 1, offsets.size())); - offsets[_module_index] = - _static_tls_offset - tls_size - sched::kernel_tls_size(); + auto offset = - _static_tls_offset - tls_size - sched::kernel_tls_size(); + offsets[_module_index] = offset; +} + +void object::prepare_local_tls(std::vector<ptrdiff_t>& offsets) +{ + if (!_static_tls && !is_executable()) { + return; + } + + offsets.resize(std::max(_module_index + 1, offsets.size())); + auto offset = - get_tls_size(); + offsets[_module_index] = offset; } } diff --git a/arch/x64/arch-switch.hh b/arch/x64/arch-switch.hh index 88bef949..cd1acb83 100644 --- a/arch/x64/arch-switch.hh +++ b/arch/x64/arch-switch.hh @@ -151,33 +151,83 @@ void thread::init_stack() } void thread::setup_tcb() -{ - assert(tls.size); +{ // + // Most importantly this method allocates TLS memory region and + // sets up TCB (Thread Control Block) that points to that allocated + // memory region. The TLS memory region is designated to a specific thread + // and holds thread local variables (with __thread modifier) defined + // in OSv kernel and the application ELF objects including dependant ones + // through DT_NEEDED tag. + // + // Each ELF object and OSv kernel gets its own TLS block with offsets + // specified in DTV structure (the offsets get calculated as ELF is loaded and symbols + // resolved before we get to this point). + // + // Because both OSv kernel and position-in-dependant (pie) or position-dependant + // executable (non library) are compiled to use local-exec mode to access the thread + // local variables, we need to setup the offsets and TLS blocks in a special way + // to avoid any collisions. Specifically we define OSv TLS segment + // (see arch/x64/loader.ld for specifics) with an extra buffer at + // the end of the kernel TLS to accommodate TLS block of pies and + // position-dependant executables. + + // (1) - TLS memory area layout with app shared library + // |-----|-----|-----|--------------|------| + // |SO_3 |SO_2 |SO_1 |KERNEL |<NONE>| + // |-----|-----|-----|--------------|------| + + // (2) - TLS memory area layout with pie or + // position dependant executable + // |-----|-----|---------------------| + // |SO_3 |SO_2 |KERNEL | EXE | + // |-----|-----|--------------|------| + + assert(sched::tls.size); void* user_tls_data; size_t user_tls_size = 0; + void* executable_tls_data; + size_t executable_tls_size = 0; if (_app_runtime) { auto obj = _app_runtime->app.lib(); assert(obj); user_tls_size = obj->initial_tls_size(); user_tls_data = obj->initial_tls(); + if (obj->is_executable()) { + executable_tls_size = obj->get_tls_size(); + executable_tls_data = obj->get_tls_segment(); + } } // In arch/x64/loader.ld, the TLS template segment is aligned to 64 // bytes, and that's what the objects placed in it assume. So make // sure our copy is allocated with the same 64-byte alignment, and // verify that object::init_static_tls() ensured that user_tls_size - // also doesn't break this alignment . - assert(align_check(tls.size, (size_t)64)); + // also doesn't break this alignment. + auto kernel_tls_size = sched::tls.size; + assert(align_check(kernel_tls_size, (size_t)64)); assert(align_check(user_tls_size, (size_t)64)); - void* p = aligned_alloc(64, sched::tls.size + user_tls_size + sizeof(*_tcb)); + + auto total_tls_size = kernel_tls_size + user_tls_size; + void* p = aligned_alloc(64, total_tls_size + sizeof(*_tcb)); + // First goes user TLS data if (user_tls_size) { memcpy(p, user_tls_data, user_tls_size); } - memcpy(p + user_tls_size, sched::tls.start, sched::tls.filesize); - memset(p + user_tls_size + sched::tls.filesize, 0, - sched::tls.size - sched::tls.filesize); - _tcb = static_cast<thread_control_block*>(p + tls.size + user_tls_size); + // Next goes kernel TLS data + auto kernel_tls_offset = user_tls_size; + memcpy(p + kernel_tls_offset, sched::tls.start, sched::tls.filesize); + memset(p + kernel_tls_offset + sched::tls.filesize, 0, + kernel_tls_size - sched::tls.filesize); + + if (executable_tls_size) { + // If executable copy its TLS block data at the designated offset + // at the end of area as described in the ascii art for executables + // TLS layout + auto executable_tls_offset = total_tls_size - executable_tls_size; + memcpy(p + executable_tls_offset, executable_tls_data, executable_tls_size); + } + _tcb = static_cast<thread_control_block*>(p + total_tls_size); _tcb->self = _tcb; _tcb->tls_base = p + user_tls_size; diff --git a/arch/x64/loader.ld b/arch/x64/loader.ld index 062094ba..b75a0fe8 100644 --- a/arch/x64/loader.ld +++ b/arch/x64/loader.ld @@ -90,7 +90,11 @@ SECTIONS .tdata : AT(ADDR(.tdata) - OSV_KERNEL_VM_SHIFT) { *(.tdata .tdata.* .gnu.linkonce.td.*) } :tls :text .tbss : AT(ADDR(.tbss) - OSV_KERNEL_VM_SHIFT) { *(.tbss .tbss.* .gnu.linkonce.tb.*) + _pie_static_tls_start = .; + /* This is a reserve intended for executables' (pie or non-pie) TLS block */ + . = . + 64; . = ALIGN(64); + _pie_static_tls_end = .; } :tls :text .tls_template_size = SIZEOF(.tdata) + SIZEOF(.tbss); .bss : AT(ADDR(.bss) - OSV_KERNEL_VM_SHIFT) { *(.bss .bss.*) } :text diff --git a/core/elf.cc b/core/elf.cc index c0c24e79..196f3405 100644 --- a/core/elf.cc +++ b/core/elf.cc @@ -403,6 +403,7 @@ Elf64_Note::Elf64_Note(void *_base, char *str) } } +extern "C" char _pie_static_tls_start, _pie_static_tls_end; void object::load_segments() { for (unsigned i = 0; i < _ehdr.e_phnum; ++i) { @@ -470,9 +471,13 @@ void object::load_segments() // As explained in issue #352, we currently don't correctly support TLS // used in PIEs. if (_is_executable && _tls_segment) { - std::cout << "WARNING: " << pathname() << " is a PIE using TLS. This " - << "is currently unsupported (see issue #352). Link with " - << "'-shared' instead of '-pie'.\n"; + auto tls_size = _tls_init_size + _tls_uninit_size; + ulong pie_static_tls_maximum_size = &_pie_static_tls_end - &_pie_static_tls_start; + if (tls_size > pie_static_tls_maximum_size) { + std::cout << "WARNING: " << pathname() << " is a PIE using TLS of size " << tls_size + << " which is greater than " << pie_static_tls_maximum_size << " bytes limit. " + << "Either increase the limit or link with '-shared' instead of '-pie'.\n"; + } } } @@ -1103,8 +1108,13 @@ void object::init_static_tls() if (obj->is_core()) { continue; } - obj->prepare_initial_tls(_initial_tls.get(), _initial_tls_size, - _initial_tls_offsets); + if (obj->is_executable()) { + obj->prepare_local_tls(_initial_tls_offsets); + } + else { + obj->prepare_initial_tls(_initial_tls.get(), _initial_tls_size, + _initial_tls_offsets); + } } } diff --git a/include/osv/elf.hh b/include/osv/elf.hh index 775d8c8d..cf79a8a1 100644 --- a/include/osv/elf.hh +++ b/include/osv/elf.hh @@ -365,8 +365,11 @@ public: void init_static_tls(); size_t initial_tls_size() { return _initial_tls_size; } void* initial_tls() { return _initial_tls.get(); } + void* get_tls_segment() { return _tls_segment; } bool is_non_pie_executable() { return _ehdr.e_type == ET_EXEC; } std::vector<ptrdiff_t>& initial_tls_offsets() { return _initial_tls_offsets; } + bool is_executable() { return _is_executable; } + ulong get_tls_size(); protected: virtual void load_segment(const Elf64_Phdr& segment) = 0; virtual void unload_segment(const Elf64_Phdr& segment) = 0; @@ -391,9 +394,9 @@ private: void relocate_rela(); void relocate_pltgot(); unsigned symtab_len(); - ulong get_tls_size(); void collect_dependencies(std::unordered_set<elf::object*>& ds); void prepare_initial_tls(void* buffer, size_t size, std::vector<ptrdiff_t>& offsets); + void prepare_local_tls(std::vector<ptrdiff_t>& offsets); void alloc_static_tls(); void make_text_writable(bool flag); protected: @@ -440,7 +443,7 @@ protected: Elf64_Sxword addend); bool arch_relocate_jump_slot(u32 sym, void *addr, Elf64_Sxword addend, bool ignore_missing = false); size_t static_tls_end() { - if (is_core()) { + if (is_core() || is_executable()) { return 0; } return _static_tls_offset + get_tls_size(); diff --git a/modules/tests/Makefile b/modules/tests/Makefile index d8ea6049..3c5bc324 100644 --- a/modules/tests/Makefile +++ b/modules/tests/Makefile @@ -119,7 +119,7 @@ tests := tst-pthread.so misc-ramdisk.so tst-vblk.so tst-bsd-evh.so \ tst-sendfile.so misc-lock-perf.so tst-uio.so tst-printf.so \ tst-pthread-affinity.so tst-pthread-tsd.so tst-thread-local.so \ tst-zfs-mount.so tst-regex.so tst-tcp-siocoutq.so \ - libtls.so tst-tls.so tst-select-timeout.so tst-faccessat.so \ + libtls.so tst-tls.so tst-tls-pie.so tst-select-timeout.so tst-faccessat.so \ tst-fstatat.so misc-reboot.so tst-fcntl.so payload-namespace.so \ tst-namespace.so tst-without-namespace.so payload-env.so \ payload-merge-env.so misc-execve.so misc-execve-payload.so misc-mutex2.so \ @@ -150,7 +150,17 @@ $(out)/tests/tst-tls.so: \ $(src)/tests/tst-tls.cc \ $(out)/tests/libtls.so $(makedir) - $(call quiet, cd $(out); $(CXX) $(CXXFLAGS) -shared -o $@ $< tests/libtls.so, CXX tst-tls.so) + $(call quiet, cd $(out); $(CXX) $(CXXFLAGS) -D__SHARED_OBJECT__=1 -shared -o $@ $< tests/libtls.so, CXX tst-tls.so) + +$(out)/tests/tst-tls-pie.o: CXXFLAGS:=$(subst -fPIC,-fpie,$(CXXFLAGS)) +$(out)/tests/tst-tls-pie.o: $(src)/tests/tst-tls.cc + $(makedir) + $(call quiet, $(CXX) $(CXXFLAGS) -c -o $@ $<, CXX $*.cc) +$(out)/tests/tst-tls-pie.so: \ + $(out)/tests/tst-tls-pie.o \ + $(out)/tests/libtls.so + $(makedir) + $(call quiet, cd $(out); $(CXX) $(CXXFLAGS) -fuse-ld=bfd -pthread -pie -o $@ $< tests/libtls.so, LD tst-tls-pie.so) boost-tests := tst-vfs.so tst-libc-locking.so misc-fs-stress.so \ misc-bdev-write.so misc-bdev-wlatency.so misc-bdev-rw.so \ diff --git a/tests/libtls.cc b/tests/libtls.cc index bf7e7e70..96e380e5 100644 --- a/tests/libtls.cc +++ b/tests/libtls.cc @@ -12,9 +12,16 @@ __thread int ex1 = 321; __thread int ex2 __attribute__ ((tls_model ("initial-exec"))) = 432; __thread int ex3 = 765; +extern __thread int v1; +extern __thread int v5; + void external_library() { + // ex1 and ex3 get accessed by _tls_get_addr() ex1++; ex2++; ex3++; + // These 2 below get handled by get _tls_get_addr() function in core/elf.cc + v1++; + v5++; } diff --git a/tests/tst-tls.cc b/tests/tst-tls.cc index f827aa71..452052c2 100644 --- a/tests/tst-tls.cc +++ b/tests/tst-tls.cc @@ -37,7 +37,7 @@ static __thread int v6 __attribute__ ((tls_model ("initial-exec"))) = 678; extern __thread int ex3 __attribute__ ((tls_model ("initial-exec"))); -#ifndef __OSV__ +#ifndef __SHARED_OBJECT__ // We can also try to force the "Local Exec" TLS model, but OSv's makefile // builds all tests as shared objects (.so), and the linker will report an // error, because local-exec is not allowed in shared libraries, just in @@ -65,7 +65,7 @@ int main(int argc, char** argv) report(v5 == 567, "v5"); report(v6 == 678, "v6"); report(ex3 == 765, "ex3"); -#ifndef __OSV__ +#ifndef __SHARED_OBJECT__ report(v7 == 789, "v7"); #endif @@ -73,6 +73,8 @@ int main(int argc, char** argv) report(ex1 == 322, "ex1 modified"); report(ex2 == 433, "ex2 modified"); report(ex3 == 766, "ex3 modified"); + report(v1 == 124, "v1 modified"); + report(v5 == 568, "v5 modified"); // Write on this thread's variables, and see a new thread gets // the original default values @@ -82,7 +84,7 @@ int main(int argc, char** argv) v4 = 0; v5 = 0; v6 = 0; -#ifndef __OSV__ +#ifndef __SHARED_OBJECT__ v7 = 0; #endif @@ -97,7 +99,7 @@ int main(int argc, char** argv) report(v5 == 567, "v5 in new thread"); report(v6 == 678, "v6 in new thread"); report(ex3 == 765, "ex3 in new thread"); -#ifndef __OSV__ +#ifndef __SHARED_OBJECT__ report(v7 == 789, "v7 in new thread"); #endif @@ -105,6 +107,8 @@ int main(int argc, char** argv) report(ex1 == 322, "ex1 modified in new thread"); report(ex2 == 433, "ex2 modified in new thread"); report(ex3 == 766, "ex3 modified in new thread"); + report(v1 == 124, "v1 modified in new thread"); + report(v5 == 568, "v5 modified"); }); t1.join(); -- 2.20.1 -- You received this message because you are subscribed to the Google Groups "OSv Development" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/osv-dev/20190714041003.4698-1-jwkozaczuk%40gmail.com. For more options, visit https://groups.google.com/d/optout.
