Index: lldb/include/lldb/Host/common/NativeProcessProtocol.h =================================================================== --- lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -472,6 +472,16 @@ NativeThreadProtocol *GetThreadByIDUnlocked(lldb::tid_t tid); + // Software breakpoints saved for the duration of vfork(). + llvm::SmallVector, 8> m_saved_breakpoints; + + // Disable all current software breakpoints and store them for reenabling + // later. + llvm::Error DisableSoftwareBreakpoints(); + + // Reenable all software breakpoints (undo DisableSoftwareBreakpoints()). + llvm::Error EnableSoftwareBreakpoints(); + private: void SynchronouslyNotifyProcessStateChanged(lldb::StateType state); llvm::Expected 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/source/Host/common/NativeProcessProtocol.cpp =================================================================== --- lldb/source/Host/common/NativeProcessProtocol.cpp +++ lldb/source/Host/common/NativeProcessProtocol.cpp @@ -760,4 +760,26 @@ // Default implementation does nothing. } +llvm::Error NativeProcessProtocol::DisableSoftwareBreakpoints() { + while (!m_software_breakpoints.empty()) { + auto &bp = *m_software_breakpoints.begin(); + m_saved_breakpoints.emplace_back(bp.first, bp.second.saved_opcodes.size()); + Status ret = RemoveSoftwareBreakpoint(bp.first); + if (ret.Fail()) + return ret.ToError(); + } + + return llvm::Error::success(); +} + +llvm::Error NativeProcessProtocol::EnableSoftwareBreakpoints() { + for (auto it = m_saved_breakpoints.begin(); it != m_saved_breakpoints.end(); + it = m_saved_breakpoints.erase(it)) { + Status ret = SetSoftwareBreakpoint(it->first, it->second); + if (ret.Fail()) + return ret.ToError(); + } + return llvm::Error::success(); +} + NativeProcessProtocol::Factory::~Factory() = default; 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/FreeBSD/NativeProcessFreeBSD.h =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h +++ lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h @@ -120,6 +120,21 @@ Status Attach(); Status SetupTrace(); Status ReinitializeThreads(); + + struct CloneInfo { + int32_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_freebsd Index: lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp +++ lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp @@ -163,6 +163,9 @@ LLDB_LOG(log, "got exit signal({0}) , pid = {1}", status, pid); + if (pid != GetID()) + return; + /* Stop Tracking All Threads attached to Process */ m_threads.clear(); @@ -173,12 +176,15 @@ } void NativeProcessFreeBSD::MonitorSIGSTOP(lldb::pid_t pid) { - /* Stop all Threads attached to Process */ - for (const auto &thread : m_threads) { - static_cast(*thread).SetStoppedBySignal(SIGSTOP, - nullptr); - } - SetState(StateType::eStateStopped, true); + if (pid == GetID()) { + /* Stop all Threads attached to Process */ + for (const auto &thread : m_threads) { + static_cast(*thread).SetStoppedBySignal(SIGSTOP, + nullptr); + } + SetState(StateType::eStateStopped, true); + } else + MonitorClone(pid, llvm::None); } void NativeProcessFreeBSD::MonitorSIGTRAP(lldb::pid_t pid) { @@ -196,6 +202,9 @@ info.pl_lwpid, info.pl_flags); NativeThreadFreeBSD *thread = nullptr; + if (pid != GetID()) + return; + if (info.pl_flags & (PL_FLAG_BORN | PL_FLAG_EXITED)) { if (info.pl_flags & PL_FLAG_BORN) { LLDB_LOG(log, "monitoring new thread, tid = {0}", info.pl_lwpid); @@ -247,6 +256,28 @@ return; } + if (info.pl_flags & (PL_FLAG_FORKED | PL_FLAG_VFORKED)) { + MonitorClone(info.pl_child_pid, + {{info.pl_flags & (PL_FLAG_FORKED | PL_FLAG_VFORKED), pid}}); + return; + } + + if (info.pl_flags & PL_FLAG_VFORK_DONE) { + // restore breakpoints + llvm::Error bp_err = EnableSoftwareBreakpoints(); + if (bp_err) { + LLDB_LOG_ERROR(log, std::move(bp_err), + "failed to restore breakpoints after vfork: {0}"); + SetState(StateType::eStateInvalid); + return; + } + Status error = + PtraceWrapper(PT_CONTINUE, pid, reinterpret_cast(1), 0); + if (error.Fail()) + SetState(StateType::eStateInvalid); + return; + } + if (info.pl_lwpid > 0) { for (const auto &t : m_threads) { if (t->GetID() == static_cast(info.pl_lwpid)) @@ -324,6 +355,9 @@ Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS)); struct ptrace_lwpinfo info; + if (pid != GetID()) + return; + const auto siginfo_err = PtraceWrapper(PT_LWPINFO, pid, &info, sizeof(info)); if (siginfo_err.Fail()) { LLDB_LOG(log, "PT_LWPINFO failed {0}", siginfo_err); @@ -706,32 +740,36 @@ void NativeProcessFreeBSD::SigchldHandler() { Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS)); // Process all pending waitpid notifications. - int status; - ::pid_t wait_pid = - llvm::sys::RetryAfterSignal(-1, waitpid, GetID(), &status, WNOHANG); + while (true) { + int status; + ::pid_t wait_pid = + llvm::sys::RetryAfterSignal(-1, waitpid, -1, &status, WNOHANG); - if (wait_pid == 0) - return; // We are done. + if (wait_pid == 0) + return; // We are done. - if (wait_pid == -1) { - Status error(errno, eErrorTypePOSIX); - LLDB_LOG(log, "waitpid ({0}, &status, _) failed: {1}", GetID(), error); - } - - WaitStatus wait_status = WaitStatus::Decode(status); - bool exited = wait_status.type == WaitStatus::Exit || - (wait_status.type == WaitStatus::Signal && - wait_pid == static_cast<::pid_t>(GetID())); + if (wait_pid == -1) { + Status error(errno, eErrorTypePOSIX); + LLDB_LOG(log, "waitpid (-1, &status, _) failed: {0}", error); + break; + } - LLDB_LOG(log, - "waitpid ({0}, &status, _) => pid = {1}, status = {2}, exited = {3}", - GetID(), wait_pid, status, exited); + WaitStatus wait_status = WaitStatus::Decode(status); + bool exited = wait_status.type == WaitStatus::Exit || + (wait_status.type == WaitStatus::Signal && + wait_pid == static_cast<::pid_t>(GetID())); - if (exited) - MonitorExited(wait_pid, wait_status); - else { - assert(wait_status.type == WaitStatus::Stop); - MonitorCallback(wait_pid, wait_status.status); + LLDB_LOG( + log, + "waitpid (-1, &status, _) => pid = {0}, status = {1}, exited = {2}", + wait_pid, status, exited); + + if (exited) + MonitorExited(wait_pid, wait_status); + else { + assert(wait_status.type == WaitStatus::Stop); + MonitorCallback(wait_pid, wait_status.status); + } } } @@ -885,7 +923,7 @@ PtraceWrapper(PT_GET_EVENT_MASK, GetID(), &events, sizeof(events)); if (status.Fail()) return status; - events |= PTRACE_LWP; + events |= PTRACE_LWP | PTRACE_FORK | PTRACE_VFORK; status = PtraceWrapper(PT_SET_EVENT_MASK, GetID(), &events, sizeof(events)); if (status.Fail()) return status; @@ -919,3 +957,91 @@ bool NativeProcessFreeBSD::SupportHardwareSingleStepping() const { return !m_arch.IsMIPS(); } + +bool NativeProcessFreeBSD::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); + + MainLoop unused_loop; + NativeProcessFreeBSD child_process{static_cast<::pid_t>(child_pid), + m_terminal_fd, *m_delegates[0], m_arch, + unused_loop}; + child_process.ReinitializeThreads(); + auto *child_thread = + static_cast(child_process.GetCurrentThread()); + assert(child_thread); + // new processes inherit dbregs, so we need to clear them + llvm::Error error = child_thread->GetRegisterContext().ClearDBRegs(); + if (error) { + LLDB_LOG_ERROR(log, std::move(error), + "failed to clear dbregs in forked process {1}: {0}", + child_pid); + SetState(StateType::eStateInvalid); + return true; + } + + switch (clone_info->event) { + case PL_FLAG_FORKED: { + child_process.m_software_breakpoints = m_software_breakpoints; + error = child_process.DisableSoftwareBreakpoints(); + if (error) { + LLDB_LOG_ERROR( + log, std::move(error), + "failed to remove breakpoints from forked process {1}: {0}", + child_pid); + SetState(StateType::eStateInvalid); + return true; + } + break; + } + case PL_FLAG_FORKED | PL_FLAG_VFORKED: { + // vfork shares memory with the parent, so we need to temporarily remove + // breakpoints + error = DisableSoftwareBreakpoints(); + if (error) { + LLDB_LOG_ERROR( + log, std::move(error), + "failed to remove breakpoints from vforked process {1}: {0}", + child_pid); + SetState(StateType::eStateInvalid); + return true; + } + break; + } + default: + assert(false && "unknown clone_info.event"); + } + + child_process.Detach(); + Status pt_error = + PtraceWrapper(PT_CONTINUE, GetID(), reinterpret_cast(1), 0); + if (pt_error.Fail()) { + LLDB_LOG_ERROR(log, std::move(pt_error.ToError()), + "unable to resume parent process {1}: {0}", GetID()); + SetState(StateType::eStateInvalid); + } + + m_pending_pid_map.erase(child_pid); + return true; +} Index: lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h +++ lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h @@ -32,6 +32,10 @@ virtual llvm::Error CopyHardwareWatchpointsFrom(NativeRegisterContextFreeBSD &source) = 0; + virtual llvm::Error ClearDBRegs() { + return llvm::Error::success(); + } + protected: virtual NativeProcessFreeBSD &GetProcess(); virtual ::pid_t GetProcessPid(); Index: lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h +++ lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h @@ -58,6 +58,8 @@ llvm::Error CopyHardwareWatchpointsFrom(NativeRegisterContextFreeBSD &source) override; + llvm::Error ClearDBRegs() override; + private: // Due to alignment, FreeBSD reg/fpreg are a few bytes larger than // LLDB's GPR/FPU structs. However, all fields have matching offsets Index: lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp +++ lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp @@ -285,4 +285,20 @@ #endif } +llvm::Error NativeRegisterContextFreeBSD_arm64::ClearDBRegs() { +#ifdef LLDB_HAS_FREEBSD_WATCHPOINT + llvm::Error error = ReadHardwareDebugInfo(); + if (error) + return error; + + for (uint32_t i = 0; i < m_max_hbp_supported; i++) + m_hbp_regs[i].control = 0; + for (uint32_t i = 0; i < m_max_hwp_supported; i++) + m_hwp_regs[i].control = 0; + return WriteHardwareDebugRegs(eDREGTypeWATCH); +#else + return llvm::error::success(); +#endif +} + #endif // defined (__aarch64__) Index: lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.h =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.h +++ lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.h @@ -55,6 +55,8 @@ llvm::Error CopyHardwareWatchpointsFrom(NativeRegisterContextFreeBSD &source) override; + llvm::Error ClearDBRegs() override; + private: // Private member types. enum RegSetKind { Index: lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.cpp =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.cpp +++ lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_x86_64.cpp @@ -653,4 +653,10 @@ return YMMSplitPtr{&fpreg->sv_xmm[reg_index], &ymmreg[reg_index]}; } +llvm::Error NativeRegisterContextFreeBSD_x86_64::ClearDBRegs() { + uint64_t zero = 0; + RegisterValue dr7{zero}; + return WriteRegister(GetDR(7), dr7).ToError(); +} + #endif // defined(__x86_64__) Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -158,7 +158,7 @@ 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 +248,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,7 @@ #include "lldb/Host/PseudoTerminal.h" #include "lldb/Host/ThreadLauncher.h" #include "lldb/Host/common/NativeRegisterContext.h" +#include "lldb/Host/linux/Host.h" #include "lldb/Host/linux/Ptrace.h" #include "lldb/Host/linux/Uio.h" #include "lldb/Host/posix/ProcessLauncherPosixFork.h" @@ -383,14 +384,22 @@ 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. + ptrace_opts |= PTRACE_O_TRACEFORK; + + // Have the tracer trace vforks. + ptrace_opts |= PTRACE_O_TRACEVFORK; + + // Have the tracer trace vfork-done in order to restore breakpoints after + // the child finishes sharing memory. + ptrace_opts |= PTRACE_O_TRACEVFORKDONE; + return PtraceWrapper(PTRACE_SETOPTIONS, pid, nullptr, (void *)ptrace_opts); } @@ -444,11 +453,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 +517,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 +542,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, @@ -564,26 +561,24 @@ assert(info.si_signo == SIGTRAP && "Unexpected child signal!"); switch (info.si_code) { - // TODO: these two cases are required if we want to support tracing of the - // inferiors' children. We'd need this to debug a monitor. case (SIGTRAP | - // (PTRACE_EVENT_FORK << 8)): case (SIGTRAP | (PTRACE_EVENT_VFORK << 8)): - case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)): { - // This is the notification on the parent thread which informs us of new - // thread creation. We don't want to do anything with the parent thread so - // we just resume it. In case we want to implement "break on thread - // creation" functionality, we would need to stop here. + // This can either mean a new thread or a new process spawned via + // clone(2) without SIGCHLD or CLONE_VFORK flag. Note that clone(2) + // can also cause PTRACE_EVENT_FORK and PTRACE_EVENT_VFORK if one + // of these flags are passed. unsigned long event_message = 0; if (GetEventMessage(thread.GetID(), &event_message).Fail()) { LLDB_LOG(log, - "pid {0} received thread creation event but " - "GetEventMessage failed so we don't know the new tid", + "pid {0} received clone() event but GetEventMessage failed " + "so we don't know the new pid/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 +644,39 @@ 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 (SIGTRAP | (PTRACE_EVENT_VFORK << 8)): { + unsigned long data = 0; + if (GetEventMessage(thread.GetID(), &data).Fail()) + data = 0; + + if (!MonitorClone(data, {{PTRACE_EVENT_VFORK, thread.GetID()}})) + WaitForCloneNotification(data); + break; + } + + case (SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8)): { + // restore breakpoints + llvm::Error bp_err = EnableSoftwareBreakpoints(); + if (bp_err) { + LLDB_LOG_ERROR(log, std::move(bp_err), + "failed to restore breakpoints after vfork: {0}"); + SetState(StateType::eStateInvalid); + return; + } + ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER); + break; + } + case 0: case TRAP_TRACE: // We receive this on single stepping. case TRAP_HWBKPT: // We receive this on watchpoint hit @@ -858,6 +886,102 @@ 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); + + switch (clone_info->event) { + case PTRACE_EVENT_CLONE: { + // PTRACE_EVENT_CLONE can either mean a new thread or a new process. + // Try to grab the new process' PGID to figure out which one it is. + // If PGID is the same as the PID, then it's a new process. Otherwise, + // it's a thread. + auto pgid_ret = getPIDForTID(child_pid); + if (!pgid_ret || pgid_ret.getValue() != child_pid) { + // A new thread should have PGID matching our process' PID. + assert(!pgid_ret || pgid_ret.getValue() == GetID()); + + 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; + } + } + // fallthrough + case PTRACE_EVENT_FORK: { + MainLoop unused_loop; + NativeProcessLinux child_process{child_pid, m_terminal_fd, *m_delegates[0], + m_arch, unused_loop, {child_pid}}; + child_process.m_software_breakpoints = m_software_breakpoints; + llvm::Error bp_err = child_process.DisableSoftwareBreakpoints(); + if (bp_err) { + LLDB_LOG_ERROR(log, std::move(bp_err), + "failed to remove breakpoints from forked process {1}: {0}", + child_pid); + SetState(StateType::eStateInvalid); + return true; + } + child_process.Detach(); + ResumeThread(*parent_thread, parent_thread->GetState(), + LLDB_INVALID_SIGNAL_NUMBER); + break; + } + case PTRACE_EVENT_VFORK: { + MainLoop unused_loop; + NativeProcessLinux child_process{child_pid, m_terminal_fd, *m_delegates[0], + m_arch, unused_loop, {child_pid}}; + // vfork shares memory with the parent, so we need to temporarily remove + // breakpoints + llvm::Error bp_err = DisableSoftwareBreakpoints(); + if (bp_err) { + LLDB_LOG_ERROR(log, std::move(bp_err), + "failed to remove breakpoints from vforked process {1}: {0}", + child_pid); + SetState(StateType::eStateInvalid); + return true; + } + child_process.Detach(); + 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; Index: lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h =================================================================== --- lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h +++ lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h @@ -113,6 +113,20 @@ Status Attach(); Status SetupTrace(); Status ReinitializeThreads(); + + struct CloneInfo { + int32_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, CloneInfo clone_info); }; } // namespace process_netbsd Index: lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp =================================================================== --- lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp +++ lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp @@ -168,6 +168,9 @@ LLDB_LOG(log, "got exit signal({0}) , pid = {1}", status, pid); + if (pid != GetID()) + return; + /* Stop Tracking All Threads attached to Process */ m_threads.clear(); @@ -180,6 +183,9 @@ void NativeProcessNetBSD::MonitorSIGSTOP(lldb::pid_t pid) { ptrace_siginfo_t info; + if (pid != GetID()) + return; + const auto siginfo_err = PtraceWrapper(PT_GET_SIGINFO, pid, &info, sizeof(info)); @@ -215,6 +221,19 @@ info.psi_lwpid, info.psi_siginfo.si_code); NativeThreadNetBSD *thread = nullptr; + if (pid != GetID()) { + // a signal from a forked child + ptrace_state_t pst; + Status error = PtraceWrapper(PT_GET_PROCESS_STATE, pid, &pst, sizeof(pst)); + if (error.Fail()) { + SetState(StateType::eStateInvalid); + return; + } + + MonitorClone(pid, {pst.pe_report_event, pst.pe_other_pid}); + return; + } + if (info.psi_lwpid > 0) { for (const auto &t : m_threads) { if (t->GetID() == static_cast(info.psi_lwpid)) { @@ -256,6 +275,32 @@ SetState(StateType::eStateStopped, true); return; } + case TRAP_CHLD: { + ptrace_state_t pst; + Status error = PtraceWrapper(PT_GET_PROCESS_STATE, pid, &pst, sizeof(pst)); + if (error.Fail()) { + SetState(StateType::eStateInvalid); + return; + } + + if (pst.pe_report_event == PTRACE_VFORK_DONE) { + // restore breakpoints + llvm::Error bp_err = EnableSoftwareBreakpoints(); + if (bp_err) { + LLDB_LOG_ERROR(log, std::move(bp_err), + "failed to restore breakpoints after vfork: {0}"); + SetState(StateType::eStateInvalid); + return; + } + Status error = + PtraceWrapper(PT_CONTINUE, pid, reinterpret_cast(1), 0); + if (error.Fail()) + SetState(StateType::eStateInvalid); + return; + } else + MonitorClone(pst.pe_other_pid, {pst.pe_report_event, pid}); + return; + } case TRAP_LWP: { ptrace_state_t pst; Status error = PtraceWrapper(PT_GET_PROCESS_STATE, pid, &pst, sizeof(pst)); @@ -327,6 +372,9 @@ Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS)); ptrace_siginfo_t info; + if (pid != GetID()) + return; + const auto siginfo_err = PtraceWrapper(PT_GET_SIGINFO, pid, &info, sizeof(info)); if (siginfo_err.Fail()) { @@ -510,7 +558,7 @@ if (GetID() == LLDB_INVALID_PROCESS_ID) return error; - return PtraceWrapper(PT_DETACH, GetID()); + return PtraceWrapper(PT_DETACH, GetID(), reinterpret_cast(1)); } Status NativeProcessNetBSD::Signal(int signo) { @@ -739,32 +787,36 @@ void NativeProcessNetBSD::SigchldHandler() { Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS)); // Process all pending waitpid notifications. - int status; - ::pid_t wait_pid = llvm::sys::RetryAfterSignal(-1, waitpid, GetID(), &status, - WALLSIG | WNOHANG); - - if (wait_pid == 0) - return; // We are done. + while (true) { + int status; + ::pid_t wait_pid = llvm::sys::RetryAfterSignal(-1, waitpid, -1, &status, + WALLSIG | WNOHANG); - if (wait_pid == -1) { - Status error(errno, eErrorTypePOSIX); - LLDB_LOG(log, "waitpid ({0}, &status, _) failed: {1}", GetID(), error); - } + if (wait_pid == 0) + return; // We are done. - WaitStatus wait_status = WaitStatus::Decode(status); - bool exited = wait_status.type == WaitStatus::Exit || - (wait_status.type == WaitStatus::Signal && - wait_pid == static_cast<::pid_t>(GetID())); + if (wait_pid == -1) { + Status error(errno, eErrorTypePOSIX); + LLDB_LOG(log, "waitpid (-1, &status, _) failed: {0}", error); + return; + } - LLDB_LOG(log, - "waitpid ({0}, &status, _) => pid = {1}, status = {2}, exited = {3}", - GetID(), wait_pid, status, exited); + WaitStatus wait_status = WaitStatus::Decode(status); + bool exited = wait_status.type == WaitStatus::Exit || + (wait_status.type == WaitStatus::Signal && + wait_pid == static_cast<::pid_t>(GetID())); - if (exited) - MonitorExited(wait_pid, wait_status); - else { - assert(wait_status.type == WaitStatus::Stop); - MonitorCallback(wait_pid, wait_status.status); + LLDB_LOG( + log, + "waitpid (-1, &status, _) => pid = {0}, status = {1}, exited = {2}", + wait_pid, status, exited); + + if (exited) + MonitorExited(wait_pid, wait_status); + else { + assert(wait_status.type == WaitStatus::Stop); + MonitorCallback(wait_pid, wait_status.status); + } } } @@ -936,8 +988,9 @@ PtraceWrapper(PT_GET_EVENT_MASK, GetID(), &events, sizeof(events)); if (status.Fail()) return status; - // TODO: PTRACE_FORK | PTRACE_VFORK | PTRACE_POSIX_SPAWN? - events.pe_set_event |= PTRACE_LWP_CREATE | PTRACE_LWP_EXIT; + // TODO: PTRACE_POSIX_SPAWN? + events.pe_set_event |= PTRACE_LWP_CREATE | PTRACE_LWP_EXIT | PTRACE_FORK | + PTRACE_VFORK | PTRACE_VFORK_DONE; status = PtraceWrapper(PT_SET_EVENT_MASK, GetID(), &events, sizeof(events)); if (status.Fail()) return status; @@ -974,3 +1027,74 @@ return error; } + +bool NativeProcessNetBSD::MonitorClone( + lldb::pid_t child_pid, NativeProcessNetBSD::CloneInfo clone_info) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PROCESS)); + LLDB_LOG(log, "clone, child_pid={0}", child_pid); + + 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 + // on NetBSD, both signals include full info, so ensure that we have + // a match + assert(clone_info.event == find_it->second.event); + assert(clone_info.parent_tid == find_it->second.parent_tid); + + LLDB_LOG(log, "second signal for child_pid={0}, parent_tid={1}, event={2}", + child_pid, clone_info.parent_tid, clone_info.event); + + MainLoop unused_loop; + NativeProcessNetBSD child_process{static_cast<::pid_t>(child_pid), + m_terminal_fd, *m_delegates[0], m_arch, + unused_loop}; + switch (clone_info.event) { + case PTRACE_FORK: { + child_process.m_software_breakpoints = m_software_breakpoints; + llvm::Error error = child_process.DisableSoftwareBreakpoints(); + if (error) { + LLDB_LOG_ERROR( + log, std::move(error), + "failed to remove breakpoints from forked process {1}: {0}", + child_pid); + SetState(StateType::eStateInvalid); + return true; + } + break; + } + case PTRACE_VFORK: { + // vfork shares memory with the parent, so we need to temporarily remove + // breakpoints + llvm::Error error = DisableSoftwareBreakpoints(); + if (error) { + LLDB_LOG_ERROR( + log, std::move(error), + "failed to remove breakpoints from vforked process {1}: {0}", + child_pid); + SetState(StateType::eStateInvalid); + return true; + } + break; + } + default: + assert(false && "unknown clone_info.event"); + } + + child_process.Detach(); + Status pt_error = PtraceWrapper(PT_CONTINUE, GetID(), + reinterpret_cast(1), 0); + if (pt_error.Fail()) { + LLDB_LOG_ERROR(log, std::move(pt_error.ToError()), + "unable to resume parent process {1}: {0}", + GetID()); + SetState(StateType::eStateInvalid); + } + + m_pending_pid_map.erase(child_pid); + return true; +} Index: lldb/test/Shell/Subprocess/Inputs/clone.c =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/Inputs/clone.c @@ -0,0 +1,36 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +int g_val = 0; + +void parent_func() { + g_val = 1; + printf("function run in parent\n"); +} + +int child_func(void *unused) { + g_val = 2; + printf("function run in child\n"); + return 0; +} + +int main() { + char stack[4096]; + + pid_t pid = clone(child_func, &stack[sizeof(stack)], 0, NULL); + assert(pid != -1); + + parent_func(); + int status; + pid_t waited = waitpid(pid, &status, __WALL); + assert(waited == pid); + assert(WIFEXITED(status)); + printf("child exited: %d\n", WEXITSTATUS(status)); + + return 0; +} Index: lldb/test/Shell/Subprocess/Inputs/fork.c =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/Inputs/fork.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +int g_val = 0; + +void parent_func() { + g_val = 1; + printf("function run in parent\n"); +} + +void child_func() { + g_val = 2; + printf("function run in child\n"); +} + +int main() { + pid_t pid = fork(); + assert(pid != -1); + + 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/Inputs/vfork.c =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/Inputs/vfork.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +int g_val = 0; + +void parent_func() { + g_val = 1; + printf("function run in parent\n"); +} + +void child_func() { + g_val = 1; + g_val = 0; +} + +int main() { + pid_t pid = vfork(); + assert(pid != -1); + + 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/clone-follow-parent-softbp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/clone-follow-parent-softbp.test @@ -0,0 +1,12 @@ +# REQUIRES: native && (system-linux || system-netbsd) +# RUN: %clang_host %p/Inputs/clone.c -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +b parent_func +b child_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 Index: lldb/test/Shell/Subprocess/clone-follow-parent-wp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/clone-follow-parent-wp.test @@ -0,0 +1,13 @@ +# REQUIRES: native && (system-linux || system-netbsd) && (target-x86 || target-x86_64 || target-aarch64) && dbregs-set +# RUN: %clang_host -g %p/Inputs/clone.c -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +process launch -s +watchpoint set variable -w write g_val +# CHECK: Watchpoint created: +continue +# CHECK: function run in child +# CHECK-NOT: function run in parent +# CHECK: stop reason = watchpoint +continue +# CHECK: function run in parent +# CHECK: child exited: 0 Index: lldb/test/Shell/Subprocess/clone-follow-parent.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/clone-follow-parent.test @@ -0,0 +1,11 @@ +# REQUIRES: native && (system-linux || system-netbsd) +# RUN: %clang_host %p/Inputs/clone.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 Index: lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test @@ -0,0 +1,12 @@ +# REQUIRES: native +# RUN: %clang_host %p/Inputs/fork.c -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +b parent_func +b child_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 Index: lldb/test/Shell/Subprocess/fork-follow-parent-wp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/fork-follow-parent-wp.test @@ -0,0 +1,13 @@ +# REQUIRES: native && (target-x86 || target-x86_64 || target-aarch64) && dbregs-set +# RUN: %clang_host -g %p/Inputs/fork.c -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +process launch -s +watchpoint set variable -w write g_val +# CHECK: Watchpoint created: +continue +# CHECK: function run in child +# CHECK-NOT: function run in parent +# CHECK: stop reason = watchpoint +continue +# CHECK: function run in parent +# CHECK: child exited: 0 Index: lldb/test/Shell/Subprocess/fork-follow-parent.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/fork-follow-parent.test @@ -0,0 +1,11 @@ +# 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 Index: lldb/test/Shell/Subprocess/vfork-follow-parent-softbp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/vfork-follow-parent-softbp.test @@ -0,0 +1,11 @@ +# REQUIRES: native +# RUN: %clang_host %p/Inputs/vfork.c -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +b parent_func +b child_func +process launch +# CHECK-NOT: function run in parent +# CHECK: stop reason = breakpoint +continue +# CHECK: function run in parent +# CHECK: child exited: 0 Index: lldb/test/Shell/Subprocess/vfork-follow-parent-wp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/vfork-follow-parent-wp.test @@ -0,0 +1,12 @@ +# REQUIRES: native && (target-x86 || target-x86_64 || target-aarch64) && dbregs-set +# RUN: %clang_host -g %p/Inputs/vfork.c -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +process launch -s +watchpoint set variable -w write g_val +# CHECK: Watchpoint created: +continue +# CHECK-NOT: function run in parent +# CHECK: stop reason = watchpoint +continue +# CHECK: function run in parent +# CHECK: child exited: 0 Index: lldb/test/Shell/Subprocess/vfork-follow-parent.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/vfork-follow-parent.test @@ -0,0 +1,10 @@ +# REQUIRES: native +# RUN: %clang_host %p/Inputs/vfork.c -o %t +# RUN: %lldb -b -s %s %t | FileCheck %s +b parent_func +process launch +# CHECK-NOT: function run in parent +# CHECK: stop reason = breakpoint +continue +# CHECK: function run in parent +# CHECK: child exited: 0