mgorny updated this revision to Diff 332792. mgorny retitled this revision from "[lldb] follow-fork/vfork support [WIP]" to "[lldb] [Process/Linux] Watch for fork notifications". mgorny edited the summary of this revision. mgorny added a comment.
So I've decided to circle back a bit and start by adding server-side detaching-on-fork support. This means that this patch doesn't depend on multiprocess support anymore. It includes refactoring clone/fork/vfork event monitoring into a single function. Right now, only forks are supported — the handler creates a local `NativeProcessLinux` instance (@labath, any suggestions how to make this less hacky?), duplicates parent's breakpoints into it (just like the child gets parent's memory) and uses it to clear these breakpoints and detach the child process. I suppose this will make things easier when we want to make the class more persistent in the future. TODO: vfork, clone CHANGES SINCE LAST ACTION https://reviews.llvm.org/D98822/new/ https://reviews.llvm.org/D98822 Files: lldb/include/lldb/Host/common/NativeProcessProtocol.h lldb/source/Host/common/NativeProcessProtocol.cpp lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp lldb/source/Plugins/Process/Linux/NativeProcessLinux.h lldb/test/Shell/Subprocess/Inputs/fork.c lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test lldb/test/Shell/Subprocess/fork-follow-parent.test
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 Index: lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test @@ -0,0 +1,13 @@ +# 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 +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/Inputs/fork.c =================================================================== --- /dev/null +++ lldb/test/Shell/Subprocess/Inputs/fork.c @@ -0,0 +1,37 @@ +#include <sys/types.h> +#include <sys/wait.h> +#include <assert.h> +#include <stdio.h> +#include <unistd.h> + +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/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<lldb::pid_t, llvm::Optional<CloneInfo>> 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<CloneInfo> 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 @@ -383,14 +383,15 @@ 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; + return PtraceWrapper(PTRACE_SETOPTIONS, pid, nullptr, (void *)ptrace_opts); } @@ -444,11 +445,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 +509,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 +534,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 +569,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 +640,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 +858,65 @@ StopRunningThreads(thread.GetID()); } +bool NativeProcessLinux::MonitorClone( + lldb::pid_t child_pid, + llvm::Optional<NativeProcessLinux::CloneInfo> 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: { + 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: { + 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; + child_process.RemoveAllSoftwareBreakpoints(); + 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/Host/common/NativeProcessProtocol.cpp =================================================================== --- lldb/source/Host/common/NativeProcessProtocol.cpp +++ lldb/source/Host/common/NativeProcessProtocol.cpp @@ -642,6 +642,15 @@ return RemoveSoftwareBreakpoint(addr); } +Status NativeProcessProtocol::RemoveAllSoftwareBreakpoints() { + while (!m_software_breakpoints.empty()) { + Status ret = RemoveBreakpoint(m_software_breakpoints.begin()->first); + if (ret.Fail()) + return ret; + } + return Status(); +} + Status NativeProcessProtocol::ReadMemoryWithoutTrap(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read) { Index: lldb/include/lldb/Host/common/NativeProcessProtocol.h =================================================================== --- lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -142,6 +142,8 @@ virtual Status RemoveBreakpoint(lldb::addr_t addr, bool hardware = false); + virtual Status RemoveAllSoftwareBreakpoints(); + // Hardware Breakpoint functions virtual const HardwareBreakpointMap &GetHardwareBreakpointMap() const;
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits