[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 ®_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