augusto2112 updated this revision to Diff 314641.
augusto2112 added a comment.
Herald added a subscriber: dang.

Adds support to 'vAttachOrWait', as well as the 'waitfor-interval' and 
'waitfor-duration' flags.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D93895/new/

https://reviews.llvm.org/D93895

Files:
  lldb/include/lldb/Target/Process.h
  lldb/include/lldb/Utility/StringExtractor.h
  lldb/include/lldb/lldb-enumerations.h
  lldb/source/Commands/CommandObjectProcess.cpp
  lldb/source/Commands/Options.td
  lldb/source/Interpreter/CommandObject.cpp
  lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
  lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
  lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
  lldb/source/Utility/StringExtractor.cpp
  lldb/test/API/tools/lldb-server/TestGdbRemoteAttachWait.py

Index: lldb/test/API/tools/lldb-server/TestGdbRemoteAttachWait.py
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-server/TestGdbRemoteAttachWait.py
@@ -0,0 +1,69 @@
+
+import os
+from time import sleep
+
+import gdbremote_testcase
+import lldbgdbserverutils
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestGdbRemoteAttachWait(gdbremote_testcase.GdbRemoteTestCaseBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def test_attach_with_vAttachWait(self):
+        exe = '%s_%d' % (self.testMethodName, os.getpid())
+        self.build(dictionary={'EXE': exe})
+        self.set_inferior_startup_attach_manually()
+
+        server = self.connect_to_debug_monitor()
+        self.assertIsNotNone(server)
+
+
+        self.add_no_ack_remote_stream()
+        self.test_sequence.add_log_lines([
+            # Do the attach.
+            "read packet: $vAttachWait;{}#00".format(lldbgdbserverutils.gdbremote_hex_encode_string(exe)),
+        ], True)
+        # Run the stream until attachWait.
+        context = self.expect_gdbremote_sequence()
+        self.assertIsNotNone(context)
+
+        # Sleep so we're sure that the inferior is launched after we ask for the attach.
+        sleep(1)
+
+        # Launch the inferior.
+        inferior = self.launch_process_for_attach(
+            inferior_args=["sleep:60"],
+            exe_path=self.getBuildArtifact(exe))
+        self.assertIsNotNone(inferior)
+        self.assertTrue(inferior.pid > 0)
+        self.assertTrue(
+            lldbgdbserverutils.process_is_running(
+                inferior.pid, True))
+
+        # Make sure the attach succeeded.
+        self.test_sequence.add_log_lines([
+            {"direction": "send",
+             "regex": r"^\$T([0-9a-fA-F]{2})[^#]*#[0-9a-fA-F]{2}$",
+             "capture": {1: "stop_signal_hex"}},
+        ], True)
+        self.add_process_info_collection_packets()
+
+
+        # Run the stream sending the response..
+        context = self.expect_gdbremote_sequence()
+        self.assertIsNotNone(context)
+
+        # Gather process info response.
+        process_info = self.parse_process_info_response(context)
+        self.assertIsNotNone(process_info)
+
+        # Ensure the process id matches what we expected.
+        pid_text = process_info.get('pid', None)
+        self.assertIsNotNone(pid_text)
+        reported_pid = int(pid_text, base=16)
+        self.assertEqual(reported_pid, inferior.pid)
+
Index: lldb/source/Utility/StringExtractor.cpp
===================================================================
--- lldb/source/Utility/StringExtractor.cpp
+++ lldb/source/Utility/StringExtractor.cpp
@@ -297,11 +297,11 @@
   return bytes_extracted;
 }
 
-size_t StringExtractor::GetHexByteString(std::string &str) {
+size_t StringExtractor::GetHexByteString(std::string &str, bool set_eof_on_fail) {
   str.clear();
   str.reserve(GetBytesLeft() / 2);
   char ch;
-  while ((ch = GetHexU8()) != '\0')
+  while ((ch = GetHexU8(0, set_eof_on_fail)) != '\0')
     str.append(1, ch);
   return str.size();
 }
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
@@ -1184,11 +1184,24 @@
         }
       } else
         packet.PutCString("vAttachName");
+
       packet.PutChar(';');
       packet.PutBytesAsRawHex8(process_name, strlen(process_name),
                                endian::InlHostByteOrder(),
                                endian::InlHostByteOrder());
 
+      if (attach_info.HasWaitForLaunchInterval()) {
+        packet.PutChar(';');
+        packet.PutChar('I');
+        packet.PutHex32(attach_info.GetWaitForLaunchInterval());
+      }
+
+      if (attach_info.HasWaitForLaunchDuration()) {
+        packet.PutChar(';');
+        packet.PutChar('d');
+        packet.PutHex32(attach_info.GetWaitForLaunchDuration());
+      }
+
       m_async_broadcaster.BroadcastEvent(
           eBroadcastBitAsyncContinue,
           new EventDataBytes(packet.GetString().data(), packet.GetSize()));
Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
+++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
@@ -59,6 +59,20 @@
   ///     attach operation.
   Status AttachToProcess(lldb::pid_t pid);
 
+  /// Wait to attach to a process with a given name.
+  ///
+  /// This method supports waiting for the next instance of a process
+  /// with a given name and attaching llgs to that via the configured
+  /// Platform.
+  ///
+  /// \return
+  ///     An Status object indicating the success or failure of the
+  ///     attach operation.
+  Status AttachWaitProcess(llvm::StringRef process_name,
+                           llvm::Optional<uint32_t> user_polling_interval,
+                           llvm::Optional<uint32_t> timeout,
+                           bool include_existing);
+
   // NativeProcessProtocol::NativeDelegate overrides
   void InitializeDelegate(NativeProcessProtocol *process) override;
 
@@ -170,6 +184,12 @@
 
   PacketResult Handle_vAttach(StringExtractorGDBRemote &packet);
 
+  PacketResult Handle_qVAttachOrWaitSupported(StringExtractorGDBRemote &packet);
+
+  PacketResult Handle_vAttachWait(StringExtractorGDBRemote &packet);
+
+  PacketResult Handle_vAttachOrWait(StringExtractorGDBRemote &packet);
+
   PacketResult Handle_D(StringExtractorGDBRemote &packet);
 
   PacketResult Handle_qThreadStopInfo(StringExtractorGDBRemote &packet);
@@ -227,6 +247,12 @@
 
   void StopSTDIOForwarding();
 
+  PacketResult
+  Handle_vAttachWait_or_vAttachOrWait(StringExtractorGDBRemote &packet,
+                                      bool include_existing,
+                                      const char *identifier);
+
+
   // For GDBRemoteCommunicationServerLLGS only
   GDBRemoteCommunicationServerLLGS(const GDBRemoteCommunicationServerLLGS &) =
       delete;
Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -159,6 +159,15 @@
   RegisterMemberFunctionHandler(
       StringExtractorGDBRemote::eServerPacketType_vAttach,
       &GDBRemoteCommunicationServerLLGS::Handle_vAttach);
+  RegisterMemberFunctionHandler(
+      StringExtractorGDBRemote::eServerPacketType_vAttachWait,
+      &GDBRemoteCommunicationServerLLGS::Handle_vAttachWait);
+  RegisterMemberFunctionHandler(
+      StringExtractorGDBRemote::eServerPacketType_qVAttachOrWaitSupported,
+      &GDBRemoteCommunicationServerLLGS::Handle_qVAttachOrWaitSupported);
+  RegisterMemberFunctionHandler(
+      StringExtractorGDBRemote::eServerPacketType_vAttachOrWait,
+      &GDBRemoteCommunicationServerLLGS::Handle_vAttachOrWait);
   RegisterMemberFunctionHandler(
       StringExtractorGDBRemote::eServerPacketType_vCont,
       &GDBRemoteCommunicationServerLLGS::Handle_vCont);
@@ -334,6 +343,100 @@
   return Status();
 }
 
+Status GDBRemoteCommunicationServerLLGS::AttachWaitProcess(
+    llvm::StringRef process_name,
+    llvm::Optional<uint32_t> user_polling_interval,
+    llvm::Optional<uint32_t> timeout, bool include_existing) {
+  Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS));
+
+  std::chrono::milliseconds polling_interval;
+  if (user_polling_interval.hasValue()) {
+    polling_interval =
+        std::chrono::milliseconds(user_polling_interval.getValue());
+    LLDB_LOG(log, "using user-defined polling interval '{0}'",
+             polling_interval);
+  } else {
+    polling_interval = std::chrono::milliseconds(1);
+  }
+
+  // Create the matcher used to search the process list.
+  ProcessInstanceInfoList exclusion_list;
+  ProcessInstanceInfoMatch match_info;
+  match_info.GetProcessInfo().GetExecutableFile().SetFile(
+      process_name, llvm::sys::path::Style::posix);
+  match_info.SetNameMatchType(NameMatch::EndsWith);
+
+  if (!include_existing) {
+    // Create the excluded process list before polling begins.
+    Host::FindProcesses(match_info, exclusion_list);
+    LLDB_LOG(log, "placed '{0}' processes in the exclusion list.",
+             exclusion_list.size());
+  } else {
+    LLDB_LOG(log, "including existing processes in search", process_name);
+  }
+
+  LLDB_LOG(log, "waiting for '{0}' to appear", process_name);
+
+  auto is_in_exclusion_list = [&exclusion_list](ProcessInstanceInfo &info) {
+    for (auto &excluded : exclusion_list) {
+      if (excluded.GetProcessID() == info.GetProcessID()) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  auto now = std::chrono::system_clock::now();
+  auto target = now;
+  if (timeout.hasValue())
+    target += std::chrono::seconds(timeout.getValue());
+
+  ProcessInstanceInfoList loop_process_list;
+  while (!timeout.hasValue() || now < target) {
+    loop_process_list.clear();
+    if (Host::FindProcesses(match_info, loop_process_list)) {
+      // Remove all the elements that are in the exclusion list.
+      loop_process_list.erase(std::remove_if(loop_process_list.begin(),
+                                             loop_process_list.end(),
+                                             is_in_exclusion_list),
+                              loop_process_list.end());
+
+      // One match! We found the desired process.
+      if (loop_process_list.size() == 1) {
+        auto matching_process_pid = loop_process_list[0].GetProcessID();
+        LLDB_LOG(log, "found pid {0}", matching_process_pid);
+        return AttachToProcess(matching_process_pid);
+      }
+
+      // Multiple matches! Return an error reporting the PIDs we found.
+      if (loop_process_list.size() > 1) {
+        StreamString error_stream;
+        error_stream.Printf(
+            "Multiple executables with name: '%s' found. Pids: ",
+            process_name.str().c_str());
+        for (size_t i = 0; i < loop_process_list.size() - 1; ++i) {
+          error_stream.Printf("%lu, ", loop_process_list[i].GetProcessID());
+        }
+        error_stream.Printf("%lu.", loop_process_list.back().GetProcessID());
+
+        Status error;
+        error.SetErrorString(error_stream.GetString());
+        return error;
+      }
+    }
+    // No matches, we have not found the process. Sleep until next poll.
+    LLDB_LOG(log, "sleep {0} seconds", polling_interval);
+    std::this_thread::sleep_for(polling_interval);
+    if (timeout.hasValue())
+      now = std::chrono::system_clock::now();
+  }
+
+  // Timed out
+  Status error;
+  error.SetErrorString("waitfor timed out.");
+  return error;
+}
+
 void GDBRemoteCommunicationServerLLGS::InitializeDelegate(
     NativeProcessProtocol *process) {
   assert(process && "process cannot be NULL");
@@ -3188,6 +3291,94 @@
   return SendStopReasonForState(m_debugged_process_up->GetState());
 }
 
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_vAttachWait(
+    StringExtractorGDBRemote &packet) {
+  return Handle_vAttachWait_or_vAttachOrWait(packet, false, "vAttachWait");
+}
+
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_vAttachOrWait(
+    StringExtractorGDBRemote &packet) {
+  return Handle_vAttachWait_or_vAttachOrWait(packet, true, "vAttachOrWait");
+}
+
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_qVAttachOrWaitSupported(
+    StringExtractorGDBRemote &packet) {
+  return SendOKResponse();
+}
+
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_vAttachWait_or_vAttachOrWait(
+    StringExtractorGDBRemote &packet, bool include_existing,
+    const char *identifier) {
+  Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS));
+
+  // Consume the ';' after the identifier.
+  packet.SetFilePos(strlen(identifier));
+
+  if (!packet.GetBytesLeft() || packet.GetChar() != ';')
+    return SendIllFormedResponse(
+        packet, "vAttachWait/vAttachOrWait missing expected ';'");
+
+  // Allocate the buffer for the process name from vAttachWait.
+  std::string process_name;
+  if (!packet.GetHexByteString(process_name, false))
+    return SendIllFormedResponse(
+        packet, "vAttachWait/vAttachOrWait failed to parse process name");
+
+  llvm::Optional<uint32_t> polling_interval;
+  llvm::Optional<uint32_t> waitfor_duration;
+
+  while (packet.GetBytesLeft() && *packet.Peek() == ';') {
+    // Skip the semi-colon.
+    packet.GetChar();
+
+    const char action = packet.GetChar();
+    switch (action) {
+    case 'I': {
+      uint32_t interval = packet.GetU32(UINT32_MAX, 16);
+      if (interval != UINT32_MAX) {
+        polling_interval = interval;
+      } else {
+        return SendIllFormedResponse(packet,
+                                     "vAttachWait/vAttachOrWait failed to "
+                                     "parse waitfor polling interval");
+      }
+      break;
+    }
+    case 'd': {
+      uint32_t duration = packet.GetU32(UINT32_MAX, 16);
+      if (duration != UINT32_MAX) {
+        waitfor_duration = duration;
+      } else {
+        return SendIllFormedResponse(
+            packet,
+            "vAttachWait/vAttachOrWait failed to parse waitfor duration");
+      }
+      break;
+    }
+    default:
+      return SendIllFormedResponse(packet, "Unsupported vAttachWait action");
+      break;
+    }
+  }
+
+  LLDB_LOG(log, "attempting to attach to process named '{0}'", process_name);
+
+  Status error = AttachWaitProcess(process_name, polling_interval,
+                                   waitfor_duration, include_existing);
+  if (error.Fail()) {
+    LLDB_LOG(log, "failed to attach to process named '{0}': {1}", process_name,
+             error);
+    return SendErrorResponse(error);
+  }
+
+  // Notify we attached by sending a stop packet.
+  return SendStopReasonForState(m_debugged_process_up->GetState());
+}
+
 GDBRemoteCommunication::PacketResult
 GDBRemoteCommunicationServerLLGS::Handle_D(StringExtractorGDBRemote &packet) {
   Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS));
Index: lldb/source/Interpreter/CommandObject.cpp
===================================================================
--- lldb/source/Interpreter/CommandObject.cpp
+++ lldb/source/Interpreter/CommandObject.cpp
@@ -1088,6 +1088,8 @@
     { eArgTypePermissionsNumber, "perms-numeric", CommandCompletions::eNoCompletion, { nullptr, false }, "Permissions given as an octal number (e.g. 755)." },
     { eArgTypePermissionsString, "perms=string", CommandCompletions::eNoCompletion, { nullptr, false }, "Permissions given as a string value (e.g. rw-r-xr--)." },
     { eArgTypePid, "pid", CommandCompletions::eProcessIDCompletion, { nullptr, false }, "The process ID number." },
+    { eArgTypeWaitforInterval, "waitfor-interval", CommandCompletions::eProcessIDCompletion, { nullptr, false }, "The interval in milliseconds that waitfor uses to poll the list of processes. Defaults to 1." },
+    { eArgTypeWaitforDuration, "waitfor-duration", CommandCompletions::eProcessIDCompletion, { nullptr, false }, "The timout that in seconds when waiting for a process with waitfor. Defaults to infinite." },
     { eArgTypePlugin, "plugin", CommandCompletions::eProcessPluginCompletion, { nullptr, false }, "Help text goes here." },
     { eArgTypeProcessName, "process-name", CommandCompletions::eProcessNameCompletion, { nullptr, false }, "The name of the process." },
     { eArgTypePythonClass, "python-class", CommandCompletions::eNoCompletion, { nullptr, false }, "The name of a Python class." },
Index: lldb/source/Commands/Options.td
===================================================================
--- lldb/source/Commands/Options.td
+++ lldb/source/Commands/Options.td
@@ -657,6 +657,12 @@
     Group<2>, Desc<"Include existing processes when doing attach -w.">;
   def process_attach_waitfor : Option<"waitfor", "w">, Group<2>,
     Desc<"Wait for the process with <process-name> to launch.">;
+  def process_attach_waitfor_interval : Option<"waitfor-interval", "I">,
+    Group<2>, Arg<"WaitforInterval">,
+    Desc<"The interval that waitfor uses to poll the list of processes.">;
+  def process_attach_waitfor_duration : Option<"waitfor-duration", "d">,
+    Group<2>, Arg<"WaitforDuration">,
+    Desc<"The interval that waitfor uses to poll the list of processes.">;
 }
 
 let Command = "process continue" in {
Index: lldb/source/Commands/CommandObjectProcess.cpp
===================================================================
--- lldb/source/Commands/CommandObjectProcess.cpp
+++ lldb/source/Commands/CommandObjectProcess.cpp
@@ -306,6 +306,26 @@
         attach_info.SetIgnoreExisting(false);
         break;
 
+      case 'I':
+        uint32_t interval;
+        if (option_arg.getAsInteger(0, interval)) {
+          error.SetErrorStringWithFormat("invalid polling interval '%s'",
+                                         option_arg.str().c_str());
+        } else {
+          attach_info.SetWaitForLaunchInterval(interval);
+        }
+        break;
+
+      case 'd':
+        uint32_t duration;
+        if (option_arg.getAsInteger(0, duration)) {
+          error.SetErrorStringWithFormat("invalid duration '%s'",
+                                         option_arg.str().c_str());
+        } else {
+          attach_info.SetWaitForLaunchDuration(duration);
+        }
+        break;
+
       default:
         llvm_unreachable("Unimplemented option");
       }
Index: lldb/include/lldb/lldb-enumerations.h
===================================================================
--- lldb/include/lldb/lldb-enumerations.h
+++ lldb/include/lldb/lldb-enumerations.h
@@ -555,6 +555,8 @@
   eArgTypePermissionsNumber,
   eArgTypePermissionsString,
   eArgTypePid,
+  eArgTypeWaitforInterval,
+  eArgTypeWaitforDuration,
   eArgTypePlugin,
   eArgTypeProcessName,
   eArgTypePythonClass,
Index: lldb/include/lldb/Utility/StringExtractor.h
===================================================================
--- lldb/include/lldb/Utility/StringExtractor.h
+++ lldb/include/lldb/Utility/StringExtractor.h
@@ -89,7 +89,7 @@
 
   size_t GetHexBytesAvail(llvm::MutableArrayRef<uint8_t> dest);
 
-  size_t GetHexByteString(std::string &str);
+  size_t GetHexByteString(std::string &str, bool set_eof_on_fail = true);
 
   size_t GetHexByteStringFixedLength(std::string &str, uint32_t nibble_length);
 
Index: lldb/include/lldb/Target/Process.h
===================================================================
--- lldb/include/lldb/Target/Process.h
+++ lldb/include/lldb/Target/Process.h
@@ -111,14 +111,18 @@
   ProcessAttachInfo()
       : ProcessInstanceInfo(), m_listener_sp(), m_hijack_listener_sp(),
         m_plugin_name(), m_resume_count(0), m_wait_for_launch(false),
-        m_ignore_existing(true), m_continue_once_attached(false),
-        m_detach_on_error(true), m_async(false) {}
+        m_wait_for_launch_interval(UINT32_MAX),
+        m_wait_for_launch_duration(UINT32_MAX), m_ignore_existing(true),
+        m_continue_once_attached(false), m_detach_on_error(true),
+        m_async(false) {}
 
   ProcessAttachInfo(const ProcessLaunchInfo &launch_info)
       : ProcessInstanceInfo(), m_listener_sp(), m_hijack_listener_sp(),
         m_plugin_name(), m_resume_count(0), m_wait_for_launch(false),
-        m_ignore_existing(true), m_continue_once_attached(false),
-        m_detach_on_error(true), m_async(false) {
+        m_wait_for_launch_interval(UINT32_MAX),
+        m_wait_for_launch_duration(UINT32_MAX), m_ignore_existing(true),
+        m_continue_once_attached(false), m_detach_on_error(true),
+        m_async(false) {
     ProcessInfo::operator=(launch_info);
     SetProcessPluginName(launch_info.GetProcessPluginName());
     SetResumeCount(launch_info.GetResumeCount());
@@ -131,6 +135,30 @@
 
   void SetWaitForLaunch(bool b) { m_wait_for_launch = b; }
 
+  bool HasWaitForLaunchInterval() const {
+    return m_wait_for_launch_interval != UINT32_MAX;
+  }
+
+  uint32_t GetWaitForLaunchInterval() const {
+    return m_wait_for_launch_interval;
+  }
+
+  void SetWaitForLaunchInterval(uint32_t new_interval) {
+    m_wait_for_launch_interval = new_interval;
+  }
+
+  bool HasWaitForLaunchDuration() const {
+    return m_wait_for_launch_duration != UINT32_MAX;
+  }
+
+  uint32_t GetWaitForLaunchDuration() const {
+    return m_wait_for_launch_duration;
+  }
+
+  void SetWaitForLaunchDuration(uint32_t new_duration) {
+    m_wait_for_launch_duration = new_duration;
+  }
+
   bool GetAsync() const { return m_async; }
 
   void SetAsync(bool b) { m_async = b; }
@@ -160,6 +188,8 @@
     m_plugin_name.clear();
     m_resume_count = 0;
     m_wait_for_launch = false;
+    m_wait_for_launch_interval = UINT32_MAX;
+    m_wait_for_launch_duration = UINT32_MAX;
     m_ignore_existing = true;
     m_continue_once_attached = false;
   }
@@ -199,6 +229,8 @@
   std::string m_plugin_name;
   uint32_t m_resume_count; // How many times do we resume after launching
   bool m_wait_for_launch;
+  uint32_t m_wait_for_launch_interval;
+  uint32_t m_wait_for_launch_duration;
   bool m_ignore_existing;
   bool m_continue_once_attached; // Supports the use-case scenario of
                                  // immediately continuing the process once
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to