diff options
author | Viktor Dukhovni <ietf-dane@dukhovni.org> | 2021-04-19 04:02:33 -0400 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2021-04-22 17:00:55 -0400 |
commit | aa685c50d575ccf6c46a89d4ecdc9cffcf8b73f4 (patch) | |
tree | 29bd281a842b6dd355222c46cd866d61fee7b9e6 | |
parent | 350f4f61ae847c8adf376b9ca49ad1fee64e2e95 (diff) | |
download | haskell-aa685c50d575ccf6c46a89d4ecdc9cffcf8b73f4.tar.gz |
Add background note in elf_tlsgd.c.
Also some code cleanup, and a fix for an (extant unrelated) missing
<pthread_np.h> include that should hopefully resolve a failure in the
FreeBSD CI build, since it is best to make sure that this MR actually
builds on FreeBSD systems other than mine.
Some unexpected metric changes on FreeBSD (perhaps because CI had been
failing for a while???):
Metric Decrease:
T3064
T5321Fun
T5642
T9020
T12227
T13253-spj
T15164
T18282
WWRec
Metric Increase:
haddock.compiler
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | rts/LinkerInternals.h | 1 | ||||
-rw-r--r-- | rts/linker/Elf.c | 58 | ||||
-rw-r--r-- | rts/linker/SymbolExtras.c | 1 | ||||
-rw-r--r-- | rts/linker/elf_tlsgd.c | 117 | ||||
-rw-r--r-- | rts/posix/itimer/Pthread.c | 15 |
6 files changed, 173 insertions, 23 deletions
diff --git a/configure.ac b/configure.ac index f414308ae8..013f21f3ca 100644 --- a/configure.ac +++ b/configure.ac @@ -1219,6 +1219,8 @@ dnl ~~~~~~~~~~~~~~~~~~~~ dnl The portability situation here is complicated: dnl dnl * FreeBSD supports pthread_set_name_np in <pthread_np.h> +dnl and (if not _POSIX_SOURCE) pthread_setname_np() in <pthread.h> +dnl because of the conditional visibility, we prefer the former. dnl * glibc supports pthread_setname_np dnl * Darwin supports pthread_setname_np but does not take a dnl pthread_t argument. @@ -1270,7 +1272,7 @@ AC_LINK_IFELSE([ [[ #include <pthread_np.h> ]], - [[pthread_setname_np(pthread_self(), "name");]] + [[pthread_set_name_np(pthread_self(), "name");]] )], [ AC_MSG_RESULT(yes) diff --git a/rts/LinkerInternals.h b/rts/LinkerInternals.h index f56eec47fa..e1da899c89 100644 --- a/rts/LinkerInternals.h +++ b/rts/LinkerInternals.h @@ -195,6 +195,7 @@ typedef struct { } jumpIsland; #elif defined(x86_64_HOST_ARCH) uint64_t addr; + // See Note [TLSGD relocation] in elf_tlsgd.c uint8_t jumpIsland[8]; #elif defined(arm_HOST_ARCH) uint8_t jumpIsland[16]; diff --git a/rts/linker/Elf.c b/rts/linker/Elf.c index 9c4b1f9463..84cb72bd6b 100644 --- a/rts/linker/Elf.c +++ b/rts/linker/Elf.c @@ -1528,17 +1528,42 @@ do_Elf_Rela_relocations ( ObjectCode* oc, char* ehdrC, S = 0; } else { Elf_Sym sym = stab[ELF_R_SYM(info)]; - /* First see if it is a local symbol. */ - if (ELF_ST_BIND(sym.st_info) == STB_LOCAL) { - /* Yes, so we can get the address directly from the ELF symbol - table. */ - symbol = sym.st_name==0 ? "(noname)" : strtab+sym.st_name; - if (ELF_R_TYPE(info) == COMPAT_R_X86_64_TLSGD) { - /* No support for TLSGD locals, requires new RTLD API */ - errorBelch("%s: unhandled ELF TLSGD relocation for symbol `%s'", - oc->fileName, symbol); - return 0; + if (ELF_R_TYPE(info) == COMPAT_R_X86_64_TLSGD) { + /* + * No support for TLSGD variables *defined* by the object, + * only references to *external* TLS variables in already + * loaded shared objects (the executable, libc, ...) are + * supported. See Note [TLSGD relocation] in elf_tlsgd.c. + */ + symbol = sym.st_name == 0 ? "(noname)" : strtab+sym.st_name; + if (ELF_ST_BIND(sym.st_info) == STB_LOCAL + || sym.st_value != 0 || sym.st_name == 0) { + errorBelch("%s: unsupported internal ELF TLSGD relocation for" + " symbol `%s'", oc->fileName, symbol); + return 0; } +#if defined(x86_64_HOST_ARCH) && defined(freebsd_HOST_OS) + S = lookupTlsgdSymbol(symbol, ELF_R_SYM(info), oc); +#else + errorBelch("%s: ELF TLSGD relocation for symbol `%s'" + " not supported on the target platform", + oc->fileName, symbol); + return 0; +#endif + } else if (ELF_ST_BIND(sym.st_info) == STB_LOCAL) { + /* + * For local symbols, we can get the address directly from the ELF + * symbol table. + * + * XXX: Is STB_LOCAL the right test here? Should we instead be + * checking whether the symbol is *defined* by the current object? + * Defined globals also need relocation. Perhaps the point is that + * conflicts are resolved in favour of any prior definition, so we + * must look at the accumulated symbol table instead (which has + * already been updated with our global symbols by the time we get + * here). + */ + symbol = sym.st_name==0 ? "(noname)" : strtab+sym.st_name; /* See Note [Many ELF Sections] */ Elf_Word secno = sym.st_shndx; #if defined(SHN_XINDEX) @@ -1548,20 +1573,11 @@ do_Elf_Rela_relocations ( ObjectCode* oc, char* ehdrC, #endif S = (Elf_Addr)oc->sections[secno].start + stab[ELF_R_SYM(info)].st_value; - } else if (ELF_R_TYPE(info) != COMPAT_R_X86_64_TLSGD) { - /* No, so look up the name in our global table. */ + } else { + /* If not local, look up the name in our global table. */ symbol = strtab + sym.st_name; S_tmp = lookupDependentSymbol( symbol, oc ); S = (Elf_Addr)S_tmp; - } else { - symbol = strtab + sym.st_name; -#if defined(x86_64_HOST_ARCH) && defined(freebsd_HOST_OS) - S = lookupTlsgdSymbol(symbol, ELF_R_SYM(info), oc); -#else - errorBelch("%s: unhandled ELF TLSGD relocation for symbol `%s'", - oc->fileName, symbol); - return 0; -#endif } if (!S) { errorBelch("%s: unknown symbol `%s'", oc->fileName, symbol); diff --git a/rts/linker/SymbolExtras.c b/rts/linker/SymbolExtras.c index e209e211e1..ddb58e4a4e 100644 --- a/rts/linker/SymbolExtras.c +++ b/rts/linker/SymbolExtras.c @@ -183,6 +183,7 @@ SymbolExtra* makeSymbolExtra( ObjectCode const* oc, // jmp *-14(%rip) // 0xFF 25 is opcode + ModRM of near absolute indirect jump // Two bytes trailing padding, needed for TLSGD GOT entries + // See Note [TLSGD relocation] in elf_tlsgd.c static uint8_t jmp[] = { 0xFF, 0x25, 0xF2, 0xFF, 0xFF, 0xFF, 0x00, 0x00 }; extra->addr = target; memcpy(extra->jumpIsland, jmp, 8); diff --git a/rts/linker/elf_tlsgd.c b/rts/linker/elf_tlsgd.c index 9e9d6a820f..ec42e29ac6 100644 --- a/rts/linker/elf_tlsgd.c +++ b/rts/linker/elf_tlsgd.c @@ -2,6 +2,123 @@ #if defined(x86_64_HOST_ARCH) && defined(freebsd_HOST_OS) +/* + * Note [TLSGD relocation] + * + * Quick background: FreeBSD's <ctype.h> is poisoned with static inline code + * that gets compiled into every program that uses functions like isdigit(3). + * When compiled "-c -fpic" for inclusion in position-independent ".a" files + * that are used in GHCi and HLS to load dependent packages at runtime, code + * that uses <ctype.h> in some FFI ends up with previously unsupported + * thread-specific variable (TLSGD) relocations. This module narrowly addresses + * the issue for FreeBSD, where one often ends up using thread-local storage + * without meaning to. + * + * In the "General Dynamic" Thread-Local-Storage (TLSGD) model, relocations need + * an offset into a block of thread-local data associated with a particular + * module in which the given thread-local variable is defined. Such blocks are + * not used directly, since after all, the variables are thread-specific. + * Rather, each module's initialized thread locals and uninitialised (zeroed) + * thread-locals are used to initialise a corresponding block of data in each + * thread, possibly on first use by a thread of a variable from a given module. + * + * A thread that needs the address of a particular TLS variable needs to pass + * the module id and offset to __tls_get_addr() (provided by the ELF runtime + * linker ld.so, a.k.a. the RTLD, which also manages the loading and unloading + * of modules, and dynamic creation of the backing storage for each thread's + * dynamic thread-local-storage vector (dtv). + * + * The data to pass to __tls_get_addr() is found as two consecutive 64-bit + * values in the global offset table (GOT) of the object being relocated. + * (There are therefore many GOT tables, what's global is the addresses they + * point to, which are often outside the current object, not the tables + * themselves). + * + * The module id and offset are not known at compile time, and require + * relocation with assistance from the RTLD, because only the RTLD knows the + * logical module number for each loaded object (the main executable, and any + * shared libraries, such as libc). Fortunately, modern RTLDs provide an + * iterator for the currently loaded modules of a program, which exposes + * the associated module id and ELF section headers of each loaded object. + * (For static executables, this is instead handled by the C library). + * + * The iterator in question is dl_iterate_phdr(3). It repeatedly invokes + * the provided callback for each loaded module until the callback returns + * a non-zero value indicating that it has found what it was looking for + * and does not need to be called with any further modules. + * + * The "dlpi_info" structure provided to the callback contains the module + * id and a reference to the ELF program header list. In the program header + * list the "dynamic" section contains a number of subsections, which include + * the symbol table, the string table and either or both the sysv or GNU-style + * symbol hash table. + * + * The size of the symbol table is not directly available, so linear search + * through the symbol table is not only inefficient, but in fact not really + * possible, since we don't reliably know where the table ends. However, the + * hash tables (sysv and/or GNU) do have clear bounds, and substantially speed + * up symbol lookup, so we need to have code to use these tables. For now, + * only the sysv table is supported, but it should be easy to also support the + * GNU table (which could be the only present). On FreeBSD it is rumoured (or + * least anecdotally observed) that the tool chains ensure that the sysv table + * is always present. + * + * Thus armed with the symbol, string and hash table for a module, we can use + * our wanted symbol's hash to quickly find the relevant hash bucket, and from + * there traverse the list of symbols that share that hash, checking that + * whether the name is in fact an exact match. + * + * Note that the name we want may also appear as an undefined entry in the + * symbol tables of other modules that also reference it as an external symbol. + * Thus the module we're looking for is the one where the symbol's st_value is + * non-zero (indicating that it is actually defined in that module). + * + * Since we're looking for a TLS variable, we just in case also check the type + * and avoid erroneous bindings to some other sort of symbol. + * + * Once the right module is found, we need to push two values into a new slot + * in the GOT. This is done via the makeSymbolExtra() function of the GHC RTS. + * Our GOT entries must therefore be wide enough to hold two 64-bit values, but + * previously their X86_64 incarnation was only 14 bytes wide. It has now been + * expanded to 16 bytes, by adding two padding bytes to the jumpIsland slot + * that follows the `addr` field field of the original GOT entry. We store the + * module id in the `addr` field and the symbol's offset in the expanded + * jumpIsland field. The address `S` of the start of the new GOT entry is + * then adjusted to form the relative address `S + A - P` which is stored at the + * relocation address `P`. + * + * The magic additional offsets `0x8000` and `0x800` for MIPS, ... and RISC-V, + * were suggested by Fangrui Song (a.k.a. @MaskRay) in a comment on the ticket + * discussing the motivating FreeBSD issue: + * <https://gitlab.haskell.org/ghc/ghc/-/issues/19086#note_347076>. + * His blog at <https://maskray.me/blog/2021-02-14-all-about-thread-local-storage> + * may shed more light on these. + * + * Finally, the bad news. This code only works when the target TLS variable is + * defined by a preloaded shared object (.SO) that is known to the RTLD, has a + * module id, and TLS data and bss segments from which the RTLD initialises + * (perhaps lazily just-in-time) the per-thread TLS segments. It is not + * presently possible to support TLS variables from runtime loaded ".o" files, + * These are not loaded via the RTLD, and don't get a new module id, and + * __tls_get_addr() cannot return an appropriate thread-specific address for + * these. + * + * The best solution is probably to deprecate runtime loading of ".o" files, + * all runtime loaded objects should be shared objects, loaded via dlopen(), + * in which case the RTLD will take of all the TLS relocation details! + * Otherwise, packages with FFI code that uses the _Thread_local storage class + * will not be runtime loadable in GHCi, Haskell-language-server, and similar + * programs that use the GHC RTS runtime linker. As the popularity of such + * variables increases, we'll need have a more comprehensive approach to dealing + * with them, not limited to just "external references" as supported here. + * + * A much more complex approach would be to filter calls to __tls_get_addr(), + * using GHC-specific code to allocate per-thread storage for TLS variables in + * code loaded via ".o" files, delegating just external TLS variables to the + * RTLD. It is far from clear how to do that, and likely unwise to even think + * about going there. + */ + #include "linker/Elf.h" #include "linker/SymbolExtras.h" #include <link.h> diff --git a/rts/posix/itimer/Pthread.c b/rts/posix/itimer/Pthread.c index 82379b9172..438bc2f69c 100644 --- a/rts/posix/itimer/Pthread.c +++ b/rts/posix/itimer/Pthread.c @@ -63,6 +63,9 @@ #include <string.h> #include <pthread.h> +#if defined(HAVE_PTHREAD_NP_H) +#include <pthread_np.h> +#endif #include <unistd.h> #include <fcntl.h> @@ -175,10 +178,20 @@ initTicker (Time interval, TickProc handle_tick) /* * We can't use the RTS's createOSThread here as we need to remain attached * to the thread we create so we can later join to it if requested + * + * On FreeBSD 12.2 pthread_set_name_np() is unconditionally declared in + * <pthread_np.h>, while pthread_setname_np() is conditionally declared in + * <pthread.h> when _POSIX_SOURCE is not defined, but we're including + * <PosixSource.h>, so must use pthread_set_name_np() instead. See similar + * code in "rts/posix/OSThreads.c". */ if (! pthread_create(&thread, NULL, itimer_thread_func, (void*)handle_tick)) { -#if defined(HAVE_PTHREAD_SETNAME_NP) +#if defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_set_name_np(thread, "ghc_ticker"); +#elif defined(HAVE_PTHREAD_SETNAME_NP) pthread_setname_np(thread, "ghc_ticker"); +#elif defined(HAVE_PTHREAD_SETNAME_NP_DARWIN) + pthread_setname_np("ghc_ticker"); #endif } else { barf("Itimer: Failed to spawn thread: %s", strerror(errno)); |