[Lldb-commits] [lldb] [lldb][Windows] Identify the DebugBreakProcess break-in thread (PR #223985)

Charles Zablit via lldb-commits lldb-commits at lists.llvm.org
Wed Sep 16 04:44:13 PDT 2026


https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/223985

Windows has no `SIGSTOP`, so `HaltProcess` interrupts a debuggee with `DebugBreakProcess`, which injects a thread running `ntdll!DbgUiRemoteBreakin` that fires one `int3`. It arrives as a bare `EXCEPTION_BREAKPOINT`, with no data to distinguish with a "real breakpoint". As of #208233, both Windows plugins carried an `m_pending_halt` flag, set in `DoHalt` and `Halt` to track if the next breakpoint would be a "real breakpoint" or processed breakpoint.

This patch improves this implementation by not relying on custom state so much. We know that `CREATE_THREAD_DEBUG_EVENT` reports the injected thread's entry point, and ntdll sits at the same address in every same-arch process per boot. This patch therefore matches on that address to detect if the thread is injected or not.

rdar://182930148

>From eaeb9312ea5a68fe901798164b2fa6a9716e34c6 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 16 Sep 2026 12:11:13 +0100
Subject: [PATCH] [lldb][Windows] Identify the DebugBreakProcess break-in
 thread

---
 .../Process/Windows/Common/DebuggerThread.cpp |  3 +-
 .../Process/Windows/Common/IDebugDelegate.h   |  3 +-
 .../Windows/Common/LocalDebugDelegate.cpp     |  5 +-
 .../Windows/Common/LocalDebugDelegate.h       |  3 +-
 .../Windows/Common/NativeProcessWindows.cpp   | 22 +++----
 .../Windows/Common/NativeProcessWindows.h     | 11 ++--
 .../Windows/Common/ProcessDebugger.cpp        | 50 +++++++++++++++-
 .../Process/Windows/Common/ProcessDebugger.h  | 13 ++++-
 .../Process/Windows/Common/ProcessWindows.cpp | 28 ++++-----
 .../Process/Windows/Common/ProcessWindows.h   |  4 +-
 lldb/test/API/windows/interrupt/Makefile      |  3 +
 .../windows/interrupt/TestWindowsInterrupt.py | 57 +++++++++++++++++++
 lldb/test/API/windows/interrupt/main.c        | 11 ++++
 13 files changed, 172 insertions(+), 41 deletions(-)
 create mode 100644 lldb/test/API/windows/interrupt/Makefile
 create mode 100644 lldb/test/API/windows/interrupt/TestWindowsInterrupt.py
 create mode 100644 lldb/test/API/windows/interrupt/main.c

diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
index 260ac0fe0faf6..4ba7fdd074a24 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
@@ -449,7 +449,8 @@ DebuggerThread::HandleCreateThreadEvent(const CREATE_THREAD_DEBUG_INFO &info,
            m_process.GetProcessId());
   HostThread thread(info.hThread);
   thread.GetNativeThread().SetOwnsHandle(false);
-  m_debug_delegate->OnCreateThread(thread);
+  m_debug_delegate->OnCreateThread(
+      thread, reinterpret_cast<lldb::addr_t>(info.lpStartAddress));
   return DBG_CONTINUE;
 }
 
diff --git a/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h b/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
index cecd58a15f9c2..0a53cf61f0c8f 100644
--- a/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
+++ b/lldb/source/Plugins/Process/Windows/Common/IDebugDelegate.h
@@ -30,7 +30,8 @@ class IDebugDelegate {
   virtual void OnDebuggerConnected(lldb::addr_t image_base) = 0;
   virtual ExceptionResult OnDebugException(bool first_chance,
                                            const ExceptionRecord &record) = 0;
-  virtual void OnCreateThread(const HostThread &thread) = 0;
+  virtual void OnCreateThread(const HostThread &thread,
+                              lldb::addr_t start_address) = 0;
   virtual void OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) = 0;
   virtual DllEventAction OnLoadDll(const ModuleSpec &module_spec,
                                    lldb::addr_t module_addr,
diff --git a/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp b/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp
index bbdc44e008331..20c023f0d6c47 100644
--- a/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.cpp
@@ -34,9 +34,10 @@ LocalDebugDelegate::OnDebugException(bool first_chance,
     return ExceptionResult::MaskException;
 }
 
-void LocalDebugDelegate::OnCreateThread(const HostThread &thread) {
+void LocalDebugDelegate::OnCreateThread(const HostThread &thread,
+                                        lldb::addr_t start_address) {
   if (ProcessWindowsSP process = GetProcessPointer())
-    process->OnCreateThread(thread);
+    process->OnCreateThread(thread, start_address);
 }
 
 void LocalDebugDelegate::OnExitThread(lldb::tid_t thread_id,
diff --git a/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h b/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h
index f623d4e7d5436..d1c46f16258f1 100644
--- a/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h
+++ b/lldb/source/Plugins/Process/Windows/Common/LocalDebugDelegate.h
@@ -46,7 +46,8 @@ class LocalDebugDelegate : public IDebugDelegate {
   void OnDebuggerConnected(lldb::addr_t image_base) override;
   ExceptionResult OnDebugException(bool first_chance,
                                    const ExceptionRecord &record) override;
-  void OnCreateThread(const HostThread &thread) override;
+  void OnCreateThread(const HostThread &thread,
+                      lldb::addr_t start_address) override;
   void OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) override;
   DllEventAction OnLoadDll(const lldb_private::ModuleSpec &module_spec,
                            lldb::addr_t module_addr,
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 0fa3eae2c7ed6..4808ebbe670da 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -180,13 +180,8 @@ NativeProcessWindows::GetThreadByID(lldb::tid_t thread_id) {
 Status NativeProcessWindows::Halt() {
   bool caused_stop = false;
   StateType state = GetState();
-  if (state != eStateStopped) {
-    m_pending_halt = true;
-    Status err = HaltProcess(caused_stop);
-    if (err.Fail() || !caused_stop)
-      m_pending_halt = false;
-    return err;
-  }
+  if (state != eStateStopped)
+    return HaltProcess(caused_stop);
   return Status();
 }
 
@@ -599,13 +594,13 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
   }
 
   // Our own DebugBreakProcess() injection, used to implement
-  // Halt()/Interrupt().
-  if (m_pending_halt) {
+  // Halt()/Interrupt(). The int3 runs on a thread the OS created for us, which
+  // is what tells it apart from an int3 the inferior itself executed.
+  if (IsBreakInThread(thread_id)) {
     LLDB_LOG(log,
              "DebugBreakProcess injection treated as Halt SIGSTOP for tid "
              "{0:x}",
              thread_id);
-    m_pending_halt = false;
     ThreadStopInfo signal_info;
     signal_info.reason = StopReason::eStopReasonSignal;
     signal_info.signo = 19; // SIGSTOP on POSIX
@@ -696,7 +691,10 @@ NativeProcessWindows::OnDebugException(bool first_chance,
   return result;
 }
 
-void NativeProcessWindows::OnCreateThread(const HostThread &new_thread) {
+void NativeProcessWindows::OnCreateThread(const HostThread &new_thread,
+                                          lldb::addr_t start_address) {
+  ProcessDebugger::OnCreateThread(new_thread, start_address);
+
   llvm::sys::ScopedLock lock(m_mutex);
 
   auto thread = std::make_unique<NativeThreadWindows>(*this, new_thread);
@@ -724,6 +722,8 @@ void NativeProcessWindows::OnCreateThread(const HostThread &new_thread) {
 
 void NativeProcessWindows::OnExitThread(lldb::tid_t thread_id,
                                         uint32_t exit_code) {
+  ProcessDebugger::OnExitThread(thread_id, exit_code);
+
   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..06e6787c8a748 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -131,7 +131,8 @@ class NativeProcessWindows : public NativeProcessProtocol,
   void OnDebuggerConnected(lldb::addr_t image_base) override;
   ExceptionResult OnDebugException(bool first_chance,
                                    const ExceptionRecord &record) override;
-  void OnCreateThread(const HostThread &thread) override;
+  void OnCreateThread(const HostThread &thread,
+                      lldb::addr_t start_address) override;
   void OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) override;
   DllEventAction OnLoadDll(const ModuleSpec &module_spec,
                            lldb::addr_t module_addr,
@@ -186,9 +187,6 @@ class NativeProcessWindows : public NativeProcessProtocol,
 
   bool m_expecting_loader_int3 = false;
 
-  /// Set when Halt() / Interrupt() schedules a DebugBreakProcess injection.
-  bool m_pending_halt = false;
-
   bool m_client_supports_libraries_read = false;
 
   /// PseudoConsole for the lldb-server stdio-forwarding path.
@@ -229,8 +227,9 @@ class NativeDebugDelegate : public IDebugDelegate {
     return m_process.OnDebugException(first_chance, record);
   }
 
-  void OnCreateThread(const HostThread &thread) override {
-    m_process.OnCreateThread(thread);
+  void OnCreateThread(const HostThread &thread,
+                      lldb::addr_t start_address) override {
+    m_process.OnCreateThread(thread, start_address);
   }
 
   void OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) override {
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
index 51d47751ed05d..2c53a89db5cf0 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
@@ -14,6 +14,7 @@
 
 #include "lldb/Host/FileSystem.h"
 #include "lldb/Host/HostNativeProcessBase.h"
+#include "lldb/Host/HostNativeThread.h"
 #include "lldb/Host/HostProcess.h"
 #include "lldb/Host/HostThread.h"
 #include "lldb/Host/ProcessLaunchInfo.h"
@@ -33,6 +34,25 @@
 using namespace lldb;
 using namespace lldb_private;
 
+/// Entry point of the thread DebugBreakProcess() injects into a target, i.e.
+/// ntdll!DbgUiRemoteBreakin. ntdll is mapped at the same address in every
+/// process of a given architecture for the lifetime of a boot, so the address
+/// resolved here is also the address the inferior reports in its
+/// CREATE_THREAD_DEBUG_EVENT. Returns LLDB_INVALID_ADDRESS if the export
+/// cannot be resolved.
+static lldb::addr_t GetBreakInThreadStartAddress() {
+  static const lldb::addr_t g_address = []() -> lldb::addr_t {
+    HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
+    if (!ntdll)
+      return LLDB_INVALID_ADDRESS;
+    FARPROC break_in = ::GetProcAddress(ntdll, "DbgUiRemoteBreakin");
+    if (!break_in)
+      return LLDB_INVALID_ADDRESS;
+    return reinterpret_cast<lldb::addr_t>(reinterpret_cast<void *>(break_in));
+  }();
+  return g_address;
+}
+
 static void NormalizeWindowsPathSeparators(std::string &s) {
   for (char &c : s)
     if (c == '/')
@@ -597,12 +617,36 @@ ProcessDebugger::OnDebugException(bool first_chance,
   return result;
 }
 
-void ProcessDebugger::OnCreateThread(const HostThread &thread) {
-  // Do nothing by default
+void ProcessDebugger::OnCreateThread(const HostThread &thread,
+                                     lldb::addr_t start_address) {
+  llvm::sys::ScopedLock lock(m_mutex);
+  if (!m_session_data)
+    return;
+
+  const lldb::addr_t break_in_start = GetBreakInThreadStartAddress();
+  if (break_in_start == LLDB_INVALID_ADDRESS || start_address != break_in_start)
+    return;
+
+  lldb::tid_t thread_id = thread.GetNativeThread().GetThreadId();
+  LLDB_LOG(GetLog(WindowsLog::Thread),
+           "thread {0} starts at ntdll!DbgUiRemoteBreakin, tracking it as a "
+           "break-in thread",
+           thread_id);
+  m_session_data->m_break_in_threads.insert(thread_id);
 }
 
 void ProcessDebugger::OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) {
-  // Do nothing by default
+  llvm::sys::ScopedLock lock(m_mutex);
+  // Windows reuses thread IDs, so this has to be dropped as soon as the thread
+  // is gone or a later thread inheriting the ID would be mistaken for it.
+  if (m_session_data)
+    m_session_data->m_break_in_threads.erase(thread_id);
+}
+
+bool ProcessDebugger::IsBreakInThread(lldb::tid_t thread_id) {
+  llvm::sys::ScopedLock lock(m_mutex);
+  return m_session_data &&
+         m_session_data->m_break_in_threads.count(thread_id) > 0;
 }
 
 DllEventAction ProcessDebugger::OnLoadDll(const ModuleSpec &module_spec,
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
index 99b59b1918a4f..60e8ae5f05014 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
@@ -47,6 +47,9 @@ class ProcessWindowsData {
   bool m_stop_at_entry;
   std::map<lldb::tid_t, lldb::ThreadSP> m_new_threads;
   std::set<lldb::tid_t> m_exited_threads;
+  /// TIDs of the threads DebugBreakProcess() injects into the inferior. They
+  /// start at ntdll!DbgUiRemoteBreakin and exist only to run one int3.
+  std::set<lldb::tid_t> m_break_in_threads;
 };
 
 class ProcessDebugger {
@@ -58,7 +61,8 @@ class ProcessDebugger {
   virtual void OnDebuggerConnected(lldb::addr_t image_base);
   virtual ExceptionResult OnDebugException(bool first_chance,
                                            const ExceptionRecord &record);
-  virtual void OnCreateThread(const HostThread &thread);
+  virtual void OnCreateThread(const HostThread &thread,
+                              lldb::addr_t start_address);
   virtual void OnExitThread(lldb::tid_t thread_id, uint32_t exit_code);
   virtual DllEventAction OnLoadDll(const ModuleSpec &module_spec,
                                    lldb::addr_t module_addr,
@@ -73,6 +77,13 @@ class ProcessDebugger {
 
   bool IsSystemModuleAddress(lldb::addr_t addr);
 
+  /// Whether `thread_id` is a break-in thread: one the OS injected into the
+  /// inferior on our behalf when HaltProcess() called DebugBreakProcess().
+  /// Such a thread runs ntdll!DbgUiRemoteBreakin, whose only job is to execute
+  /// an int3, so the EXCEPTION_BREAKPOINT it raises is the debugger's own
+  /// interrupt rather than anything the inferior did.
+  bool IsBreakInThread(lldb::tid_t thread_id);
+
 protected:
   Status DetachProcess();
 
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index d72856bdc8d2c..16c08d7e503ec 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -301,12 +301,8 @@ Status ProcessWindows::DoDestroy() {
 
 Status ProcessWindows::DoHalt(bool &caused_stop) {
   StateType state = GetPrivateState();
-  if (state != eStateStopped) {
-    m_pending_halt = true;
-    Status error = HaltProcess(caused_stop);
-    if (error.Fail() || !caused_stop)
-      m_pending_halt = false;
-  }
+  if (state != eStateStopped)
+    return HaltProcess(caused_stop);
   caused_stop = false;
   return Status();
 }
@@ -722,12 +718,13 @@ ProcessWindows::OnDebugException(bool first_chance,
   switch (record.GetExceptionValue()) {
   case EXCEPTION_BREAKPOINT: {
     const lldb::addr_t bp_addr = record.GetExceptionAddress();
-    if (m_pending_halt) {
-      m_pending_halt = false;
-    } else if (m_expecting_loader_int3 && first_chance &&
-               m_session_data->m_initial_stop_received &&
-               !GetBreakpointSiteList().FindByAddress(bp_addr) &&
-               IsSystemModuleAddress(bp_addr)) {
+    // A break-in thread's int3 is the one we asked DebugBreakProcess() for, so
+    // it must reach the generic breakpoint handling below even though it lands
+    // in ntdll.
+    if (!IsBreakInThread(record.GetThreadID()) && m_expecting_loader_int3 &&
+        first_chance && m_session_data->m_initial_stop_received &&
+        !GetBreakpointSiteList().FindByAddress(bp_addr) &&
+        IsSystemModuleAddress(bp_addr)) {
       m_expecting_loader_int3 = false;
       LLDB_LOG(log,
                "Skipping expected loader breakpoint at address {0:x} in a "
@@ -781,7 +778,10 @@ ProcessWindows::OnDebugException(bool first_chance,
   return result;
 }
 
-void ProcessWindows::OnCreateThread(const HostThread &new_thread) {
+void ProcessWindows::OnCreateThread(const HostThread &new_thread,
+                                    lldb::addr_t start_address) {
+  ProcessDebugger::OnCreateThread(new_thread, start_address);
+
   llvm::sys::ScopedLock lock(m_mutex);
 
   ThreadSP thread = std::make_shared<TargetThreadWindows>(*this, new_thread);
@@ -802,6 +802,8 @@ void ProcessWindows::OnCreateThread(const HostThread &new_thread) {
 }
 
 void ProcessWindows::OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) {
+  ProcessDebugger::OnExitThread(thread_id, exit_code);
+
   llvm::sys::ScopedLock lock(m_mutex);
 
   // On a forced termination, we may get exit thread events after the session
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index 73fd92f11cdff..38e16ddc69e9b 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -86,7 +86,8 @@ class ProcessWindows : public Process, public ProcessDebugger {
   void OnDebuggerConnected(lldb::addr_t image_base) override;
   ExceptionResult OnDebugException(bool first_chance,
                                    const ExceptionRecord &record) override;
-  void OnCreateThread(const HostThread &thread) override;
+  void OnCreateThread(const HostThread &thread,
+                      lldb::addr_t start_address) override;
   void OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) override;
   DllEventAction OnLoadDll(const ModuleSpec &module_spec,
                            lldb::addr_t module_addr,
@@ -133,7 +134,6 @@ class ProcessWindows : public Process, public ProcessDebugger {
   std::map<lldb::break_id_t, WatchpointInfo> m_watchpoints;
   std::vector<lldb::break_id_t> m_watchpoint_ids;
   std::shared_ptr<PTY> m_pty;
-  bool m_pending_halt = false;
   bool m_expecting_loader_int3 = false;
 };
 } // namespace lldb_private
diff --git a/lldb/test/API/windows/interrupt/Makefile b/lldb/test/API/windows/interrupt/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/windows/interrupt/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/windows/interrupt/TestWindowsInterrupt.py b/lldb/test/API/windows/interrupt/TestWindowsInterrupt.py
new file mode 100644
index 0000000000000..5144034dec267
--- /dev/null
+++ b/lldb/test/API/windows/interrupt/TestWindowsInterrupt.py
@@ -0,0 +1,57 @@
+"""
+Test that interrupting a running process on Windows is reported as an
+interrupt rather than as an exception.
+
+Windows has no SIGSTOP, so a halt is implemented with DebugBreakProcess(),
+which injects a thread into the inferior that runs ntdll!DbgUiRemoteBreakin
+and executes an int3. lldb tells that int3 apart from one the inferior itself
+executed by the injected thread's entry point; if that stops working, the
+interrupt surfaces as a bare 0x80000003 exception instead.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+ at requireWindows
+class WindowsInterruptTestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_interrupt_is_not_reported_as_an_exception(self):
+        self.build()
+        (target, process, _, _) = lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.c")
+        )
+
+        self.setAsync(True)
+        listener = self.dbg.GetListener()
+
+        # Interrupt more than once: the injected thread has to be recognized
+        # every time, not just for the first halt.
+        for i in range(3):
+            process.Continue()
+            lldbutil.expect_state_changes(self, listener, process, [lldb.eStateRunning])
+
+            self.assertSuccess(process.Stop(), "interrupt #%d" % i)
+            lldbutil.expect_state_changes(self, listener, process, [lldb.eStateStopped])
+
+            for thread in process:
+                self.assertNotEqual(
+                    thread.GetStopReason(),
+                    lldb.eStopReasonException,
+                    "interrupt #%d reported as an exception on thread %d: %s"
+                    % (i, thread.GetThreadID(), thread.GetStopDescription(256)),
+                )
+
+        # The inferior must still run to completion once the loop is let go,
+        # i.e. the injected threads did not leave the process wedged.
+        self.setAsync(False)
+        keep_running = target.FindFirstGlobalVariable("keep_running")
+        self.assertTrue(keep_running.IsValid(), "found the loop's exit condition")
+        self.assertTrue(keep_running.SetValueFromCString("0"), "cleared keep_running")
+
+        process.Continue()
+        self.assertState(process.GetState(), lldb.eStateExited)
+        self.assertEqual(process.GetExitStatus(), 0)
diff --git a/lldb/test/API/windows/interrupt/main.c b/lldb/test/API/windows/interrupt/main.c
new file mode 100644
index 0000000000000..15d735d4297ae
--- /dev/null
+++ b/lldb/test/API/windows/interrupt/main.c
@@ -0,0 +1,11 @@
+#include <stdio.h>
+
+volatile int keep_running = 1;
+
+int main(int argc, char *argv[]) {
+  puts("running"); // break here
+  fflush(stdout);
+  while (keep_running)
+    ;
+  return 0;
+}



More information about the lldb-commits mailing list