Skip to content

[lldb] Adjust DynamicLoaderDarwin::GetThreadLocalData to support changes to TLS on macOS #77854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

bulbazord
Copy link
Member

TLS implementation on Apple OSes has changed. Instead of acquiring a pthread_key and calling pthread_getspecific, we instead acquire a function pointer and perform a function call with it. This fixes accessing thread local storage on macOS 14 (Sonoma) and newer.

Note: Some versions of Apple's new linker do not emit debug symbols for TLS symbols. This causes the TLS tests to fail because LLDB and dsymutil expects there to be debug symbols to resolve the relevant TLS block. You may work around this by switching to the older linker or disabling the test until you have a newer version of the new linker.

rdar://120676969

…ges to TLS on macOS

TLS implementation on Apple OSes has changed. Instead of acquiring a
pthread_key and calling pthread_getspecific, we instead acquire a
function pointer and perform a function call with
it. This fixes accessing thread local storage on macOS 14 (Sonoma) and
newer.

Note: Some versions of Apple's new linker do not emit debug symbols for
TLS symbols. This causes the TLS tests to fail because LLDB and dsymutil
expects there to be debug symbols to resolve the relevant TLS block.
You may work around this by switching to the older linker or disabling
the test until you have a newer version of the new linker.
@llvmbot
Copy link
Member

llvmbot commented Jan 12, 2024

@llvm/pr-subscribers-lldb

Author: Alex Langford (bulbazord)

Changes

TLS implementation on Apple OSes has changed. Instead of acquiring a pthread_key and calling pthread_getspecific, we instead acquire a function pointer and perform a function call with it. This fixes accessing thread local storage on macOS 14 (Sonoma) and newer.

Note: Some versions of Apple's new linker do not emit debug symbols for TLS symbols. This causes the TLS tests to fail because LLDB and dsymutil expects there to be debug symbols to resolve the relevant TLS block. You may work around this by switching to the older linker or disabling the test until you have a newer version of the new linker.

rdar://120676969


Full diff: https://github.com/llvm/llvm-project/pull/77854.diff

1 Files Affected:

  • (modified) lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp (+98-61)
diff --git a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp
index 1e3e2e5641ad83..7c5c0efd9ba464 100644
--- a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp
+++ b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp
@@ -1048,70 +1048,107 @@ DynamicLoaderDarwin::GetThreadLocalData(const lldb::ModuleSP module_sp,
 
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
 
-  const uint32_t addr_size = m_process->GetAddressByteSize();
+  lldb_private::Address tls_addr;
+  if (!module_sp->ResolveFileAddress(tls_file_addr, tls_addr))
+    return LLDB_INVALID_ADDRESS;
+
+  Target &target = m_process->GetTarget();
   uint8_t buf[sizeof(lldb::addr_t) * 3];
+  const uint32_t addr_size = m_process->GetAddressByteSize();
+  const size_t tls_data_size = addr_size * 3;
+  Status error;
+  const size_t bytes_read = target.ReadMemory(
+      tls_addr, buf, tls_data_size, error, /*force_live_memory = */ true);
+  if (bytes_read != tls_data_size || error.Fail())
+    return LLDB_INVALID_ADDRESS;
 
-  lldb_private::Address tls_addr;
-  if (module_sp->ResolveFileAddress(tls_file_addr, tls_addr)) {
-    Status error;
-    const size_t tsl_data_size = addr_size * 3;
-    Target &target = m_process->GetTarget();
-    if (target.ReadMemory(tls_addr, buf, tsl_data_size, error, true) ==
-        tsl_data_size) {
-      const ByteOrder byte_order = m_process->GetByteOrder();
-      DataExtractor data(buf, sizeof(buf), byte_order, addr_size);
-      lldb::offset_t offset = addr_size; // Skip the first pointer
-      const lldb::addr_t pthread_key = data.GetAddress(&offset);
-      const lldb::addr_t tls_offset = data.GetAddress(&offset);
-      if (pthread_key != 0) {
-        // First check to see if we have already figured out the location of
-        // TLS data for the pthread_key on a specific thread yet. If we have we
-        // can re-use it since its location will not change unless the process
-        // execs.
-        const tid_t tid = thread_sp->GetID();
-        auto tid_pos = m_tid_to_tls_map.find(tid);
-        if (tid_pos != m_tid_to_tls_map.end()) {
-          auto tls_pos = tid_pos->second.find(pthread_key);
-          if (tls_pos != tid_pos->second.end()) {
-            return tls_pos->second + tls_offset;
-          }
+  DataExtractor data(buf, sizeof(buf), m_process->GetByteOrder(), addr_size);
+  lldb::offset_t offset = 0;
+  const lldb::addr_t thunk_addr = data.GetAddress(&offset);
+  const lldb::addr_t key = data.GetAddress(&offset);
+  const lldb::addr_t tls_offset = data.GetAddress(&offset);
+
+  TypeSystemClangSP scratch_ts_sp =
+      ScratchTypeSystemClang::GetForTarget(target);
+  if (!scratch_ts_sp)
+    return LLVM_INVALID_ADDRESS;
+  CompilerType clang_void_ptr_type =
+      scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType();
+  EvaluateExpressionOptions options;
+  DiagnosticManager execution_errors;
+  ExecutionContext exe_ctx(thread_sp);
+
+  // On modern apple platforms, there is a small data structure that looks
+  // approximately like this:
+  // struct TLS_Thunk {
+  //  void *(*get_addr)(struct TLS_Thunk *);
+  //  size_t key;
+  //  size_t offset;
+  // }
+  //
+  // The strategy is to take get_addr from the structure, call it with the
+  // address of the containing TLS_Thunk structure, and add the offset to the
+  // resulting pointer.
+
+  if (thunk_addr != 0) {
+    Address thunk_load_addr;
+    if (target.ResolveLoadAddress(thunk_addr, thunk_load_addr)) {
+      const lldb::addr_t tls_load_addr = tls_addr.GetLoadAddress(&target);
+      ThreadPlanSP thread_plan_sp(new ThreadPlanCallFunction(
+          *thread_sp, thunk_load_addr, clang_void_ptr_type,
+          llvm::ArrayRef<lldb::addr_t>(tls_load_addr), options));
+
+      lldb::ExpressionResults results = m_process->RunThreadPlan(
+          exe_ctx, thread_plan_sp, options, execution_errors);
+      if (results == lldb::eExpressionCompleted) {
+        if (lldb::ValueObjectSP result_valobj_sp =
+                thread_plan_sp->GetReturnValueObject()) {
+          const lldb::addr_t pointer_to_tls_data =
+              result_valobj_sp->GetValueAsUnsigned(0);
+          if (pointer_to_tls_data)
+            return pointer_to_tls_data;
         }
-        StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(0);
-        if (frame_sp) {
-          TypeSystemClangSP scratch_ts_sp =
-              ScratchTypeSystemClang::GetForTarget(target);
-
-          if (!scratch_ts_sp)
-            return LLDB_INVALID_ADDRESS;
-
-          CompilerType clang_void_ptr_type =
-              scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType();
-          Address pthread_getspecific_addr = GetPthreadSetSpecificAddress();
-          if (pthread_getspecific_addr.IsValid()) {
-            EvaluateExpressionOptions options;
-
-            lldb::ThreadPlanSP thread_plan_sp(new ThreadPlanCallFunction(
-                *thread_sp, pthread_getspecific_addr, clang_void_ptr_type,
-                llvm::ArrayRef<lldb::addr_t>(pthread_key), options));
-
-            DiagnosticManager execution_errors;
-            ExecutionContext exe_ctx(thread_sp);
-            lldb::ExpressionResults results = m_process->RunThreadPlan(
-                exe_ctx, thread_plan_sp, options, execution_errors);
-
-            if (results == lldb::eExpressionCompleted) {
-              lldb::ValueObjectSP result_valobj_sp =
-                  thread_plan_sp->GetReturnValueObject();
-              if (result_valobj_sp) {
-                const lldb::addr_t pthread_key_data =
-                    result_valobj_sp->GetValueAsUnsigned(0);
-                if (pthread_key_data) {
-                  m_tid_to_tls_map[tid].insert(
-                      std::make_pair(pthread_key, pthread_key_data));
-                  return pthread_key_data + tls_offset;
-                }
-              }
-            }
+      }
+    }
+  }
+
+  // If the previous approach fails, we will attempt to fall back to the older
+  // implementation. In this scenario, the key element of TLS_Thunk is a pthread
+  // key type. This can be passed to pthread_getspecific to get the address
+  // of the TLS block, which we then add offset to.
+
+  if (key != 0) {
+    // First check to see if we have already figured out the location of
+    // TLS data for the pthread_key on a specific thread yet. If we have we
+    // can re-use it since its location will not change unless the process
+    // execs.
+    const tid_t tid = thread_sp->GetID();
+    auto tid_pos = m_tid_to_tls_map.find(tid);
+    if (tid_pos != m_tid_to_tls_map.end()) {
+      auto tls_pos = tid_pos->second.find(key);
+      if (tls_pos != tid_pos->second.end()) {
+        return tls_pos->second + tls_offset;
+      }
+    }
+    Address pthread_getspecific_addr = GetPthreadSetSpecificAddress();
+    if (pthread_getspecific_addr.IsValid()) {
+
+      lldb::ThreadPlanSP thread_plan_sp(new ThreadPlanCallFunction(
+          *thread_sp, pthread_getspecific_addr, clang_void_ptr_type,
+          llvm::ArrayRef<lldb::addr_t>(key), options));
+
+      lldb::ExpressionResults results = m_process->RunThreadPlan(
+          exe_ctx, thread_plan_sp, options, execution_errors);
+
+      if (results == lldb::eExpressionCompleted) {
+        lldb::ValueObjectSP result_valobj_sp =
+            thread_plan_sp->GetReturnValueObject();
+        if (result_valobj_sp) {
+          const lldb::addr_t pthread_key_data =
+              result_valobj_sp->GetValueAsUnsigned(0);
+          if (pthread_key_data) {
+            m_tid_to_tls_map[tid].insert(std::make_pair(key, pthread_key_data));
+            return pthread_key_data + tls_offset;
           }
         }
       }

Comment on lines +1090 to +1091
// address of the containing TLS_Thunk structure, and add the offset to the
// resulting pointer.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to double check this actually. Not sure if we need to add the offset.

// void *(*get_addr)(struct TLS_Thunk *);
// size_t key;
// size_t offset;
// }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this comment would be more appropriate a few lines above? It seems like we are trying to find these fields around line 1067

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 as this is also shared between the "old" and "new" implementation.

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this is a little hard to read given a bunch of shared setup followed by two possible ways to read the TLS data. If those implementations don't require too many inputs, it might be worth splitting this out into two helper functions to make this easier to follow and extend in the future.

@bulbazord
Copy link
Member Author

bulbazord commented Jan 12, 2024

Currently this is a little hard to read given a bunch of shared setup followed by two possible ways to read the TLS data. If those implementations don't require too many inputs, it might be worth splitting this out into two helper functions to make this easier to follow and extend in the future.

I can do that. Since this change is quite large as-is, perhaps I should break this PR up into smaller changes and submit them separately?

@clayborg
Copy link
Collaborator

It will be nice to not have the TLS test failing on macOS anymore!

@bulbazord
Copy link
Member Author

I broke up this PR into multiple commits and resubmitted at #77988. Please take a look!

@bulbazord bulbazord closed this Jan 12, 2024
@bulbazord bulbazord deleted the new-tls-impl branch January 12, 2024 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants