Index: lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py =================================================================== --- lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py +++ lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py @@ -284,9 +284,14 @@ response_packet = _STRIP_COMMAND_PREFIX_M_REGEX.sub("", response_packet) response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet) - # Return list of thread ids - return [int(thread_id_hex, 16) for thread_id_hex in response_packet.split( - ",") if len(thread_id_hex) > 0] + for tid in response_packet.split(","): + if not tid: + continue + if tid.startswith("p"): + pid, _, tid = tid.partition(".") + yield (int(pid[1:], 16), int(tid, 16)) + else: + yield int(tid, 16) def unpack_endian_binary_string(endian, value_string): 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 @@ -1930,38 +1930,48 @@ return SendPacketNoLock(response.GetString()); } -GDBRemoteCommunication::PacketResult -GDBRemoteCommunicationServerLLGS::Handle_qfThreadInfo( - StringExtractorGDBRemote &packet) { +static void AddProcessThreads(StreamGDBRemote &response, + NativeProcessProtocol &process, bool &had_any, + bool include_pid) { Log *log = GetLog(LLDBLog::Thread); - // Fail if we don't have a current process. - if (!m_current_process || - (m_current_process->GetID() == LLDB_INVALID_PROCESS_ID)) { - LLDB_LOG(log, "no process ({0}), returning OK", - m_current_process ? "invalid process id" - : "null m_current_process"); - return SendOKResponse(); - } - - StreamGDBRemote response; - response.PutChar('m'); + lldb::pid_t pid = process.GetID(); + if (pid == LLDB_INVALID_PROCESS_ID) + return; - LLDB_LOG(log, "starting thread iteration"); + LLDB_LOG(log, "iterating over threads of process {0}", process.GetID()); NativeThreadProtocol *thread; uint32_t thread_index; - for (thread_index = 0, - thread = m_current_process->GetThreadAtIndex(thread_index); - thread; ++thread_index, - thread = m_current_process->GetThreadAtIndex(thread_index)) { - LLDB_LOG(log, "iterated thread {0}(tid={2})", thread_index, + for (thread_index = 0, thread = process.GetThreadAtIndex(thread_index); + thread; + ++thread_index, thread = process.GetThreadAtIndex(thread_index)) { + LLDB_LOG(log, "iterated thread {0} (tid={1})", thread_index, thread->GetID()); - if (thread_index > 0) - response.PutChar(','); - response.Printf("%" PRIx64, thread->GetID()); + response.PutChar(had_any ? ',' : 'm'); + if (include_pid) + response.Format("p{0:x-}.", pid); + response.Format("{0:x-}", thread->GetID()); + had_any = true; } +} - LLDB_LOG(log, "finished thread iteration"); +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_qfThreadInfo( + StringExtractorGDBRemote &packet) { + bool multiprocess = bool(m_extensions_supported & + NativeProcessProtocol::Extension::multiprocess); + + bool had_any = false; + StreamGDBRemote response; + + if (multiprocess) { + for (auto &pid_ptr : m_debugged_processes) + AddProcessThreads(response, *pid_ptr.second, had_any, true); + } else if (m_current_process) + AddProcessThreads(response, *m_current_process, had_any, false); + + if (!had_any) + response.PutChar('l'); return SendPacketNoLock(response.GetString()); } Index: lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py =================================================================== --- lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py +++ lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py @@ -554,6 +554,66 @@ ], True) self.expect_gdbremote_sequence() + @add_test_categories(["fork"]) + def test_threadinfo(self): + self.build() + self.prep_debug_monitor_and_inferior( + inferior_args=["fork", + "thread:new", + "trap", + ]) + self.add_qSupported_packets(["multiprocess+", + "fork-events+"]) + ret = self.expect_gdbremote_sequence() + self.assertIn("fork-events+", ret["qSupported_response"]) + self.reset_test_sequence() + + # continue and expect fork + self.test_sequence.add_log_lines([ + "read packet: $c#00", + {"direction": "send", "regex": self.fork_regex.format("fork"), + "capture": self.fork_capture}, + ], True) + self.add_threadinfo_collection_packets() + ret = self.expect_gdbremote_sequence() + pidtids = [ + (ret["parent_pid"], ret["parent_tid"]), + (ret["child_pid"], ret["child_tid"]), + ] + prev_pidtids = set(self.parse_threadinfo_packets(ret)) + self.assertEqual(prev_pidtids, + frozenset((int(pid, 16), int(tid, 16)) + for pid, tid in pidtids)) + self.reset_test_sequence() + + for pidtid in pidtids: + self.test_sequence.add_log_lines( + ["read packet: $Hcp{}.{}#00".format(*pidtid), + "send packet: $OK#00", + "read packet: $c#00", + {"direction": "send", + "regex": "^[$]T05thread:p{}.{}.*".format(*pidtid), + }, + ], True) + self.add_threadinfo_collection_packets() + ret = self.expect_gdbremote_sequence() + self.reset_test_sequence() + new_pidtids = set(self.parse_threadinfo_packets(ret)) + added_pidtid = new_pidtids - prev_pidtids + prev_pidtids = new_pidtids + + # verify that we've got exactly one new thread, and that + # the PID matches + self.assertEqual(len(added_pidtid), 1) + self.assertEqual(added_pidtid.pop()[0], int(pidtid[0], 16)) + + for pidtid in new_pidtids: + self.test_sequence.add_log_lines( + ["read packet: $Hgp{:x}.{:x}#00".format(*pidtid), + "send packet: $OK#00", + ], True) + self.expect_gdbremote_sequence() + @add_test_categories(["fork"]) def test_memory_read_write(self): self.build()