[Lldb-commits] [lldb] [lldb][Windows] Report all queued exceptions in one lldb-server stop (PR #227353)

via lldb-commits lldb-commits at lists.llvm.org
Tue Sep 29 08:49:04 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Charles Zablit (charles-zablit)

<details>
<summary>Changes</summary>

# Brief

Windows only gives one debug event per process at a time. When 2 threads hit the same breakpoint, the second thread's event is only received once the first thread is continued.

In this scenario, lldb-server reports the first stop from the exception handler and returns [`MaskException`](https://github.com/llvm/llvm-project/blob/34ba34d7f9bd2c756db7df96d3a5be39f006aa19/lldb/source/Plugins/Process/Windows/Common/ForwardDecl.h#L20). `DebuggerThread` continues the event immediatly and goes back to handling new events, **while the user is still looking at the stop**.

# Detailed explanation

Threads `A` and `B` both hit a breakpoint at address `X` almost at the same time.

1. The kernel sends `A`'s `EXCEPTION_BREAKPOINT`. `B` has already executed the trap, so its program counter (PC) is `X+1`, but its event has not been sent by the kernel yet.
2. lldb-server reports the stop. For `A`, `HandleBreakpointException`:
   - suspends every thread (`StopThread`),
   - moves `A`'s `PC` back to `X` and gives `A` a breakpoint reason,
   - calls `SetState(eStateStopped, true)`, which makes LLGS build and send the stop reply immediatly.

   In that reply `A` has reason breakpoint. `B` is listed with no reason and its `PC` is `X+1`.
3. The loop picks up `B`'s event. It returns `MaskException`, the event is continued, and the loop receives `B`'s queued event about 50 µs after the reply went out.
4. `B`'s breakpoint hit is applied silently. `HandleBreakpointException` runs for `B`: it gives `B` a breakpoint reason and moves `B`'s `PC` back to `X`. Its `SetState(eStateStopped, true)` does nothing, because the state is already stopped. No second stop reply is sent.
5. The client ends up with an inconsistent picture of `B`.
   - From the reply, it decided `B` has no stop reason. `X+1` is not a breakpoint site, so it didn't mark `B` as `"stopped at an unexecuted breakpoint"`.
   - A later `jThreadsInfo` updates `B`'s `PC` to `X`, but the client considers `B`'s stop info already current and doesn't re-read the reason.
   - So `B` is at a breakpoint site with no reason and no `"unexecuted"` mark.
6. On resume the hit is lost. `Thread::SetupToStepOverBreakpointIfNeeded` sees a thread sitting on a site it supposedly already hit, and steps it over the trap.

This is how tests hang. In TestThreadStepOut, for example:
- the test waits for `B`'s breakpoint while `A` stays suspended,
- `B` runs to the end instead,
- main blocks joining `A`, so `Continue()` never returns.

# Fix

This patch collects the stop before reporting it: the exception handlers record the stop through `DeferStop`. In the meantime, `DebuggerThread` processes the events that are already queued and then reports one stop that contains every thread that hit a breakpoint. It then waits for the client to resume.

# Testing

At desk, I was able to reproduce consistent failures on a loaded machine. With the patch, the three hanging tests now pass under the same conditions.

---

Patch is 23.90 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/227353.diff


15 Files Affected:

- (modified) lldb/include/lldb/Host/common/NativeProcessProtocol.h (+4-1) 
- (modified) lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp (+34-3) 
- (modified) lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h (+4) 
- (modified) lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h (+3) 
- (modified) lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp (+77-9) 
- (modified) lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h (+24) 
- (modified) lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp (+1) 
- (modified) lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h (+4) 
- (modified) lldb/test/API/commands/thread/backtrace/TestThreadBacktraceRepeat.py (-2) 
- (modified) lldb/test/API/commands/watchpoints/hello_watchlocation/TestWatchLocation.py (-1) 
- (modified) lldb/test/API/functionalities/thread/num_threads/TestNumThreads.py (+3-3) 
- (modified) lldb/test/API/functionalities/thread/step_out/TestThreadStepOut.py (+7-6) 
- (modified) lldb/test/API/python_api/watchpoint/watchlocation/TestSetWatchlocation.py (-1) 
- (modified) lldb/test/API/tools/lldb-dap/stopped-events/TestDAP_stopped_events.py (+7-1) 
- (modified) lldb/test/API/tools/lldb-server/TestGdbRemoteThreadsInStopReply.py (-1) 


``````````diff
diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
index 209cb068aba21..da1fa26abefa9 100644
--- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h
+++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
@@ -544,8 +544,11 @@ class NativeProcessProtocol {
 
   NativeThreadProtocol *GetThreadByIDUnlocked(lldb::tid_t tid);
 
-private:
+  /// Notify the delegates of \p state. For plugins that set a state with
+  /// SetState(state, /*notify_delegates=*/false) and report it later.
   void SynchronouslyNotifyProcessStateChanged(lldb::StateType state);
+
+private:
   llvm::Expected<SoftwareBreakpoint>
   EnableSoftwareBreakpoint(lldb::addr_t addr, uint32_t size_hint);
 };
diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
index 260ac0fe0faf6..03e50675b2bd8 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
@@ -259,6 +259,13 @@ void DebuggerThread::ContinueAsyncException(ExceptionResult result) {
   m_exception_pred.SetValue(result, eBroadcastAlways);
 }
 
+void DebuggerThread::ReportDeferredStopAndWaitForResume() {
+  m_dll_event_pred.SetValue(false, eBroadcastNever);
+  m_debug_delegate->ReportDeferredStop();
+  if (!m_is_shutting_down)
+    m_dll_event_pred.WaitForValueEqualTo(true);
+}
+
 void DebuggerThread::ContinueAsyncDllEvent() {
   m_dll_event_pred.SetValue(true, eBroadcastAlways);
 }
@@ -276,10 +283,28 @@ void DebuggerThread::DebugLoop() {
   Log *log = GetLog(WindowsLog::Event);
   DEBUG_EVENT dbe = {};
   bool should_debug = true;
+  constexpr unsigned max_drained_events = 64;
+  unsigned drained_events = 0;
   LLDB_LOG_VERBOSE(log, "Entering WaitForDebugEventEx loop");
   while (should_debug) {
+    bool draining = !m_is_shutting_down && m_debug_delegate->HasDeferredStop();
+    if (draining && drained_events >= max_drained_events) {
+      LLDB_LOG(log, "reporting the stop after taking {0} queued debug events",
+               drained_events);
+      ReportDeferredStopAndWaitForResume();
+      draining = false;
+    }
+    if (!draining)
+      drained_events = 0;
     LLDB_LOG_VERBOSE(log, "Calling WaitForDebugEvent");
-    BOOL wait_result = g_wait_for_debug_event(&dbe, INFINITE);
+    BOOL wait_result = g_wait_for_debug_event(&dbe, draining ? 0 : INFINITE);
+    if (draining) {
+      if (!wait_result) {
+        ReportDeferredStopAndWaitForResume();
+        continue;
+      }
+      ++drained_events;
+    }
     if (wait_result) {
       DWORD continue_status = DBG_CONTINUE;
       bool shutting_down = m_is_shutting_down;
@@ -432,6 +457,8 @@ DebuggerThread::HandleExceptionEvent(const EXCEPTION_DEBUG_INFO &info,
   // result is what the wait below returns.
   if (result != ExceptionResult::BreakInDebugger)
     ContinueAsyncException(result);
+  else
+    m_debug_delegate->ReportDeferredStop();
 
   LLDB_LOG(log, "waiting for ExceptionPred != BreakInDebugger");
   result = *m_exception_pred.WaitForValueNotEqualTo(
@@ -785,8 +812,10 @@ DebuggerThread::HandleLoadDllEvent(const LOAD_DLL_DEBUG_INFO &info,
   if (info.hFile != nullptr)
     ::CloseHandle(info.hFile);
 
-  if (action == DllEventAction::ParkDebugLoop && !m_is_shutting_down.load())
+  if (action == DllEventAction::ParkDebugLoop && !m_is_shutting_down.load()) {
+    m_debug_delegate->ReportDeferredStop();
     m_dll_event_pred.WaitForValueEqualTo(true);
+  }
   return DBG_CONTINUE;
 }
 
@@ -800,8 +829,10 @@ DebuggerThread::HandleUnloadDllEvent(const UNLOAD_DLL_DEBUG_INFO &info,
   m_dll_event_pred.SetValue(false, eBroadcastNever);
   DllEventAction action = m_debug_delegate->OnUnloadDll(
       reinterpret_cast<lldb::addr_t>(info.lpBaseOfDll), thread_id);
-  if (action == DllEventAction::ParkDebugLoop && !m_is_shutting_down.load())
+  if (action == DllEventAction::ParkDebugLoop && !m_is_shutting_down.load()) {
+    m_debug_delegate->ReportDeferredStop();
     m_dll_event_pred.WaitForValueEqualTo(true);
+  }
   return DBG_CONTINUE;
 }
 
diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
index 03202264b01ee..4e81099e7afb7 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
@@ -50,6 +50,10 @@ class DebuggerThread : public std::enable_shared_from_this<DebuggerThread> {
   void ContinueAsyncDllEvent();
 
 private:
+  /// Have the delegate report the stop it collected, then wait until the
+  /// client resumes before taking the next debug event.
+  void ReportDeferredStopAndWaitForResume();
+
   void FreeProcessHandles();
   void DebugLoop();
   ExceptionResult HandleExceptionEvent(const EXCEPTION_DEBUG_INFO &info,
diff --git a/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h b/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
index cecd58a15f9c2..f5c9b53447f3b 100644
--- a/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
+++ b/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
@@ -37,6 +37,9 @@ class IDebugDelegate {
                                    lldb::tid_t thread_id) = 0;
   virtual DllEventAction OnUnloadDll(lldb::addr_t module_addr,
                                      lldb::tid_t thread_id) = 0;
+  virtual bool HasDeferredStop() { return false; }
+  /// Report the stop the delegate holds, if any.
+  virtual void ReportDeferredStop() {}
   virtual void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
                              uint16_t length_lower_word) = 0;
   virtual void OnDebuggerError(const Status &error, uint32_t type) = 0;
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 849931eaec372..7e311cf28ea7d 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -519,6 +519,9 @@ void NativeProcessWindows::OnExitProcess(uint32_t exit_code) {
 
   ProcessDebugger::OnExitProcess(exit_code);
 
+  // The exit replaces a stop that has not been reported yet.
+  m_deferred_stop = false;
+
   // No signal involved.  It is just an exit event.
   WaitStatus wait_status(WaitStatus::Exit, exit_code);
   SetExitStatus(wait_status, true);
@@ -563,6 +566,57 @@ void NativeProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) {
       *this, m_session_data->m_debugger->GetMainThread()));
 }
 
+void NativeProcessWindows::DeferStop() {
+  if (GetState() == eStateStopped) {
+    if (!m_deferred_stop)
+      LLDB_LOG(GetLog(WindowsLog::Exception),
+               "debug event on thread {0:x} while a reported stop is current",
+               GetCurrentThreadID());
+    return;
+  }
+  SetState(eStateStopped, /*notify_delegates=*/false);
+  m_deferred_stop = true;
+  m_deferred_stop_tid = GetCurrentThreadID();
+}
+
+void NativeProcessWindows::ReportDeferredStop() {
+  llvm::sys::ScopedLock lock(m_mutex);
+  if (!m_deferred_stop)
+    return;
+  m_deferred_stop = false;
+  if (GetThreadByID(m_deferred_stop_tid))
+    SetCurrentThreadID(m_deferred_stop_tid);
+  SynchronouslyNotifyProcessStateChanged(eStateStopped);
+}
+
+bool NativeProcessWindows::RewindTrapOfRemovedBreakpoint(
+    const ExceptionRecord &record) {
+  if (!m_initial_stop_seen)
+    return false;
+  NativeThreadWindows *thread = GetThreadByID(record.GetThreadID());
+  if (!thread)
+    return false;
+  const lldb::addr_t trap_addr = record.GetExceptionAddress();
+  llvm::ArrayRef<uint8_t> trap_opcode =
+      cantFail(GetSoftwareBreakpointTrapOpcode(0));
+  NativeRegisterContextWindows &reg_ctx = thread->GetRegisterContext();
+  if (reg_ctx.GetPC() != trap_addr + trap_opcode.size())
+    return false;
+  llvm::SmallVector<uint8_t, 4> bytes(trap_opcode.size(), 0);
+  size_t bytes_read = 0;
+  if (ProcessDebugger::ReadMemory(trap_addr, bytes.data(), bytes.size(),
+                                  bytes_read)
+          .Fail() ||
+      bytes_read != bytes.size() ||
+      llvm::ArrayRef<uint8_t>(bytes) == trap_opcode)
+    return false;
+  LLDB_LOG(GetLog(WindowsLog::Exception),
+           "Trap at {0:x} on thread {1:x} is from a removed breakpoint, "
+           "rewinding the thread onto the original instruction.",
+           trap_addr, thread->GetID());
+  return reg_ctx.SetPC(trap_addr).Success();
+}
+
 ExceptionResult
 NativeProcessWindows::HandleSingleStepException(const ExceptionRecord &record) {
   uint32_t wp_id = LLDB_INVALID_INDEX32;
@@ -586,10 +640,19 @@ NativeProcessWindows::HandleSingleStepException(const ExceptionRecord &record) {
     }
   }
 #endif
-  if (wp_id == LLDB_INVALID_INDEX32)
+  if (wp_id == LLDB_INVALID_INDEX32) {
+    NativeThreadWindows *thread = GetThreadByID(record.GetThreadID());
+    if (thread && !thread->IsSingleStepping()) {
+      LLDB_LOG(GetLog(WindowsLog::Exception),
+               "ignoring a late single-step trap on thread {0:x}, which this "
+               "resume did not step",
+               record.GetThreadID());
+      return ExceptionResult::MaskException;
+    }
     StopThread(record.GetThreadID(), StopReason::eStopReasonTrace);
+  }
 
-  SetState(eStateStopped, true);
+  DeferStop();
   return ExceptionResult::MaskException;
 }
 
@@ -608,7 +671,7 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
       StopThread(thread_id, StopReason::eStopReasonBreakpoint);
       // The current PC is AFTER the BP opcode, on all architectures.
       reg_ctx.SetPC(reg_ctx.GetPC() - GetSoftwareBreakpointPCOffset());
-      SetState(eStateStopped, true);
+      DeferStop();
       return ExceptionResult::MaskException;
     }
 
@@ -635,10 +698,13 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
                     exception_addr)
                 .str();
         StopThread(thread_id, StopReason::eStopReasonWatchpoint, desc);
-        SetState(eStateStopped, true);
+        DeferStop();
         return ExceptionResult::MaskException;
       }
     }
+
+    if (RewindTrapOfRemovedBreakpoint(record))
+      return ExceptionResult::MaskException;
   }
 
   if (!m_initial_stop_seen) {
@@ -686,7 +752,7 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
     SetCurrentThreadID(thread_id);
     if (NativeThreadWindows *injected = GetThreadByID(thread_id))
       injected->SetStopReason(signal_info, "interrupt");
-    SetState(eStateStopped, true);
+    DeferStop();
     return ExceptionResult::BreakInDebugger;
   }
 
@@ -703,7 +769,7 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
                              record.GetExceptionValue(), exception_addr)
                          .str();
   StopThread(thread_id, StopReason::eStopReasonException, std::move(desc));
-  SetState(eStateStopped, true);
+  DeferStop();
   return ExceptionResult::MaskException;
 }
 
@@ -729,7 +795,7 @@ NativeProcessWindows::HandleGenericException(bool first_chance,
   StopThread(record.GetThreadID(), StopReason::eStopReasonException,
              std::move(desc));
 
-  SetState(eStateStopped, true);
+  DeferStop();
   return ExceptionResult::BreakInDebugger;
 }
 
@@ -762,6 +828,8 @@ NativeProcessWindows::OnDebugException(bool first_chance,
 
 void NativeProcessWindows::OnCreateThread(const HostThread &new_thread) {
   llvm::sys::ScopedLock lock(m_mutex);
+  const StateType state = GetState();
+  std::lock_guard<std::recursive_mutex> threads_guard(m_threads_mutex);
 
   auto thread = std::make_unique<NativeThreadWindows>(*this, new_thread);
   thread->GetRegisterContext().ClearAllHardwareWatchpoints();
@@ -771,8 +839,7 @@ void NativeProcessWindows::OnCreateThread(const HostThread &new_thread) {
                           wp.m_hardware);
   }
 
-  if (StateType state = GetState();
-      state == eStateStopped || state == eStateCrashed) {
+  if (state == eStateStopped || state == eStateCrashed) {
     if (Status error = thread->DoStop(); error.Fail()) {
       Log *log = GetLog(WindowsLog::Thread);
       LLDB_LOG(log, "failed to suspend newly-created thread {0}: {1}",
@@ -788,6 +855,7 @@ void NativeProcessWindows::OnCreateThread(const HostThread &new_thread) {
 
 void NativeProcessWindows::OnExitThread(lldb::tid_t thread_id,
                                         uint32_t exit_code) {
+  llvm::sys::ScopedLock lock(m_mutex);
   std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
   llvm::erase_if(m_threads, [thread_id](const auto &t) {
     return t->GetID() == thread_id;
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
index 114b4a200e18c..79046dd6846a4 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -140,6 +140,8 @@ class NativeProcessWindows : public NativeProcessProtocol,
                              lldb::tid_t thread_id) override;
   void OnDebugString(lldb::addr_t debug_string_addr, bool is_unicode,
                      uint16_t length_lower_word) override;
+  bool HasDeferredStop() const { return m_deferred_stop; }
+  void ReportDeferredStop();
 
 protected:
   NativeThreadWindows *GetThreadByID(lldb::tid_t thread_id);
@@ -167,6 +169,16 @@ class NativeProcessWindows : public NativeProcessProtocol,
   NativeProcessWindows(lldb::pid_t pid, int terminal_fd,
                        NativeDelegate &delegate, llvm::Error &E);
 
+  /// Stop the process without notifying the delegate yet.
+  void DeferStop();
+
+  /// A breakpoint trap whose debug event was only delivered after the client
+  /// removed that breakpoint: the thread executed the trap before the stop
+  /// that removed it, but its event was held back until it ran again. Moves
+  /// the PC back onto the original instruction. Returns false if \p record is
+  /// not such a trap.
+  bool RewindTrapOfRemovedBreakpoint(const ExceptionRecord &record);
+
   ExceptionResult HandleSingleStepException(const ExceptionRecord &record);
   ExceptionResult HandleBreakpointException(const ExceptionRecord &record);
   ExceptionResult HandleGenericException(bool first_chance,
@@ -189,6 +201,14 @@ class NativeProcessWindows : public NativeProcessProtocol,
   /// Set when Halt() / Interrupt() schedules a DebugBreakProcess injection.
   bool m_pending_halt = false;
 
+  /// Set by DeferStop(), cleared once the stop is reported or the process
+  /// exits.
+  bool m_deferred_stop = false;
+
+  /// The thread whose debug event started the deferred stop. Reported as the
+  /// stop's thread even if later events made another thread current.
+  lldb::tid_t m_deferred_stop_tid = LLDB_INVALID_THREAD_ID;
+
   bool m_client_supports_libraries_read = false;
 
   /// PseudoConsole for the lldb-server stdio-forwarding path.
@@ -257,6 +277,10 @@ class NativeDebugDelegate : public IDebugDelegate {
     return m_process.OnDebuggerError(error, type);
   }
 
+  bool HasDeferredStop() override { return m_process.HasDeferredStop(); }
+
+  void ReportDeferredStop() override { m_process.ReportDeferredStop(); }
+
 private:
   NativeProcessWindows &m_process;
 };
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
index 0842968f25d60..0c6761e1027c0 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
@@ -95,6 +95,7 @@ Status NativeThreadWindows::DoResume(lldb::StateType resume_state) {
 
     } while (previous_suspend_count > 1);
     m_state = eStateRunning;
+    m_single_stepping = resume_state == eStateStepping;
   }
 
   return Status();
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h
index 392a5b9a2a7d4..fca4095ac8888 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h
@@ -28,6 +28,9 @@ class NativeThreadWindows : public NativeThreadProtocol {
   Status DoStop();
   Status DoResume(lldb::StateType resume_state);
 
+  /// Whether the last DoResume() single-stepped this thread.
+  bool IsSingleStepping() const { return m_single_stepping; }
+
   std::string GetName() override;
 
   lldb::StateType GetState() override { return m_state; }
@@ -55,6 +58,7 @@ class NativeThreadWindows : public NativeThreadProtocol {
 protected:
   lldb::StateType m_state = lldb::StateType::eStateInvalid;
   std::string m_name;
+  bool m_single_stepping = false;
   std::unique_ptr<NativeRegisterContextWindows> m_reg_context_up;
   // Cache address and index of the watchpoints and hardware breakpoints since
   // the register context does not.
diff --git a/lldb/test/API/commands/thread/backtrace/TestThreadBacktraceRepeat.py b/lldb/test/API/commands/thread/backtrace/TestThreadBacktraceRepeat.py
index 50ed9dfe1b31c..7eb1f116a772b 100644
--- a/lldb/test/API/commands/thread/backtrace/TestThreadBacktraceRepeat.py
+++ b/lldb/test/API/commands/thread/backtrace/TestThreadBacktraceRepeat.py
@@ -128,8 +128,6 @@ def check_two_threads(
     # the comments whether it was getting two threads to the same breakpoint that was
     # problematic, or the step-out part.  This test stops at the rendevous point so I'm
     # removing the skipIfLinux to see if we see any flakiness in just this part of the test.
-    @skipIfWindows  # This test will hang on windows llvm.org/pr21753
-    @expectedFailureAll(oslist=["windows"])
     @expectedFailureNetBSD
     def test_thread_backtrace_two_threads(self):
         """Test that repeat works even when backtracing on more than one thread."""
diff --git a/lldb/test/API/commands/watchpoints/hello_watchlocation/TestWatchLocation.py b/lldb/test/API/commands/watchpoints/hello_watchlocation/TestWatchLocation.py
index bce56ce6e79e4..8986fd6fa88d2 100644
--- a/lldb/test/API/commands/watchpoints/hello_watchlocation/TestWatchLocation.py
+++ b/lldb/test/API/commands/watchpoints/hello_watchlocation/TestWatchLocation.py
@@ -43,7 +43,6 @@ def affected_by_radar_93863107(self):
     @expectedFailureAll(triple=re.compile("^mips"))
     # SystemZ and PowerPC also currently supports only one H/W watchpoint
     @expectedFailureAll(archs=["powerpc64le", "s390x"])
-    @skipIfWindows  # This test is flaky on Windows
     def test_hello_watchlocation(self):
         """Test watching a location with '-s size' option."""
         self.build(dictionary=self.d)
diff --git a/lldb/test/API/functionalities/thread/num_threads/TestNumThreads.py b/lldb/test/API/functionalities/thread/num_threads/TestNumThreads.py
index e6a8647f6b9cf..2787c751693e4 100644
--- a/lldb/test/API/functionalities/thread/num_threads/TestNumThreads.py
+++ b/lldb/test/API/functionalities/thread/num_threads/TestNumThreads.py
@@ -71,7 +71,6 @@ def test_number_of_threads(self):
         )
 
     @skipIfDarwin  # rdar://33462362
-    @skipIfWindows  # This is flakey on Windows: llvm.org/pr37658, llvm.org/pr38373
     def test_unique_stacks(self):
         """Test backtrace unique with multiple threads executing the same stack."""
         self.build()
@@ -124,11 +123,12 @@ def is_thread3(thread):
                 continue
 
             # If we aren't stopped out the thread breakpoint try to resume.
+            index_id = thread.GetIndexID()
             if thread.GetStopReason() != lldb.eStopReasonBreakpoint:
-                self.runCmd("thread continue %d" % (i + 1))
+                self.runCmd("thread continue %d" % index_id)
             self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint)
 
-            expect_threads += " #%d" % (i + 1)
+            expect_threads += " #%d" % index_id
 
         # Construct our expected back trace string
         expect_string = "10 thread(s)%s" % (expect_threads)
diff --git a/lldb/test/API/functionalities/thread/step_out/TestThreadStepOut.py b/lldb/test/API/functionalities/thread/step_out/TestThreadStepOut.py
index a7e7b43a398c5..3f6f62ba742d5 100644
--- a/lldb/test/API/functionalities/thread/step_out/TestThrea...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/227353


More information about the lldb-commits mailing list