[Lldb-commits] [lldb] [LLDB] Ptrace seize dead process (PR #137041)

Jacob Lalonde via lldb-commits lldb-commits at lists.llvm.org
Thu May 8 13:02:22 PDT 2025


https://github.com/Jlalond updated https://github.com/llvm/llvm-project/pull/137041

>From fa08811c7a90b9fd1c644b051ed3d7d1157243fe Mon Sep 17 00:00:00 2001
From: Jacob Lalonde <jalalonde at fb.com>
Date: Thu, 8 May 2025 12:56:14 -0700
Subject: [PATCH] Implement PTRACE_SEIZE when a process is coredumping

---
 .../lldb/Host/common/NativeProcessProtocol.h  |   2 +
 .../Process/Linux/NativeProcessLinux.cpp      | 120 +++++++++++++++++-
 .../Process/Linux/NativeProcessLinux.h        |   5 +-
 3 files changed, 120 insertions(+), 7 deletions(-)

diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
index 1d5fecfcd5c27..f50e9da0957f5 100644
--- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h
+++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
@@ -189,6 +189,8 @@ class NativeProcessProtocol {
 
   bool CanResume() const { return m_state == lldb::eStateStopped; }
 
+  bool IsStopped() const { return m_state == lldb::eStateStopped; }
+
   lldb::ByteOrder GetByteOrder() const {
     return GetArchitecture().GetByteOrder();
   }
diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
index 7f2aba0e4eb2c..c8e8f47524fb6 100644
--- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
@@ -312,10 +312,26 @@ NativeProcessLinux::Manager::Attach(
   Log *log = GetLog(POSIXLog::Process);
   LLDB_LOG(log, "pid = {0:x}", pid);
 
-  auto tids_or = NativeProcessLinux::Attach(pid);
-  if (!tids_or)
-    return tids_or.takeError();
-  ArrayRef<::pid_t> tids = *tids_or;
+  // This safety check lets us decide if we should
+  // seize or attach.
+  ProcessInstanceInfo process_info;
+  if (!Host::GetProcessInfo(pid, process_info))
+    return llvm::make_error<StringError>("Unable to read process info",
+                                         llvm::inconvertibleErrorCode());
+
+  std::vector<::pid_t> tids;
+  if (process_info.IsCoreDumping()) {
+    auto attached_or = NativeProcessLinux::Seize(pid);
+    if (!attached_or)
+      return attached_or.takeError();
+    tids = std::move(*attached_or);
+  } else {
+    auto attached_or = NativeProcessLinux::Attach(pid);
+    if (!attached_or)
+      return attached_or.takeError();
+    tids = std::move(*attached_or);
+  }
+
   llvm::Expected<ArchSpec> arch_or =
       NativeRegisterContextLinux::DetermineArchitecture(tids[0]);
   if (!arch_or)
@@ -444,6 +460,93 @@ NativeProcessLinux::NativeProcessLinux(::pid_t pid, int terminal_fd,
   SetState(StateType::eStateStopped, false);
 }
 
+llvm::Expected<std::vector<::pid_t>> NativeProcessLinux::Seize(::pid_t pid) {
+  // TODO: Because the Seize during coredumping change introduces the
+  // concept of a non resumable stop, we should also check for
+  // PTRACE_O_TRACEEXIT, which per the man page the status will equal
+  // status >> 8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
+  // and if this is true, we should say we can't resume.
+  Log *log = GetLog(POSIXLog::Process);
+
+  uint64_t options = GetDefaultPtraceOpts();
+  Status status;
+  // Use a map to keep track of the threads which we have attached/need to
+  // attach.
+  Host::TidMap tids_to_attach;
+  while (Host::FindProcessThreads(pid, tids_to_attach)) {
+    for (Host::TidMap::iterator it = tids_to_attach.begin();
+         it != tids_to_attach.end();) {
+      if (it->second == true) {
+        continue;
+      }
+      lldb::tid_t tid = it->first;
+      if ((status = PtraceWrapper(PTRACE_SEIZE, tid, nullptr, (void *)options))
+              .Fail()) {
+        // No such thread. The thread may have exited. More error handling
+        // may be needed.
+        if (status.GetError() == ESRCH) {
+          it = tids_to_attach.erase(it);
+          continue;
+        }
+        if (status.GetError() == EPERM) {
+          // Depending on the value of ptrace_scope, we can return a
+          // different error that suggests how to fix it.
+          return AddPtraceScopeNote(status.ToError());
+        }
+        return status.ToError();
+      }
+
+      if ((status = PtraceWrapper(PTRACE_INTERRUPT, tid)).Fail()) {
+        // No such thread. The thread may have exited. More error handling
+        // may be needed.
+        if (status.GetError() == ESRCH) {
+          it = tids_to_attach.erase(it);
+          continue;
+        }
+        if (status.GetError() == EPERM) {
+          // Depending on the value of ptrace_scope, we can return a
+          // different error that suggests how to fix it.
+          return AddPtraceScopeNote(status.ToError());
+        }
+        return status.ToError();
+      }
+
+      int wpid =
+          llvm::sys::RetryAfterSignal(-1, ::waitpid, tid, nullptr, __WALL);
+      // Need to use __WALL otherwise we receive an error with errno=ECHLD At
+      // this point we should have a thread stopped if waitpid succeeds.
+      if (wpid < 0) {
+        // No such thread. The thread may have exited. More error handling
+        // may be needed.
+        if (errno == ESRCH) {
+          it = tids_to_attach.erase(it);
+          continue;
+        }
+        return llvm::errorCodeToError(
+            std::error_code(errno, std::generic_category()));
+      }
+
+      LLDB_LOG(log, "adding tid = {0}", tid);
+      it->second = true;
+
+      // move the loop forward
+      ++it;
+    }
+  }
+
+  size_t tid_count = tids_to_attach.size();
+  if (tid_count == 0)
+    return llvm::make_error<StringError>("No such process",
+                                         llvm::inconvertibleErrorCode());
+
+  std::vector<::pid_t> tids;
+  tids.reserve(tid_count);
+  for (const auto &p : tids_to_attach)
+    tids.push_back(p.first);
+
+  return std::move(tids);
+}
+
 llvm::Expected<std::vector<::pid_t>> NativeProcessLinux::Attach(::pid_t pid) {
   Log *log = GetLog(POSIXLog::Process);
 
@@ -513,8 +616,8 @@ llvm::Expected<std::vector<::pid_t>> NativeProcessLinux::Attach(::pid_t pid) {
   return std::move(tids);
 }
 
-Status NativeProcessLinux::SetDefaultPtraceOpts(lldb::pid_t pid) {
-  long ptrace_opts = 0;
+uint64_t NativeProcessLinux::GetDefaultPtraceOpts() {
+  uint64_t ptrace_opts = 0;
 
   // Have the child raise an event on exit.  This is used to keep the child in
   // limbo until it is destroyed.
@@ -537,6 +640,11 @@ Status NativeProcessLinux::SetDefaultPtraceOpts(lldb::pid_t pid) {
   // the child finishes sharing memory.
   ptrace_opts |= PTRACE_O_TRACEVFORKDONE;
 
+  return ptrace_opts;
+}
+
+Status NativeProcessLinux::SetDefaultPtraceOpts(lldb::pid_t pid) {
+  uint64_t ptrace_opts = GetDefaultPtraceOpts();
   return PtraceWrapper(PTRACE_SETOPTIONS, pid, nullptr, (void *)ptrace_opts);
 }
 
diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
index d345f165a75d8..9ae4e57e74add 100644
--- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
+++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
@@ -175,7 +175,6 @@ class NativeProcessLinux : public NativeProcessELF,
 private:
   Manager &m_manager;
   ArchSpec m_arch;
-
   LazyBool m_supports_mem_region = eLazyBoolCalculate;
   std::vector<std::pair<MemoryRegionInfo, FileSpec>> m_mem_region_cache;
 
@@ -191,9 +190,13 @@ class NativeProcessLinux : public NativeProcessELF,
 
   // Returns a list of process threads that we have attached to.
   static llvm::Expected<std::vector<::pid_t>> Attach(::pid_t pid);
+  // Returns a list of process threads that we have seized and interrupted.
+  static llvm::Expected<std::vector<::pid_t>> Seize(::pid_t pid);
 
   static Status SetDefaultPtraceOpts(const lldb::pid_t);
 
+  static uint64_t GetDefaultPtraceOpts();
+
   bool TryHandleWaitStatus(lldb::pid_t pid, WaitStatus status);
 
   void MonitorCallback(NativeThreadLinux &thread, WaitStatus status);



More information about the lldb-commits mailing list