summaryrefslogtreecommitdiff
path: root/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
diff options
context:
space:
mode:
authorMichał Górny <mgorny@moritz.systems>2021-03-12 09:02:36 +0100
committerMichał Górny <mgorny@moritz.systems>2021-04-08 18:49:50 +0200
commita345419ee03095c8cdfbe1c2728467c4da8fa0a4 (patch)
tree72079c0b00a1fee2f119c576db7749a47fb0360a /lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
parentbcb8ef2d5a8ba4cea44bb39a856ba3ef88c8c2f7 (diff)
[lldb] [Process] Watch for fork/vfork notifications
Watch for fork(2)/vfork(2) (also fork/vfork-style clone(2) on Linux) notifications and explicitly detach the forked child process, and add initial tests for these cases. The code covers FreeBSD, Linux and NetBSD process plugins. There is no new user-visible functionality provided -- this change lays foundations over subsequent work on fork support. Differential Revision: https://reviews.llvm.org/D98822
Diffstat (limited to 'lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp')
-rw-r--r--lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp159
1 files changed, 119 insertions, 40 deletions
diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
index 5a876ce5befe..418a7a39be2a 100644
--- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
+++ b/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 @@ Status NativeProcessLinux::SetDefaultPtraceOpts(lldb::pid_t pid) {
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,8 +453,7 @@ void NativeProcessLinux::MonitorCallback(lldb::pid_t pid, bool exited,
LLDB_LOG(log, "tid {0}, si_code: {1}, si_pid: {2}", pid, info.si_code,
info.si_pid);
- NativeThreadLinux &thread = AddThread(pid, /*resume*/ true);
- ThreadWasCreated(thread);
+ MonitorClone(pid, llvm::None);
return;
}
@@ -509,29 +517,24 @@ void NativeProcessLinux::MonitorCallback(lldb::pid_t pid, bool exited,
}
}
-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.
@@ -539,17 +542,15 @@ void NativeProcessLinux::WaitForNewThread(::pid_t tid) {
}
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, /*resume*/ true);
-
- ThreadWasCreated(new_thread);
+ MonitorClone(pid, llvm::None);
}
void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info,
@@ -560,26 +561,26 @@ void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info,
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_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, {{(info.si_code >> 8), thread.GetID()}}))
+ WaitForCloneNotification(event_message);
+ }
- ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER);
break;
}
@@ -645,6 +646,11 @@ void NativeProcessLinux::MonitorSIGTRAP(const siginfo_t &info,
break;
}
+ case (SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8)): {
+ 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
@@ -854,6 +860,79 @@ void NativeProcessLinux::MonitorSignal(const siginfo_t &info,
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;
+ }
+ m_pending_pid_map.erase(find_it);
+
+ // 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 tgid_ret = getPIDForTID(child_pid);
+ if (tgid_ret != child_pid) {
+ // A new thread should have PGID matching our process' PID.
+ assert(!tgid_ret || tgid_ret.getValue() == GetID());
+
+ NativeThreadLinux &child_thread = AddThread(child_pid, /*resume*/ true);
+ // 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;
+ }
+ }
+ LLVM_FALLTHROUGH;
+ case PTRACE_EVENT_FORK:
+ case PTRACE_EVENT_VFORK: {
+ MainLoop unused_loop;
+ NativeProcessLinux child_process{static_cast<::pid_t>(child_pid),
+ m_terminal_fd,
+ *m_delegates[0],
+ m_arch,
+ unused_loop,
+ {static_cast<::pid_t>(child_pid)}};
+ child_process.Detach();
+ ResumeThread(*parent_thread, parent_thread->GetState(),
+ LLDB_INVALID_SIGNAL_NUMBER);
+ break;
+ }
+ default:
+ llvm_unreachable("unknown clone_info.event");
+ }
+
+ return true;
+}
+
bool NativeProcessLinux::SupportHardwareSingleStepping() const {
if (m_arch.GetMachine() == llvm::Triple::arm || m_arch.IsMIPS())
return false;