Skip to content

[lldb] Don't adopt in the ExecutionContext from auto-continue events#191433

Merged
JDevlieghere merged 1 commit into
llvm:mainfrom
JDevlieghere:issue-190956
Apr 17, 2026
Merged

[lldb] Don't adopt in the ExecutionContext from auto-continue events#191433
JDevlieghere merged 1 commit into
llvm:mainfrom
JDevlieghere:issue-190956

Conversation

@JDevlieghere

Copy link
Copy Markdown
Member

When a breakpoint auto-continues, the event handler receives a "stopped but restarted" event. During the transition where we step over the breakpoint (before continuing), the public state hasn't yet been set to running. This caused the DefaultEventHandler to call ExecutionContextRef with adopt_selected=true, which would fetch stale thread/frame state and needlessly (and incorrectly) interrupt the target to compute the execution context (used by the statusline). This PR fixes that by not doing that.

Fixes #190956

When a breakpoint auto-continues, the event handler receives a "stopped
but restarted" event. During the transition where we step over the
breakpoint (before continuing), the public state hasn't yet been set to
running. This caused the `DefaultEventHandler` to call
`ExecutionContextRef` with `adopt_selected=true`, which would fetch
stale thread/frame state and needlessly (and incorrectly) interrupt the
target to compute the execution context (used by the statusline). This
PR fixes that by not doing that.

Fixes llvm#190956

Co-authored-by: Jim Ingham <jingham@apple.com>
@JDevlieghere JDevlieghere requested a review from jimingham April 10, 2026 14:40
@llvmbot llvmbot added the lldb label Apr 10, 2026
@llvmbot

llvmbot commented Apr 10, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

When a breakpoint auto-continues, the event handler receives a "stopped but restarted" event. During the transition where we step over the breakpoint (before continuing), the public state hasn't yet been set to running. This caused the DefaultEventHandler to call ExecutionContextRef with adopt_selected=true, which would fetch stale thread/frame state and needlessly (and incorrectly) interrupt the target to compute the execution context (used by the statusline). This PR fixes that by not doing that.

Fixes #190956


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

5 Files Affected:

  • (modified) lldb/source/Core/Debugger.cpp (+12-3)
  • (added) lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/Makefile (+4)
  • (added) lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/TestBreakpointCommandAutoContinue.py (+40)
  • (added) lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/bpcmd.py (+11)
  • (added) lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/main.cpp (+35)
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 9deef5a4ae503..a9129c2a6aef1 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -2246,10 +2246,19 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
           uint32_t event_type = event_sp->GetType();
           llvm::StringRef broadcaster_class(broadcaster->GetBroadcasterClass());
           if (broadcaster_class == broadcaster_class_process) {
-            if (ProcessSP process_sp = HandleProcessEvent(event_sp))
+            if (ProcessSP process_sp = HandleProcessEvent(event_sp)) {
+              // Don't pass adopt_selected = true if this is a stop for an
+              // auto-continue event (e.g. an auto-continue breakpoint).  We
+              // would be fetching stale state, since the process resumed since
+              // this event, and we'd needlessly interrupt the target to do so.
+              const bool adopt_selected =
+                  process_sp->GetPrivateState() == eStateStopped &&
+                  !Process::ProcessEventData::GetRestartedFromEvent(
+                      event_sp.get());
               if (!RequiresFollowChildWorkaround(*process_sp))
-                exe_ctx_ref = ExecutionContextRef(process_sp.get(),
-                                                  /*adopt_selected=*/true);
+                exe_ctx_ref =
+                    ExecutionContextRef(process_sp.get(), adopt_selected);
+            }
           } else if (broadcaster_class == broadcaster_class_target) {
             if (Breakpoint::BreakpointEventData::GetEventDataFromEvent(
                     event_sp.get())) {
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/Makefile b/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/Makefile
new file mode 100644
index 0000000000000..c46619c662348
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/Makefile
@@ -0,0 +1,4 @@
+CXX_SOURCES := main.cpp
+ENABLE_THREADS := YES
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/TestBreakpointCommandAutoContinue.py b/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/TestBreakpointCommandAutoContinue.py
new file mode 100644
index 0000000000000..ebf75f8563256
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/TestBreakpointCommandAutoContinue.py
@@ -0,0 +1,40 @@
+"""
+Regression test for a bug in the default event handler (specifically when
+redrawing the statusline) that triggered when auto-continuing from a
+breakpoint.
+"""
+
+import os
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.lldbpexpect import PExpectTest
+
+
+@skipIfAsan
+@skipIfEditlineSupportMissing
+class BreakpointCommandAutoContinueTestCase(PExpectTest):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_breakpoint_command_auto_continue(self):
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        bpcmd = os.path.join(self.getSourceDir(), "bpcmd.py")
+
+        self.launch(executable=exe, timeout=60, dimensions=(25, 80))
+
+        self.expect("breakpoint set --name break_here", substrs=["Breakpoint 1"])
+        self.expect(
+            f"command script import {bpcmd}",
+        )
+        self.expect(
+            "breakpoint command add --python-function bpcmd.write_ok 1",
+        )
+
+        # Run the program. It should complete successfully (print PASSED).
+        # Without the fix, the debugger would interrupt the process when
+        # processing auto-continue events to fetch stale thread/frame state
+        # for the statusline, causing the memory writes to fail and the
+        # program to abort.
+        self.child.sendline("run")
+        self.child.expect("PASSED", timeout=30)
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/bpcmd.py b/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/bpcmd.py
new file mode 100644
index 0000000000000..4205732630eac
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/bpcmd.py
@@ -0,0 +1,11 @@
+import lldb
+
+
+def write_ok(frame, bp_loc, internal_dict):
+    """Write "OK" into the buffer pointed to by 'buf' and auto-continue."""
+    buf = frame.FindVariable("buf")
+    addr = buf.GetValueAsUnsigned()
+    if addr != 0:
+        error = lldb.SBError()
+        frame.GetThread().GetProcess().WriteMemory(addr, b"OK", error)
+    return False
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/main.cpp b/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/main.cpp
new file mode 100644
index 0000000000000..9657ab08b204f
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_command_auto_continue/main.cpp
@@ -0,0 +1,35 @@
+#include <pthread.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstring>
+
+__attribute__((noinline)) void break_here(char *buf) {
+  // Memory barrier to keep the function body non-empty.
+  asm volatile("" ::: "memory");
+}
+
+void *test_thread(void *) {
+  char buf[1024];
+  for (int i = 0; i < 200; i++) {
+    memset(buf, 0, sizeof(buf));
+    break_here(buf);
+    if (memcmp(buf, "OK", 2) != 0) {
+      printf("FAILED at iteration %d\n", i);
+      _exit(1);
+    }
+    usleep(50);
+  }
+  return nullptr;
+}
+
+int main() {
+  pthread_t thread;
+  if (pthread_create(&thread, nullptr, test_thread, nullptr) != 0) {
+    perror("pthread_create");
+    return 1;
+  }
+  pthread_join(thread, nullptr);
+  printf("PASSED\n");
+  return 0;
+}

@jimingham jimingham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@JDevlieghere JDevlieghere merged commit e8c8cbb into llvm:main Apr 17, 2026
13 checks passed
@JDevlieghere JDevlieghere deleted the issue-190956 branch April 17, 2026 17:05
alexfh pushed a commit to alexfh/llvm-project that referenced this pull request Apr 18, 2026
…lvm#191433)

When a breakpoint auto-continues, the event handler receives a "stopped
but restarted" event. During the transition where we step over the
breakpoint (before continuing), the public state hasn't yet been set to
running. This caused the `DefaultEventHandler` to call
`ExecutionContextRef` with `adopt_selected=true`, which would fetch
stale thread/frame state and needlessly (and incorrectly) interrupt the
target to compute the execution context (used by the statusline). This
PR fixes that by not doing that.

Fixes llvm#190956

Co-authored-by: Jim Ingham <jingham@apple.com>
KHicketts pushed a commit to KHicketts/llvm-project that referenced this pull request Apr 30, 2026
…lvm#191433)

When a breakpoint auto-continues, the event handler receives a "stopped
but restarted" event. During the transition where we step over the
breakpoint (before continuing), the public state hasn't yet been set to
running. This caused the `DefaultEventHandler` to call
`ExecutionContextRef` with `adopt_selected=true`, which would fetch
stale thread/frame state and needlessly (and incorrectly) interrupt the
target to compute the execution context (used by the statusline). This
PR fixes that by not doing that.

Fixes llvm#190956

Co-authored-by: Jim Ingham <jingham@apple.com>
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.

lldb does not correctly step over scripted breakpoint in some situations

3 participants