244 lines
6.9 KiB
C
244 lines
6.9 KiB
C
/* Locate TLS data for a thread.
|
|
Copyright (C) 2003-2019 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
|
|
The GNU C Library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
The GNU C Library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with the GNU C Library; if not, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "thread_dbP.h"
|
|
#include <link.h>
|
|
|
|
/* Get the DTV slotinfo list head entry from the dynamic loader state
|
|
into *LISTHEAD. */
|
|
static td_err_e
|
|
dtv_slotinfo_list (td_thragent_t *ta,
|
|
psaddr_t *listhead)
|
|
{
|
|
td_err_e err;
|
|
psaddr_t head;
|
|
|
|
if (ta->ta_addr__rtld_global == 0
|
|
&& td_mod_lookup (ta->ph, LD_SO, SYM__rtld_global,
|
|
&ta->ta_addr__rtld_global) != PS_OK)
|
|
ta->ta_addr__rtld_global = (void*)-1;
|
|
|
|
if (ta->ta_addr__rtld_global != (void*)-1)
|
|
{
|
|
err = DB_GET_FIELD (head, ta, ta->ta_addr__rtld_global,
|
|
rtld_global, _dl_tls_dtv_slotinfo_list, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
}
|
|
else
|
|
{
|
|
if (ta->ta_addr__dl_tls_dtv_slotinfo_list == 0
|
|
&& td_mod_lookup (ta->ph, NULL, SYM__dl_tls_dtv_slotinfo_list,
|
|
&ta->ta_addr__dl_tls_dtv_slotinfo_list) != PS_OK)
|
|
return TD_ERR;
|
|
|
|
err = _td_fetch_value (ta, ta->ta_var__dl_tls_dtv_slotinfo_list,
|
|
SYM_DESC__dl_tls_dtv_slotinfo_list,
|
|
0, ta->ta_addr__dl_tls_dtv_slotinfo_list, &head);
|
|
if (err != TD_OK)
|
|
return err;
|
|
}
|
|
|
|
*listhead = head;
|
|
return TD_OK;
|
|
}
|
|
|
|
/* Get the address of the DTV slotinfo entry for MODID into
|
|
*DTVSLOTINFO. */
|
|
static td_err_e
|
|
dtv_slotinfo (td_thragent_t *ta,
|
|
unsigned long int modid,
|
|
psaddr_t *dtvslotinfo)
|
|
{
|
|
td_err_e err;
|
|
psaddr_t slot, temp;
|
|
size_t slbase = 0;
|
|
|
|
err = dtv_slotinfo_list (ta, &slot);
|
|
if (err != TD_OK)
|
|
return err;
|
|
|
|
while (slot)
|
|
{
|
|
/* Get the number of entries in this list entry's array. */
|
|
err = DB_GET_FIELD (temp, ta, slot, dtv_slotinfo_list, len, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
size_t len = (uintptr_t)temp;
|
|
|
|
/* Did we find the list entry for modid? */
|
|
if (modid < slbase + len)
|
|
break;
|
|
|
|
/* We didn't, so get the next list entry. */
|
|
slbase += len;
|
|
err = DB_GET_FIELD (temp, ta, slot, dtv_slotinfo_list,
|
|
next, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
slot = temp;
|
|
}
|
|
|
|
/* We reached the end of the list and found nothing. */
|
|
if (!slot)
|
|
return TD_ERR;
|
|
|
|
/* Take the slotinfo for modid from the list entry. */
|
|
err = DB_GET_FIELD_ADDRESS (temp, ta, slot, dtv_slotinfo_list,
|
|
slotinfo, modid - slbase);
|
|
if (err != TD_OK)
|
|
return err;
|
|
slot = temp;
|
|
|
|
*dtvslotinfo = slot;
|
|
return TD_OK;
|
|
}
|
|
|
|
/* Return in *BASE the base address of the TLS block for MODID within
|
|
TH.
|
|
|
|
It should return success and yield the correct pointer in any
|
|
circumstance where the TLS block for the module and thread
|
|
requested has already been initialized.
|
|
|
|
It should fail with TD_TLSDEFER only when the thread could not
|
|
possibly have observed any values in that TLS block. That way, the
|
|
debugger can fall back to showing initial values from the PT_TLS
|
|
segment (and refusing attempts to mutate) for the TD_TLSDEFER case,
|
|
and never fail to make the values the program will actually see
|
|
available to the user of the debugger. */
|
|
td_err_e
|
|
td_thr_tlsbase (const td_thrhandle_t *th,
|
|
unsigned long int modid,
|
|
psaddr_t *base)
|
|
{
|
|
td_err_e err;
|
|
psaddr_t dtv, dtvslot, dtvptr, temp;
|
|
|
|
if (modid < 1)
|
|
return TD_NOTLS;
|
|
|
|
psaddr_t pd = th->th_unique;
|
|
if (pd == 0)
|
|
{
|
|
/* This is the fake handle for the main thread before libpthread
|
|
initialization. We are using 0 for its th_unique because we can't
|
|
trust that its thread register has been initialized. But we need
|
|
a real pointer to have any TLS access work. In case of dlopen'd
|
|
libpthread, initialization might not be for quite some time. So
|
|
try looking up the thread register now. Worst case, it's nonzero
|
|
uninitialized garbage and we get bogus results for TLS access
|
|
attempted too early. Tough. */
|
|
|
|
td_thrhandle_t main_th;
|
|
err = __td_ta_lookup_th_unique (th->th_ta_p, ps_getpid (th->th_ta_p->ph),
|
|
&main_th);
|
|
if (err == 0)
|
|
pd = main_th.th_unique;
|
|
if (pd == 0)
|
|
return TD_TLSDEFER;
|
|
}
|
|
|
|
err = dtv_slotinfo (th->th_ta_p, modid, &temp);
|
|
if (err != TD_OK)
|
|
return err;
|
|
|
|
psaddr_t slot;
|
|
err = DB_GET_STRUCT (slot, th->th_ta_p, temp, dtv_slotinfo);
|
|
if (err != TD_OK)
|
|
return err;
|
|
|
|
/* Take the link_map from the slotinfo. */
|
|
psaddr_t map;
|
|
err = DB_GET_FIELD_LOCAL (map, th->th_ta_p, slot, dtv_slotinfo, map, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
if (!map)
|
|
return TD_ERR;
|
|
|
|
/* Ok, the modid is good, now find out what DTV generation it
|
|
requires. */
|
|
err = DB_GET_FIELD_LOCAL (temp, th->th_ta_p, slot, dtv_slotinfo, gen, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
size_t modgen = (uintptr_t)temp;
|
|
|
|
/* Get the DTV pointer from the thread descriptor. */
|
|
err = DB_GET_FIELD (dtv, th->th_ta_p, pd, pthread, dtvp, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
|
|
psaddr_t dtvgenloc;
|
|
/* Get the DTV generation count at dtv[0].counter. */
|
|
err = DB_GET_FIELD_ADDRESS (dtvgenloc, th->th_ta_p, dtv, dtv, dtv, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
err = DB_GET_FIELD (temp, th->th_ta_p, dtvgenloc, dtv_t, counter, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
size_t dtvgen = (uintptr_t)temp;
|
|
|
|
/* Is the DTV current enough? */
|
|
if (dtvgen < modgen)
|
|
{
|
|
try_static_tls:
|
|
/* If the module uses Static TLS, we're still good. */
|
|
err = DB_GET_FIELD (temp, th->th_ta_p, map, link_map, l_tls_offset, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
ptrdiff_t tlsoff = (uintptr_t)temp;
|
|
|
|
if (tlsoff != FORCED_DYNAMIC_TLS_OFFSET
|
|
&& tlsoff != NO_TLS_OFFSET)
|
|
{
|
|
psaddr_t tp = pd;
|
|
|
|
#if TLS_TCB_AT_TP
|
|
dtvptr = tp - tlsoff;
|
|
#elif TLS_DTV_AT_TP
|
|
dtvptr = tp + tlsoff + TLS_PRE_TCB_SIZE;
|
|
#else
|
|
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
|
|
#endif
|
|
|
|
*base = dtvptr;
|
|
return TD_OK;
|
|
}
|
|
|
|
return TD_TLSDEFER;
|
|
}
|
|
|
|
/* Find the corresponding entry in the DTV. */
|
|
err = DB_GET_FIELD_ADDRESS (dtvslot, th->th_ta_p, dtv, dtv, dtv, modid);
|
|
if (err != TD_OK)
|
|
return err;
|
|
|
|
/* Extract the TLS block address from that DTV slot. */
|
|
err = DB_GET_FIELD (dtvptr, th->th_ta_p, dtvslot, dtv_t, pointer_val, 0);
|
|
if (err != TD_OK)
|
|
return err;
|
|
|
|
/* It could be that the memory for this module is not allocated for
|
|
the given thread. */
|
|
if ((uintptr_t) dtvptr & 1)
|
|
goto try_static_tls;
|
|
|
|
*base = dtvptr;
|
|
return TD_OK;
|
|
}
|