Sindbad~EG File Manager
# 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 <asm/prctl.h>
# include <sys/prctl.h>
# 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", "<linux/stp_tls.h>")->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;
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists