Patchwork [5/9] Add a helper function to resolve TLS variable addresses for FreeBSD.

login
register
mail settings
Submitter John Baldwin
Date Jan. 22, 2019, 6:42 p.m.
Message ID <f09cffd4714ca17c5fcbc6ad7c9ec5e5dd649bd7.1548180889.git.jhb@FreeBSD.org>
Download mbox | patch
Permalink /patch/31171/
State New
Headers show

Comments

John Baldwin - Jan. 22, 2019, 6:42 p.m.
The fbsd_get_thread_local_address function accepts the base address of
a thread's DTV array and the base address of an object file's link map
and uses this to compute a TLS variable's address.  FreeBSD
architectures use an architecture-specific method to determine the
address of the DTV array pointer and call this helper function to
perform the rest of the address calculation.

	* fbsd-tdep.c (fbsd_pspace_data_handle): New variable.
	(struct fbsd_pspace_data): New type.
	(get_fbsd_pspace_data, fbsd_pspace_data_cleanup)
	(fbsd_read_integer_by_name, fbsd_fetch_rtld_offsets)
	(fbsd_get_tls_index, fbsd_get_thread_local_address): New function.
	(_initialize_fbsd_tdep): Initialize 'fbsd_pspace_data_handle'.
	* fbsd-tdep.c (fbsd_get_thread_local_address): New prototype.
---
 gdb/ChangeLog   |  10 ++++
 gdb/fbsd-tdep.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++
 gdb/fbsd-tdep.h |  10 ++++
 3 files changed, 166 insertions(+)
John Baldwin - Jan. 24, 2019, 5:08 p.m.
On 1/22/19 10:42 AM, John Baldwin wrote:
> The fbsd_get_thread_local_address function accepts the base address of
> a thread's DTV array and the base address of an object file's link map
> and uses this to compute a TLS variable's address.  FreeBSD
> architectures use an architecture-specific method to determine the
> address of the DTV array pointer and call this helper function to
> perform the rest of the address calculation.
> 
> 	* fbsd-tdep.c (fbsd_pspace_data_handle): New variable.
> 	(struct fbsd_pspace_data): New type.
> 	(get_fbsd_pspace_data, fbsd_pspace_data_cleanup)
> 	(fbsd_read_integer_by_name, fbsd_fetch_rtld_offsets)
> 	(fbsd_get_tls_index, fbsd_get_thread_local_address): New function.
> 	(_initialize_fbsd_tdep): Initialize 'fbsd_pspace_data_handle'.
> 	* fbsd-tdep.c (fbsd_get_thread_local_address): New prototype.
> ---
>  gdb/ChangeLog   |  10 ++++
>  gdb/fbsd-tdep.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++
>  gdb/fbsd-tdep.h |  10 ++++
>  3 files changed, 166 insertions(+)
> 
> diff --git a/gdb/fbsd-tdep.c b/gdb/fbsd-tdep.c
> index d971d3a653..2e0f72d17b 100644
> --- a/gdb/fbsd-tdep.c
> +++ b/gdb/fbsd-tdep.c
> +
> +/* Lookup offsets of fields in the runtime linker's 'Obj_Entry'
> +   structure needed to determine the TLS index of an object file.  */
> +
> +static void
> +fbsd_fetch_rtld_offsets (struct gdbarch *gdbarch, struct fbsd_pspace_data *data)
> +{
> +  TRY
> +    {
> +      /* Fetch offsets from debug symbols in rtld.  */
> +      data->off_linkmap = parse_and_eval_long ("&((Obj_Entry *)0)->linkmap");
> +      data->off_tlsindex = parse_and_eval_long ("&((Obj_Entry *)0)->tlsindex");
> +      data->rtld_offsets_valid = true;
> +      return;

I'm not really happy about using parse_and_eval_long with an open-coded
equivalent of offsetof() here.  It seems we don't already have existing
functionality for this though?  I think I could use 'lookup_struct' to find
the 'struct type *' for 'Obj_Entry', but if I used 'lookup_struct_elt_type'
to get the type of an element that doesn't give me anything that has the
offset.  We could perhaps instead add a new function 'lookup_struct_elt_offset'
that took the element name as a string and figured out the offset.  We could
then use this to provide an 'offsetof' builtin for the C language perhaps.
However, I suspect that lookup_struct_elt_offset would have to invoke a
language-specific function to do the actual computation (just as ptype /o
handling is language-specific).
Simon Marchi - Feb. 7, 2019, 5:04 a.m.
On 2019-01-24 12:08, John Baldwin wrote:
> On 1/22/19 10:42 AM, John Baldwin wrote:
>> The fbsd_get_thread_local_address function accepts the base address of
>> a thread's DTV array and the base address of an object file's link map
>> and uses this to compute a TLS variable's address.  FreeBSD
>> architectures use an architecture-specific method to determine the
>> address of the DTV array pointer and call this helper function to
>> perform the rest of the address calculation.
>> 
>> 	* fbsd-tdep.c (fbsd_pspace_data_handle): New variable.
>> 	(struct fbsd_pspace_data): New type.
>> 	(get_fbsd_pspace_data, fbsd_pspace_data_cleanup)
>> 	(fbsd_read_integer_by_name, fbsd_fetch_rtld_offsets)
>> 	(fbsd_get_tls_index, fbsd_get_thread_local_address): New function.
>> 	(_initialize_fbsd_tdep): Initialize 'fbsd_pspace_data_handle'.
>> 	* fbsd-tdep.c (fbsd_get_thread_local_address): New prototype.
>> ---
>>  gdb/ChangeLog   |  10 ++++
>>  gdb/fbsd-tdep.c | 146 
>> ++++++++++++++++++++++++++++++++++++++++++++++++
>>  gdb/fbsd-tdep.h |  10 ++++
>>  3 files changed, 166 insertions(+)
>> 
>> diff --git a/gdb/fbsd-tdep.c b/gdb/fbsd-tdep.c
>> index d971d3a653..2e0f72d17b 100644
>> --- a/gdb/fbsd-tdep.c
>> +++ b/gdb/fbsd-tdep.c
>> +
>> +/* Lookup offsets of fields in the runtime linker's 'Obj_Entry'
>> +   structure needed to determine the TLS index of an object file.  */
>> +
>> +static void
>> +fbsd_fetch_rtld_offsets (struct gdbarch *gdbarch, struct 
>> fbsd_pspace_data *data)
>> +{
>> +  TRY
>> +    {
>> +      /* Fetch offsets from debug symbols in rtld.  */
>> +      data->off_linkmap = parse_and_eval_long ("&((Obj_Entry 
>> *)0)->linkmap");
>> +      data->off_tlsindex = parse_and_eval_long ("&((Obj_Entry 
>> *)0)->tlsindex");
>> +      data->rtld_offsets_valid = true;
>> +      return;
> 
> I'm not really happy about using parse_and_eval_long with an open-coded
> equivalent of offsetof() here.  It seems we don't already have existing
> functionality for this though?  I think I could use 'lookup_struct' to 
> find
> the 'struct type *' for 'Obj_Entry', but if I used 
> 'lookup_struct_elt_type'
> to get the type of an element that doesn't give me anything that has 
> the
> offset.  We could perhaps instead add a new function 
> 'lookup_struct_elt_offset'
> that took the element name as a string and figured out the offset.  We 
> could
> then use this to provide an 'offsetof' builtin for the C language 
> perhaps.
> However, I suspect that lookup_struct_elt_offset would have to invoke a
> language-specific function to do the actual computation (just as ptype 
> /o
> handling is language-specific).

Doesn't parse_and_eval_long also call in language-specific things?  The 
expression is parsed according to whatever is the current language, I 
suppose.  If needed, I guess we could change temporarily the current 
language to C, which is the language the dynamic linker is written in 
(until proven otherwise).

The offset of fields within structures is available (see macro 
FIELD_BITPOS).  I haven't looked too deep into it, but it sounds like it 
should be possible to implement what you need with that.

One question: does this work only when you have debug info for the 
dynamic linker data structures?  Will debug info always be available?  
On some Linux distributions, for example, I think it's rather common to 
not have the debug info for the system libraries (libc, libpthread) by 
default.

Simon
John Baldwin - Feb. 7, 2019, 5:02 p.m.
On 2/6/19 9:04 PM, Simon Marchi wrote:
> On 2019-01-24 12:08, John Baldwin wrote:
>> On 1/22/19 10:42 AM, John Baldwin wrote:
>>> The fbsd_get_thread_local_address function accepts the base address of
>>> a thread's DTV array and the base address of an object file's link map
>>> and uses this to compute a TLS variable's address.  FreeBSD
>>> architectures use an architecture-specific method to determine the
>>> address of the DTV array pointer and call this helper function to
>>> perform the rest of the address calculation.
>>>
>>> 	* fbsd-tdep.c (fbsd_pspace_data_handle): New variable.
>>> 	(struct fbsd_pspace_data): New type.
>>> 	(get_fbsd_pspace_data, fbsd_pspace_data_cleanup)
>>> 	(fbsd_read_integer_by_name, fbsd_fetch_rtld_offsets)
>>> 	(fbsd_get_tls_index, fbsd_get_thread_local_address): New function.
>>> 	(_initialize_fbsd_tdep): Initialize 'fbsd_pspace_data_handle'.
>>> 	* fbsd-tdep.c (fbsd_get_thread_local_address): New prototype.
>>> ---
>>>  gdb/ChangeLog   |  10 ++++
>>>  gdb/fbsd-tdep.c | 146 
>>> ++++++++++++++++++++++++++++++++++++++++++++++++
>>>  gdb/fbsd-tdep.h |  10 ++++
>>>  3 files changed, 166 insertions(+)
>>>
>>> diff --git a/gdb/fbsd-tdep.c b/gdb/fbsd-tdep.c
>>> index d971d3a653..2e0f72d17b 100644
>>> --- a/gdb/fbsd-tdep.c
>>> +++ b/gdb/fbsd-tdep.c
>>> +
>>> +/* Lookup offsets of fields in the runtime linker's 'Obj_Entry'
>>> +   structure needed to determine the TLS index of an object file.  */
>>> +
>>> +static void
>>> +fbsd_fetch_rtld_offsets (struct gdbarch *gdbarch, struct 
>>> fbsd_pspace_data *data)
>>> +{
>>> +  TRY
>>> +    {
>>> +      /* Fetch offsets from debug symbols in rtld.  */
>>> +      data->off_linkmap = parse_and_eval_long ("&((Obj_Entry 
>>> *)0)->linkmap");
>>> +      data->off_tlsindex = parse_and_eval_long ("&((Obj_Entry 
>>> *)0)->tlsindex");
>>> +      data->rtld_offsets_valid = true;
>>> +      return;
>>
>> I'm not really happy about using parse_and_eval_long with an open-coded
>> equivalent of offsetof() here.  It seems we don't already have existing
>> functionality for this though?  I think I could use 'lookup_struct' to 
>> find
>> the 'struct type *' for 'Obj_Entry', but if I used 
>> 'lookup_struct_elt_type'
>> to get the type of an element that doesn't give me anything that has 
>> the
>> offset.  We could perhaps instead add a new function 
>> 'lookup_struct_elt_offset'
>> that took the element name as a string and figured out the offset.  We 
>> could
>> then use this to provide an 'offsetof' builtin for the C language 
>> perhaps.
>> However, I suspect that lookup_struct_elt_offset would have to invoke a
>> language-specific function to do the actual computation (just as ptype 
>> /o
>> handling is language-specific).
> 
> Doesn't parse_and_eval_long also call in language-specific things?  The 
> expression is parsed according to whatever is the current language, I 
> suppose.  If needed, I guess we could change temporarily the current 
> language to C, which is the language the dynamic linker is written in 
> (until proven otherwise).

Mmm, that's true.

> The offset of fields within structures is available (see macro 
> FIELD_BITPOS).  I haven't looked too deep into it, but it sounds like it 
> should be possible to implement what you need with that.

Hmm.  When I looked at the code for ptype /o it seemed to keep a running
tally for the byte offset.  It wasn't clear to me if perhaps FIELD_BITPOS
was for bitfields and the bit offset within a byte/word.  However, the
Linux kernel patch series does have some code to do this.  I could perhaps
steal from that to implement lookup_struct_elt_offset.  I would probably
make it support nested fields as well (I think the Linux kernel patches are
using it for some nested fields).

> One question: does this work only when you have debug info for the 
> dynamic linker data structures?  Will debug info always be available?  
> On some Linux distributions, for example, I think it's rather common to 
> not have the debug info for the system libraries (libc, libpthread) by 
> default.

Yes, this requires debug symbols.  Recent releases of FreeBSD do include an
option to install them by default, but they are not always present.  I will
probably add global variables in rtld itself similar to the ones inside of
the thread library and then update GDB to fetch those instead once I've done
that.  I just haven't patched the runtime linker yet for that.
John Baldwin - Feb. 9, 2019, 12:33 a.m.
On 2/7/19 9:02 AM, John Baldwin wrote:
> On 2/6/19 9:04 PM, Simon Marchi wrote:
>> On 2019-01-24 12:08, John Baldwin wrote:
>>> On 1/22/19 10:42 AM, John Baldwin wrote:
>>>> The fbsd_get_thread_local_address function accepts the base address of
>>>> a thread's DTV array and the base address of an object file's link map
>>>> and uses this to compute a TLS variable's address.  FreeBSD
>>>> architectures use an architecture-specific method to determine the
>>>> address of the DTV array pointer and call this helper function to
>>>> perform the rest of the address calculation.
>>>>
>>>> 	* fbsd-tdep.c (fbsd_pspace_data_handle): New variable.
>>>> 	(struct fbsd_pspace_data): New type.
>>>> 	(get_fbsd_pspace_data, fbsd_pspace_data_cleanup)
>>>> 	(fbsd_read_integer_by_name, fbsd_fetch_rtld_offsets)
>>>> 	(fbsd_get_tls_index, fbsd_get_thread_local_address): New function.
>>>> 	(_initialize_fbsd_tdep): Initialize 'fbsd_pspace_data_handle'.
>>>> 	* fbsd-tdep.c (fbsd_get_thread_local_address): New prototype.
>>>> ---
>>>>  gdb/ChangeLog   |  10 ++++
>>>>  gdb/fbsd-tdep.c | 146 
>>>> ++++++++++++++++++++++++++++++++++++++++++++++++
>>>>  gdb/fbsd-tdep.h |  10 ++++
>>>>  3 files changed, 166 insertions(+)
>>>>
>>>> diff --git a/gdb/fbsd-tdep.c b/gdb/fbsd-tdep.c
>>>> index d971d3a653..2e0f72d17b 100644
>>>> --- a/gdb/fbsd-tdep.c
>>>> +++ b/gdb/fbsd-tdep.c
>>>> +
>>>> +/* Lookup offsets of fields in the runtime linker's 'Obj_Entry'
>>>> +   structure needed to determine the TLS index of an object file.  */
>>>> +
>>>> +static void
>>>> +fbsd_fetch_rtld_offsets (struct gdbarch *gdbarch, struct 
>>>> fbsd_pspace_data *data)
>>>> +{
>>>> +  TRY
>>>> +    {
>>>> +      /* Fetch offsets from debug symbols in rtld.  */
>>>> +      data->off_linkmap = parse_and_eval_long ("&((Obj_Entry 
>>>> *)0)->linkmap");
>>>> +      data->off_tlsindex = parse_and_eval_long ("&((Obj_Entry 
>>>> *)0)->tlsindex");
>>>> +      data->rtld_offsets_valid = true;
>>>> +      return;
>>>
>>> I'm not really happy about using parse_and_eval_long with an open-coded
>>> equivalent of offsetof() here.  It seems we don't already have existing
>>> functionality for this though?  I think I could use 'lookup_struct' to 
>>> find
>>> the 'struct type *' for 'Obj_Entry', but if I used 
>>> 'lookup_struct_elt_type'
>>> to get the type of an element that doesn't give me anything that has 
>>> the
>>> offset.  We could perhaps instead add a new function 
>>> 'lookup_struct_elt_offset'
>>> that took the element name as a string and figured out the offset.  We 
>>> could
>>> then use this to provide an 'offsetof' builtin for the C language 
>>> perhaps.
>>> However, I suspect that lookup_struct_elt_offset would have to invoke a
>>> language-specific function to do the actual computation (just as ptype 
>>> /o
>>> handling is language-specific).
>>
>> Doesn't parse_and_eval_long also call in language-specific things?  The 
>> expression is parsed according to whatever is the current language, I 
>> suppose.  If needed, I guess we could change temporarily the current 
>> language to C, which is the language the dynamic linker is written in 
>> (until proven otherwise).
> 
> Mmm, that's true.

I chose to use lookup_symbol_by_language to force C.

>> The offset of fields within structures is available (see macro 
>> FIELD_BITPOS).  I haven't looked too deep into it, but it sounds like it 
>> should be possible to implement what you need with that.
> 
> Hmm.  When I looked at the code for ptype /o it seemed to keep a running
> tally for the byte offset.  It wasn't clear to me if perhaps FIELD_BITPOS
> was for bitfields and the bit offset within a byte/word.  However, the
> Linux kernel patch series does have some code to do this.  I could perhaps
> steal from that to implement lookup_struct_elt_offset.  I would probably
> make it support nested fields as well (I think the Linux kernel patches are
> using it for some nested fields).

So this was actually simpler than I expected.  The existing
lookup_struct_elt_type didn't work because you can't work back from the
type to get to the field.  So instead I added a new function that returns
the field and the offset (you have to compute an offset still to handle
anonymous union/struct members) that is fairly similar to the lk_find_field
function in the Linux kernel patchset (and hopefully can be used in place of
that with minimal fuss).  It does return bitpositions that the consumer has
to convert to bytes, but that seems consistent and is easy to fix in the
consumer.  I then used this to replace the parse_and_eval_long above.  I'll
post a v3 with that.  I also reworded the commit message a bit for the debug
file patch you looked at and cleaned up some cruft from lookup_struct_elt_type.

Some other cleanup we might consider would be to reimplement the
lookup_struct_typedef in terms of lookup_struct (or vice versa), and to also
perhaps add a common version of the lk_find_type function from the Linux
kernel patchset that lets you name the structure type either by the tag
("foo" of "struct foo") or by a typedef ("foo_t" from
"typedef struct { } foo_t").

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 7c46bb0009..09e57e7474 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,13 @@ 
+2019-01-22  John Baldwin  <jhb@FreeBSD.org>
+
+	* fbsd-tdep.c (fbsd_pspace_data_handle): New variable.
+	(struct fbsd_pspace_data): New type.
+	(get_fbsd_pspace_data, fbsd_pspace_data_cleanup)
+	(fbsd_read_integer_by_name, fbsd_fetch_rtld_offsets)
+	(fbsd_get_tls_index, fbsd_get_thread_local_address): New function.
+	(_initialize_fbsd_tdep): Initialize 'fbsd_pspace_data_handle'.
+	* fbsd-tdep.c (fbsd_get_thread_local_address): New prototype.
+
 2019-01-22  John Baldwin  <jhb@FreeBSD.org>
 
 	* gdbarch.sh (get_thread_local_address): New method.
diff --git a/gdb/fbsd-tdep.c b/gdb/fbsd-tdep.c
index d971d3a653..2e0f72d17b 100644
--- a/gdb/fbsd-tdep.c
+++ b/gdb/fbsd-tdep.c
@@ -24,6 +24,7 @@ 
 #include "regcache.h"
 #include "regset.h"
 #include "gdbthread.h"
+#include "objfiles.h"
 #include "xml-syscall.h"
 #include <sys/socket.h>
 #include <arpa/inet.h>
@@ -444,6 +445,41 @@  get_fbsd_gdbarch_data (struct gdbarch *gdbarch)
 	  gdbarch_data (gdbarch, fbsd_gdbarch_data_handle));
 }
 
+/* Per-program-space data for FreeBSD architectures.  */
+static const struct program_space_data *fbsd_pspace_data_handle;
+
+struct fbsd_pspace_data
+  {
+    /* Offsets in the runtime linker's 'Obj_Entry' structure.  */
+    LONGEST off_linkmap;
+    LONGEST off_tlsindex;
+    bool rtld_offsets_valid;
+  };
+
+static struct fbsd_pspace_data *
+get_fbsd_pspace_data (struct program_space *pspace)
+{
+  struct fbsd_pspace_data *data;
+
+  data = ((struct fbsd_pspace_data *)
+	  program_space_data (pspace, fbsd_pspace_data_handle));
+  if (data == NULL)
+    {
+      data = XCNEW (struct fbsd_pspace_data);
+      set_program_space_data (pspace, fbsd_pspace_data_handle, data);
+    }
+
+  return data;
+}
+
+/* The cleanup callback for FreeBSD architecture per-program-space data.  */
+
+static void
+fbsd_pspace_data_cleanup (struct program_space *pspace, void *data)
+{
+  xfree (data);
+}
+
 /* This is how we want PTIDs from core files to be printed.  */
 
 static const char *
@@ -1932,6 +1968,114 @@  fbsd_get_syscall_number (struct gdbarch *gdbarch, thread_info *thread)
   internal_error (__FILE__, __LINE__, _("fbsd_get_sycall_number called"));
 }
 
+/* Read an integer symbol value from the current target.  */
+
+static LONGEST
+fbsd_read_integer_by_name (struct gdbarch *gdbarch, const char *name)
+{
+  bound_minimal_symbol ms = lookup_minimal_symbol (name, NULL, NULL);
+  if (ms.minsym == NULL)
+    error (_("Unable to resolve symbol '%s'"), name);
+
+  gdb_byte buf[4];
+  if (target_read_memory (BMSYMBOL_VALUE_ADDRESS (ms), buf, sizeof buf) != 0)
+    error (_("Unable to read value of '%s'"), name);
+
+  return extract_signed_integer (buf, sizeof buf, gdbarch_byte_order (gdbarch));
+}
+
+/* Lookup offsets of fields in the runtime linker's 'Obj_Entry'
+   structure needed to determine the TLS index of an object file.  */
+
+static void
+fbsd_fetch_rtld_offsets (struct gdbarch *gdbarch, struct fbsd_pspace_data *data)
+{
+  TRY
+    {
+      /* Fetch offsets from debug symbols in rtld.  */
+      data->off_linkmap = parse_and_eval_long ("&((Obj_Entry *)0)->linkmap");
+      data->off_tlsindex = parse_and_eval_long ("&((Obj_Entry *)0)->tlsindex");
+      data->rtld_offsets_valid = true;
+      return;
+    }
+  CATCH (e, RETURN_MASK_ERROR)
+    {
+      data->off_linkmap = -1;
+    }
+  END_CATCH
+
+  TRY
+    {
+      /* Fetch offsets from global variables in libthr.  Note that
+	 this does not work for single-threaded processes that are not
+	 linked against libthr.  */
+      data->off_linkmap = fbsd_read_integer_by_name(gdbarch,
+						    "_thread_off_linkmap");
+      data->off_tlsindex = fbsd_read_integer_by_name(gdbarch,
+						     "_thread_off_tlsindex");
+      data->rtld_offsets_valid = true;
+      return;
+    }
+  CATCH (e, RETURN_MASK_ERROR)
+    {
+      data->off_linkmap = -1;
+    }
+  END_CATCH
+}
+
+/* Helper function to read the TLS index of an object file associated
+   with a link map entry at LM_ADDR.  */
+
+static LONGEST
+fbsd_get_tls_index (struct gdbarch *gdbarch, CORE_ADDR lm_addr)
+{
+  struct fbsd_pspace_data *data = get_fbsd_pspace_data (current_program_space);
+
+  if (!data->rtld_offsets_valid)
+    fbsd_fetch_rtld_offsets (gdbarch, data);
+
+  if (data->off_linkmap == -1)
+    throw_error (TLS_GENERIC_ERROR,
+		 _("Cannot fetch runtime linker structure offsets"));
+
+  /* Simulate container_of to convert from LM_ADDR to the Obj_Entry
+     pointer and then compute the offset of the tlsindex member.  */
+  CORE_ADDR tlsindex_addr = lm_addr - data->off_linkmap + data->off_tlsindex;
+
+  gdb_byte buf[4];
+  if (target_read_memory (tlsindex_addr, buf, sizeof buf) != 0)
+    throw_error (TLS_GENERIC_ERROR,
+		 _("Cannot find thread-local variables on this target"));
+
+  return extract_signed_integer (buf, sizeof buf, gdbarch_byte_order (gdbarch));
+}
+
+/* See fbsd-tdep.h.  */
+
+CORE_ADDR
+fbsd_get_thread_local_address (struct gdbarch *gdbarch, CORE_ADDR dtv_addr,
+			       CORE_ADDR lm_addr, CORE_ADDR offset)
+{
+  LONGEST tls_index = fbsd_get_tls_index (gdbarch, lm_addr);
+
+  gdb_byte buf[gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT];
+  if (target_read_memory (dtv_addr, buf, sizeof buf) != 0)
+    throw_error (TLS_GENERIC_ERROR,
+		 _("Cannot find thread-local variables on this target"));
+
+  const struct builtin_type *builtin = builtin_type (gdbarch);
+  CORE_ADDR addr = gdbarch_pointer_to_address (gdbarch,
+					       builtin->builtin_data_ptr, buf);
+
+  addr += (tls_index + 1) * TYPE_LENGTH (builtin->builtin_data_ptr);
+  if (target_read_memory (addr, buf, sizeof buf) != 0)
+    throw_error (TLS_GENERIC_ERROR,
+		 _("Cannot find thread-local variables on this target"));
+
+  addr = gdbarch_pointer_to_address (gdbarch, builtin->builtin_data_ptr, buf);
+  return addr + offset;
+}
+
 /* To be called from GDB_OSABI_FREEBSD handlers. */
 
 void
@@ -1957,4 +2101,6 @@  _initialize_fbsd_tdep (void)
 {
   fbsd_gdbarch_data_handle =
     gdbarch_data_register_post_init (init_fbsd_gdbarch_data);
+  fbsd_pspace_data_handle
+    = register_program_space_data_with_cleanup (NULL, fbsd_pspace_data_cleanup);
 }
diff --git a/gdb/fbsd-tdep.h b/gdb/fbsd-tdep.h
index efd7c2c78f..0b4d1e92c7 100644
--- a/gdb/fbsd-tdep.h
+++ b/gdb/fbsd-tdep.h
@@ -60,4 +60,14 @@  extern void fbsd_info_proc_mappings_entry (int addr_bit, ULONGEST kve_start,
 					   int kve_flags, int kve_protection,
 					   const void *kve_path);
 
+/* Helper function to fetch the address of a thread-local variable.
+   DTV_ADDR is the base address of the thread's dtv array.  LM_ADDR is
+   the address of the link_map structure for the associated object
+   file.  */
+
+extern CORE_ADDR fbsd_get_thread_local_address (struct gdbarch *gdbarch,
+						CORE_ADDR dtv_addr,
+						CORE_ADDR lm_addr,
+						CORE_ADDR offset);
+
 #endif /* fbsd-tdep.h */