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 +#include + +#include +#include + +__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; +}