Index: lldb/bindings/interface/SBThread.i =================================================================== --- lldb/bindings/interface/SBThread.i +++ lldb/bindings/interface/SBThread.i @@ -104,6 +104,8 @@ eStopReasonSignal 1 unix signal number eStopReasonException N exception data eStopReasonExec 0 + eStopReasonFork 1 pid of the child process + eStopReasonVFork 1 pid of the child process eStopReasonPlanComplete 0") GetStopReasonDataAtIndex; uint64_t GetStopReasonDataAtIndex(uint32_t idx); Index: lldb/bindings/interface/SBThreadPlan.i =================================================================== --- lldb/bindings/interface/SBThreadPlan.i +++ lldb/bindings/interface/SBThreadPlan.i @@ -73,6 +73,8 @@ eStopReasonSignal 1 unix signal number eStopReasonException N exception data eStopReasonExec 0 + eStopReasonFork 1 pid of the child process + eStopReasonVFork 1 pid of the child process eStopReasonPlanComplete 0") GetStopReasonDataAtIndex; uint64_t GetStopReasonDataAtIndex(uint32_t idx); Index: lldb/docs/python_api_enums.rst =================================================================== --- lldb/docs/python_api_enums.rst +++ lldb/docs/python_api_enums.rst @@ -342,6 +342,8 @@ .. py:data:: eStopReasonSignal .. py:data:: eStopReasonException .. py:data:: eStopReasonExec +.. py:data:: eStopReasonFork +.. py:data:: eStopReasonVFork .. py:data:: eStopReasonPlanComplete .. py:data:: eStopReasonThreadExiting .. py:data:: eStopReasonInstrumentation Index: lldb/examples/python/performance.py =================================================================== --- lldb/examples/python/performance.py +++ lldb/examples/python/performance.py @@ -255,6 +255,12 @@ select_thread = True if self.verbose: print("signal %d" % (thread.GetStopReasonDataAtIndex(0))) + elif stop_reason == lldb.eStopReasonFork: + if self.verbose: + print("fork pid = %d" % (thread.GetStopReasonDataAtIndex(0))) + elif stop_reason == lldb.eStopReasonVFork: + if self.verbose: + print("vfork pid = %d" % (thread.GetStopReasonDataAtIndex(0))) if select_thread and not selected_thread: self.thread = thread Index: lldb/include/lldb/API/SBThread.h =================================================================== --- lldb/include/lldb/API/SBThread.h +++ lldb/include/lldb/API/SBThread.h @@ -66,6 +66,8 @@ /// eStopReasonSignal 1 unix signal number /// eStopReasonException N exception data /// eStopReasonExec 0 + /// eStopReasonFork 1 pid of the child process + /// eStopReasonVFork 1 pid of the child process /// eStopReasonPlanComplete 0 uint64_t GetStopReasonDataAtIndex(uint32_t idx); Index: lldb/include/lldb/API/SBThreadPlan.h =================================================================== --- lldb/include/lldb/API/SBThreadPlan.h +++ lldb/include/lldb/API/SBThreadPlan.h @@ -58,6 +58,8 @@ /// eStopReasonSignal 1 unix signal number /// eStopReasonException N exception data /// eStopReasonExec 0 + /// eStopReasonFork 1 pid of the child process + /// eStopReasonVFork 1 pid of the child process /// eStopReasonPlanComplete 0 uint64_t GetStopReasonDataAtIndex(uint32_t idx); Index: lldb/include/lldb/Host/Debug.h =================================================================== --- lldb/include/lldb/Host/Debug.h +++ lldb/include/lldb/Host/Debug.h @@ -144,6 +144,12 @@ uint32_t data_count; lldb::addr_t data[8]; } exception; + + // eStopReasonFork / eStopReasonVFork + struct { + lldb::pid_t child_pid; + lldb::tid_t child_tid; + } fork; } details; }; } Index: lldb/include/lldb/Host/common/NativeProcessProtocol.h =================================================================== --- lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ 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; @@ -358,6 +360,44 @@ return llvm::make_error(); } + /// Extension flag constants, passed to SetEnabledExtension(). + enum class Extension { + fork = (1u << 0), + vfork = (1u << 1), + + LLVM_MARK_AS_BITMASK_ENUM(vfork) + }; + + /// Method called in order to propagate the bitmap of protocol + /// extensions supported by the client. + /// + /// \param[in] flags + /// The bitmap of enabled extensions. + /// + /// \return An error if extension-related setup failed, success + /// otherwise. + virtual llvm::Error SetEnabledExtensions(Extension flags) { + m_enabled_extensions = flags; + return llvm::Error::success(); + } + + /// Get subprocess (i.e. forked process) corresponding to PID. + /// + /// \param[in] pid + /// The PID of requested process. + /// + /// \return A reference to NativeProcessProtocol instance, nullptr + /// if no process with specified PID is traced. + virtual NativeProcessProtocol *GetSubprocess(lldb::pid_t pid) { + return nullptr; + } + + /// Erase subprocess corresponding to PID. + /// + /// \param[in] pid + /// The PID of requested process. + virtual void EraseSubprocess(lldb::pid_t pid) {} + protected: struct SoftwareBreakpoint { uint32_t ref_count; @@ -388,6 +428,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 Index: lldb/include/lldb/Target/Process.h =================================================================== --- lldb/include/lldb/Target/Process.h +++ lldb/include/lldb/Target/Process.h @@ -978,6 +978,9 @@ /// anything after a process exec's itself. virtual void DoDidExec() {} + /// Called after a reported fork. + virtual void DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {} + /// Called before launching to a process. /// /// Allow Process plug-ins to execute some code before launching a process. Index: lldb/include/lldb/Target/StopInfo.h =================================================================== --- lldb/include/lldb/Target/StopInfo.h +++ lldb/include/lldb/Target/StopInfo.h @@ -132,6 +132,14 @@ static lldb::StopInfoSP CreateStopReasonProcessorTrace(Thread &thread, const char *description); + static lldb::StopInfoSP CreateStopReasonWithFork(Thread &thread, + lldb::pid_t child_pid, + lldb::tid_t child_tid); + + static lldb::StopInfoSP CreateStopReasonWithVFork(Thread &thread, + lldb::pid_t child_pid, + lldb::tid_t child_tid); + static lldb::ValueObjectSP GetReturnValueObject(lldb::StopInfoSP &stop_info_sp); Index: lldb/include/lldb/lldb-enumerations.h =================================================================== --- lldb/include/lldb/lldb-enumerations.h +++ lldb/include/lldb/lldb-enumerations.h @@ -249,6 +249,8 @@ eStopReasonThreadExiting, eStopReasonInstrumentation, eStopReasonProcessorTrace, + eStopReasonFork, + eStopReasonVFork, }; /// Command Return Status Types. Index: lldb/packages/Python/lldbsuite/test/lldbutil.py =================================================================== --- lldb/packages/Python/lldbsuite/test/lldbutil.py +++ lldb/packages/Python/lldbsuite/test/lldbutil.py @@ -247,6 +247,10 @@ return "watchpoint" elif enum == lldb.eStopReasonExec: return "exec" + elif enum == lldb.eStopReasonFork: + return "fork" + elif enum == lldb.eStopReasonVFork: + return "vfork" elif enum == lldb.eStopReasonSignal: return "signal" elif enum == lldb.eStopReasonException: Index: lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py =================================================================== --- lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -904,6 +904,8 @@ "qEcho", "QPassSignals", "multiprocess", + "fork-events", + "vfork-events", ] def parse_qSupported_response(self, context): Index: lldb/source/API/SBThread.cpp =================================================================== --- lldb/source/API/SBThread.cpp +++ lldb/source/API/SBThread.cpp @@ -195,6 +195,12 @@ case eStopReasonException: return 1; + + case eStopReasonFork: + return 1; + + case eStopReasonVFork: + return 1; } } } @@ -258,6 +264,12 @@ case eStopReasonException: return stop_info_sp->GetValue(); + + case eStopReasonFork: + return stop_info_sp->GetValue(); + + case eStopReasonVFork: + return stop_info_sp->GetValue(); } } } Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -249,6 +249,12 @@ // is pending second notification. bool MonitorClone(lldb::pid_t child_pid, llvm::Optional clone_info); + + MainLoop m_subprocess_loop; + std::vector> m_subprocesses; + + NativeProcessProtocol *GetSubprocess(lldb::pid_t pid) override; + void EraseSubprocess(lldb::pid_t pid) override; }; } // namespace process_linux Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -914,16 +914,19 @@ LLVM_FALLTHROUGH; case PTRACE_EVENT_FORK: case PTRACE_EVENT_VFORK: { - MainLoop unused_loop; - NativeProcessLinux child_process{static_cast<::pid_t>(child_pid), - m_terminal_fd, - *m_delegates[0], - m_arch, - unused_loop, - {static_cast<::pid_t>(child_pid)}}; - child_process.Detach(); - ResumeThread(*parent_thread, parent_thread->GetState(), - LLDB_INVALID_SIGNAL_NUMBER); + std::unique_ptr child_process{new NativeProcessLinux( + static_cast<::pid_t>(child_pid), m_terminal_fd, *m_delegates[0], m_arch, + m_subprocess_loop, {static_cast<::pid_t>(child_pid)})}; + child_process->m_software_breakpoints = m_software_breakpoints; + if ((m_enabled_extensions & Extension::fork) == Extension::fork) { + m_subprocesses.push_back(std::move(child_process)); + parent_thread->SetStoppedByFork(child_pid); + StopRunningThreads(parent_thread->GetID()); + } else { + child_process->Detach(); + ResumeThread(*parent_thread, parent_thread->GetState(), + LLDB_INVALID_SIGNAL_NUMBER); + } break; } default: @@ -1893,3 +1896,21 @@ return m_intel_pt_manager.GetBinaryData(request); return NativeProcessProtocol::TraceGetBinaryData(request); } + +NativeProcessProtocol *NativeProcessLinux::GetSubprocess(lldb::pid_t pid) { + for (auto& p : m_subprocesses) { + if (p->GetID() == pid) + return p.get(); + } + + return nullptr; +} + +void NativeProcessLinux::EraseSubprocess(lldb::pid_t pid) { + for (auto it = m_subprocesses.begin(); it != m_subprocesses.end();) { + if ((*it)->GetID() == pid) + it = m_subprocesses.erase(it); + else + ++it; + } +} Index: lldb/source/Plugins/Process/Linux/NativeThreadLinux.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeThreadLinux.h +++ lldb/source/Plugins/Process/Linux/NativeThreadLinux.h @@ -85,6 +85,8 @@ void SetStoppedByTrace(); + void SetStoppedByFork(lldb::pid_t child_pid); + void SetStoppedWithNoReason(); void SetStoppedByProcessorTrace(llvm::StringRef description); Index: lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp +++ lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp @@ -394,6 +394,14 @@ m_stop_info.details.signal.signo = SIGTRAP; } +void NativeThreadLinux::SetStoppedByFork(lldb::pid_t child_pid) { + SetStopped(); + + m_stop_info.reason = StopReason::eStopReasonFork; + m_stop_info.details.fork.child_pid = child_pid; + m_stop_info.details.fork.child_tid = child_pid; +} + void NativeThreadLinux::SetStoppedWithNoReason() { SetStopped(); Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -229,7 +229,7 @@ bool DeallocateMemory(lldb::addr_t addr); - Status Detach(bool keep_stopped); + Status Detach(bool keep_stopped, lldb::pid_t pid = LLDB_INVALID_PROCESS_ID); Status GetMemoryRegionInfo(lldb::addr_t addr, MemoryRegionInfo &range_info); @@ -334,7 +334,8 @@ // and response times. bool SendSpeedTestPacket(uint32_t send_size, uint32_t recv_size); - bool SetCurrentThread(uint64_t tid); + bool SetCurrentThread(uint64_t tid, + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID); bool SetCurrentThreadForRun(uint64_t tid); @@ -556,6 +557,8 @@ LazyBool m_supports_QPassSignals; LazyBool m_supports_error_string_reply; LazyBool m_supports_multiprocess; + LazyBool m_supports_fork_events; + LazyBool m_supports_vfork_events; bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1, m_supports_qUserName : 1, m_supports_qGroupName : 1, @@ -567,6 +570,7 @@ m_supports_jModulesInfo : 1; lldb::pid_t m_curr_pid; + lldb::pid_t m_override_pid; // Used when handling forks lldb::tid_t m_curr_tid; // Current gdb remote protocol thread index for all // other operations lldb::tid_t m_curr_tid_run; // Current gdb remote protocol thread index for Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -90,6 +90,8 @@ m_supports_QPassSignals(eLazyBoolCalculate), m_supports_error_string_reply(eLazyBoolCalculate), m_supports_multiprocess(eLazyBoolCalculate), + m_supports_fork_events(eLazyBoolCalculate), + m_supports_vfork_events(eLazyBoolCalculate), m_supports_qProcessInfoPID(true), m_supports_qfProcessInfo(true), m_supports_qUserName(true), m_supports_qGroupName(true), m_supports_qThreadStopInfo(true), m_supports_z0(true), @@ -98,7 +100,9 @@ m_supports_QEnvironmentHexEncoded(true), m_supports_qSymbol(true), m_qSymbol_requests_done(false), m_supports_qModuleInfo(true), m_supports_jThreadsInfo(true), m_supports_jModulesInfo(true), - m_curr_pid(LLDB_INVALID_PROCESS_ID), m_curr_tid(LLDB_INVALID_THREAD_ID), + m_curr_pid(LLDB_INVALID_PROCESS_ID), + m_override_pid(LLDB_INVALID_PROCESS_ID), + m_curr_tid(LLDB_INVALID_THREAD_ID), m_curr_tid_run(LLDB_INVALID_THREAD_ID), m_num_supported_hardware_watchpoints(0), m_host_arch(), m_process_arch(), m_os_build(), m_os_kernel(), m_hostname(), m_gdb_server_name(), @@ -294,6 +298,8 @@ m_attach_or_wait_reply = eLazyBoolCalculate; m_avoid_g_packets = eLazyBoolCalculate; m_supports_multiprocess = eLazyBoolCalculate; + m_supports_fork_events = eLazyBoolCalculate; + m_supports_vfork_events = eLazyBoolCalculate; m_supports_qXfer_auxv_read = eLazyBoolCalculate; m_supports_qXfer_libraries_read = eLazyBoolCalculate; m_supports_qXfer_libraries_svr4_read = eLazyBoolCalculate; @@ -345,12 +351,16 @@ m_supports_qXfer_features_read = eLazyBoolNo; m_supports_qXfer_memory_map_read = eLazyBoolNo; m_supports_multiprocess = eLazyBoolNo; + m_supports_fork_events = eLazyBoolNo; + m_supports_vfork_events = eLazyBoolNo; m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if // not, we assume no limit // build the qSupported packet std::vector features = {"xmlRegisters=i386,arm,mips,arc", - "multiprocess+"}; + "multiprocess+", + "fork-events+", + "vfork-events+"}; StreamString packet; packet.PutCString("qSupported"); for (uint32_t i = 0; i < features.size(); ++i) { @@ -442,6 +452,16 @@ else m_supports_multiprocess = eLazyBoolNo; + if (::strstr(response_cstr, "fork-events+")) + m_supports_fork_events = eLazyBoolYes; + else + m_supports_fork_events = eLazyBoolNo; + + if (::strstr(response_cstr, "vfork-events+")) + m_supports_vfork_events = eLazyBoolYes; + else + m_supports_vfork_events = eLazyBoolNo; + const char *packet_size_str = ::strstr(response_cstr, "PacketSize="); if (packet_size_str) { StringExtractorGDBRemote packet_response(packet_size_str + @@ -1454,9 +1474,12 @@ return false; } -Status GDBRemoteCommunicationClient::Detach(bool keep_stopped) { +Status GDBRemoteCommunicationClient::Detach(bool keep_stopped, + lldb::pid_t pid) { Status error; + lldb_private::StreamString packet; + packet.PutChar('D'); if (keep_stopped) { if (m_supports_detach_stay_stopped == eLazyBoolCalculate) { char packet[64]; @@ -1478,19 +1501,25 @@ error.SetErrorString("Stays stopped not supported by this target."); return error; } else { - StringExtractorGDBRemote response; - PacketResult packet_result = - SendPacketAndWaitForResponse("D1", response, false); - if (packet_result != PacketResult::Success) - error.SetErrorString("Sending extended disconnect packet failed."); + packet.PutChar('1'); } - } else { - StringExtractorGDBRemote response; - PacketResult packet_result = - SendPacketAndWaitForResponse("D", response, false); - if (packet_result != PacketResult::Success) - error.SetErrorString("Sending disconnect packet failed."); } + + if (pid != LLDB_INVALID_PROCESS_ID) { + if (!m_supports_multiprocess) { + error.SetErrorString( + "Multiprocess extension not supported by the server."); + return error; + } + packet.PutChar(';'); + packet.PutHex64(pid); + } + + StringExtractorGDBRemote response; + PacketResult packet_result = + SendPacketAndWaitForResponse(packet.GetString(), response, false); + if (packet_result != PacketResult::Success) + error.SetErrorString("Sending isconnect packet failed."); return error; } @@ -2630,22 +2659,32 @@ return false; } -bool GDBRemoteCommunicationClient::SetCurrentThread(uint64_t tid) { - if (m_curr_tid == tid) +bool GDBRemoteCommunicationClient::SetCurrentThread(uint64_t tid, + lldb::pid_t pid) { + if (m_curr_tid == tid && m_override_pid == pid) return true; - char packet[32]; - int packet_len; + lldb_private::StreamString packet; + packet.PutCString("Hg"); + if (m_override_pid != pid) { + if (pid == LLDB_INVALID_PROCESS_ID) { + assert(m_curr_pid != LLDB_INVALID_PROCESS_ID); + pid = m_curr_pid; + } + packet.PutChar('p'); + packet.PutHex64(pid); + packet.PutChar('.'); + } if (tid == UINT64_MAX) - packet_len = ::snprintf(packet, sizeof(packet), "Hg-1"); + packet.PutCString("-1"); else - packet_len = ::snprintf(packet, sizeof(packet), "Hg%" PRIx64, tid); - assert(packet_len + 1 < (int)sizeof(packet)); - UNUSED_IF_ASSERT_DISABLED(packet_len); + packet.PutHex64(tid); + StringExtractorGDBRemote response; - if (SendPacketAndWaitForResponse(packet, response, false) == + if (SendPacketAndWaitForResponse(packet.GetString(), response, false) == PacketResult::Success) { if (response.IsOKResponse()) { + m_override_pid = pid; m_curr_tid = tid; return true; } Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h @@ -38,6 +38,8 @@ uint32_t m_proc_infos_index; bool m_thread_suffix_supported; bool m_list_threads_in_stop_reply; + bool m_fork_events_supported; + bool m_vfork_events_supported; PacketResult Handle_A(StringExtractorGDBRemote &packet); @@ -145,6 +147,11 @@ virtual FileSpec FindModuleFile(const std::string &module_path, const ArchSpec &arch); + // Process client_features (qSupported) and return an array of server features + // to be returned in response. + virtual llvm::SmallVector + HandleFeatures(const llvm::ArrayRef client_features); + private: ModuleSpec GetModuleInfo(llvm::StringRef module_path, llvm::StringRef triple); }; Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp @@ -61,7 +61,8 @@ : GDBRemoteCommunicationServer(comm_name, listener_name), m_process_launch_info(), m_process_launch_error(), m_proc_infos(), m_proc_infos_index(0), m_thread_suffix_supported(false), - m_list_threads_in_stop_reply(false) { + m_list_threads_in_stop_reply(false), m_fork_events_supported(false), + m_vfork_events_supported(false) { RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_A, &GDBRemoteCommunicationServerCommon::Handle_A); RegisterMemberFunctionHandler( @@ -831,26 +832,11 @@ GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerCommon::Handle_qSupported( StringExtractorGDBRemote &packet) { - StreamGDBRemote response; - - // Features common to lldb-platform and llgs. - uint32_t max_packet_size = 128 * 1024; // 128KBytes is a reasonable max packet - // size--debugger can always use less - response.Printf("PacketSize=%x", max_packet_size); - - response.PutCString(";QStartNoAckMode+"); - response.PutCString(";QThreadSuffixSupported+"); - response.PutCString(";QListThreadsInStopReply+"); - response.PutCString(";qEcho+"); - response.PutCString(";qXfer:features:read+"); -#if defined(__linux__) || defined(__NetBSD__) || defined(__FreeBSD__) - response.PutCString(";QPassSignals+"); - response.PutCString(";qXfer:auxv:read+"); - response.PutCString(";qXfer:libraries-svr4:read+"); -#endif - response.PutCString(";multiprocess+"); - - return SendPacketNoLock(response.GetString()); + // Parse client-indicated features. + llvm::StringRef view = packet.GetStringRef(); + llvm::SmallVector client_features; + view.split(client_features, ';'); + return SendPacketNoLock(llvm::join(HandleFeatures(client_features), ";")); } GDBRemoteCommunication::PacketResult @@ -1312,3 +1298,36 @@ return matched_module_spec; } + +llvm::SmallVector +GDBRemoteCommunicationServerCommon::HandleFeatures( + const llvm::ArrayRef client_features) { + // Features common to lldb-platform and llgs. + uint32_t max_packet_size = 128 * 1024; // 128KBytes is a reasonable max packet + // size--debugger can always use less + + llvm::SmallVector server_features{{ + llvm::formatv("PacketSize={0}", max_packet_size), + "QStartNoAckMode+", + "QThreadSuffixSupported+", + "qEcho+", + "qXfer:features:read+", +#if defined(__linux__) || defined(__NetBSD__) || defined(__FreeBSD__) + "QPassSignals+", + "qXfer:auxv:read+", + "qXfer:libraries-svr4:read+", +#endif + "multiprocess+", + "fork-events+", + "vfork-events+", + }}; + + for (const auto &x : client_features) { + if (x == "fork-events+") + m_fork_events_supported = true; + else if (x == "vfork-events+") + m_vfork_events_supported = true; + } + + return server_features; +} 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 @@ -86,6 +86,7 @@ const NativeProcessProtocol::Factory &m_process_factory; lldb::tid_t m_current_tid = LLDB_INVALID_THREAD_ID; lldb::tid_t m_continue_tid = LLDB_INVALID_THREAD_ID; + lldb::pid_t m_subprocess_pid = LLDB_INVALID_PROCESS_ID; std::recursive_mutex m_debugged_process_mutex; std::unique_ptr m_debugged_process_up; @@ -201,6 +202,8 @@ PacketResult Handle_g(StringExtractorGDBRemote &packet); + PacketResult Handle_qSupported_LLGS(StringExtractorGDBRemote &packet); + void SetCurrentThreadID(lldb::tid_t tid); lldb::tid_t GetCurrentThreadID() const; @@ -209,6 +212,8 @@ lldb::tid_t GetContinueThreadID() const { return m_continue_tid; } + void SetCurrentSubprocess(lldb::pid_t pid, lldb::tid_t tid); + Status SetSTDIOFileDescriptor(int fd); FileSpec FindModuleFile(const std::string &module_path, @@ -219,6 +224,15 @@ static std::string XMLEncodeAttributeValue(llvm::StringRef value); + llvm::SmallVector + HandleFeatures(const llvm::ArrayRef client_features) override; + + // Get NativeProcessProtocol instance corresponding to the currently selected + // process. If allow_multiprocess is true, this can be a forked subprocess. + // Returns nullptr if no process is being debugged, invalid process was + // selected or allow_subprocess is false and a subprocess is selected. + NativeProcessProtocol *GetSelectedProcess(bool allow_subprocess = false); + private: llvm::Expected> BuildTargetXml(); @@ -253,6 +267,9 @@ llvm::Expected ReadTid(StringExtractorGDBRemote &packet, bool allow_all = false); + // Call SetEnabledExtensions() with appropriate flags on the process. + void SetEnabledExtensions(NativeProcessProtocol& process); + // 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 @@ -251,6 +251,8 @@ m_debugged_process_up = std::move(*process_or); } + SetEnabledExtensions(*m_debugged_process_up); + // 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 @@ -318,6 +320,7 @@ return status; } m_debugged_process_up = std::move(*process_or); + SetEnabledExtensions(*m_debugged_process_up); // Setup stdout/stderr mapping from inferior. auto terminal_fd = m_debugged_process_up->GetTerminalFileDescriptor(); @@ -647,6 +650,10 @@ return "exec"; case eStopReasonProcessorTrace: return "processor trace"; + case eStopReasonFork: + return "fork"; + case eStopReasonVFork: + return "vfork"; case eStopReasonInstrumentation: case eStopReasonInvalid: case eStopReasonPlanComplete: @@ -923,6 +930,14 @@ } } + // Include child process PID/TID for forks. + if (tid_stop_info.reason == eStopReasonFork || + tid_stop_info.reason == eStopReasonVFork) { + response.Printf("%s:p%" PRIx64 ".%" PRIx64 ";", reason_str, + tid_stop_info.details.fork.child_pid, + tid_stop_info.details.fork.child_tid); + } + return SendPacketNoLock(response.GetString()); } @@ -1607,6 +1622,8 @@ m_current_tid = tid; if (m_debugged_process_up) m_debugged_process_up->SetCurrentThreadID(m_current_tid); + // reset subprocess support + m_subprocess_pid = LLDB_INVALID_PROCESS_ID; } void GDBRemoteCommunicationServerLLGS::SetContinueThreadID(lldb::tid_t tid) { @@ -1614,6 +1631,17 @@ LLDB_LOG(log, "setting continue thread id to {0}", tid); m_continue_tid = tid; + // reset subprocess support + m_subprocess_pid = LLDB_INVALID_PROCESS_ID; +} + +void GDBRemoteCommunicationServerLLGS::SetCurrentSubprocess(lldb::pid_t pid, + lldb::tid_t tid) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); + LLDB_LOG(log, "setting current subprocess to {0}, thread id {1}", pid, tid); + + m_subprocess_pid = pid; + m_current_tid = tid; } GDBRemoteCommunication::PacketResult @@ -2067,11 +2095,33 @@ } // Parse out the thread number. - llvm::Expected tid_ret = ReadTid(packet, /*allow_all=*/true); - if (!tid_ret) - return SendErrorResponse(tid_ret.takeError()); + auto pid_tid = packet.GetPidTid(m_debugged_process_up->GetID()); + 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 != m_debugged_process_up->GetID()) { + // Is it a subprocess? + NativeProcessProtocol *subprocess = + m_debugged_process_up->GetSubprocess(pid); + + if (!subprocess) + return SendErrorResponse(llvm::make_error( + inconvertibleErrorCode(), + llvm::formatv("PID {0} not debugged", pid))); + if (h_variant != 'g') + return SendUnimplementedResponse( + "Hc packet for subprocess not supported"); + + SetCurrentSubprocess(pid, tid); + return SendOKResponse(); + } - 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) { @@ -2615,13 +2665,9 @@ GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_z(StringExtractorGDBRemote &packet) { - // Ensure we have a process. - if (!m_debugged_process_up || - (m_debugged_process_up->GetID() == LLDB_INVALID_PROCESS_ID)) { - Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); - LLDB_LOG(log, "failed, no process available"); + NativeProcessProtocol *process = GetSelectedProcess(true); + if (!process) return SendErrorResponse(0x15); - } // Parse out software or hardware breakpoint or watchpoint requested. packet.SetFilePos(strlen("z")); @@ -2680,22 +2726,21 @@ if (want_breakpoint) { // Try to clear the breakpoint. - const Status error = - m_debugged_process_up->RemoveBreakpoint(addr, want_hardware); + const Status error = process->RemoveBreakpoint(addr, want_hardware); if (error.Success()) return SendOKResponse(); Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); - LLDB_LOG(log, "pid {0} failed to remove breakpoint: {1}", - m_debugged_process_up->GetID(), error); + LLDB_LOG(log, "pid {0} failed to remove breakpoint: {1}", process->GetID(), + error); return SendErrorResponse(0x09); } else { // Try to clear the watchpoint. - const Status error = m_debugged_process_up->RemoveWatchpoint(addr); + const Status error = process->RemoveWatchpoint(addr); if (error.Success()) return SendOKResponse(); Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); - LLDB_LOG(log, "pid {0} failed to remove watchpoint: {1}", - m_debugged_process_up->GetID(), error); + LLDB_LOG(log, "pid {0} failed to remove watchpoint: {1}", process->GetID(), + error); return SendErrorResponse(0x09); } } @@ -3210,19 +3255,27 @@ return SendIllFormedResponse(packet, "D failed to parse the process id"); } - if (pid != LLDB_INVALID_PROCESS_ID && m_debugged_process_up->GetID() != pid) { - return SendIllFormedResponse(packet, "Invalid pid"); + NativeProcessProtocol* process; + if (pid == LLDB_INVALID_PROCESS_ID || m_debugged_process_up->GetID() == pid) + process = m_debugged_process_up.get(); + else { + process = m_debugged_process_up->GetSubprocess(pid); + if (!process) + return SendIllFormedResponse(packet, "PID unknown to the debugger"); } - const Status error = m_debugged_process_up->Detach(); + const Status error = process->Detach(); if (error.Fail()) { LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s failed to detach from " "pid %" PRIu64 ": %s\n", - __FUNCTION__, m_debugged_process_up->GetID(), error.AsCString()); + __FUNCTION__, process->GetID(), error.AsCString()); return SendErrorResponse(0x01); } + if (pid != LLDB_INVALID_PROCESS_ID && m_debugged_process_up->GetID() != pid) + m_debugged_process_up->EraseSubprocess(pid); + return SendOKResponse(); } @@ -3534,3 +3587,53 @@ return tid; } + +llvm::SmallVector +GDBRemoteCommunicationServerLLGS::HandleFeatures( + const llvm::ArrayRef client_features) { + auto ret = + GDBRemoteCommunicationServerCommon::HandleFeatures(client_features); + if (m_debugged_process_up) + SetEnabledExtensions(*m_debugged_process_up); + return ret; +} + +void GDBRemoteCommunicationServerLLGS::SetEnabledExtensions( + NativeProcessProtocol &process) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); + + NativeProcessProtocol::Extension flags; + if (m_fork_events_supported) + flags |= NativeProcessProtocol::Extension::fork; + if (m_vfork_events_supported) + flags |= NativeProcessProtocol::Extension::vfork; + + llvm::Error error = process.SetEnabledExtensions(flags); + if (error) + LLDB_LOG_ERROR(log, std::move(error), + "Enabling protocol extensions failed: {0}"); +} + +NativeProcessProtocol *GDBRemoteCommunicationServerLLGS::GetSelectedProcess( + bool allow_subprocess) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Ensure we have a process. + if (!m_debugged_process_up || + (m_debugged_process_up->GetID() == LLDB_INVALID_PROCESS_ID)) { + LLDB_LOG(log, "failed, no process available"); + return nullptr; + } + + // If no subprocess is selected, proceed with the main process. + if (m_subprocess_pid == LLDB_INVALID_PROCESS_ID) + return m_debugged_process_up.get(); + + if (!allow_subprocess) { + LLDB_LOG(log, "failed, subprocess selected while not allowed"); + return nullptr; + } + + // Otherwise, try to find a subprocess matching requested PID. + return m_debugged_process_up->GetSubprocess(m_subprocess_pid); +} Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -230,6 +230,8 @@ std::string HarmonizeThreadIdsForProfileData( StringExtractorGDBRemote &inputStringExtractor); + void DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) override; + protected: friend class ThreadGDBRemote; friend class GDBRemoteCommunicationClient; 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 @@ -1901,6 +1901,26 @@ } else if (reason == "processor trace") { thread_sp->SetStopInfo(StopInfo::CreateStopReasonProcessorTrace( *thread_sp, description.c_str())); + } else if (reason == "fork") { + StringExtractor desc_extractor(description.c_str()); + lldb::pid_t child_pid = desc_extractor.GetU64( + LLDB_INVALID_PROCESS_ID); + lldb::tid_t child_tid = desc_extractor.GetU64( + LLDB_INVALID_THREAD_ID); + thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithFork( + *thread_sp, child_pid, child_tid)); + handled = true; +#if 0 + } else if (reason == "vfork") { + StringExtractor desc_extractor(description.c_str()); + lldb::pid_t child_pid = desc_extractor.GetU64( + LLDB_INVALID_PROCESS_ID); + lldb::tid_t child_tid = desc_extractor.GetU64( + LLDB_INVALID_THREAD_ID); + thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithVFork( + *thread_sp, child_pid, child_tid)); + handled = true; +#endif } } else if (!signo) { addr_t pc = thread_sp->GetRegisterContext()->GetPC(); @@ -2307,6 +2327,21 @@ ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); LLDB_LOG_ERROR(log, std::move(error), "Failed to load modules: {0}"); } + } else if (key.compare("fork") == 0 || key.compare("vfork") == 0) { + // fork includes child pid/tid in thread-id format + StringExtractorGDBRemote thread_id{value}; + auto pid_tid = thread_id.GetPidTid(LLDB_INVALID_PROCESS_ID); + if (!pid_tid) { + Log *log( + ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); + LLDB_LOG(log, "Invalid PID/TID to fork: {0}", value); + pid_tid = {{LLDB_INVALID_PROCESS_ID, LLDB_INVALID_THREAD_ID}}; + } + + reason = key.str(); + StreamString ostr; + ostr.Printf("%" PRIu64 " %" PRIu64, pid_tid->first, pid_tid->second); + description = std::string(ostr.GetString()); } else if (key.size() == 2 && ::isxdigit(key[0]) && ::isxdigit(key[1])) { uint32_t reg = UINT32_MAX; if (!key.getAsInteger(16, reg)) @@ -5399,3 +5434,39 @@ GetTarget().GetDebugger().GetCommandInterpreter()); return m_command_sp.get(); } + +void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) { + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); + + if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware)) { + lldb::pid_t orig_pid = m_gdb_comm.GetCurrentProcessID(); + + if (!m_gdb_comm.SetCurrentThread(child_tid, child_pid)) { + LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid"); + return; + } + + // Disable all software breakpoints in the forked process + GetBreakpointSiteList().ForEach([this](BreakpointSite *bp_site) { + if (bp_site->IsEnabled() && (bp_site->GetType() == BreakpointSite::eSoftware || bp_site->GetType() == BreakpointSite::eExternal)) { + m_gdb_comm.SendGDBStoppointTypePacket(eBreakpointSoftware, false, bp_site->GetLoadAddress(), bp_site->GetTrapOpcodeMaxByteSize()); + } + }); + + // Reset gdb-remote to the original process. Thread ID does not really + // matter, the next command will set it anyway. + if (!m_gdb_comm.SetCurrentThread(m_thread_ids.front(), orig_pid)) { + LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid"); + return; + } + } + + LLDB_LOG(log, "Detaching forked child {0}", child_pid); + Status error = m_gdb_comm.Detach(false, child_pid); + if (error.Fail()) { + LLDB_LOG(log, + "ProcessGDBRemote::DidFork() detach packet send failed: {0}", + error.AsCString() ? error.AsCString() : ""); + return; + } +} Index: lldb/source/Target/Process.cpp =================================================================== --- lldb/source/Target/Process.cpp +++ lldb/source/Target/Process.cpp @@ -800,6 +800,8 @@ case eStopReasonWatchpoint: case eStopReasonException: case eStopReasonExec: + case eStopReasonFork: + case eStopReasonVFork: case eStopReasonThreadExiting: case eStopReasonInstrumentation: case eStopReasonProcessorTrace: Index: lldb/source/Target/StackFrameList.cpp =================================================================== --- lldb/source/Target/StackFrameList.cpp +++ lldb/source/Target/StackFrameList.cpp @@ -131,6 +131,8 @@ case eStopReasonWatchpoint: case eStopReasonException: case eStopReasonExec: + case eStopReasonFork: + case eStopReasonVFork: case eStopReasonSignal: // In all these cases we want to stop in the deepest frame. m_current_inlined_pc = curr_pc; Index: lldb/source/Target/StopInfo.cpp =================================================================== --- lldb/source/Target/StopInfo.cpp +++ lldb/source/Target/StopInfo.cpp @@ -1145,6 +1145,40 @@ bool m_performed_action; }; +// StopInfoFork + +class StopInfoFork : public StopInfo { +public: + StopInfoFork(Thread &thread, lldb::pid_t child_pid, lldb::tid_t child_tid) + : StopInfo(thread, LLDB_INVALID_UID), m_performed_action(false), + m_child_pid(child_pid), m_child_tid(child_tid) {} + + ~StopInfoFork() override = default; + + bool ShouldStop(Event *event_ptr) override { return false; } + + StopReason GetStopReason() const override { return eStopReasonFork; } + + const char *GetDescription() override { return "fork"; } + +protected: + void PerformAction(Event *event_ptr) override { + // Only perform the action once + if (m_performed_action) + return; + m_performed_action = true; + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + thread_sp->GetProcess()->DidFork(m_child_pid, m_child_tid); + } + + bool m_performed_action; + +private: + lldb::pid_t m_child_pid; + lldb::tid_t m_child_tid; +}; + } // namespace lldb_private StopInfoSP StopInfo::CreateStopReasonWithBreakpointSiteID(Thread &thread, @@ -1194,6 +1228,12 @@ return StopInfoSP(new StopInfoExec(thread)); } +StopInfoSP StopInfo::CreateStopReasonWithFork(Thread &thread, + lldb::pid_t child_pid, + lldb::tid_t child_tid) { + return StopInfoSP(new StopInfoFork(thread, child_pid, child_tid)); +} + ValueObjectSP StopInfo::GetReturnValueObject(StopInfoSP &stop_info_sp) { if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonPlanComplete) { Index: lldb/source/Target/Thread.cpp =================================================================== --- lldb/source/Target/Thread.cpp +++ lldb/source/Target/Thread.cpp @@ -1679,6 +1679,10 @@ return "exception"; case eStopReasonExec: return "exec"; + case eStopReasonFork: + return "fork"; + case eStopReasonVFork: + return "vfork"; case eStopReasonPlanComplete: return "plan complete"; case eStopReasonThreadExiting: Index: lldb/source/Utility/StringExtractorGDBRemote.cpp =================================================================== --- lldb/source/Utility/StringExtractorGDBRemote.cpp +++ lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -378,9 +378,7 @@ return eServerPacketType_C; case 'D': - if (packet_size == 1) - return eServerPacketType_D; - break; + return eServerPacketType_D; case 'g': return eServerPacketType_g; Index: lldb/tools/lldb-vscode/JSONUtils.cpp =================================================================== --- lldb/tools/lldb-vscode/JSONUtils.cpp +++ lldb/tools/lldb-vscode/JSONUtils.cpp @@ -877,6 +877,12 @@ case lldb::eStopReasonExec: body.try_emplace("reason", "entry"); break; + case lldb::eStopReasonFork: + body.try_emplace("reason", "fork"); + break; + case lldb::eStopReasonVFork: + body.try_emplace("reason", "vfork"); + break; case lldb::eStopReasonThreadExiting: case lldb::eStopReasonInvalid: case lldb::eStopReasonNone: Index: lldb/tools/lldb-vscode/LLDBUtils.cpp =================================================================== --- lldb/tools/lldb-vscode/LLDBUtils.cpp +++ lldb/tools/lldb-vscode/LLDBUtils.cpp @@ -55,6 +55,8 @@ case lldb::eStopReasonSignal: case lldb::eStopReasonException: case lldb::eStopReasonExec: + case lldb::eStopReasonFork: + case lldb::eStopReasonVFork: case lldb::eStopReasonProcessorTrace: return true; case lldb::eStopReasonThreadExiting: