aboutsummaryrefslogtreecommitdiff
path: root/gdb/svr4-tls-tdep.c
blob: 56e1470e460112f1c746d5c68d21df0ed3e2a967 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/* Target-dependent code for GNU/Linux, architecture independent.

   Copyright (C) 2009-2024 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "svr4-tls-tdep.h"
#include "solib-svr4.h"
#include "inferior.h"
#include "objfiles.h"
#include "cli/cli-cmds.h"
#include <optional>

struct svr4_tls_gdbarch_data
{
  /* Method for looking up TLS DTV.  */
  get_tls_dtv_addr_ftype *get_tls_dtv_addr = nullptr;

  /* Method for looking up the TLS DTP offset.  */
  get_tls_dtp_offset_ftype *get_tls_dtp_offset = nullptr;

  /* Cached libc value for TLS lookup purposes.  */
  enum svr4_tls_libc libc = svr4_tls_libc_unknown;
};

static const registry<gdbarch>::key<svr4_tls_gdbarch_data>
     svr4_tls_gdbarch_data_handle;

static struct svr4_tls_gdbarch_data *
get_svr4_tls_gdbarch_data (struct gdbarch *gdbarch)
{
  struct svr4_tls_gdbarch_data *result = svr4_tls_gdbarch_data_handle.get (gdbarch);
  if (result == nullptr)
    result = svr4_tls_gdbarch_data_handle.emplace (gdbarch);
  return result;
}

/* When true, force internal TLS address lookup instead of lookup via
   the thread stratum.  */

static bool force_internal_tls_address_lookup = false;

/* For TLS lookup purposes, use heuristics to decide whether program
   was linked against MUSL or GLIBC.  */

static enum svr4_tls_libc
libc_tls_sniffer (struct gdbarch *gdbarch)
{
  /* Check for cached libc value.  */
  svr4_tls_gdbarch_data *gdbarch_data = get_svr4_tls_gdbarch_data (gdbarch);
  if (gdbarch_data->libc != svr4_tls_libc_unknown)
    return gdbarch_data->libc;

  svr4_tls_libc libc = svr4_tls_libc_unknown;

  /* Fetch the program interpreter.  */
  std::optional<gdb::byte_vector> interp_name_holder
    = svr4_find_program_interpreter ();
  if (interp_name_holder)
    {
      /* A dynamically linked program linked against MUSL will have a
	 "ld-musl-" in its interpreter name.  (Two examples of MUSL
	 interpreter names are "/lib/ld-musl-x86_64.so.1" and
	 "lib/ld-musl-aarch64.so.1".)  If it's not found, assume GLIBC. */
      const char *interp_name = (const char *) interp_name_holder->data ();
      if (strstr (interp_name, "/ld-musl-") != nullptr)
	libc = svr4_tls_libc_musl;
      else
	libc = svr4_tls_libc_glibc;
      gdbarch_data->libc = libc;
      return libc;
    }

  /* If there is no interpreter name, it's statically linked.  For
     programs with TLS data, a program statically linked against MUSL
     will have the symbols 'main_tls' and 'builtin_tls'.  If both of
     these are present, assume that it was statically linked against
     MUSL, otherwise assume GLIBC.  */
  if (lookup_minimal_symbol (current_program_space, "main_tls").minsym
	!= nullptr
      && lookup_minimal_symbol (current_program_space, "builtin_tls").minsym
	!= nullptr)
    libc = svr4_tls_libc_musl;
  else
    libc = svr4_tls_libc_glibc;
  gdbarch_data->libc = libc;
  return libc;
}

/* Implement gdbarch method, get_thread_local_address, for architectures
   which provide a method for determining the DTV and possibly the DTP
   offset.  */

CORE_ADDR
svr4_tls_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
				CORE_ADDR lm_addr, CORE_ADDR offset)
{
  svr4_tls_gdbarch_data *gdbarch_data = get_svr4_tls_gdbarch_data (gdbarch);

  /* Use the target's get_thread_local_address method when:

     - No method has been provided for finding the TLS DTV.

     or

     - The thread stratum has been pushed (at some point) onto the
       target stack, except when 'force_internal_tls_address_lookup'
       has been set.

     The idea here is to prefer use of of the target's thread_stratum
     method since it should be more accurate.  */
  if (gdbarch_data->get_tls_dtv_addr == nullptr
      || (find_target_at (thread_stratum) != nullptr
	  && !force_internal_tls_address_lookup))
    {
      struct target_ops *target = current_inferior ()->top_target ();
      return target->get_thread_local_address (ptid, lm_addr, offset);
    }
  else
    {
      /* Details, found below, regarding TLS layout is for the GNU C
	 library (glibc) and the MUSL C library (musl), circa 2024.
	 While some of this layout is defined by the TLS ABI, some of
	 it, such as how/where to find the DTV pointer in the TCB, is
	 not.  A good source of ABI info for some architectures can be
	 found in "ELF Handling For Thread-Local Storage" by Ulrich
	 Drepper.  That document is worth consulting even for
	 architectures not described there, since the general approach
	 and terminology is used regardless.

	 Some architectures, such as aarch64, are not described in
	 that document, so some details had to ferreted out using the
	 glibc source code.  Likewise, the MUSL source code was
	 consulted for details which differ from GLIBC.  */
      enum svr4_tls_libc libc = libc_tls_sniffer (gdbarch);
      int mod_id;
      if (libc == svr4_tls_libc_glibc)
	mod_id = glibc_link_map_to_tls_module_id (lm_addr);
      else /* Assume MUSL.  */
	mod_id = musl_link_map_to_tls_module_id (lm_addr);
      if (mod_id == 0)
	throw_error (TLS_GENERIC_ERROR, _("Unable to determine TLS module id"));

      /* Use the architecture specific DTV fetcher to obtain the DTV.  */
      CORE_ADDR dtv_addr = gdbarch_data->get_tls_dtv_addr (gdbarch, ptid, libc);

      /* In GLIBC, The DTV (dynamic thread vector) is an array of
	 structs consisting of two fields, the first of which is a
	 pointer to the TLS block of interest.  (The second field is a
	 pointer that assists with memory management, but that's not
	 of interest here.)  Also, the 0th entry is the generation
	 number, but although it's a single scalar, the 0th entry is
	 padded to be the same size as all the rest.  Thus each
	 element of the DTV array is two pointers in size.

	 In MUSL, the DTV is simply an array of pointers.  The 0th
	 entry is still the generation number, but contains no padding
	 aside from that which is needed to make it pointer sized.  */
      int m;	/* Multiplier, for size of DTV entry.  */
      switch (libc)
	{
	  case svr4_tls_libc_glibc:
	    m = 2;
	    break;
	  default:
	    m = 1;
	    break;
	}

      /* Obtain TLS block address.  Module ids start at 1, so there's
	 no need to adjust it to skip over the 0th entry of the DTV,
	 which is the generation number.  */
      CORE_ADDR dtv_elem_addr
	= dtv_addr + mod_id * m * (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
      gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
      if (target_read_memory (dtv_elem_addr, buf.data (), buf.size ()) != 0)
	throw_error (TLS_GENERIC_ERROR, _("Unable to fetch TLS block address"));
      const struct builtin_type *builtin = builtin_type (gdbarch);
      CORE_ADDR tls_block_addr = gdbarch_pointer_to_address
				   (gdbarch, builtin->builtin_data_ptr,
				    buf.data ());

      /* When the TLS block addr is 0 or -1, this usually indicates that
	 the TLS storage hasn't been allocated yet.  (In GLIBC, some
	 architectures use 0 while others use -1.)  */
      if (tls_block_addr == 0 || tls_block_addr == (CORE_ADDR) -1)
	throw_error (TLS_NOT_ALLOCATED_YET_ERROR, _("TLS not allocated yet"));

      /* MUSL (and perhaps other C libraries, though not GLIBC) have
	 TLS implementations for some architectures which, for some
	 reason, have DTV entries which must be negatively offset by
	 DTP_OFFSET in order to obtain the TLS block address.
	 DTP_OFFSET is a constant in the MUSL sources - these offsets,
	 when they're non-zero, seem to be either 0x800 or 0x8000,
	 and are present for riscv[32/64], powerpc[32/64], m68k, and
	 mips.

	 Use the architecture specific get_tls_dtp_offset method, if
	 present, to obtain this offset.  */
      ULONGEST dtp_offset
	= gdbarch_data->get_tls_dtp_offset == nullptr
	  ? 0
	  : gdbarch_data->get_tls_dtp_offset (gdbarch, ptid, libc);

      return tls_block_addr - dtp_offset + offset;
    }
}

/* See svr4-tls-tdep.h.  */

void
svr4_tls_register_tls_methods (struct gdbarch_info info, struct gdbarch *gdbarch,
			    get_tls_dtv_addr_ftype *get_tls_dtv_addr,
			    get_tls_dtp_offset_ftype *get_tls_dtp_offset)
{
  gdb_assert (get_tls_dtv_addr != nullptr);

  svr4_tls_gdbarch_data *gdbarch_data = get_svr4_tls_gdbarch_data (gdbarch);
  gdbarch_data->get_tls_dtv_addr = get_tls_dtv_addr;
  gdbarch_data->get_tls_dtp_offset = get_tls_dtp_offset;
}

void _initialize_svr4_tls_tdep ();
void
_initialize_svr4_tls_tdep ()
{
  add_setshow_boolean_cmd ("force-internal-tls-address-lookup", class_obscure,
			   &force_internal_tls_address_lookup, _("\
Set to force internal TLS address lookup."), _("\
Show whether GDB is forced to use internal TLS address lookup."), _("\
When resolving addresses for TLS (Thread Local Storage) variables,\n\
GDB will attempt to use facilities provided by the thread library (i.e.\n\
libthread_db).  If those facilities aren't available, GDB will fall\n\
back to using some internal (to GDB), but possibly less accurate\n\
mechanisms to resolve the addresses for TLS variables.  When this flag\n\
is set, GDB will force use of the fall-back TLS resolution mechanisms.\n\
This flag is used by some GDB tests to ensure that the internal fallback\n\
code is exercised and working as expected.  The default is to not force\n\
the internal fall-back mechanisms to be used."),
			   NULL, NULL,
			   &maintenance_set_cmdlist,
			   &maintenance_show_cmdlist);
}