jasonmolenda created this revision.
jasonmolenda added reviewers: JDevlieghere, jingham.
jasonmolenda added a project: LLDB.
Herald added a subscriber: kristof.beyls.
Herald added a project: All.
jasonmolenda requested review of this revision.
Herald added a subscriber: lldb-commits.

When you are very early in process startup, before the system libraries have 
been initialized, and you run a simple expression, on Darwin systems, the 
Objective-C runtime plugin will run two utility functions to fetch the list of 
Objective-C class names in the inferior process.  These function calls can 
cause problems when the process launch is this early, and the user expression 
may have been a simple one like "p globalvar=1" which would be harmless and not 
require a jitted expression.

This patch adds code to debugserver to use libdyld calls to find the process 
launch state, returns it in a JSON reply for a "jGetDyldProcessState" packet.  
This is passed up to DynamicLoaderMacOS::IsFullyInitialized() which checks for 
three specific process states that happen early in process startup, before 
system library initialization is completed.

Thread::SafeToCallFunctions() currently, on macOS, checks to see if the current 
thread is in __select, and does not run utility functions if it is.  This patch 
updates SafeToCallFunctions() to also call DynamicLoader::IsFullyInitialized(), 
and avoid running utility functions if that is the case.

Then there are updates to AppleObjCRuntimeV2 to check the threads for 
SafeToCallFunctions() before scanning for dynamic objective-c classes, or the 
static shared cache objective-c classes.

I added a test case which stops process launch on the first malloc() call, runs 
a simple expression, and confirms (via the types log) that we did not read the 
objc class lists.  Then it continues to main(), runs the simple expression 
again, and confirms (via types log) that it did read the objc class list.

The patch looks a bit big, mostly from piping the data from 
MachProcess::GetDyldProcessState() up to 
DynamicLoaderMacOS::IsFullyInitialized() through a handful of layers.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D139054

Files:
  lldb/docs/lldb-gdb-remote.txt
  lldb/include/lldb/Target/Process.h
  lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.cpp
  lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.h
  
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
  lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h
  lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
  lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
  lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
  lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
  lldb/source/Target/Thread.cpp
  lldb/test/API/macosx/early-process-launch/Makefile
  lldb/test/API/macosx/early-process-launch/TestEarlyProcessLaunch.py
  lldb/test/API/macosx/early-process-launch/main.c
  lldb/tools/debugserver/source/DNB.cpp
  lldb/tools/debugserver/source/DNB.h
  lldb/tools/debugserver/source/MacOSX/MachProcess.h
  lldb/tools/debugserver/source/MacOSX/MachProcess.mm
  lldb/tools/debugserver/source/RNBRemote.cpp
  lldb/tools/debugserver/source/RNBRemote.h

Index: lldb/tools/debugserver/source/RNBRemote.h
===================================================================
--- lldb/tools/debugserver/source/RNBRemote.h
+++ lldb/tools/debugserver/source/RNBRemote.h
@@ -108,34 +108,35 @@
     json_query_get_shared_cache_info,              // 'jGetSharedCacheInfo'
     pass_signals_to_inferior,                      // 'QPassSignals'
     start_noack_mode,                              // 'QStartNoAckMode'
-    prefix_reg_packets_with_tid,        // 'QPrefixRegisterPacketsWithThreadID
-    set_logging_mode,                   // 'QSetLogging:'
-    set_ignored_exceptions,             // 'QSetIgnoredExceptions'           
-    set_max_packet_size,                // 'QSetMaxPacketSize:'
-    set_max_payload_size,               // 'QSetMaxPayloadSize:'
-    set_environment_variable,           // 'QEnvironment:'
-    set_environment_variable_hex,       // 'QEnvironmentHexEncoded:'
-    set_launch_arch,                    // 'QLaunchArch:'
-    set_disable_aslr,                   // 'QSetDisableASLR:'
-    set_stdin,                          // 'QSetSTDIN:'
-    set_stdout,                         // 'QSetSTDOUT:'
-    set_stderr,                         // 'QSetSTDERR:'
-    set_working_dir,                    // 'QSetWorkingDir:'
-    set_list_threads_in_stop_reply,     // 'QListThreadsInStopReply:'
-    sync_thread_state,                  // 'QSyncThreadState:'
-    memory_region_info,                 // 'qMemoryRegionInfo:'
-    get_profile_data,                   // 'qGetProfileData'
-    set_enable_profiling,               // 'QSetEnableAsyncProfiling'
-    enable_compression,                 // 'QEnableCompression:'
-    watchpoint_support_info,            // 'qWatchpointSupportInfo:'
-    allocate_memory,                    // '_M'
-    deallocate_memory,                  // '_m'
-    set_process_event,                  // 'QSetProcessEvent:'
-    save_register_state,                // '_g'
-    restore_register_state,             // '_G'
-    speed_test,                         // 'qSpeedTest:'
-    set_detach_on_error,                // 'QSetDetachOnError:'
-    query_transfer,                     // 'qXfer:'
+    prefix_reg_packets_with_tid,    // 'QPrefixRegisterPacketsWithThreadID
+    set_logging_mode,               // 'QSetLogging:'
+    set_ignored_exceptions,         // 'QSetIgnoredExceptions'
+    set_max_packet_size,            // 'QSetMaxPacketSize:'
+    set_max_payload_size,           // 'QSetMaxPayloadSize:'
+    set_environment_variable,       // 'QEnvironment:'
+    set_environment_variable_hex,   // 'QEnvironmentHexEncoded:'
+    set_launch_arch,                // 'QLaunchArch:'
+    set_disable_aslr,               // 'QSetDisableASLR:'
+    set_stdin,                      // 'QSetSTDIN:'
+    set_stdout,                     // 'QSetSTDOUT:'
+    set_stderr,                     // 'QSetSTDERR:'
+    set_working_dir,                // 'QSetWorkingDir:'
+    set_list_threads_in_stop_reply, // 'QListThreadsInStopReply:'
+    sync_thread_state,              // 'QSyncThreadState:'
+    memory_region_info,             // 'qMemoryRegionInfo:'
+    get_profile_data,               // 'qGetProfileData'
+    set_enable_profiling,           // 'QSetEnableAsyncProfiling'
+    enable_compression,             // 'QEnableCompression:'
+    watchpoint_support_info,        // 'qWatchpointSupportInfo:'
+    allocate_memory,                // '_M'
+    deallocate_memory,              // '_m'
+    set_process_event,              // 'QSetProcessEvent:'
+    save_register_state,            // '_g'
+    restore_register_state,         // '_G'
+    speed_test,                     // 'qSpeedTest:'
+    set_detach_on_error,            // 'QSetDetachOnError:'
+    query_transfer,                 // 'qXfer:'
+    json_query_dyld_process_state,  // 'jGetDyldProcessState'
     unknown_type
   };
 
@@ -246,6 +247,7 @@
   rnb_err_t HandlePacket_qXfer(const char *p);
   rnb_err_t HandlePacket_stop_process(const char *p);
   rnb_err_t HandlePacket_QSetDetachOnError(const char *p);
+  rnb_err_t HandlePacket_jGetDyldProcessState(const char *p);
   rnb_err_t SendStopReplyPacketForThread(nub_thread_t tid);
   rnb_err_t SendHexEncodedBytePacket(const char *header, const void *buf,
                                      size_t buf_len, const char *footer);
Index: lldb/tools/debugserver/source/RNBRemote.cpp
===================================================================
--- lldb/tools/debugserver/source/RNBRemote.cpp
+++ lldb/tools/debugserver/source/RNBRemote.cpp
@@ -499,6 +499,10 @@
       "Test the maximum speed at which packet can be sent/received."));
   t.push_back(Packet(query_transfer, &RNBRemote::HandlePacket_qXfer, NULL,
                      "qXfer:", "Support the qXfer packet."));
+  t.push_back(Packet(json_query_dyld_process_state,
+                     &RNBRemote::HandlePacket_jGetDyldProcessState, NULL,
+                     "jGetDyldProcessState",
+                     "Query the process state from dyld."));
 }
 
 void RNBRemote::FlushSTDIO() {
@@ -5256,6 +5260,22 @@
   return SendPacket(strm.str());
 }
 
+rnb_err_t RNBRemote::HandlePacket_jGetDyldProcessState(const char *p) {
+  const nub_process_t pid = m_ctx.ProcessID();
+  if (pid == INVALID_NUB_PROCESS)
+    return SendPacket("E87");
+
+  JSONGenerator::ObjectSP dyld_state_sp = DNBGetDyldProcessState(pid);
+  if (dyld_state_sp) {
+    std::ostringstream strm;
+    dyld_state_sp->DumpBinaryEscaped(strm);
+    dyld_state_sp->Clear();
+    if (strm.str().size() > 0)
+      return SendPacket(strm.str());
+  }
+  return SendPacket("E88");
+}
+
 // A helper function that retrieves a single integer value from
 // a one-level-deep JSON dictionary of key-value pairs.  e.g.
 // jThreadExtendedInfo:{"plo_pthread_tsd_base_address_offset":0,"plo_pthread_tsd_base_offset":224,"plo_pthread_tsd_entry_size":8,"thread":144305}]
Index: lldb/tools/debugserver/source/MacOSX/MachProcess.mm
===================================================================
--- lldb/tools/debugserver/source/MacOSX/MachProcess.mm
+++ lldb/tools/debugserver/source/MacOSX/MachProcess.mm
@@ -508,7 +508,6 @@
 #define _POSIX_SPAWN_DISABLE_ASLR 0x0100
 #endif
 
-
 MachProcess::MachProcess()
     : m_pid(0), m_cpu_type(0), m_child_stdin(-1), m_child_stdout(-1),
       m_child_stderr(-1), m_path(), m_args(), m_task(this),
@@ -516,8 +515,8 @@
       m_stdio_mutex(PTHREAD_MUTEX_RECURSIVE), m_stdout_data(),
       m_profile_enabled(false), m_profile_interval_usec(0), m_profile_thread(0),
       m_profile_data_mutex(PTHREAD_MUTEX_RECURSIVE), m_profile_data(),
-      m_profile_events(0, eMachProcessProfileCancel),
-      m_thread_actions(), m_exception_messages(),
+      m_profile_events(0, eMachProcessProfileCancel), m_thread_actions(),
+      m_exception_messages(),
       m_exception_messages_mutex(PTHREAD_MUTEX_RECURSIVE), m_thread_list(),
       m_activities(), m_state(eStateUnloaded),
       m_state_mutex(PTHREAD_MUTEX_RECURSIVE), m_events(0, kAllEventsMask),
@@ -528,7 +527,8 @@
       m_dyld_process_info_create(nullptr),
       m_dyld_process_info_for_each_image(nullptr),
       m_dyld_process_info_release(nullptr),
-      m_dyld_process_info_get_cache(nullptr) {
+      m_dyld_process_info_get_cache(nullptr),
+      m_dyld_process_info_get_state(nullptr) {
   m_dyld_process_info_create =
       (void *(*)(task_t task, uint64_t timestamp, kern_return_t * kernelError))
           dlsym(RTLD_DEFAULT, "_dyld_process_info_create");
@@ -542,6 +542,8 @@
       RTLD_DEFAULT, "_dyld_process_info_get_cache");
   m_dyld_process_info_get_platform = (uint32_t (*)(void *info))dlsym(
       RTLD_DEFAULT, "_dyld_process_info_get_platform");
+  m_dyld_process_info_get_state = (void (*)(void *info, void *stateInfo))dlsym(
+      RTLD_DEFAULT, "_dyld_process_info_get_state");
 
   DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__);
 }
@@ -2430,6 +2432,84 @@
   return m_task.GetDYLDAllImageInfosAddress(err);
 }
 
+/// From dyld SPI header dyld_process_info.h
+struct dyld_process_state_info {
+  uint64_t timestamp;
+  uint32_t imageCount;
+  uint32_t initialImageCount;
+  // one of dyld_process_state_* values
+  uint8_t dyldState;
+};
+enum {
+  dyld_process_state_not_started = 0x00,
+  dyld_process_state_dyld_initialized = 0x10,
+  dyld_process_state_terminated_before_inits = 0x20,
+  dyld_process_state_libSystem_initialized = 0x30,
+  dyld_process_state_running_initializers = 0x40,
+  dyld_process_state_program_running = 0x50,
+  dyld_process_state_dyld_terminated = 0x60
+};
+
+JSONGenerator::ObjectSP MachProcess::GetDyldProcessState() {
+  JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary());
+  if (!m_dyld_process_info_get_state) {
+    reply_sp->AddStringItem("error",
+                            "_dyld_process_info_get_state unavailable");
+    return reply_sp;
+  }
+  if (!m_dyld_process_info_create) {
+    reply_sp->AddStringItem("error", "_dyld_process_info_create unavailable");
+    return reply_sp;
+  }
+
+  kern_return_t kern_ret;
+  dyld_process_info info =
+      m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret);
+  if (!info || kern_ret != KERN_SUCCESS) {
+    reply_sp->AddStringItem(
+        "error", "Unable to create dyld_process_info for inferior task");
+    return reply_sp;
+  }
+
+  struct dyld_process_state_info state_info;
+  m_dyld_process_info_get_state(info, &state_info);
+  reply_sp->AddIntegerItem("process_state_value", state_info.dyldState);
+  switch (state_info.dyldState) {
+  case dyld_process_state_not_started:
+    reply_sp->AddStringItem("process_state string",
+                            "dyld_process_state_not_started");
+    break;
+  case dyld_process_state_dyld_initialized:
+    reply_sp->AddStringItem("process_state string",
+                            "dyld_process_state_dyld_initialized");
+    break;
+  case dyld_process_state_terminated_before_inits:
+    reply_sp->AddStringItem("process_state string",
+                            "dyld_process_state_terminated_before_inits");
+    break;
+  case dyld_process_state_libSystem_initialized:
+    reply_sp->AddStringItem("process_state string",
+                            "dyld_process_state_libSystem_initialized");
+    break;
+  case dyld_process_state_running_initializers:
+    reply_sp->AddStringItem("process_state string",
+                            "dyld_process_state_running_initializers");
+    break;
+  case dyld_process_state_program_running:
+    reply_sp->AddStringItem("process_state string",
+                            "dyld_process_state_program_running");
+    break;
+  case dyld_process_state_dyld_terminated:
+    reply_sp->AddStringItem("process_state string",
+                            "dyld_process_state_dyld_terminated");
+    break;
+  };
+
+  m_dyld_process_info_release(info);
+
+  return reply_sp;
+}
+
 size_t MachProcess::GetAvailableSTDERR(char *buf, size_t buf_size) { return 0; }
 
 void *MachProcess::STDIOThread(void *arg) {
Index: lldb/tools/debugserver/source/MacOSX/MachProcess.h
===================================================================
--- lldb/tools/debugserver/source/MacOSX/MachProcess.h
+++ lldb/tools/debugserver/source/MacOSX/MachProcess.h
@@ -362,6 +362,8 @@
 
   DNBProfileDataScanType GetProfileScanType() { return m_profile_scan_type; }
 
+  JSONGenerator::ObjectSP GetDyldProcessState();
+
 private:
   enum {
     eMachProcessFlagsNone = 0,
@@ -468,6 +470,7 @@
   void (*m_dyld_process_info_release)(void *info);
   void (*m_dyld_process_info_get_cache)(void *info, void *cacheInfo);
   uint32_t (*m_dyld_process_info_get_platform)(void *info);
+  void (*m_dyld_process_info_get_state)(void *info, void *stateInfo);
 };
 
 #endif // LLDB_TOOLS_DEBUGSERVER_SOURCE_MACOSX_MACHPROCESS_H
Index: lldb/tools/debugserver/source/DNB.h
===================================================================
--- lldb/tools/debugserver/source/DNB.h
+++ lldb/tools/debugserver/source/DNB.h
@@ -157,6 +157,7 @@
 nub_size_t DNBProcessGetStopCount(nub_process_t pid) DNB_EXPORT;
 uint32_t DNBProcessGetCPUType(nub_process_t pid) DNB_EXPORT;
 size_t DNBGetAllInfos(std::vector<struct kinfo_proc> &proc_infos);
+JSONGenerator::ObjectSP DNBGetDyldProcessState(nub_process_t pid);
 
 // Process executable and arguments
 const char *DNBProcessGetExecutablePath(nub_process_t pid);
Index: lldb/tools/debugserver/source/DNB.cpp
===================================================================
--- lldb/tools/debugserver/source/DNB.cpp
+++ lldb/tools/debugserver/source/DNB.cpp
@@ -599,6 +599,14 @@
   return proc_infos.size();
 }
 
+JSONGenerator::ObjectSP DNBGetDyldProcessState(nub_process_t pid) {
+  MachProcessSP procSP;
+  if (GetProcessSP(pid, procSP)) {
+    return procSP->GetDyldProcessState();
+  }
+  return {};
+}
+
 static size_t
 GetAllInfosMatchingName(const char *full_process_name,
                         std::vector<struct kinfo_proc> &matching_proc_infos) {
Index: lldb/test/API/macosx/early-process-launch/main.c
===================================================================
--- /dev/null
+++ lldb/test/API/macosx/early-process-launch/main.c
@@ -0,0 +1,2 @@
+int global = 10;
+int main () {return global;}
Index: lldb/test/API/macosx/early-process-launch/TestEarlyProcessLaunch.py
===================================================================
--- /dev/null
+++ lldb/test/API/macosx/early-process-launch/TestEarlyProcessLaunch.py
@@ -0,0 +1,57 @@
+"""Test that we don't read objc class tables early in process startup."""
+
+
+import time
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestEarlyProcessLaunch(TestBase):
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipUnlessDarwin
+    @add_test_categories(['pyapi'])
+    def test_early_process_launch(self):
+        """Test that we don't read objc class tables early in proc startup"""
+        self.build()
+
+        ###
+        ### Hit a breakpoint on the first malloc() call, which 
+        ### is before libSystem has finished initializing.  At
+        ### this point, we should not read the objc class tables.
+        ### Then continue to main(), which is past libSystem 
+        ### initializing.  Try again, and they should be read.
+        ### 
+        ### Use the types logging to detect the difference.
+
+        target, process, _, bkpt = lldbutil.run_to_name_breakpoint(
+            self, 'malloc')
+
+        target.DisableAllBreakpoints()
+        target.BreakpointCreateByName("main")
+
+        logfile_early = os.path.join(self.getBuildDir(), "types-log-early.txt")
+        self.addTearDownHook(lambda: self.runCmd("log disable lldb types"))
+        self.runCmd("log enable -f %s lldb types" % logfile_early)
+        self.runCmd("p global = 15")
+
+        err = process.Continue()
+        self.assertTrue(err.Success())
+
+        logfile_later = os.path.join(self.getBuildDir(), "types-log-later.txt")
+        self.runCmd("log enable -f %s lldb types" % logfile_later)
+        self.runCmd("p global = 25")
+
+        self.assertTrue(os.path.exists(logfile_early))
+        self.assertTrue(os.path.exists(logfile_later))
+        early_text = open(logfile_early).read()
+        later_text = open(logfile_later).read()
+
+        self.assertIn("ran: no, retry: yes", early_text)
+        self.assertNotIn("ran: no, retry: yes", later_text)
+
+        self.assertNotIn("ran: yes, retry: no", early_text)
+        self.assertIn("ran: yes, retry: no", later_text)
Index: lldb/test/API/macosx/early-process-launch/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/macosx/early-process-launch/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
Index: lldb/source/Target/Thread.cpp
===================================================================
--- lldb/source/Target/Thread.cpp
+++ lldb/source/Target/Thread.cpp
@@ -1663,6 +1663,10 @@
 bool Thread::SafeToCallFunctions() {
   Process *process = GetProcess().get();
   if (process) {
+    DynamicLoader *loader = GetProcess()->GetDynamicLoader();
+    if (loader && loader->IsFullyInitialized() == false)
+      return false;
+
     SystemRuntime *runtime = process->GetSystemRuntime();
     if (runtime) {
       return runtime->SafeToCallFunctionsOnThisThread(shared_from_this());
Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -222,6 +222,8 @@
 
   StructuredData::ObjectSP GetSharedCacheInfo() override;
 
+  StructuredData::ObjectSP GetDynamicLoaderProcessState() override;
+
   std::string HarmonizeThreadIdsForProfileData(
       StringExtractorGDBRemote &inputStringExtractor);
 
Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -3829,6 +3829,29 @@
   return object_sp;
 }
 
+StructuredData::ObjectSP ProcessGDBRemote::GetDynamicLoaderProcessState() {
+  StructuredData::ObjectSP object_sp;
+  StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
+
+  if (m_gdb_comm.GetDynamicLoaderProcessStateSupported()) {
+    StringExtractorGDBRemote response;
+    response.SetResponseValidatorToJSON();
+    if (m_gdb_comm.SendPacketAndWaitForResponse("jGetDyldProcessState",
+                                                response) ==
+        GDBRemoteCommunication::PacketResult::Success) {
+      StringExtractorGDBRemote::ResponseType response_type =
+          response.GetResponseType();
+      if (response_type == StringExtractorGDBRemote::eResponse) {
+        if (!response.Empty()) {
+          object_sp =
+              StructuredData::ParseJSON(std::string(response.GetStringRef()));
+        }
+      }
+    }
+  }
+  return object_sp;
+}
+
 StructuredData::ObjectSP ProcessGDBRemote::GetSharedCacheInfo() {
   StructuredData::ObjectSP object_sp;
   StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -429,6 +429,8 @@
 
   bool GetSharedCacheInfoSupported();
 
+  bool GetDynamicLoaderProcessStateSupported();
+
   bool GetMemoryTaggingSupported();
 
   bool UsesNativeSignals();
@@ -553,6 +555,7 @@
   LazyBool m_supports_jThreadExtendedInfo = eLazyBoolCalculate;
   LazyBool m_supports_jLoadedDynamicLibrariesInfos = eLazyBoolCalculate;
   LazyBool m_supports_jGetSharedCacheInfo = eLazyBoolCalculate;
+  LazyBool m_supports_jGetDyldProcessState = eLazyBoolCalculate;
   LazyBool m_supports_QPassSignals = eLazyBoolCalculate;
   LazyBool m_supports_error_string_reply = eLazyBoolCalculate;
   LazyBool m_supports_multiprocess = eLazyBoolCalculate;
Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -613,6 +613,19 @@
   return m_supports_jGetSharedCacheInfo;
 }
 
+bool GDBRemoteCommunicationClient::GetDynamicLoaderProcessStateSupported() {
+  if (m_supports_jGetDyldProcessState == eLazyBoolCalculate) {
+    StringExtractorGDBRemote response;
+    m_supports_jGetDyldProcessState = eLazyBoolNo;
+    if (SendPacketAndWaitForResponse("jGetDyldProcessState", response) ==
+        PacketResult::Success) {
+      if (!response.IsUnsupportedResponse())
+        m_supports_jGetDyldProcessState = eLazyBoolYes;
+    }
+  }
+  return m_supports_jGetDyldProcessState;
+}
+
 bool GDBRemoteCommunicationClient::GetMemoryTaggingSupported() {
   if (m_supports_memory_tagging == eLazyBoolCalculate) {
     GetRemoteQSupported();
Index: lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h
===================================================================
--- lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h
+++ lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h
@@ -286,18 +286,24 @@
 
   struct DescriptorMapUpdateResult {
     bool m_update_ran;
+    bool m_retry_update;
     uint32_t m_num_found;
 
-    DescriptorMapUpdateResult(bool ran, uint32_t found) {
+    DescriptorMapUpdateResult(bool ran, bool retry, uint32_t found) {
       m_update_ran = ran;
+
+      m_retry_update = retry;
+
       m_num_found = found;
     }
 
-    static DescriptorMapUpdateResult Fail() { return {false, 0}; }
+    static DescriptorMapUpdateResult Fail() { return {false, false, 0}; }
 
     static DescriptorMapUpdateResult Success(uint32_t found) {
-      return {true, found};
+      return {true, false, found};
     }
+
+    static DescriptorMapUpdateResult Retry() { return {false, true, 0}; }
   };
 
   /// Abstraction to read the Objective-C class info.
@@ -395,6 +401,7 @@
                                uint32_t num_class_infos);
 
   enum class SharedCacheWarningReason {
+    eExpressionUnableToRun,
     eExpressionExecutionFailure,
     eNotEnoughClassesRead
   };
Index: lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
===================================================================
--- lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
+++ lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp
@@ -1875,6 +1875,9 @@
   if (!thread_sp)
     return DescriptorMapUpdateResult::Fail();
 
+  if (!thread_sp->SafeToCallFunctions())
+    return DescriptorMapUpdateResult::Retry();
+
   thread_sp->CalculateExecutionContext(exe_ctx);
   TypeSystemClang *ast =
       ScratchTypeSystemClang::GetForTarget(process->GetTarget());
@@ -2042,7 +2045,7 @@
     }
   }
 
-  return DescriptorMapUpdateResult(success, num_class_infos);
+  return DescriptorMapUpdateResult(success, false, num_class_infos);
 }
 
 uint32_t AppleObjCRuntimeV2::ParseClassInfoArray(const DataExtractor &data,
@@ -2137,6 +2140,9 @@
   if (!thread_sp)
     return DescriptorMapUpdateResult::Fail();
 
+  if (!thread_sp->SafeToCallFunctions())
+    return DescriptorMapUpdateResult::Retry();
+
   thread_sp->CalculateExecutionContext(exe_ctx);
   TypeSystemClang *ast =
       ScratchTypeSystemClang::GetForTarget(process->GetTarget());
@@ -2314,7 +2320,7 @@
   // Deallocate the memory we allocated for the ClassInfo array
   process->DeallocateMemory(class_infos_addr);
 
-  return DescriptorMapUpdateResult(success, num_class_infos);
+  return DescriptorMapUpdateResult(success, false, num_class_infos);
 }
 
 lldb::addr_t AppleObjCRuntimeV2::GetSharedCacheReadOnlyAddress() {
@@ -2414,18 +2420,23 @@
 
       LLDB_LOGF(log,
                 "attempted to read objc class data - results: "
-                "[dynamic_update]: ran: %s, count: %" PRIu32
-                " [shared_cache_update]: ran: %s, count: %" PRIu32,
+                "[dynamic_update]: ran: %s, retry: %s, count: %" PRIu32
+                " [shared_cache_update]: ran: %s, retry: %s, count: %" PRIu32,
                 dynamic_update_result.m_update_ran ? "yes" : "no",
+                dynamic_update_result.m_retry_update ? "yes" : "no",
                 dynamic_update_result.m_num_found,
                 shared_cache_update_result.m_update_ran ? "yes" : "no",
+                shared_cache_update_result.m_retry_update ? "yes" : "no",
                 shared_cache_update_result.m_num_found);
 
       // warn if:
       // - we could not run either expression
       // - we found fewer than num_classes_to_warn_at classes total
-      if ((!shared_cache_update_result.m_update_ran) ||
-          (!dynamic_update_result.m_update_ran))
+      if (dynamic_update_result.m_retry_update ||
+          shared_cache_update_result.m_retry_update)
+        WarnIfNoClassesCached(SharedCacheWarningReason::eExpressionUnableToRun);
+      else if ((!shared_cache_update_result.m_update_ran) ||
+               (!dynamic_update_result.m_update_ran))
         WarnIfNoClassesCached(
             SharedCacheWarningReason::eExpressionExecutionFailure);
       else if (dynamic_update_result.m_num_found +
@@ -2504,6 +2515,11 @@
         "reduce the quality of type information available.\n",
         debugger.GetID(), &m_no_classes_cached_warning);
     break;
+  case SharedCacheWarningReason::eExpressionUnableToRun:
+    Debugger::ReportWarning("Objective-C class names could not be read "
+                            "right now, will retry later.\n",
+                            debugger.GetID(), nullptr);
+    break;
   }
 }
 
Index: lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.h
===================================================================
--- lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.h
+++ lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.h
@@ -79,6 +79,8 @@
 
   void DoClear() override;
 
+  bool IsFullyInitialized() override;
+
   static bool
   NotifyBreakpointHit(void *baton,
                       lldb_private::StoppointCallbackContext *context,
@@ -106,6 +108,7 @@
                                             // exec's when talking to
                                             // debugservers that don't support
                                             // the "reason:exec" annotation.
+  bool m_libsystem_fully_initalized;
 };
 
 #endif // LLDB_SOURCE_PLUGINS_DYNAMICLOADER_MACOSX_DYLD_DYNAMICLOADERMACOS_H
Index: lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.cpp
===================================================================
--- lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.cpp
+++ lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.cpp
@@ -79,7 +79,8 @@
     : DynamicLoaderDarwin(process), m_image_infos_stop_id(UINT32_MAX),
       m_break_id(LLDB_INVALID_BREAK_ID),
       m_dyld_handover_break_id(LLDB_INVALID_BREAK_ID), m_mutex(),
-      m_maybe_image_infos_address(LLDB_INVALID_ADDRESS) {}
+      m_maybe_image_infos_address(LLDB_INVALID_ADDRESS),
+      m_libsystem_fully_initalized(false) {}
 
 // Destructor
 DynamicLoaderMacOS::~DynamicLoaderMacOS() {
@@ -129,6 +130,7 @@
   if (did_exec) {
     m_libpthread_module_wp.reset();
     m_pthread_getspecific_addr.Clear();
+    m_libsystem_fully_initalized = false;
   }
   return did_exec;
 }
@@ -144,6 +146,33 @@
 
   m_break_id = LLDB_INVALID_BREAK_ID;
   m_dyld_handover_break_id = LLDB_INVALID_BREAK_ID;
+  m_libsystem_fully_initalized = false;
+}
+
+bool DynamicLoaderMacOS::IsFullyInitialized() {
+  if (m_libsystem_fully_initalized)
+    return true;
+
+  StructuredData::ObjectSP process_state_sp(
+      m_process->GetDynamicLoaderProcessState());
+  if (!process_state_sp)
+    return true;
+  if (process_state_sp->GetAsDictionary()->HasKey("error"))
+    return true;
+  if (!process_state_sp->GetAsDictionary()->HasKey("process_state string"))
+    return true;
+  std::string proc_state = process_state_sp->GetAsDictionary()
+                               ->GetValueForKey("process_state string")
+                               ->GetAsString()
+                               ->GetValue()
+                               .str();
+  if (proc_state == "dyld_process_state_not_started" ||
+      proc_state == "dyld_process_state_dyld_initialized" ||
+      proc_state == "dyld_process_state_terminated_before_inits") {
+    return false;
+  }
+  m_libsystem_fully_initalized = true;
+  return true;
 }
 
 // Check if we have found DYLD yet
Index: lldb/include/lldb/Target/Process.h
===================================================================
--- lldb/include/lldb/Target/Process.h
+++ lldb/include/lldb/Target/Process.h
@@ -1321,6 +1321,15 @@
     return StructuredData::ObjectSP();
   }
 
+  // Get information about the launch state of the process, if possible.
+  //
+  // On Darwin systems, libdyld can report on process state, most importantly
+  // the startup stages where the system library is not yet initialized.
+  virtual lldb_private::StructuredData::ObjectSP
+  GetDynamicLoaderProcessState() {
+    return {};
+  }
+
   /// Print a user-visible warning about a module being built with
   /// optimization
   ///
Index: lldb/docs/lldb-gdb-remote.txt
===================================================================
--- lldb/docs/lldb-gdb-remote.txt
+++ lldb/docs/lldb-gdb-remote.txt
@@ -2153,3 +2153,21 @@
 The data in this packet is a single a character, which should be '0' if the
 inferior process should be killed, or '1' if the server should remove all
 breakpoints and detach from the inferior.
+
+//----------------------------------------------------------------------
+// "jGetDyldProcessState"
+//
+// BRIEF
+//  This packet fetches the process launch state, as reported by libdyld on
+//  Darwin systems, most importantly to indicate when the system libraries 
+//  have initialized sufficiently to safely call utility functions.
+//
+//
+//  LLDB SENDS: jGetDyldProcessState
+//  STUB REPLIES: {"process_state_value":48,"process_state string":"dyld_process_state_libSystem_initialized"}
+//
+// PRIORITY TO IMPLEMENT
+//  Low. This packet is needed to prevent lldb's utility functions for
+//  scanning the Objective-C class list from running very early in 
+//  process startup.
+//----------------------------------------------------------------------
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to