[Lldb-commits] [lldb] 4a019b7 - [lldb][Windows] Ignore stale traps delivered after a resume (#228440)

via lldb-commits lldb-commits at lists.llvm.org
Fri Oct 9 03:54:06 PDT 2026


Author: Charles Zablit
Date: 2026-10-09T11:54:00+01:00
New Revision: 4a019b753fc6b14a6816ba9bafb61a4bf3a445ec

URL: https://github.com/llvm/llvm-project/commit/4a019b753fc6b14a6816ba9bafb61a4bf3a445ec
DIFF: https://github.com/llvm/llvm-project/commit/4a019b753fc6b14a6816ba9bafb61a4bf3a445ec.diff

LOG: [lldb][Windows] Ignore stale traps delivered after a resume (#228440)

A thread can hit a trap at the same time as the thread whose exception
caused the stop. Windows only delivers one debug event at a time, so the
second thread's event is not sent until the process is resumed. By then
the trap it reports can be stale:

1. A
[`STATUS_SINGLE_STEP`](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/specific-exceptions)
for a thread that the current resume did not step.
`NativeProcessWindows::HandleSingleStepException` reports it as a
`eStopReasonTrace` stop. `NativeThreadWindows` now records whether its
last `DoResume()` stepped it, and masks the trap which arrives too late.

2. A
[`STATUS_BREAKPOINT`](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/specific-exceptions)
for a breakpoint the client removed during the stop. The original byte
is back in memory, so `HandleBreakpointException` does not find a
breakpoint site and reports an exception stop, with the program counter
one byte past the instruction. `RewindTrapOfRemovedBreakpoint()` detects
this and moves the PC back onto the original instruction and masks the
exception.

This is 1/2 patches. The second one
(https://github.com/llvm/llvm-project/pull/227353) makes lldb-server
collect every queued exception into one stop, which hits these late
traps much more often, and re-enables the multi-threaded tests that
depend on both.

Added: 
    

Modified: 
    lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
    lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
    lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
    lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 14bd760670640..09986d3d1f7cc 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -118,6 +118,7 @@ Status NativeProcessWindows::Resume(const ResumeActionList &resume_actions) {
     bool failed = false;
     for (uint32_t i = 0; i < m_threads.size(); ++i) {
       auto thread = static_cast<NativeThreadWindows *>(m_threads[i].get());
+      thread->ClearSingleStepping();
       const ResumeAction *const action =
           resume_actions.GetActionForThread(thread->GetID(), true);
       if (action == nullptr)
@@ -582,6 +583,34 @@ void NativeProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) {
       *this, m_session_data->m_debugger->GetMainThread()));
 }
 
+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;
@@ -605,8 +634,17 @@ 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);
   return ExceptionResult::MaskException;
@@ -658,6 +696,9 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
         return ExceptionResult::MaskException;
       }
     }
+
+    if (RewindTrapOfRemovedBreakpoint(record))
+      return ExceptionResult::MaskException;
   }
 
   if (!m_initial_stop_seen) {

diff  --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
index 114b4a200e18c..e79596fecc9f8 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -167,6 +167,13 @@ class NativeProcessWindows : public NativeProcessProtocol,
   NativeProcessWindows(lldb::pid_t pid, int terminal_fd,
                        NativeDelegate &delegate, llvm::Error &E);
 
+  /// 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,

diff  --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
index b6363616cf31a..f7d2b06f0f58d 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 b867489a2fd28..536ace3384c72 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h
@@ -28,6 +28,11 @@ class NativeThreadWindows : public NativeThreadProtocol {
   Status DoStop();
   Status DoResume(lldb::StateType resume_state);
 
+  /// Whether the current resume single-steps this thread.
+  bool IsSingleStepping() const { return m_single_stepping; }
+
+  void ClearSingleStepping() { m_single_stepping = false; }
+
   std::string GetName() override;
 
   lldb::StateType GetState() override { return m_state; }
@@ -57,6 +62,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.


        


More information about the lldb-commits mailing list