[Lldb-commits] [lldb] [lldb] Guard stale SBProcess target access (PR #192842)
via lldb-commits
lldb-commits at lists.llvm.org
Sun Apr 19 04:08:01 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: Henry (sfu2)
<details>
<summary>Changes</summary>
**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.
---
Full diff: https://github.com/llvm/llvm-project/pull/192842.diff
4 Files Affected:
- (modified) lldb/include/lldb/Target/Process.h (+5)
- (modified) lldb/source/API/SBProcess.cpp (+16-7)
- (modified) lldb/unittests/API/CMakeLists.txt (+1)
- (added) lldb/unittests/API/SBProcessDeleteTargetTest.cpp (+101)
``````````diff
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());
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/192842
More information about the lldb-commits
mailing list