[lldb] [gdb-remote server] Support selecting process via Hg
authorMichał Górny <mgorny@moritz.systems>
Sun, 11 Apr 2021 10:31:06 +0000 (12:31 +0200)
committerMichał Górny <mgorny@moritz.systems>
Fri, 2 Jul 2021 08:23:11 +0000 (10:23 +0200)
Support using the extended thread-id syntax with Hg packet to select
a subprocess.  This makes it possible to start providing support for
running some of the debugger packets against another subprocesses.

Differential Revision: https://reviews.llvm.org/D100261

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py

index 000d024..f6c3ba4 100644 (file)
@@ -2088,16 +2088,6 @@ GDBRemoteCommunication::PacketResult
 GDBRemoteCommunicationServerLLGS::Handle_H(StringExtractorGDBRemote &packet) {
   Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_THREAD));
 
-  // Fail if we don't have a current process.
-  if (!m_current_process ||
-      (m_current_process->GetID() == LLDB_INVALID_PROCESS_ID)) {
-    LLDB_LOGF(
-        log,
-        "GDBRemoteCommunicationServerLLGS::%s failed, no process available",
-        __FUNCTION__);
-    return SendErrorResponse(0x15);
-  }
-
   // Parse out which variant of $H is requested.
   packet.SetFilePos(strlen("H"));
   if (packet.GetBytesLeft() < 1) {
@@ -2109,14 +2099,14 @@ GDBRemoteCommunicationServerLLGS::Handle_H(StringExtractorGDBRemote &packet) {
   }
 
   const char h_variant = packet.GetChar();
-  lldb::pid_t default_pid;
+  NativeProcessProtocol *default_process;
   switch (h_variant) {
   case 'g':
-    default_pid = m_current_process->GetID();
+    default_process = m_current_process;
     break;
 
   case 'c':
-    default_pid = m_continue_process->GetID();
+    default_process = m_continue_process;
     break;
 
   default:
@@ -2129,16 +2119,32 @@ GDBRemoteCommunicationServerLLGS::Handle_H(StringExtractorGDBRemote &packet) {
   }
 
   // Parse out the thread number.
-  llvm::Expected<lldb::tid_t> tid_ret =
-      ReadTid(packet, /*allow_all=*/true, default_pid);
-  if (!tid_ret)
-    return SendErrorResponse(tid_ret.takeError());
+  auto pid_tid = packet.GetPidTid(default_process ? default_process->GetID()
+                                                  : LLDB_INVALID_PROCESS_ID);
+  if (!pid_tid)
+    return SendErrorResponse(llvm::make_error<StringError>(
+        inconvertibleErrorCode(), "Malformed thread-id"));
+
+  lldb::pid_t pid = pid_tid->first;
+  lldb::tid_t tid = pid_tid->second;
+
+  if (pid == StringExtractorGDBRemote::AllProcesses)
+    return SendUnimplementedResponse("Selecting all processes not supported");
+  if (pid == LLDB_INVALID_PROCESS_ID)
+    return SendErrorResponse(llvm::make_error<StringError>(
+        inconvertibleErrorCode(), "No current process and no PID provided"));
+
+  // Check the process ID and find respective process instance.
+  auto new_process_it = m_debugged_processes.find(pid);
+  if (new_process_it == m_debugged_processes.end())
+    return SendErrorResponse(llvm::make_error<StringError>(
+        inconvertibleErrorCode(),
+        llvm::formatv("No process with PID {0} debugged", pid)));
 
-  lldb::tid_t tid = tid_ret.get();
   // Ensure we have the given thread when not specifying -1 (all threads) or 0
   // (any thread).
   if (tid != LLDB_INVALID_THREAD_ID && tid != 0) {
-    NativeThreadProtocol *thread = m_current_process->GetThreadByID(tid);
+    NativeThreadProtocol *thread = new_process_it->second->GetThreadByID(tid);
     if (!thread) {
       LLDB_LOGF(log,
                 "GDBRemoteCommunicationServerLLGS::%s failed, tid %" PRIu64
@@ -2148,13 +2154,15 @@ GDBRemoteCommunicationServerLLGS::Handle_H(StringExtractorGDBRemote &packet) {
     }
   }
 
-  // Now switch the given thread type.
+  // Now switch the given process and thread type.
   switch (h_variant) {
   case 'g':
+    m_current_process = new_process_it->second.get();
     SetCurrentThreadID(tid);
     break;
 
   case 'c':
+    m_continue_process = new_process_it->second.get();
     SetContinueThreadID(tid);
     break;
 
index af39dbb..bcf728c 100644 (file)
@@ -16,7 +16,7 @@ class TestGdbRemoteFork(gdbremote_testcase.GdbRemoteTestCaseBase):
         self.reset_test_sequence()
 
         # continue and expect fork
-        fork_regex = "[$]T.*;{}:p([0-9a-f]*)[.]([0-9a-f]*).*".format(variant)
+        fork_regex = "[$]T.*;{}:p([0-9a-f]+)[.]([0-9a-f]+).*".format(variant)
         self.test_sequence.add_log_lines([
             "read packet: $c#00",
             {"direction": "send", "regex": fork_regex,
@@ -57,3 +57,137 @@ class TestGdbRemoteFork(gdbremote_testcase.GdbRemoteTestCaseBase):
             {"direction": "send", "regex": r"[$]W00#.*"},
         ], True)
         self.expect_gdbremote_sequence()
+
+    def fork_and_follow_test(self, variant):
+        self.build()
+        self.prep_debug_monitor_and_inferior(inferior_args=[variant])
+        self.add_qSupported_packets(["multiprocess+",
+                                     "{}-events+".format(variant)])
+        ret = self.expect_gdbremote_sequence()
+        self.assertIn("{}-events+".format(variant), ret["qSupported_response"])
+        self.reset_test_sequence()
+
+        # continue and expect fork
+        procinfo_regex = "[$]pid:([0-9a-f]+);.*"
+        fork_regex = "[$]T.*;{}:p([0-9a-f]+)[.]([0-9a-f]+).*".format(variant)
+        self.test_sequence.add_log_lines([
+            "read packet: $qProcessInfo#00",
+            {"direction": "send", "regex": procinfo_regex,
+             "capture": {1: "parent_pid"}},
+            "read packet: $c#00",
+            {"direction": "send", "regex": fork_regex,
+             "capture": {1: "pid", 2: "tid"}},
+        ], True)
+        ret = self.expect_gdbremote_sequence()
+        parent_pid, pid, tid = (int(ret[x], 16) for x
+                                in ("parent_pid", "pid", "tid"))
+        self.reset_test_sequence()
+
+        # switch to the forked child
+        self.test_sequence.add_log_lines([
+            "read packet: $Hgp{:x}.{:x}#00".format(pid, tid),
+            {"direction": "send", "regex": r"[$]OK#.*"},
+            "read packet: $Hcp{:x}.{:x}#00".format(pid, tid),
+            {"direction": "send", "regex": r"[$]OK#.*"},
+        ], True)
+
+        # detach the parent
+        self.test_sequence.add_log_lines([
+            "read packet: $D;{:x}#00".format(parent_pid),
+            {"direction": "send", "regex": r"[$]OK#.*"},
+        ], True)
+        ret = self.expect_gdbremote_sequence()
+        self.reset_test_sequence()
+
+        # resume the child
+        self.test_sequence.add_log_lines([
+            "read packet: $c#00",
+            {"direction": "send", "regex": r"[$]W00#.*"},
+        ], True)
+        self.expect_gdbremote_sequence()
+
+    @add_test_categories(["fork"])
+    def test_fork_follow(self):
+        self.fork_and_follow_test("fork")
+
+    @add_test_categories(["fork"])
+    def test_vfork_follow(self):
+        self.fork_and_follow_test("vfork")
+
+    @add_test_categories(["fork"])
+    def test_select_wrong_pid(self):
+        self.build()
+        self.prep_debug_monitor_and_inferior()
+        self.add_qSupported_packets(["multiprocess+"])
+        ret = self.expect_gdbremote_sequence()
+        self.assertIn("multiprocess+", ret["qSupported_response"])
+        self.reset_test_sequence()
+
+        # get process pid
+        procinfo_regex = "[$]pid:([0-9a-f]+);.*"
+        self.test_sequence.add_log_lines([
+            "read packet: $qProcessInfo#00",
+            {"direction": "send", "regex": procinfo_regex,
+             "capture": {1: "pid"}},
+            "read packet: $qC#00",
+            {"direction": "send", "regex": "[$]QC([0-9a-f]+)#.*",
+             "capture": {1: "tid"}},
+        ], True)
+        ret = self.expect_gdbremote_sequence()
+        pid, tid = (int(ret[x], 16) for x in ("pid", "tid"))
+        self.reset_test_sequence()
+
+        # try switching to correct pid
+        self.test_sequence.add_log_lines([
+            "read packet: $Hgp{:x}.{:x}#00".format(pid, tid),
+            {"direction": "send", "regex": r"[$]OK#.*"},
+            "read packet: $Hcp{:x}.{:x}#00".format(pid, tid),
+            {"direction": "send", "regex": r"[$]OK#.*"},
+        ], True)
+        ret = self.expect_gdbremote_sequence()
+
+        # try switching to invalid tid
+        self.test_sequence.add_log_lines([
+            "read packet: $Hgp{:x}.{:x}#00".format(pid, tid+1),
+            {"direction": "send", "regex": r"[$]E15#.*"},
+            "read packet: $Hcp{:x}.{:x}#00".format(pid, tid+1),
+            {"direction": "send", "regex": r"[$]E15#.*"},
+        ], True)
+        ret = self.expect_gdbremote_sequence()
+
+        # try switching to invalid pid
+        self.test_sequence.add_log_lines([
+            "read packet: $Hgp{:x}.{:x}#00".format(pid+1, tid),
+            {"direction": "send", "regex": r"[$]Eff#.*"},
+            "read packet: $Hcp{:x}.{:x}#00".format(pid+1, tid),
+            {"direction": "send", "regex": r"[$]Eff#.*"},
+        ], True)
+        ret = self.expect_gdbremote_sequence()
+
+    def test_detach_current(self):
+        self.build()
+        self.prep_debug_monitor_and_inferior()
+        self.add_qSupported_packets(["multiprocess+"])
+        ret = self.expect_gdbremote_sequence()
+        self.assertIn("multiprocess+", ret["qSupported_response"])
+        self.reset_test_sequence()
+
+        # get process pid
+        procinfo_regex = "[$]pid:([0-9a-f]+);.*"
+        self.test_sequence.add_log_lines([
+            "read packet: $qProcessInfo#00",
+            {"direction": "send", "regex": procinfo_regex,
+             "capture": {1: "pid"}},
+        ], True)
+        ret = self.expect_gdbremote_sequence()
+        pid = int(ret["pid"], 16)
+        self.reset_test_sequence()
+
+        # detach the process
+        self.test_sequence.add_log_lines([
+            "read packet: $D;{:x}#00".format(pid),
+            {"direction": "send", "regex": r"[$]OK#.*"},
+            "read packet: $qC#00",
+            {"direction": "send", "regex": r"[$]E44#.*"},
+        ], True)
+        ret = self.expect_gdbremote_sequence()