mgorny updated this revision to Diff 333441.
mgorny retitled this revision from "[lldb] [Process/Linux] Watch for fork/vfork
notifications" to "[lldb] [Process] Watch for fork/vfork notifications".
mgorny added a comment.
Now includes initial FreeBSD support. The watchpoint test still fails, we
probably need to clean dbregs on fork too.
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D98822/new/
https://reviews.llvm.org/D98822
Files:
lldb/include/lldb/Host/common/NativeProcessProtocol.h
lldb/include/lldb/Host/linux/Host.h
lldb/source/Host/common/NativeProcessProtocol.cpp
lldb/source/Host/linux/Host.cpp
lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h
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/Inputs/vfork.c
lldb/test/Shell/Subprocess/fork-follow-parent-softbp.test
lldb/test/Shell/Subprocess/fork-follow-parent-wp.test
lldb/test/Shell/Subprocess/fork-follow-parent.test
lldb/test/Shell/Subprocess/vfork-follow-parent-softbp.test
lldb/test/Shell/Subprocess/vfork-follow-parent.test
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
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/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/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-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/Inputs/vfork.c
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/Inputs/vfork.c
@@ -0,0 +1,37 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int g_val = 0;
+
+void parent_func() {
+ g_val = 1;
+ printf("function run in parent\n");
+}
+
+void child_func() {
+ // do something relatively safe
+ volatile int val = 0;
+ ++val;
+}
+
+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/Inputs/fork.c
===================================================================
--- /dev/null
+++ lldb/test/Shell/Subprocess/Inputs/fork.c
@@ -0,0 +1,36 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+
+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/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
@@ -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<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: {
+ // 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() == clone_info->parent_tid);
+
+ 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/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<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_freebsd
Index: lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
===================================================================
--- lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
+++ lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp
@@ -173,12 +173,15 @@
}
void NativeProcessFreeBSD::MonitorSIGSTOP(lldb::pid_t pid) {
- /* Stop all Threads attached to Process */
- for (const auto &thread : m_threads) {
- static_cast<NativeThreadFreeBSD &>(*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<NativeThreadFreeBSD &>(*thread).SetStoppedBySignal(SIGSTOP,
+ nullptr);
+ }
+ SetState(StateType::eStateStopped, true);
+ } else
+ MonitorClone(pid, llvm::None);
}
void NativeProcessFreeBSD::MonitorSIGTRAP(lldb::pid_t pid) {
@@ -247,6 +250,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<void *>(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<lldb::tid_t>(info.pl_lwpid))
@@ -706,32 +731,34 @@
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);
- }
+ if (wait_pid == -1) {
+ Status error(errno, eErrorTypePOSIX);
+ LLDB_LOG(log, "waitpid (-1, &status, _) failed: {0}", 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()));
+ 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()));
- LLDB_LOG(log,
- "waitpid ({0}, &status, _) => pid = {1}, status = {2}, exited = {3}",
- GetID(), wait_pid, status, exited);
+ 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);
+ if (exited)
+ MonitorExited(wait_pid, wait_status);
+ else {
+ assert(wait_status.type == WaitStatus::Stop);
+ MonitorCallback(wait_pid, wait_status.status);
+ }
}
}
@@ -885,7 +912,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 +946,75 @@
bool NativeProcessFreeBSD::SupportHardwareSingleStepping() const {
return !m_arch.IsMIPS();
}
+
+bool NativeProcessFreeBSD::MonitorClone(
+ lldb::pid_t child_pid,
+ llvm::Optional<NativeProcessFreeBSD::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);
+
+ MainLoop unused_loop;
+ NativeProcessFreeBSD child_process{static_cast<::pid_t>(child_pid),
+ m_terminal_fd, *m_delegates[0], m_arch,
+ unused_loop};
+
+ switch (clone_info->event) {
+ case PL_FLAG_FORKED: {
+ 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;
+ }
+ break;
+ }
+ case PL_FLAG_FORKED|PL_FLAG_VFORKED: {
+ // 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;
+ }
+ break;
+ }
+ default:
+ assert(false && "unknown clone_info.event");
+ }
+
+ child_process.Detach();
+ Status error = PtraceWrapper(PT_CONTINUE, clone_info->parent_tid,
+ reinterpret_cast<void *>(1), 0);
+ if (error.Fail())
+ SetState(StateType::eStateInvalid);
+
+ m_pending_pid_map.erase(child_pid);
+ return true;
+}
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::pid_t> 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/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/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<lldb::pid_t> getPIDForTID(lldb::pid_t tid);
+
+} // namespace lldb_private
+
+#endif // #ifndef LLDB_HOST_LINUX_HOST_H
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<std::pair<lldb::addr_t, size_t>, 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<SoftwareBreakpoint>
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits