# Implicit Thread Local Storage tapset # Copyright (C) 2020, 2021 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # Reference: "ELF Handling For Thread-Local Storage" by Ulrich Drepper /* * TCB Pointer points to the thread control block. dtv points to the DTV * array. DTV[1]-DTV[N] point to tls entries for module I from the link * map. DTV[1] is the main executable. The dtv val points to the tls * entry where the tls variable value is located. The tls variable value * is also located a fixed offset from the tcbhead so if relocations are * simple enough a compiler can generate a direct load. Complicated * relocations may result in a compiler calling a runtime routine * __tls_get_addr which uses the DTV. * * TCB Pointer * | * V * TLS struct tcbhead * +---------------------+ +----------------+ * |OFFSET|OFFSET|OFFSET | | void *tcb | * | 2 | 1 | 0 | | dtv_t *dtv | --+ * +---------------------+ +----------------+ | * ^-------------------+ | * v----------------------------------------+ * +----------------------------+ | * DTV |length|gen|dtv[1]|dtv[2]|...| | * +----------------------------+ | * dtv[n] | | * +---------------------+ | * |unsigned long counter| | * |struct dtv_pointer { | | * | void *val |->------+ * | void *to_free} | * +---------------------| */ global tls_debug = 0 private global tls_link_map_name private global tls_module = 0 %{ # if defined(__DYNINST__) # if defined(__i386) || defined(__x86_64) # include # include # endif # endif %} // Get the thread context pointer that points to the tls thread control block @__private30 function get_thread_context_pointer (tls_thread_reg_val:long) %{ # ifdef __i386 { unsigned long gs; # if defined(__KERNEL__) rdmsrl(MSR_FS_BASE, gs); # elif defined(__DYNINST__) int arch_prctl(int code, unsigned long *addr); arch_prctl(ARCH_GET_FS, &gs); # endif STAP_RETVALUE = gs; } # elif defined __x86_64 { unsigned long fs; #if defined(__KERNEL__) rdmsrl(MSR_FS_BASE, fs); # elif defined(__DYNINST__) int arch_prctl(int code, unsigned long *addr); arch_prctl(ARCH_GET_FS, &fs); # endif STAP_RETVALUE = fs; } # elif defined __s390x__ { unsigned long tlsval; asm volatile ("ear %0,%%a0" : "=r" (tlsval)); asm volatile ("sllg %0,%0,32" : "=r" (tlsval)); asm volatile ("ear %0,%%a1" : "=r" (tlsval)); STAP_RETVALUE = tlsval; } # elif defined __aarch64__ { unsigned long tlsval; asm("mrs %0,tpidr_el0" : "=r" (tlsval)); STAP_RETVALUE = tlsval; } # elif defined __powerpc64__ { unsigned long tcb; asm("subi %0,%1,28688," : "=r" (tcb) : "r" (STAP_ARG_tls_thread_reg_val)); // -0x7010 STAP_RETVALUE = tcb; } # endif %} // Get inode of followed link // TODO Does not follow symbolic links, e.g. to follow /lib64/libc.so.6 // The idea was to do 'inode = get_inode_from_path (l_name); name = inode_path(inode);' @__private30 function get_inode_from_path:long (tls_module_name) %{ struct inode *tls_inode; struct path tls_path; int code; code = kern_path(/* string */ STAP_ARG_tls_module_name, LOOKUP_FOLLOW, &tls_path); if (code == 0) STAP_RETVALUE = (int64_t)tls_path.dentry->d_inode; else STAP_RETVALUE = 0; %} // Return basename of a path @__private30 function basename (l_name:string) { for (lni = 0; lni < strlen (l_name); lni++) { if (stringat(l_name, lni) == 0x2f) // '/' slash = lni; } if (slash > 0) { slash += 1; return substr (l_name, slash, strlen(l_name)-slash); } else return l_name; } // Setup the link_map module/l_name mapping @__private30 function setup_link_map () { delete tls_link_map_name %( arch == "x86_64" %? l_map_count = @var("_rtld_global","/usr/lib64/ld-linux-x86-64.so.2")->_dl_nns // TODO Do namespaces need to be supported? assume [0] l_map = @var("_rtld_global","/usr/lib64/ld-linux-x86-64.so.2")->_dl_ns[0]->_ns_loaded; if (tls_debug > 1) println ("l_map=", @var("_rtld_global","/usr/lib64/ld-linux-x86-64.so.2")->_dl_ns[0]->_ns_loaded$); %) %( arch == "powerpc" %? l_map_count = @var("_rtld_global","/usr/lib64/ld64.so.2")->_dl_nns l_map = @var("_rtld_global","/usr/lib64/ld64.so.2")->_dl_ns[0]->_ns_loaded; if (tls_debug > 1) println ("l_map=", @var("_rtld_global","/usr/lib64/ld64.so.2")->_dl_ns[0]->_ns_loaded$); %) %( arch == "s390" %? l_map_count = @var("_rtld_global","/usr/lib/ld64.so.1")->_dl_nns l_map = @var("_rtld_global","/usr/lib/ld64.so.1")->_dl_ns[0]->_ns_loaded; if (tls_debug > 1) println ("l_map=", @var("_rtld_global","/usr/lib/ld64.so.1")->_dl_ns[0]->_ns_loaded$); %) while (l_map) { l_name = user_string(l_map->l_name); if (l_name == "") { l_map = l_map->l_next continue } l_addr = user_uint64(l_map->l_addr); l_tls_modid = l_map->l_tls_modid name_base = basename (l_name); if (l_tls_modid != 0) { tls_link_map_name[l_tls_modid] = name_base; if (tls_debug) printf("link_map: %s l_addr=%#lx modid=%d\n", tls_link_map_name[l_tls_modid], l_addr, l_tls_modid); } l_map = l_map->l_next } } // Compare module names. // sonames, real sonames, and linker sonames should compare equal @__private30 function regex_module_compare (stap_mod:string, map_mod:string) { // Not an soname if (stap_mod !~ "^lib.*$") return stap_mod == map_mod; // Most common case libMATCH-1.soMATCH-2 // MATCH-1 is the soname minus lib prefix and .so* suffix if (stap_mod !~ "lib([-\._\+a-zA-Z0-9]+)\.so([\.0-9]*)") return 0; stap_lib = matched(1); if (map_mod !~ "lib([-\._\+a-zA-Z0-9]+)\.so([\.0-9]*)") return 0; map_lib = matched(1); if (stap_lib == map_lib) return 1; // The above will miss e.g. libc.so.6/libc-2.31.so // MATCH-1 is the soname minus lib prefix and -version.so suffix if (stap_mod !~ "lib([_\+a-zA-Z0-9]+)([-\.0-9]*)\.so([\.0-9]*)") return 0; stap_lib = matched(1); if (map_mod !~ "lib([_\+a-zA-Z0-9]+)([-\.0-9]*)\.so([\.0-9]*)") return 0; map_lib = matched(1); if (stap_lib == map_lib) return 1; else return 0; } // Find the module by matching the probe name to the link_map name @__private30 function set_tls_module_by_name (tls_module:string) { matched_proc = basename(tls_module); if (tls_debug) printf("Looking for %s in link_map\n", matched_proc); foreach (lm in tls_link_map_name) { if (lm==0) continue; if (regex_module_compare (matched_proc, tls_link_map_name[lm])) { if (tls_debug) printf("Name Match: %s \n", tls_link_map_name[lm]); return lm; } } return 1; } /** * sfunction __push_tls_address - Handle implicit tls variable relocation * * Description: Called for DW_OP_GNU_push_tls_address relocation from loc2stap.css::location_context::translate */ function __push_tls_address (tls_var_offset:long, tls_module:string) { %( arch == "powerpc" %? // It seems the value of r13 cannot be dependably relied on in get_thread_context_pointer r13 = register("r13") %: r13 = 0 %) tcbhead = get_thread_context_pointer (r13); if (tls_debug > 1) printf ("tls_module=%s tcbhead=%#lx/%d\n", tls_module, tcbhead, tcbhead); setup_link_map (); module = set_tls_module_by_name (tls_module); if (module >= 0) { // dtv[-1] is module count // dtv[0] is generation count // dtv[1] is module[0] info, the main executable // dtv[2..n] is module info for dependent shared objects dtv$pointer$val = @cast(tcbhead, "tcbhead", "")->dtv[module]->pointer->val; if (tls_debug > 1) printf ("dtv$pointer$val=%#lx, tls_var_offset=%#lx, module=%d\n", dtv$pointer$val, tls_var_offset, module); return dtv$pointer$val + tls_var_offset; } else return 0; }