Skip to content

Fix incorrect tests and DTV extraction, it is always indirect for gli…#34

Closed
dalehamel wants to merge 1 commit intomainfrom
dtv-fixes
Closed

Fix incorrect tests and DTV extraction, it is always indirect for gli…#34
dalehamel wants to merge 1 commit intomainfrom
dtv-fixes

Conversation

@dalehamel
Copy link
Copy Markdown
Member

@dalehamel dalehamel commented Mar 27, 2026

In #929 I incorrectly added a field, 'Indirect' to DTV, copying the structure of TSDInfo. I had believed it was necessary at the time, but on closer examination it doesn't seem to every be the case that it is used - DTV is always accessed Indirectly.

During the review of open-telemetry#1226, the first actual use of the DTV, nsavoire correctly pointed this out.

I could not find a single case where it was actually directly in the TCB and I think even for esoteric libc's it would likely still be indirect, as you need to be able to update it if new libs get added.

To simplify the usage, the Indirect member is removed from the DTV struct.

My apologies for missing this and submitting incorrect code in the first place, as I've been switching tasks it has been difficult to keep the context in my head.

Proof that DTV is always accessed indirectly

glibc — TCB Structure & DTV Access

File: sysdeps/x86_64/nptl/tls.h (https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/nptl/tls.h;h=836b2e22440c0fb437444b6fca2dc677aae98eab;hb=6abe432ec4aa1456151be8f9567c4d68f41d68f7#l46)

  typedef struct
  {
    void *tcb;            /* Pointer to the TCB.  Not necessarily the
                             thread descriptor used by libpthread.  */
    dtv_t *dtv;           // <-- POINTER at offset 0x8 from FS base
    void *self;           /* Pointer to the thread descriptor.  */
    ...
  } tcbhead_t;

The dtv field is a pointer (dtv_t *dtv) at offset 8 in the TCB. It is not an inline array. So FS:0x8 reads a pointer to the DTV, not DTV data itself.

DTV access macro (same file):

  #define GET_DTV(descr) \
    (((tcbhead_t *) (descr))->dtv)

DTV allocation in elf/dl-tls.c (https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/dl-tls.c;h=edf31383b22ada95103d4c4be0aeda84a5e5e3ef;hb=6abe432ec4aa1456151be8f9567c4d68f41d68f7#l466):

  static void *
  allocate_dtv (void *result)
  {
    dtv_t *dtv;
    size_t dtv_length;
    size_t max_modid = atomic_load_relaxed (&GL(dl_tls_max_dtv_idx));
    dtv_length = max_modid + DTV_SURPLUS;
    dtv = calloc (dtv_length + 2, sizeof (dtv_t));  // <-- heap allocated
    if (dtv != NULL)
      {
        dtv[0].counter = dtv_length;
        INSTALL_DTV (result, dtv);  // stores pointer into tcbhead_t.dtv
      }
    ...
  }

__tls_get_addr in elf/dl-tls.c (https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/dl-tls.c;h=edf31383b22ada95103d4c4be0aeda84a5e5e3ef;hb=6abe432ec4aa1456151be8f9567c4d68f41d68f7#l1091):

  void *
  __tls_get_addr (tls_index *ti)
  {
    dtv_t *dtv = THREAD_DTV ();      // reads the POINTER from tcbhead_t.dtv
    ...
    void *p = dtv[ti->ti_module].pointer.val;  // indexes into the pointed-to array
    ...
    return tls_get_addr_adjust (p, ti);
  }

THREAD_DTV() macro (https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/nptl/tls.h;h=836b2e22440c0fb437444b6fca2dc677aae98eab;hb=6abe432ec4aa1456151be8f9567c4d68f41d68f7#l166):

  #define THREAD_DTV() \
    ({ struct pthread *__pd;                    \
       THREAD_GETMEM (__pd, header.dtv); })

This reads header.dtv from the thread struct — i.e., loads the pointer.

musl — pthread struct & DTV Access

File: src/internal/pthread_impl.h (https://git.musl-libc.org/cgit/musl/tree/src/internal/pthread_impl.h?id=9fa28ece75d8a2191de7c5bb53bed224c5947417#n23)

  struct pthread {
    struct pthread *self;
  #ifndef TLS_ABOVE_TP
    uintptr_t *dtv;       // <-- POINTER, not inline array
  #endif
    ...
  };

The dtv field is uintptr_t *dtv — a pointer to a separately-allocated array.

DTV allocation in src/env/__init_tls.c (https://git.musl-libc.org/cgit/musl/tree/src/env/__init_tls.c?id=9fa28ece75d8a2191de7c5bb53bed224c5947417#n39):

  void *__copy_tls(unsigned char *mem)
  {
    ...
    uintptr_t *dtv;
  #ifdef TLS_ABOVE_TP
    dtv = (uintptr_t*)(mem + libc.tls_size) - (libc.tls_cnt + 1);
  #else
    dtv = (uintptr_t *)mem;          // allocated as part of the TLS block
  #endif
    ...
    td->dtv = dtv;                   // store POINTER into pthread.dtv
    return td;
  }

DTV updates in ldso/dynlink.c (https://git.musl-libc.org/cgit/musl/tree/ldso/dynlink.c?id=9fa28ece75d8a2191de7c5bb53bed224c5947417#n1647):

  static void install_new_tls(void)
  {
    ...
    uintptr_t (*newdtv)[tls_cnt+1] = (void *)dtv_provider->new_dtv;
    ...
    /* Install new dtv for each thread. */
    for (j=0, td=self; !j || td!=self; j++, td=td->next) {
      td->dtv = newdtv[j];           // replaces the POINTER
    }
    ...
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant