[Lldb-commits] [lldb] [lldb] Guard stale SBProcess target access (PR #192842)

via lldb-commits lldb-commits at lists.llvm.org
Sun Apr 19 04:07:29 PDT 2026


https://github.com/sfu2 created https://github.com/llvm/llvm-project/pull/192842

**Description**

`SBSaveCoreOptions` can retain a `ProcessSP` after `DeleteTarget` destroys the `Target`, leaving `Process` with a `weak_ptr<Target>` whose pointee has already been destroyed. Stale `SBProcess` handles then reach through `Process::GetTarget()` and crash while dereferencing that expired target relationship.

**Reproduction**

1. Create an `SBDebugger`.
2. Load a stopped Linux core file through the SB API to obtain an `SBTarget` and `SBProcess`.
3. Store the process in `SBSaveCoreOptions`. This retains the underlying `ProcessSP` even after the target is deleted.
4. Call `SBDebugger::DeleteTarget(target)`. At this point the public `SBTarget` is invalid, but the retained `Process` may still outlive it.
5. Call `process.GetTarget()`.
   Pre-fix, this can crash because `SBProcess::GetTarget()` reaches `Process::GetTarget()`, which dereferences an expired weak target relationship after the `Target` has already been destroyed.
6. Call `process.SaveCore(options)`.
   Pre-fix, this can also crash because `SBProcess::SaveCore()` takes the target API mutex through `process_sp->GetTarget().GetAPIMutex()`, which dereferences the same expired target relationship.

The new regression test `SBProcessDeleteTargetTest.GetTargetAndSaveCoreFailSafelyAfterDelete` exercises this sequence with a core-backed `SBProcess` retained through `SBSaveCoreOptions`. It reuses the checked-in core-file fixtures `lldb/test/API/tools/lldb-dap/coreFile/linux-x86_64.out` and `lldb/test/API/tools/lldb-dap/coreFile/linux-x86_64.core`, resolving them directly from the source tree at runtime.

```
Note: Google Test filter = SBProcessDeleteTargetTest.*
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from SBProcessDeleteTargetTest
[ RUN      ] SBProcessDeleteTargetTest.GetTargetAndSaveCoreFailSafelyAfterDelete
Segmentation fault (core dumped)
EXIT_STATUS:139
```

**Fix**

Add a nullable `Process::GetTargetSP()` helper for SB API callers, use it to make `SBProcess::GetTarget()` return an invalid target and make `SBProcess::SaveCore()` fail with an error when the target has already been deleted. Add an API regression test that keeps a core-backed process alive through `SBSaveCoreOptions`, deletes the target, and verifies both paths fail safely instead of crashing.

>From 6c490895c157c40354872764c7aebfa23048631e Mon Sep 17 00:00:00 2001
From: sfu <33919933+sfu2 at users.noreply.github.com>
Date: Sun, 19 Apr 2026 17:54:39 +0800
Subject: [PATCH] lldb: guard stale SBProcess target access

SBSaveCoreOptions can retain a ProcessSP after DeleteTarget destroys
the Target, leaving Process with a weak_ptr<Target> whose pointee has
already been destroyed. Stale SBProcess handles then reach through
Process::GetTarget() and crash while dereferencing that expired target
relationship.

Add a nullable Process::GetTargetSP() helper for SB API callers, use it
to make SBProcess::GetTarget() return an invalid target and make
SBProcess::SaveCore() fail with an error when the target has already been
deleted. Add an API regression test that keeps a core-backed process
alive through SBSaveCoreOptions, deletes the target, and verifies both
paths fail safely instead of crashing.
---
 lldb/include/lldb/Target/Process.h            |   5 +
 lldb/source/API/SBProcess.cpp                 |  23 ++--
 lldb/unittests/API/CMakeLists.txt             |   1 +
 .../API/SBProcessDeleteTargetTest.cpp         | 101 ++++++++++++++++++
 4 files changed, 123 insertions(+), 7 deletions(-)
 create mode 100644 lldb/unittests/API/SBProcessDeleteTargetTest.cpp

diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 19b5ae3041826..ca53f4633f4d5 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -1244,6 +1244,11 @@ class Process : public std::enable_shared_from_this<Process>,
 
   /// Get the target object pointer for this module.
   ///
+  /// \return
+  ///     A Target object pointer to the target that owns this
+  ///     module.
+  lldb::TargetSP GetTargetSP() const { return m_target_wp.lock(); }
+
   /// \return
   ///     A Target object pointer to the target that owns this
   ///     module.
diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp
index 14ce236b4f1b5..ca8d2100f4075 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -241,12 +241,15 @@ SBTarget SBProcess::GetTarget() const {
   LLDB_INSTRUMENT_VA(this);
 
   SBTarget sb_target;
-  TargetSP target_sp;
   ProcessSP process_sp(GetSP());
-  if (process_sp) {
-    target_sp = process_sp->GetTarget().shared_from_this();
-    sb_target.SetSP(target_sp);
-  }
+  if (!process_sp)
+    return sb_target;
+
+  TargetSP target_sp = process_sp->GetTargetSP();
+  if (!target_sp)
+    return sb_target;
+
+  sb_target.SetSP(target_sp);
 
   return sb_target;
 }
@@ -1274,8 +1277,14 @@ lldb::SBError SBProcess::SaveCore(SBSaveCoreOptions &options) {
     return error;
   }
 
-  std::lock_guard<std::recursive_mutex> guard(
-      process_sp->GetTarget().GetAPIMutex());
+  TargetSP target_sp = process_sp->GetTargetSP();
+  if (!target_sp) {
+    error = Status::FromErrorString(
+        "SBProcess is invalid because its target has been deleted");
+    return error;
+  }
+
+  std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
 
   if (process_sp->GetState() != eStateStopped) {
     error = Status::FromErrorString("the process is not stopped");
diff --git a/lldb/unittests/API/CMakeLists.txt b/lldb/unittests/API/CMakeLists.txt
index b86054fb353f7..19a6302021d8e 100644
--- a/lldb/unittests/API/CMakeLists.txt
+++ b/lldb/unittests/API/CMakeLists.txt
@@ -3,6 +3,7 @@ add_lldb_unittest(APITests
   SBLineEntryTest.cpp
   SBMutexTest.cpp
   SBBreakpointClearConditionTest.cpp
+  SBProcessDeleteTargetTest.cpp
 
   SBAPITEST
 
diff --git a/lldb/unittests/API/SBProcessDeleteTargetTest.cpp b/lldb/unittests/API/SBProcessDeleteTargetTest.cpp
new file mode 100644
index 0000000000000..078d5aa53ed6f
--- /dev/null
+++ b/lldb/unittests/API/SBProcessDeleteTargetTest.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Use the umbrella header for -Wdocumentation.
+#include "lldb/API/LLDB.h"
+
+#include "TestingSupport/SubsystemRAII.h"
+#include "gtest/gtest.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+namespace {
+
+class SBProcessDeleteTargetTest : public testing::Test {
+protected:
+  void SetUp() override {
+    debugger = SBDebugger::Create(/*source_init_files=*/false);
+  }
+
+  void TearDown() override { SBDebugger::Destroy(debugger); }
+
+  static bool DebuggerSupportsLLVMTarget(llvm::StringRef target) {
+    SBStructuredData data = SBDebugger::GetBuildConfiguration()
+                                .GetValueForKey("targets")
+                                .GetValueForKey("value");
+    for (size_t i = 0; i < data.GetSize(); ++i) {
+      char buf[100] = {0};
+      size_t size = data.GetItemAtIndex(i).GetStringValue(buf, sizeof(buf));
+      if (llvm::StringRef(buf, size) == target)
+        return true;
+    }
+    return false;
+  }
+
+  static std::string GetCoreFixturePath(llvm::StringRef name) {
+    llvm::SmallString<256> path(__FILE__);
+    llvm::sys::path::remove_filename(path);
+    llvm::sys::path::append(path, "..", "..", "test", "API");
+    llvm::sys::path::append(path, "tools", "lldb-dap", "coreFile", name);
+    return std::string(path.str());
+  }
+
+  void LoadCore(SBTarget &target, SBProcess &process) {
+    std::string binary_path = GetCoreFixturePath("linux-x86_64.out");
+    std::string core_path = GetCoreFixturePath("linux-x86_64.core");
+    ASSERT_TRUE(llvm::sys::fs::exists(binary_path));
+    ASSERT_TRUE(llvm::sys::fs::exists(core_path));
+
+    SBError error;
+    target = debugger.CreateTarget(binary_path.c_str(), /*target_triple=*/"",
+                                   /*platform_name=*/"",
+                                   /*add_dependent_modules=*/false, error);
+    EXPECT_TRUE(target);
+    EXPECT_TRUE(error.Success()) << error.GetCString();
+    debugger.SetSelectedTarget(target);
+
+    process = target.LoadCore(core_path.c_str());
+    EXPECT_TRUE(process);
+  }
+
+  SubsystemRAII<lldb::SBDebugger> subsystems;
+  SBDebugger debugger;
+};
+
+} // namespace
+
+TEST_F(SBProcessDeleteTargetTest, GetTargetAndSaveCoreFailSafelyAfterDelete) {
+  if (!DebuggerSupportsLLVMTarget("X86"))
+    GTEST_SKIP() << "X86 target is not enabled";
+
+  SBTarget target;
+  SBProcess process;
+  LoadCore(target, process);
+  ASSERT_TRUE(target.IsValid());
+  ASSERT_TRUE(process.IsValid());
+  ASSERT_EQ(process.GetState(), eStateStopped);
+
+  SBSaveCoreOptions options;
+  options.SetProcess(process);
+
+  ASSERT_TRUE(debugger.DeleteTarget(target));
+  ASSERT_FALSE(target.IsValid());
+  EXPECT_FALSE(process.IsValid());
+
+  EXPECT_FALSE(process.GetTarget().IsValid());
+
+  SBError error = process.SaveCore(options);
+  EXPECT_TRUE(error.Fail());
+  EXPECT_STREQ("SBProcess is invalid because its target has been deleted",
+               error.GetCString());
+}



More information about the lldb-commits mailing list