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; @@ -397,6 +399,26 @@ 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) { + return llvm::Error::success(); + } + protected: struct SoftwareBreakpoint { uint32_t ref_count; Index: lldb/include/lldb/Host/linux/Host.h =================================================================== --- /dev/null +++ lldb/include/lldb/Host/linux/Host.h @@ -0,0 +1,23 @@ +//===-- Host.h --------------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_HOST_LINUX_HOST_H +#define LLDB_HOST_LINUX_HOST_H + +#include "llvm/ADT/Optional.h" + +#include "lldb/lldb-types.h" + +namespace lldb_private { + +// Get PID (i.e. the primary thread ID) corresponding to the specified TID. +llvm::Optional getPIDForTID(lldb::pid_t tid); + +} // namespace lldb_private + +#endif // #ifndef LLDB_HOST_LINUX_HOST_H 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/Host/linux/Host.cpp =================================================================== --- lldb/source/Host/linux/Host.cpp +++ lldb/source/Host/linux/Host.cpp @@ -27,6 +27,7 @@ #include "lldb/Host/FileSystem.h" #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" +#include "lldb/Host/linux/Host.h" #include "lldb/Host/linux/Support.h" #include "lldb/Utility/DataExtractor.h" @@ -53,7 +54,8 @@ } static bool GetStatusInfo(::pid_t Pid, ProcessInstanceInfo &ProcessInfo, - ProcessState &State, ::pid_t &TracerPid) { + ProcessState &State, ::pid_t &TracerPid, + ::pid_t &Tgid) { Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); auto BufferOrError = getProcFile(Pid, "status"); @@ -107,6 +109,9 @@ } else if (Line.consume_front("TracerPid:")) { Line = Line.ltrim(); Line.consumeInteger(10, TracerPid); + } else if (Line.consume_front("Tgid:")) { + Line = Line.ltrim(); + Line.consumeInteger(10, Tgid); } } return true; @@ -204,6 +209,7 @@ static bool GetProcessAndStatInfo(::pid_t pid, ProcessInstanceInfo &process_info, ProcessState &State, ::pid_t &tracerpid) { + ::pid_t tgid; tracerpid = 0; process_info.Clear(); @@ -214,7 +220,7 @@ GetProcessEnviron(pid, process_info); // Get User and Group IDs and get tracer pid. - if (!GetStatusInfo(pid, process_info, State, tracerpid)) + if (!GetStatusInfo(pid, process_info, State, tracerpid, tgid)) return false; return true; @@ -308,3 +314,13 @@ Status Host::ShellExpandArguments(ProcessLaunchInfo &launch_info) { return Status("unimplemented"); } + +llvm::Optional lldb_private::getPIDForTID(lldb::pid_t tid) { + ::pid_t tracerpid, tgid = LLDB_INVALID_PROCESS_ID; + ProcessInstanceInfo process_info; + ProcessState state; + + if (!GetStatusInfo(tid, process_info, state, tracerpid, tgid) || tgid == 0) + return llvm::None; + return tgid; +} Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -128,6 +128,8 @@ bool SupportHardwareSingleStepping() const; + llvm::Error SetEnabledExtensions(Extension flags) override; + protected: llvm::Expected> GetSoftwareBreakpointTrapOpcode(size_t size_hint) override; @@ -146,6 +148,8 @@ /// Inferior memory (allocated by us) and its size. llvm::DenseMap m_allocated_memory; + Extension m_enabled_extensions; + // Private Instance Methods NativeProcessLinux(::pid_t pid, int terminal_fd, NativeDelegate &delegate, const ArchSpec &arch, MainLoop &mainloop, @@ -154,11 +158,11 @@ // Returns a list of process threads that we have attached to. static llvm::Expected> Attach(::pid_t pid); - static Status SetDefaultPtraceOpts(const lldb::pid_t); + static Status SetPtraceOpts(const lldb::pid_t, Extension extensions = {}); void MonitorCallback(lldb::pid_t pid, bool exited, WaitStatus status); - void WaitForNewThread(::pid_t tid); + void WaitForCloneNotification(::pid_t pid); void MonitorSIGTRAP(const siginfo_t &info, NativeThreadLinux &thread); @@ -248,6 +252,21 @@ lldb::user_id_t m_pt_proces_trace_id = LLDB_INVALID_UID; TraceOptions m_pt_process_trace_config; + + struct CloneInfo { + uint32_t event; + lldb::tid_t parent_tid; + }; + + // Map of child processes that have been signaled once, and we are + // waiting for the second signal. + llvm::DenseMap> m_pending_pid_map; + + // Handle a clone()-like event. If received by parent, clone_info contains + // additional info. Returns true if the event is handled, or false if it + // is pending second notification. + bool MonitorClone(lldb::pid_t child_pid, + llvm::Optional clone_info); }; } // 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 @@ -30,6 +30,9 @@ #include "lldb/Host/PseudoTerminal.h" #include "lldb/Host/ThreadLauncher.h" #include "lldb/Host/common/NativeRegisterContext.h" +#if 0 +#include "lldb/Host/linux/Host.h" +#endif #include "lldb/Host/linux/Ptrace.h" #include "lldb/Host/linux/Uio.h" #include "lldb/Host/posix/ProcessLauncherPosixFork.h" @@ -247,7 +250,7 @@ LLDB_LOG(log, "pid = {0:x}, detected architecture {1}", pid, Info.GetArchitecture().GetArchitectureName()); - status = SetDefaultPtraceOpts(pid); + status = SetPtraceOpts(pid); if (status.Fail()) { LLDB_LOG(log, "failed to set default ptrace options: {0}", status); return status.ToError(); @@ -286,7 +289,8 @@ NativeDelegate &delegate, const ArchSpec &arch, MainLoop &mainloop, llvm::ArrayRef<::pid_t> tids) - : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch) { + : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch), + m_enabled_extensions() { if (m_terminal_fd != -1) { Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK); assert(status.Success()); @@ -351,7 +355,7 @@ std::error_code(errno, std::generic_category())); } - if ((status = SetDefaultPtraceOpts(tid)).Fail()) + if ((status = SetPtraceOpts(tid)).Fail()) return status.ToError(); LLDB_LOG(log, "adding tid = {0}", tid); @@ -375,7 +379,8 @@ return std::move(tids); } -Status NativeProcessLinux::SetDefaultPtraceOpts(lldb::pid_t pid) { +Status NativeProcessLinux::SetPtraceOpts(lldb::pid_t pid, + Extension extensions) { long ptrace_opts = 0; // Have the child raise an event on exit. This is used to keep the child in @@ -383,14 +388,17 @@ ptrace_opts |= PTRACE_O_TRACEEXIT; // Have the tracer trace threads which spawn in the inferior process. - // TODO: if we want to support tracing the inferiors' child, add the - // appropriate ptrace flags here (PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK) ptrace_opts |= PTRACE_O_TRACECLONE; // Have the tracer notify us before execve returns (needed to disable legacy // SIGTRAP generation) ptrace_opts |= PTRACE_O_TRACEEXEC; + // Have the tracer trace forked children if the fork-events extension + // is supported. + if ((extensions & Extension::fork) == Extension::fork) + ptrace_opts |= PTRACE_O_TRACEFORK; + return PtraceWrapper(PTRACE_SETOPTIONS, pid, nullptr, (void *)ptrace_opts); } @@ -444,11 +452,7 @@ LLDB_LOG(log, "tid {0}, si_code: {1}, si_pid: {2}", pid, info.si_code, info.si_pid); - NativeThreadLinux &thread = AddThread(pid); - - // Resume the newly created thread. - ResumeThread(thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); - ThreadWasCreated(thread); + MonitorClone(pid, llvm::None); return; } @@ -512,29 +516,24 @@ } } -void NativeProcessLinux::WaitForNewThread(::pid_t tid) { +void NativeProcessLinux::WaitForCloneNotification(::pid_t pid) { Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS)); - if (GetThreadByID(tid)) { - // We are already tracking the thread - we got the event on the new thread - // (see MonitorSignal) before this one. We are done. - return; - } - - // The thread is not tracked yet, let's wait for it to appear. + // The PID is not tracked yet, let's wait for it to appear. int status = -1; LLDB_LOG(log, - "received thread creation event for tid {0}. tid not tracked " - "yet, waiting for thread to appear...", - tid); - ::pid_t wait_pid = llvm::sys::RetryAfterSignal(-1, ::waitpid, tid, &status, __WALL); - // Since we are waiting on a specific tid, this must be the creation event. + "received clone event for pid {0}. pid not tracked yet, " + "waiting for it to appear...", + pid); + ::pid_t wait_pid = + llvm::sys::RetryAfterSignal(-1, ::waitpid, pid, &status, __WALL); + // Since we are waiting on a specific pid, this must be the creation event. // But let's do some checks just in case. - if (wait_pid != tid) { + if (wait_pid != pid) { LLDB_LOG(log, - "waiting for tid {0} failed. Assuming the thread has " + "waiting for pid {0} failed. Assuming the pid has " "disappeared in the meantime", - tid); + pid); // The only way I know of this could happen is if the whole process was // SIGKILLed in the mean time. In any case, we can't do anything about that // now. @@ -542,18 +541,15 @@ } if (WIFEXITED(status)) { LLDB_LOG(log, - "waiting for tid {0} returned an 'exited' event. Not " - "tracking the thread.", - tid); + "waiting for pid {0} returned an 'exited' event. Not " + "tracking it.", + pid); // Also a very improbable event. + m_pending_pid_map.erase(pid); return; } - LLDB_LOG(log, "pid = {0}: tracking new thread tid {1}", GetID(), tid); - NativeThreadLinux &new_thread = AddThread(tid); - - ResumeThread(new_thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); - ThreadWasCreated(new_thread); + MonitorClone(pid, llvm::None); } void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info, @@ -580,10 +576,12 @@ "pid {0} received thread creation event but " "GetEventMessage failed so we don't know the new tid", thread.GetID()); - } else - WaitForNewThread(event_message); + ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER); + } else { + if (!MonitorClone(event_message, {{PTRACE_EVENT_CLONE, thread.GetID()}})) + WaitForCloneNotification(event_message); + } - ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER); break; } @@ -649,6 +647,15 @@ break; } + case (SIGTRAP | (PTRACE_EVENT_FORK << 8)): { + unsigned long data = 0; + if (GetEventMessage(thread.GetID(), &data).Fail()) + data = 0; + + if (!MonitorClone(data, {{PTRACE_EVENT_FORK, thread.GetID()}})) + WaitForCloneNotification(data); + break; + } case 0: case TRAP_TRACE: // We receive this on single stepping. case TRAP_HWBKPT: // We receive this on watchpoint hit @@ -858,6 +865,71 @@ StopRunningThreads(thread.GetID()); } +bool NativeProcessLinux::MonitorClone( + lldb::pid_t child_pid, + llvm::Optional clone_info) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS)); + LLDB_LOG(log, "clone, child_pid={0}, clone info?={1}", child_pid, + clone_info.hasValue()); + + auto find_it = m_pending_pid_map.find(child_pid); + if (find_it == m_pending_pid_map.end()) { + // not in the map, so this is the first signal for the PID + m_pending_pid_map.insert({child_pid, clone_info}); + return false; + } + + // second signal for the pid + assert(clone_info.hasValue() != find_it->second.hasValue()); + if (!clone_info) { + // child signal does not indicate the event, so grab the one stored + // earlier + clone_info = find_it->second; + } + + LLDB_LOG(log, "second signal for child_pid={0}, parent_tid={1}, event={2}", + child_pid, clone_info->parent_tid, clone_info->event); + + auto parent_thread = GetThreadByID(clone_info->parent_tid); + assert(parent_thread); + +#if 0 + llvm::Optional tgid = getPIDForTID(pid); + if (tgid && tgid.getValue() != GetID()) { + LLDB_LOG(log, "tid {0} belongs to a different tgid {1}, assuming child", + pid, tgid.getValue()); + MonitorClone(pid, llvm::None); + return; + } +#endif + + switch (clone_info->event) { + case PTRACE_EVENT_CLONE: { + NativeThreadLinux &child_thread = AddThread(child_pid); + // Resume the newly created thread. + ResumeThread(child_thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); + ThreadWasCreated(child_thread); + + // Resume the parent. + ResumeThread(*parent_thread, parent_thread->GetState(), + LLDB_INVALID_SIGNAL_NUMBER); + break; + } + case PTRACE_EVENT_FORK: { + // for now, just detach the child and resume the parent + Detach(child_pid); + ResumeThread(*parent_thread, parent_thread->GetState(), + LLDB_INVALID_SIGNAL_NUMBER); + break; + } + default: + assert(false && "unknown clone_info.event"); + } + + m_pending_pid_map.erase(child_pid); + return true; +} + bool NativeProcessLinux::SupportHardwareSingleStepping() const { if (m_arch.GetMachine() == llvm::Triple::arm || m_arch.IsMIPS()) return false; @@ -2021,3 +2093,16 @@ return error; } + +llvm::Error NativeProcessLinux::SetEnabledExtensions(Extension flags) { + m_enabled_extensions = flags; + for (const auto &thread : m_threads) { + assert(thread && "thread list should not contain NULL threads"); + + Status status = SetPtraceOpts(thread->GetID(), flags); + if (status.Fail()) + return status.ToError(); + } + + return llvm::Error::success(); +} 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 @@ -559,6 +559,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, 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), @@ -294,6 +296,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 +349,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 +450,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 + 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 @@ -201,6 +201,8 @@ PacketResult Handle_g(StringExtractorGDBRemote &packet); + PacketResult Handle_qSupported_LLGS(StringExtractorGDBRemote &packet); + void SetCurrentThreadID(lldb::tid_t tid); lldb::tid_t GetCurrentThreadID() const; @@ -219,6 +221,9 @@ static std::string XMLEncodeAttributeValue(llvm::StringRef value); + llvm::SmallVector + HandleFeatures(const llvm::ArrayRef client_features) override; + private: llvm::Expected> BuildTargetXml(); @@ -255,6 +260,9 @@ bool allow_any = false, 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 @@ -254,6 +254,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 @@ -321,6 +323,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(); @@ -3703,3 +3706,29 @@ 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}"); +} Index: lldb/test/Shell/Subprocess/Inputs/fork.c =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/Inputs/fork.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +void common_func() { + printf("common function\n"); +} + +void parent_func() { + printf("function run in parent\n"); +} + +void child_func() { + printf("function run in child\n"); +} + +int main() { + pid_t pid = fork(); + assert(pid != -1); + + common_func(); + if (pid == 0) { + child_func(); + _exit(0); + } + + parent_func(); + int status; + pid_t waited = waitpid(pid, &status, 0); + assert(waited == pid); + assert(WIFEXITED(status)); + printf("child exited: %d\n", WEXITSTATUS(status)); + + return 0; +} Index: lldb/test/Shell/Subprocess/fork-follow-parent.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/fork-follow-parent.test @@ -0,0 +1,12 @@ +# Verify that the debugger continues tracing the parent after fork(). +# REQUIRES: native +# RUN: %clang_host %p/Inputs/fork.c -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +b parent_func +process launch +# CHECK: function run in child +# CHECK-NOT: function run in parent +# CHECK: stop reason = breakpoint +continue +# CHECK: function run in parent +# CHECK: child exited: 0