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; 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/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 @@ -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 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/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-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/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