Skip to content

Reland "[lldb] Clear thread-creation breakpoints in ProcessGDBRemote::Clear (#134397)" #135296

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

felipepiovezan
Copy link
Contributor

@felipepiovezan felipepiovezan commented Apr 11, 2025

This reapplies commit 232525f.

The original commit triggered a sanitizer failure when Target was
destroyed. In Target::Destroy, DeleteCurrentProcess was called, but
it did not destroy the thread creation breakpoints for the underlying
ProcessGDBRemote because ProcessGDBRemote::Clear was not called in
that path.

Target then proceeded to destroy its breakpoints, which resulted in a
call to the destructor of a std::vector containing the breakpoints.
Through a sequence of complicated events, destroying breakpoints caused
the reference count of the underlying ProcessGDBRemote to finally
reach zero. This, in turn, called ProcessGDBRemote::Clear, which
attempted to destroy the breakpoints. To do that, it would go back into
the Target's vector of breakpoints, which we are in the middle of
destroying.

We solve this by moving the breakpoint deletion into
Process:DoDestroy, which is a virtual Process method that will be
called much earlier.

@llvmbot
Copy link
Member

llvmbot commented Apr 11, 2025

@llvm/pr-subscribers-lldb

Author: Felipe de Azevedo Piovezan (felipepiovezan)

Changes

This reapplies commit 232525f.

The original commit triggered a sanitizer failure when Target was destroyed. In Target::Destroy, DeleteCurrentProcess was called, but it did not destroy the thread creation breakpoints for the underlying ProcessGDBRemote, because said method would not call ProcessGDBRemote::Clear.
Target then proceeded to destroy its breakpoints, which resulted in a call to the destructor a std::vector containing the breakpoints. Through a sequence of complicated events, destroying breakpoints caused the reference count of the underlying to finally reach zero. This, in turn, called ProcessGDBRemote::Clear, which attempted to destroy the breakpoints. To do that, it would query back into the Target vector of breakpoint, which we are in the middle of destroying.

We solve this by moving the breakpoint deletion into Process:DoDestroy, which is a virtual Process method that will be called much earlier.


Full diff: https://github.com/llvm/llvm-project/pull/135296.diff

3 Files Affected:

  • (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp (+9)
  • (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h (+3)
  • (modified) lldb/test/API/macosx/thread_start_bps/TestBreakpointsThreadInit.py (+20)
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 68360788c96e6..b616e99be83b2 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -2571,9 +2571,18 @@ Status ProcessGDBRemote::DoDestroy() {
 
   StopAsyncThread();
   KillDebugserverProcess();
+  RemoveNewThreadBreakpoints();
   return Status();
 }
 
+void ProcessGDBRemote::RemoveNewThreadBreakpoints() {
+  if (m_thread_create_bp_sp) {
+    if (TargetSP target_sp = m_target_wp.lock())
+      target_sp->RemoveBreakpointByID(m_thread_create_bp_sp->GetID());
+    m_thread_create_bp_sp.reset();
+  }
+}
+
 void ProcessGDBRemote::SetLastStopPacket(
     const StringExtractorGDBRemote &response) {
   const bool did_exec =
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 1cbd1e82b381d..20d7fc0801eb3 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -436,6 +436,9 @@ class ProcessGDBRemote : public Process,
                                            lldb::user_id_t break_id,
                                            lldb::user_id_t break_loc_id);
 
+  /// Remove the breakpoints associated with thread creation from the Target.
+  void RemoveNewThreadBreakpoints();
+
   // ContinueDelegate interface
   void HandleAsyncStdout(llvm::StringRef out) override;
   void HandleAsyncMisc(llvm::StringRef data) override;
diff --git a/lldb/test/API/macosx/thread_start_bps/TestBreakpointsThreadInit.py b/lldb/test/API/macosx/thread_start_bps/TestBreakpointsThreadInit.py
index 1c6fd4f91c73e..bf667f6f7d336 100644
--- a/lldb/test/API/macosx/thread_start_bps/TestBreakpointsThreadInit.py
+++ b/lldb/test/API/macosx/thread_start_bps/TestBreakpointsThreadInit.py
@@ -35,3 +35,23 @@ def test_internal_bps_resolved(self):
         for bp in bps:
             num_resolved += bp.GetNumResolvedLocations()
         self.assertGreater(num_resolved, 0)
+
+    @skipUnlessDarwin
+    def test_internal_bps_deleted_on_relaunch(self):
+        self.build()
+
+        source_file = lldb.SBFileSpec("main.c")
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "initial hello", source_file
+        )
+
+        self.runCmd("break list --internal")
+        output = self.res.GetOutput()
+        self.assertEqual(output.count("thread-creation"), 1)
+
+        process.Kill()
+        self.runCmd("run", RUN_SUCCEEDED)
+
+        self.runCmd("break list --internal")
+        output = self.res.GetOutput()
+        self.assertEqual(output.count("thread-creation"), 1)

…:Clear (llvm#134397)"

This reapplies commit 232525f.

The original commit triggered a sanitizer failure when `Target` was
destroyed. In `Target::Destroy`, `DeleteCurrentProcess` was called, but
it did not destroy the thread creation breakpoints for the underlying
`ProcessGDBRemote` because `ProcessGDBRemote::Clear` was not called in
that path.

`Target `then proceeded to destroy its breakpoints, which resulted in a
call to the destructor of a `std::vector` containing the breakpoints.
Through a sequence of complicated events, destroying breakpoints caused
the reference count of the underlying `ProcessGDBRemote` to finally
reach zero. This, in turn, called `ProcessGDBRemote::Clear`, which
attempted to destroy the breakpoints. To do that, it would go back into
the Target's vector of breakpoints, which we are in the middle of
destroying.

We solve this by moving the breakpoint deletion into
`Process:DoDestroy`, which is a virtual Process method that will be
called much earlier.
@felipepiovezan felipepiovezan force-pushed the felipe/reland_threadcretiondeletion branch from ae63d5b to b24dc5d Compare April 11, 2025 01:49
Copy link
Collaborator

@jasonmolenda jasonmolenda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice debugging on this one, it can be a little unclear of the intended jobs for each of the reset/clear/terminate methods. I definitely didn't remember them. This looks good to me.

Copy link
Collaborator

@jimingham jimingham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is a more appropriate place to do that work.

@felipepiovezan felipepiovezan merged commit c2939b9 into llvm:main Apr 11, 2025
10 checks passed
felipepiovezan added a commit to felipepiovezan/llvm-project that referenced this pull request Apr 11, 2025
…:Clear (llvm#134397)" (llvm#135296)

This reapplies commit
llvm@232525f.

The original commit triggered a sanitizer failure when `Target` was
destroyed. In `Target::Destroy`, `DeleteCurrentProcess` was called, but
it did not destroy the thread creation breakpoints for the underlying
`ProcessGDBRemote` because `ProcessGDBRemote::Clear` was not called in
that path.

`Target `then proceeded to destroy its breakpoints, which resulted in a
call to the destructor of a `std::vector` containing the breakpoints.
Through a sequence of complicated events, destroying breakpoints caused
the reference count of the underlying `ProcessGDBRemote` to finally
reach zero. This, in turn, called `ProcessGDBRemote::Clear`, which
attempted to destroy the breakpoints. To do that, it would go back into
the Target's vector of breakpoints, which we are in the middle of
destroying.

We solve this by moving the breakpoint deletion into
`Process:DoDestroy`, which is a virtual Process method that will be
called much earlier.

(cherry picked from commit c2939b9)
@felipepiovezan felipepiovezan deleted the felipe/reland_threadcretiondeletion branch April 11, 2025 20:29
var-const pushed a commit to ldionne/llvm-project that referenced this pull request Apr 17, 2025
…:Clear (llvm#134397)" (llvm#135296)

This reapplies commit
llvm@232525f.

The original commit triggered a sanitizer failure when `Target` was
destroyed. In `Target::Destroy`, `DeleteCurrentProcess` was called, but
it did not destroy the thread creation breakpoints for the underlying
`ProcessGDBRemote` because `ProcessGDBRemote::Clear` was not called in
that path.

`Target `then proceeded to destroy its breakpoints, which resulted in a
call to the destructor of a `std::vector` containing the breakpoints.
Through a sequence of complicated events, destroying breakpoints caused
the reference count of the underlying `ProcessGDBRemote` to finally
reach zero. This, in turn, called `ProcessGDBRemote::Clear`, which
attempted to destroy the breakpoints. To do that, it would go back into
the Target's vector of breakpoints, which we are in the middle of
destroying.

We solve this by moving the breakpoint deletion into
`Process:DoDestroy`, which is a virtual Process method that will be
called much earlier.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants