diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -30,6 +30,8 @@ #include namespace lldb_private { +LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); + class MemoryRegionInfo; class ResumeActionList; @@ -228,6 +230,16 @@ virtual Status GetFileLoadAddress(const llvm::StringRef &file_name, lldb::addr_t &load_addr) = 0; + /// Extension flag constants, returned by Factory::GetSupportedExtensions() + /// and passed to SetEnabledExtension() + enum class Extension { + multiprocess = (1u << 0), + fork = (1u << 1), + vfork = (1u << 2), + + LLVM_MARK_AS_BITMASK_ENUM(vfork) + }; + class Factory { public: virtual ~Factory(); @@ -274,6 +286,12 @@ virtual llvm::Expected> Attach(lldb::pid_t pid, NativeDelegate &native_delegate, MainLoop &mainloop) const = 0; + + /// Get the bitmask of extensions supported by this process plugin. + /// + /// \return + /// A NativeProcessProtocol::Extension bitmask. + virtual Extension GetSupportedExtensions() const { return {}; } }; /// Start tracing a process or its threads. @@ -328,6 +346,15 @@ return llvm::make_error(); } + /// Method called in order to propagate the bitmap of protocol + /// extensions supported by the client. + /// + /// \param[in] flags + /// The bitmap of enabled extensions. + virtual void SetEnabledExtensions(Extension flags) { + m_enabled_extensions = flags; + } + protected: struct SoftwareBreakpoint { uint32_t ref_count; @@ -357,6 +384,9 @@ // stopping it. llvm::DenseSet m_signals_to_ignore; + // Extensions enabled per the last SetEnabledExtensions() call. + Extension m_enabled_extensions; + // lldb_private::Host calls should be used to launch a process for debugging, // and then the process should be attached to. When attaching to a process // lldb_private::Host calls should be used to locate the process to attach diff --git a/lldb/packages/Python/lldbsuite/test/dotest.py b/lldb/packages/Python/lldbsuite/test/dotest.py --- a/lldb/packages/Python/lldbsuite/test/dotest.py +++ b/lldb/packages/Python/lldbsuite/test/dotest.py @@ -858,6 +858,15 @@ if configuration.verbose: print(skip_msg%"lldb-server"); + +def checkForkVForkSupport(): + from lldbsuite.test import lldbplatformutil + + platform = lldbplatformutil.getPlatform() + if platform not in []: + configuration.skip_categories.append("fork") + + def run_suite(): # On MacOS X, check to make sure that domain for com.apple.DebugSymbols defaults # does not exist before proceeding to running the test suite. @@ -954,6 +963,7 @@ checkDebugInfoSupport() checkDebugServerSupport() checkObjcSupport() + checkForkVForkSupport() print("Skipping the following test categories: {}".format(configuration.skip_categories)) diff --git a/lldb/packages/Python/lldbsuite/test/test_categories.py b/lldb/packages/Python/lldbsuite/test/test_categories.py --- a/lldb/packages/Python/lldbsuite/test/test_categories.py +++ b/lldb/packages/Python/lldbsuite/test/test_categories.py @@ -30,6 +30,7 @@ 'dyntype': 'Tests related to dynamic type support', 'expression': 'Tests related to the expression parser', 'flakey': 'Flakey test cases, i.e. tests that do not reliably pass at each execution', + 'fork': 'Tests requiring the process plugin fork/vfork event support', 'gmodules': 'Tests that can be run with -gmodules debug information', 'instrumentation-runtime': 'Tests for the instrumentation runtime plugins', 'libc++': 'Test for libc++ data formatters', diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -835,9 +835,10 @@ "send packet: $OK#00", ], True) - def add_qSupported_packets(self): + def add_qSupported_packets(self, client_features=[]): + features = ''.join(';' + x for x in client_features) self.test_sequence.add_log_lines( - ["read packet: $qSupported#00", + ["read packet: $qSupported{}#00".format(features), {"direction": "send", "regex": r"^\$(.*)#[0-9a-fA-F]{2}", "capture": {1: "qSupported_response"}}, ], True) @@ -854,6 +855,8 @@ "qEcho", "QPassSignals", "multiprocess", + "fork-events", + "vfork-events", ] def parse_qSupported_response(self, context): diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -103,6 +103,8 @@ bool m_thread_suffix_supported = false; bool m_list_threads_in_stop_reply = false; + NativeProcessProtocol::Extension m_extensions_supported = {}; + PacketResult SendONotification(const char *buffer, uint32_t len); PacketResult SendWResponse(NativeProcessProtocol *process); @@ -264,6 +266,9 @@ llvm::Expected ReadTid(StringExtractorGDBRemote &packet, bool allow_all, lldb::pid_t default_pid); + // Call SetEnabledExtensions() with appropriate flags on the process. + void SetEnabledExtensions(NativeProcessProtocol &process); + // For GDBRemoteCommunicationServerLLGS only GDBRemoteCommunicationServerLLGS(const GDBRemoteCommunicationServerLLGS &) = delete; 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 @@ -259,6 +259,8 @@ m_continue_process = m_current_process = m_debugged_process_up.get(); } + SetEnabledExtensions(*m_current_process); + // Handle mirroring of inferior stdout/stderr over the gdb-remote protocol as // needed. llgs local-process debugging may specify PTY paths, which will // make these file actions non-null process launch -i/e/o will also make @@ -327,6 +329,7 @@ } m_debugged_process_up = std::move(*process_or); m_continue_process = m_current_process = m_debugged_process_up.get(); + SetEnabledExtensions(*m_current_process); // Setup stdout/stderr mapping from inferior. auto terminal_fd = m_debugged_process_up->GetTerminalFileDescriptor(); @@ -3557,14 +3560,48 @@ std::vector GDBRemoteCommunicationServerLLGS::HandleFeatures( const llvm::ArrayRef client_features) { - auto ret = + std::vector ret = GDBRemoteCommunicationServerCommon::HandleFeatures(client_features); ret.insert(ret.end(), { "QThreadSuffixSupported+", "QListThreadsInStopReply+", - "qXfer:features:read+", "multiprocess+", + "qXfer:features:read+", #if defined(__linux__) || defined(__NetBSD__) || defined(__FreeBSD__) "QPassSignals+", "qXfer:auxv:read+", "qXfer:libraries-svr4:read+", #endif }); + + // check for client features + using Extension = NativeProcessProtocol::Extension; + m_extensions_supported = {}; + for (llvm::StringRef x : client_features) + m_extensions_supported |= + llvm::StringSwitch(x) + .Case("multiprocess+", Extension::multiprocess) + .Case("fork-events+", Extension::fork) + .Case("vfork-events+", Extension::vfork) + .Default({}); + m_extensions_supported &= m_process_factory.GetSupportedExtensions(); + + // fork & vfork require multiprocess + if (!bool(m_extensions_supported & Extension::multiprocess)) + m_extensions_supported &= ~(Extension::fork | Extension::vfork); + + // report only if actually supported + if (bool(m_extensions_supported & Extension::multiprocess)) + ret.push_back("multiprocess+"); + if (bool(m_extensions_supported & Extension::fork)) + ret.push_back("fork-events+"); + if (bool(m_extensions_supported & Extension::vfork)) + ret.push_back("vfork-events+"); + + if (m_debugged_process_up) + SetEnabledExtensions(*m_debugged_process_up); return ret; } + +void GDBRemoteCommunicationServerLLGS::SetEnabledExtensions( + NativeProcessProtocol &process) { + NativeProcessProtocol::Extension flags = m_extensions_supported; + assert(!bool(flags & ~m_process_factory.GetSupportedExtensions())); + process.SetEnabledExtensions(flags); +} diff --git a/lldb/test/API/tools/lldb-server/TestLldbGdbServer.py b/lldb/test/API/tools/lldb-server/TestLldbGdbServer.py --- a/lldb/test/API/tools/lldb-server/TestLldbGdbServer.py +++ b/lldb/test/API/tools/lldb-server/TestLldbGdbServer.py @@ -954,23 +954,58 @@ self.set_inferior_startup_launch() self.breakpoint_set_and_remove_work(want_hardware=True) - def test_qSupported_returns_known_stub_features(self): + def get_qSupported_dict(self, features=[]): self.build() self.set_inferior_startup_launch() # Start up the stub and start/prep the inferior. procs = self.prep_debug_monitor_and_inferior() - self.add_qSupported_packets() + self.add_qSupported_packets(features) # Run the packet stream. context = self.expect_gdbremote_sequence() self.assertIsNotNone(context) # Retrieve the qSupported features. - supported_dict = self.parse_qSupported_response(context) + return self.parse_qSupported_response(context) + + def test_qSupported_returns_known_stub_features(self): + supported_dict = self.get_qSupported_dict() self.assertIsNotNone(supported_dict) self.assertTrue(len(supported_dict) > 0) + @add_test_categories(["fork"]) + def test_qSupported_fork_events(self): + supported_dict = ( + self.get_qSupported_dict(['multiprocess+', 'fork-events+'])) + self.assertEqual(supported_dict.get('multiprocess', '-'), '+') + self.assertEqual(supported_dict.get('fork-events', '-'), '+') + self.assertEqual(supported_dict.get('vfork-events', '-'), '-') + + @add_test_categories(["fork"]) + def test_qSupported_fork_events_without_multiprocess(self): + supported_dict = ( + self.get_qSupported_dict(['fork-events+'])) + self.assertEqual(supported_dict.get('multiprocess', '-'), '-') + self.assertEqual(supported_dict.get('fork-events', '-'), '-') + self.assertEqual(supported_dict.get('vfork-events', '-'), '-') + + @add_test_categories(["fork"]) + def test_qSupported_vfork_events(self): + supported_dict = ( + self.get_qSupported_dict(['multiprocess+', 'vfork-events+'])) + self.assertEqual(supported_dict.get('multiprocess', '-'), '+') + self.assertEqual(supported_dict.get('fork-events', '-'), '-') + self.assertEqual(supported_dict.get('vfork-events', '-'), '+') + + @add_test_categories(["fork"]) + def test_qSupported_vfork_events_without_multiprocess(self): + supported_dict = ( + self.get_qSupported_dict(['vfork-events+'])) + self.assertEqual(supported_dict.get('multiprocess', '-'), '-') + self.assertEqual(supported_dict.get('fork-events', '-'), '-') + self.assertEqual(supported_dict.get('vfork-events', '-'), '-') + @skipIfWindows # No pty support to test any inferior output def test_written_M_content_reads_back_correctly(self): self.build()