diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -2088,16 +2088,6 @@ 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 @@ } 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 @@ } // Parse out the thread number. - llvm::Expected 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( + 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( + 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( + 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 @@ } } - // 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; diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py b/lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py --- a/lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py +++ b/lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py @@ -16,7 +16,7 @@ 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 @@ {"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()