Access to TLS variables on GNU/Linux

I'm trying to access thread-local variables using the API on GNU/Linux.
Here's my test program:

#include <err.h>
#include <sys/wait.h>
#include <unistd.h>

#include <lldb/API/SBDebugger.h>
#include <lldb/API/SBProcess.h>
#include <lldb/API/SBTarget.h>

thread_local int global_tls_variable
  __attribute__ ((tls_model ("initial-exec")))= 17;

int
main(void)
{
  // Target process for the debugger.
  pid_t pid = fork();
  if (pid < 0)
    err(1, "fork");
  if (pid == 0)
    while (true)
      pause();

  lldb::SBDebugger::Initialize();
  {
    lldb::SBDebugger debugger{lldb::SBDebugger::Create()};
    if (!debugger.IsValid())
      errx(1, "SBDebugger::Create failed");

    lldb::SBTarget target{debugger.CreateTarget(nullptr)};
    if (!target.IsValid())
      errx(1, "SBDebugger::CreateTarget failed");

    lldb::SBAttachInfo attachinfo(pid);
    lldb::SBError error;
    lldb::SBProcess process{target.Attach(attachinfo, error)};
    if (!process.IsValid())
      errx(1, "SBTarget::Attach failed: %s", error.GetCString());

    lldb::SBValue value{target.FindFirstGlobalVariable("global_tls_variable")};
    if (!value.IsValid())
      errx(1, "SBTarget::FindFirstGlobalVariable: %s",
           value.GetError().GetCString());
    printf("global_tls_variable (LLDB): %d\n",
           (int) value.GetValueAsSigned());
    printf("value type: %d\n", (int) value.GetValueType());
  }
  lldb::SBDebugger::Terminate();

  if (kill(pid, SIGKILL) != 0)
    err(1, "kill");
  if (waitpid(pid, NULL, 0) < 0)
    err(1, "waitpid");

  return 0;
}

It prints:

global_tls_variable (LLDB): 0
value type: 4

The target process has loaded libpthread.so.0, so it's not the usual
problem of libthread_db not working without libpthread.

On the other hand, I realize now that the lldb command cannot access TLS
variables, either. Is this expected to work at all?

I'm using lldb-7.0.1-1.fc29.x86_64 from Fedora 29 (which is built around
GCC 8 and glibc 2.28).

Thanks,
Florian

TLS is implemented only for FreeBSD as there is
FreeBSDThread::GetThreadPointer() but on Linux it falls back to unimplemented:
  lldb::addr_t Thread::GetThreadPointer() { return LLDB_INVALID_ADDRESS; }

On Linux it uses DynamicLoaderPOSIXDYLD::GetThreadLocalData() which may work
without libthread_db as it is reading "_thread_db_*" symbols in
DYLDRendezvous::GetThreadInfo(). But it needs that GetThreadPointer() which
could get implemented (for x86_64) by reading %fs_base. LLDB currently does
not know anything about %fs_base+%gs_base.

Is it a good idea to implement %fs_base+%gs_base to make TLS working on Linux?

Jan

* Jan Kratochvil:

The target process has loaded libpthread.so.0, so it's not the usual
problem of libthread_db not working without libpthread.

On the other hand, I realize now that the lldb command cannot access TLS
variables, either. Is this expected to work at all?

TLS is implemented only for FreeBSD as there is
FreeBSDThread::GetThreadPointer() but on Linux it falls back to unimplemented:
  lldb::addr_t Thread::GetThreadPointer() { return LLDB_INVALID_ADDRESS; }

On Linux it uses DynamicLoaderPOSIXDYLD::GetThreadLocalData() which may work
without libthread_db as it is reading "_thread_db_*" symbols in
DYLDRendezvous::GetThreadInfo(). But it needs that GetThreadPointer() which
could get implemented (for x86_64) by reading %fs_base. LLDB currently does
not know anything about %fs_base+%gs_base.

If I can get the TLS base address on x86 for a thread and if LLDB can
expose the offset of an initial-exec TLS variable inside the TLS block
of an object (which should be encoded in the ELF data), I can poke at
glibc internals and figure out the offset from thread pointer.

(Global-dynamic TLS is much more difficult to handle, of course.)

Is it a good idea to implement %fs_base+%gs_base to make TLS working
on Linux?

The register access would help, I think. Even if the rest doesn't work.
If you have an experimental build, I can try it.

Thanks,
Florian